fix: correct MCPServer call_tool result type (#2816)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
This commit is contained in:
parent
f253682393
commit
fda4c54362
3 changed files with 63 additions and 45 deletions
|
|
@ -1038,7 +1038,8 @@ def test_structured_output_aliases():
|
|||
|
||||
# Check that the actual output uses aliases too
|
||||
result = ModelWithAliases(**{"first": "hello", "second": "world"})
|
||||
_, structured_content = meta.convert_result(result)
|
||||
structured_content = meta.convert_result(result).structured_content
|
||||
assert structured_content is not None
|
||||
|
||||
# The structured content should use aliases to match the schema
|
||||
assert "first" in structured_content
|
||||
|
|
@ -1050,7 +1051,8 @@ def test_structured_output_aliases():
|
|||
|
||||
# Also test the case where we have a model with defaults to ensure aliases work in all cases
|
||||
result_with_defaults = ModelWithAliases() # Uses default None values
|
||||
_, structured_content_defaults = meta.convert_result(result_with_defaults)
|
||||
structured_content_defaults = meta.convert_result(result_with_defaults).structured_content
|
||||
assert structured_content_defaults is not None
|
||||
|
||||
# Even with defaults, should use aliases in output
|
||||
assert "first" in structured_content_defaults
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from mcp.types import (
|
|||
INVALID_PARAMS,
|
||||
AudioContent,
|
||||
BlobResourceContents,
|
||||
CallToolResult,
|
||||
Completion,
|
||||
CompletionArgument,
|
||||
CompletionContext,
|
||||
|
|
@ -306,6 +307,29 @@ class TestServerTools:
|
|||
assert result.structured_content is not None
|
||||
assert result.structured_content == {"result": 3}
|
||||
|
||||
async def test_call_tool_always_returns_call_tool_result(self):
|
||||
mcp = MCPServer()
|
||||
|
||||
@mcp.tool()
|
||||
def direct() -> CallToolResult:
|
||||
return CallToolResult(content=[TextContent(type="text", text="direct")])
|
||||
|
||||
@mcp.tool(structured_output=False)
|
||||
def unstructured() -> str:
|
||||
return "plain"
|
||||
|
||||
@mcp.tool()
|
||||
def structured() -> int:
|
||||
return 3
|
||||
|
||||
assert await mcp.call_tool("direct", {}) == CallToolResult(content=[TextContent(type="text", text="direct")])
|
||||
assert await mcp.call_tool("unstructured", {}) == CallToolResult(
|
||||
content=[TextContent(type="text", text="plain")]
|
||||
)
|
||||
assert await mcp.call_tool("structured", {}) == CallToolResult(
|
||||
content=[TextContent(type="text", text="3")], structured_content={"result": 3}
|
||||
)
|
||||
|
||||
async def test_tool_image_helper(self, tmp_path: Path):
|
||||
# Create a test image
|
||||
image_path = tmp_path / "test.png"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from mcp.server.mcpserver import Context, MCPServer
|
|||
from mcp.server.mcpserver.exceptions import ToolError
|
||||
from mcp.server.mcpserver.tools import Tool, ToolManager
|
||||
from mcp.server.mcpserver.utilities.func_metadata import ArgModelBase, FuncMetadata
|
||||
from mcp.types import TextContent, ToolAnnotations
|
||||
from mcp.types import CallToolResult, TextContent, ToolAnnotations
|
||||
|
||||
|
||||
class TestAddTools:
|
||||
|
|
@ -455,7 +455,8 @@ class TestStructuredOutput:
|
|||
manager.add_tool(get_user)
|
||||
result = await manager.call_tool("get_user", {"user_id": 1}, Context(), convert_result=True)
|
||||
# don't test unstructured output here, just the structured conversion
|
||||
assert len(result) == 2 and result[1] == {"name": "John", "age": 30}
|
||||
assert isinstance(result, CallToolResult)
|
||||
assert result.structured_content == {"name": "John", "age": 30}
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_with_primitive_output(self):
|
||||
|
|
@ -470,7 +471,8 @@ class TestStructuredOutput:
|
|||
result = await manager.call_tool("double_number", {"n": 5}, Context())
|
||||
assert result == 10
|
||||
result = await manager.call_tool("double_number", {"n": 5}, Context(), convert_result=True)
|
||||
assert isinstance(result[0][0], TextContent) and result[1] == {"result": 10}
|
||||
assert isinstance(result, CallToolResult)
|
||||
assert isinstance(result.content[0], TextContent) and result.structured_content == {"result": 10}
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_with_typeddict_output(self):
|
||||
|
|
@ -510,7 +512,8 @@ class TestStructuredOutput:
|
|||
manager.add_tool(get_person)
|
||||
result = await manager.call_tool("get_person", {}, Context(), convert_result=True)
|
||||
# don't test unstructured output here, just the structured conversion
|
||||
assert len(result) == 2 and result[1] == expected_output
|
||||
assert isinstance(result, CallToolResult)
|
||||
assert result.structured_content == expected_output
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_with_list_output(self):
|
||||
|
|
@ -528,7 +531,8 @@ class TestStructuredOutput:
|
|||
result = await manager.call_tool("get_numbers", {}, Context())
|
||||
assert result == expected_list
|
||||
result = await manager.call_tool("get_numbers", {}, Context(), convert_result=True)
|
||||
assert isinstance(result[0][0], TextContent) and result[1] == expected_output
|
||||
assert isinstance(result, CallToolResult)
|
||||
assert isinstance(result.content[0], TextContent) and result.structured_content == expected_output
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_tool_without_structured_output(self):
|
||||
|
|
|
|||
Loading…
Reference in a new issue