diff --git a/src/claude_agent_sdk/__init__.py b/src/claude_agent_sdk/__init__.py index 85052364..cfd9c37f 100644 --- a/src/claude_agent_sdk/__init__.py +++ b/src/claude_agent_sdk/__init__.py @@ -248,7 +248,15 @@ def create_sdk_mcp_server( - ClaudeAgentOptions: Configuration for using servers with query() """ from mcp.server import Server - from mcp.types import ImageContent, TextContent, Tool + from mcp.types import ( + AudioContent, + CallToolResult, + EmbeddedResource, + ImageContent, + ResourceLink, + TextContent, + Tool, + ) # Create MCP server instance server = Server(name, version=version) @@ -317,9 +325,13 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any: result = await tool_def.handler(arguments) # Convert result to MCP format - # The decorator expects us to return the content, not a CallToolResult - # It will wrap our return value in CallToolResult - content: list[TextContent | ImageContent] = [] + content: list[ + TextContent + | ImageContent + | AudioContent + | ResourceLink + | EmbeddedResource + ] = [] if "content" in result: for item in result["content"]: if item.get("type") == "text": @@ -333,8 +345,9 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any: ) ) - # Return just the content list - the decorator wraps it - return content + return CallToolResult( + content=content, isError=result.get("is_error", False) + ) # Return SDK server configuration return McpSdkServerConfig(type="sdk", name=name, instance=server) diff --git a/src/claude_agent_sdk/_internal/query.py b/src/claude_agent_sdk/_internal/query.py index bd7512e0..d640ec36 100644 --- a/src/claude_agent_sdk/_internal/query.py +++ b/src/claude_agent_sdk/_internal/query.py @@ -500,8 +500,8 @@ async def _handle_sdk_mcp_request( ) response_data = {"content": content} - if hasattr(result.root, "is_error") and result.root.is_error: - response_data["is_error"] = True # type: ignore[assignment] + if hasattr(result.root, "isError") and result.root.isError: + response_data["isError"] = True # type: ignore[assignment] return { "jsonrpc": "2.0", diff --git a/tests/test_sdk_mcp_integration.py b/tests/test_sdk_mcp_integration.py index 67f9e66e..1d5304c8 100644 --- a/tests/test_sdk_mcp_integration.py +++ b/tests/test_sdk_mcp_integration.py @@ -148,6 +148,42 @@ async def fail_tool(args: dict[str, Any]) -> dict[str, Any]: assert "Expected error" in str(result.root.content[0].text) +@pytest.mark.asyncio +async def test_is_error_flag_propagated(): + """Test that is_error flag from tool result dict is propagated to CallToolResult.""" + + @tool("divide", "Divide two numbers", {"a": float, "b": float}) + async def divide(args: dict[str, Any]) -> dict[str, Any]: + if args["b"] == 0: + return { + "content": [{"type": "text", "text": "Division by zero"}], + "is_error": True, + } + return {"content": [{"type": "text", "text": str(args["a"] / args["b"])}]} + + server_config = create_sdk_mcp_server(name="error-flag-test", tools=[divide]) + server = server_config["instance"] + call_handler = server.request_handlers[CallToolRequest] + + # Test error case — is_error: True should be propagated + error_request = CallToolRequest( + method="tools/call", + params=CallToolRequestParams(name="divide", arguments={"a": 1, "b": 0}), + ) + result = await call_handler(error_request) + assert result.root.isError is True + assert result.root.content[0].text == "Division by zero" + + # Test success case — is_error should default to False + success_request = CallToolRequest( + method="tools/call", + params=CallToolRequestParams(name="divide", arguments={"a": 6, "b": 3}), + ) + result = await call_handler(success_request) + assert result.root.isError is not True + assert "2.0" in result.root.content[0].text + + @pytest.mark.asyncio async def test_mixed_servers(): """Test that SDK and external MCP servers can work together."""