Python framework for building agents using claude-code-sdk with programmable tools
Python
28
58 commits
updated Jan 25, 2026
claude-code-sdk wrapper for enhanced developer experience with easy setup and runtime isolation using Docker
A Python framework that wraps claude-code-sdk to provide better developer experience through decorator-based tools, runtime isolation, and simplified agent development. Built for production safety with Docker containers that ensure controlled tool execution and consistent behavior across all environments.
Working directly with claude-code-sdk presents two major challenges:
Claude Agent Toolkit solves these issues through:
@tool decorator converts any Python function into a Claude-compatible tool"An intuitive and stable development experience similar to Google's ADK"
Before (Direct claude-code-sdk):
# Manual tool naming and complex schema definition required
@tool("greet", "Greet a user", {"name": str})
async def greet_user(args):
return {
"content": [
{"type": "text", "text": f"Hello, {args['name']}!"}
]
}
# Tool functions and MCP server are decoupled - difficult to maintain at scale
server = create_sdk_mcp_server(
name="my-tools",
version="1.0.0",
tools=[greet_user]
)
# At Runtime:
# β Subprocess can access system tools (Read, LS, Grep)
# β Manual environment configuration required
# β No control over Claude Code's tool access
# β Risk of unintended system interactions
After (Claude Agent Toolkit):
# Intuitive class-based tool definition with integrated MCP server
class CalculatorTool(BaseTool):
@tool()
async def add(self, a: float, b: float) -> dict:
"""Adds two numbers together"""
return {"result": a + b}
# Single line agent creation with controlled tool access
agent = Agent(tools=[CalculatorTool()])
# At Runtime:
# β
Docker container runs only your defined tools
# β
No access to system tools (Read, LS, Grep)
# β
Clean, isolated, predictable execution environment
# β
Complete control over Claude Code's capabilities
| Feature | claude-code-sdk | Claude Agent Toolkit |
|---|---|---|
| Custom Tools | Manual schema definition, No parallel execution support | Simple and intuitive class-based definition with @tool decorator, Built-in parallel execution with parallel=True |
| Runtime Isolation | No built-in isolation You need to design your own | Docker by default Allows only tools you explicitly added |
| Environment Consistency | Manual environment setup Explicit tool/option configuration required | Zero setup needed Works out of the box |
| Setup Complexity | ~20 lines just for ClaudeCodeOptions configuration | ~25 lines for complete agent with calculatorAgent.run(verbose=True) shows all responses |
| Built-in Tools | Build everything from scratch | FileSystemTool with permission control DataTransferTool for formatted output handling |
| Best For | Using Claude Code as-is Minimal dependencies only | Fast development Using Claude Code as reasoning engine (like LangGraph agents) |
from claude_agent_toolkit import Agent, BaseTool, tool
# 1. Create a custom tool with @tool decorator
class CalculatorTool(BaseTool):
@tool()
async def add(self, a: float, b: float) -> dict:
"""Adds two numbers together"""
result = a + b
return {
"operation": f"{a} + {b}",
"result": result,
"message": f"The result of adding {a} and {b} is {result}"
}
# 2. Create and run an agent
async def main():
agent = Agent(
system_prompt="You are a helpful calculator assistant",
tools=[CalculatorTool()],
model="sonnet" # haiku, sonnet, or opus
)
result = await agent.run("What is 15 + 27?")
print(result) # Claude will use your tool and return the answer
if __name__ == "__main__":
import asyncio
asyncio.run(main())
uv package manager# Using pip
pip install claude-agent-toolkit
# Using uv (recommended)
uv add claude-agent-toolkit
# Using poetry
poetry add claude-agent-toolkit
Get your token by running claude setup-token in your terminal, then:
export CLAUDE_CODE_OAUTH_TOKEN='your-token-here'
# Clone examples (optional)
git clone https://github.com/cheolwanpark/claude-agent-toolkit.git
cd claude-agent-toolkit/src/examples/calculator
python main.py
from claude_agent_toolkit import Agent, BaseTool, tool, ExecutorType
class MyTool(BaseTool):
def __init__(self):
super().__init__()
self.counter = 0 # Explicit data management
@tool()
async def increment(self) -> dict:
"""Increment counter and return value"""
self.counter += 1
return {"value": self.counter}
# Docker executor (default, production-ready)
agent = Agent(tools=[MyTool()])
# Subprocess executor (faster startup, development)
agent = Agent(tools=[MyTool()], executor=ExecutorType.SUBPROCESS)
result = await agent.run("Increment the counter twice")
# Fast and efficient for simple tasks
weather_agent = Agent(
tools=[weather_tool],
model="haiku"
)
# Balanced performance (default)
general_agent = Agent(
tools=[calc_tool, weather_tool],
model="sonnet"
)
# Most capable for complex reasoning
analysis_agent = Agent(
tools=[analysis_tool],
model="opus"
)
# Override per query
result = await weather_agent.run(
"Complex weather pattern analysis",
model="opus"
)
class HeavyComputeTool(BaseTool):
@tool(parallel=True, timeout_s=120)
def process_data(self, data: str) -> dict:
"""Heavy computation"""
# Sync function - runs in separate process
import time
time.sleep(5) # Simulate heavy work
return {"processed": f"result_{data}"}
from claude_agent_toolkit import (
Agent, BaseTool,
ConfigurationError, ConnectionError, ExecutionError
)
try:
agent = Agent(tools=[MyTool()])
result = await agent.run("Process my request")
except ConfigurationError as e:
print(f"Setup issue: {e}") # Missing token, invalid config
except ConnectionError as e:
print(f"Connection failed: {e}") # Docker, network issues
except ExecutionError as e:
print(f"Execution failed: {e}") # Tool failures, timeouts
@tool decorator| Feature | Docker (Default) | Subprocess |
|---|---|---|
| Isolation | Full container isolation | Process isolation only |
| Setup Time | ~3 seconds | ~0.5 seconds |
| Use Case | Production, testing | Development, CI/CD |
| Requirements | Docker Desktop | None |
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Your Tools β β Agent β β Claude Code β
β (MCP Servers) βββββΊβ (Orchestrator) βββββΊβ (Reasoning) β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Host Process β β Docker Container β β Claude Code API β
β (localhost) β β or Subprocess β β (claude.ai) β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
Integrate with any existing MCP server using stdio or HTTP transport:
from claude_agent_toolkit import Agent
from claude_agent_toolkit.tool.mcp import StdioMCPTool, HttpMCPTool
# Connect to an MCP server via command execution
everything_server = StdioMCPTool(
command="npx",
args=["-y", "@modelcontextprotocol/server-everything"],
name="everything"
)
# Connect to an HTTP MCP server
http_server = HttpMCPTool(
url="http://localhost:3001/mcp",
name="my-http-server"
)
# Mix with your custom tools
agent = Agent(
system_prompt="You can use both custom and external tools",
tools=[MyCustomTool(), everything_server, http_server]
)
result = await agent.run("Use the everything server to echo 'Hello World'")
| Transport | Use Case | Example |
|---|---|---|
| Stdio | Command-line tools, npm packages | npx servers, Python scripts |
| HTTP | Web services, microservices | REST APIs, containerized servers |
Control exactly what files your agent can access with pattern-based permissions.
from claude_agent_toolkit.tools import FileSystemTool
# Define access patterns
permissions = [
("*.txt", "read"), # Read all text files
("data/**", "write"), # Write to data directory
("logs/*.log", "read"), # Read log files only
]
fs_tool = FileSystemTool(
permissions=permissions,
root_dir="/path/to/workspace" # Restrict to directory
)
agent = Agent(
system_prompt="You are a file manager assistant",
tools=[fs_tool]
)
result = await agent.run(
"List all text files and create a summary in data/report.txt"
)
Transfer structured data between Claude agents and your application using Pydantic models.
from claude_agent_toolkit.tools import DataTransferTool
from pydantic import BaseModel, Field
from typing import List
class UserProfile(BaseModel):
name: str = Field(..., description="Full name")
age: int = Field(..., ge=0, le=150, description="Age in years")
interests: List[str] = Field(default_factory=list)
# Create tool for specific model
user_tool = DataTransferTool.create(UserProfile, "UserProfileTool")
agent = Agent(
system_prompt="You handle user profile data transfers",
tools=[user_tool]
)
# Transfer data through Claude
await agent.run(
"Transfer user: Alice Johnson, age 28, interests programming and hiking"
)
# Retrieve validated data
user_data = user_tool.get()
if user_data:
print(f"Retrieved: {user_data.name}, age {user_data.age}")
from claude_agent_toolkit import BaseTool, tool
class MyTool(BaseTool):
def __init__(self):
super().__init__() # Server starts automatically
self.data = {} # Explicit data management
@tool()
async def process_async(self, data: str) -> dict:
"""Async operation"""
# Async operations for I/O, API calls
return {"result": f"processed_{data}"}
@tool(parallel=True, timeout_s=60)
def process_heavy(self, data: str) -> dict:
"""CPU-intensive operation"""
# Sync function - runs in separate process
# Note: New instance created, self.data won't persist
import time
time.sleep(2)
return {"result": f"heavy_{data}"}
# Single tool with guaranteed cleanup
with MyTool() as tool:
agent = Agent(tools=[tool])
result = await agent.run("Process my data")
# Server automatically cleaned up
# Multiple tools
with MyTool() as calc_tool, WeatherTool() as weather_tool:
agent = Agent(tools=[calc_tool, weather_tool])
result = await agent.run("Calculate and check weather")
# Both tools cleaned up automatically
A Python framework that lets you build AI agents using Claude Code with custom tools. Unlike generic agent frameworks, this specifically leverages Claude Code's advanced reasoning capabilities with your existing subscription.
Docker is recommended for production but not required. Use ExecutorType.SUBPROCESS for subprocess execution:
agent = Agent(tools=[my_tool], executor=ExecutorType.SUBPROCESS)
It also runs in an isolated directory to ensure maximum isolation.
The framework provides specific exception types:
from claude_agent_toolkit import ConfigurationError, ConnectionError, ExecutionError
try:
result = await agent.run("task")
except ConfigurationError:
# Missing OAuth token, invalid config
except ConnectionError:
# Docker/network issues
except ExecutionError:
# Tool failures, timeouts
Yes! Claude Code intelligently orchestrates multiple tools:
agent = Agent(tools=[calc_tool, weather_tool, file_tool])
result = await agent.run(
"Calculate the average temperature and save results to report.txt"
)
The framework is validated through comprehensive examples rather than traditional unit tests. Each example demonstrates specific capabilities and serves as both documentation and validation.
# Clone the repository
git clone https://github.com/cheolwanpark/claude-agent-toolkit.git
cd claude-agent-toolkit
# Set your OAuth token
export CLAUDE_CODE_OAUTH_TOKEN='your-token-here'
# Run different examples
cd src/examples/calculator && python main.py # Stateful operations, parallel processing
cd src/examples/weather && python main.py # External API integration
cd src/examples/subprocess && python main.py # No Docker required
cd src/examples/filesystem && python main.py # Permission-based file access
cd src/examples/datatransfer && python main.py # Type-safe data transfer
cd src/examples/mcp && python main.py # External MCP server integration
src/examples/
βββ calculator/ # Mathematical operations with state management
βββ weather/ # External API integration (OpenWeatherMap)
βββ subprocess/ # Subprocess executor demonstration
βββ filesystem/ # FileSystemTool with permissions
βββ datatransfer/ # DataTransferTool with Pydantic models
βββ mcp/ # External MCP server integration (stdio, HTTP)
βββ README.md # Detailed example documentation
Examples can run with both executors:
# Docker executor (default)
python main.py
# Subprocess executor (faster startup)
# Examples automatically use subprocess when Docker unavailable
git checkout -b feature-namegit clone https://github.com/cheolwanpark/claude-agent-toolkit.git
cd claude-agent-toolkit
uv sync --group dev
# Validate your changes by running examples
export CLAUDE_CODE_OAUTH_TOKEN='your-token'
cd src/examples/calculator && python main.py
This project is licensed under the MIT License - see the LICENSE file for details.
Created by: Cheolwan Park β’ Blog: Project Background
Links: Homepage β’ Claude Code β’ Issues β’ Model Context Protocol
Python
99.4%
Python framework for building agents using claude-code-sdk with programmable tools
Python
28
58 commits
updated Jan 25, 2026
claude-code-sdk wrapper for enhanced developer experience with easy setup and runtime isolation using Docker
A Python framework that wraps claude-code-sdk to provide better developer experience through decorator-based tools, runtime isolation, and simplified agent development. Built for production safety with Docker containers that ensure controlled tool execution and consistent behavior across all environments.
Working directly with claude-code-sdk presents two major challenges:
Claude Agent Toolkit solves these issues through:
@tool decorator converts any Python function into a Claude-compatible tool"An intuitive and stable development experience similar to Google's ADK"
Before (Direct claude-code-sdk):
# Manual tool naming and complex schema definition required
@tool("greet", "Greet a user", {"name": str})
async def greet_user(args):
return {
"content": [
{"type": "text", "text": f"Hello, {args['name']}!"}
]
}
# Tool functions and MCP server are decoupled - difficult to maintain at scale
server = create_sdk_mcp_server(
name="my-tools",
version="1.0.0",
tools=[greet_user]
)
# At Runtime:
# β Subprocess can access system tools (Read, LS, Grep)
# β Manual environment configuration required
# β No control over Claude Code's tool access
# β Risk of unintended system interactions
After (Claude Agent Toolkit):
# Intuitive class-based tool definition with integrated MCP server
class CalculatorTool(BaseTool):
@tool()
async def add(self, a: float, b: float) -> dict:
"""Adds two numbers together"""
return {"result": a + b}
# Single line agent creation with controlled tool access
agent = Agent(tools=[CalculatorTool()])
# At Runtime:
# β
Docker container runs only your defined tools
# β
No access to system tools (Read, LS, Grep)
# β
Clean, isolated, predictable execution environment
# β
Complete control over Claude Code's capabilities
| Feature | claude-code-sdk | Claude Agent Toolkit |
|---|---|---|
| Custom Tools | Manual schema definition, No parallel execution support | Simple and intuitive class-based definition with @tool decorator, Built-in parallel execution with parallel=True |
| Runtime Isolation | No built-in isolation You need to design your own | Docker by default Allows only tools you explicitly added |
| Environment Consistency | Manual environment setup Explicit tool/option configuration required | Zero setup needed Works out of the box |
| Setup Complexity | ~20 lines just for ClaudeCodeOptions configuration | ~25 lines for complete agent with calculatorAgent.run(verbose=True) shows all responses |
| Built-in Tools | Build everything from scratch | FileSystemTool with permission control DataTransferTool for formatted output handling |
| Best For | Using Claude Code as-is Minimal dependencies only | Fast development Using Claude Code as reasoning engine (like LangGraph agents) |
from claude_agent_toolkit import Agent, BaseTool, tool
# 1. Create a custom tool with @tool decorator
class CalculatorTool(BaseTool):
@tool()
async def add(self, a: float, b: float) -> dict:
"""Adds two numbers together"""
result = a + b
return {
"operation": f"{a} + {b}",
"result": result,
"message": f"The result of adding {a} and {b} is {result}"
}
# 2. Create and run an agent
async def main():
agent = Agent(
system_prompt="You are a helpful calculator assistant",
tools=[CalculatorTool()],
model="sonnet" # haiku, sonnet, or opus
)
result = await agent.run("What is 15 + 27?")
print(result) # Claude will use your tool and return the answer
if __name__ == "__main__":
import asyncio
asyncio.run(main())
uv package manager# Using pip
pip install claude-agent-toolkit
# Using uv (recommended)
uv add claude-agent-toolkit
# Using poetry
poetry add claude-agent-toolkit
Get your token by running claude setup-token in your terminal, then:
export CLAUDE_CODE_OAUTH_TOKEN='your-token-here'
# Clone examples (optional)
git clone https://github.com/cheolwanpark/claude-agent-toolkit.git
cd claude-agent-toolkit/src/examples/calculator
python main.py
from claude_agent_toolkit import Agent, BaseTool, tool, ExecutorType
class MyTool(BaseTool):
def __init__(self):
super().__init__()
self.counter = 0 # Explicit data management
@tool()
async def increment(self) -> dict:
"""Increment counter and return value"""
self.counter += 1
return {"value": self.counter}
# Docker executor (default, production-ready)
agent = Agent(tools=[MyTool()])
# Subprocess executor (faster startup, development)
agent = Agent(tools=[MyTool()], executor=ExecutorType.SUBPROCESS)
result = await agent.run("Increment the counter twice")
# Fast and efficient for simple tasks
weather_agent = Agent(
tools=[weather_tool],
model="haiku"
)
# Balanced performance (default)
general_agent = Agent(
tools=[calc_tool, weather_tool],
model="sonnet"
)
# Most capable for complex reasoning
analysis_agent = Agent(
tools=[analysis_tool],
model="opus"
)
# Override per query
result = await weather_agent.run(
"Complex weather pattern analysis",
model="opus"
)
class HeavyComputeTool(BaseTool):
@tool(parallel=True, timeout_s=120)
def process_data(self, data: str) -> dict:
"""Heavy computation"""
# Sync function - runs in separate process
import time
time.sleep(5) # Simulate heavy work
return {"processed": f"result_{data}"}
from claude_agent_toolkit import (
Agent, BaseTool,
ConfigurationError, ConnectionError, ExecutionError
)
try:
agent = Agent(tools=[MyTool()])
result = await agent.run("Process my request")
except ConfigurationError as e:
print(f"Setup issue: {e}") # Missing token, invalid config
except ConnectionError as e:
print(f"Connection failed: {e}") # Docker, network issues
except ExecutionError as e:
print(f"Execution failed: {e}") # Tool failures, timeouts
@tool decorator| Feature | Docker (Default) | Subprocess |
|---|---|---|
| Isolation | Full container isolation | Process isolation only |
| Setup Time | ~3 seconds | ~0.5 seconds |
| Use Case | Production, testing | Development, CI/CD |
| Requirements | Docker Desktop | None |
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Your Tools β β Agent β β Claude Code β
β (MCP Servers) βββββΊβ (Orchestrator) βββββΊβ (Reasoning) β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Host Process β β Docker Container β β Claude Code API β
β (localhost) β β or Subprocess β β (claude.ai) β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
Integrate with any existing MCP server using stdio or HTTP transport:
from claude_agent_toolkit import Agent
from claude_agent_toolkit.tool.mcp import StdioMCPTool, HttpMCPTool
# Connect to an MCP server via command execution
everything_server = StdioMCPTool(
command="npx",
args=["-y", "@modelcontextprotocol/server-everything"],
name="everything"
)
# Connect to an HTTP MCP server
http_server = HttpMCPTool(
url="http://localhost:3001/mcp",
name="my-http-server"
)
# Mix with your custom tools
agent = Agent(
system_prompt="You can use both custom and external tools",
tools=[MyCustomTool(), everything_server, http_server]
)
result = await agent.run("Use the everything server to echo 'Hello World'")
| Transport | Use Case | Example |
|---|---|---|
| Stdio | Command-line tools, npm packages | npx servers, Python scripts |
| HTTP | Web services, microservices | REST APIs, containerized servers |
Control exactly what files your agent can access with pattern-based permissions.
from claude_agent_toolkit.tools import FileSystemTool
# Define access patterns
permissions = [
("*.txt", "read"), # Read all text files
("data/**", "write"), # Write to data directory
("logs/*.log", "read"), # Read log files only
]
fs_tool = FileSystemTool(
permissions=permissions,
root_dir="/path/to/workspace" # Restrict to directory
)
agent = Agent(
system_prompt="You are a file manager assistant",
tools=[fs_tool]
)
result = await agent.run(
"List all text files and create a summary in data/report.txt"
)
Transfer structured data between Claude agents and your application using Pydantic models.
from claude_agent_toolkit.tools import DataTransferTool
from pydantic import BaseModel, Field
from typing import List
class UserProfile(BaseModel):
name: str = Field(..., description="Full name")
age: int = Field(..., ge=0, le=150, description="Age in years")
interests: List[str] = Field(default_factory=list)
# Create tool for specific model
user_tool = DataTransferTool.create(UserProfile, "UserProfileTool")
agent = Agent(
system_prompt="You handle user profile data transfers",
tools=[user_tool]
)
# Transfer data through Claude
await agent.run(
"Transfer user: Alice Johnson, age 28, interests programming and hiking"
)
# Retrieve validated data
user_data = user_tool.get()
if user_data:
print(f"Retrieved: {user_data.name}, age {user_data.age}")
from claude_agent_toolkit import BaseTool, tool
class MyTool(BaseTool):
def __init__(self):
super().__init__() # Server starts automatically
self.data = {} # Explicit data management
@tool()
async def process_async(self, data: str) -> dict:
"""Async operation"""
# Async operations for I/O, API calls
return {"result": f"processed_{data}"}
@tool(parallel=True, timeout_s=60)
def process_heavy(self, data: str) -> dict:
"""CPU-intensive operation"""
# Sync function - runs in separate process
# Note: New instance created, self.data won't persist
import time
time.sleep(2)
return {"result": f"heavy_{data}"}
# Single tool with guaranteed cleanup
with MyTool() as tool:
agent = Agent(tools=[tool])
result = await agent.run("Process my data")
# Server automatically cleaned up
# Multiple tools
with MyTool() as calc_tool, WeatherTool() as weather_tool:
agent = Agent(tools=[calc_tool, weather_tool])
result = await agent.run("Calculate and check weather")
# Both tools cleaned up automatically
A Python framework that lets you build AI agents using Claude Code with custom tools. Unlike generic agent frameworks, this specifically leverages Claude Code's advanced reasoning capabilities with your existing subscription.
Docker is recommended for production but not required. Use ExecutorType.SUBPROCESS for subprocess execution:
agent = Agent(tools=[my_tool], executor=ExecutorType.SUBPROCESS)
It also runs in an isolated directory to ensure maximum isolation.
The framework provides specific exception types:
from claude_agent_toolkit import ConfigurationError, ConnectionError, ExecutionError
try:
result = await agent.run("task")
except ConfigurationError:
# Missing OAuth token, invalid config
except ConnectionError:
# Docker/network issues
except ExecutionError:
# Tool failures, timeouts
Yes! Claude Code intelligently orchestrates multiple tools:
agent = Agent(tools=[calc_tool, weather_tool, file_tool])
result = await agent.run(
"Calculate the average temperature and save results to report.txt"
)
The framework is validated through comprehensive examples rather than traditional unit tests. Each example demonstrates specific capabilities and serves as both documentation and validation.
# Clone the repository
git clone https://github.com/cheolwanpark/claude-agent-toolkit.git
cd claude-agent-toolkit
# Set your OAuth token
export CLAUDE_CODE_OAUTH_TOKEN='your-token-here'
# Run different examples
cd src/examples/calculator && python main.py # Stateful operations, parallel processing
cd src/examples/weather && python main.py # External API integration
cd src/examples/subprocess && python main.py # No Docker required
cd src/examples/filesystem && python main.py # Permission-based file access
cd src/examples/datatransfer && python main.py # Type-safe data transfer
cd src/examples/mcp && python main.py # External MCP server integration
src/examples/
βββ calculator/ # Mathematical operations with state management
βββ weather/ # External API integration (OpenWeatherMap)
βββ subprocess/ # Subprocess executor demonstration
βββ filesystem/ # FileSystemTool with permissions
βββ datatransfer/ # DataTransferTool with Pydantic models
βββ mcp/ # External MCP server integration (stdio, HTTP)
βββ README.md # Detailed example documentation
Examples can run with both executors:
# Docker executor (default)
python main.py
# Subprocess executor (faster startup)
# Examples automatically use subprocess when Docker unavailable
git checkout -b feature-namegit clone https://github.com/cheolwanpark/claude-agent-toolkit.git
cd claude-agent-toolkit
uv sync --group dev
# Validate your changes by running examples
export CLAUDE_CODE_OAUTH_TOKEN='your-token'
cd src/examples/calculator && python main.py
This project is licensed under the MIT License - see the LICENSE file for details.
Created by: Cheolwan Park β’ Blog: Project Background
Links: Homepage β’ Claude Code β’ Issues β’ Model Context Protocol
Python
99.4%