> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs-dev.band.ai/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: <path>` 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