Skip to main content
Glama
Rodriguespn

e2b-code-mode

by Rodriguespn

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 with execute_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.


Related MCP server: opensrc-mcp

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 per POST /mcp (sessionIdGenerator: undefined, enableJsonResponse: true — no sessions, no SSE), so the server holds no protocol state and scales horizontally; GET/DELETE /mcp return 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.

  • search sandbox — all egress denied; the slimmed OpenAPI spec is embedded as spec for the agent's read-only code to query.

  • execute_* sandbox — egress limited to api.e2b.dev; the caller's key is injected as E2B_API_KEY and used by api.request(). The read/write split is enforced in the client: execute_read throws on any non-GET before the fetch (annotated read-only), while execute_write allows every method (destructive).

  • Runner — agent code is written to a file and run with node (never eval); output returns via a result file. The code sits on its own lines in the invoke scaffold, so a trailing // comment can'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, or admin (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-Authenticate otherwise); 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 -c reads (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_KEY lives in the sandbox env (agent code can read it). Safety rests on isolation + egress — the key only reaches api.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 the reqId, redacted key, duration, and calledEndpoints count. Use LOG_FORMAT=pretty locally. 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 with MCP_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/endpoints manifest. No key.

  • test/live.test.ts — end-to-end against real e2b: spec search, execute_read GET plus non-GET rejection, trailing-comment tolerance, error/SyntaxError surfacing inside the boundary, the credential/admin guard (including encoded bypasses), an execute_write create+delete round-trip, egress denial, and the stdout size cap. Runs only when E2B_API_KEY is set.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables LLMs to interact with MCP servers by writing TypeScript/JavaScript code instead of direct tool calls. Provides a code execution sandbox that accesses MCP servers through HTTP proxy endpoints.
    8 npm
    124
    Apache 2.0
  • A
    license
    B
    quality
    C
    maintenance
    A codemode MCP server for fetching and querying dependency source code from npm, PyPI, crates.io, and GitHub. It allows agents to execute server-side JavaScript for context-efficient searching and browsing of large codebases without overwhelming the LLM's context window.
    1
    37 npm
    40
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    A general-purpose MCP server providing web search, persistent memory storage, and secure code execution capabilities. It enables AI agents to search the web, store and retrieve data, and run Python/JavaScript code in sandboxed environments.
    8
    MIT