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.
- Why Claude Agent Toolkit?
- When Should You Use This?
- Quick Start
- Installation & Setup
- Usage Examples
- Core Features
- Architecture
- Built-in Tools
- Creating Custom Tools
- FAQ
- Testing
- Contributing
- License
Working directly with claude-code-sdk presents two major challenges:
- Complex Tool Integration - Manual MCP server setup, connection handling, and tool registration
- Runtime Safety - Need for controlled tool execution with clean, isolated environments
Claude Agent Toolkit solves these issues through:
- 🎯 Decorator-Based Tools - Simple
@tool
decorator converts any Python function into a Claude-compatible tool - 🐳 Runtime Isolation - Docker containers provide safe, controlled execution with only your specified tools
- ⚡ Zero Configuration - Automatic MCP server management and tool discovery
"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())
- Python 3.12+ with
uv
package manager - Docker Desktop (required for Docker executor, recommended)
- Claude Code OAuth Token - Get from Claude Code
# 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
- 🎯 Decorator-Based Tools - Transform any Python function into a Claude tool with simple
@tool
decorator - 🔌 External MCP Integration - Connect to existing MCP servers via stdio and HTTP transports
- 🐳 Isolated Execution - Docker containers ensure consistent behavior across all environments
- ⚡ Zero Configuration - Automatic MCP server management, port selection, and tool discovery
- 🔧 Flexible Execution Modes - Choose Docker isolation (production) or subprocess (development)
- 📝 Explicit Data Management - You control data persistence with no hidden state
- ⚙️ CPU-bound Operations - Process pools for heavy computations with parallel processing
- 🎭 Multi-tool Coordination - Claude Code intelligently orchestrates multiple tools
- 🏗️ Production Ready - Built for scalable, reliable agent deployment
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'")
- Existing MCP Ecosystem: Leverage community MCP servers and tools
- Language Diversity: Use MCP servers written in Node.js, Python, Go, etc.
- Specialized Tools: Integrate domain-specific tools without reimplementation
- Rapid Prototyping: Quickly test MCP servers before building custom equivalents
- Service Architecture: Connect to MCP servers running as microservices
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.
- Uses Claude Code: Leverages Claude's production infrastructure and reasoning
- MCP Protocol: Industry-standard tool integration, not proprietary APIs
- Explicit Data: You control data persistence, no hidden state management
- Production Focus: Built for real deployment, not just experiments
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.
- haiku: Fast, cost-effective for simple operations
- sonnet: Balanced performance, good default choice
- opus: Maximum capability for complex reasoning
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
- Fork the repository
- Create a feature branch:
git checkout -b feature-name
- Make your changes and validate with examples
- Run examples to verify functionality
- Submit a pull request
git 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