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
8 changes: 8 additions & 0 deletions tests/interaction/_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=(
Expand Down
25 changes: 25 additions & 0 deletions tests/interaction/mcpserver/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
CallToolResult,
ElicitRequestURLParams,
ErrorData,
ImageContent,
LoggingMessageNotification,
LoggingMessageNotificationParams,
TextContent,
Expand Down Expand Up @@ -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:
Expand Down
Loading