
Security News
White House Authorizes Private Companies to Conduct Offensive Cyber Operations
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.
claude-runner
Advanced tools
The easiest way to build AI agents with Claude. MCP-native, sandbox-first, 5 lines to start.
The easiest way to build AI agents with Claude. MCP-native, sandbox-first, 5 lines to start.
The official SDK is an engine 🔧 — claude-runner is the car 🚗.
A thin, clean wrapper around the official Claude Agent SDK. One dependency. Zero bloat.
import { Runner } from 'claude-runner';
const runner = new Runner();
const result = await runner.run('Analyze this codebase and suggest improvements');
console.log(result.text);
The official @anthropic-ai/claude-agent-sdk is powerful but low-level — 40+ options, 20+ message types, raw async generators. Every developer builds their own wrapper.
claude-runner is that wrapper:
| raw Agent SDK | claude-runner | |
|---|---|---|
| Lines to start | 20+ | 5 |
| Message types | 20+ nested | 7 flat events |
| MCP config | Object only | Shorthand strings |
| Sandbox | Manual spawnClaudeCodeProcess | sandbox: 'e2b' |
| Session resume | resume: id option | runner.resume(id) |
| Permissions | canUseTool callback | permissions: 'auto' |
| Custom tools | tool() + createSdkMcpServer() | defineTool() |
# Agent Mode — full Claude Code power (needs CLI)
npm install claude-runner
# API Mode — no CLI needed, deploys anywhere (needs API key)
npm install claude-runner @anthropic-ai/sdk
Agent Mode requires Claude Code CLI installed and authenticated. API Mode requires an Anthropic API key — no CLI needed.
Run agents directly from your terminal:
# Simple prompt
npx claude-runner "Analyze this codebase and suggest improvements"
# Choose model
npx claude-runner -m opus "Refactor the auth module"
# With MCP servers
npx claude-runner --mcp github="npx @modelcontextprotocol/server-github" "List open issues"
# Auto-approve all tools (for CI/scripts)
npx claude-runner -p auto "Fix all failing tests"
# Resume a previous session
npx claude-runner --resume abc-123-uuid "Now deploy it"
# JSON output (no streaming)
npx claude-runner --json "What files are in this project?"
import { Runner } from 'claude-runner';
const runner = new Runner();
const result = await runner.run('Fix the failing tests in this project');
console.log(result.text); // Claude's response
console.log(`Cost: $${result.cost}`); // API cost
console.log(`Turns: ${result.turns}`); // Agentic turns
for await (const event of runner.stream('Refactor the auth module')) {
switch (event.type) {
case 'text':
process.stdout.write(event.text);
break;
case 'tool_start':
console.log(`\n[${event.tool}]`);
break;
case 'tool_end':
console.log(`[${event.tool}] done (${event.duration}ms)`);
break;
case 'done':
console.log(`\nCost: $${event.result.cost.toFixed(4)}`);
break;
}
}
// First run — Claude analyzes and asks for approval
const r1 = await runner.run('Create a test plan for the auth module');
console.log(r1.text); // "Here's the plan... approve?"
// Resume — continue the conversation with full context
const r2 = await runner.resume(r1.sessionId, 'Approved. Generate the tests.').result;
console.log(r2.text); // "Tests generated at..."
const stream = runner.stream('Build a REST API for user management');
// Inject guidance while Claude is working
setTimeout(() => stream.send('Use Express, not Fastify'), 5000);
for await (const event of stream) {
if (event.type === 'text') process.stdout.write(event.text);
}
Connect to any MCP server with shorthand strings or full config objects.
const runner = new Runner({
mcp: {
// Command string (auto-parsed)
github: 'npx @modelcontextprotocol/server-github',
// URL (HTTP/SSE server)
docs: 'https://api.example.com/mcp',
// Full config
postgres: {
command: 'npx',
args: ['@modelcontextprotocol/server-postgres', process.env.DATABASE_URL!],
env: { PGPASSWORD: process.env.PGPASSWORD! },
},
},
});
const result = await runner.run('How many users signed up last week?');
All MCP tools are auto-discovered and auto-allowed. Claude sees them and can use them immediately.
Define tools that run in your process:
import { Runner, defineTool } from 'claude-runner';
import { z } from 'zod';
const weather = defineTool(
'get_weather',
'Get current weather for a city',
{ city: z.string() },
async ({ city }) => ({
content: [{ type: 'text', text: `72°F and sunny in ${city}` }],
})
);
const runner = new Runner({ tools: [weather] });
const result = await runner.run('What is the weather in San Francisco?');
Control what Claude can do:
// Auto-approve everything (for CI, sandboxed environments)
const runner = new Runner({ permissions: 'auto' });
// Deny unknown tools (safe default)
const runner = new Runner({ permissions: 'deny-unknown' });
// Interactive approval
const runner = new Runner({
permissions: 'prompt',
onPermission: async ({ tool, description }) => {
return confirm(`Allow ${tool}? ${description}`);
},
});
// Fine-grained policy
const runner = new Runner({
permissions: {
allow: ['Read', 'Glob', 'Grep', 'mcp__github__*'],
deny: ['Bash(rm *)'],
prompt: ['Bash', 'Write'],
},
onPermission: async (req) => confirm(`Allow ${req.tool}?`),
});
Deploy Claude agents anywhere — Lambda, Cloud Run, Vercel, any serverless platform. Just an API key.
const runner = new Runner({
apiKey: process.env.ANTHROPIC_API_KEY,
model: 'sonnet',
});
const result = await runner.run('Summarize this data');
Or from the CLI:
ANTHROPIC_API_KEY=sk-xxx npx claude-runner "Summarize this data"
npx claude-runner --api-key sk-xxx "Summarize this data"
| Agent Mode (default) | API Mode | |
|---|---|---|
| Requires | Claude CLI installed | API key only |
| Built-in tools | Read, Write, Bash, Edit | Bring your own |
| MCP servers | Yes | Coming soon |
| Sandbox | Docker, E2B | No |
| Hooks & skills | Yes | No |
| Deploys to | Local, CI with CLI | Anywhere |
| Cost model | Claude subscription | Pay-per-token |
When to use which:
Run agents in isolated Docker containers. Your project is bind-mounted at /workspace — all tool calls operate inside the container.
// Docker container — filesystem isolation, no extra deps
const runner = new Runner({
sandbox: 'docker',
docker: {
image: 'node:22-slim',
mount: ['/shared/fixtures'], // additional read-only mounts
network: 'host', // optional network mode
},
permissions: 'auto',
});
const result = await runner.run('Install deps and run the test suite');
// E2B cloud sandbox
const runner = new Runner({
sandbox: 'e2b',
e2b: { apiKey: process.env.E2B_API_KEY },
});
// Custom spawner — bring your own container runtime
const runner = new Runner({
sandbox: (options) => myCustomSpawner(options),
});
Check Docker availability:
import { getDockerStatus } from 'claude-runner';
const version = getDockerStatus(); // "29.1.3" or null
macOS note: Rancher Desktop only shares
$HOMEby default. Docker Desktop shares/Users,/Volumes,/private,/tmp. Ensure yourcwdis in a shared directory.
Define programmatic subagents:
const runner = new Runner({
agents: {
researcher: {
description: 'Research agent for gathering information',
prompt: 'You are a research assistant. Search thoroughly.',
tools: ['Read', 'Glob', 'Grep', 'WebSearch'],
model: 'haiku',
},
coder: {
description: 'Coding agent for implementation',
prompt: 'You are an expert programmer. Write clean code.',
tools: ['Read', 'Write', 'Edit', 'Bash'],
model: 'sonnet',
},
},
});
Runnerclass Runner {
constructor(options?: RunnerOptions);
run(prompt: string, overrides?: RunOverrides): Promise<RunResult>;
stream(prompt: string, overrides?: RunOverrides): RunStream;
resume(sessionId: string, prompt?: string): RunStream;
get lastSessionId(): string | null;
abort(): void;
}
RunResultinterface RunResult {
text: string; // Final response text
sessionId: string; // For resume
cost: number; // USD
duration: number; // ms
usage: { input; output }; // Token counts
turns: number; // Agentic turns
toolCalls: ToolCallSummary[];
error?: string;
}
RunEvent (7 types)| Type | Fields | When |
|---|---|---|
text | text | Each streamed text chunk |
tool_start | tool, id | Tool execution begins |
tool_end | tool, id, duration | Tool execution ends |
session_init | sessionId, model, tools | Session initialized |
mcp_status | server, status | MCP server connected/failed |
error | message, code? | Error occurred |
done | result | Run complete |
RunStreaminterface RunStream extends AsyncIterable<RunEvent> {
result: Promise<RunResult>; // Await final result
text: Promise<string>; // Await full text
send(message: string): void; // Inject mid-stream message
interrupt(): Promise<void>; // Pause execution
abort(): void; // Stop completely
sessionId: string | null; // Current session ID
}
RunnerOptions| Option | Type | Default | Description |
|---|---|---|---|
model | string | 'claude-sonnet-4-6' | Claude model (shorthands supported — see below) |
cwd | string | process.cwd() | Working directory |
systemPrompt | string | { preset: 'claude_code' } | minimal | System prompt |
mcp | Record<string, McpConfig | string> | {} | MCP servers |
tools | ToolDefinition[] | [] | Custom tools |
agents | Record<string, AgentDefinition> | — | Subagents |
sandbox | 'local' | 'e2b' | 'docker' | SpawnFn | 'local' | Execution environment |
permissions | 'auto' | 'prompt' | 'deny-unknown' | PermissionPolicy | 'deny-unknown' | Permission handling |
onPermission | (req) => Promise<boolean> | — | Permission callback |
maxTurns | number | — | Max agentic turns |
maxBudget | number | — | Max cost in USD |
effort | 'low' | 'medium' | 'high' | 'max' | — | Effort level |
sdkOptions | object | — | Pass-through to Agent SDK |
Use shorthand names or full model IDs:
// Shorthands
const runner = new Runner({ model: 'opus' }); // claude-opus-4-6
const runner = new Runner({ model: 'sonnet' }); // claude-sonnet-4-6
const runner = new Runner({ model: 'haiku' }); // claude-haiku-4-5
// Version-specific shorthands
const runner = new Runner({ model: 'opus-4.5' }); // claude-opus-4-5-20250918
const runner = new Runner({ model: 'sonnet-4.5' }); // claude-sonnet-4-5-20250514
const runner = new Runner({ model: 'opus-4.6' }); // claude-opus-4-6
const runner = new Runner({ model: 'sonnet-4.6' }); // claude-sonnet-4-6
// Full model IDs also work
const runner = new Runner({ model: 'claude-opus-4-6' });
// Per-run override
const result = await runner.run('Quick task', { model: 'haiku' });
| Shorthand | Full Model ID |
|---|---|
opus | claude-opus-4-6 |
opus-4.6 | claude-opus-4-6 |
opus-4.5 | claude-opus-4-5-20250918 |
sonnet | claude-sonnet-4-6 |
sonnet-4.6 | claude-sonnet-4-6 |
sonnet-4.5 | claude-sonnet-4-5-20250514 |
haiku | claude-haiku-4-5-20251001 |
haiku-4.5 | claude-haiku-4-5-20251001 |
claude-runner is runtime-agnostic. No framework lock-in.
claude-runner is a thin wrapper (~1,500 lines) around the official @anthropic-ai/claude-agent-sdk. It:
Options objectquery() session with MCP servers, permissions, and tools configuredSDKMessage types into 7 flat RunEvent typesYou get the full power of Claude Code (skills, agents, tools, MCP) through a simple API.
Ready-to-run examples in the examples/ directory:
| Example | Description | Run |
|---|---|---|
| Code Reviewer | Reviews your codebase for bugs and security issues | node examples/code-reviewer.js |
| Test Generator | Analyzes code and generates tests (multi-turn) | node examples/test-generator.js |
| GitHub Bot | Analyzes GitHub issues via MCP | GITHUB_TOKEN=xxx node examples/mcp-github-bot.js |
MIT
FAQs
The easiest way to build AI agents with Claude. MCP-native, sandbox-first, 5 lines to start.
We found that claude-runner demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.