Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:23:58 +08:00
commit 18c5d51e47
22 changed files with 5082 additions and 0 deletions

View File

@@ -0,0 +1,387 @@
# MCP Servers Guide
Complete guide to creating and using Model Context Protocol (MCP) servers with Claude Agent SDK.
---
## What Are MCP Servers?
MCP servers extend agent capabilities with custom tools. Think of them as plugins that give your agent new abilities.
**Use Cases**:
- Database access
- API integrations
- Custom calculations
- External service interactions
- File system operations
---
## Creating In-Process MCP Servers
### Basic Server
```typescript
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
const myServer = createSdkMcpServer({
name: "my-service",
version: "1.0.0",
tools: [
tool(
"tool_name",
"Tool description",
{ /* Zod schema */ },
async (args) => {
// Implementation
return {
content: [{ type: "text", text: "Result" }]
};
}
)
]
});
```
### Tool Definition Pattern
```typescript
tool(
name: string, // Tool identifier
description: string, // What it does
inputSchema: ZodSchema, // Input validation
handler: Handler // Implementation
)
```
---
## Zod Schemas
### Common Patterns
```typescript
import { z } from "zod";
// String
z.string()
z.string().email()
z.string().url()
z.string().min(5).max(100)
z.string().describe("Description for AI")
// Number
z.number()
z.number().int()
z.number().positive()
z.number().min(0).max(100)
// Boolean
z.boolean()
z.boolean().default(false)
// Enum
z.enum(["option1", "option2", "option3"])
z.union([z.literal("a"), z.literal("b")])
// Optional
z.string().optional()
z.number().default(10)
// Object
z.object({
name: z.string(),
age: z.number(),
email: z.string().email().optional()
})
// Array
z.array(z.string())
z.array(z.number()).min(1).max(10)
// Complex nested
z.object({
user: z.object({
id: z.string().uuid(),
name: z.string(),
roles: z.array(z.enum(["admin", "user", "guest"]))
}),
metadata: z.record(z.any()).optional()
})
```
### Best Practices
```typescript
// ✅ Good: Clear descriptions
{
location: z.string().describe("City name or coordinates (e.g., 'San Francisco, CA')"),
radius: z.number().min(1).max(100).describe("Search radius in kilometers")
}
// ❌ Bad: No descriptions
{
location: z.string(),
radius: z.number()
}
// ✅ Good: Validation constraints
{
email: z.string().email(),
age: z.number().int().min(0).max(120),
role: z.enum(["admin", "user", "guest"])
}
// ❌ Bad: No validation
{
email: z.string(),
age: z.number(),
role: z.string()
}
```
---
## Handler Implementation
### Success Response
```typescript
async (args) => {
const result = await performOperation(args);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
```
### Error Response
```typescript
async (args) => {
try {
const result = await riskyOperation(args);
return {
content: [{ type: "text", text: result }]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true // Mark as error
};
}
}
```
---
## Complete Examples
### Weather Service
```typescript
const weatherServer = createSdkMcpServer({
name: "weather",
version: "1.0.0",
tools: [
tool(
"get_weather",
"Get current weather for a location",
{
location: z.string().describe("City name"),
units: z.enum(["celsius", "fahrenheit"]).default("celsius")
},
async (args) => {
const response = await fetch(
`https://api.weather.com/v1/current?location=${args.location}&units=${args.units}`
);
const data = await response.json();
return {
content: [{
type: "text",
text: `Temp: ${data.temp}° ${args.units}\nConditions: ${data.conditions}`
}]
};
}
),
tool(
"get_forecast",
"Get 7-day forecast",
{
location: z.string(),
days: z.number().min(1).max(7).default(7)
},
async (args) => {
const forecast = await fetchForecast(args.location, args.days);
return {
content: [{ type: "text", text: JSON.stringify(forecast, null, 2) }]
};
}
)
]
});
```
### Database Service
```typescript
const databaseServer = createSdkMcpServer({
name: "database",
version: "1.0.0",
tools: [
tool(
"query",
"Execute SQL query",
{
sql: z.string().describe("SQL query to execute"),
params: z.array(z.any()).optional().describe("Query parameters")
},
async (args) => {
try {
const results = await db.query(args.sql, args.params);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }]
};
} catch (error) {
return {
content: [{ type: "text", text: `SQL Error: ${error.message}` }],
isError: true
};
}
}
)
]
});
```
---
## Using MCP Servers
### In Query Options
```typescript
const response = query({
prompt: "What's the weather in NYC?",
options: {
mcpServers: {
"weather": weatherServer,
"database": databaseServer
},
allowedTools: [
"mcp__weather__get_weather",
"mcp__database__query"
]
}
});
```
### Tool Naming Convention
**Format**: `mcp__<server-name>__<tool-name>`
Examples:
- `mcp__weather__get_weather`
- `mcp__database__query`
- `mcp__filesystem__read_file`
**CRITICAL**: Server and tool names must match exactly.
---
## External MCP Servers
### Stdio Servers
```typescript
options: {
mcpServers: {
"filesystem": {
command: "npx",
args: ["@modelcontextprotocol/server-filesystem"],
env: {
ALLOWED_PATHS: "/path/to/allowed/dir"
}
}
}
}
```
### HTTP/SSE Servers
```typescript
options: {
mcpServers: {
"remote": {
url: "https://api.example.com/mcp",
headers: {
"Authorization": "Bearer token",
"Content-Type": "application/json"
}
}
}
}
```
---
## Best Practices
### ✅ Do
- Use clear tool names and descriptions
- Add `.describe()` to all Zod fields
- Implement error handling in handlers
- Validate inputs with Zod constraints
- Return clear, formatted responses
- Test tools independently before integration
- Use unique tool names across servers
- Version your servers
### ❌ Don't
- Use generic names like "process" or "run"
- Skip input validation
- Return raw error objects
- Forget `isError: true` on errors
- Use duplicate tool names
- Expose sensitive operations without checks
- Skip testing in isolation
---
## Troubleshooting
### Tool Not Found
**Problem**: `"Tool mcp__server__tool not found"`
**Solution**:
1. Check server name matches
2. Check tool name matches
3. Include in `allowedTools` array
4. Verify server added to `mcpServers`
### Tool Name Collision
**Problem**: Two tools with same name
**Solution**: Use unique names or prefix with server name
### Validation Errors
**Problem**: Invalid input to tool
**Solution**: Add descriptive Zod schemas with constraints
---
**For more details**: See SKILL.md
**Official MCP docs**: https://modelcontextprotocol.io/

View File

@@ -0,0 +1,429 @@
# Permissions Guide
Complete guide to permission control in Claude Agent SDK.
---
## Permission Modes
### Overview
Three built-in modes:
| Mode | Behavior | Use Case |
|------|----------|----------|
| `default` | Standard checks | General use, production |
| `acceptEdits` | Auto-approve file edits | Trusted refactoring |
| `bypassPermissions` | Skip ALL checks | CI/CD, sandboxed envs |
---
## Default Mode
Standard permission checks.
```typescript
options: {
permissionMode: "default"
}
```
**Prompts user for**:
- File writes/edits
- Potentially dangerous bash commands
- Sensitive operations
**Auto-allows**:
- Read operations (Read, Grep, Glob)
- Safe bash commands
---
## Accept Edits Mode
Automatically approves file modifications.
```typescript
options: {
permissionMode: "acceptEdits"
}
```
**Auto-approves**:
- File edits (Edit)
- File writes (Write)
**Still prompts for**:
- Dangerous bash commands
- Sensitive operations
**Use when**: Refactoring, code generation workflows
---
## Bypass Permissions Mode
⚠️ **DANGER**: Skips ALL permission checks.
```typescript
options: {
permissionMode: "bypassPermissions"
}
```
**Auto-approves EVERYTHING**:
- All file operations
- All bash commands
- All tools
**ONLY use in**:
- CI/CD pipelines
- Sandboxed containers
- Docker environments
- Trusted, isolated contexts
**NEVER use in**:
- Production systems
- User-facing environments
- Untrusted inputs
---
## Custom Permission Logic
### canUseTool Callback
```typescript
type CanUseTool = (
toolName: string,
input: any
) => Promise<PermissionDecision>;
type PermissionDecision =
| { behavior: "allow" }
| { behavior: "deny"; message?: string }
| { behavior: "ask"; message?: string };
```
### Basic Example
```typescript
options: {
canUseTool: async (toolName, input) => {
// Allow read-only
if (['Read', 'Grep', 'Glob'].includes(toolName)) {
return { behavior: "allow" };
}
// Deny dangerous bash
if (toolName === 'Bash' && input.command.includes('rm -rf')) {
return {
behavior: "deny",
message: "Destructive command blocked"
};
}
// Ask for confirmation
if (toolName === 'Write') {
return {
behavior: "ask",
message: `Create ${input.file_path}?`
};
}
return { behavior: "allow" };
}
}
```
---
## Common Patterns
### Pattern 1: Block Destructive Commands
```typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Bash') {
const dangerous = [
'rm -rf',
'dd if=',
'mkfs',
'> /dev/',
'shutdown',
'reboot',
'kill -9',
'pkill'
];
for (const pattern of dangerous) {
if (input.command.includes(pattern)) {
return {
behavior: "deny",
message: `Blocked dangerous command: ${pattern}`
};
}
}
}
return { behavior: "allow" };
}
```
### Pattern 2: Protect Sensitive Files
```typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Write' || toolName === 'Edit') {
const sensitivePaths = [
'/etc/',
'/root/',
'.env',
'credentials',
'secrets',
'config/production',
'.ssh/',
'private_key'
];
for (const path of sensitivePaths) {
if (input.file_path?.includes(path)) {
return {
behavior: "ask",
message: `⚠️ Modify sensitive file: ${input.file_path}?`
};
}
}
}
return { behavior: "allow" };
}
```
### Pattern 3: Environment-Based Permissions
```typescript
const environment = process.env.NODE_ENV; // 'development' | 'staging' | 'production'
canUseTool: async (toolName, input) => {
// Production: require approval for everything
if (environment === 'production') {
if (toolName === 'Bash' || toolName === 'Write' || toolName === 'Edit') {
return {
behavior: "ask",
message: `PRODUCTION: Approve ${toolName}?`
};
}
}
// Staging: auto-approve edits
if (environment === 'staging') {
if (toolName === 'Write' || toolName === 'Edit') {
return { behavior: "allow" };
}
}
// Development: allow most things
if (environment === 'development') {
return { behavior: "allow" };
}
return { behavior: "allow" };
}
```
### Pattern 4: Deployment Confirmation
```typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Bash') {
const deploymentPatterns = [
'deploy',
'kubectl apply',
'terraform apply',
'helm install',
'docker push',
'npm publish'
];
for (const pattern of deploymentPatterns) {
if (input.command.includes(pattern)) {
return {
behavior: "ask",
message: `🚀 DEPLOYMENT: ${input.command}\n\nProceed?`
};
}
}
}
return { behavior: "allow" };
}
```
### Pattern 5: Audit Logging
```typescript
const auditLog: Array<{
tool: string;
input: any;
decision: string;
timestamp: Date;
}> = [];
canUseTool: async (toolName, input) => {
// Log everything
console.log(`[${new Date().toISOString()}] ${toolName}`);
const decision = { behavior: "allow" as const };
// Store audit log
auditLog.push({
tool: toolName,
input,
decision: decision.behavior,
timestamp: new Date()
});
// Could also send to external service
// await logToDatadog(toolName, input, decision);
return decision;
}
```
---
## Combining Modes with Custom Logic
```typescript
options: {
permissionMode: "acceptEdits", // Auto-approve file edits
canUseTool: async (toolName, input) => {
// But still block dangerous bash
if (toolName === 'Bash' && input.command.includes('rm -rf')) {
return { behavior: "deny", message: "Blocked" };
}
// Custom logic runs AFTER permission mode
return { behavior: "allow" };
}
}
```
---
## Security Best Practices
### ✅ Do
- Start with `"default"` mode
- Use `canUseTool` for fine-grained control
- Block known dangerous patterns
- Require confirmation for sensitive ops
- Log all tool usage for auditing
- Test permission logic thoroughly
- Use environment-based rules
- Implement rate limiting if needed
### ❌ Don't
- Use `"bypassPermissions"` in production
- Skip permission checks for "trusted" inputs
- Allow arbitrary bash without filtering
- Trust file paths without validation
- Ignore audit logging
- Assume AI won't make mistakes
- Give blanket approvals
---
## Testing Permissions
```typescript
async function testPermissions() {
const tests = [
{ tool: "Read", input: { file_path: "/etc/passwd" }, expectAllow: true },
{ tool: "Bash", input: { command: "rm -rf /" }, expectDeny: true },
{ tool: "Write", input: { file_path: ".env" }, expectAsk: true }
];
for (const test of tests) {
const decision = await canUseTool(test.tool, test.input);
if (test.expectAllow && decision.behavior !== 'allow') {
console.error(`FAIL: Expected allow for ${test.tool}`);
}
if (test.expectDeny && decision.behavior !== 'deny') {
console.error(`FAIL: Expected deny for ${test.tool}`);
}
if (test.expectAsk && decision.behavior !== 'ask') {
console.error(`FAIL: Expected ask for ${test.tool}`);
}
}
}
```
---
## Common Scenarios
### Allow Read-Only
```typescript
canUseTool: async (toolName, input) => {
if (['Read', 'Grep', 'Glob'].includes(toolName)) {
return { behavior: "allow" };
}
return {
behavior: "deny",
message: "Read-only mode"
};
}
```
### Require Approval for All
```typescript
canUseTool: async (toolName, input) => {
return {
behavior: "ask",
message: `Approve ${toolName}?`
};
}
```
### Block Bash Entirely
```typescript
canUseTool: async (toolName, input) => {
if (toolName === 'Bash') {
return {
behavior: "deny",
message: "Bash execution disabled"
};
}
return { behavior: "allow" };
}
```
---
## Error Handling
Handle permission errors gracefully:
```typescript
for await (const message of response) {
if (message.type === 'error') {
if (message.error.type === 'permission_denied') {
console.log('Permission denied for:', message.error.tool);
// Continue with fallback or skip
}
}
}
```
---
**For more details**: See SKILL.md
**Template**: templates/permission-control.ts

View File

@@ -0,0 +1,437 @@
# Query API Reference
Complete reference for the `query()` function - the primary interface for Claude Agent SDK.
---
## Function Signature
```typescript
function query(config: {
prompt: string | AsyncIterable<SDKUserMessage>;
options?: Options;
}): AsyncGenerator<SDKMessage, void>;
```
---
## Parameters
### prompt
**Type**: `string | AsyncIterable<SDKUserMessage>`
**Required**: Yes
The task or question for the agent.
```typescript
// Simple string prompt
query({ prompt: "Analyze the codebase" })
// Streaming prompt (advanced)
query({ prompt: streamingUserMessages() })
```
### options
**Type**: `Options`
**Required**: No
Configuration options for the query.
---
## Options Reference
### model
**Type**: `"sonnet" | "haiku" | "opus" | "claude-sonnet-4-5" | "inherit"`
**Default**: `"sonnet"`
Model to use for the agent.
```typescript
options: {
model: "claude-sonnet-4-5" // Specific version
model: "haiku" // Fast
model: "opus" // Maximum capability
model: "inherit" // Use parent model (subagents)
}
```
### workingDirectory
**Type**: `string`
**Default**: Current working directory
Directory where agent operates.
```typescript
options: {
workingDirectory: "/path/to/project"
}
```
### systemPrompt
**Type**: `string | { type: 'preset', preset: 'claude_code' }`
**Default**: None
System prompt that defines agent behavior.
```typescript
// Custom prompt
options: {
systemPrompt: "You are a security-focused code reviewer."
}
// Use CLAUDE.md from project
options: {
systemPrompt: { type: 'preset', preset: 'claude_code' },
settingSources: ["project"] // Required to load CLAUDE.md
}
```
### allowedTools
**Type**: `string[]`
**Default**: All tools
Whitelist of tools agent can use.
```typescript
options: {
allowedTools: ["Read", "Grep", "Glob"] // Read-only
}
```
### disallowedTools
**Type**: `string[]`
**Default**: None
Blacklist of tools agent cannot use.
```typescript
options: {
disallowedTools: ["Bash", "Write", "Edit"] // No modifications
}
```
**Note**: If both specified, `allowedTools` wins.
### permissionMode
**Type**: `"default" | "acceptEdits" | "bypassPermissions"`
**Default**: `"default"`
Permission strategy.
```typescript
options: {
permissionMode: "default" // Standard checks
permissionMode: "acceptEdits" // Auto-approve edits
permissionMode: "bypassPermissions" // Skip all checks (caution!)
}
```
### canUseTool
**Type**: `(toolName: string, input: any) => Promise<PermissionDecision>`
**Default**: None
Custom permission logic.
```typescript
options: {
canUseTool: async (toolName, input) => {
if (toolName === 'Bash' && input.command.includes('rm -rf')) {
return { behavior: "deny", message: "Blocked" };
}
return { behavior: "allow" };
}
}
```
**PermissionDecision**:
- `{ behavior: "allow" }` - Allow execution
- `{ behavior: "deny", message?: string }` - Block execution
- `{ behavior: "ask", message?: string }` - Prompt user
### agents
**Type**: `Record<string, AgentDefinition>`
**Default**: None
Subagent definitions.
```typescript
options: {
agents: {
"test-runner": {
description: "Run test suites",
prompt: "You run tests. Fail if any test fails.",
tools: ["Bash", "Read"],
model: "haiku"
}
}
}
```
**AgentDefinition**:
- `description` (string, required) - When to use agent
- `prompt` (string, required) - Agent's system prompt
- `tools` (string[], optional) - Allowed tools
- `model` (string, optional) - Model override
### mcpServers
**Type**: `Record<string, McpServerConfig>`
**Default**: None
MCP server configurations.
```typescript
options: {
mcpServers: {
"custom-server": customServer, // In-process
"filesystem": { // External (stdio)
command: "npx",
args: ["@modelcontextprotocol/server-filesystem"]
},
"remote": { // External (HTTP)
url: "https://api.example.com/mcp",
headers: { "Authorization": "Bearer token" }
}
}
}
```
### settingSources
**Type**: `("user" | "project" | "local")[]`
**Default**: `[]` (no filesystem settings)
Filesystem settings to load.
```typescript
options: {
settingSources: ["project"] // Project only (CI/CD)
settingSources: ["user", "project", "local"] // All sources
settingSources: [] // Isolated (no files)
}
```
**Files**:
- `user` = `~/.claude/settings.json`
- `project` = `.claude/settings.json`
- `local` = `.claude/settings.local.json`
**Priority**: Programmatic > Local > Project > User
### resume
**Type**: `string`
**Default**: None
Session ID to resume.
```typescript
options: {
resume: "session-id-here"
}
```
### forkSession
**Type**: `boolean`
**Default**: `false`
Create new branch from resumed session.
```typescript
options: {
resume: "session-id-here",
forkSession: true // New branch, original unchanged
}
```
---
## Return Value
**Type**: `AsyncGenerator<SDKMessage, void>`
Asynchronous generator yielding messages.
```typescript
const response = query({ prompt: "..." });
for await (const message of response) {
// Process message
}
```
---
## Message Types
See full details in SKILL.md. Summary:
| Type | When | Data |
|------|------|------|
| `system` | Session events | `session_id`, `model`, `tools` |
| `assistant` | Agent response | `content` (string or blocks) |
| `tool_call` | Tool requested | `tool_name`, `input` |
| `tool_result` | Tool completed | `tool_name`, `result` |
| `error` | Error occurred | `error` object |
---
## Usage Patterns
### Basic Query
```typescript
const response = query({
prompt: "Analyze code",
options: { model: "sonnet" }
});
for await (const message of response) {
if (message.type === 'assistant') {
console.log(message.content);
}
}
```
### With Tools
```typescript
const response = query({
prompt: "Review and fix bugs",
options: {
model: "sonnet",
allowedTools: ["Read", "Grep", "Edit"]
}
});
```
### With Subagents
```typescript
const response = query({
prompt: "Deploy to production",
options: {
agents: {
"tester": { /* ... */ },
"deployer": { /* ... */ }
}
}
});
```
### With Custom Tools
```typescript
const response = query({
prompt: "Get weather and send notification",
options: {
mcpServers: { "weather": weatherServer },
allowedTools: ["mcp__weather__get_weather"]
}
});
```
### With Session Management
```typescript
// Start
let session = await startSession("Build API");
// Resume
await resumeSession(session, "Add auth");
// Fork
await forkSession(session, "Try GraphQL instead");
```
---
## Best Practices
### ✅ Do
- Set specific `allowedTools` for security
- Use `canUseTool` for fine-grained control
- Implement error handling for all queries
- Capture `session_id` for resuming
- Use `workingDirectory` for clarity
- Test MCP servers independently
- Monitor tool execution with `tool_call` messages
### ❌ Don't
- Use `bypassPermissions` in production (unless sandboxed)
- Ignore error messages
- Skip session ID capture if planning to resume
- Allow unrestricted Bash without `canUseTool`
- Load user settings in CI/CD
- Use duplicate tool names
---
## Error Handling
```typescript
try {
const response = query({ prompt: "..." });
for await (const message of response) {
if (message.type === 'error') {
console.error('Agent error:', message.error);
}
}
} catch (error) {
if (error.code === 'CLI_NOT_FOUND') {
console.error('Install Claude Code CLI');
}
}
```
---
## TypeScript Types
```typescript
type Options = {
model?: "sonnet" | "haiku" | "opus" | string;
workingDirectory?: string;
systemPrompt?: string | { type: 'preset', preset: 'claude_code' };
allowedTools?: string[];
disallowedTools?: string[];
permissionMode?: "default" | "acceptEdits" | "bypassPermissions";
canUseTool?: (toolName: string, input: any) => Promise<PermissionDecision>;
agents?: Record<string, AgentDefinition>;
mcpServers?: Record<string, McpServerConfig>;
settingSources?: ("user" | "project" | "local")[];
resume?: string;
forkSession?: boolean;
};
type AgentDefinition = {
description: string;
prompt: string;
tools?: string[];
model?: "sonnet" | "opus" | "haiku" | "inherit";
};
type PermissionDecision =
| { behavior: "allow" }
| { behavior: "deny"; message?: string }
| { behavior: "ask"; message?: string };
```
---
**For more details**: See SKILL.md
**Official docs**: https://docs.claude.com/en/api/agent-sdk/typescript

View File

@@ -0,0 +1,419 @@
# Session Management Guide
Complete guide to sessions, resuming, and forking in Claude Agent SDK.
---
## What Are Sessions?
Sessions enable:
- **Persistent conversations** - Resume where you left off
- **Context preservation** - Agent remembers everything
- **Alternative paths** - Fork to explore different approaches
---
## Session Lifecycle
```
Start → Capture Session ID → Resume → Resume → ... → End
Fork (alternative path)
```
---
## Starting a Session
Every `query()` call creates a session.
```typescript
let sessionId: string | undefined;
const response = query({
prompt: "Build a REST API",
options: { model: "sonnet" }
});
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
sessionId = message.session_id;
console.log(`Session started: ${sessionId}`);
}
}
```
**CRITICAL**: Capture `session_id` from `system` init message.
---
## Resuming a Session
Continue a previous conversation.
```typescript
const resumed = query({
prompt: "Now add authentication",
options: {
resume: sessionId, // Resume previous session
model: "sonnet"
}
});
```
**What's preserved**:
- All previous messages
- Agent's understanding of context
- Files created/modified
- Decisions made
**What's NOT preserved**:
- Environment variables
- Tool availability (specify again)
- Permission settings (specify again)
---
## Forking a Session
Create alternative path without modifying original.
```typescript
const forked = query({
prompt: "Actually, make it GraphQL instead",
options: {
resume: sessionId,
forkSession: true, // Creates new branch
model: "sonnet"
}
});
```
**Result**:
- New session created
- Starts from same point as original
- Original session unchanged
- Can compare approaches
---
## Use Case Patterns
### Pattern 1: Sequential Development
Step-by-step feature building.
```typescript
// Step 1: Initial implementation
let session = await startSession("Create user authentication");
// Step 2: Add feature
session = await resumeSession(session, "Add OAuth support");
// Step 3: Add tests
session = await resumeSession(session, "Write integration tests");
// Step 4: Deploy
session = await resumeSession(session, "Deploy to production");
```
### Pattern 2: Exploration & Decision
Try multiple approaches, choose best.
```typescript
// Start main conversation
let mainSession = await startSession("Design payment system");
// Explore option A
let optionA = await forkSession(mainSession, "Use Stripe");
// Explore option B
let optionB = await forkSession(mainSession, "Use PayPal");
// Explore option C
let optionC = await forkSession(mainSession, "Use Square");
// Choose winner
let chosenSession = optionA; // Decision made
await resumeSession(chosenSession, "Implement chosen approach");
```
### Pattern 3: Multi-User Collaboration
Multiple developers, independent work.
```typescript
// Developer A starts work
let sessionA = await startSession("Implement user profile page");
// Developer B forks for different feature
let sessionB = await forkSession(sessionA, "Add avatar upload");
// Developer C forks for another feature
let sessionC = await forkSession(sessionA, "Implement search");
// All can work independently
```
### Pattern 4: Error Recovery
Backup and restore points.
```typescript
// Save checkpoint before risky operation
let checkpoint = sessionId;
try {
await resumeSession(checkpoint, "Refactor entire auth system");
} catch (error) {
console.log("Refactor failed, restoring from checkpoint");
await forkSession(checkpoint, "Try safer incremental refactor");
}
```
### Pattern 5: A/B Testing
Test different implementations.
```typescript
let baseline = await startSession("Implement feature X");
// Approach A
let approachA = await forkSession(baseline, "Use algorithm A");
const metricsA = await measurePerformance(approachA);
// Approach B
let approachB = await forkSession(baseline, "Use algorithm B");
const metricsB = await measurePerformance(approachB);
// Compare and choose
const winner = metricsA.better(metricsB) ? approachA : approachB;
```
---
## Session Best Practices
### ✅ Do
- Always capture `session_id` from init message
- Store session IDs for later use
- Use descriptive prompts when resuming
- Fork for alternative approaches
- Test resuming before deploying
- Consider session lifetime limits
### ❌ Don't
- Forget to capture session ID
- Assume sessions last forever
- Resume with completely unrelated prompts
- Fork excessively (creates many branches)
- Rely on sessions for critical state
- Skip testing resume functionality
---
## Helper Functions
```typescript
async function startSession(prompt: string): Promise<string> {
let sessionId: string | undefined;
const response = query({ prompt, options: { model: "sonnet" } });
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
sessionId = message.session_id;
}
}
if (!sessionId) throw new Error('Failed to start session');
return sessionId;
}
async function resumeSession(
sessionId: string,
prompt: string
): Promise<void> {
const response = query({
prompt,
options: { resume: sessionId, model: "sonnet" }
});
for await (const message of response) {
if (message.type === 'assistant') {
console.log(message.content);
}
}
}
async function forkSession(
sessionId: string,
prompt: string
): Promise<string> {
let newSessionId: string | undefined;
const response = query({
prompt,
options: {
resume: sessionId,
forkSession: true,
model: "sonnet"
}
});
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
newSessionId = message.session_id;
} else if (message.type === 'assistant') {
console.log(message.content);
}
}
if (!newSessionId) throw new Error('Failed to fork session');
return newSessionId;
}
```
---
## Session Storage
Store sessions for later use:
```typescript
// In-memory storage
const sessions = new Map<string, { id: string; created: Date }>();
async function saveSession(prompt: string) {
const id = await startSession(prompt);
sessions.set(id, { id, created: new Date() });
return id;
}
function getSession(id: string) {
return sessions.get(id);
}
// Database storage
async function saveSessionToDb(sessionId: string, metadata: any) {
await db.insert('sessions', {
id: sessionId,
created_at: new Date(),
metadata
});
}
```
---
## Session Limits
### Context Window
Sessions have context window limits (200k tokens for Sonnet).
**Strategies**:
- SDK auto-compacts context
- Fork to start fresh from a point
- Summarize and start new session
### Lifetime
Sessions may expire after inactivity.
**Strategies**:
- Don't rely on sessions lasting indefinitely
- Store important state separately
- Test resume functionality
---
## Troubleshooting
### Session Not Found
**Problem**: `"Invalid session ID"`
**Causes**:
- Session expired
- Invalid session ID
- Session from different CLI instance
**Solution**: Start new session
### Context Preserved Incorrectly
**Problem**: Agent doesn't remember previous work
**Causes**:
- Different settings/tools specified
- Context window exceeded
- Fork instead of resume
**Solution**: Verify using `resume` not `forkSession`
### Too Many Forks
**Problem**: Hard to track branches
**Solution**: Limit forking, clean up unused branches
---
## Complete Example
```typescript
class SessionManager {
private sessions = new Map<string, string>();
async start(name: string, prompt: string): Promise<string> {
const id = await startSession(prompt);
this.sessions.set(name, id);
console.log(`✨ Started: ${name}`);
return id;
}
async resume(name: string, prompt: string): Promise<void> {
const id = this.sessions.get(name);
if (!id) throw new Error(`Session ${name} not found`);
console.log(`↪️ Resuming: ${name}`);
await resumeSession(id, prompt);
}
async fork(
fromName: string,
newName: string,
prompt: string
): Promise<string> {
const fromId = this.sessions.get(fromName);
if (!fromId) throw new Error(`Session ${fromName} not found`);
console.log(`🔀 Forking: ${fromName}${newName}`);
const newId = await forkSession(fromId, prompt);
this.sessions.set(newName, newId);
return newId;
}
list(): string[] {
return Array.from(this.sessions.keys());
}
}
// Usage
const manager = new SessionManager();
await manager.start("main", "Build a web app");
await manager.resume("main", "Add authentication");
await manager.fork("main", "option-a", "Use JWT tokens");
await manager.fork("main", "option-b", "Use sessions");
console.log("Sessions:", manager.list());
// ["main", "option-a", "option-b"]
```
---
**For more details**: See SKILL.md
**Template**: templates/session-management.ts

View File

@@ -0,0 +1,464 @@
# Subagents Patterns
Guide to designing and orchestrating specialized subagents.
---
## What Are Subagents?
Specialized agents with:
- **Specific expertise** - Focused domain knowledge
- **Custom tools** - Only tools they need
- **Different models** - Match capability to task cost
- **Dedicated prompts** - Tailored instructions
---
## When to Use Subagents
### ✅ Use Subagents When:
- Task requires different expertise areas
- Some subtasks need different models (cost optimization)
- Tool access should be restricted per role
- Clear separation of concerns needed
- Multiple steps with specialized knowledge
### ❌ Don't Use Subagents When:
- Single straightforward task
- All work can be done by one agent
- Overhead of orchestration > benefit
- Tools/permissions don't vary
---
## AgentDefinition Structure
```typescript
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 Guidelines
**description**:
- Clear, action-oriented
- When to invoke this agent
- 1-2 sentences
- Examples: "Run test suites", "Deploy to production"
**prompt**:
- Agent's role and behavior
- Instructions and constraints
- What to do and what not to do
- Can be detailed (100-500 tokens)
**tools**:
- If omitted, inherits all tools from main agent
- Use to restrict agent to specific tools
- Examples: `["Read", "Grep"]` for read-only
**model**:
- `"haiku"` - Fast, cost-effective ($0.25/$1.25 per MTok)
- `"sonnet"` - Balanced ($3/$15 per MTok)
- `"opus"` - Maximum capability ($15/$75 per MTok)
- `"inherit"` - Use main agent's model
- If omitted, inherits main agent's model
---
## Design Patterns
### Pattern 1: DevOps Pipeline
```typescript
agents: {
"test-runner": {
description: "Run automated test suites and verify coverage",
prompt: `You run tests.
Execute:
- Unit tests
- Integration tests
- End-to-end tests
FAIL if any tests fail. Report clear errors.`,
tools: ["Bash", "Read"],
model: "haiku" // Fast, cost-effective
},
"security-checker": {
description: "Security audits and vulnerability scanning",
prompt: `You check security.
Scan for:
- Exposed secrets
- Dependency vulnerabilities
- Permission issues
- OWASP compliance
Block deployment if critical issues found.`,
tools: ["Read", "Grep", "Bash"],
model: "sonnet" // Balance for analysis
},
"deployer": {
description: "Application deployment and rollbacks",
prompt: `You deploy applications.
Process:
1. Deploy to staging
2. Verify health checks
3. Deploy to production
4. Create rollback plan
ALWAYS have rollback ready.`,
tools: ["Bash", "Read"],
model: "sonnet" // Reliable for critical ops
}
}
```
### Pattern 2: Code Review Workflow
```typescript
agents: {
"syntax-checker": {
description: "Check syntax, formatting, and linting",
prompt: `You check syntax and formatting.
Run:
- ESLint
- Prettier
- TypeScript type checking
Report all violations clearly.`,
tools: ["Bash", "Read"],
model: "haiku" // Fast checks
},
"logic-reviewer": {
description: "Review logic, algorithms, and architecture",
prompt: `You review code logic.
Check:
- Algorithmic correctness
- Edge cases
- Performance issues
- Design patterns
Suggest improvements.`,
tools: ["Read", "Grep"],
model: "sonnet" // Complex analysis
},
"security-reviewer": {
description: "Review for security vulnerabilities",
prompt: `You review security.
Check:
- SQL injection
- XSS vulnerabilities
- Authentication bypass
- Data exposure
FAIL on critical issues.`,
tools: ["Read", "Grep"],
model: "sonnet" // Security expertise
}
}
```
### Pattern 3: Content Generation
```typescript
agents: {
"researcher": {
description: "Research topics and gather information",
prompt: `You research topics.
Gather:
- Relevant information
- Latest trends
- Best practices
- Examples
Provide comprehensive research.`,
tools: ["WebSearch", "WebFetch", "Read"],
model: "haiku" // Fast research
},
"writer": {
description: "Write content based on research",
prompt: `You write content.
Write:
- Clear, engaging prose
- Well-structured
- Audience-appropriate
- Fact-based
Use research provided.`,
tools: ["Write", "Read"],
model: "sonnet" // Quality writing
},
"editor": {
description: "Edit and polish content",
prompt: `You edit content.
Check:
- Grammar and spelling
- Clarity and flow
- Consistency
- Formatting
Polish to perfection.`,
tools: ["Read", "Edit"],
model: "sonnet" // Quality editing
}
}
```
### Pattern 4: Incident Response
```typescript
agents: {
"incident-detector": {
description: "Detect and triage incidents",
prompt: `You detect incidents.
Monitor:
- Error rates
- Response times
- System health
- Alerts
Assess severity and impact.`,
tools: ["Bash", "Read"],
model: "haiku" // Fast detection
},
"root-cause-analyzer": {
description: "Analyze root cause of incidents",
prompt: `You analyze root causes.
Investigate:
- Logs
- Metrics
- Recent changes
- Dependencies
Identify exact cause.`,
tools: ["Bash", "Read", "Grep"],
model: "sonnet" // Deep analysis
},
"fix-implementer": {
description: "Implement fixes for incidents",
prompt: `You implement fixes.
Fix:
- Apply patches
- Rollback changes
- Update config
- Deploy hotfixes
Verify fix resolves issue.`,
tools: ["Read", "Edit", "Bash"],
model: "sonnet" // Careful fixes
}
}
```
---
## Orchestration Strategies
### Sequential Execution
Main agent coordinates agents in order:
```
test-runner → security-checker → deployer
```
**Use when**: Steps must happen in order
### Parallel Execution
Main agent delegates to multiple agents at once:
```
test-runner
security-checker } in parallel
code-reviewer
```
**Use when**: Steps are independent
### Conditional Execution
Main agent decides which agents to use based on context:
```
if (tests fail) → test-fixer
if (security issue) → security-fixer
if (all pass) → deployer
```
**Use when**: Different paths needed
---
## Model Selection Strategy
### Cost vs Capability
| Model | Cost (in/out) | Speed | Use For |
|-------|---------------|-------|---------|
| Haiku | $0.25/$1.25 | Fastest | Monitoring, simple checks |
| Sonnet | $3/$15 | Medium | Code review, analysis |
| Opus | $15/$75 | Slowest | Complex reasoning |
### Optimization Tips
```typescript
// ✅ Good: Match model to task
agents: {
"monitor": { model: "haiku" }, // Simple checks
"reviewer": { model: "sonnet" }, // Analysis
"architect": { model: "opus" } // Complex design
}
// ❌ Bad: Opus for everything
agents: {
"monitor": { model: "opus" }, // Wasteful
"reviewer": { model: "opus" }, // Wasteful
"simple-task": { model: "opus" } // Wasteful
}
```
---
## Tool Restriction Patterns
### Read-Only Agent
```typescript
{
description: "Analyze code without modifications",
tools: ["Read", "Grep", "Glob"],
model: "haiku"
}
```
### Write-Only Agent
```typescript
{
description: "Generate new files",
tools: ["Write"],
model: "sonnet"
}
```
### Modify Agent
```typescript
{
description: "Edit existing files",
tools: ["Read", "Edit"],
model: "sonnet"
}
```
### Full Access Agent
```typescript
{
description: "Complete control",
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"],
model: "sonnet"
}
```
---
## Communication Between Agents
Agents communicate through the main agent:
```
Main Agent: "Run tests"
Test Runner: "Tests passed"
Main Agent: "Deploy to staging"
Deployer: "Deployed to staging"
Main Agent: "Verify health"
Monitor: "Health checks passing"
Main Agent: "Deploy to production"
```
**No direct agent-to-agent communication**.
---
## Best Practices
### ✅ Do
- Give agents clear, specific roles
- Match model to task complexity
- Restrict tools per agent's needs
- Write detailed prompts with constraints
- Use descriptive agent names
- Test agents independently
- Monitor which agents are invoked
### ❌ Don't
- Create overlapping responsibilities
- Use Opus for simple tasks
- Give all agents all tools
- Write vague prompts
- Use generic names like "agent1"
- Skip testing in isolation
- Assume agents will coordinate perfectly
---
## Troubleshooting
### Agent Not Invoked
**Problem**: Main agent doesn't call subagent
**Solution**: Improve `description` to be more specific
### Wrong Agent Invoked
**Problem**: Main agent calls incorrect subagent
**Solution**: Make descriptions more distinct
### Agent Lacks Capability
**Problem**: Agent can't complete task
**Solution**: Add required tools or upgrade model
---
**For more details**: See SKILL.md
**Examples**: templates/subagents-orchestration.ts

