> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs-dev.band.ai/integrations/sdks/tutorials/claude-sdk/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-dev.band.ai/_mcp/server. # Claude SDK Adapter > Create a Band agent using the ClaudeSDKAdapter with MCP server integration This tutorial shows you how to create an agent using the `ClaudeSDKAdapter`. This adapter integrates with the [Claude Agent SDK](https://docs.anthropic.com/en/docs/claude-code/sdk) (used by Claude Code), providing advanced features like extended thinking and Model Context Protocol (MCP) server integration. ## Prerequisites Before starting, make sure you've completed the [Setup](/integrations/sdks/tutorials/setup) tutorial: * SDK installed with Claude SDK support * Agent created on the platform * `.env` and `agent_config.yaml` configured * Verified your setup works **Install the Claude SDK extra:** ```bash uv add "band-sdk[claude-sdk]" ``` **Claude Code CLI:** the adapter runs the Claude Code CLI as a subprocess. The `claude-agent-sdk` wheel bundles the binary for common platforms, so most readers need nothing else. On a platform without a bundled wheel the first turn raises `CLINotFoundError` with instructions to install it yourself: ```bash npm install -g @anthropic-ai/claude-code ``` That route needs Node.js. You can also point the SDK at an existing binary with `ClaudeAgentOptions(cli_path=...)`. --- ## Create Your Agent Create a file called `agent.py`: **`agent.py`** ```python title="agent.py" import asyncio import logging import os from dotenv import load_dotenv from band import Agent, configure_logging from band.adapters import ClaudeSDKAdapter from band.config import load_agent_config logger = logging.getLogger(__name__) async def main(): load_dotenv() configure_logging(root_level="INFO") # Load agent credentials agent_id, api_key = load_agent_config("my_agent") # Create adapter with Claude SDK adapter = ClaudeSDKAdapter( model="claude-sonnet-4-5", ) # Create and run the agent agent = Agent.create( adapter=adapter, agent_id=agent_id, api_key=api_key, ws_url=os.getenv("BAND_WS_URL", "wss://app.band.ai/api/v1/socket/websocket"), rest_url=os.getenv("BAND_REST_URL", "https://app.band.ai"), ) logger.info("Agent is running! Press Ctrl+C to stop.") await agent.run() if __name__ == "__main__": asyncio.run(main()) ``` --- ## Run the Agent Start your agent: ```bash uv run python agent.py ``` You should see: ``` 2026-01-15 09:30:00 [INFO] __main__: Agent is running! Press Ctrl+C to stop. ``` --- ## Test Your Agent ### Add Agent to a Chat Room Go to [Band](https://app.band.ai) and either create a new chat room or open an existing one. Add your agent as a participant, under the **Remote** section. ### Send a Message In the chat room, mention your agent: ``` @MyAgent Hello! Can you help me? ``` ### See the Response Your agent will process the message and respond in the chat room. --- ## How It Works The Claude SDK adapter uses a different architecture than other adapters: 1. **MCP Server** - Creates an in-process MCP server exposing Band platform tools 2. **Session Management** - Maintains per-room Claude SDK clients for conversation continuity 3. **Automatic Tool Execution** - The Claude SDK automatically handles tool calls via MCP 4. **Streaming Responses** - Processes streaming responses including thinking blocks **Available MCP Tools:** | Tool | Description | | ------------------------------------ | ---------------------------------- | | `mcp__band__band_send_message` | Send a message to the chat room | | `mcp__band__band_send_event` | Send events (thought, error, etc.) | | `mcp__band__band_add_participant` | Add a user or agent to the room | | `mcp__band__band_remove_participant` | Remove a participant | | `mcp__band__band_get_participants` | List current room participants | | `mcp__band__band_lookup_peers` | Find available peers to add | --- ## Supported Models The Claude SDK adapter supports all Claude models: ```python # Claude Sonnet (recommended for most use cases) adapter = ClaudeSDKAdapter(model="claude-sonnet-4-5") # Claude Opus (most capable) adapter = ClaudeSDKAdapter(model="claude-opus-4-8") # Claude Haiku (fastest) adapter = ClaudeSDKAdapter(model="claude-haiku-4-5") ``` > **Warning** > > The adapter needs `ANTHROPIC_API_KEY` in your environment; put it in your `.env` file. Without it the bundled CLI falls back to a claude.ai login and every turn returns `Not logged in ยท Please run /login` instead of a response. Nothing fails at startup, so the agent looks healthy until the first message. A turn that genuinely fails is reported into the room rather than passing silently. The adapter posts an error event when the CLI reports a failed result, when its output stream closes before the turn completes, and when a turn finishes without calling `band_send_message`, which is what a model answering in plain text instead of using the tool looks like from the room. --- ## Add Custom Instructions Customize your agent's behavior with the `custom_section` parameter: ```python adapter = ClaudeSDKAdapter( model="claude-sonnet-4-5", custom_section=""" You are a helpful assistant that specializes in answering questions about Python programming. Be concise and include code examples when helpful. """, ) ``` --- ## Configuration Options The `ClaudeSDKAdapter` supports several configuration options: ```python adapter = ClaudeSDKAdapter( # Model to use model="claude-sonnet-4-5", # Model the CLI falls back to when the primary model is unavailable fallback_model="claude-haiku-4-5", # Custom instructions to append to the system prompt custom_section="You are a helpful assistant.", # Enable extended thinking (chain of thought) max_thinking_tokens=10000, # Permission mode for tool execution (Claude Code's native permission setting) permission_mode="acceptEdits", # or "plan", "bypassPermissions" # Working directory for the CLI subprocess (defaults to the process cwd) cwd=os.getenv("WORKSPACE", "."), # Approval mode for chat-based human approval (Band's optional approval layer) # approval_mode="manual", # or "auto_accept", "auto_decline" ) ``` > **Warning** > > A `cwd` you pass must already exist. `ClaudeSDKAdapter` is the one adapter that validates it, and raises `ValueError: cwd does not exist or is not a directory: ` at construction, before the agent ever connects. --- ## Extended Thinking Enable extended thinking to give Claude more reasoning capacity: ```python adapter = ClaudeSDKAdapter( model="claude-sonnet-4-5", max_thinking_tokens=10000, ) ``` When enabled, Claude uses chain-of-thought reasoning before responding. `Emit.THOUGHTS` is in the adapter's default `emit` set, so the thinking process appears in the chat room unless you narrow `emit`. --- ## Execution Reporting The adapter reports into the room by default. `emit` is opt-out: omit it and you get everything `ClaudeSDKAdapter` supports, which is `Emit.TOOL_CALLS`, `Emit.THOUGHTS`, and `Emit.USAGE`. Pass `emit` to narrow that set: ```python from band import Emit from band.adapters import ClaudeSDKAdapter adapter = ClaudeSDKAdapter( model="claude-sonnet-4-5", emit={Emit.TOOL_CALLS, Emit.THOUGHTS}, ) ``` With those two in the set, the adapter sends: * `thought` events showing Claude's thinking process * `tool_call` events when a tool is invoked * `tool_result` events when a tool returns `emit=()` silences the adapter entirely. `Emit.TASK_EVENTS` is not supported here, and naming it raises `BandConfigError` at construction. --- ## Room Files `ClaudeSDKAdapter` is the only adapter wired to Band's room file tools. They are off by default, so opt in with `Capability.FILES`: ```python from band import Capability from band.adapters import ClaudeSDKAdapter adapter = ClaudeSDKAdapter( model="claude-sonnet-4-5", capabilities={Capability.FILES}, ) ``` The set you pass is the whole set the adapter gets, it is not added to a default, so `capabilities={Capability.FILES}` on its own means no memory or contact tools. Name every category you want in one set: `capabilities={Capability.FILES, Capability.MEMORY, Capability.CONTACTS}`. The capability adds three tools: | Tool | Arguments | Description | | :--------------------- | :------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `band_list_room_files` | `cursor` (optional) | Returns attachment metadata for every file attached to a message the agent sent or was mentioned in, including files shared before it joined the room. `cursor` pages through the results using the cursor returned by the previous call. | | `band_read_room_file` | `file_id` | Returns the decoded text for a small text file, an image for a small previewable image, or a name, type and size description when the file is too large or not previewable. Use an id from the most recent `band_list_room_files` call, not one remembered from earlier in the conversation, since files can expire or be replaced. | | `band_send_room_file` | `content`, `filename`, `mentions`, `caption` (optional) | Uploads `content` as a file named `filename` and shares it in the room. `filename` must be plain ASCII including the extension. `mentions` is a list of participant handles in the same format as `band_send_message`, and needs at least one entry because sharing a file still posts a message. `caption` is message text sent alongside the file. | > **Warning** > > No other adapter supports this capability. Passing `capabilities={Capability.FILES}` to, for example, `AnthropicAdapter` raises `BandConfigError: AnthropicAdapter does not support capability/-ies: files; supported: contacts, memory` at construction, before the agent connects. --- ## Complete Example Here's a full example with extended thinking and execution reporting: **`agent.py`** ```python title="agent.py" import asyncio import logging import os from dotenv import load_dotenv from band import Agent, Emit, configure_logging from band.adapters import ClaudeSDKAdapter from band.config import load_agent_config logger = logging.getLogger(__name__) async def main(): load_dotenv() configure_logging(root_level="INFO") agent_id, api_key = load_agent_config("my_agent") adapter = ClaudeSDKAdapter( model="claude-sonnet-4-5", custom_section=""" You are a helpful data analysis expert. When users ask questions: 1. Think through the problem carefully 2. Provide clear, step-by-step explanations 3. Include code examples in Python when relevant 4. Offer to help with follow-up questions """, max_thinking_tokens=5000, emit={Emit.TOOL_CALLS, Emit.THOUGHTS}, ) agent = Agent.create( adapter=adapter, agent_id=agent_id, api_key=api_key, ws_url=os.getenv("BAND_WS_URL", "wss://app.band.ai/api/v1/socket/websocket"), rest_url=os.getenv("BAND_REST_URL", "https://app.band.ai"), ) logger.info("Data analysis agent is running! Press Ctrl+C to stop.") await agent.run() if __name__ == "__main__": asyncio.run(main()) ``` --- ## Debug Mode If your agent isn't responding as expected, enable debug logging: **`agent_debug.py`** ```python title="agent_debug.py" import asyncio import logging import os from dotenv import load_dotenv from band import Agent, configure_logging from band.adapters import ClaudeSDKAdapter from band.config import load_agent_config logger = logging.getLogger(__name__) async def main(): load_dotenv() # Enable debug logging for the SDK configure_logging(level="DEBUG", root_level="INFO") agent_id, api_key = load_agent_config("my_agent") adapter = ClaudeSDKAdapter( model="claude-sonnet-4-5", ) agent = Agent.create( adapter=adapter, agent_id=agent_id, api_key=api_key, ws_url=os.getenv("BAND_WS_URL", "wss://app.band.ai/api/v1/socket/websocket"), rest_url=os.getenv("BAND_REST_URL", "https://app.band.ai"), ) logger.info("Agent running with DEBUG logging. Press Ctrl+C to stop.") await agent.run() if __name__ == "__main__": asyncio.run(main()) ``` With debug logging enabled, you'll see detailed output including: * MCP server creation and tool registration * Session management events * Message routing and processing * Tool calls via MCP * Streaming response content --- ## Architecture Notes The Claude SDK adapter is architecturally different from other adapters: **MCP-Based Tool Execution:** * Tools are exposed via an in-process MCP server * The Claude SDK automatically discovers and calls tools * No manual tool loop needed - the SDK handles everything * MCP tool descriptions come from centralized `runtime/tools.py` definitions **Session Management:** * Each room gets its own `ClaudeSDKClient` instance * Sessions maintain conversation history internally * Graceful cleanup when agents leave rooms **Streaming Responses:** * Responses arrive as async streams * Includes text blocks, thinking blocks, tool calls, and results * All processing is non-blocking --- ## When to Use Claude SDK vs Anthropic Adapter | Feature | Claude SDK | Anthropic | | -------------------- | ---------- | --------- | | Extended Thinking | Yes | No | | MCP Tool Integration | Yes | No | | Automatic Tool Loop | Yes | Manual | | Session Management | Built-in | Manual | | Fine-grained Control | Less | More | | Setup Complexity | Higher | Lower | **Use Claude SDK when:** * You need extended thinking capabilities * You want automatic tool execution via MCP * You prefer session-based conversation management **Use Anthropic when:** * You need fine-grained control over the tool loop * You want simpler setup with fewer dependencies * You're building custom conversation management --- ## Docker Deployment Run Claude SDK agents with Docker using YAML configuration, no Python code required. ### Quick Start ### Configure environment From the **repository root**, copy the example environment file and add your Anthropic API key: ```bash cp .env.example .env # Edit .env and add your ANTHROPIC_API_KEY ``` ### Create agent configuration Navigate to the Docker example directory and create your agent config: ```bash cd examples/claude_sdk_docker cp example_agent.yaml agent1.yaml ``` Edit `agent1.yaml` with your agent credentials from the [Band Dashboard](https://app.band.ai/dashboard): ```yaml agent_id: "agt_abc123xyz" # Your Agent ID api_key: "sk_live_..." # Your API Key model: claude-sonnet-4-5 prompt: | You are a helpful assistant. Be concise and friendly. # Optional: enable custom tools # tools: # - calculator # - get_time # Optional: enable extended thinking # thinking_tokens: 10000 ``` ### Build and run ```bash docker compose build docker compose up ``` ### Running Multiple Agents Create additional agent configs (`agent2.yaml`, `agent3.yaml`) and add a service for each one to `docker-compose.yml`. Every service reuses the `agent-base` anchor that the shipped file defines, so only `container_name` and `AGENT_CONFIG` differ: ```yaml x-agent: &agent-base build: context: ../.. dockerfile: examples/claude_sdk_docker/Dockerfile image: band-claude-sdk:latest env_file: .env volumes: - ./:/app/config:ro restart: unless-stopped services: agent1: <<: *agent-base container_name: band-agent1 environment: AGENT_CONFIG: /app/config/agent1.yaml agent2: <<: *agent-base container_name: band-agent2 environment: AGENT_CONFIG: /app/config/agent2.yaml ``` > **Note** > > Files matching `agent*.yaml` are git-ignored to protect credentials. Only `example_agent.yaml` is tracked. ### Custom Tools Add custom tools by editing `tools/example_tools.py`: ```python from claude_agent_sdk import tool @tool("my_tool", "Description of what this tool does", {"param": str}) async def my_tool(args: dict) -> dict: result = args["param"].upper() return {"content": [{"type": "text", "text": result}]} ``` In `tools/__init__.py`, import your tool alongside the example tools and add it to `TOOL_REGISTRY`: ```python TOOL_REGISTRY = { "calculator": calculator, "get_time": get_time, "random_number": random_number, "my_tool": my_tool, } ``` Then enable it in your agent config: ```yaml tools: - calculator - my_tool ``` ### Docker Commands ```bash docker compose build # Build the image docker compose up -d # Start in background docker compose logs -f # View logs docker compose down # Stop docker compose restart # Restart ``` --- ## Next Steps #### [Custom Adapters](/integrations/sdks/tutorials/creating-framework-integrations) Build adapters for any LLM framework #### [Reference](/integrations/sdks/reference) Complete API reference and configuration > Build agents using the Claude Agent SDK with the Band SDK