Skip to content

Commit 05942ba

Browse files
g97iulio1609Copilot
andcommitted
fix: detect Context parameter in callable class instances
find_context_parameter() uses typing.get_type_hints() which raises TypeError on callable class instances. Fall back to inspecting the __call__ method so that the Context parameter is properly detected and excluded from the tool's JSON schema. Fixes #1974 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 62575ed commit 05942ba

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

src/mcp/server/mcpserver/utilities/context_injection.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ def find_context_parameter(fn: Callable[..., Any]) -> str | None:
2222
"""
2323
from mcp.server.mcpserver.server import Context
2424

25+
# Handle callable class instances by inspecting __call__ method
26+
target = fn
27+
if not (inspect.isfunction(fn) or inspect.ismethod(fn)):
28+
if callable(fn) and hasattr(fn, "__call__"):
29+
target = fn.__call__
30+
2531
# Get type hints to properly resolve string annotations
2632
try:
27-
hints = typing.get_type_hints(fn)
33+
hints = typing.get_type_hints(target)
2834
except Exception: # pragma: lax no cover
2935
# If we can't resolve type hints, we can't find the context parameter
3036
return None

tests/server/mcpserver/test_tool_manager.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,25 @@ async def async_tool(x: int, ctx: Context[ServerSessionT, None]) -> str:
385385
result = await manager.call_tool("async_tool", {"x": 42}, context=ctx)
386386
assert result == "42"
387387

388+
@pytest.mark.anyio
389+
async def test_context_injection_callable_class(self):
390+
"""Test that context is properly injected for callable class instances."""
391+
392+
class MyTool:
393+
async def __call__(
394+
self, x: int, ctx: Context[ServerSessionT, None]
395+
) -> str:
396+
assert isinstance(ctx, Context)
397+
return str(x)
398+
399+
manager = ToolManager()
400+
manager.add_tool(MyTool(), name="callable_tool")
401+
402+
mcp = MCPServer()
403+
ctx = mcp.get_context()
404+
result = await manager.call_tool("callable_tool", {"x": 42}, context=ctx)
405+
assert result == "42"
406+
388407
@pytest.mark.anyio
389408
async def test_context_optional(self):
390409
"""Test that context is optional when calling tools."""

0 commit comments

Comments
 (0)