503
references/top-errors.md Normal file
View File

@@ -0,0 +1,503 @@
# Top Errors & Solutions
Complete reference for common Claude Agent SDK errors and how to fix them.
---
## Error #1: CLI Not Found
### Error Message
```
"Claude Code CLI not installed"
```
### Why It Happens
The SDK requires Claude Code CLI to be installed globally, but it's not found in PATH.
### Solution
```bash
npm install -g @anthropic-ai/claude-code
```
Verify installation:
```bash
which claude-code
# Should output: /usr/local/bin/claude-code or similar
```
### Prevention
- Install CLI before using SDK
- Add to project setup documentation
- Check CLI availability in CI/CD
---
## Error #2: Authentication Failed
### Error Message
```
"Invalid API key"
"Authentication failed"
```
### Why It Happens
- ANTHROPIC_API_KEY environment variable not set
- API key is invalid or expired
- API key has wrong format
### Solution
```bash
# Set API key
export ANTHROPIC_API_KEY="sk-ant-..."
# Verify it's set
echo $ANTHROPIC_API_KEY
```
Get API key:
1. Visit https://console.anthropic.com/
2. Navigate to API Keys section
3. Create new key
4. Copy and save securely
### Prevention
- Use environment variables (never hardcode)
- Check key before running
- Add to .env.example template
- Document setup process
---
## Error #3: Permission Denied
### Error Message
```
"Tool use blocked"
"Permission denied for tool: Bash"
```
### Why It Happens
- Tool not in `allowedTools` array
- `permissionMode` is too restrictive
- Custom `canUseTool` callback denied execution
### Solution
```typescript
// Add tool to allowedTools
options: {
allowedTools: ["Read", "Write", "Edit", "Bash"] // Add needed tools
}
// Or use less restrictive permission mode
options: {
permissionMode: "acceptEdits" // Auto-approve edits
}
// Or check canUseTool logic
options: {
canUseTool: async (toolName, input) => {
console.log("Tool requested:", toolName); // Debug
return { behavior: "allow" };
}
}
```
### Prevention
- Set appropriate `allowedTools` from start
- Test permission logic thoroughly
- Use `permissionMode: "bypassPermissions"` in CI/CD
---
## Error #4: Context Length Exceeded
### Error Message
```
"Prompt too long"
"Context length exceeded"
"Too many tokens"
```
### Why It Happens
- Input prompt exceeds model's context window (200k tokens)
- Long conversation without pruning
- Large files in context
### Solution
SDK auto-compacts context, but you can:
```typescript
// Fork session to start fresh from a point
const forked = query({
prompt: "Continue with fresh context",
options: {
resume: sessionId,
forkSession: true // Start fresh
}
});
// Or reduce context
options: {
allowedTools: ["Read", "Grep"], // Limit tools
systemPrompt: "Keep responses concise."
}
```
### Prevention
- Use session forking for long tasks
- Keep prompts focused
- Don't load unnecessary files
- Monitor context usage
---
## Error #5: Tool Execution Timeout
### Error Message
```
"Tool did not respond"
"Tool execution timeout"
```
### Why It Happens
- Tool takes too long (>5 minutes default)
- Infinite loop in tool implementation
- Network timeout for external tools
### Solution
For custom tools:
```typescript
tool("long_task", "Description", schema, async (args) => {
// Add timeout
const timeout = 60000; // 1 minute
const promise = performLongTask(args);
const result = await Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
return { content: [{ type: "text", text: result }] };
})
```
For bash commands:
```typescript
// Add timeout to bash command
command: "timeout 60s long-running-command"
```
### Prevention
- Set reasonable timeouts
- Optimize tool implementations
- Use background jobs for long tasks
- Test tools independently
---
## Error #6: Session Not Found
### Error Message
```
"Invalid session ID"
"Session not found"
```
### Why It Happens
- Session ID is incorrect
- Session expired (old)
- Session from different CLI instance
### Solution
```typescript
// Ensure session ID captured correctly
let sessionId: string | undefined;
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
sessionId = message.session_id; // Capture here
console.log("Session:", sessionId); // Verify
}
}
// Use correct session ID
query({
prompt: "...",
options: { resume: sessionId } // Must match exactly
});
```
### Prevention
- Always capture session_id from system init
- Store session IDs reliably
- Don't rely on sessions lasting indefinitely
- Handle session errors gracefully
---
## Error #7: MCP Server Connection Failed
### Error Message
```
"Server connection error"
"MCP server not responding"
"Failed to connect to MCP server"
```
### Why It Happens
- Server command/URL incorrect
- Server crashed or not running
- Network issues (HTTP/SSE servers)
- Missing dependencies
### Solution
For stdio servers:
```typescript
// Verify command works independently
// Test: npx @modelcontextprotocol/server-filesystem
options: {
mcpServers: {
"filesystem": {
command: "npx", // Verify npx is available
args: ["@modelcontextprotocol/server-filesystem"],
env: {
ALLOWED_PATHS: "/path" // Verify path exists
}
}
}
}
```
For HTTP servers:
```typescript
// Test URL separately
const testResponse = await fetch("https://api.example.com/mcp");
console.log(testResponse.status); // Should be 200
```
### Prevention
- Test MCP servers independently before integration
- Verify command/URL works
- Add error handling for server failures
- Use health checks
---
## Error #8: Subagent Definition Error
### Error Message
```
"Invalid AgentDefinition"
"Agent configuration error"
```
### Why It Happens
- Missing required fields (`description` or `prompt`)
- Invalid `model` value
- Invalid `tools` array
### Solution
```typescript
agents: {
"my-agent": {
description: "Clear description of when to use", // Required
prompt: "Detailed system prompt", // Required
tools: ["Read", "Write"], // Optional
model: "sonnet" // Optional
}
}
```
### Prevention
- Always include `description` and `prompt`
- Use TypeScript types
- Test agent definitions
- Follow examples in templates
---
## Error #9: Settings File Not Found
### Error Message
```
"Cannot read settings"
"Settings file not found"
```
### Why It Happens
- `settingSources` includes non-existent file
- File path incorrect
- File permissions deny read
### Solution
```typescript
// Check file exists before loading
import fs from 'fs';
const projectSettingsPath = '.claude/settings.json';
const settingSources = [];
if (fs.existsSync(projectSettingsPath)) {
settingSources.push('project');
}
options: {
settingSources // Only existing files
}
```
### Prevention
- Check file exists before including in sources
- Use empty array for isolated execution
- Handle missing files gracefully
---
## Error #10: Tool Name Collision
### Error Message
```
"Duplicate tool name"
"Tool already defined"
```
### Why It Happens
- Two MCP servers define same tool name
- Tool name conflicts with built-in tool
### Solution
```typescript
// Use unique tool names
const server1 = createSdkMcpServer({
name: "service-a",
tools: [
tool("service_a_process", ...) // Prefix with server name
]
});
const server2 = createSdkMcpServer({
name: "service-b",
tools: [
tool("service_b_process", ...) // Different name
]
});
```
### Prevention
- Use unique tool names
- Prefix tools with server name
- Test integration before deployment
---
## Error #11: Zod Schema Validation Error
### Error Message
```
"Invalid tool input"
"Schema validation failed"
```
### Why It Happens
- Agent provided data that doesn't match Zod schema
- Schema too restrictive
- Missing `.describe()` on fields
### Solution
```typescript
// Add descriptive schemas
{
email: z.string().email().describe("User email address"),
age: z.number().int().min(0).max(120).describe("Age in years"),
role: z.enum(["admin", "user"]).describe("User role")
}
// Make fields optional if appropriate
{
email: z.string().email(),
phoneOptional: z.string().optional() // Not required
}
```
### Prevention
- Use `.describe()` on all fields
- Add validation constraints
- Test with various inputs
- Make optional fields explicit
---
## Error #12: Filesystem Permission Denied
### Error Message
```
"Access denied"
"Cannot access path"
"EACCES: permission denied"
```
### Why It Happens
- Path outside `workingDirectory`
- No read/write permissions
- Protected system directory
### Solution
```typescript
// Set correct working directory
options: {
workingDirectory: "/path/to/accessible/dir"
}
// Or fix permissions
// chmod +r file.txt (add read)
// chmod +w file.txt (add write)
```
### Prevention
- Set appropriate `workingDirectory`
- Verify file permissions
- Don't access system directories
- Use dedicated project directories
---
## General Error Handling Pattern
```typescript
try {
const response = query({ prompt: "...", options: { ... } });
for await (const message of response) {
if (message.type === 'error') {
console.error('Agent error:', message.error);
// Handle non-fatal errors
}
}
} catch (error) {
console.error('Fatal error:', error);
// Handle specific errors
switch (error.code) {
case 'CLI_NOT_FOUND':
console.error('Install: npm install -g @anthropic-ai/claude-code');
break;
case 'AUTHENTICATION_FAILED':
console.error('Check ANTHROPIC_API_KEY');
break;
case 'RATE_LIMIT_EXCEEDED':
console.error('Rate limited. Retry with backoff.');
break;
case 'CONTEXT_LENGTH_EXCEEDED':
console.error('Reduce context or fork session');
break;
default:
console.error('Unexpected error:', error);
}
}
```
---
**For more details**: See SKILL.md
**Template**: templates/error-handling.ts