modelcontextprotocol / modelcontextprotocol/python-sdk

Parameterized Context loses request state in resource and prompt handlers

Đang mở
#3,235 2 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

P1 v2
Ngôn ngữ chính
Python
Star
24.3k
Fork
4k
Merge trung bình
1 ngày 1 giờ
Pull request đã merge (30 ngày)
31

Mô tả

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.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu tại ResourceTemplate.from_function và Prompt.from_function, sau đó so sánh đường dẫn validation của chúng với FuncMetadata.call_fn_with_arg_validation được tools sử dụng. Tái hiện các lệnh gọi resource và prompt từ ví dụ tối thiểu và kiểm tra xem Context được inject có giữ nguyên identity và trạng thái request hay không. Hoàn tất khi các đối số của client vẫn được validation, trong khi Context do server tạo ra đến cả hai handler mà không bị thay đổi.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
backend-api-design
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
55/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.