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>
This commit is contained in:
Bill Easton 2026-04-14 11:10:23 -05:00 committed by GitHub
commit 4ea102b433
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 37 additions and 1 deletions

View file

@ -158,7 +158,13 @@ class ProxyTool(Tool):
name=backend_name, arguments=arguments, meta=meta
)
if result.isError:
raise ToolError(cast(mcp.types.TextContent, result.content[0]).text)
first = result.content[0] if result.content else None
if isinstance(first, mcp.types.TextContent):
raise ToolError(first.text)
elif first is None:
raise ToolError("Tool returned an error with no content")
else:
raise ToolError(f"Tool returned an error ({type(first).__name__})")
# Preserve backend's meta (includes task metadata for background tasks)
return ToolResult(
content=result.content,

View file

@ -330,6 +330,36 @@ class TestTools:
async with Client(proxy_server) as client:
await client.call_tool("error_tool", {})
async def test_error_tool_with_image_content(self, proxy_server):
"""Non-TextContent error responses should not crash with AttributeError."""
error_result = mcp_types.CallToolResult(
content=[
mcp_types.ImageContent(
type="image", data="abc123", mimeType="image/png"
)
],
isError=True,
)
with patch.object(
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
):
with pytest.raises(ToolError):
async with Client(proxy_server) as client:
await client.call_tool("error_tool", {})
async def test_error_tool_with_empty_content(self, proxy_server):
"""Error responses with empty content should not crash."""
error_result = mcp_types.CallToolResult(
content=[],
isError=True,
)
with patch.object(
Client, "call_tool_mcp", new_callable=AsyncMock, return_value=error_result
):
with pytest.raises(ToolError):
async with Client(proxy_server) as client:
await client.call_tool("error_tool", {})
async def test_call_tool_forwards_meta(self, fastmcp_server, proxy_server):
"""Test that metadata from proxied tool results is properly forwarded."""