Ana içeriğe geç

Sorun giderme

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.

Bu sayfadaki her başlık, SDK'nın ürettiği bir hatanın birebir metnidir; ardından ne anlama geldiği ve tek hamlelik çözümü gelir. Traceback'inizin (veya sunucu log'unuzun) son satırını tarayıcınızın sayfada bul özelliğiyle burada arayın ve yalnızca o girdiyi okuyun.

Girdilerin birkaçı şu tek sunucuya karşı çalışır. Bir araç ve bir şablonlu kaynak; her biri tanımadığı bir şehir için istisna fırlatır:

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ResourceNotFoundError

mcp = MCPServer("Weather")

FORECASTS = {"London": "Rain.", "Cairo": "Sun."}


@mcp.tool()
def forecast(city: str) -> str:
    """Today's forecast for one city."""
    if city not in FORECASTS:
        raise ValueError(f"No forecast for {city!r}.")
    return FORECASTS[city]


@mcp.resource("weather://{city}")
def report(city: str) -> str:
    """The full report for one city."""
    if city not in FORECASTS:
        raise ResourceNotFoundError(f"No forecast for {city!r}.")
    return f"{city}: {FORECASTS[city]}"

Bu sayfanın alıntıladığı hatalar gerçektir: SDK'nın kendi test paketi her birini yeniden üretir.

ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)

Bu bir MCP hatası değil. anyio gürültüsüdür ve asıl hatanız yapıştırdığınız metnin son satırıdır.

Client.__aenter__ bir görev grubu başlatır. anyio, görev grubundan çıkan her şeyi bir ExceptionGroup içine sarar; bu yüzden bir async with Client(...) bloğundan kaçan her istisna, ne olursa olsun, böyle bir grubun içinde gelir:

async def main() -> None:
    async with Client(mcp) as client:
        await client.read_resource("weather://Atlantis")
  + Exception Group Traceback (most recent call last):
  |   ...
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Exception Group Traceback (most recent call last):
    |   ...
    | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
    +-+---------------- 1 ----------------
      | Traceback (most recent call last):
      |   ...
      | mcp.shared.exceptions.MCPError: No forecast for 'Atlantis'.
      +------------------------------------

Bununla yapılacak iki şey var:

  1. En altı okuyun. Hata MCPError: No forecast for 'Atlantis'. satırıdır; bu sayfada onun metnini arayın.
  2. Bloğun içinde yakalayın. ExceptionGroup yalnızca istisna async with bloğundan çıktığında ortaya çıkar. İçeride yakalandığında aynı hata düz bir MCPError'dır; ortada hiçbir grup yoktur:
async def main() -> None:
    async with Client(mcp) as client:
        try:
            await client.read_resource("weather://Atlantis")
        except MCPError as e:
            print(e)  # No forecast for 'Atlantis'.

Tip

Bağlantı sırasındaki bir hata (yanlış bir URL, çalışmayan bir sunucu, bu sayfanın ilerisindeki 421) async with'in kendisinden kaçar; dolayısıyla onu yakalayacak bir "içerisi" yoktur. Bunlar için grubun en altını okuyun.

RuntimeError: Client must be used within an async context manager

Client(...) yalnızca nesneyi kurar. async with'e kadar hiçbir şey bağlanmaz; bu yüzden her yöntem reddeder:

async def main() -> None:
    client = Client(mcp)
    tools = await client.list_tools()  # RuntimeError

İçine girin. Bağlantının kendisi __aenter__'dır:

async def main() -> None:
    async with Client(mcp) as client:
        tools = await client.list_tools()

__aexit__ ise bağlantının kesilmesidir; unutulacak bir client.close() olmamasının nedeni de budur. Test etme tam olarak bu kalıp üzerine kuruludur.

Error executing tool <name>: <message> ve Unknown tool: <name>

Okuduğunuz şey bir istisna değil, bir sonuç. call_tool istisna fırlatmadı ve başarısız olan bir araç için hiçbir zaman fırlatmaz.

forecast'i sunucunun tanımadığı bir şehir için çağırın; fırlattığı istisna, istek başarılı olarak işaretlenmiş halde geri döner:

