claude-runner

The easiest way to build AI agents with Claude. MCP-native, sandbox-ready, 5 lines to start.
Built on 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);
Why claude-runner?
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:
| 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() |
Install
npm install claude-runner
Requires Claude Code CLI to be installed and authenticated.
Quick Start
Simple await
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);
console.log(`Cost: $${result.cost}`);
console.log(`Turns: ${result.turns}`);
Streaming
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;
}
}
Session Resume
const r1 = await runner.run('Create a test plan for the auth module');
console.log(r1.text);
const r2 = await runner.resume(r1.sessionId, 'Approved. Generate the tests.').result;
console.log(r2.text);
Multi-turn (mid-stream messages)
const stream = runner.stream('Build a REST API for user management');
setTimeout(() => stream.send('Use Express, not Fastify'), 5000);
for await (const event of stream) {
if (event.type === 'text') process.stdout.write(event.text);
}
MCP Servers
Connect to any MCP server with shorthand strings or full config objects.
const runner = new Runner({
mcp: {
github: 'npx @modelcontextprotocol/server-github',
docs: 'https://api.example.com/mcp',
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.
Custom Tools
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?');
Permissions
Control what Claude can do:
const runner = new Runner({ permissions: 'auto' });
const runner = new Runner({ permissions: 'deny-unknown' });
const runner = new Runner({
permissions: 'prompt',
onPermission: async ({ tool, description }) => {
return confirm(`Allow ${tool}? ${description}`);
},
});
const runner = new Runner({
permissions: {
allow: ['Read', 'Glob', 'Grep', 'mcp__github__*'],
deny: ['Bash(rm *)'],
prompt: ['Bash', 'Write'],
},
onPermission: async (req) => confirm(`Allow ${req.tool}?`),
});
Sandbox (Coming Soon)
Run agents in isolated environments:
const runner = new Runner({ sandbox: 'e2b' });
const runner = new Runner({ sandbox: 'docker' });
const runner = new Runner({
sandbox: (options) => myCustomSpawner(options),
});
Subagents
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',
},
},
});
API Reference
Runner
class 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;
}
RunResult
interface RunResult {
text: string;
sessionId: string;
cost: number;
duration: number;
usage: { input; output };
turns: number;
toolCalls: ToolCallSummary[];
error?: string;
}
RunEvent (7 types)
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 |
RunStream
interface RunStream extends AsyncIterable<RunEvent> {
result: Promise<RunResult>;
text: Promise<string>;
send(message: string): void;
interrupt(): Promise<void>;
abort(): void;
sessionId: string | null;
}
RunnerOptions
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 |
Models
Use shorthand names or full model IDs:
const runner = new Runner({ model: 'opus' });
const runner = new Runner({ model: 'sonnet' });
const runner = new Runner({ model: 'haiku' });
const runner = new Runner({ model: 'opus-4.5' });
const runner = new Runner({ model: 'sonnet-4.5' });
const runner = new Runner({ model: 'opus-4.6' });
const runner = new Runner({ model: 'sonnet-4.6' });
const runner = new Runner({ model: 'claude-opus-4-6' });
const result = await runner.run('Quick task', { model: 'haiku' });
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 |
Runtime Support
claude-runner is runtime-agnostic. No framework lock-in.
- Node.js 18+
- Bun
- Deno
- Electron (for desktop apps)
- Any cloud — AWS, GCP, Azure, self-hosted
How It Works
claude-runner is a thin wrapper (~500 lines) around the official @anthropic-ai/claude-agent-sdk. It:
- Normalizes your options into the SDK's 40+ field
Options object
- Starts a
query() session with MCP servers, permissions, and tools configured
- Transforms the SDK's 20+
SDKMessage types into 7 flat RunEvent types
- Manages session lifecycle (resume, multi-turn, abort)
You get the full power of Claude Code (skills, agents, tools, MCP) through a simple API.
License
MIT