codemode-node
This server exposes one code tool that executes a JavaScript async arrow function in a sandbox to accomplish multi-step tasks and return only a compact result.
Run shell commands, including PowerShell on Windows or POSIX shell elsewhere.
Read, write, append, inspect, list, glob, grep, remove, and create files/directories.
Make HTTP requests, including JSON-parsed requests, with methods, headers, bodies, and timeouts.
Loop and chain operations over many files or URLs, catch errors, and aggregate/filter intermediate data inside the code.
Return a small JSON-able summary;
console.logoutput is captured but not returned.Use relative paths under the sandbox root, with forward slashes and a default 120s timeout.
Provides tools for interacting with the Cloudflare API (cf_get, cf_post, cf_put, cf_patch, cf_del, cf_request) from inside the sandbox, enabled when CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are set.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@codemode-nodefind every package.json missing a description and summarize"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
node-code-mode
One MCP tool in front of all your MCP servers. Cloudflare's Code Mode pattern, running on Node instead of Workers.
npx -y node-code-mode --statsStatus: published on npm, running on my own machine. Unpolished in places, but every number below was measured on a real config.
The problem
Every MCP server you connect gets its schemas pasted into the context on every request. From my own config, with four servers turned off:
8 MCP servers 113 tools 174,406 bytes ~43,602 tokens
node-code-mode 1 tool 6,524 bytes ~1,631 tokens43k tokens before the model has done anything, and it grows with each server you install (github alone was 120 KB). The --stats command below measures your own config instead of trusting mine.
With the code tool the model writes one program per task:
const files = await codemode.fs_glob({ pattern: '*/package.json' });
const stale = [];
for (const f of files) {
const pkg = JSON.parse(await codemode.fs_read({ path: f }));
if (!pkg.description) stale.push(f);
}
return { total: files.length, stale };One round trip, one small object back. Everything else stays on disk and in the process.
Related MCP server: CodeForge MCP
Install
npx -y node-code-modeClaude Desktop / Claude Code:
{
"mcpServers": {
"codemode": {
"command": "npx",
"args": ["-y", "node-code-mode"]
}
}
}OpenCode:
{
"mcp": {
"codemode": {
"type": "local",
"command": ["npx", "-y", "node-code-mode"],
"enabled": true
}
}
}Cursor and VS Code take the same shape (servers in .vscode/mcp.json, mcpServers in .mcp.json).
Then stop advertising the servers you don't want in context. They remain reachable through codemode.mcp_call({ server, tool, args }). Disabled means hidden, not gone.
The audit
--stats reads your real config, boots each server, and counts what you're actually sending:
server tools bytes tokens
----------------------------------------------------------------------------
· higgsfield 48 182757 45689 ██████████████████████████████
github 44 120572 30143 ████████████████████
director 26 19725 4931 ███
· playwright 24 18502 4626 ███
----------------------------------------------------------------------------
TOTAL POOLED 282 441280 110320
· = not advertised (still reachable through codemode.mcp_call)
ADVERTISED: 113 tools, 174,406 bytes ≈ 43,602 tokens
WITH CODEMODE: 1 tool ≈ 1,631 tokens
You are paying ~43,602 tokens per turn for tools you did not call.--stats --json for anything that isn't a human.
Sandbox API
Every function takes one object. All of it hangs off a single codemode namespace inside the sandbox.
group | functions |
shell / files |
|
network |
|
MCP |
|
Cloudflare |
|
Moli |
|
globals |
|
Whatever the program returns is the only thing sent back. console.log is captured in logs, never returned to the model, so progress has to be part of the return value.
Config
Looks for the first of these:
--config <path>orCODEMODE_CONFIG./.mcp.json~/.config/opencode/opencode.jsoncClaude Desktop config, any platform
It reads mcpServers (Claude), mcp (OpenCode) and servers (VS Code), including JSONC comments and {env:VAR} interpolation.
npx -y node-code-mode --print-configenv | effect |
| base directory for relative fs paths (default: home) |
| wall clock limit per call (default 120000) |
| forward pooled-server stderr |
| enables |
| enables |
Security
Each call gets a fresh Node child process that exits afterwards. The sandbox is node:vm with no require, import, process or filesystem; the proxies above are its entire capability set.
node:vm is not a security boundary. Escapes exist, and the proxies are real: shell runs commands, fs_write writes files, http opens sockets. The model can do anything you can do in a terminal, which is the same deal every coding agent offers. If that's not acceptable, run the whole thing in a container.
No credentials are read from disk. They come from the environment.
Numbers
Same task both ways, three runs, median, Windows 11 / Node 22:
round trips | bytes to the model | tokens | wall time | |
5 tool calls | 5 | 41,812 | 10,453 | 2,240 ms |
1 | 1 | 145 | 36 | 2,733 ms |
About half a second slower locally, 288× less context. Each skipped round trip is a full model call, so on a real task the difference is seconds, not milliseconds.
Limitations
No streaming. One result at the end; long jobs look like a hang.
Plain JavaScript. No TypeScript, no imports.
Cold start around 1.5 s, then 250 to 500 ms per call from process spawn and IPC.
stdio on the server side only. Upstream servers can be stdio or HTTP.
Pinned to
@cloudflare/codemode@0.5.5. Upstream is Workers-first and a minor bump can add acloudflare:workersimport that Node cannot load (#2142). Upgrade deliberately and run the tests.
Prior art
@cloudflare/codemode supplies the type generation, tool description, result unwrapping and truncation. This repo supplies the executor and the server. The protocol idea comes from UTCP's code-mode, which is more ambitious and framework-agnostic.
MIT.
Available Tools
1 toolcodeA
Execute one JavaScript program that achieves the whole goal. This replaces chains of separate tool calls: one call here does the work of N, and intermediate data never enters your context.
Available: type ShellInput = { cmd: string; cwd?: string; timeout_ms?: number; } type ShellOutput = unknown type FsReadInput = { path: string; } type FsReadOutput = unknown type FsWriteInput = { path: string; content: string; } type FsWriteOutput = unknown type FsAppendInput = { path: string; content: string; } type FsAppendOutput = unknown type FsExistsInput = { path: string; } type FsExistsOutput = unknown type FsStatInput = { path: string; } type FsStatOutput = unknown type FsListInput = { dir?: string; } type FsListOutput = unknown type FsGlobInput = { pattern: string; cwd?: string; limit?: number; } type FsGlobOutput = unknown type FsGrepInput = { pattern: string; cwd?: string; include?: string; limit?: number; ignore_case?: boolean; } type FsGrepOutput = unknown type FsRemoveInput = { path: string; } type FsRemoveOutput = unknown type FsMkdirInput = { path: string; } type FsMkdirOutput = unknown type HttpInput = { url: string; method?: string; headers?: { [key: string]: string; }; body?: string; timeout_ms?: number; } type HttpOutput = unknown type HttpJsonInput = { url: string; method?: string; headers?: { [key: string]: string; }; body?: string; timeout_ms?: number; } type HttpJsonOutput = unknown
declare const codemode: { /** * Run a PowerShell (Windows) or POSIX shell command. Returns {code,stdout,stderr,ms}. */ shell: (input: ShellInput) => Promise;
/**
* Read a UTF-8 text file. Relative paths resolve under the sandbox ROOT.
*/
fs_read: (input: FsReadInput) => Promise<FsReadOutput>;
/**
* Write a file (creates parent dirs).
*/
fs_write: (input: FsWriteInput) => Promise<FsWriteOutput>;
/**
* Append to a file.
*/
fs_append: (input: FsAppendInput) => Promise<FsAppendOutput>;
/**
* True if the path exists.
*/
fs_exists: (input: FsExistsInput) => Promise<FsExistsOutput>;
/**
* Size / dir / mtime.
*/
fs_stat: (input: FsStatInput) => Promise<FsStatOutput>;
/**
* List a directory.
*/
fs_list: (input: FsListInput) => Promise<FsListOutput>;
/**
* Glob files under cwd (forward slashes, *, ?). Skips node_modules/.git/dist.
*/
fs_glob: (input: FsGlobInput) => Promise<FsGlobOutput>;
/**
* Regex search under cwd. Returns ["file:line: text"].
*/
fs_grep: (input: FsGrepInput) => Promise<FsGrepOutput>;
/**
* Delete a file or directory tree.
*/
fs_remove: (input: FsRemoveInput) => Promise<FsRemoveOutput>;
/**
* Create a directory tree.
*/
fs_mkdir: (input: FsMkdirInput) => Promise<FsMkdirOutput>;
/**
* HTTP request. Returns {status, ok, headers, text, ms}.
*/
http: (input: HttpInput) => Promise<HttpOutput>;
/**
* HTTP request parsed as JSON. Returns {status, ok, json}.
*/
http_json: (input: HttpJsonInput) => Promise<HttpJsonOutput>;
}
Rules:
Write an async arrow function that returns the final value. Do NOT use TypeScript syntax.
Use forward slashes in paths. A backslash inside a JS string is an escape ("\p" becomes "p") and silently corrupts the path.
Return a small JSON-able summary. Anything you do not return is discarded, which is the point: filter and aggregate inside the code, not in the conversation.
console.log output is captured host-side but is NOT returned — collect progress in an array and return it.
Loop freely: many files, many URLs, many tool calls per execution.
Default timeout 120s (CODEMODE_TIMEOUT_MS). Errors throw; catch them inside the code if you want to continue.
Example: async () => { const r = await codemode.shell({ cmd: "...", cwd: "...", timeout_ms: 0 }); return r; }
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | JavaScript async arrow function to execute |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so: default 120s timeout with the CODEMODE_TIMEOUT_MS override, errors throw rather than return, console.log is captured but not returned, unreturned data is discarded, relative paths resolve under a sandbox ROOT, and glob skips node_modules/.git/dist. The return shapes for each helper (shell → {code,stdout,stderr,ms}, http → {status,ok,headers,text,ms}) are spelled out inline.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The value proposition and execution rules are front-loaded, and the bullet rules each carry a distinct, non-obvious constraint. It is long, and the helper API is presented twice (TypeScript type blocks plus the declare const with JSDoc), which is mild redundancy, though the second listing adds return shapes and path semantics.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, and the description compensates by explaining what is returned versus discarded. Combined with the enumerated helper surface and the timeout/error model, an agent has everything needed to write a correct call on the first attempt.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for the single 'code' parameter, so the baseline is a 3-4, and the description exceeds it by specifying the required shape: an async arrow function returning the final value, no TypeScript syntax, forward slashes only because backslashes are escape sequences. The concrete example anchors the expected form.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb and resource ('Execute one JavaScript program') and frames the scope as 'achieves the whole goal,' which tells an agent exactly what unit of work this tool covers. The follow-up contrast with 'chains of separate tool calls' pins down the abstraction level unambiguously.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clearly indicates when to prefer this over sequential tool calls ('one call here does the work of N, and intermediate data never enters your context') and gives operational guidance such as looping freely and catching errors inside the code. It stops short of stating when not to use it, e.g. a single trivial operation where a direct tool would be simpler.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.1.0- First observed
code
TDQS
Scored across 1 tool
There is only one MCP tool, so an agent cannot misselect between tools. The only ambiguity is inside the exposed runtime API, where `shell` can do everything the `fs_*` methods and even `http` (via curl) can do, so multiple paths exist for the same task.
With a single tool there is no cross-tool pattern to violate, and the embedded API uses predictable snake_case with `fs_`/`http` prefixes for most methods. The outliers are bare names like `shell` and `http`/`http_json`, which break the prefix convention slightly but remain obvious.
A single entry point is the deliberate design of a code-mode server, and it is far from trivial—it encapsulates shell, filesystem, and HTTP capability. The tradeoff is that all functionality is compressed behind one untyped-looking call, so per-capability discovery relies entirely on reading the description.
The surface covers the full file lifecycle (write/read/append/exists/stat/list/glob/grep/mkdir/remove) plus shell and HTTP, with `shell` acting as a universal escape hatch for anything missing. Minor gaps exist for rename/copy, binary file handling, and line-level edits, all of which must be hand-rolled via read+write or shell.
Maintenance
Related MCP Connectors
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
The OpenRouter for tools. One MCP connection gives any AI agent 254 hosted tools, pay per call.
- WauldoOAuthcom.wauldo
Stateless agentic tools over MCP: concept extraction, long-context, knowledge graph, planning.
One MCP endpoint for Claude, GPT & Gemini: 100+ tools + no-code connectors + agent workers.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceUniversal MCP server for executing TypeScript and Python code with progressive disclosure, reducing token usage by 98% by enabling on-demand access to all other MCP tools through code execution rather than loading tool definitions directly.37 npm129MIT
- AlicenseNot gradedqualityDmaintenanceA self-hosted MCP server that provides a single execute_code tool, enabling agents to write TypeScript to call multiple REST APIs via fetch() with transparent credential injection, reducing token usage by keeping intermediate results in the sandbox.11BSD 3-Clause
- AlicenseAqualityAmaintenanceCode-mode MCP server (docs_search + execute_code two-tool surface) backed by a unified capability manifest across three sandbox kernels (in-process node:vm, WASM via QuickJS / Pyodide / Wasmtime, and remote microVM via E2B / Cloudflare Sandbox). At N=30 tools the bootstrap-context cost drops to 13.6% of direct tool-use. Apache-2.0.26Apache 2.0
- AlicenseNot gradedqualityBmaintenanceFronts every MCP server you have with two tools: search shortlists relevant tools, and execute runs sandboxed Python that calls upstream tools as mcp..(). It enables agents to make bulk, repeated, or conditional tool calls using calibrated decision-model choices instead of per-item LLM generation.3MIT