ClaudeSDKClient streaming mode: deferred MCP tools fail to execute after ToolSearch
Description
When using ClaudeSDKClient from claude-code-sdk (Python) with MCP servers, deferred MCP tools are found via ToolSearch but never actually executed. The conversation ends after the first ResultMessage with num_turns=1, even when max_turns is set to a higher value (e.g., 20).
The same MCP tools work correctly when using claude --print --mcp-config <file> directly via CLI.
Steps to Reproduce
from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient, create_sdk_mcp_server, tool
@tool("greet", "Say hello to someone", {"name": str})
async def greet(args):
return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
server = create_sdk_mcp_server("test-tools", tools=[greet])
options = ClaudeCodeOptions(
system_prompt="You are a helpful assistant. Use the greet tool when asked.",
permission_mode="bypassPermissions",
max_turns=20,
mcp_servers={"test-tools": server},
)
async with ClaudeSDKClient(options) as client:
await client.query("Use the greet tool to say hello to Alice")
async for msg in client.receive_response():
print(type(msg).__name__, msg)
Expected behavior
The model should:
- Find the
greettool viaToolSearch - Call the
greettool with{"name": "Alice"} - Return the tool result
Actual behavior
The model:
- Finds the tool via
ToolSearch({'query': 'select:mcp__test-tools__greet', 'max_results': 1}) - Immediately returns a
ResultMessagewithnum_turns=1 - The actual MCP tool is never called
Root Cause Analysis
ClaudeSDKClient uses the streaming/control-protocol mode (--output-format stream-json --input-format stream-json with CLAUDE_CODE_ENTRYPOINT=sdk-py). In contrast, the working claude --print --mcp-config path uses the standard --print mode.
In the streaming SDK mode, after the CLI finds a deferred MCP tool via ToolSearch, it does not proceed to the actual tool invocation. Instead, it emits the first ResultMessage and closes the conversation. receive_response() ends on that ResultMessage, so the client-side cannot recover.
max_turns=20 is not exhausted — the CLI itself decides to complete after 1 turn.
Also tried (same result):
McpStdioServerConfig(subprocess-based MCP server) instead ofcreate_sdk_mcp_server- Removing
allowed_toolsconstraint - Various
cwdsettings
Workaround
Using subprocess.run(["claude", "--print", "--mcp-config", config_path, "-p", prompt]) instead of ClaudeSDKClient. This uses the standard --print path where MCP tools work correctly.
Environment
claude-code-sdk: 0.0.25 (latest on PyPI)- Claude Code CLI: 2.1.87
- Python: 3.13
- OS: Linux (Kubernetes container)
- Auth: OAuth (Claude Max subscription)
Additional note
rate_limit_event message type also causes MessageParseError in SDK 0.0.25 — unrelated but worth noting.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