---
name: claude-agent-sdk-builder
description: "Build production agents on the Claude Agent SDK — agent loop, custom tools, subagents, hooks, sessions, MCP. Production agents on the Claude Agent SDK (Python and TypeScript): the query() loop and ClaudeAgentOptions, custom tools via @tool and create_sdk_mcp_server, subagents with AgentDefinition, PreToolUse/PostToolUse hooks and permission callbacks, session resume/fork, system-prompt and setting-source config, MCP server wiring, and streaming/partial-message handling."
license: MIT
---

# Claude Agent SDK Builder

This skill is a hands-on guide for building production agents on the Claude Agent SDK in both Python (`claude-agent-sdk`) and TypeScript (`@anthropic-ai/claude-agent-sdk`). It covers the full surface: the streaming `query()` loop, custom in-process tools, subagents, hooks, permission control, and session persistence. It's for developers who have outgrown a single chat completion call and need an agent that runs tools, enforces guardrails, and resumes work across runs.

## When to use it

- You want an agent that loops autonomously over tool calls instead of returning one message.
- You need to expose your own functions (DB queries, API calls, file ops) as tools the model can invoke.
- You're delegating focused work to subagents with their own prompts and tool allowlists.
- You need to intercept tool calls for approval, logging, or blocking before they execute.
- You want to pause a run and continue later without losing context.

## How it works

The core is the `query()` async generator. You pass a prompt plus a `ClaudeAgentOptions` object and iterate over streamed messages. Key options:

- **Custom tools** — define with the `@tool(name, description, schema)` decorator, then bundle them with `create_sdk_mcp_server(name, version, tools)`. Register the server under `options.mcp_servers` and gate it via `allowed_tools`.
- **Subagents** — declare `AgentDefinition` entries (description, prompt, tools, model) in `options.agents`; the main agent can hand off to them.
- **Hooks** — register `PreToolUse` and `PostToolUse` callbacks to inspect or veto tool execution; pair with `can_use_tool` / `permission_mode` for approval flows.
- **Sessions** — capture the session id from the result, then resume with `options.resume`, or branch history with `fork_session`.
- **Context** — set `system_prompt` and choose `setting_sources` to control whether filesystem `CLAUDE.md` and settings are loaded.

## Usage example

```bash
curl -fsSL --create-dirs -o ~/.claude/skills/claude-agent-sdk-builder/SKILL.md https://claudeskil.com/skills/claude-agent-sdk-builder/SKILL.md
pip install claude-agent-sdk
```

```python
from claude_agent_sdk import query, tool, create_sdk_mcp_server, ClaudeAgentOptions

@tool("get_weather", "Look up weather", {"city": str})
async def get_weather(args):
    return {"content": [{"type": "text", "text": f"Sunny in {args['city']}"}]}

server = create_sdk_mcp_server("utils", "1.0.0", [get_weather])
opts = ClaudeAgentOptions(
    mcp_servers={"utils": server},
    allowed_tools=["mcp__utils__get_weather"],
    system_prompt="You are a concise weather assistant.",
)

async for msg in query(prompt="Weather in Tokyo?", options=opts):
    print(msg)
```

## Tips & gotchas

- SDK tool names are namespaced as `mcp__<server>__<tool>` — use that exact form in `allowed_tools`, or the model can't call them.
- `create_sdk_mcp_server` runs in-process; no subprocess or stdio transport is spawned, so it's faster than an external MCP server but shares your process.
- By default `setting_sources` does not load project files — pass it explicitly if you want `CLAUDE.md` picked up.
- A `PreToolUse` hook returning a deny decision blocks the call; use this rather than trusting the prompt for hard guardrails.

## FAQ

**Python vs TypeScript — same features?** Yes, the APIs mirror each other; TS uses `tool()` and the same options object with camelCase fields.

**Do I need an API key or the Claude CLI?** Set `ANTHROPIC_API_KEY`; the SDK manages the agent loop itself.

**How do I resume a run?** Save the session id from the result and pass it to `options.resume`; use `fork_session` to branch without mutating the original.