result.is_error  # True
result.content   # [TextContent(text="Error executing tool forecast: No forecast for 'Atlantis'.")]
result.structured_content  # None

Unknown tool: get_forecast, sunucunun hiç kaydetmediği bir ad için aynı biçimdir; hatalı bir argüman da aynı şekilde, fonksiyonunuz daha hiç çalışmadan, aracın girdi şemasına göre reddedilir.

Çözüm istemcinizde: result.is_error'ı kontrol edin. call_tool etrafındaki bir try/except bunların hiçbirini yakalamaz, çünkü yakalanacak bir şey yoktur. Bu kasıtlıdır ve bu sayfada içselleştirilecek en yararlı tek şeydir: çağrıyı model seçti, bu yüzden mesajı ve yeniden deneme şansını da model alır. Ayrıntıların tamamı, gerçekten istisna fırlatan MCPError yolu dahil, Hataları ele alma sayfasında.

TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool

@mcp.tool() yerine @mcp.tool yazdınız. tool() bir dekoratör fabrikasıdır: parantezler olmadan Python, fonksiyonunuzu onun name= parametresine verir.

@mcp.tool  # <- missing ()
def forecast(city: str) -> str:
    """Today's forecast for one city."""
    return f"{city}: Rain."
TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool

Parantezleri ekleyin. @mcp.resource(...) ve @mcp.prompt() de aynı sürçme için aynı şeyi söyler.

Note

Bu, herhangi bir istemci bağlanmadan önce, modül içe aktarıldığında fırlatılır. Yani sunucunuzu sıfır araçla bağlı olarak değil de başlatılamadı (veya bağlantı kesildi) olarak gösteren bir host bu biçimdedir: python server.py komutunu kendiniz çalıştırın ve traceback'i okuyun. Bir tür denetleyicisi de bunu yakalar: bir fonksiyon geçerli bir name= değildir.

Tool already exists: <name>

İki kayıt aynı araç adını kullandı. İlki kazanır, ikincisi sessizce düşürülür ve sunucu log'undaki bu uyarı tek işarettir:

server.py
from mcp.server import MCPServer

mcp = MCPServer("Weather")


@mcp.tool(name="forecast")
def forecast_today(city: str) -> str:
    """Today's forecast for one city."""
    return f"{city}: Rain."


@mcp.tool(name="forecast")  # Same name. This registration is dropped.
def forecast_hourly(city: str, hours: int) -> str:
    """The next few hours for one city."""
    return f"{city}: Rain for {hours}h."
WARNING mcp.server.mcpserver.tools.tool_manager: Tool already exists: forecast

tools/list tek bir forecast bildirir ve o da forecast_today'dir. Birinin adını değiştirin. MCPServer(..., warn_on_duplicate_tools=False) sonucu değiştirmeden uyarıyı susturur; bu yüzden açık bırakın. Kaynaklar ve prompt'lar için de aynı kural ve aynı log satırı geçerlidir (Resource already exists:, Prompt already exists:).

Host'um sıfır araç listeliyor

Bunun bir hata metni yoktur; aranmasının zor olmasının nedeni de tam olarak budur. SDK kayıtlı bir aracı tools/list'ten asla düşürmez; bu yüzden içeriden dışarıya doğru ilerleyin:

  • Sunucu hiç başladı mı? Parantezsiz @mcp.tool içe aktarma sırasında fırlatır ve çökmüş bir sunucu bazı host'larda boş bir sunucuya çok benzer. python server.py komutunu kendiniz çalıştırın.
  • Araç, host'un çalıştırdığı mcp üzerinde mi? Başka bir modüldeki ikinci bir MCPServer(...) farklı, boş bir sunucudur. Host'un komutunun gerçekte hangi nesneyi içe aktardığını kontrol edin.
  • İki araç aynı adı mı paylaştı? O zaman biri gitmiştir. Sunucu log'unda Tool already exists: satırını arayın.
  • Host'un listesi eski mi? Başlangıçtan sonra eklenen bir araç yalnızca notifications/tools/list_changed bildirimini işleyen istemcilere ulaşır. Host'u yeniden başlatmak kaba ama kesin çözümdür.
  • Yönlendirilen pencerenin dışında bir şey stdout'a mı yazdı? SDK hizmet verirken başıboş ve flush edilmiş stdout çıktısını stderr'e yönlendirir (elinden geldiğince: standart akışları değiştiren bir ortama olduğu gibi hizmet verilir). Ancak daha önce stdout'a flush edilmiş çıktı (echo yapan bir sarmalayıcı betik, tamponsuz bir süreçte içe aktarma sırasında çalışan bir print()) veya yorumlayıcı çıkışında boşaltılan tamponlanmış bir print() protokol akışına düşer ve tek bir çöp satır host'un bağlantıyı kesmesine yol açabilir; bazı host'lar bunu içinde hiçbir şey olmayan bir sunucu olarak gösterir. Bunun yerine logging modülüyle log tutun. Host tarafı kontrol listesinin geri kalanı Gerçek bir host'a bağlanma sayfasında.

