Codemode
БесплатноНе проверенCode Mode MCP tools from OpenAPI specs. Two tools (search + execute) replace hundreds of individual MCP tools.
Описание
Code Mode MCP tools from OpenAPI specs. Two tools (search + execute) replace hundreds of individual MCP tools.
README
Two MCP tools that replace hundreds. Give an AI agent your OpenAPI spec and a request handler — it discovers and calls your entire API by writing JavaScript in a sandboxed runtime.
Instead of defining individual MCP tools for every API endpoint (list-pods, create-product, get-logs, ...), CodeMode exposes just two tools:
search— the agent writes JS to filter your OpenAPI spec and discover endpointsexecute— the agent writes JS to call your API via an injected client
This is the same pattern Cloudflare uses to expose 2,500+ API endpoints through just two MCP tools, reducing context window usage by 99.9%.
Try It
Requires mise for tooling (Node.js, pnpm, Task):
git clone https://github.com/cnap-tech/codemode.git
cd codemode
mise install # installs Node 24, pnpm 10, Task
task install # installs dependencies
task example # runs the Petstore demo
Fetches the real Petstore OpenAPI spec from the web, then runs search + execute against a local Hono mock — no API keys needed.
Install
pnpm add @robinbraemer/codemode
# Install a sandbox runtime (at least one):
pnpm add @robinbraemer/llrt # Native LLRT — default candidate
pnpm add isolated-vm # V8 isolates — data-only Node.js fallback
If both are installed, the auto-selector (createExecutor) picks native LLRT first, then isolated-vm on Node for data-only execution. Request-capable execution requires LLRT. QuickJSExecutor is still exported for explicit advanced use, but it is not selected automatically because its host-callback bridge cannot enforce all byte limits before values cross into host JavaScript.
Quick Start
import { CodeMode } from '@robinbraemer/codemode';
import { Hono } from 'hono';
const app = new Hono();
app.get('/v1/clusters', (c) => c.json([{ id: '1', name: 'prod' }]));
app.post('/v1/clusters', async (c) => {
const body = await c.req.json();
return c.json({ id: '2', ...body }, 201);
});
const codemode = new CodeMode({
spec: myOpenAPISpec, // OpenAPI 3.x spec, or async getter
request: app.request.bind(app), // in-process, no network hop
});
// The agent searches the spec to discover endpoints...
const search = await codemode.callTool('search', {
code: `async () => {
const results = [];
for (const [path, methods] of Object.entries(spec.paths)) {
for (const [method, op] of Object.entries(methods)) {
if (op.tags?.some(t => t.toLowerCase() === 'clusters')) {
results.push({ method: method.toUpperCase(), path, summary: op.summary });
}
}
}
return results;
}`
});
// ...then executes API calls
const result = await codemode.callTool('execute', {
code: `async () => {
const res = await api.request({ method: "GET", path: "/v1/clusters" });
return res.body;
}`
});
MCP Server Integration
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CodeMode } from '@robinbraemer/codemode';
import { registerTools } from '@robinbraemer/codemode/mcp';
const codemode = new CodeMode({
spec: () => fetchOpenAPISpec(),
request: app.request.bind(app),
});
const server = new McpServer({ name: 'my-api', version: '1.0.0' });
registerTools(codemode, server);
const transport = new StdioServerTransport();
await server.connect(transport);
How It Works
AI Agent
│ writes JavaScript code
▼
CodeMode MCP Server
│
├─ search(code) → runs JS with preprocessed OpenAPI spec
│ → all $refs resolved inline, only essential fields kept
│ → agent discovers endpoints, schemas, parameters
│
└─ execute(code) → runs JS with injected request client
→ api.request() calls your handler in-process
→ no network hop, auth handled automatically
All code runs in a fresh sandbox runtime. The sandbox has zero I/O by default — no require, no process, no fetch, no filesystem. Request-capable execution uses injected host callbacks (spec for search, {namespace}.request() for execute) and is supported by LLRT.
Each tool call gets a fresh sandbox with no state carried over between calls.
API
new CodeMode(options)
| Option | Type | Default | Description |
|---|---|---|---|
spec |
OpenAPISpec | () => OpenAPISpec | Promise<OpenAPISpec> |
required | OpenAPI 3.x spec or async getter |
request |
(input, init?) => Response |
required | Fetch-compatible handler (app.request.bind(app) for Hono) |
namespace |
string |
"api" |
Client name in sandbox (api.request(...)). Must be a valid JS identifier, not a reserved name. |
baseUrl |
string |
"http://localhost" |
Base URL for relative paths |
sandbox |
SandboxOptions |
see below | Sandbox resource limits |
executor |
Executor |
createExecutor() |
Custom sandbox executor |
maxResponseTokens |
number |
25000 |
Token limit for response truncation (0 to disable) |
maxRequests |
number |
50 |
Max requests per execute() call |
maxResponseBytes |
number |
10485760 |
Max response body size in bytes (10MB) |
allowedHeaders |
string[] |
undefined |
Header whitelist. When unset, a blocklist strips Authorization, Cookie, Host, X-Forwarded-*, Proxy-*. |
maxRefDepth |
number |
50 |
Max $ref resolution depth |
SandboxOptions
| Option | Type | Default | Description |
|---|---|---|---|
memoryMB |
number |
64 |
Sandbox heap memory limit |
timeoutMs |
number |
30000 |
CPU timeout in ms (caps pure compute) |
wallTimeMs |
number |
60000 |
Wall-clock timeout in ms (caps total elapsed time including async I/O) |
Methods
codemode.tools(): ToolDefinition[]
Returns MCP-compatible tool definitions for search and execute.
codemode.callTool(name, { code }): Promise<ToolCallResult>
Route a tool call. Returns { content: [{ type: "text", text }], isError? }.
codemode.search(code): Promise<ToolCallResult>
Run search code directly (shorthand for callTool('search', { code })).
codemode.execute(code): Promise<ToolCallResult>
Run execute code directly (shorthand for callTool('execute', { code })).
codemode.setToolNames(search, execute): this
Override default tool names. Useful when running multiple CodeMode instances.
codemode.dispose(): void
Clean up sandbox resources.
Sandbox API
Inside search
The spec global is the preprocessed OpenAPI spec with all $ref pointers resolved inline:
// Find endpoints by tag
async () => {
const results = [];
for (const [path, methods] of Object.entries(spec.paths)) {
for (const [method, op] of Object.entries(methods)) {
if (op.tags?.some(t => t.toLowerCase() === 'clusters')) {
results.push({ method: method.toUpperCase(), path, summary: op.summary });
}
}
}
return results;
}
// Get endpoint with requestBody schema (refs are already resolved)
async () => {
const op = spec.paths['/v1/products']?.post;
return { summary: op?.summary, requestBody: op?.requestBody };
}
// Spec metadata
async () => ({
title: spec.info.title,
version: spec.info.version,
endpoints: Object.keys(spec.paths).length,
})
Inside execute
The {namespace}.request() function makes API calls through the host handler:
// GET with query params
async () => {
const res = await api.request({
method: "GET",
path: "/v1/clusters",
query: { limit: 10 },
});
return res.body;
}
// POST with body
async () => {
const res = await api.request({
method: "POST",
path: "/v1/products",
body: { name: "Redis", chart: "bitnami/redis" },
});
return { status: res.status, body: res.body };
}
// Chain calls
async () => {
const list = await api.request({ method: "GET", path: "/v1/clusters" });
const details = await Promise.all(
list.body.map(c =>
api.request({ method: "GET", path: `/v1/clusters/${c.id}` })
)
);
return details.map(d => d.body);
}
Request options:
| Field | Type | Description |
|---|---|---|
method |
string |
HTTP method ("GET", "POST", etc.) |
path |
string |
API path ("/v1/clusters") |
query |
Record<string, string | number | boolean> |
Query parameters (optional) |
body |
unknown |
Request body, auto-serialized as JSON (optional) |
headers |
Record<string, string> |
Additional headers (optional) |
Response: { status: number, headers: Record<string, string>, body: unknown }
Spec Preprocessing
CodeMode automatically preprocesses your OpenAPI spec before passing it to the search sandbox:
$refresolution — all$refpointers are resolved inline (circular refs become{ $circular: ref })- Field extraction — only essential fields kept per operation:
summary,description,tags,operationId,parameters,requestBody,responses - Metadata preserved —
info,servers, andcomponents.schemasare kept alongside processed paths
You can also use the preprocessing utilities directly:
import { resolveRefs, processSpec, extractTags } from '@robinbraemer/codemode';
const processed = processSpec(rawSpec);
const tags = extractTags(rawSpec);
Executors
CodeMode ships three executor backends. LlrtNativeExecutor is the default and the only request-capable backend. IsolatedVMExecutor is the Node.js data-only fallback, and QuickJSExecutor is an explicit data-only compatibility backend.
Use createExecutor() for automatic selection, or pass an executor instance explicitly:
import { CodeMode, createExecutor, IsolatedVMExecutor, LlrtNativeExecutor } from '@robinbraemer/codemode';
// Automatic — picks native LLRT first, then data-only isolated-vm on Node
const codemode = new CodeMode({
spec,
request: handler,
executor: await createExecutor({ memoryMB: 128, timeoutMs: 60_000 }),
});
// Or explicit
const codemode = new CodeMode({
spec,
request: handler,
executor: new IsolatedVMExecutor({
memoryMB: 128,
timeoutMs: 60_000, // CPU time limit
wallTimeMs: 120_000, // total elapsed time limit
}),
});
| Executor | Package | Performance | Portability | Production-ready |
|---|---|---|---|---|
LlrtNativeExecutor |
@robinbraemer/llrt |
Lightweight native LLRT | Node.js | ✅ default candidate |
IsolatedVMExecutor |
isolated-vm |
Native V8 speed | Node.js | ⚠️ data-only fallback |
QuickJSExecutor |
quickjs-emscripten |
Slower (interpreted WASM) | Node, Bun, CF Workers, browser | ⚠️ explicit only — see caveats |
QuickJSExecutor caveats
- Not a production backend and not auto-selected. Use this only by constructing
new QuickJSExecutor(...)explicitly. Production request-capable callers should useLlrtNativeExecutor. - Host callbacks are disabled. QuickJS cannot enforce host-call byte limits before guest values are dumped into host JavaScript, and sequential host awaits still hit upstream release-asyncify crashes.
QuickJSExecutornow fails closed when function globals are provided. - Return-value semantics differ from
isolated-vm. Final values cross via aJSON.stringifyenvelope.Date,Map,Set,BigIntare converted to strings/objects, not preserved as instances.isolated-vmuses structured clone and preserves them. Stick to plain JSON-safe shapes in sandboxed code that targets both backends. - CPU timeout is wall-clock-based.
isolated-vmuses true CPU time; QuickJS uses elapsed time.
Custom Executor
Implement the Executor interface to use your own sandbox:
import { CodeMode, type Executor, type ExecuteResult } from '@robinbraemer/codemode';
class MyExecutor implements Executor {
async execute(code: string, globals: Record<string, unknown>): Promise<ExecuteResult> {
// `code` is an async arrow function as a string: "async () => { ... }"
// `globals` contains named values to inject:
// - plain data (objects, arrays, primitives) → read-only values
// - functions → callable host functions
// - objects with function values → namespace with callable methods
return { result: ..., logs: [] };
}
dispose() { /* clean up */ }
}
const codemode = new CodeMode({
spec,
request: handler,
executor: new MyExecutor(),
});
Token Efficiency
| Approach | Context Tokens |
|---|---|
| Individual MCP tools (15-50+ tools) | ~15,000-50,000+ |
| Full OpenAPI spec in context | ~1,000,000+ |
| CodeMode (2 tools) | ~1,000 |
License
MIT
Установить Codemode в Claude Desktop, Claude Code, Cursor
unyly install codemodeСтавит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.
Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh
Или настроить вручную
Выполни в терминале:
claude mcp add codemode -- npx -y @robinbraemer/codemodeFAQ
Codemode MCP бесплатный?
Да, Codemode MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Codemode?
Нет, Codemode работает без API-ключей и переменных окружения.
Codemode — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Codemode в Claude Desktop, Claude Code или Cursor?
Открой Codemode на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Codemode with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
