What is Claude Code and can you use it to build production agents?
Claude Code (also known as the Claude Agent SDK) is Anthropic's agentic coding runtime. It can read files, write code, run terminal commands, search the web, and reason through multi-step tasks autonomously. It's the same engine that powers Claude Code's CLI - exposed as a programmable runtime.
Out of the box, Claude Code is a local CLI tool. To turn it into a production AI agent that your users can interact with, you need infrastructure: cloud sandboxing, SSE streaming, authentication, multi-tenancy, cost controls, and a UI.
That's what 21st SDK provides. You define the agent in TypeScript, deploy with one command, and 21st handles sandboxing, streaming, auth, and gives you a drop-in React chat component.
What tools does a Claude Code agent have access to?
When you set runtime: "claude-code" (the default), your agent runs on the full Claude Agent SDK inside a secure E2B sandbox with these built-in tools:
Bash- execute terminal commands, run scripts, install packagesRead/Write/Edit- full filesystem access within the sandboxGlob/Grep- search files by name pattern and contentWebSearch/WebFetch- search the web and fetch any URLTodoWrite- manage task lists for multi-step workflows- Custom tools - your own business-logic tools with Zod schemas
This means your agent can clone repos, analyze codebases, write and execute code, research topics, install dependencies, run tests, and interact with external APIs - all autonomously.
How do you run Claude Code in an E2B cloud sandbox?
With 21st SDK you don't configure E2B manually. You define your agent, deploy with one CLI command, and the platform provisions E2B sandboxes on demand. Each sandbox is an isolated Linux environment with:
- Isolated filesystem - each conversation gets its own sandbox
- Full terminal access - agent can run any command, install packages
- Network access - web search, API calls, git clone, URL fetching
- Persistence - sandbox state persists across messages
- Auto-cleanup - ephemeral by default, no manual teardown
Permissions are auto-bypassed inside the sandbox (safe because it's isolated), so you don't need --dangerously-skip-permissions.
What is the architecture of a 21st SDK agent?
Here's how the pieces fit together:
- You define an agent in a TypeScript file using
@21st-sdk/agent - You deploy it with one CLI command - the SDK bundles and pushes it to an E2B sandbox
- The relay server (hosted by 21st) handles authentication, creates sandboxes, and streams responses
- Your frontend connects via
@21st-sdk/reactor@21st-sdk/nextjs- no WebSocket setup, no infra to manage
The Claude Code runtime gives your agent access to powerful built-in tools: Bash, Read, Write, Edit, Glob, Grep, WebSearch, WebFetch, and more. On top of that, you can define your own custom tools.
How do you define and deploy a Claude Code agent?
Install the SDK and create your agent:
npm install @21st-sdk/agent zodCreate agents/my-agent/index.ts:
import { agent, tool } from "@21st-sdk/agent"
import { z } from "zod"
export default agent({
model: "claude-sonnet-4-6",
runtime: "claude-code",
systemPrompt: "You are a helpful AI assistant that can research topics, write code, and answer questions.",
tools: {
summarize: tool({
description: "Summarize a URL and return key points",
inputSchema: z.object({
url: z.string().describe("URL to summarize"),
}),
execute: async ({ url }) => {
// The agent also has built-in WebFetch -
// custom tools are for your own business logic
return {
content: [{ type: "text", text: `Summarizing: ${url}` }],
}
},
}),
},
})The agent() function is a pure config wrapper - full type inference, zero runtime overhead. Key options:
model- which Claude model to use (claude-sonnet-4-6,claude-opus-4-6, etc.)runtime-"claude-code"(default) gives you the full Claude Agent SDK with file/terminal toolssystemPrompt- define your agent's personality and behaviortools- custom tools defined with Zod schemas for full type safetypermissionMode-"default","acceptEdits", or"bypassPermissions"maxTurns- limit the number of agentic turns (default 50)maxBudgetUsd- set a per-conversation cost cap
Deploy with two commands:
npx @21st-sdk/cli login # authenticate with your API key
npx @21st-sdk/cli deploy # bundle and deploy to E2B sandboxThat's it. No Docker, no Kubernetes, no infra to manage. Your agent is live.
How does streaming work with a Claude Code agent?
The 21st platform streams agent responses via Server-Sent Events (SSE), compatible with the Vercel AI SDK. You don't need to set up WebSockets or poll for results.
Every event streams in real time: text output, tool calls (file edits, bash commands, web searches), tool results, thinking blocks, and the final response. The React component handles all of this automatically. For server-side streaming, the Node.js / Python / Go SDKs return a streaming response you can iterate line-by-line.
How do you add a React / Next.js chat UI for a Claude Code agent?
Install the frontend SDK:
npm install @21st-sdk/nextjs @21st-sdk/react @ai-sdk/react aiCreate a token route to keep your API key server-side:
import { createTokenHandler } from "@21st-sdk/nextjs/server"
export const POST = createTokenHandler({
apiKey: process.env.API_KEY_21ST!,
})Add the chat component:
"use client"
import { AgentChat, createAgentChat } from "@21st-sdk/nextjs"
import { useChat } from "@ai-sdk/react"
const chat = createAgentChat({
agent: "my-agent",
tokenUrl: "/api/an-token",
sandboxId: "sb_abc123",
})
export default function Page() {
const { messages, input, handleInputChange, handleSubmit, status, stop, error } =
useChat({ chat })
return (
<AgentChat
messages={messages}
onSend={() => handleSubmit()}
status={status}
onStop={stop}
error={error ?? undefined}
/>
)
}The <AgentChat> component renders a full chat UI with streaming markdown, syntax highlighting, tool call visualizations (file edits, terminal commands, web searches), and theming support.
How do you control the agent from a backend (Node.js / Python / Go)?
For backend integrations, use the server SDKs to create sandboxes, manage threads, and stream responses programmatically - for CI/CD, webhooks, or backend-only integrations:
import { AgentClient } from "@21st-sdk/node"
const client = new AgentClient({ apiKey: process.env.API_KEY_21ST! })
const sandbox = await client.sandboxes.create({ agent: "my-agent" })
const thread = await client.threads.create({
sandboxId: sandbox.id,
name: "Research Task",
})Server SDKs: Node.js, Python, Go.
How do you add custom tools to a Claude Code agent?
Define tools using the tool() function with Zod schemas. They run inside the same E2B sandbox alongside the agent:
const notifySlack = tool({
description: "Send a Slack notification",
inputSchema: z.object({
channel: z.string(),
message: z.string(),
}),
execute: async ({ channel, message }) => {
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ channel, text: message }),
})
return { content: [{ type: "text", text: "Sent" }] }
},
})You can also connect MCP servers for databases, APIs, GitHub, email, and more. The 21st platform supports the full MCP protocol.
How do you control costs and prevent runaway API spend?
Multiple layers of cost control:
maxBudgetUsd- hard per-conversation cost cap in agent configmaxTurns- limits agentic loops to prevent infinite tool chainsdisallowedTools- restrict which tools the agent can use per request- Dashboard tracking - per-dialog token counts and USD costs in real time
How do you monitor and debug a Claude Code agent in production?
The 21st dashboard gives you full observability:
- Session logs - every conversation with full tool call traces
- Token and cost tracking - per-dialog USD costs
- Live monitoring - watch agent executions in real time
- Error tracking - see failures and debug traces
What's the difference between raw Claude Code and 21st SDK?
| Raw Claude Code CLI | 21st SDK + Claude Code | |
|---|---|---|
| Use case | Local dev, personal automation | Production agents for your users |
| Execution | Local terminal | Cloud sandbox (E2B) |
| Auth | Local API key | JWT tokens, team API keys |
| UI | CLI / terminal | React chat component, theming |
| Streaming | stdout / stream-json | SSE, Vercel AI SDK compatible |
| Monitoring | None | Dashboard, logs, cost tracking |
| Multi-tenant | No | Yes, per-sandbox isolation |
| Custom tools | MCP servers | MCP + typed Zod tools |
| Deployment | Manual | One CLI command |
Get Started
- Install:
npm install @21st-sdk/agent zod - Define: Create your agent in
agents/my-agent/index.ts - Deploy:
npx @21st-sdk/cli deploy - Integrate: Add
<AgentChat>to your React app
Check the full documentation, explore templates for real-world examples, or dive into the 21st SDK source on GitHub.
Full Agent Example
A complete agent definition with custom tools, permission settings, and cost controls — ready to deploy.
import { agent, tool } from "@21st-sdk/agent"
import { z } from "zod"
export default agent({
model: "claude-sonnet-4-6",
runtime: "claude-code",
systemPrompt: `You are a production AI assistant.
You can research topics, write and execute code,
and answer questions with citations.`,
// Permission modes:
// "default" - ask user before risky ops
// "acceptEdits" - auto-approve file edits
// "bypassPermissions" - auto-approve everything (sandbox only)
permissionMode: "bypassPermissions",
maxTurns: 30,
maxBudgetUsd: 2,
tools: {
searchDocs: tool({
description: "Search documentation by query",
inputSchema: z.object({
query: z.string().describe("Search query"),
limit: z.number().optional().describe("Max results"),
}),
execute: async ({ query, limit }) => {
// Your custom business logic here
const results = await fetch(
`https://api.example.com/search?q=${query}&limit=${limit ?? 5}`
).then((r) => r.json())
return {
content: [{ type: "text", text: JSON.stringify(results) }],
}
},
}),
notifySlack: tool({
description: "Send a notification to Slack",
inputSchema: z.object({
channel: z.string(),
message: z.string(),
}),
execute: async ({ channel, message }) => {
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ channel, text: message }),
})
return { content: [{ type: "text", text: "Sent" }] }
},
}),
},
})