"Geçersiz" bir araç adı bu listede değildir: kurala uymayan bir ad log'a bir uyarı yazar, ancak araç yine de kaydedilir ve listelenir.

MCPError: Server returned an error response

Sunucu HTTP isteğini, JSON-RPC olmayan bir gövdeyle doğrudan reddetti; bu yüzden python Client'ın size gösterebileceği bu yer tutucudan daha iyi bir şey yok.

Açık ara en yaygın neden, yeni dağıtılmış bir Streamable HTTP sunucusudur. transport_security= verilmeyen streamable_http_app() (ve mcp.run("streamable-http")) varsayılan olarak DNS rebinding koruması uygular: yalnızca Host başlığı localhost olan istekleri kabul eder. Bu, dizüstü bilgisayarınızda doğru varsayılandır; gerçek bir ana bilgisayar adının arkasında ise yanlış:

server.py
from mcp.server import MCPServer

mcp = MCPServer("Weather")


@mcp.tool()
def forecast(city: str) -> str:
    """Today's forecast for one city."""
    return f"{city}: Rain."


app = mcp.streamable_http_app()

Bunu dağıtın, bir istemciyi ona yönlendirin; bağlantı el sıkışmada başarısız olur:

async with Client("https://mcp.example.com/mcp") as client:
    ...
mcp.shared.exceptions.MCPError: Server returned an error response

Sunucunun gerçekte gönderdiği sözcükler, 421 ve Invalid Host header, size asla ulaşmaz: 421 gövdesinde Content-Type: application/json yoktur, bu yüzden istemci onu ayrıştıramaz. Bunlar sunucunun log'undadır; bir sonraki bakılacak yer de orasıdır:

WARNING mcp.server.transport_security: Invalid Host header: mcp.example.com

Çözüm transport_security=. Gerçekte hizmet verdiğiniz ana bilgisayar adını izin listesine ekleyin:

server.py
from mcp.server import MCPServer
from mcp.server.transport_security import TransportSecuritySettings

mcp = MCPServer("Weather")


@mcp.tool()
def forecast(city: str) -> str:
    """Today's forecast for one city."""
    return f"{city}: Rain."


app = mcp.streamable_http_app(
    transport_security=TransportSecuritySettings(
        allowed_hosts=["mcp.example.com", "mcp.example.com:*"],
        allowed_origins=["https://app.example.com"],
    )
)

Check

Değişikliğin tamamı bu. Aynı istemci artık bağlanır, 2026-07-28 üzerinde anlaşır ve forecast'i çağırır.

Dağıtım ve ölçekleme her alanın ne anlama geldiğini, ters vekil sunucu durumunu ve dağıtım sırasında değişen diğer her şeyi ele alır. Hemen aşağıdaki 421 Misdirected Request / Invalid Host header ise aynı hatanın öbür taraftan görünüşüdür.

421 Misdirected Request / Invalid Host header

Bu, python Client olmayan herhangi bir yerden görülen Server returned an error response'tır: curl, bir tarayıcının ağ sekmesi, bir ters vekil sunucunun erişim log'u veya başka bir SDK.

curl -i https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
HTTP/1.1 421 Misdirected Request

Invalid Host header

