Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions src/mcp/server/mcpserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import base64
import inspect
import json
import re
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
from contextlib import AbstractAsyncContextManager, asynccontextmanager
Expand Down Expand Up @@ -322,14 +321,6 @@ async def _handle_call_tool(
content=list(unstructured_content), # type: ignore[arg-type]
structured_content=structured_content, # type: ignore[arg-type]
)
if isinstance(result, dict): # pragma: no cover
# TODO: this code path is unreachable — convert_result never returns a raw dict.
# The call_tool return type (Sequence[ContentBlock] | dict[str, Any]) is wrong
# and needs to be cleaned up.
return CallToolResult(
content=[TextContent(type="text", text=json.dumps(result, indent=2))],
structured_content=result,
)
return CallToolResult(content=list(result))

async def _handle_list_resources(
Expand Down Expand Up @@ -399,7 +390,7 @@ async def list_tools(self) -> list[MCPTool]:

async def call_tool(
self, name: str, arguments: dict[str, Any], context: Context[LifespanResultT, Any] | None = None
) -> Sequence[ContentBlock] | dict[str, Any]:
) -> CallToolResult | Sequence[ContentBlock] | tuple[Sequence[ContentBlock], dict[str, Any]]:
"""Call a tool by name with arguments."""
if context is None:
context = Context(mcp_server=self)
Expand Down
24 changes: 22 additions & 2 deletions tests/server/mcpserver/test_func_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from pydantic import BaseModel, Field

from mcp.server.mcpserver.exceptions import InvalidSignature
from mcp.server.mcpserver.utilities.func_metadata import func_metadata
from mcp.types import CallToolResult
from mcp.server.mcpserver.utilities.func_metadata import FuncMetadata, func_metadata
from mcp.types import CallToolResult, TextContent


class SomeInputModelA(BaseModel):
Expand Down Expand Up @@ -203,6 +203,26 @@ def func_with_many_params(keep_this: int, skip_this: str, also_keep: float, also
assert model.also_keep == 2.5 # type: ignore


def test_convert_result_serializes_unstructured_dict_to_text_content():
"""Unstructured dict returns are content blocks, not raw dict values."""

def func_dict(): # pragma: no cover
return {"ok": True, "count": 2}

meta: FuncMetadata = func_metadata(func_dict)

assert meta.output_schema is None
assert meta.convert_result({"ok": True, "count": 2}) == [
TextContent(
type="text",
text="""{
"ok": true,
"count": 2
}""",
)
]


def test_structured_output_dict_str_types():
"""Test that dict[str, T] types are handled without wrapping."""

Expand Down
Loading