diff --git a/tests/interaction/_requirements.py b/tests/interaction/_requirements.py index caed8905d..4bc4fe440 100644 --- a/tests/interaction/_requirements.py +++ b/tests/interaction/_requirements.py @@ -528,6 +528,14 @@ def __post_init__(self) -> None: "in content, not as a JSON-RPC error." ), ), + "tools:call:is-error-with-content": Requirement( + source="issue:#348", + behavior=( + "A tool can return a hand-built CallToolResult with isError true that carries arbitrary " + "content (e.g. an image), not just text; the content blocks and the isError flag reach the " + "caller intact." + ), + ), "tools:call:logging-mid-execution": Requirement( source=f"{SPEC_BASE_URL}/server/utilities/logging#log-message-notifications", behavior=( diff --git a/tests/interaction/mcpserver/test_tools.py b/tests/interaction/mcpserver/test_tools.py index 05135c128..9f33e303d 100644 --- a/tests/interaction/mcpserver/test_tools.py +++ b/tests/interaction/mcpserver/test_tools.py @@ -16,6 +16,7 @@ CallToolResult, ElicitRequestURLParams, ErrorData, + ImageContent, LoggingMessageNotification, LoggingMessageNotificationParams, TextContent, @@ -133,6 +134,30 @@ def add() -> None: assert result == snapshot(CallToolResult(content=[TextContent(text="Unknown tool: nope")], is_error=True)) +@requirement("tools:call:is-error-with-content") +async def test_tool_returning_call_tool_result_can_flag_is_error_on_non_text_content(connect: Connect) -> None: + """A tool may hand back a CallToolResult with is_error=True carrying non-text content. + + Raising an exception is the usual way to produce an is_error result, but that only yields a + text message. A tool that wants to report failure while returning richer content (here, an + image) can return a CallToolResult directly; MCPServer passes it through unchanged, so both the + image block and the is_error flag reach the caller. Regression lock-in for issue #348. + """ + mcp = MCPServer("imager") + + @mcp.tool() + def render() -> CallToolResult: + return CallToolResult( + content=[ImageContent(data="aW1n", mime_type="image/png")], + is_error=True, + ) + + async with connect(mcp) as client: + result = await client.call_tool("render", {}) + + assert result == snapshot(CallToolResult(content=[ImageContent(data="aW1n", mime_type="image/png")], is_error=True)) + + @requirement("mcpserver:tool:output-schema:model") @requirement("tools:call:structured-content:text-mirror") async def test_call_tool_model_return_becomes_structured_content(connect: Connect) -> None: