Ana içeriğe geç

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:

server.py
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 kendi path argümanını adıyla okur, klasörü listeler ve yalnızca gerektiğinde sorar: boş bir klasör, istemciye hiç gidip dönmeden Confirm(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 durumu match ile ele alır: kabul edip onaylama, kabul edip tutma (ok=False), reddetme, iptal.
  • confirm parametresi 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:

server.py
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 veren Context parametresidir; 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ış bir AlternativeDate ö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:

server.py
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 bir elicitation_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:

client.py
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, ElicitRequestFormParams ile ElicitRequestURLParams'ın bir birleşimidir; dallanma isinstance ile 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. Asla content yok.
  • Form için gerçek bir uygulama params.requested_schema'yı çizer ve kullanıcının girdisini content olarak 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ğinde Elicit(...) 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.data yalnı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_callback ile 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.