Skip to content

Commit c2e1d55

Browse files
cognis-digitalclaude
andcommitted
test: lock in CallToolResult(is_error=True) with non-text content (#348)
A FastMCP tool can already report failure while returning rich (non-text) content by returning a CallToolResult with is_error=True directly -- MCPServer passes it through unchanged. This was untested and undocumented, which is what issue #348 reports ("no way to set isError=True for arbitrary tool result content"). Add a regression test proving an image content block plus is_error=True round-trips to the caller over all transports, and a matching requirements manifest entry (source issue:#348). Test-only; no library change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7267818 commit c2e1d55

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

tests/interaction/_requirements.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,14 @@ def __post_init__(self) -> None:
528528
"in content, not as a JSON-RPC error."
529529
),
530530
),
531+
"tools:call:is-error-with-content": Requirement(
532+
source="issue:#348",
533+
behavior=(
534+
"A tool can return a hand-built CallToolResult with isError true that carries arbitrary "
535+
"content (e.g. an image), not just text; the content blocks and the isError flag reach the "
536+
"caller intact."
537+
),
538+
),
531539
"tools:call:logging-mid-execution": Requirement(
532540
source=f"{SPEC_BASE_URL}/server/utilities/logging#log-message-notifications",
533541
behavior=(

tests/interaction/mcpserver/test_tools.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
CallToolResult,
1717
ElicitRequestURLParams,
1818
ErrorData,
19+
ImageContent,
1920
LoggingMessageNotification,
2021
LoggingMessageNotificationParams,
2122
TextContent,
@@ -133,6 +134,30 @@ def add() -> None:
133134
assert result == snapshot(CallToolResult(content=[TextContent(text="Unknown tool: nope")], is_error=True))
134135

135136

137+
@requirement("tools:call:is-error-with-content")
138+
async def test_tool_returning_call_tool_result_can_flag_is_error_on_non_text_content(connect: Connect) -> None:
139+
"""A tool may hand back a CallToolResult with is_error=True carrying non-text content.
140+
141+
Raising an exception is the usual way to produce an is_error result, but that only yields a
142+
text message. A tool that wants to report failure while returning richer content (here, an
143+
image) can return a CallToolResult directly; MCPServer passes it through unchanged, so both the
144+
image block and the is_error flag reach the caller. Regression lock-in for issue #348.
145+
"""
146+
mcp = MCPServer("imager")
147+
148+
@mcp.tool()
149+
def render() -> CallToolResult:
150+
return CallToolResult(
151+
content=[ImageContent(data="aW1n", mime_type="image/png")],
152+
is_error=True,
153+
)
154+
155+
async with connect(mcp) as client:
156+
result = await client.call_tool("render", {})
157+
158+
assert result == snapshot(CallToolResult(content=[ImageContent(data="aW1n", mime_type="image/png")], is_error=True))
159+
160+
136161
@requirement("mcpserver:tool:output-schema:model")
137162
@requirement("tools:call:structured-content:text-mirror")
138163
async def test_call_tool_model_return_becomes_structured_content(connect: Connect) -> None:

0 commit comments

Comments
 (0)