421 Misdirected Request, HTTP'nin bu durum kodu için kendi gerekçe ifadesidir; Invalid Host header SDK'nın yanıt gövdesidir; python Client ise aynı olayı Server returned an error response olarak gösterir. Üçü de tek bir rettir. Denetim, sunucunun bağlandığı adrese değil, isteğin taşıdığı Host başlığına karşı çalışır; bu yüzden genel ana bilgisayar adını ileten bir ters vekil sunucu, ona tıpkı doğrudan bir istemci gibi takılır.

Çözüm, Server returned an error response altında gösterilen aynı transport_security=TransportSecuritySettings(allowed_hosts=[...], allowed_origins=[...]). İki ince noktasını adlandırmaya değer:

  • Bir allowed_hosts girdisi birebir bir dizedir. "mcp.example.com" yalın bir Host başlığıyla, "mcp.example.com:*" ise açıkça belirtilmiş herhangi bir portla eşleşir. İkisini de listeleyin.
  • Gövdesi Invalid Origin header olan bir 403, Origin başlığı üzerindeki kardeş denetimdir. Yalnızca tarayıcılar için tetiklenir (başka hiçbir şey Origin göndermez) ve onun izin listesi de allowed_origins= parametresidir.

Denetimi kapatmanın dürüst yapılandırma olduğu durumlar dahil, konunun tamamı Dağıtım ve ölçekleme sayfasında.

RuntimeError: Task group is not initialized. Make sure to use run().

MCP uygulamanız başka bir ASGI uygulamasının içine bağlanmış (mount edilmiş) ve oturum yöneticisini hiçbir şey başlatmamış.

mcp.streamable_http_app(), kendi lifespan'i (yaşam döngüsü) yöneticiyi başlatan bir Starlette uygulaması döndürür ve uvicorn server:app bu lifespan'i sizin için çalıştırır. Ancak Starlette bağlanmış bir alt uygulamanın lifespan'ini asla çalıştırmaz; bu yüzden uygulama bir Mount içine girdiği anda yönetici hiç başlamaz ve ilk istek patlar:

server.py
from starlette.applications import Starlette
from starlette.routing import Mount

from mcp.server import MCPServer

mcp = MCPServer("Weather")


@mcp.tool()
def forecast(city: str) -> str:
    """Today's forecast for one city."""
    return f"{city}: Rain."


# The mount works. The MCP app's own lifespan never runs.
app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())])

Sunucu başlar. Rota çözümlenir. Ardından uvicorn her istek için şunu yazdırır:

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  ...
RuntimeError: Task group is not initialized. Make sure to use run().

İstemci bir 500 görür. Çözüm, ana uygulamada mcp.session_manager.run()'a giren bir lifespan'dir:

@asynccontextmanager
async def lifespan(app: Starlette) -> AsyncIterator[None]:
    async with mcp.session_manager.run():
        yield


app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())], lifespan=lifespan)

Bunun sayfası, tek uygulamada birden fazla sunucu ve FastAPI dahil, Mevcut bir uygulamaya ekleme. Aynı sınıftan iki komşu metin:

  • StreamableHTTPSessionManager .run() can only be called once per instance. Create a new instance if you need to run again. Yönetici tek kullanımlıktır; aynı uygulamanın lifespan'ine iki kez girmek buna çarpar.
  • mcp.session_manager yalnızca streamable_http_app() çağrıldıktan sonra var olur; bu yüzden önce rotaları kurun ve yöneticiye yalnızca lifespan'in içinde dokunun.

MCPError: Session not found

Sunucu, istemcinizin gönderdiği Mcp-Session-Id'yi tanımıyor; bunun nedeni neredeyse her zaman sunucunun yeniden başlamış olmasıdır (ya da farklı bir örneğe yönlendirilmişsinizdir). Oturumlar o tek sürecin belleğinde yaşar.

Bulunacak bir sunucu hatası yok. HTTP yanıtı, gövdesi JSON-RPC olan bir 404'tür; bu yüzden yukarıdaki 421'in aksine python Client bunu size birebir gösterir:

{"jsonrpc": "2.0", "id": null, "error": {"code": -32600, "message": "Session not found"}}

Çözüm yeniden bağlanmaktır: async with Client(...) bloğundan çıkın ve yeni bir oturum üzerinde anlaşan yeni bir bloğa girin. Uzun ömürlü bir istemci için bu, çağrılarınızın etrafında MCPError'ı yakalamak ve ölü bir oturumun içinde yeniden denemek yerine bu mesajda yeniden bağlanmak demektir.

Bu, yeniden başlatma olmadan oluyorsa, yapışkan oturumlar olmadan birden fazla worker çalıştırıyorsunuz demektir: her worker kendi oturum tablosunu tutar, bu yüzden yanlış olana yönlendirilen bir istek buraya düşer. Bu konu ve iki çözümü (yapışkan yönlendirme veya stateless_http=True) Dağıtım ve ölçekleme ile Eski nesil istemcilere hizmet verme sayfalarında.

Sunucu operatörü için eşleşen log satırı Rejected request with unknown or expired session ID: <id>'dir. INFO düzeyinde log'a yazılır; bu yüzden olağan WARNING eşiğinde görünmez. Bir dağıtımın hemen ardından bunu öbekler halinde görmek normaldir; bağlı her istemci yeniden bağlanıyordur.

MCPError: Method not found

Bir taraf, diğer tarafın işleyicisi olmayan bir JSON-RPC isteği gönderdi ve e.error.data yöntemin adını verir. Olağan neden bir nesil uyuşmazlığıdır: bir protokol sürümünde olup diğerinde olmayan bir yöntemin yanlış sürümdeki bir eşe gönderilmesi; örneğin 2025 neslinden bir resources/subscribe'ın bir 2026-07-28 bağlantısına ulaşması ya da mode="legacy" değerine sabitlenmiş bir istemcinin yalnızca 2026'da var olan subscriptions/listen'ı göndermesi. Hangi tarafın ne konuştuğunun haritası Protokol sürümleri sayfasıdır; diğer dürüst neden (hiç işleyici kaydetmediğiniz isteğe bağlı bir yetenek) ise Tamamlamalar sayfasında.

Modern protokolün kaldırdığı bir istek olmasına rağmen bu hatayı üretmeyen bir şey var: bir 2026-07-28 bağlantısında ctx.elicit() çağıran bir araç. Sunucu o isteği göndermeyi baştan reddeder; bu yüzden bunun yerine, bu sayfanın ilerisindeki Cannot send 'elicitation/create': ... hatasını alırsınız.

MCPError: Client did not declare the form elicitation capability required by resolver '<name>'

Sunucunuz kullanıcıya bir şey sormak istiyor ve bu istemci kendisine soru sorulabileceğini hiç söylemedi.

Bir elicitation (kullanıcıdan bilgi isteme) çözümleyicisi, bağlı istemci form elicitation'ı bildirmediğinde baştan reddeder ve e.error.data tam olarak neyin eksik olduğunu adlandırır:

{
  "code": -32021,
  "message": "Client did not declare the form elicitation capability required by resolver 'server:ask_to_confirm'",
  "data": {"requiredCapabilities": {"elicitation": {"form": {}}}}
}

Client(...)'a elicitation_callback= geçirin. Callback'i kaydetmek yetenek bildiriminin ta kendisidir; ikinci bir anahtar yoktur:

async def main() -> None:
    async with Client(mcp, elicitation_callback=handle_elicitation) as client:
        result = await client.call_tool("book_table", {"date": "Friday"})

İstemci callback'leri diğerlerini listeler (sampling_callback, list_roots_callback); her biri aynı şekilde bir bildirimdir.

Info

-32021, MISSING_REQUIRED_CLIENT_CAPABILITY'dir; 2026-07-28 spesifikasyonunun eklediği üç hata kodundan biridir. Hiçbiri bir istisna sınıfı değildir: hepsi MCPError olarak gelir ve bakılacak yer e.error.code'dur. Sabitleri mcp.types dışa aktarır. Diğer ikisi -32020 HEADER_MISMATCH (bir HTTP başlığı eşlik ettiği istek gövdesiyle uyuşmuyor) ve -32022 UNSUPPORTED_PROTOCOL_VERSION'dır (istek, bu sunucunun konuşmadığı bir sürümü belirtmiş). Uyumlu bir SDK istemcisi ikisini de üretemez; bu yüzden birini görürseniz, istemcinizle sunucunuz arasında istekleri yeniden yazan şey her neyse ona bakın.

