From 2d9c0890c01b122464471914b31f2ab42d10c694 Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Sat, 28 Feb 2026 06:55:14 +0100 Subject: [PATCH 1/2] 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 --- .../mcpserver/utilities/context_injection.py | 8 +++++++- tests/server/mcpserver/test_tool_manager.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/mcpserver/utilities/context_injection.py b/src/mcp/server/mcpserver/utilities/context_injection.py index 9cba83e86..cbfdfa4a4 100644 --- a/src/mcp/server/mcpserver/utilities/context_injection.py +++ b/src/mcp/server/mcpserver/utilities/context_injection.py @@ -22,9 +22,15 @@ def find_context_parameter(fn: Callable[..., Any]) -> str | None: """ from mcp.server.mcpserver.server import Context + # Handle callable class instances by inspecting __call__ method + target = fn + if not (inspect.isfunction(fn) or inspect.ismethod(fn)): + if callable(fn) and hasattr(fn, "__call__"): + target = fn.__call__ + # Get type hints to properly resolve string annotations try: - hints = typing.get_type_hints(fn) + hints = typing.get_type_hints(target) except Exception: # pragma: lax no cover # If we can't resolve type hints, we can't find the context parameter return None diff --git a/tests/server/mcpserver/test_tool_manager.py b/tests/server/mcpserver/test_tool_manager.py index 550bba50a..43a29530b 100644 --- a/tests/server/mcpserver/test_tool_manager.py +++ b/tests/server/mcpserver/test_tool_manager.py @@ -385,6 +385,25 @@ async def async_tool(x: int, ctx: Context[ServerSessionT, None]) -> str: result = await manager.call_tool("async_tool", {"x": 42}, context=ctx) assert result == "42" + @pytest.mark.anyio + async def test_context_injection_callable_class(self): + """Test that context is properly injected for callable class instances.""" + + class MyTool: + async def __call__( + self, x: int, ctx: Context[ServerSessionT, None] + ) -> str: + assert isinstance(ctx, Context) + return str(x) + + manager = ToolManager() + manager.add_tool(MyTool(), name="callable_tool") + + mcp = MCPServer() + ctx = mcp.get_context() + result = await manager.call_tool("callable_tool", {"x": 42}, context=ctx) + assert result == "42" + @pytest.mark.anyio async def test_context_optional(self): """Test that context is optional when calling tools.""" From b0fc099d46bf7ab7601fd2e3ca1f52383225919d Mon Sep 17 00:00:00 2001 From: g97iulio1609 Date: Sat, 28 Feb 2026 14:05:53 +0100 Subject: [PATCH 2/2] style: apply ruff format --- tests/server/mcpserver/test_tool_manager.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/server/mcpserver/test_tool_manager.py b/tests/server/mcpserver/test_tool_manager.py index 43a29530b..6e3477c87 100644 --- a/tests/server/mcpserver/test_tool_manager.py +++ b/tests/server/mcpserver/test_tool_manager.py @@ -390,9 +390,7 @@ async def test_context_injection_callable_class(self): """Test that context is properly injected for callable class instances.""" class MyTool: - async def __call__( - self, x: int, ctx: Context[ServerSessionT, None] - ) -> str: + async def __call__(self, x: int, ctx: Context[ServerSessionT, None]) -> str: assert isinstance(ctx, Context) return str(x)