Tools
The model
The daemon ships an embedded MCP server. When your /v1/messages request includes a tools[] array, the daemon writes those definitions to a per-Session tools file, signals claude through the MCP transport, and claude sees them as live tools. If the model decides to call one, the call comes back to your code as a tool_use content block.
This is the same shape the Anthropic API uses. Your SDK code doesn't change.
Round-trip protocol
A single tool round-trip is three messages:
- You: prompt +
tools[]definition. - Model:
assistantresponse containing atool_useblock. - You: next-turn
usermessage with atool_resultblock matching thetool_use_id.
The daemon handles the MCP forwarding transparently; you write standard Anthropic SDK code.
from anthropic import Anthropic
client = Anthropic(api_key="unused", base_url="http://localhost:7421")
tools = [{
"name": "get_weather",
"description": "Look up the current weather in a city.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}]
# Turn 1
msg = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
tools=tools,
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
)
# Inspect tool_use block
tool_call = next(b for b in msg.content if b.type == "tool_use")
print(tool_call.name) # 'get_weather'
print(tool_call.input) # {'city': 'Paris'}
print(tool_call.id) # 'toolu_abc123...'The tool_use_idis generated by the daemon's MCP server. Always include the assistant's full content array on the follow-up turn so claude can correlate.
# Turn 2 — feed result back
follow = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
tools=tools,
messages=[
{"role": "user", "content": "What's the weather in Paris?"},
{"role": "assistant", "content": msg.content},
{"role": "user", "content": [{
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": "Paris: 18°C, light rain",
}]},
],
)
print(follow.content[0].text) # natural-language responseTool definition shape
Match the Anthropic tool definition spec exactly. The input_schema must be a valid JSON Schema; the daemon passes it to claude unchanged.
{
"name": "execute_query",
"description": "Run a read-only SQL query against the analytics database.",
"input_schema": {
"type": "object",
"properties": {
"sql": {"type": "string", "description": "SELECT statement only"},
"max_rows": {"type": "integer", "minimum": 1, "maximum": 1000}
},
"required": ["sql"]
}
}Names must be unique within a request. Reuse the same tools[]array across turns of the same conversation — claude doesn't "remember" tools between turns; it only sees what's on the current request.
tool_result shape
The follow-up turn's tool_result block can carry a string OR structured content:
# String result
{"type": "tool_result", "tool_use_id": "toolu_abc", "content": "42"}
# Multi-block result (e.g., tool returned text + image)
{"type": "tool_result", "tool_use_id": "toolu_abc", "content": [
{"type": "text", "text": "Here's the chart:"},
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "..."}}
]}
# Error result
{"type": "tool_result", "tool_use_id": "toolu_abc",
"content": "Database connection refused", "is_error": True}Set is_error: true for tool execution failures; claude will reason about retry / fallback rather than treating the error string as a factual answer.
Inspecting tool calls
The daemon emits both the tool_use block (on the assistant message) and a stop_reason: "tool_use" flag on the response. Standard pattern:
if msg.stop_reason == "tool_use":
# one or more tool_use blocks in msg.content
for block in msg.content:
if block.type == "tool_use":
print(f"calling {block.name}({block.input})")
# ...execute tool, build tool_result, follow up
elif msg.stop_reason == "end_turn":
# model finished without calling a tool
print(msg.content[0].text)What's supported
- Multi-tool definitions per request — pass an array of as many tools as your context allows.
- Multi-tool calls per turn — the model can request several tools in one assistant response; handle each in parallel and feed all
tool_results on the next turn. - Tool use in sticky Sessions — the
X-IR-Session-IDheader works alongsidetools[]; the daemon preserves the MCP wiring across calls within the same Session.
What's NOT supported (today)
- Stand-alone MCP servers — the daemon's MCP server is internal; it doesn't accept connections from external MCP clients. If your use case is the inverse (your tooling exposes MCP to claude), see Claude Code's MCP docs on the v1.0 page (still relevant for that integration shape).
- Tool definition caching across requests — every
/v1/messagescall re-declares its tools. There's no per-Session "register these tools once" affordance. The overhead is small (~few ms per call) but worth knowing.
Where to go next
- Vision + document attachments → Attachments
- Tool calls in an agent loop → Agents Cookbook recipe 3
- Full API reference → API Reference