-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
When inferring the context kwarg for tools, the relevant code —
python-sdk/src/mcp/server/fastmcp/tools/base.py
Lines 62 to 69 in d28a1a6
if context_kwarg is None: | |
sig = inspect.signature(fn) | |
for param_name, param in sig.parameters.items(): | |
if get_origin(param.annotation) is not None: | |
continue | |
if issubclass(param.annotation, Context): | |
context_kwarg = param_name | |
break |
if issubclass(param.annotation, Context):
.
However, param.annotation
is the raw value of the annotation. In particular, if using from __future__ import annotations
, this will be a string, and so the check will incorrectly result in the context argument not being detected.
Not only is it unfortunate that this currently misbehaves at all, but it is unfortunate that you don't get an error until the model actually tries to call the tool with "mis-annotated" context, and even worse, when the model does try to call the tool with context you get a cryptic (and as far as I can tell inaccurate) error message saying that you can't access context outside of a request, even though the tool really is being called in a request. (This was a painful one to debug.)
In Pydantic AI we work around stuff like this by relying on a combination of inpect.Signature.parameters
and typing.get_type_hints
— see https://github.com/pydantic/pydantic-ai/blob/a08a20ea1f87a4d093fdb08cb5dd8691e3589656/pydantic_ai_slim/pydantic_ai/_function_schema.py#L102-L133 for more details. (Actually we use pydantic._internal._typing_extra.get_function_type_hints
, which is very similar to get_type_hints
but has tweaks to better handle some edge cases, but typing.get_type_hints
might be enough here.)
Example Code
# https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/tool_progress.py
# with the future import added
from __future__ import annotations
from mcp.server.fastmcp import Context, FastMCP
mcp = FastMCP(name="Progress Example")
@mcp.tool()
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
"""Execute a task with progress updates."""
await ctx.info(f"Starting: {task_name}")
for i in range(steps):
progress = (i + 1) / steps
await ctx.report_progress(
progress=progress,
total=1.0,
message=f"Step {i + 1}/{steps}",
)
await ctx.debug(f"Completed step {i + 1}")
return f"Task '{task_name}' completed"
Python & MCP Python SDK
Confirmed explicitly with python 3.13 and 3.11, and MCP SDK 1.7.1 and 1.11.0.