e2b-code-mode
A Code Mode MCP server for the e2b API, giving agents three tools (search, execute_read, execute_write) to write and run JavaScript functions inside sandboxed environments for API discovery and calls.
README
e2b MCP Code Mode
A Model Context Protocol server that gives an agent three tools instead of dozens for driving the e2b REST API. The agent writes a short JavaScript function that runs inside an e2b sandbox and calls the API through a thin api.request() client — looping, filtering, and chaining calls in one round-trip.
This applies Cloudflare's Code Mode pattern to e2b:
- LLMs write API-calling code more reliably than long chains of individual tool calls.
- Intermediate data stays in the sandbox, out of the model's context.
- The agent discovers endpoints with
search(the OpenAPI spec), then calls them withexecute_read/execute_write.
Quickstart
Prerequisites: Node.js ≥ 20.12, pnpm (corepack enable provides it), and an e2b API key (dashboard).
pnpm install
pnpm build
pnpm start # or: pnpm dev (no build step)
The server starts a stateless streamable-HTTP MCP endpoint on http://127.0.0.1:3000/mcp. It has no API key of its own — each request carries the caller's e2b key as a bearer token.
Connect an MCP client (the key travels as Authorization: Bearer <e2b-key>; this repo ships a .mcp.json):
{
"mcpServers": {
"e2b-code-mode": {
"type": "http",
"url": "http://127.0.0.1:3000/mcp",
"headers": { "Authorization": "Bearer ${E2B_API_KEY}" }
}
}
}
Copy .env.example to .env and set E2B_API_KEY so ${E2B_API_KEY} expands and the live tests can run.
Try it — call execute_read with an async arrow function:
async () => {
const res = await api.request({ method: 'GET', path: '/sandboxes' })
return { status: res.status, running: res.data.length }
}
You get back e.g. { "status": 200, "running": 2 } — produced by code the agent wrote, run in a sandbox that can reach only the e2b API. See TEST-PROMPT.md for a richer, dashboard-visible demo task.
The three tools
Each takes a single code string — an async arrow function that returns a value. console.log output comes back separately as stdout.
| Tool | What it does | In scope | Annotation |
|---|---|---|---|
search |
Query the e2b OpenAPI spec (all $refs resolved) to find endpoints and their shapes. Network-isolated. |
spec |
read-only |
execute_read |
Call the e2b API via api.request() — GET only. |
api |
read-only |
execute_write |
Call the e2b API via api.request() — any method (POST/PUT/PATCH/DELETE). |
api |
destructive |
The workflow is: search to find an endpoint's path and request shape, then execute_read/execute_write to call it. The client is api.request({ method, path, query?, body?, contentType?, rawBody? }), which returns { status, ok, data }.
How it works
MCP client ──HTTP (stateless JSON)──▶ express /mcp ──▶ McpServer (3 tools)
Bearer: e2b key │
┌────────────────┼────────────────┐
search execute_read execute_write
│ └───────┬───────┘
▼ ▼
fresh sandbox fresh sandbox
(egress DENIED) (egress → api.e2b.dev only;
embeds `spec` E2B_API_KEY injected;
`api.request()` client)
- Transport — stateless JSON: a fresh
McpServer+ transport perPOST /mcp(sessionIdGenerator: undefined,enableJsonResponse: true— no sessions, no SSE), so the server holds no protocol state and scales horizontally;GET/DELETE /mcpreturn 405. - Per call — a fresh e2b sandbox, killed in
finally. No SDK is installed inside it, so there's nothing to amortize with a warm pool. searchsandbox — all egress denied; the slimmed OpenAPI spec is embedded asspecfor the agent's read-only code to query.execute_*sandbox — egress limited toapi.e2b.dev; the caller's key is injected asE2B_API_KEYand used byapi.request(). The read/write split is enforced in the client:execute_readthrows on any non-GET before the fetch (annotated read-only), whileexecute_writeallows every method (destructive).- Runner — agent code is written to a file and run with
node(nevereval); output returns via a result file. The code sits on its own lines in the invoke scaffold, so a trailing// commentcan't swallow the closing tokens.
The source is small and layered by responsibility:
| File | Responsibility |
|---|---|
src/index.ts |
Entry point: loads .env, starts the HTTP listener, configures loopback DNS-rebinding protection. |
src/http.ts |
Express app: streamable-HTTP transport, bearer auth, Host/Origin validation, /healthz, /debug/endpoints. |
src/server.ts |
The MCP surface: the three tools, response formatting, the untrusted-data boundary. |
src/sandbox.ts |
Sandbox plumbing: runner construction, the two sandbox shapes, the injected api.request() client, output caps, the credential/admin guard. |
src/spec.ts |
Fetches, $ref-resolves, slims, and caches the e2b OpenAPI spec for search. |
src/log.ts |
pino logger + credential redaction. |
Security notes
Agent code is untrusted but runs inside an e2b Firecracker microVM and authenticates with the caller's own e2b key — so by design it can use that key against the e2b API. Beyond the sandbox isolation, per-tool egress, and read/write split above:
- Untrusted-data boundary — all execute output (
result/stdout/stderr) may embed API content (logs, metadata), so it's wrapped in a<untrusted-data-{uuid}>…</>envelope (error path too), with the model told not to follow instructions inside. Truncation happens before wrapping, so a size cap can't sever the closing tag. - Credential/admin block — paths containing
api-keys,access-tokens,teams, oradmin(incl./admin/teams/{id}/api-keys) are refused. The path is first normalized to a fixpoint (percent-decode, re-resolve dot-segments, collapse slashes, lowercase), so encoded spellings (/%61pi-keys,//access-tokens,/x%2F..%2Fteams,/TEAMS) still match; it only over-blocks. - HTTP hardening — bearer token required on
/mcp(401+WWW-Authenticateotherwise); loopback binds get DNS-rebinding protection (Host/Origin allowlist, incl.[::1]); 1 MB body cap. - Output caps — bounded inside the sandbox before reaching the server: runner result cap (400k chars),
head -creads (500k file / 20k per stream), final server truncate (100k result / 10k logs). - Token location (vs Cloudflare) — Cloudflare keeps the token out of the isolate via an outbound-fetch proxy; e2b has no such hook, so
E2B_API_KEYlives in the sandbox env (agent code can read it). Safety rests on isolation + egress — the key only reachesapi.e2b.dev— not token secrecy.
Configuration
All optional except the caller's bearer token.
| Env var | Default | Meaning |
|---|---|---|
PORT |
3000 |
HTTP listen port |
HOST |
127.0.0.1 |
Bind address (loopback enables DNS-rebinding protection) |
E2B_TEMPLATE |
base |
e2b template the sandboxes boot from (needs Node with global fetch; secure-mode compatible) |
E2B_EXEC_TIMEOUT_MS |
120000 |
Per-call budget for the agent's code |
E2B_OPENAPI_URL |
e2b infra spec | Override the OpenAPI spec URL the search tool loads |
E2B_API_KEY |
— | Only used by local tooling/tests; HTTP callers send it as the bearer token |
LOG_LEVEL |
info |
trace/debug/info/warn/error/fatal/silent |
LOG_FORMAT |
json |
json (structured) or pretty (dev) |
MCP_DEBUG_ENDPOINT |
true |
Set false to disable GET /debug/endpoints |
Observability
-
Structured logs via pino to stderr: one line per HTTP request (method, path, status,
responseTime,req.id; 4xx→warn, 5xx→error) plus per-call lifecycle lines carrying thereqId, redacted key, duration, andcalledEndpointscount. UseLOG_FORMAT=prettylocally. Credentials are never logged. -
GET /debug/endpoints— a no-auth JSON listing of every route (method, path, auth, description), the tool names, and a small status block (version, pid, uptime, log level). No secrets; disable withMCP_DEBUG_ENDPOINT=false.curl -s localhost:3000/debug/endpoints | jq
Testing
pnpm test
test/server.test.ts— the MCP surface via an in-memory client/server pair (tool list, annotations, error shapes). No key.test/http.test.ts— the express app over a real ephemeral port: stateless JSON shape, the bearer-auth gate, non-Bearer rejection, token→handler plumbing, 405s, DNS-rebinding enforcement, and the/debug/endpointsmanifest. No key.test/live.test.ts— end-to-end against real e2b: spec search,execute_readGET plus non-GET rejection, trailing-comment tolerance, error/SyntaxErrorsurfacing inside the boundary, the credential/admin guard (including encoded bypasses), anexecute_writecreate+delete round-trip, egress denial, and the stdout size cap. Runs only whenE2B_API_KEYis set.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。