A community-driven registry for Claude, Cursor, Windsurf, Cline & more. Not affiliated with Anthropic.
Are you the author? Sign in to claim
Run Claude Agent (Claude Code) in a sandbox, control it via websocket
A WebSocket server that wraps the Claude Agent SDK, allowing real-time bidirectional communication with Claude through WebSockets. Deploy it as an E2B sandbox and connect via the TypeScript client library.
Typical Workflow:
bun run build:e2b@dzhng/claude-agent in your project and connect to your E2B sandboxpackages/server/bun run start:server and bun run test:local to test your changes before rebuildingCreate a .env file in the root directory:
cp .env.example .env
Add your API keys:
ANTHROPIC_API_KEY=sk-ant-your-api-key-here
E2B_API_KEY=e2b_your-api-key-here
Install dependencies:
bun install
Build and deploy the server as an E2B template:
bun run build:e2b
This creates a sandbox template named claude-agent-server on E2B. The build process:
The build may take a few minutes. Once complete, your template is ready to use.
Install the client library in your project:
npm install @dzhng/claude-agent
# or
bun add @dzhng/claude-agent
Connect to your E2B sandbox:
import { ClaudeAgentClient } from '@dzhng/claude-agent'
const client = new ClaudeAgentClient({
e2bApiKey: process.env.E2B_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
template: 'claude-agent-server', // Your E2B template name
debug: true,
})
// Start the client (creates E2B sandbox and connects)
await client.start()
// Listen for messages from Claude
client.onMessage(message => {
if (message.type === 'sdk_message') {
console.log('Claude:', message.data)
}
})
// Send a message to Claude
client.send({
type: 'user_message',
data: {
type: 'user',
session_id: 'my-session',
message: {
role: 'user',
content: 'Hello, Claude!',
},
},
})
// Clean up when done
await client.stop()
That's it! The client library handles:
If you want to customize the server behavior:
The server code is in packages/server/:
index.ts - Main server and WebSocket handlingmessage-handler.ts - Message processing logicconst.ts - Configuration constantsStart the server locally:
bun run start:server
In another terminal, run the test client against localhost:
bun run test:local
This runs packages/client/example-client.ts connected to localhost:3000 instead of E2B.
Once you're satisfied with your changes, rebuild the E2B template:
bun run build:e2b
Your updated server will be deployed to E2B with the same template name.
bun run build:e2bBuilds and deploys the server as an E2B template. This is the main way to deploy your server to the cloud.
bun run test:clientRuns the example client (packages/client/example-client.ts) connected to an E2B sandbox. Requires both E2B_API_KEY and ANTHROPIC_API_KEY in your .env file.
bun run start:serverStarts the server locally on http://localhost:3000. Use this for local development and testing.
bun run test:localRuns the example client connected to localhost:3000. Use this to test your local server changes before rebuilding the E2B image.
npm install @dzhng/claude-agent
# or
bun add @dzhng/claude-agent
interface ClientOptions {
// Required (unless using environment variables)
anthropicApiKey?: string
// E2B Configuration (optional if using connectionUrl)
e2bApiKey?: string
template?: string // E2B template name, defaults to 'claude-agent-server'
timeoutMs?: number // Sandbox timeout, defaults to 5 minutes
// Connection Configuration
connectionUrl?: string // Use this to connect to local/custom server instead of E2B
// Other Options
debug?: boolean // Enable debug logging
// Query Configuration (passed to server)
agents?: Record<string, AgentDefinition>
allowedTools?: string[]
systemPrompt?:
| string
| { type: 'preset'; preset: 'claude_code'; append?: string }
model?: string
}
async start() - Initialize the client and connect to the serversend(message: WSInputMessage) - Send a message to the agentonMessage(handler: (message: WSOutputMessage) => void) - Register a message handler (returns unsubscribe function)async stop() - Disconnect and clean up resourcesimport { ClaudeAgentClient } from '@dzhng/claude-agent'
const client = new ClaudeAgentClient({
e2bApiKey: process.env.E2B_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
template: 'claude-agent-server',
debug: true,
})
await client.start()
client.onMessage(message => {
if (message.type === 'sdk_message') {
console.log('Claude:', message.data)
}
})
client.send({
type: 'user_message',
data: {
type: 'user',
session_id: 'session-1',
message: { role: 'user', content: 'Hello' },
},
})
await client.stop()
const client = new ClaudeAgentClient({
connectionUrl: 'http://localhost:3000',
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
})
await client.start()
For more details, see packages/client/README.md.
Note: If you're using the @dzhng/claude-agent library, you don't need to interact with these endpoints directly. The client handles configuration and WebSocket connections for you. This section is for advanced users who want to connect to the server directly or build their own client.
The server runs on http://localhost:3000 (or your E2B sandbox URL) with:
http://localhost:3000/configws://localhost:3000/wsSet the configuration for the Claude Agent SDK query:
type QueryConfig = {
agents?: Record<string, AgentDefinition>
allowedTools?: string[]
systemPrompt?:
| string
| {
type: 'preset'
preset: 'claude_code'
append?: string
}
model?: string
anthropicApiKey?: string
}
Example:
curl -X POST http://localhost:3000/config \
-H "Content-Type: application/json" \
-d '{
"systemPrompt": "You are a helpful assistant.",
"allowedTools": ["read_file", "write_file"],
"anthropicApiKey": "sk-ant-...",
"model": "claude-3-5-sonnet-20241022",
"agents": {
"myAgent": {
"name": "My Custom Agent",
"description": "A custom agent"
}
}
}'
Response:
{
"success": true,
"config": {
"systemPrompt": "You are a helpful assistant.",
"allowedTools": ["read_file", "write_file"],
"agents": { ... }
}
}
Get the current configuration:
curl http://localhost:3000/config
Response:
{
"config": {
"systemPrompt": "You are a helpful assistant.",
"allowedTools": ["read_file", "write_file"]
}
}
Connect to the WebSocket endpoint:
const ws = new WebSocket('ws://localhost:3000/ws')
Note: The server only accepts one active connection at a time. If another client is already connected, new connection attempts will be rejected with an error message.
Sending Messages (Client → Server)
type WSInputMessage =
| {
type: 'user_message'
data: SDKUserMessage
}
| {
type: 'interrupt'
}
User Message:
Send a wrapped SDKUserMessage:
{
"type": "user_message",
"data": {
"type": "user",
"session_id": "your-session-id",
"parent_tool_use_id": null,
"message": {
"role": "user",
"content": "Hello, Claude!"
}
}
}
Structure:
type: Must be "user_message"data: An SDKUserMessage object containing:
type: Must be "user"session_id: Your session identifier (string)message: An object with role and content
role: Must be "user"content: The message content (string)parent_tool_use_id: Optional, for tool use responsesuuid: Optional, message UUID (auto-generated if not provided)Interrupt Message:
Send an interrupt to stop the current agent operation:
{
"type": "interrupt"
}
Receiving Messages (Server → Client)
type WSOutputMessage =
| { type: 'connected' }
| { type: 'sdk_message'; data: unknown }
| { type: 'error'; error: string }
Connection confirmation:
{
"type": "connected"
}
SDK messages (responses from Claude):
{
"type": "sdk_message",
"data": {
"type": "assistant",
"session_id": "...",
"message": {
/* Claude's response */
}
}
}
Error messages:
{
"type": "error",
"error": "Error description"
}
The server is a simple 1-to-1 relay between a single WebSocket client and the Claude Agent SDK:
/config to set agents, allowedTools, and systemPromptKey Design Principles:
/config endpoint before connectingThe codebase follows a monorepo structure:
claude-agent-server/
├── packages/
│ ├── server/ # Main server implementation
│ │ ├── index.ts
│ │ ├── message-handler.ts
│ │ └── ...
│ ├── client/ # Client library and examples
│ │ ├── src/
│ │ └── example-client.ts
│ └── e2b-build/ # E2B build scripts
│ └── build.prod.ts
├── package.json # Root package.json (workspaces)
└── README.md
Open http://localhost:3000/ in your browser to access the built-in test client. You can:
Run the example client script:
bun run test:client
This will connect to the server (or E2B sandbox), send a few test messages, and display the responses.
This section provides additional details about E2B deployment. For the basic setup, see the Quick Start section.
By default, bun run build:e2b creates a template named claude-agent-server. To use a different name, you can modify packages/e2b-build/build.prod.ts or specify it when using the client:
import { ClaudeAgentClient } from '@dzhng/claude-agent'
const client = new ClaudeAgentClient({
template: 'my-custom-template', // Use your custom template name
e2bApiKey: process.env.E2B_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
})
await client.start()
When you use the client library with E2B:
claude-agent-server by default)setStartCmd in build.prod.ts)To test with E2B, simply run:
bun run test:client
This runs packages/client/example-client.ts which creates an E2B sandbox, connects to it, runs test commands, and cleans up.
The template is defined in packages/e2b-build/build.prod.ts:
const template = Template()
.fromBunImage('1.3') // Use Bun 1.3 base image
.runCmd('sudo apt install -y git') // Install git
.gitClone('https://github.com/...', ...) // Clone repository
.setWorkdir('/home/user/app') // Set working directory
.runCmd('bun install') // Install dependencies
.setStartCmd('bun packages/server/index.ts', waitForPort(3000)) // Start server
You can customize this template to:
Local Development (localhost):
E2B Deployment:
The server uses port 3000 by default. You can modify this in packages/server/index.ts:
const server = Bun.serve<SessionData>({
port: 3000, // Change this
// ...
})
Environment variables are loaded from the root .env file. See Quick Start for setup instructions.
API Key Priority:
anthropicApiKey via the Configuration API (/config endpoint), it will override the ANTHROPIC_API_KEY environment variable.anthropicApiKey in the constructor options.MIT
A Claude Code skill by Hao (駱君昊) that learns your Facebook voice and auto-posts to FB / IG / Threads / X with a 14-day c
1000+ skills curated from Anthropic, Vercel, Stripe, and other engineering teams
Claude Code skill for YouTube creators — channel audits, video SEO, retention scripts, thumbnails, content strategy, Sho
AI image generation skill for Claude Code -- Creative Director powered by Gemini