MCPError: Elicitation not supported

Client did not declare the form elicitation capability ... ile aynı boşluk; bu kez baştan denetim yapmayan yolların ifadesiyle: sunucunun bir elicitation'ın yanıtlanmasına ihtiyacı vardı ve bağlı istemci hiçbir elicitation_callback kaydetmemişti.

Bunu eski nesil bir bağlantıda ctx.elicit()'ten görürsünüz; herhangi bir bağlantıda ise onu yanıtlayacak callback'i olmayan bir istemciye ulaşan, döndürülmüş bir çok turlu (multi-round-trip) sorudan (Çok turlu istekler). Çözüm aynıdır: Client(...)'a elicitation_callback= geçirin. "Kullanıcıya sorulmadı" durumunun, aracınıza decline olarak ulaşan bir hâli yoktur; soru sorulamayan bir istemci başarısız bir çağrı demektir, araçlarınızı buna göre tasarlayın.

MCPError: Cannot send 'elicitation/create': this transport context has no back-channel for server-initiated requests.

İşleyiciniz, isteğin ortasında istemciye ulaşmaya çalıştı; hem de çağrısının sunucudan gelen bir isteği taşıyabilecek hiçbir kanalı olmayan bir bağlantıda. Bir çağrıyı bu duruma sokan üç sunucu yapılandırması var.

Bir 2026-07-28 bağlantısı: her aktarımda, her zaman. Modern protokolde sunucunun başlattığı istek diye bir şey hiç yoktur; bu yüzden sunucu daha hiçbir şey gönderilmeden reddeder. Bununla karşılaşmanın klasik yolu bir aracın içindeki ctx.elicit()'tir (hem de daha ilk bellek içi testte, çünkü Client(server) sorulmadan 2026-07-28 üzerinde anlaşır) ve elicitation_callback= geçirmek hiçbir şeyi değiştirmez, çünkü istemciye yanıtlayacağı bir istek hiç ulaşmaz:

server.py
from pydantic import BaseModel

from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bistro")


class Confirmation(BaseModel):
    confirm: bool


@mcp.tool()
async def book_table(date: str, ctx: Context) -> str:
    """Book a table at the bistro."""
    result = await ctx.elicit(f"Book a table for {date}?", schema=Confirmation)
    if result.action == "accept" and result.data.confirm:
        return f"Booked for {date}."
    return "No booking made."
async def main() -> None:
    async with Client(mcp) as client:
        await client.call_tool("book_table", {"date": "Friday"})
mcp.shared.exceptions.MCPError: Cannot send 'elicitation/create': this transport context has no back-channel for server-initiated requests.

stateless_http=True bir sunucuda eski nesil bir bağlantı. Durumsuzluk, her isteğin kendi dünyası olması demektir: oturum yok, sunucudan istemciye akış yok; dolayısıyla bunlara sahip olan nesil için bile bir elicitation/create (veya sampling/createMessage ya da roots/list) gönderecek hiçbir yer yok:

server.py
from pydantic import BaseModel

from mcp.server import MCPServer
from mcp.server.mcpserver import Context

mcp = MCPServer("Bistro")


class Confirmation(BaseModel):
    confirm: bool


@mcp.tool()
async def book_table(date: str, ctx: Context) -> str:
    """Book a table at the bistro."""
    result = await ctx.elicit(f"Book a table for {date}?", schema=Confirmation)
    if result.action == "accept" and result.data.confirm:
        return f"Booked for {date}."
    return "No booking made."


# Stateless HTTP: every request is its own world. No channel back to the client.
app = mcp.streamable_http_app(stateless_http=True)

