Skip to content

Commit 4ea102b

Browse files
strawgateclaude
andauthored
fix: ProxyTool crashes on non-TextContent error responses (#3926)
* fix: handle non-TextContent error responses in ProxyTool 🤖 Generated with Claude Code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Avoid serializing binary content into ToolError messages Use type name instead of str(content) to prevent dumping large base64 payloads into error messages. 🤖 Generated with Claude Code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ruff format fix 🤖 Generated with Claude Code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 757678b commit 4ea102b

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

src/fastmcp/server/providers/proxy.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,13 @@ async def run(
158158
name=backend_name, arguments=arguments, meta=meta
159159
)
160160
if result.isError:
161-
raise ToolError(cast(mcp.types.TextContent, result.content[0]).text)
161+
first = result.content[0] if result.content else None
162+
if isinstance(first, mcp.types.TextContent):
163+
raise ToolError(first.text)
164+
elif first is None:
165+
raise ToolError("Tool returned an error with no content")
166+
else:
167+
raise ToolError(f"Tool returned an error ({type(first).__name__})")
162168
# Preserve backend's meta (includes task metadata for background tasks)
163169
return ToolResult(
164170
content=result.content,

tests/server/providers/proxy/test_proxy_server.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,36 @@ async def test_error_tool_raises_error(self, proxy_server):
330330
async with Client(proxy_server) as client:
331331
await client.call_tool("error_tool", {})
332332

333+
async def test_error_tool_with_image_content(self, proxy_server):
334+
"""Non-TextContent error responses should not crash with AttributeError."""
335+
error_result = mcp_types.CallToolResult(
336+
content=[
337+
mcp_types.ImageContent(
338+
type="image", data="abc123", mimeType="image/png"
339+
)
340+
],
341+
isError=True,
342+
)
343+
with patch.object(
344+
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
345+
):
346+
with pytest.raises(ToolError):
347+
async with Client(proxy_server) as client:
348+
await client.call_tool("error_tool", {})
349+
350+
async def test_error_tool_with_empty_content(self, proxy_server):
351+
"""Error responses with empty content should not crash."""
352+
error_result = mcp_types.CallToolResult(
353+
content=[],
354+
isError=True,
355+
)
356+
with patch.object(
357+
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
358+
):
359+
with pytest.raises(ToolError):
360+
async with Client(proxy_server) as client:
361+
await client.call_tool("error_tool", {})
362+
333363
async def test_call_tool_forwards_meta(self, fastmcp_server, proxy_server):
334364
"""Test that metadata from proxied tool results is properly forwarded."""
335365

0 commit comments

Comments
 (0)