Build autonomous AI agents with Claude Agent SDK. Structured outputs (v0.1.45, Nov 2025) guarantee JSON schema validation, plugins system, hooks for event-driven workflows...
Package: @anthropic-ai/claude-agent-sdk@0.1.50 (Nov 21, 2025) Breaking Changes: v0.1.45 - Structured outputs (Nov 2025), v0.1.0 - No default system prompt, settingSources required
Major Features:
outputFormat parameter - Define output structure with JSON schema or Zodmessage.structured_outputstructured-outputs-2025-11-13Example:
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().min(0).max(1)
});
const response = query({
prompt: "Analyze this code review feedback",
options: {
model: "claude-sonnet-4-5",
outputFormat: {
type: "json_schema",
json_schema: {
name: "AnalysisResult",
strict: true,
schema: zodToJsonSchema(schema)
}
}
}
});
for await (const message of response) {
if (message.type === 'result' && message.structured_output) {
// Guaranteed to match schema
const validated = schema.parse(message.structured_output);
console.log(`Sentiment: ${validated.sentiment}`);
}
}
plugins array - Load local plugin pathsfallbackModel - Automatic model fallback on failuresmaxThinkingTokens - Control extended thinking budgetstrictMcpConfig - Strict MCP configuration validationcontinue - Resume with new prompt (differs from resume)permissionMode: 'plan' - New permission mode for planning workflows📚 Docs: https://platform.claude.com/docs/en/agent-sdk/structured-outputs
Key signature:
query(prompt: string | AsyncIterable<SDKUserMessage>, options?: Options)
-> AsyncGenerator<SDKMessage>
Critical Options:
outputFormat - Structured JSON schema validation (v0.1.45+)settingSources - Filesystem settings loading ('user'|'project'|'local')canUseTool - Custom permission logic callbackagents - Programmatic subagent definitionsmcpServers - MCP server configurationpermissionMode - 'default'|'acceptEdits'|'bypassPermissions'|'plan'Tool Control:
allowedTools - Whitelist (takes precedence)disallowedTools - BlacklistcanUseTool - Custom permission callback (see Permission Control section)Built-in Tools: Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, Task, NotebookEdit, BashOutput, KillBash, ListMcpResources, ReadMcpResource
Server Types:
createSdkMcpServer() with tool() definitionsTool Definition:
tool(name: string, description: string, zodSchema, handler)
Handler Return:
{ content: [{ type: "text", text: "..." }], isError?: boolean }
const response = query({
prompt: "List files and analyze Git history",
options: {
mcpServers: {
// Filesystem server
"filesystem": {
command: "npx",
args: ["@modelcontextprotocol/server-filesystem"],
env: {
ALLOWED_PATHS: "/Users/developer/projects:/tmp"
}
},
// Git operations server
"git": {
command: "npx",
args: ["@modelcontextprotocol/server-git"],
env: {
GIT_REPO_PATH: "/Users/developer/projects/my-repo"
}
}
},
allowedTools: [
"mcp__filesystem__list_files",
"mcp__filesystem__read_file",
"mcp__git__log",
"mcp__git__diff"
]
}
});
const response = query({
prompt: "Analyze data from remote service",
options: {
mcpServers: {
"remote-service": {
url: "https://api.example.com/mcp",
headers: {
"Authorization": "Bearer your-token-here",
"Content-Type": "application/json"
}
}
},
allowedTools: ["mcp__remote-service__analyze"]
}
});
Format: mcp__<server-name>__<tool-name>
CRITICAL:
__) as separatorsallowedTools arrayExamples: mcp__weather-service__get_weather, mcp__filesystem__read_file
type AgentDefinition = {
description: string; // When to use this agent
prompt: string; // System prompt for agent
tools?: string[]; // Allowed tools (optional)
model?: 'sonnet' | 'opus' | 'haiku' | 'inherit'; // Model (optional)
}
Field Details:
haiku/sonnet/opus/inherit)Usage:
agents: {
"security-checker": {
description: "Security audits and vulnerability scanning",
prompt: "You check security. Scan for secrets, verify OWASP compliance.",
tools: ["Read", "Grep", "Bash"],
model: "sonnet"
}
}
Options:
resume: sessionId - Continue previous sessionforkSession: true - Create new branch from sessioncontinue: prompt - Resume with new prompt (differs from resume)Session Forking Pattern (Unique Capability):
// Explore alternative without modifying original
const forked = query({
prompt: "Try GraphQL instead of REST",
options: {
resume: sessionId,
forkSession: true // Creates new branch, original session unchanged
}
});
Capture Session ID:
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
sessionId = message.session_id; // Save for later resume/fork
}
}
Permission Modes:
type PermissionMode = "default" | "acceptEdits" | "bypassPermissions" | "plan";
default - Standard permission checksacceptEdits - Auto-approve file editsbypassPermissions - Skip ALL checks (use in CI/CD only)plan - Planning mode (v0.1.45+)const response = query({
prompt: "Deploy application to production",
options: {
permissionMode: "default",
canUseTool: async (toolName, input) => {
// Allow read-only operations
if (['Read', 'Grep', 'Glob'].includes(toolName)) {
return { behavior: "allow" };
}
// Deny destructive bash commands
if (toolName === 'Bash') {
const dangerous = ['rm -rf', 'dd if=', 'mkfs', '> /dev/'];
if (dangerous.some(pattern => input.command.includes(pattern))) {
return {
behavior: "deny",
message: "Destructive command blocked for safety"
};
}
}
// Require confirmation for deployments
if (input.command?.includes('deploy') || input.command?.includes('kubectl apply')) {
return {
behavior: "ask",
message: "Confirm deployment to production?"
};
}
// Allow by default
return { behavior: "allow" };
}
}
});
type CanUseToolCallback = (
toolName: string,
input: any
) => Promise<PermissionDecision>;
type PermissionDecision =
| { behavior: "allow" }
| { behavior: "deny"; message?: string }
| { behavior: "ask"; message?: string };
Examples:
// Block all file writes
canUseTool: async (toolName, input) => {
if (toolName === 'Write' || toolName === 'Edit') {
return { behavior: "deny", message: "No file modifications allowed" };
}
return { behavior: "allow" };
}
// Require confirmation for specific files
canUseTool: async (toolName, input) => {
const sensitivePaths = ['/etc/', '/root/', '.env', 'credentials.json'];
if ((toolName === 'Write' || toolName === 'Edit') &&
sensitivePaths.some(path => input.file_path?.includes(path))) {
return {
behavior: "ask",
message: `Modify sensitive file ${input.file_path}?`
};
}
return { behavior: "allow" };
}
// Log all tool usage
canUseTool: async (toolName, input) => {
console.log(`Tool requested: ${toolName}`, input);
await logToDatabase(toolName, input);
return { behavior: "allow" };
}
Setting Sources:
type SettingSource = 'user' | 'project' | 'local';
user - ~/.claude/settings.json (global)project - .claude/settings.json (team-shared)local - .claude/settings.local.json (gitignored overrides)Default: NO settings loaded (settingSources: [])
When multiple sources loaded, settings merge in this order (highest priority first):
query()) - Always win.claude/settings.local.json).claude/settings.json)~/.claude/settings.json)Example:
// .claude/settings.json
{
"allowedTools": ["Read", "Write", "Edit"]
}
// .claude/settings.local.json
{
"allowedTools": ["Read"] // Overrides project settings
}
// Programmatic
const response = query({
options: {
settingSources: ["project", "local"],
allowedTools: ["Read", "Grep"] // ← This wins
}
});
// Actual allowedTools: ["Read", "Grep"]
Best Practice: Use settingSources: ["project"] in CI/CD for consistent behavior.
Message Types:
system - Session init/completion (includes session_id)assistant - Agent responsestool_call - Tool execution requeststool_result - Tool execution resultserror - Error messagesresult - Final result (includes structured_output for v0.1.45+)Streaming Pattern:
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
sessionId = message.session_id; // Capture for resume/fork
}
if (message.type === 'result' && message.structured_output) {
// Structured output available (v0.1.45+)
const validated = schema.parse(message.structured_output);
}
}
Error Codes:
| Error Code | Cause | Solution |
|---|---|---|
CLI_NOT_FOUND |
Claude Code not installed | Install: npm install -g @anthropic-ai/claude-code |
AUTHENTICATION_FAILED |
Invalid API key | Check ANTHROPIC_API_KEY env var |
RATE_LIMIT_EXCEEDED |
Too many requests | Implement retry with backoff |
CONTEXT_LENGTH_EXCEEDED |
Prompt too long | Use session compaction, reduce context |
PERMISSION_DENIED |
Tool blocked | Check permissionMode, canUseTool |
TOOL_EXECUTION_FAILED |
Tool error | Check tool implementation |
SESSION_NOT_FOUND |
Invalid session ID | Verify session ID |
MCP_SERVER_FAILED |
Server error | Check server configuration |
This skill prevents 12 documented issues:
Error: "Claude Code CLI not installed"
Source: SDK requires Claude Code CLI
Why It Happens: CLI not installed globally
Prevention: Install before using SDK: npm install -g @anthropic-ai/claude-code
Error: "Invalid API key"
Source: Missing or incorrect ANTHROPIC_API_KEY
Why It Happens: Environment variable not set
Prevention: Always set export ANTHROPIC_API_KEY="sk-ant-..."
Error: Tool execution blocked
Source: permissionMode restrictions
Why It Happens: Tool not allowed by permissions
Prevention: Use allowedTools or custom canUseTool callback
Error: "Prompt too long"
Source: Input exceeds model context window
Why It Happens: Large codebase, long conversations
Prevention: SDK auto-compacts, but reduce context if needed
Error: Tool doesn't respond Source: Long-running tool execution Why It Happens: Tool takes too long (>5 minutes default) Prevention: Implement timeout handling in tool implementations
Error: "Invalid session ID"
Source: Session expired or invalid
Why It Happens: Session ID incorrect or too old
Prevention: Capture session_id from system init message
Error: Server not responding Source: Server not running or misconfigured Why It Happens: Command/URL incorrect, server crashed Prevention: Test MCP server independently, verify command/URL
Error: Invalid AgentDefinition
Source: Missing required fields
Why It Happens: description or prompt missing
Prevention: Always include description and prompt fields
Error: "Cannot read settings"
Source: Settings file doesn't exist
Why It Happens: settingSources includes non-existent file
Prevention: Check file exists before including in sources
Error: Duplicate tool name Source: Multiple tools with same name Why It Happens: Two MCP servers define same tool name Prevention: Use unique tool names, prefix with server name
Error: Invalid tool input
Source: Input doesn't match Zod schema
Why It Happens: Agent provided wrong data type
Prevention: Use descriptive Zod schemas with .describe()
Error: Cannot access path
Source: Restricted filesystem access
Why It Happens: Path outside workingDirectory or no permissions
Prevention: Set correct workingDirectory, check file permissions
Token Efficiency:
Errors prevented: 12 documented issues with exact solutions Key value: Structured outputs (v0.1.45+), session forking, canUseTool patterns, settingSources priority, MCP naming, error codes
Last verified: 2025-11-22 | Skill version: 2.0.0 | Changes: Added v0.1.45 structured outputs, plugins, hooks, new options. Removed tutorial/basic examples (~750 lines). Focused on knowledge gaps + error prevention + advanced patterns.