json_response=True bir sunucuda eski nesil bir bağlantı. POST tek bir JSON gövdesiyle yanıtlanır ve tek bir gövde yalnızca yanıtı taşır; bu yüzden isteğin ortasındaki bir ctx.elicit()'in ihtiyaç duyduğu istek kapsamlı akış burada da yoktur. Oturum, onun Mcp-Session-Id'si ve bağımsız akışı hâlâ yerindedir; giden yalnızca istek kapsamlı kanaldır.

Mesaj, gönderemediği yöntemin adını verir. Sunucunun fırlattığı sınıf NoBackChannelError'dır, ancak ağ üzerinden yalnızca temel MCPError taşınır; bu yüzden traceback'inizin son satırı sınıf adı değil, yukarıdaki cümledir.

Bir 2026-07-28 istemcisi için çözüm üçünde de aynıdır: çağrının ortasında geriye uzanmayın. Soruyu bir çözümleyiciye taşıyın (ya da kendiniz bir InputRequiredResult döndürün); böylece soru, her bağlantının taşıyabildiği yanıtın bir parçası olur:

server.py
from typing import Annotated

from pydantic import BaseModel

from mcp.server import MCPServer
from mcp.server.mcpserver import Elicit, Resolve

mcp = MCPServer("Bistro")


class Confirmation(BaseModel):
    confirm: bool


async def ask_to_confirm(date: str) -> Elicit[Confirmation]:
    """Resolver: ask the user to confirm the booking."""
    return Elicit(f"Book a table for {date}?", Confirmation)


@mcp.tool()
async def book_table(date: str, answer: Annotated[Confirmation, Resolve(ask_to_confirm)]) -> str:
    """Book a table at the bistro."""
    if answer.confirm:
        return f"Booked for {date}."
    return "No booking made."

Aynı soru, istemcide aynı elicitation_callback. Fark arka plandadır: çözümleyici, sunucunun soruyu itmek yerine çağrıdan döndürmesini sağlar; böylece sunucudan istemciye hiçbir şey akmaz. Bu, sunucu üç yapılandırmanın hangisinde olursa olsun her 2026-07-28 istemcisini kurtarır. Eski nesil bir istemciyi ise tek başına bu yeniden yazım kurtarmaz: 2025-11-25'te bir soruyu döndürmenin yolu yoktur; bu yüzden eski nesil bir bağlantıda çözümleyici elicitation/create'i yine istek kapsamlı kanaldan gönderir ve yine bu kanalı koruyan bir sunucuya ihtiyaç duyar: ne stateless_http=True ne de json_response=True. Çözümleyicileri Elicitation sayfası, ağ üzerinde neler olduğunu ise Çok turlu istekler sayfası ele alır.

Check

ctx.elicit() kullanan araç yanlış değil, 2026 öncesi. Ne stateless_http=True ne de json_response=True olan bir sunucuya mode="legacy" ile (klasik initialize el sıkışması, spesifikasyon 2025-11-25 ve öncesi) bağlanın; çalışır, çünkü orada sunucudan istemciye kanal vardır. Her sürümde nelerin olduğunu anlatan sayfa Protokol sürümleri.

MCPError: Invalid or expired requestState

Sunucu, istemcinizin geri yansıttığı requestState token'ını doğrulayamadı; bu yüzden turu reddetti.

requestState, çok turlu bir çağrının ayaklar arasında taşıdığı opak devam token'ıdır. MCPServer onu çıkışta mühürler ve her yansımayı doğrular; üstelik tools/call, prompts/get ve resources/read üzerindeki gelen her request_state'i, hiç token üretmeyen bir işleyici için bile doğrular. Bu yüzden bu sürecin mühürlemediği bir token nereye düşerse düşsün reddedilir:

async def main() -> None:
    async with Client(mcp) as client:
        await client.call_tool("forecast", {"city": "London"}, request_state="round-1-from-worker-a")
mcp.shared.exceptions.MCPError: Invalid or expired requestState

Mesaj kasıtlı olarak sabittir: ağ üzerinden hangi denetimin başarısız olduğu asla açığa çıkmaz. Neden sunucu log'una gider ve onu okumak teşhisin tamamıdır:

WARNING mcp.server.request_state: requestState rejected on tools/call: malformed

Gerçekte göreceğiniz nedenler:

  • unknown key önemli olandır. Varsayılan mühürleme anahtarı süreç başlangıcında üretilir; bu yüzden farklı bir worker'a, yük dengeleyici arkasındaki farklı bir örneğe ya da yeniden başlatma sonrası aynı sunucuya düşen bir yeniden deneme, bu sürecin hiç sahip olmadığı bir anahtarla mühürlenmiştir. Bu bir saldırgan değildir; varsayılanın birden fazla süreçle karşılaşmasıdır.
  • audience: token'ı farklı bir sunucu adına sahip bir örnek mühürlemiş. Ad, mührün varsayılan audience claim'idir; bu yüzden bir filonun anahtarların yanı sıra adı da paylaşması (ya da açık bir RequestStateSecurity(audience=...) ayarlaması) gerekir.
  • expired: tur, mührün ttl süresinden uzun sürdü; bu süre 600 saniyedir ve çağrı başına değil, tur başınadır.
  • malformed / codec error: token yolda değiştirilmiş ya da hiçbir zaman mühürlü bir token olmamış.
  • request binding: token farklı bir araçla, farklı argümanlarla ya da farklı bir yöntemle geri geldi.

Çok süreçli çözüm tek bir argüman (her örnekte aynı keys) artı argüman bile olmayan bir şeydir: aynı sunucu adı (ya da açıkça paylaşılan bir audience=).

mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key]))

keys[0] mühürler; listedeki her anahtar doğrular; kesintisiz rotasyonu mümkün kılan da budur. Mührün neyi koruduğunu ve rotasyon sırasını Çok turlu istekler açıklar; Dağıtım ve ölçekleme ise iki worker'lı hatanın tamamını ve iki parçalı çözümünü adım adım anlatır.

Tip

keys=[...] zayıf bir anahtarı, alışılmadık derecede yardımcı bir mesajla hemen reddeder:

ValueError: request-state keys must be at least 32 bytes of secret randomness; keys[0] is 7 bytes. Generate one with: python -c "import secrets; print(secrets.token_hex(32))"

Dediğini yapın.

Hâlâ takıldınız mı?

Özet

  • ExceptionGroup: unhandled errors in a TaskGroup hiçbir zaman asıl hata değildir. Son satırı okuyun; MCPErrorasync with Client(...) bloğunun içinde yakalamak sarmalamayı tamamen atlar.
  • call_tool başarısız olan bir araç için istisna fırlatmaz. Error executing tool ... ve Unknown tool: ... birer sonuçtur: result.is_error'ı kontrol edin.
  • Client must be used within an async context manager -> async with kullanın. Use @tool() instead of @tool -> parantezleri ekleyin.
  • Sunucu log'undaki Tool already exists:, aynı adlı iki aracın teke indiğinin tek işaretidir.
  • Tek 421, üç yazım: Server returned an error response (python Client), 421 Misdirected Request / Invalid Host header (geri kalan her şey), Invalid Host header: <host> (sunucu log'u). Çözüm: transport_security=TransportSecuritySettings(allowed_hosts=[...]).
  • Task group is not initialized -> ana uygulamanın lifespan'i mcp.session_manager.run()'a hiç girmemiş, bağlanmış bir uygulama.
  • Session not found -> sunucu yeniden başladı; yeniden bağlanın.
  • Cannot send 'elicitation/create': ... no back-channel ... -> ctx.elicit() sunucudan istemciye bir kanala ihtiyaç duyar: bir 2026-07-28 bağlantısında hiç yoktur, stateless_http=True eski nesil olanı, json_response=True ise istek kapsamlı olanı ortadan kaldırır. Bir çözümleyici kullanın (eski nesil bir istemci için ayrıca kanalı koruyan bir sunucu gerekir). Komşusu Method not found, karşı tarafın protokol sürümünde olmayan bir yöntem için yapılmış bir istektir.
  • Client did not declare the form elicitation capability ... ve Elicitation not supported -> istemcide elicitation_callback= eksik.
  • Invalid or expired requestState nedenini ağ üzerinde asla söylemez. Sunucu log'u söyler; unknown key, RequestStateSecurity(keys=[...])'i worker'lar arasında paylaşın demektir.