Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/claude_agent_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,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")
Expand Down
8 changes: 8 additions & 0 deletions src/claude_agent_sdk/_internal/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
83 changes: 83 additions & 0 deletions tests/test_sdk_mcp_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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 ---


Expand Down