Anthropic
The Anthropic provider formats Composio tools for Claude and executes the tool calls Claude returns. It works two ways:
- The Claude Messages API, where you run the tool-call loop yourself.
- The Claude Agent SDK, where the SDK runs the loop and Composio tools are exposed as an in-process MCP server.
Pick the tab that matches your integration.
The AnthropicProvider transforms Composio tools into the format the Claude Messages API expects, then executes the tool calls Claude requests and shapes the results back into Messages API content blocks.
Install
uv add composio composio_anthropic anthropicConfigure API Keys
Set COMPOSIO_API_KEY with your API key from Settings and ANTHROPIC_API_KEY with your Anthropic API key.
COMPOSIO_API_KEY=xxxxxxxxx
ANTHROPIC_API_KEY=xxxxxxxxxCreate session and run
Passing a session to handle_tool_calls / handleToolCalls requires composio newer than 0.19.0 (Python) or @composio/core ≥ 0.17.0 with @composio/anthropic ≥ 0.11.0 (TypeScript). On earlier versions, execute session tools with session.execute().
import json
import anthropic
from composio import Composio
from composio_anthropic import AnthropicProvider
composio = Composio(provider=AnthropicProvider())
client = anthropic.Anthropic()
# Create a session for your user
session = composio.create(user_id="user_123")
tools = session.tools()
messages = [
{"role": "user", "content": "Send an email to john@example.com with the subject 'Hello' and body 'Hello from Composio!'"}
]
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=4096,
tools=tools,
messages=messages,
)
# Agentic loop: keep executing tool calls until the model responds with text
while response.stop_reason == "tool_use":
tool_use_blocks = [block for block in response.content if block.type == "tool_use"]
results = composio.provider.handle_tool_calls(response=response, session=session)
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": tool_use_blocks[i].id, "content": json.dumps(result)}
for i, result in enumerate(results)
],
})
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=4096,
tools=tools,
messages=messages,
)
# Print final response
for block in response.content:
if block.type == "text":
print(block.text)Pass the session to handleToolCalls / handle_tool_calls when the model received tools from session.tools(). The helper preserves Anthropic input normalization and restored schema aliases before executing every call through that session. For tools fetched via tools.get, pass the user ID instead.
Provider specifics
A few things are specific to the Anthropic provider:
- Tool caching. Pass
cacheTools: trueto the constructor (new AnthropicProvider({ cacheTools: true })) to attach Anthropic's ephemeralcache_controlto every tool definition and tool-result block. This lets Claude reuse cached tool schemas across requests and can cut prompt cost when you send the same large tool set on every turn. handleToolCallsreturns Messages API content, not raw strings. TypeScript returns a ready-to-appendusermessage oftool_resultblocks. Python returns raw results in model-call order, which the sample wraps intotool_resultblocks.- String-encoded tool inputs are handled for you. Claude occasionally emits a tool's
inputas a JSON string instead of an object. The provider normalizes it before both direct and session execution. cacheToolsonly. The constructor takes no other options. There is no agentic execution in this provider; you run the loop (Messages API) or hand the loop to the Claude Agent SDK (Agent SDK tab).
Next
What is a session?
How sessions scope users, tools, and auth, and how to reuse them across requests.