K

Explore

Agent Templates

Build

/Build an Agent on Claude Code

How to Build an AI Agent on Top of Claude Code With 21st SDK

Step-by-step guide to building, deploying, and embedding a production AI agent powered by Claude Code — using 21st SDK for sandboxing, streaming, and React UI.

serafimcloud
serafimcloud
@serafimcloud

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 packages
  • Read / Write / Edit - full filesystem access within the sandbox
  • Glob / Grep - search files by name pattern and content
  • WebSearch / WebFetch - search the web and fetch any URL
  • TodoWrite - 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:

  1. You define an agent in a TypeScript file using @21st-sdk/agent
  2. You deploy it with one CLI command - the SDK bundles and pushes it to an E2B sandbox
  3. The relay server (hosted by 21st) handles authentication, creates sandboxes, and streams responses
  4. Your frontend connects via @21st-sdk/react or @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 zod

Create 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 tools
  • systemPrompt - define your agent's personality and behavior
  • tools - custom tools defined with Zod schemas for full type safety
  • permissionMode - "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 sandbox

That'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 ai

Create a token route to keep your API key server-side:

app/api/an-token/route.ts
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 config
  • maxTurns - limits agentic loops to prevent infinite tool chains
  • disallowedTools - 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 CLI21st SDK + Claude Code
Use caseLocal dev, personal automationProduction agents for your users
ExecutionLocal terminalCloud sandbox (E2B)
AuthLocal API keyJWT tokens, team API keys
UICLI / terminalReact chat component, theming
Streamingstdout / stream-jsonSSE, Vercel AI SDK compatible
MonitoringNoneDashboard, logs, cost tracking
Multi-tenantNoYes, per-sandbox isolation
Custom toolsMCP serversMCP + typed Zod tools
DeploymentManualOne CLI command

Get Started

  1. Install: npm install @21st-sdk/agent zod
  2. Define: Create your agent in agents/my-agent/index.ts
  3. Deploy: npx @21st-sdk/cli deploy
  4. 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.

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 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" }] }
      },
    }),
  },
})
How to Build an AI Agent on Top of Claude Code With 21st SDK | 21st | 21st