Elicitation
Makine çevirisi
Bu sayfa İngilizce dokümantasyondan otomatik olarak çevrildi; esas alınması gereken sürüm İngilizce sayfadır. Yanlış görünen bir şey varsa, nasıl bildireceğinizi Çeviriler sayfası açıklar.
İşinin yarısına gelmiş ve tek bir yanıtı eksik olan bir aracın başarısız olması gerekmez.
Elicitation (kullanıcıdan bilgi isteme) onun sormasını sağlar. Araç çağrısının ortasında kullanıcıya bir soru gelir ve verdiği yanıt aynı fonksiyon çağrısına geri döner.
İki mod var:
- Form modu: bir değere ihtiyacınız vardır (bir onay, bir tarih, bir miktar). Alanları siz tanımlarsınız, formu istemci çizer.
- URL modu: kullanıcının başka bir yere gitmesi gerekir (bir OAuth onay ekranı, bir ödeme sayfası). Orada yaptığı hiçbir şey protokolden geçmez.
Sormanın da iki yolu var. İlk başvurmanız gereken bir çözümleyicidir: soruyu bir parametreye asarsınız ve SDK sorar; hangi bağlantı olursa olsun, istemci hangi protokol neslini konuşursa konuşsun. Doğrudan yol olan await ctx.elicit(...), sunucudan istemciye giden bir istektir; bu kanal yalnızca eski nesil bir bağlantıdaki (spesifikasyon sürümü 2025-11-25 veya öncesi) istemciler için vardır. İkisi de bu sayfada; çözümleyiciyle başlayın.
Çözümleyiciyle sorma
Aracın tamamının önünde duran bir soru (emin misiniz? eşleşen üç hesaptan hangisi?) araç gövdesinden çıkarılıp bir çözümleyiciye taşınabilir; soruyu sizin yerinize framework sorar.
Annotated[T, Resolve(fn)] ile işaretlenmiş bir parametre, araç gövdesinden önce fn çalıştırılarak doldurulur. Çözümleyici değeri zaten biliyorsa doğrudan döndürür; framework'ün sormasını istiyorsa Elicit(...) döndürür:
from typing import Annotated
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver import (
AcceptedElicitation,
CancelledElicitation,
DeclinedElicitation,
Elicit,
ElicitationResult,
Resolve,
)
mcp = MCPServer("Files")
_FOLDERS: dict[str, list[str]] = {"/tmp/empty": [], "/tmp/project": ["main.py", "README.md"]}
class Confirm(BaseModel):
ok: bool
async def confirm_delete(path: str) -> Confirm | Elicit[Confirm]:
"""Resolver: ask for confirmation only when the folder is not empty."""
file_count = len(_FOLDERS.get(path, []))
if file_count == 0:
return Confirm(ok=True) # nothing to confirm, no round-trip to the client
return Elicit(f"{path} has {file_count} file(s). Delete anyway?", Confirm)
@mcp.tool()
async def delete_folder(
path: str,
confirm: Annotated[ElicitationResult[Confirm], Resolve(confirm_delete)],
) -> str:
"""Delete a folder, asking for confirmation when it is not empty."""
match confirm:
case AcceptedElicitation(data=Confirm(ok=True)):
_FOLDERS.pop(path, None)
return f"deleted {path}"
case AcceptedElicitation():
return "kept the folder"
case DeclinedElicitation():
return "declined: folder not deleted"
case CancelledElicitation():
return "cancelled: folder not deleted"
confirm_delete, aracın kendipathargümanını adıyla okur, klasörü listeler ve yalnızca gerektiğinde sorar: boş bir klasör, istemciye hiç gidip dönmedenConfirm(ok=True)olarak çözümlenir.delete_folder,ElicitationResult[Confirm]tür ipucunu kullanır; bu yüzden framework sonucun tamamını enjekte eder ve araç her durumumatchile ele alır: kabul edip onaylama, kabul edip tutma (ok=False), reddetme, iptal.confirmparametresi aracın girdi şemasında hiç görünmez:path'i istemci sağlar,confirm'ü çözümleyici.
Aracın dallanması gerekmiyorsa bunun yerine sarmalanmamış modeli işaretleyin (Annotated[Confirm, Resolve(confirm_delete)]): kabulde modeli alır, ret veya iptalde ise çağrı bir hatayla sonlanır.
Çözümleyici her bağlantıda çalışır. Eski nesil bağlantıdaki bir istemciye SDK soruyu doğrudan gönderir; 2026-07-28 bağlantısında ise SDK soruyu çağrıdan döndürür ve istemcinin bir sonraki denemesi yanıtı taşır. Çözümleyiciniz aradaki farkı hiçbir zaman bilmez; arka planda olan biten Çok turlu istekler (multi-round-trip) sayfasında.
Sormak, bir çözümleyicinin yapabileceklerinden yalnızca biri. Genel mekanizma (sormadan hesaplayan bağımlılıklar, bağımlılıkların bağımlılıkları, modelin neyi sağlayıp neyi sağlayamayacağı) Bağımlılıklar sayfasında.
Aracın içinden sorma
Bir araç kendi gövdesinin ortasında durup da sorabilir.
Warning
ctx.elicit() ve ctx.elicit_url(), sunucudan istemciye giden isteklerdir; bu kanal
yalnızca eski nesil bir bağlantıdaki (spesifikasyon sürümü 2025-11-25 veya öncesi)
istemciler için vardır. 2026-07-28 bağlantısında sunucunun başlattığı istek yoktur,
bu yüzden bu çağrılar başarısız olur. Çözümleyici ikisinde de çalışır. Ayrıntıların tamamı
Protokol sürümleri sayfasında.
await ctx.elicit() bir mesaj ve bir Pydantic modeli alır:
from pydantic import BaseModel, Field
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Bistro")
class AlternativeDate(BaseModel):
accept_alternative: bool = Field(description="Try another date?")
date: str = Field(default="2025-12-26", description="Alternative date (YYYY-MM-DD)")
@mcp.tool()
async def book_table(date: str, party_size: int, ctx: Context) -> str:
"""Book a table at the bistro."""
if date != "2025-12-25":
return f"Booked a table for {party_size} on {date}."
result = await ctx.elicit(
message=f"No tables for {party_size} on {date}. Would you like to try another date?",
schema=AlternativeDate,
)
if result.action == "accept" and result.data.accept_alternative:
return await book_table(result.data.date, party_size, ctx)
return "No booking made."
- Size
ctx.elicit'i verenContextparametresidir; her araç bir tane alabilir. Bu nesnenin kendi sayfası var: Context nesnesi. AlternativeDate, istediğiniz yanıtın şemasıdır.- Araç
async def. Öyle olmak zorunda: ortada durup bir insanı bekler. - Başka herhangi bir tarihte araç hemen döner. Yalnızca mecbur kaldığında sorar.
- Kullanıcının kabul ettiği tarih yine
book_table'ın kendisinden geçer. Yanıt da diğerleri gibi bir girdidir: kendisi de tamamen dolu olan bir alternatif körlemesine onaylanmaz, yeniden sorulur.
İstemcinin aldığı
İstemci mesajınızı ve yanında modelden üretilmiş bir JSON Schema alır:
{
"properties": {
"accept_alternative": {
"description": "Try another date?",
"title": "Accept Alternative",
"type": "boolean"
},
"date": {
"default": "2025-12-26",
"description": "Alternative date (YYYY-MM-DD)",
"title": "Date",
"type": "string"
}
},
"required": ["accept_alternative"],
"title": "AlternativeDate",
"type": "object"
}
Bu şema formun kendisidir. Field(description=...) etikettir; bir varsayılan değer girdiyi önceden doldurur ve alanı isteğe bağlı yapar. Bu, Araçlar sayfasının bir aracın argümanları için anlattığı Pydantic'ten JSON Schema'ya dönüşüm mekanizmasının aynısıdır.
Warning
Bir elicitation şeması, bir aracın girdi şeması kadar ifade gücüne sahip değildir. Yalnızca
düz, ilkel alanlar: str, int, float, bool veya dizelerden oluşan bir Literal
(bir enum'a dönüşür). Modelin içine bir model koyarsanız ctx.elicit, istemciye hiçbir
şey gönderilmeden önce istisna fırlatır:
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
Bir insanı işinin ortasında bölüyorsunuz. Yanıt iç içe yapı gerektiriyorsa, o zaten aracın bir argümanı olmalıydı.
Üç yanıt
result.action kullanıcının ne yaptığını söyler ve tam olarak üç olasılık vardır:
"accept": formu gönderdi.result.data, zaten doğrulanmış birAlternativeDateörneğidir."decline": hayır dedi."cancel": seçim yapmadan soruyu kapattı.
result.data yalnızca "accept" durumunda vardır; örneğin önce result.action'ı denetlemesinin nedeni budur. Tür denetleyiciniz bu sırayı zorunlu kılar: result.action == "accept" sonrasında result.data bir AlternativeDate'tir; öncesinde .data diye bir şey hiç yoktur.
Ret bir hata değildir. Reddetmenin ne anlama geldiğine araç karar verir (burada: rezervasyon yok) ve modele normal şekilde yanıt verir.
Tip
Yanıt, kodunuz görmeden önce modelinize göre doğrulanır. Bir bool için "maybe" gönderen
bir istemci rezervasyonunuzu bozmaz: çağrı bir şema uyuşmazlığı hatasıyla başarısız olur,
if'iniz hiç çalışmaz.
Kullanıcıyı bir URL'ye gönderme
Bazı şeyler modelden veya istemciden geçmemelidir: kimlik bilgileri, kart numaraları, OAuth onayı. Bunlar için veri istemezsiniz; kullanıcıdan bir yere gitmesini istersiniz:
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Bistro")
@mcp.tool()
async def pay_deposit(booking_id: str, ctx: Context) -> str:
"""Take the deposit that confirms a booking."""
result = await ctx.elicit_url(
message="A 20 EUR deposit confirms your booking.",
url=f"https://pay.example.com/deposit/{booking_id}",
elicitation_id=f"deposit-{booking_id}",
)
if result.action == "accept":
return "Complete the payment in your browser."
return "No deposit taken. The booking expires in one hour."
@mcp.tool()
async def confirm_deposit(booking_id: str, ctx: Context) -> str:
"""Record a payment reported by the payment provider."""
await ctx.session.send_elicit_complete(f"deposit-{booking_id}")
return f"Deposit received for booking {booking_id}."
ctx.elicit_url(); mesajı, ziyaret edilecek URL'yi ve sizin seçtiğiniz birelicitation_id'yi alır: sunucunuz içinde bu elicitation'ı tanımlayan herhangi bir dize.- Sonuçta bir eylem vardır, başka hiçbir şey yoktur.
"accept", kullanıcının URL'yi açmayı kabul ettiği anlamına gelir; öbür taraftaki işi bitirdiği anlamına gelmez. - Ödeme bant dışında, kullanıcının tarayıcısı ile ödeme sağlayıcınız arasında gerçekleşir. MCP üzerinden hiçbir içerik geri gelmez.
İkinci araca bakın. Sunucunuz bant dışı akışın bittiğini öğrendiğinde (bir webhook, bir yoklama; burada ikinci bir araç olarak modellenmiş), ctx.session.send_elicit_complete(...) aynı elicitation_id ile notifications/elicitation/complete gönderir. İstemci, "ödeme bekleniyor..." göstermeyi bırakabileceğini böyle anlar. Bu olmadan istemci yalnızca tahmin yürütebilir.
İstemci tarafı
Sunucular sorar. İstemciler Client(...)'a bir elicitation_callback geçirerek yanıtlar:
from mcp import Client
from mcp.client import ClientRequestContext
from mcp.types import ElicitRequestParams, ElicitRequestURLParams, ElicitResult
async def handle_elicitation(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult:
if isinstance(params, ElicitRequestURLParams):
print(f"Open this link to continue: {params.url}")
return ElicitResult(action="accept")
print(params.message)
return ElicitResult(action="accept", content={"accept_alternative": True, "date": "2025-12-27"})
async def main() -> None:
async with Client(
"http://127.0.0.1:8000/mcp",
mode="legacy",
elicitation_callback=handle_elicitation,
) as client:
result = await client.call_tool("book_table", {"date": "2025-12-25", "party_size": 2})
print(result.content)
- Tek bir callback iki modu da ele alır.
params,ElicitRequestFormParamsileElicitRequestURLParams'ın bir birleşimidir; dallanmaisinstanceile yapılır. - URL için
params.url'yi kullanıcıya gösterir ve seçtiği eylemi döndürürsünüz. Aslacontentyok. - Form için gerçek bir uygulama
params.requested_schema'yı çizer ve kullanıcının girdisinicontentolarak döndürür. Buradaki ise hazır bir yanıtla her zaman evet der; bir testte tam da istediğiniz callback budur. - Callback'i geçirmek aynı zamanda yetenek bildirimidir: sunucu bu istemciye soru sorulabileceğini böyle öğrenir. Bir istemcinin sunucu adına yanıtlayabileceği diğer şeyler İstemci callback'leri sayfasında.
Info
Elicitation sunucudan istemciye giden bir istektir ve bunlar yalnızca klasik
el sıkışmalı bir oturumda vardır; bu istemcinin mode="legacy" geçirmesinin nedeni budur.
2026-07-28 bağlantısında bir araç bunun yerine soruyu çağrıdan döndürerek sorar;
o akış Çok turlu istekler sayfasında.
Deneyin
Form modundaki ctx.elicit kullanan server.py dosyasını (book_table olanı) Streamable HTTP üzerinde başlatın (tek satırlık komut Sunucunuzu çalıştırma sayfasında), ardından istemcinin main() fonksiyonunu çalıştırın ve book_table'dan Noel günü için rezervasyon isteyin.
Callback kendisine gönderilen soruyu yazdırır:
No tables for 2 on 2025-12-25. Would you like to try another date?
{"accept_alternative": True, "date": "2025-12-27"} ile yanıt verir ve bunca zamandır await ctx.elicit(...) içinde bekleyen araç rezervasyonu tamamlar:
Booked a table for 2 on 2025-12-27.
Şimdi URL modundaki server.py dosyasına geçin ve aynı main()'i pay_deposit'e yöneltin: aynı callback diğer dala girer, ödeme bağlantısını yazdırır ve araç "Complete the payment in your browser." ile geri döner. Çağrının ortasında, iki yönde de tek bir tur.
Check
Şimdi Client'tan elicitation_callback= parametresini kaldırın ve book_table'ı Noel günü
için yeniden çağırın. Çağrının tamamı bir protokol hatasıyla başarısız olur:
Elicitation not supported
Hiç callback kaydetmemiş bir istemci elicitation yeteneğini hiç bildirmemiştir, dolayısıyla
soracak kimse yoktur. Aracınız "decline" almadı; bir istisna aldı. Buna göre tasarlayın:
her elicitation'ın "ya soramazsam?" sorusuna mantıklı bir yanıtı olmalı.
Özet
Annotated[T, Resolve(fn)]ile işaretlenmiş bir parametreyi bir çözümleyici doldurur; çözümleyici sorması gerektiğindeElicit(...)döndürür. Her bağlantıda çalışır.- Şema düz bir Pydantic modelidir: yalnızca ilkel alanlar, dönüşte doğrulanır.
result.action;"accept","decline"veya"cancel"olur;result.datayalnızca kabulde vardır.await ctx.elicit(message, schema=Model)araç gövdesinin içinden sorar;await ctx.elicit_url(message, url, elicitation_id)ise modelden geçmemesi gereken her şey içindir (ctx.session.send_elicit_complete(elicitation_id)bant dışı kısmın bittiğini söyler). İkisi de sunucudan istemciye giden isteklerdir: istemcinin eski nesil bir bağlantıda olmasını gerektirirler.- İstemci, params türüne göre dallanan tek bir
elicitation_callbackile yanıtlar; yeteneği bildiren şey onu kaydetmektir. - 2026-07-28 bağlantısında sunucu soruyu itmek yerine döndürür; aynı callback'i Çok turlu istekler besler.
O dönüşün altında yatan her şey (yeniden deneme döngüsü, requestState'i koruma, akışı kendiniz yürütme) Çok turlu istekler sayfasında.