diff --git a/src/claude_agent_sdk/__init__.py b/src/claude_agent_sdk/__init__.py index 6403415b..7895ad83 100644 --- a/src/claude_agent_sdk/__init__.py +++ b/src/claude_agent_sdk/__init__.py @@ -453,6 +453,14 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any: mimeType=item["mimeType"], ) ) + elif item_type == "audio": + content.append( + AudioContent( + type="audio", + data=item["data"], + mimeType=item["mimeType"], + ) + ) elif item_type == "resource_link": parts = [] link_name = item.get("name") diff --git a/src/claude_agent_sdk/_internal/query.py b/src/claude_agent_sdk/_internal/query.py index 80b6d93c..985601fa 100644 --- a/src/claude_agent_sdk/_internal/query.py +++ b/src/claude_agent_sdk/_internal/query.py @@ -537,6 +537,14 @@ async def _handle_sdk_mcp_request( "mimeType": getattr(item, "mimeType", ""), } ) + elif item_type == "audio": + content.append( + { + "type": "audio", + "data": getattr(item, "data", ""), + "mimeType": getattr(item, "mimeType", ""), + } + ) elif item_type == "resource_link": parts = [] name = getattr(item, "name", None) diff --git a/tests/test_sdk_mcp_integration.py b/tests/test_sdk_mcp_integration.py index 29c9da5d..8635b64a 100644 --- a/tests/test_sdk_mcp_integration.py +++ b/tests/test_sdk_mcp_integration.py @@ -309,6 +309,48 @@ async def generate_chart(args: dict[str, Any]) -> dict[str, Any]: assert tool_executions[0]["args"]["title"] == "Sales Report" +@pytest.mark.asyncio +async def test_audio_content_support(): + """Test that tools can return audio content with base64 data.""" + + wav_data = base64.b64encode(b"RIFF....WAVEfmt ").decode("utf-8") + + @tool("generate_audio", "Generates an audio clip", {"prompt": str}) + async def generate_audio(args: dict[str, Any]) -> dict[str, Any]: + return { + "content": [ + {"type": "text", "text": f"Generated audio: {args['prompt']}"}, + { + "type": "audio", + "data": wav_data, + "mimeType": "audio/wav", + }, + ] + } + + server_config = create_sdk_mcp_server( + name="audio-test-server", version="1.0.0", tools=[generate_audio] + ) + server = server_config["instance"] + call_handler = server.request_handlers[CallToolRequest] + + request = CallToolRequest( + method="tools/call", + params=CallToolRequestParams( + name="generate_audio", + arguments={"prompt": "Dial tone"}, + ), + ) + result = await call_handler(request) + + assert len(result.root.content) == 2 + assert result.root.content[0].type == "text" + assert result.root.content[0].text == "Generated audio: Dial tone" + assert result.root.content[1].type == "audio" + assert result.root.content[1].data == wav_data + assert result.root.content[1].mimeType == "audio/wav" + + @pytest.mark.asyncio async def test_tool_annotations(): """Test that tool annotations are stored and flow through list_tools.""" @@ -715,6 +757,47 @@ async def link_tool(args: dict[str, Any]) -> dict[str, Any]: assert "https://api.example.com" in result_content[0]["text"] +@pytest.mark.asyncio +async def test_jsonrpc_bridge_audio_content(): + """Test that the JSONRPC bridge preserves audio content blocks.""" + from unittest.mock import AsyncMock + + from mcp.server import Server + from mcp.types import AudioContent, CallToolResult, ServerResult + + from claude_agent_sdk._internal.query import Query + + audio = AudioContent(type="audio", data="ZGF0YQ==", mimeType="audio/wav") + fake_result = ServerResult(root=CallToolResult(content=[audio])) + + server = Server("test-server") + handler = AsyncMock(return_value=fake_result) + server.request_handlers[CallToolRequest] = handler + + query_instance = Query.__new__(Query) + query_instance.sdk_mcp_servers = {"test": server} + + response = await query_instance._handle_sdk_mcp_request( + "test", + { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": {"name": "test_tool", "arguments": {}}, + }, + ) + + assert response is not None + result_content = response["result"]["content"] + assert result_content == [ + { + "type": "audio", + "data": "ZGF0YQ==", + "mimeType": "audio/wav", + } + ] + + # --- Tests for _python_type_to_json_schema and TypedDict schema conversion ---