modelcontextprotocol / modelcontextprotocol/python-sdk

Parameterized Context loses request state in resource and prompt handlers

未關閉
#3,235 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

P1 v2
主要語言
Python
星號
24.3k
分支
4k
平均合併
1 天 1 小時
30 天內合併 PR
31

描述

Description

A resource-template or prompt handler annotated with a parameterized Context[LifespanContextT] receives a different Context object from the one the server injected. The reconstructed object has none of the private request state, so accessing ctx.request_context, ctx.session, ctx.request_id, or lifespan state fails with:

ValueError: Context is not available outside of a request

The equivalent tool handler works, and handlers annotated with unparameterized Context happen to work. This makes the documented typed lifespan-context pattern unusable specifically in dynamic resources and prompts.

Minimal reproduction

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

import anyio
from mcp.client import Client
from mcp.server.mcpserver import Context, MCPServer


@asynccontextmanager
async def lifespan(_: MCPServer[str]) -> AsyncIterator[str]:
    yield "live-state"


async def main() -> None:
    server = MCPServer("probe", lifespan=lifespan)

    @server.resource("probe://{name}")
    async def resource(name: str, ctx: Context[str]) -> str:
        return f"{name}:{ctx.request_context.lifespan_context}"

    @server.prompt("probe")
    async def prompt(name: str, ctx: Context[str]) -> str:
        return f"{name}:{ctx.request_context.lifespan_context}"

    async with Client(server) as client:
        await client.read_resource("probe://value")
        await client.get_prompt("probe", {"name": "value"})


anyio.run(main)

Both calls fail. Each should return content containing value:live-state.

Root cause

ResourceTemplate.from_function and Prompt.from_function correctly omit the detected context parameter from their public argument schemas, but then wrap the original handler with pydantic.validate_call. At invocation time they inject the live Context into the wrapped handler, so Pydantic validates it again.

For a generic annotation, Pydantic converts the unparameterized runtime instance into Context[str, Any]. That creates a new model instance and does not carry over Context's private _request_context, _mcp_server, and related attributes:

server-created Context
        |
        v
validate_call parameter: Context[str]
        |
        v
new Context[str, Any] instance
        |
        v
private request state absent

A direct identity probe on current main shows:

Context       -> same object, request state present
Context[str]  -> new object, request state absent

Tools avoid this because FuncMetadata.call_fn_with_arg_validation validates only client-supplied arguments and passes injected values directly to the raw handler.

Impact

This affects resource templates and prompts that use typed lifespan context, including otherwise valid code following the SDK's generic Context[LifespanContextT] typing. It can also hide in tests that only assert that ctx is non-null rather than reading request-scoped state.

A compatible fix should preserve the existing validation/coercion of client-supplied resource or prompt arguments while passing the server-created context object directly, with its identity and private state intact.

Environment

  • MCP Python SDK: 2.0.0 and current main at a4f4ccd091138771535e17191123f20b30fda68e
  • Python: 3.12.13
  • Pydantic: 2.12.5

AI assistance was used to investigate and draft this report.

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

從 ResourceTemplate.from_function 和 Prompt.from_function 開始,然後將它們的驗證路徑與 tools 使用的 FuncMetadata.call_fn_with_arg_validation 進行比較。根據最小範例重現 resource 和 prompt 呼叫,並檢查注入的 Context 是否維持其身分和請求狀態不變。當用戶端參數仍然經過驗證,同時伺服器建立的 Context 未經修改地到達兩個 handler 時,即表示完成。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
backend-api-design
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
冷清
描述清晰度
描述清楚
新手友好度
55/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。