Markdown Converter
Agent skill for markdown-converter
Sign in to like and favorite skills
name: claude-agent-sdk description: | Build autonomous AI agents with Claude Agent SDK v0.2.37. Covers the complete TypeScript API: query(), hooks, subagents, MCP, permissions, sandbox, structured outputs, and sessions.
Package:
@anthropic-ai/[email protected]
Docs: https://platform.claude.com/docs/en/agent-sdk/overview
Repo: https://github.com/anthropics/claude-agent-sdk-typescript
Migration: Renamed from @anthropic-ai/claude-code. See migration guide.
systemPrompt: { type: 'preset', preset: 'claude_code' } for old behavior.settingSources defaults to []. Add settingSources: ['project'] to load CLAUDE.md.ClaudeCodeOptions renamed — Now ClaudeAgentOptions (Python).query()import { query } from "@anthropic-ai/claude-agent-sdk"; function query({ prompt: string | AsyncIterable<SDKUserMessage>, options?: Options }): Query // extends AsyncGenerator<SDKMessage, void>
tool()Creates type-safe MCP tool definitions with Zod schemas.
import { tool } from "@anthropic-ai/claude-agent-sdk"; function tool<Schema extends ZodRawShape>( name: string, description: string, inputSchema: Schema, handler: (args: z.infer<ZodObject<Schema>>, extra: unknown) => Promise<CallToolResult> ): SdkMcpToolDefinition<Schema>
Handler returns:
{ content: [{ type: "text", text: "..." }], isError?: boolean }
createSdkMcpServer()Creates an in-process MCP server.
import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk"; function createSdkMcpServer(options: { name: string; version?: string; tools?: Array<SdkMcpToolDefinition<any>>; }): McpSdkServerConfigWithInstance
| Option | Type | Default | Description |
|---|---|---|---|
| | CLI default | Claude model to use |
| | | Working directory |
| | All tools | Allowed tool names |
| | | Blocked tool names |
| | — | Tool configuration |
| | | |
| | — | Custom permission callback |
| | minimal | System prompt |
| | | |
| | — | Subagent definitions |
| | | MCP server configs |
| | | Hook callbacks |
| | — | Structured output schema |
| | — | Session ID to resume |
| | | Fork when resuming |
| | | Continue most recent conversation |
| | auto | Custom UUID for session (v0.2.33) |
| | — | Resume at specific message UUID |
| | — | Max conversation turns |
| | — | Max budget in USD |
| | — | Max thinking tokens |
| | — | Fallback model on failure |
| | | Beta features (e.g., ) |
| | — | Sandbox configuration |
| | | Enable file rollback |
| | | |
| | | Extra directories for Claude to access |
| | | Environment variables |
| | — | Cancellation controller |
| | | Required with |
| | | Include streaming partial messages |
| | — | Enable debug logging (v0.2.30) |
| | — | Debug log file path (v0.2.30) |
| | | Strict MCP validation |
| | — | stderr callback |
| | auto | JS runtime |
const q = query({ prompt: "..." }); for await (const message of q) { ... } // Primary: iterate messages // Control await q.interrupt(); // Interrupt (streaming input mode) q.close(); // Force terminate (v0.2.15) await q.setModel("claude-opus-4-6"); // Change model await q.setPermissionMode("acceptEdits"); // Change permissions await q.setMaxThinkingTokens(4096); // Change thinking budget // Introspection await q.supportedModels(); // List available models await q.supportedCommands(); // List slash commands await q.mcpServerStatus(); // MCP server status await q.accountInfo(); // Account info // File checkpointing (requires enableFileCheckpointing: true) await q.rewindFiles(userMessageUuid); // Rewind to checkpoint
type SDKMessage = | SDKAssistantMessage // type: 'assistant' — agent responses | SDKUserMessage // type: 'user' — user input | SDKResultMessage // type: 'result' — final result | SDKSystemMessage // type: 'system', subtype: 'init' — session init | SDKPartialAssistantMessage // type: 'stream_event' (includePartialMessages) | SDKCompactBoundaryMessage // type: 'system', subtype: 'compact_boundary'
// Success { type: 'result', subtype: 'success', session_id, duration_ms, duration_api_ms, is_error: false, num_turns, result: string, total_cost_usd, usage, modelUsage, permission_denials, structured_output?, stop_reason? } // Error variants { type: 'result', subtype: 'error_max_turns' | 'error_during_execution' | 'error_max_budget_usd' | 'error_max_structured_output_retries', session_id, is_error: true, errors: string[], ... }
{ type: 'system', subtype: 'init', session_id, model, tools: string[], cwd, mcp_servers: { name, status }[], permissionMode, slash_commands, apiKeySource, output_style }
{ type: 'assistant', uuid, session_id, message: APIAssistantMessage, parent_tool_use_id: string | null }
let sessionId: string; for await (const message of query({ prompt: "...", options })) { switch (message.type) { case 'system': if (message.subtype === 'init') sessionId = message.session_id; break; case 'assistant': console.log(message.message); break; case 'result': if (message.subtype === 'success') { console.log(message.result); if (message.structured_output) console.log(message.structured_output); } else { console.error(message.errors); } break; } }
Hooks use callback matchers: an optional regex
matcher for tool names and an array of hooks callbacks.
| Event | Fires When | TS | Py |
|---|---|---|---|
| Before tool execution | Yes | Yes |
| After tool execution | Yes | Yes |
| Tool execution failed | Yes | No |
| User prompt received | Yes | Yes |
| Agent stopping | Yes | Yes |
| Subagent spawned | Yes | No |
| Subagent completed | Yes | Yes |
| Before context compaction | Yes | Yes |
| Permission dialog would show | Yes | No |
| Session begins | Yes | No |
| Session ends | Yes | No |
| Agent status message | Yes | No |
type HookCallback = ( input: HookInput, // Event-specific data toolUseID: string | undefined, // Correlate Pre/PostToolUse options: { signal: AbortSignal } ) => Promise<HookJSONOutput>;
const response = query({ prompt: "...", options: { hooks: { PreToolUse: [ { matcher: 'Write|Edit', hooks: [protectFiles] }, { matcher: '^mcp__', hooks: [logMcpCalls] }, { hooks: [globalLogger] } // no matcher = all tools ], Stop: [{ hooks: [cleanup] }], // matchers ignored for lifecycle hooks Notification: [{ hooks: [notifySlack] }] } } });
// Allow (empty = allow) return {}; // Block a tool (PreToolUse only) return { hookSpecificOutput: { hookEventName: input.hook_event_name, permissionDecision: 'deny', // 'allow' | 'deny' | 'ask' permissionDecisionReason: 'Blocked: dangerous command' } }; // Modify tool input (PreToolUse only, requires permissionDecision: 'allow') return { hookSpecificOutput: { hookEventName: input.hook_event_name, permissionDecision: 'allow', updatedInput: { ...input.tool_input, file_path: `/sandbox${path}` } } }; // Inject context (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart) return { hookSpecificOutput: { hookEventName: input.hook_event_name, additionalContext: 'Extra instructions for Claude' } }; // Stop agent return { continue: false, stopReason: 'Budget exceeded' }; // Inject system message return { systemMessage: 'Remember: /etc is protected' };
Common fields on all hooks:
session_id, transcript_path, cwd, permission_mode
| Field | Hooks |
|---|---|
, | PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest |
| PostToolUse |
, | PostToolUseFailure |
| UserPromptSubmit |
| Stop, SubagentStop |
, | SubagentStart |
, | PreCompact |
| SessionStart () |
| SessionEnd |
, | Notification |
| PermissionRequest |
type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
type CanUseTool = ( toolName: string, input: ToolInput, options: { signal: AbortSignal; suggestions?: PermissionUpdate[] } ) => Promise<PermissionResult>; type PermissionResult = | { behavior: 'allow'; updatedInput: ToolInput; updatedPermissions?: PermissionUpdate[] } | { behavior: 'deny'; message: string; interrupt?: boolean };
Example:
canUseTool: async (toolName, input, { signal }) => { if (['Read', 'Grep', 'Glob'].includes(toolName)) { return { behavior: 'allow', updatedInput: input }; } if (toolName === 'Bash' && /rm -rf|dd if=|mkfs/.test(input.command)) { return { behavior: 'deny', message: 'Destructive command blocked' }; } return { behavior: 'allow', updatedInput: input }; }
// stdio (type field optional, defaults to 'stdio') { command: "npx", args: ["@playwright/mcp@latest"], env?: Record<string, string> } // HTTP (type required) { type: "http", url: "https://api.example.com/mcp", headers?: Record<string, string> } // SSE (type required) { type: "sse", url: "https://api.example.com/mcp/sse", headers?: Record<string, string> } // In-process SDK server { type: "sdk", name: "my-server", instance: mcpServerInstance }
Tool naming:
mcp__<server-name>__<tool-name> (double underscores)
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk"; import { z } from "zod"; const weatherTool = tool("get_weather", "Get weather for a city", { city: z.string().describe("City name") }, async (args) => ({ content: [{ type: "text", text: `Weather in ${args.city}: 72°F, sunny` }] })); const server = createSdkMcpServer({ name: "weather", tools: [weatherTool] }); for await (const msg of query({ prompt: "What's the weather in Tokyo?", options: { mcpServers: { weather: server } } })) { if (msg.type === 'result' && msg.subtype === 'success') console.log(msg.result); }
const myTool = tool("delete_record", "Delete a record", { id: z.string() }, async (args) => { ... }); myTool.annotations = { destructiveHint: true, readOnlyHint: false };
type AgentDefinition = { description: string; // When to use (used by main agent for delegation) prompt: string; // System prompt tools?: string[]; // Allowed tools (inherits if omitted) model?: 'sonnet' | 'opus' | 'haiku' | 'inherit'; }
Include
Task in parent's allowedTools — subagents are invoked via the Task tool.
for await (const msg of query({ prompt: "Use the reviewer to check this code", options: { allowedTools: ["Read", "Glob", "Grep", "Task"], agents: { "reviewer": { description: "Code review specialist", prompt: "Review code for bugs and best practices.", tools: ["Read", "Glob", "Grep"], model: "haiku" } } } })) { ... }
Subagents don't auto-stop when the parent stops (#132). This causes orphan processes and potential OOM (#4850).
Workaround — use a Stop hook:
hooks: { Stop: [{ hooks: [async () => { console.log("Cleaning up subagents"); // Track and terminate spawned processes return {}; }] }] }
Auto-termination tracked at #142.
Define a JSON Schema and get validated data in
message.structured_output.
import { query } from "@anthropic-ai/claude-agent-sdk"; import { z } from "zod"; const schema = z.object({ summary: z.string(), sentiment: z.enum(['positive', 'neutral', 'negative']), confidence: z.number() }); for await (const msg of query({ prompt: "Analyze this feedback", options: { outputFormat: { type: "json_schema", schema: z.toJSONSchema(schema) // Zod v3.24.1+ or v4+ } } })) { if (msg.type === 'result' && msg.subtype === 'success' && msg.structured_output) { const parsed = schema.safeParse(msg.structured_output); if (parsed.success) console.log(parsed.data); } }
Error subtype
error_max_structured_output_retries indicates validation failures after retries.
type SandboxSettings = { enabled?: boolean; autoAllowBashIfSandboxed?: boolean; excludedCommands?: string[]; // Always bypass sandbox allowUnsandboxedCommands?: boolean; // Let model request unsandboxed execution network?: { allowLocalBinding?: boolean; allowUnixSockets?: string[]; allowAllUnixSockets?: boolean; httpProxyPort?: number; socksProxyPort?: number; }; ignoreViolations?: { file?: string[]; network?: string[] }; enableWeakerNestedSandbox?: boolean; };
excludedCommands = static allowlist (model has no control).
allowUnsandboxedCommands = model can set dangerouslyDisableSandbox: true in Bash input, which falls back to canUseTool for approval.
// Capture session ID let sessionId: string; for await (const msg of query({ prompt: "Read auth module" })) { if (msg.type === 'system' && msg.subtype === 'init') sessionId = msg.session_id; } // Resume for await (const msg of query({ prompt: "Now find callers", options: { resume: sessionId } })) { ... } // Fork (creates new branch, original unchanged) for await (const msg of query({ prompt: "Try GraphQL instead", options: { resume: sessionId, forkSession: true } })) { ... }
V2 preview interface available — see TypeScript V2 preview docs.
Error:
CLI_NOT_FOUND
Fix: npm install -g @anthropic-ai/claude-code
Error: "Prompt is too long" (#138) Behavior: Session permanently broken — cannot recover or compact. Prevention: Monitor session age, fork proactively, use
maxTurns / maxBudgetUsd.
type FieldError: Cryptic "process exited with code 1" (#131) Fix: URL-based MCP servers require
type: "http" or type: "sse".
Subagents don't stop when parent stops (#132, #142). Fix: Implement Stop hook cleanup (see Subagents section).
U+2028/U+2029 in MCP tool results break parsing (#137, MCP Python SDK #1356). Fix: Sanitize:
content.replace(/[\u2028\u2029]/g, ' ')
Error:
error_during_execution with 0 tokens when using custom base URL (#144)
Fix: Downgrade to v0.2.7 or set ANTHROPIC_BASE_URL as environment variable before process start instead of via options.env.
Error: Second+ concurrent queries timeout after 60s with "MCP error -32001: Request timed out" (#122) Fix: Use stdio MCP servers instead of
createSdkMcpServer() for concurrent queries.
Error: TypeScript types resolve as
any for SDK messages/events (#121)
Fix: npm install @anthropic-ai/sdk as peer dependency.
Error: Commands/agents from
.claude/commands/ and .claude/agents/ silently not discovered on Linux remote (#129)
Fix: VS Code extensions should chmod +x ripgrep binaries at activation:
const rgPath = path.join(extensionPath, "node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep/x64-linux/rg"); await fs.promises.chmod(rgPath, 0o755);
Error: MCP tools timeout at exactly 300s with "fetch failed" even with
MCP_TOOL_TIMEOUT=1200000 (#118)
Cause: Hardcoded undici headersTimeout overrides environment variable.
Status: No workaround available — long-running MCP tools (>5min) not currently supported.
Error: Cryptic crash without detail when input is too long, session expired, or other failures (#106) Impact: Difficult to debug production issues — all errors look identical. Workaround: Enable
debug: true or debugFile: 'debug.log' option to capture detailed logs.
Error:
invalid_request_error - "tool_use ids were found without tool_result blocks" (#170)
Cause: PreToolUse hook with permissionDecision: 'deny' blocks tool execution but doesn't generate a corresponding tool_result, breaking conversation history.
Fix: Use permissionDecision: 'allow' with modified input instead:
return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', updatedInput: { command: `echo "BLOCKED: ${reason}"` } } };
Error: Zero thinking blocks despite
thinking: { type: 'adaptive' } configured (#168)
Cause: SDK sets maxThinkingTokens = undefined for adaptive mode, preventing --max-thinking-tokens CLI flag from being passed.
Fix: Use deprecated maxThinkingTokens option directly instead of thinking:
// WRONG (disables thinking) thinking: { type: 'adaptive' }, effort: 'medium' // CORRECT maxThinkingTokens: 10000, effort: 'medium'
Error: "The socket connection was closed unexpectedly" when HTTP MCP servers used behind corporate proxy with SSL inspection (#169) Cause: Bundled MCP transport doesn't propagate proxy configuration from environment variables (HTTP_PROXY, HTTPS_PROXY, NODE_EXTRA_CA_CERTS). Workaround: Use SSE-type MCP servers or stdio MCP servers instead of HTTP type.
Error:
CLI output was not valid JSON when ANTHROPIC_LOG=debug is set (#157)
Cause: Debug logs written to stdout corrupt JSON protocol between SDK and CLI subprocess.
Fix: Don't use ANTHROPIC_LOG=debug with SDK. Use debug: true or debugFile option instead.
Error: "Stream closed" errors or excessive query durations with SDK MCP servers (#114) Cause:
sendMcpServerMessageToCli() resolves MCP responses but doesn't reset lastActivityTime, causing premature timeouts or unnecessary waits.
Fix: Increase CLAUDE_CODE_STREAM_CLOSE_TIMEOUT (e.g., 120000 for 120s) or apply community patch.
Error:
Claude Code executable not found at /$bunfs/root/cli.js (#150)
Cause: import.meta.url resolves to virtual filesystem path when bundled, where CLI binary doesn't physically exist.
Workaround: Set pathToClaudeCodeExecutable option explicitly to the physical CLI path, or avoid bundling the SDK.
| Version | Change |
|---|---|
| v0.2.33 | / hook events; custom option |
| v0.2.31 | field on result messages |
| v0.2.30 | / options; PDF page reading |
| v0.2.27 | MCP tool support |
| v0.2.23 | Structured output validation fix |
| v0.2.21 | , , MCP status |
| v0.2.15 | method on Query; notification hooks |
Last verified: 2026-02-09 | SDK version: 0.2.37