Skip to content
Back to skills

Claude Agent Sdk

ASecurity

Building custom agents with the Claude Agent SDK (TypeScript `@anthropic-ai/claude-agent-sdk`, Python `claude-agent-sdk`): install/auth, `query()` vs multi-turn clients, the full Options surface, in-process custom tools, wiring external MCP servers, permission evaluation order and `canUseTool`, SDK hooks, sessions/resume/fork/SessionStore, programmatic subagents, structured outputs, hosting and multi-tenant isolation, cost tracking. WHEN: \"Agent SDK\", \"claude-agent-sdk\", \"@anthropic-ai/c...

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 24, 2026
toolstypescriptpythonrustgojavashellbashnodeawstesting

Works with

  • claude code
  • terminal
  • cli
  • api
  • mcp

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 10 files and shows the line behind each finding

Scanned September 24, 2026

npx -y skills add chrishuffman5/domain-expert --skill claude-agent-sdk --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Claude Agent Sdk?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Claude Agent Sdk
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/chrishuffman5-claude-agent-sdk/badge)](https://www.skillsdirectory.com/skills/chrishuffman5-claude-agent-sdk)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: claude-agent-sdk
description: "Building custom agents with the Claude Agent SDK (TypeScript `@anthropic-ai/claude-agent-sdk`, Python `claude-agent-sdk`): install/auth, `query()` vs multi-turn clients, the full Options surface, in-process custom tools, wiring external MCP servers, permission evaluation order and `canUseTool`, SDK hooks, sessions/resume/fork/SessionStore, programmatic subagents, structured outputs, hosting and multi-tenant isolation, cost tracking. WHEN: \"Agent SDK\", \"claude-agent-sdk\", \"@anthropic-ai/claude-agent-sdk\", \"ClaudeAgentOptions\", \"ClaudeSDKClient\", \"createSdkMcpServer\", \"create_sdk_mcp_server\", \"canUseTool\", \"AgentDefinition\", \"fork_session\", \"structured_output\", \"total_cost_usd\", \"build an agent in TypeScript/Python\". NOT for: the Claude Code CLI/harness itself (settings.json, .claude/ hooks, plugins, headless CI) — use `claude-code`; the raw Messages API and hand-rolled tool loops — use `claude-api`; MCP spec/transports/OAuth or authoring standalone MCP servers — use `mcp`; writing SKILL.md files — use `agent-skills`; harness-vs-SDK architecture choice — use `overview`; building on another vendor's agent SDK — OpenAI Agents SDK (`openai-agents`, `@openai/agents`) use `openai-agents-sdk`, Google ADK (`LlmAgent`, `McpToolset`, `root_agent`) use `google-adk`; model/tier choice — use `model-selection`; prompt-injection threat modeling — use `ai-security`; container and egress isolation mechanics — use `sandboxing`; testing agents — use `evals`."
license: MIT
---

# Claude Agent SDK

The Claude Agent SDK is a library (Python and TypeScript only, as of 2026-08-05) that runs the same agent loop, tools, and context management that power Claude Code inside your own process. This skill covers building, permissioning, persisting, and hosting agents with it.

## Pick the right product first

| If you're… | Use | Why |
|---|---|---|
| Building an agent without implementing the tool loop yourself | **Agent SDK** | Runs the loop in your process, Python or TypeScript |
| Doing interactive dev / one-off terminal tasks | **Claude Code CLI** | Terminal interface for daily use — see `claude-code` |
| Calling the API directly, owning the tool loop | **Client SDK** | Direct Anthropic API access — see `claude-api` |
| Running long/async agents without owning sandbox/session infra | **Managed Agents** | Hosted REST API; Anthropic runs agent and sandbox |

Never tell a Go/Java/Rust user to wait for an SDK — drive the same loop by running the CLI as a subprocess with `-p --output-format json` (headless mode).

Never ship claude.ai login or subscription rate limits in a third-party product built on the SDK — that is not permitted unless previously approved; use API-key auth. Never brand your product "Claude Code" or "Claude Code Agent"; "Claude Agent" and "{YourAgentName} Powered by Claude" are allowed.

## Install and authenticate

Prerequisites: **Node.js 18+** or **Python 3.10+**.

```bash
npm install @anthropic-ai/claude-agent-sdk && npm install --save-dev tsx   # TypeScript
uv add claude-agent-sdk                                                    # Python (uv)
pip install claude-agent-sdk                                               # Python (venv)
```

Always set `"type": "module"` in `package.json` (or name the file `agent.mts`) — the SDK examples rely on top-level `await`, and `.mts` avoids converting a CommonJS project. Run with `npx tsx agent.ts`.

Always export `ANTHROPIC_API_KEY` in the process environment. Never assume a `.env` file is picked up — the SDK reads the process env only; load dotenv yourself before calling the SDK.

Both SDKs bundle a native Claude Code binary, so a separate Claude Code install is normally unnecessary. Two exceptions produce a "binary not found" failure: a pip **source** distribution (e.g. ARM64 Windows) and `npm ci --omit=optional`. Fix by installing Claude Code natively and pointing `pathToClaudeCodeExecutable` / `cli_path` at it.

Third-party model providers are selected by env var, not by an option: `CLAUDE_CODE_USE_BEDROCK=1`, `CLAUDE_CODE_USE_ANTHROPIC_AWS=1` (+ `ANTHROPIC_AWS_WORKSPACE_ID`), `CLAUDE_CODE_USE_VERTEX=1`, `CLAUDE_CODE_USE_FOUNDRY=1`, each with that cloud's credentials.

## The core loop

`query()` returns an async iterator. Each iteration yields reasoning text, a tool call, a tool result, or the terminal `ResultMessage`/`result`. The SDK owns orchestration, tool execution, context management, and retries.

```python
async for message in query(
    prompt="Review utils.py for bugs that would cause crashes. Fix any issues you find.",
    options=ClaudeAgentOptions(allowed_tools=["Read", "Edit", "Glob"], permission_mode="acceptEdits"),
):
    if isinstance(message, ResultMessage):
        print(message.subtype, message.result)
```

```typescript
for await (const message of query({
  prompt: "Review utils.py for bugs that would cause crashes. Fix any issues you find.",
  options: { allowedTools: ["Read", "Edit", "Glob"], permissionMode: "acceptEdits" }
})) {
  if (message.type === "result") console.log(message.subtype);
}
```

Always wrap a single-shot `query()` in try/catch: it **yields an error result and then throws/raises**. Connection or process-start failures yield no result message at all — code that only reads `ResultMessage` will silently see nothing.

Start from the smallest tool set that can finish the job: `Read, Glob, Grep` for read-only analysis; `+ Edit` to modify code; `+ Bash` for full automation.

## Multi-turn: the TS/Python asymmetry

There is no session-holding client object in TypeScript. Python has `ClaudeSDKClient`; TypeScript uses `continue: true` on the next `query()`.

| Goal | Python | TypeScript |
|---|---|---|
| Multi-turn chat in one process | `async with ClaudeSDKClient(...)` + `client.query()` / `receive_response()` | second `query()` with `continue: true` |
| Resume after process restart | `continue_conversation=True` (most recent session in that directory) | `continue: true` |
| Resume a specific session | `resume="<session-id>"` | `resume: sessionId` |
| Branch without touching the original | `fork_session=True` | `forkSession: true` |
| Nothing written to disk | **not available — Python always persists** | `persistSession: false` |

Never reach for the experimental V2 session API (`createSession()` with send/stream) — removed in TypeScript Agent SDK 0.3.142.

Read `references/sessions-and-subagents.md` before writing any resume, fork, multi-host, or `SessionStore` code — the cwd-encoding gotcha silently returns a fresh session instead of erroring.

## Options

Both languages take one options object: TS `Options` (camelCase), Python `ClaudeAgentOptions` (snake_case dataclass). Read `references/options-reference.md` for the complete field tables, the `Query`/`ClaudeSDKClient` method surfaces, session-management module functions, thinking/effort config, and the custom `Transport` ABC.

Field choices that change behavior most:

- `allowed_tools` / `allowedTools` — skip the permission prompt. Does **not** limit what exists.
- `tools` — controls **availability**: only listed built-ins enter context; `tools: []` removes all built-ins and leaves only your MCP tools.
- `disallowed_tools` — bare name (`"Bash"`) deletes the tool from context; scoped rule (`"Bash(rm *)"`) keeps the tool and denies matching calls.
- `setting_sources` / `settingSources` — omit and you inherit `user`, `project`, and `local` filesystem settings (CLI default). Pass `[]` in any hosted or multi-tenant service.
- `system_prompt` — use the `{"type": "file", "path": ...}` form for large prompts; OS argv limits are ~128 KB single-arg on Linux and ~32 KB total on Windows.
- `max_turns` / `max_budget_usd` — the only stop controls; there is no top-level session timeout.
- `env` (TS) **replaces** the subprocess environment — always spread `...process.env` unless you intend a clean env.
- `session_id` cannot be combined with `continue_conversation` or `resume` unless `fork_session=True`.

Prefer `thinking` over the deprecated `max_thinking_tokens`/`maxThinkingTokens`.

### Cross-language discrepancy: `PermissionMode` literals

The Python reference declares six modes — `"default"`, `"acceptEdits"`, `"plan"`, `"dontAsk"`, `"bypassPermissions"`, `"auto"`. The TypeScript reference page declares only four in its type union: `'default' | 'plan' | 'dontAsk' | 'bypassPermissions'`, while the permissions page documents `acceptEdits` and `auto` for both languages and the TS quickstart itself passes `permissionMode: "acceptEdits"`.

Treat all six as runtime-valid in both languages; treat the four-member TS union as a documentation lag. If a TypeScript build rejects `"acceptEdits"` or `"auto"`, pin the mode at runtime via `query.setPermissionMode(mode)` rather than assuming the mode is unsupported. Whether a given TS SDK build actually type-errors is **unverified** — the corpus records only the doc-page discrepancy.

## Custom tools are in-process MCP servers

A tool is name + description + input schema + handler. Wrap tools in `create_sdk_mcp_server` / `createSdkMcpServer` and pass the server under `mcp_servers`. Claude sees it as `mcp__{server_name}__{tool_name}` — that fully-qualified name is what goes in `allowed_tools` (wildcard `mcp__weather__*` works).

```python
@tool("get_temperature", "Get the current temperature at a location", {"latitude": float, "longitude": float})
async def get_temperature(args): ...
weather_server = create_sdk_mcp_server(name="weather", version="1.0.0", tools=[get_temperature])
options = ClaudeAgentOptions(mcp_servers={"weather": weather_server},
                             allowed_tools=["mcp__weather__get_temperature"])
```

Always set `readOnlyHint: true` on tools that don't mutate anything — it is the only annotation with behavior (enables parallel batching); the rest are informational. Always catch exceptions in the handler and return `is_error: true` with your own message: an uncaught exception does not stop the agent loop, it just hands Claude the raw exception text.

In-process SDK servers never delay the first turn. stdio and uncached HTTP/SSE servers do — default 30s, `MCP_TIMEOUT` to raise.

Read `references/custom-tools-and-mcp.md` for schema forms and optional params, annotations, image/resource/audio block handling, the Python-only `structuredContent` limitation, external stdio/HTTP/SSE config, OAuth behavior (`needs-auth`), and MCP troubleshooting.

## Permissions: know the evaluation order

Permission checks run in a fixed order, and getting this wrong is the most common source of "my `canUseTool` never fires" and "my agent did something I disallowed":

1. **Hooks** — an `allow` here does *not* skip the deny/ask rules below.
2. **Deny rules** — enforced even under `bypassPermissions`.
3. **Ask rules** — fall through to `canUseTool` even under `bypassPermissions`.
4. **Permission mode**.
5. **Allow rules**.
6. **`canUseTool`** — skipped entirely in `dontAsk` (denied instead).

Never assume `allowed_tools` constrains `bypassPermissions` — unlisted tools reach step 4 and get approved there. Carve out exceptions with `disallowed_tools` instead.

Never rely on `permissionMode: "acceptEdits"` to approve MCP tools — it covers file edits and filesystem Bash only. Use explicit `mcp__server__*` allow entries.

The locked-down read-only pattern:

```typescript
const options = { allowedTools: ["Read", "Glob", "Grep"], permissionMode: "dontAsk" };
```

Read `references/permissions-and-hooks.md` for the full mode table, rule/glob syntax (including the `Edit(path)` rule governing all file writes and the ignored unanchored `["*"]` allow rule), subagent inheritance rules, and the `CLAUDE_SDK_CAN_USE_TOOL_SHADOWED` warning.

## Hooks

Register callbacks per event via `hooks`, filtered by `matcher`. `PreToolUse` returns `permissionDecision` (`allow`/`deny`/`ask`/`defer`) plus optional `updatedInput`; `PostToolUse` returns `additionalContext`/`updatedToolOutput`. When hooks disagree: `deny` > `defer` > `ask` > `allow`.

TypeScript supports many more hook events than Python. `SessionStart` and `SessionEnd` are **not** Python SDK callback hooks — only shell-command hooks in settings files, which requires enabling `setting_sources`. Check the event table in `references/permissions-and-hooks.md` before designing a Python hook flow around an event that only exists in TS.

Matchers are exact-string when they contain only letters/digits/`_`/`-`/spaces/`,`/`|`, and unanchored regex otherwise. `mcp__memory` matches nothing — write `mcp__memory__.*`.

Never put a required policy gate only in a hook that can time out on a non-blocking event: on timeout `PreToolUse` blocks the tool, but `PostToolUse` keeps the result and continues. Hooks may not fire at all when the agent hits `max_turns`.

## Subagents

Define subagents programmatically with `agents` (recommended for SDK apps) rather than `.claude/agents/*.md` files. Always include `"Agent"` in `allowedTools` or Claude cannot invoke them.

```python
options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep", "Glob", "Agent"],
    agents={"code-reviewer": AgentDefinition(
        description="Expert code review specialist. Use for quality and security reviews.",
        prompt="You are a code review specialist...",
        tools=["Read", "Grep", "Glob"], model="sonnet")},
)
```

`AgentDefinition` uses camelCase field names even in Python (`disallowedTools`, `mcpServers`, `permissionMode`) — it mirrors the wire format, not Python convention.

A subagent receives only its own prompt plus the Agent tool's prompt string. It gets no parent conversation history, no parent system prompt, and no preloaded skill content unless listed in `AgentDefinition.skills`. Always inline the paths, errors, and decisions the subagent needs into the invocation prompt.

For dozens-to-hundreds of coordinated agents, use the `Workflow` tool instead of turn-by-turn delegation — it moves orchestration into a script outside conversation context.

Read `references/sessions-and-subagents.md` for the full `AgentDefinition` field table, subagent resumption, nesting depth, API-error handling inside subagents, and the `Task`→`Agent` tool rename.

## Structured outputs

Pass `output_format={"type": "json_schema", "schema": {...}}`. The agent still uses tools freely mid-task; the SDK validates the final output and re-prompts on mismatch.

The validator targets **JSON Schema draft-07** and rejects newer declarations — always convert Zod with `z.toJSONSchema(schema, { target: "draft-7" })`. Pydantic's `.model_json_schema()` is used directly.

Never treat `subtype === "success"` as proof of structured data — a successful run can still lack `structured_output`. Handle three cases: success with output, `error_max_structured_output_retries`, and success without output.

`"format"` (e.g. `"format": "email"`) is accepted as an annotation but not enforced. Keep schemas shallow and mark uncertain fields optional; deep nesting and many required fields drive retry exhaustion.

Read `references/structured-outputs.md` for Zod/Pydantic patterns and the two distinct causes behind `error_max_structured_output_retries`.

## Hosting and cost

`query()` spawns a `claude` CLI subprocess over stdio. One session = one subprocess; that subprocess owns the shell, cwd, and JSONL transcripts on **local disk** — none of it survives a restart, scale-down, or node move.

Always pass a per-call `cwd` when running concurrent sessions in one container; they otherwise share your app's working directory. In multi-tenant containers apply all four isolation controls together — `settingSources: []`, per-tenant `CLAUDE_CONFIG_DIR`, `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`, and per-tenant `cwd`. Auto memory loads regardless of `settingSources`, so omitting that env var leaks context across tenants.

Never run the hybrid (ephemeral container + rehydrate) pattern without a `SessionStore` — shutting down loses the transcript.

Start capacity planning at **1 GiB RAM, 5 GiB disk, 1 CPU per agent**, then measure peak RSS on a representative session; memory grows with session length and tool activity.

**`total_cost_usd`/`costUSD` are client-side estimates from a bundled price table — never bill end users or trigger financial decisions from them.** Use the Usage and Cost API or the Console for authoritative numbers.

Use `model_usage`/`modelUsage` for whole-tree accounting: `usage` excludes subagent activity and undercounts as soon as subagents run. There is no session-level total across `resume`-linked calls — accumulate it yourself. Dedupe per-step usage by message ID; parallel tool calls share one ID.

Read `references/hosting-and-cost.md` for session patterns, egress/proxy and OpenTelemetry setup, scaling math, known limitations, and cache-TTL tuning.

## Failure modes worth recognizing immediately

| Symptom | Cause |
|---|---|
| `resume` silently starts a fresh session | Process ran from a different `cwd`; transcripts key off `~/.claude/projects/<encoded-cwd>/` |
| MCP tools never called | `allowedTools` lacks `mcp__server__*`; `acceptEdits` does not cover MCP tools |
| `canUseTool` never fires | `bypassPermissions`, a bare `allowedTools` entry, or `dontAsk` mode |
| Tool result replaced by an error naming a file | MCP output exceeded 25,000 tokens — raise `MAX_MCP_OUTPUT_TOKENS` |
| Agent returns prose instead of JSON | Invalid schema silently ignored on Claude Code before v2.1.205 |
| Subagent returns "terminated early due to an API error" | API error before it produced any text |
| Windows subagent invocation fails | 8191-character command-line limit on long prompts |

## Reference files

- `references/options-reference.md` — complete TS `Options` table, Python `ClaudeAgentOptions` dataclass, `Query`/`ClaudeSDKClient` methods, session module functions, thinking/effort, custom `Transport`.
- `references/custom-tools-and-mcp.md` — tool definition, annotations, return-block types, external MCP config, connection timing, auth, troubleshooting.
- `references/permissions-and-hooks.md` — evaluation order, rule syntax, permission modes, full hook event matrix and matcher rules.
- `references/sessions-and-subagents.md` — continue/resume/fork, `SessionStore`, `AgentDefinition`, subagent inheritance and resumption.
- `references/structured-outputs.md` — schema constraints, Zod/Pydantic, error subtypes.
- `references/hosting-and-cost.md` — subprocess model, session patterns, provisioning, multi-tenant isolation, cost/usage fields.
- `references/versions/claude-code-2.1.md` — CLI-version-gated behavior changes that affect SDK apps.
- `references/versions/typescript-sdk-0.3.md` — TypeScript SDK package-version changes.

## Diagnostic scripts

- `scripts/sdk-preflight.mjs` — read-only environment check: Node/Python versions against SDK minimums, `ANTHROPIC_API_KEY`/provider env vars, installed SDK package and bundled-binary presence, and the resolved session transcript directory for the current `cwd` (the `resume` gotcha). Run with `node scripts/sdk-preflight.mjs`.

## Sources

- https://code.claude.com/docs/en/agent-sdk/overview
- https://code.claude.com/docs/en/agent-sdk/quickstart
- https://code.claude.com/docs/en/agent-sdk/typescript
- https://code.claude.com/docs/en/agent-sdk/python
- https://code.claude.com/docs/en/agent-sdk/custom-tools
- https://code.claude.com/docs/en/agent-sdk/mcp
- https://code.claude.com/docs/en/agent-sdk/permissions
- https://code.claude.com/docs/en/agent-sdk/hooks
- https://code.claude.com/docs/en/agent-sdk/sessions
- https://code.claude.com/docs/en/agent-sdk/subagents
- https://code.claude.com/docs/en/agent-sdk/structured-outputs
- https://code.claude.com/docs/en/agent-sdk/hosting
- https://code.claude.com/docs/en/agent-sdk/cost-tracking

Fetched: 2026-08-05

Files in this skill

  • SKILL.md19.5 KB
  • references/custom-tools-and-mcp.md13.2 KB
  • references/hosting-and-cost.md11.5 KB
  • references/options-reference.md18.9 KB
  • references/permissions-and-hooks.md12.8 KB
  • references/sessions-and-subagents.md13.2 KB
  • references/structured-outputs.md5.6 KB
  • references/versions/claude-code-2.1.md3.6 KB
  • references/versions/typescript-sdk-0.3.md2 KB
  • scripts/sdk-preflight.mjs8.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…