Anthropic API¶
🤖 AI Summary
Install: pip install anthropic mcp. Define tools with input_schema. Create MCPToolExecutor class to call MCP server. Implement agentic loop: call Claude with tools → check stop_reason → if tool_use, execute via MCP, return results → repeat until end_turn. Supports streaming with messages.stream().
Integrate MCP tools directly with Claude API for full control.
Overview¶
This approach gives you complete control over the tool calling loop. You define tools for Claude, handle tool calls yourself by calling the MCP server, and return results to Claude.
Install¶
Setup¶
import anthropic
import asyncio
import json
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
client = anthropic.Anthropic()
# Tool definitions for Claude
TOOLS = [
{
"name": "get_hero_deaths",
"description": "Get all hero deaths in a Dota 2 match. Returns killer, victim, ability, and timing for each death.",
"input_schema": {
"type": "object",
"properties": {
"match_id": {
"type": "integer",
"description": "The Dota 2 match ID"
}
},
"required": ["match_id"]
}
},
{
"name": "get_fight_combat_log",
"description": "Get combat log for a fight around a specific time. Auto-detects fight boundaries.",
"input_schema": {
"type": "object",
"properties": {
"match_id": {"type": "integer"},
"reference_time": {"type": "number", "description": "Game time in seconds"},
"hero": {"type": "string", "description": "Optional hero to anchor detection"}
},
"required": ["match_id", "reference_time"]
}
},
{
"name": "get_objective_kills",
"description": "Get Roshan, tormentor, tower, and barracks kills.",
"input_schema": {
"type": "object",
"properties": {
"match_id": {"type": "integer"}
},
"required": ["match_id"]
}
},
{
"name": "get_item_purchases",
"description": "Get item purchase timings for heroes.",
"input_schema": {
"type": "object",
"properties": {
"match_id": {"type": "integer"},
"hero_filter": {"type": "string", "description": "Optional hero name filter"}
},
"required": ["match_id"]
}
}
]
MCP Tool Executor¶
class MCPToolExecutor:
def __init__(self, server_path: str):
self.server_path = server_path
async def execute(self, tool_name: str, tool_input: dict) -> str:
server_params = StdioServerParameters(
command="uv",
args=["run", "python", "dota_match_mcp_server.py"],
cwd=self.server_path
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(tool_name, arguments=tool_input)
return result.content[0].text
executor = MCPToolExecutor("/path/to/mcp-replay-dota2")
Agentic Loop¶
async def analyze_match(user_message: str):
messages = [{"role": "user", "content": user_message}]
while True:
# Call Claude
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=TOOLS,
messages=messages
)
# Check if done
if response.stop_reason == "end_turn":
# Extract final text response
for block in response.content:
if block.type == "text":
return block.text
return ""
# Handle tool use
if response.stop_reason == "tool_use":
# Add assistant message
messages.append({"role": "assistant", "content": response.content})
# Process each tool call
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f"Calling {block.name}({block.input})")
# Execute via MCP
result = await executor.execute(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
# Add tool results
messages.append({"role": "user", "content": tool_results})
# Run
result = asyncio.run(analyze_match(
"Analyze match 8461956309. Focus on the first blood and subsequent laning phase."
))
print(result)
Streaming Response¶
async def analyze_match_streaming(user_message: str):
messages = [{"role": "user", "content": user_message}]
while True:
with client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=TOOLS,
messages=messages
) as stream:
response = stream.get_final_message()
if response.stop_reason == "end_turn":
for block in response.content:
if block.type == "text":
print(block.text)
break
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = await executor.execute(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
messages.append({"role": "user", "content": tool_results})
asyncio.run(analyze_match_streaming("Why did Radiant lose match 8461956309?"))
With System Prompt¶
SYSTEM_PROMPT = """You are an expert Dota 2 analyst. When analyzing matches:
1. Start by getting hero deaths to understand the flow of the game
2. For important deaths, get the fight combat log to understand what happened
3. Check objectives to understand macro gameplay
4. Always explain findings in terms casual players can understand
Focus on actionable insights - what could the losing team have done differently?"""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
system=SYSTEM_PROMPT,
tools=TOOLS,
messages=[{"role": "user", "content": "Analyze match 8461956309"}]
)