mirror of
https://github.com/anthropics/claude-code-sdk-python.git
synced 2025-12-23 09:19:52 +00:00
fix: preserve is_error attribute from MCP tool output in ToolResultBlock
Fix bug where the is_error attribute from MCP tool output was being
disregarded instead of being passed to the resulting ToolResultBlock.
The issue was in the create_sdk_mcp_server function's call_tool handler,
which was only processing the content field from tool results and ignoring
the is_error field. Now when a tool returns {"is_error": True}, the handler
raises an exception so the MCP decorator can properly set isError=True in
the CallToolResult.
Changes:
- Modified call_tool handler in create_sdk_mcp_server to check for is_error field
- When is_error=True, raise RuntimeError with tool's error message
- Added comprehensive test for is_error field handling
Fixes issue where ToolResultBlock.is_error was None instead of True when
MCP tools returned error results.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f896cd6f7f
commit
e15e3f82ae
2 changed files with 73 additions and 1 deletions
|
|
@ -270,7 +270,20 @@ def create_sdk_mcp_server(
|
|||
# Call the tool's handler with arguments
|
||||
result = await tool_def.handler(arguments)
|
||||
|
||||
# Convert result to MCP format
|
||||
# Check if the tool result indicates an error
|
||||
# If so, we need to raise an exception so the MCP decorator
|
||||
# can properly set isError=True in the CallToolResult
|
||||
if result.get("is_error", False):
|
||||
# Extract error message from content if available
|
||||
error_message = "Tool execution failed"
|
||||
if "content" in result and result["content"]:
|
||||
for item in result["content"]:
|
||||
if item.get("type") == "text" and item.get("text"):
|
||||
error_message = item["text"]
|
||||
break
|
||||
raise RuntimeError(error_message)
|
||||
|
||||
# Convert result to MCP format for successful execution
|
||||
# The decorator expects us to return the content, not a CallToolResult
|
||||
# It will wrap our return value in CallToolResult
|
||||
content = []
|
||||
|
|
|
|||
|
|
@ -145,6 +145,65 @@ async def test_error_handling():
|
|||
assert "Expected error" in str(result.root.content[0].text)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_is_error_field_handling():
|
||||
"""Test that tools can return is_error field and it's properly handled."""
|
||||
|
||||
@tool("error_tool", "Returns error via is_error field", {})
|
||||
async def error_tool(args: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"content": [{"type": "text", "text": "Tool error message"}],
|
||||
"is_error": True,
|
||||
}
|
||||
|
||||
@tool("success_tool", "Returns success with is_error=False", {})
|
||||
async def success_tool(args: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"content": [{"type": "text", "text": "Tool success message"}],
|
||||
"is_error": False,
|
||||
}
|
||||
|
||||
@tool("normal_tool", "Returns without is_error field", {})
|
||||
async def normal_tool(args: dict[str, Any]) -> dict[str, Any]:
|
||||
return {"content": [{"type": "text", "text": "Normal response"}]}
|
||||
|
||||
server_config = create_sdk_mcp_server(
|
||||
name="error-field-test", tools=[error_tool, success_tool, normal_tool]
|
||||
)
|
||||
|
||||
server = server_config["instance"]
|
||||
from mcp.types import CallToolRequest, CallToolRequestParams
|
||||
|
||||
call_handler = server.request_handlers[CallToolRequest]
|
||||
|
||||
# Test error_tool - should have isError=True
|
||||
error_request = CallToolRequest(
|
||||
method="tools/call",
|
||||
params=CallToolRequestParams(name="error_tool", arguments={}),
|
||||
)
|
||||
error_result = await call_handler(error_request)
|
||||
assert error_result.root.isError is True
|
||||
assert "Tool error message" in error_result.root.content[0].text
|
||||
|
||||
# Test success_tool - should have isError=False
|
||||
success_request = CallToolRequest(
|
||||
method="tools/call",
|
||||
params=CallToolRequestParams(name="success_tool", arguments={}),
|
||||
)
|
||||
success_result = await call_handler(success_request)
|
||||
assert success_result.root.isError is False
|
||||
assert "Tool success message" in success_result.root.content[0].text
|
||||
|
||||
# Test normal_tool - should have isError=False (default)
|
||||
normal_request = CallToolRequest(
|
||||
method="tools/call",
|
||||
params=CallToolRequestParams(name="normal_tool", arguments={}),
|
||||
)
|
||||
normal_result = await call_handler(normal_request)
|
||||
assert normal_result.root.isError is False
|
||||
assert "Normal response" in normal_result.root.content[0].text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mixed_servers():
|
||||
"""Test that SDK and external MCP servers can work together."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue