|
4 | 4 |
|
5 | 5 | import base64 |
6 | 6 | import inspect |
7 | | -import json |
8 | 7 | import re |
9 | 8 | from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence |
10 | 9 | from contextlib import AbstractAsyncContextManager, asynccontextmanager |
11 | | -from typing import Any, Generic, Literal, TypeVar, overload |
| 10 | +from typing import Any, Generic, Literal, TypeAlias, TypeVar, cast, overload |
12 | 11 |
|
13 | 12 | import anyio |
14 | 13 | import pydantic_core |
|
76 | 75 |
|
77 | 76 | _CallableT = TypeVar("_CallableT", bound=Callable[..., Any]) |
78 | 77 |
|
| 78 | +ToolResult: TypeAlias = CallToolResult | Sequence[ContentBlock] | tuple[Sequence[ContentBlock], dict[str, Any]] |
| 79 | + |
79 | 80 |
|
80 | 81 | class Settings(BaseSettings, Generic[LifespanResultT]): |
81 | 82 | """MCPServer settings. |
@@ -317,18 +318,10 @@ async def _handle_call_tool( |
317 | 318 | if isinstance(result, CallToolResult): |
318 | 319 | return result |
319 | 320 | if isinstance(result, tuple) and len(result) == 2: |
320 | | - unstructured_content, structured_content = result |
321 | | - return CallToolResult( |
322 | | - content=list(unstructured_content), # type: ignore[arg-type] |
323 | | - structured_content=structured_content, # type: ignore[arg-type] |
324 | | - ) |
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. |
| 321 | + unstructured_content, structured_content = cast(tuple[Sequence[ContentBlock], dict[str, Any]], result) |
329 | 322 | return CallToolResult( |
330 | | - content=[TextContent(type="text", text=json.dumps(result, indent=2))], |
331 | | - structured_content=result, |
| 323 | + content=list(unstructured_content), |
| 324 | + structured_content=structured_content, |
332 | 325 | ) |
333 | 326 | return CallToolResult(content=list(result)) |
334 | 327 |
|
@@ -399,8 +392,15 @@ async def list_tools(self) -> list[MCPTool]: |
399 | 392 |
|
400 | 393 | async def call_tool( |
401 | 394 | 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.""" |
| 395 | + ) -> ToolResult: |
| 396 | + """Call a tool by name with arguments. |
| 397 | +
|
| 398 | + Returns: |
| 399 | + The tool result converted for the low-level handler: |
| 400 | + - a `CallToolResult` returned directly by the tool, |
| 401 | + - a sequence of content blocks for unstructured tools, or |
| 402 | + - a `(content, structured_content)` tuple for tools with structured output. |
| 403 | + """ |
404 | 404 | if context is None: |
405 | 405 | context = Context(mcp_server=self) |
406 | 406 | return await self._tool_manager.call_tool(name, arguments, context, convert_result=True) |
|
0 commit comments