Skip to content

Commit 73ec962

Browse files
committed
fix: correct MCPServer.call_tool return type and remove dead code
1 parent 3eb5799 commit 73ec962

2 files changed

Lines changed: 29 additions & 11 deletions

File tree

docs/migration.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,26 @@ async def my_tool(x: int, ctx: Context) -> str:
428428

429429
The internal layers (`ToolManager.call_tool`, `Tool.run`, `Prompt.render`, `ResourceTemplate.create_resource`, etc.) now require `context` as a positional argument.
430430

431+
### `MCPServer.call_tool()` return type corrected
432+
433+
`MCPServer.call_tool()`'s return type signature has been corrected from `Sequence[ContentBlock] | dict[str, Any]` to `CallToolResult | Sequence[ContentBlock] | tuple[Sequence[ContentBlock], dict[str, Any]]` to match what the internal tool manager actually returns when converting tool results.
434+
435+
**Before (v1):**
436+
437+
```python
438+
async def call_tool(
439+
self, name: str, arguments: dict[str, Any], context: Context[LifespanResultT, Any] | None = None
440+
) -> Sequence[ContentBlock] | dict[str, Any]:
441+
```
442+
443+
**After (v2):**
444+
445+
```python
446+
async def call_tool(
447+
self, name: str, arguments: dict[str, Any], context: Context[LifespanResultT, Any] | None = None
448+
) -> CallToolResult | Sequence[ContentBlock] | tuple[Sequence[ContentBlock], dict[str, Any]]:
449+
```
450+
431451
### Registering lowlevel handlers on `MCPServer` (workaround)
432452

433453
`MCPServer` does not expose public APIs for `subscribe_resource`, `unsubscribe_resource`, or `set_logging_level` handlers. In v1, the workaround was to reach into the private lowlevel server and use its decorator methods:

src/mcp/server/mcpserver/server.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import base64
66
import inspect
7-
import json
87
import re
98
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
109
from contextlib import AbstractAsyncContextManager, asynccontextmanager
@@ -322,14 +321,6 @@ async def _handle_call_tool(
322321
content=list(unstructured_content), # type: ignore[arg-type]
323322
structured_content=structured_content, # type: ignore[arg-type]
324323
)
325-
if isinstance(result, dict): # pragma: no cover
326-
# TODO: this code path is unreachable — convert_result never returns a raw dict.
327-
# The call_tool return type (Sequence[ContentBlock] | dict[str, Any]) is wrong
328-
# and needs to be cleaned up.
329-
return CallToolResult(
330-
content=[TextContent(type="text", text=json.dumps(result, indent=2))],
331-
structured_content=result,
332-
)
333324
return CallToolResult(content=list(result))
334325

335326
async def _handle_list_resources(
@@ -399,8 +390,15 @@ async def list_tools(self) -> list[MCPTool]:
399390

400391
async def call_tool(
401392
self, name: str, arguments: dict[str, Any], context: Context[LifespanResultT, Any] | None = None
402-
) -> Sequence[ContentBlock] | dict[str, Any]:
403-
"""Call a tool by name with arguments."""
393+
) -> CallToolResult | Sequence[ContentBlock] | tuple[Sequence[ContentBlock], dict[str, Any]]:
394+
"""Call a tool by name with arguments.
395+
396+
Returns:
397+
CallToolResult: If the tool returned a CallToolResult directly.
398+
Sequence[ContentBlock]: If the tool returned unstructured content and has no output schema.
399+
tuple[Sequence[ContentBlock], dict[str, Any]]: If the tool has an output schema,
400+
returning both unstructured content and structured content.
401+
"""
404402
if context is None:
405403
context = Context(mcp_server=self)
406404
return await self._tool_manager.call_tool(name, arguments, context, convert_result=True)

0 commit comments

Comments
 (0)