关于 mcp-v8: V8 JavaScript MCP Server
mcp-v8 是用 Rust 编写的 MCP 服务,让 AI Agent 在隔离的 V8 环境中运行 JavaScript 和 TypeScript。它将代码执行集中到一个工具,并支持持久化堆快照及由策略控制的主机能力访问。
主要功能
- 01在 V8 中运行 JavaScript 和 TypeScript
- 02持久化并恢复堆快照
- 03通过 Rego 策略限制主机访问
- 04从 JavaScript 调用上游 MCP 服务
- 05支持 stdio、HTTP 和 SSE 传输
- 06将长时间运行的调用作为 MCP 任务
适用场景
- 01通过一次工具调用用 JavaScript 转换数据
- 02在 Agent 多轮调用间保留变量和对象
- 03按策略开放特定网络或文件系统操作
- 04组合已连接 MCP 服务的工具
MCP 相关
该服务提供 `run_js`,并在有状态模式下提供执行、输出、会话和堆快照管理工具。JavaScript 也可通过 `mcp.callTool()` 和 `mcp.listTools()` 调用已连接的上游 MCP 服务。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
mcp-v8 — 面向 AI 智能体的 JavaScript/TypeScript 运行时
mcp-v8 是一个用 Rust 编写的 Model Context Protocol 服务,
可让 AI Agent 在沙箱化的 V8 隔离环境中运行 JavaScript 和 TypeScript。你无需连接数十个功能狭窄的工具,只需提供一个工具 — run_js,由 Agent 编写代码:循环、分支、转换数据并调用其他工具,通常比同等的工具调用链所需的令牌少得多。
在默认的有状态模式下,V8 堆会保存为内容寻址快照,因此 Agent 可以在多轮对话中累积状态。主机能力(网络、文件系统、子进程、WebAssembly、模块导入和调用其他 MCP 服务)默认全部禁用,只有通过明确的 OPA/Rego 策略 才能启用。
为什么选择 mcp-v8
- 一个工具,能力无限。 Agent 运行的是程序,而不是固定的工具菜单。
- 持久状态。 堆快照会在多次调用之间保留变量和对象。
- 默认安全。 在你通过策略授权之前,
fetch、文件系统、子进程和外部导入均不可用。 - 由内核强制实施隔离。 可选择启用
--sandbox-manifest,通过 nono 能力清单限制整个进程(Linux 上使用 Landlock,macOS 上使用 Seatbelt),在策略层之下提供纵深防御。 - 适用于生产环境。 支持 stdio / Streamable HTTP / SSE 传输、REST 辅助服务、带分页的异步执行、JWKS 身份验证,以及通过 Raft 复制的集群。
文档
维护者请参阅发布到 MCP Registry,了解基于 Docker 的 Registry 清单和自动化发布流程。
完整文档位于 https://r33drichards.github.io/mcp-js/(由 site-docs/ 构建),包含教程、操作指南、概念说明,以及 CLI 标志、HTTP API 和 MCP 工具的完整参考。
社区
加入 mcp-js Discord 社区,交流问题、示例和项目相关话题。
快速开始
安装
# Server
curl -fsSL https://raw.githubusercontent.com/r33drichards/mcp-js/main/install.sh | sudo bash
# Optional CLI client
curl -fsSL https://raw.githubusercontent.com/r33drichards/mcp-js/main/install-cli.sh | sudo bash
安装到 /usr/local/bin。支持的平台:Linux x86_64/arm64 和 macOS Apple Silicon。你也可以使用 nix run github:r33drichards/mcp-js、Docker(参见 docker-compose.*.yml 配置),或从源代码构建。
想使用托管服务?在 Railway 上部署 — 仓库的 railway.json 配置了构建、健康检查和重启策略,RAILWAY.md 介绍了卷、变量和一键模板的设置方法。
连接 MCP 客户端
# Claude Code (stdio)
claude mcp add mcp-v8 -- mcp-v8 --directory-path /tmp/mcp-v8-heaps # stateful
claude mcp add mcp-v8 -- mcp-v8 --stateless # stateless
对于 Claude Desktop / Cursor,请将以下内容添加到客户端的 mcpServers 配置中:
{ "mcpServers": { "js": { "command": "mcp-v8", "args": ["--stateless"] } } }
然后向 Agent 提出请求:“运行这段 JavaScript:console.log([1,2,3].map(x => x*2))”。
通过 HTTP 运行
mcp-v8 --stateless --http-port 8080
# MCP endpoint: POST http://localhost:8080/mcp
# REST sidecar: POST http://localhost:8080/api/exec (JSON body, or a raw-body file upload)
/api/exec 接受 JSON 正文或原始正文文件上传——将脚本作为请求正文发送,并使用非 JSON 的 Content-Type(
curl --data-binary @script.js -H 'Content-Type: application/javascript' .../api/exec)。
run_js MCP 工具也可以通过可选的 file 参数读取服务端本地路径中的脚本——默认关闭,可通过
--allow-run-js-file 或 run_js_file
策略启用。
使用单个文件进行配置
所有标志也可以放在通过 --config(或
MCP_V8_CONFIG)传入的单个 TOML 或 JSON 文件中,其中包括可替代单独的
WASM / MCP 服务 / fetch 标头 / 策略 JSON 文件的结构化分区:
# server.toml — run with: mcp-v8 --config server.toml
http_port = 8080
heap_store = "dir"
heap_dir = "/var/lib/mcp-v8/heaps"
[policies.fetch]
policies = [{ url = "file:///etc/mcp-v8/fetch.rego" }]
优先级为:CLI 标志 > MCP_V8_* 环境变量 > 配置文件 > 默认值。请参阅
配置文件参考。
功能
- JavaScript 和 TypeScript:在隔离的 V8 引擎中运行(通过
deno_core);使用 SWC 移除 TypeScript 类型(仅移除类型,不进行类型检查)。 - Async/await 和计时器:支持 Promise 和事件循环,以及
setTimeout/clearTimeout。 - 控制台捕获:捕获
console.log/info/warn/error/debug/trace,将其流式写入存储,并支持按行或按字节分页读取。 - 异步执行模型:
run_js返回执行 ID;可轮询状态、流式读取输出,并取消正在运行的任务。 - 内容寻址堆快照:在多次调用之间持久化/还原 V8 状态(本地文件系统、S3,或 S3 + 写透缓存),也可使用无状态模式。
- WebAssembly:提供标准的
WebAssemblyAPI,以及预加载模块(--wasm-module);这些模块会作为全局变量公开,并以runjs__wasm__<name>存根工具的形式告知客户端。 - ES 模块导入:可选支持运行时获取
npm:、jsr:和 URL 导入(受策略控制)。 - 受策略控制的功能:
fetch、文件系统(fs)和子进程访问均会针对每项操作检查 Rego 策略;此外,fetch还支持注入标头/OAuth。 - 组合其他 MCP 服务:连接上游 MCP 服务,并通过
mcp.callTool()/mcp.listTools()从 JS 调用它们。 - 可自定义的界面:覆盖服务端的
instructions和run_js描述(--instructions、--run-js-description)。 - 单文件配置:一个 TOML/JSON
--config文件即可设置所有标志(优先级:CLI 标志 > 环境变量 > 配置文件 > 默认值)。 - 身份验证和集群:基于 JWKS 的 JWT 验证,以及可选的 Raft 集群功能,支持复制会话元数据和水平扩展。
- 多种传输方式:stdio、Streamable HTTP(MCP 2025-03-26+)和旧版 HTTP+SSE 传输方式(
--sse-port,由 vendored rmcp 0.1.5 提供服务),并提供 REST sidecar 和 OpenAPI 规范。 - 任务:通过 Streamable HTTP / stdio 使用原生 MCP 任务扩展(
io.modelcontextprotocol/tasks,SEP-2663):声明了该扩展的客户端会将run_js作为任务运行(tasks/get、tasks/cancel),适用于长时间运行的调用。(旧版 SSE 传输方式不支持任务。)
Agent 的代码可以做什么
以下全局变量可在 run_js 中使用(能力全局变量需要策略):
| 全局变量 | 用途 | 受限于 |
|---|---|---|
console, setTimeout | 输出和计时器 | — |
fetch(url, opts?) | HTTP 请求(Fetch API) | fetch 策略 |
fs.* | 文件 I/O(readFile、writeFile,……) | filesystem 策略 |
child_process / Deno.Command | 运行子进程 | subprocess 策略 |
import(npm: / jsr: / URL) | 外部 ES 模块 | --allow-external-modules + modules 策略 |
WebAssembly, __wasm_<name> | 运行/实例化 WASM | — |
mcp.callTool/listTools/servers | 调用上游 MCP 服务 | mcp_tools 策略 |
有关策略模型,请参阅概念 → 安全策略。
MCP 工具
| 工具 | 模式 | 描述 |
|---|---|---|
run_js | 两者 | 有状态:将执行任务加入队列 → {execution_id}。无状态:运行并返回 {output, error?}。 |
get_execution | 有状态 | 轮询执行状态/结果。 |
get_execution_output | 有状态 | 读取分页的控制台输出(按行或字节)。 |
cancel_execution | 有状态 | 终止正在运行的执行。 |
list_executions | 有状态 | 列出执行及其状态。 |
list_sessions, list_session_snapshots | 有状态 | 浏览命名会话和历史记录。 |
get_heap_tags, set_heap_tags, delete_heap_tags, query_heaps_by_tags | 有状态 | 为堆快照添加标签并搜索。 |
完整参数请参阅:MCP 工具参考。
将长时间运行的调用作为任务处理
该服务通过 rmcp 原生实现了 MCP tasks 扩展
(io.modelcontextprotocol/tasks,SEP-2663),支持 Streamable
HTTP 和 stdio 两种传输方式。initialize 结果会在
capabilities.extensions 下声明此扩展;声明了相同扩展的客户端可为 run_js 启用任务执行:
// client initialize params
{ "capabilities": { "extensions": { "io.modelcontextprotocol/tasks": {} } } }
// → tools/call { name: "run_js", ... } returns immediately with a task:
// { "resultType": "task", "taskId": "…", "status": "working", … }
之后,客户端会轮询 tasks/get(已完成的任务会内联携带工具结果,与调用本身的返回结果完全相同),并通过
tasks/cancel 停止运行。未声明此扩展的客户端仍会获得同步结果,不受影响。
配置
mcp-v8 完全通过 CLI 标志配置,包括存储后端、传输方式、执行限制、策略、Fetch 标头注入、WASM 模块、集群、JWKS
身份验证,以及提示词/工具描述覆盖项。完整且始终保持最新的列表请参阅生成的 CLI 标志参考。
mcp-v8 --help # all flags
mcp-v8 --print-openapi # print the REST OpenAPI spec
上游 MCP 服务的浏览器 OAuth
使用 --mcp-config 通过 OAuth 2.1 授权码流程连接到受保护的 Streamable HTTP MCP 服务。此设置仅接受 JSON;无法通过精简的 --mcp-server 语法使用。
[
{
"name": "protected",
"transport": "http",
"url": "https://mcp.example.com/mcp",
"auth": {
"type": "oauth_browser",
"scope": ["tools.read", "tools.call"],
"client_id": "optional-registered-client-id",
"client_secret": "optional-client-secret",
"redirect_port": 48123,
"token_cache": "/home/alice/.cache/mcp-js/oauth-protected.json"
}
}
]
auth 中除 type 外的所有字段均为可选项。如果没有 client_id,服务将使用 OAuth 动态客户端注册;否则将使用提供的已注册客户端。scope 是请求的权限范围数组。
除环回端点外,受保护资源、授权、令牌和动态注册端点都必须使用 HTTPS。这样既允许本地 OAuth 测试服务,又能拒绝使用明文的远程授权基础设施。
首次使用时,或没有可用的缓存凭据时,mcp-v8 会启动环回回调侦听器并打印授权 URL。随后,它会尝试在本地打开该 URL。如果主机没有图形界面,请将打印出的 URL 复制到另一台计算机上的浏览器中,完成授权,并确保该浏览器能够访问主机的 http://localhost:<port>/callback URL(例如,通过 SSH 端口转发)。授权最多等待五分钟。state 与 OAuth 状态不匹配的回调会被忽略;授权被拒或超时则会导致连接失败。
省略 redirect_port 时,侦听器会选择一个可用的本地端口。仅当身份提供方要求使用已注册的回调端口时才设置此项。回调始终绑定到环回地址,并使用生成的 http://localhost:<port>/callback 重定向 URI。
凭据缓存在 token_cache 指定的位置,或默认缓存在 ${XDG_CACHE_HOME:-$HOME/.cache}/mcp-js/oauth-<server-name>.json(如果这两个缓存位置都不可用,则使用系统临时目录)。缓存与 MCP URL、权限范围和客户端配置绑定。每次初始连接或重新连接时都会解析 OAuth 凭据。若有有效的刷新令牌,解析时会续期已过期的访问令牌,而无需打开浏览器;只有缓存凭据无法提供令牌时,才会进行交互式授权。已建立的传输会一直使用其承载令牌,直到重新连接或令牌失效。它不会主动刷新已建立传输的令牌。
请将缓存视为机密:其中可能包含刷新令牌。在 Unix 上,mcp-v8 会以 0600 权限写入缓存,并拒绝符号链接、非普通文件、由其他用户拥有的文件,以及可被组或其他用户读取的文件。存在安全风险、所有者不符或使用符号链接的缓存文件绝不会被使用。这些文件会被视为不可用,因此会重新授权;授权成功后,可以安全地替换缓存。请勿提交、分享或编辑缓存。若要撤销本地访问权限,请在授权服务器上撤销授权,并删除配置的缓存文件;下次连接时会重新开始授权。
CLI 客户端(mcp-v8-cli)
使用 progenitor 根据 OpenAPI 规范生成的完全类型化 REST API 客户端:
mcp-v8 --stateless --http-port 3000 &
mcp-v8-cli exec "console.log('hello'); 1 + 1"
mcp-v8-cli exec --file ./script.js # run a local file (uploaded as the code)
mcp-v8-cli executions get <execution_id>
mcp-v8-cli executions output <execution_id>
export MCP_V8_URL=https://my-server.example.com # point at a remote server
Rust 客户端(mcp-v8-client)
[dependencies]
mcp-v8-client = { git = "https://github.com/r33drichards/mcp-js" }
use mcp_v8_client::Client;
let client = Client::new("http://localhost:3000");
let body = mcp_v8_client::types::ExecRequest {
code: "1 + 1".to_string(),
heap: None, session: None,
heap_memory_max_mb: None, execution_timeout_secs: None, tags: None,
};
let resp = client.exec_handler(&body).await?;
println!("execution_id: {}", resp.into_inner().execution_id);
TypeScript 客户端(@mcp-v8/client)
同样根据 OpenAPI 规范生成的完全类型化 TypeScript 客户端(openapi-typescript 类型 + openapi-fetch 运行时)。位于 clients/typescript。
import { createMcpV8Client } from "@mcp-v8/client";
const client = createMcpV8Client("http://localhost:3000");
const { status, output, heap } = await client.runJs("console.log('hi'); 1 + 1");
API 更改后,使用 npm run generate 重新生成类型(读取 openapi.json)。
从源代码构建
该仓库是一个 Nix flake(它会接入预先获取的 V8 归档文件,让构建尽可能离线进行):
nix build github:r33drichards/mcp-js # → ./result/bin/server
# or for development:
nix develop # then: cargo build -p server
如果工具链能够构建 deno_core/V8,也可以在 server/ 中直接运行 cargo build --release。
限制
- 不支持
setInterval——请使用带有await的setTimeout循环。 - 没有 DOM 或浏览器 API——不存在
window/document。 - TypeScript 只会剥离类型,不会进行类型检查——无效类型会被移除,而不会报告错误。不支持 JSX/TSX(解析时会产生清晰的错误)。
许可证
MCP-V8 负载测试基准报告 v0.18.1
不同请求速率下的单节点与 3 节点集群对比。
结果
| 拓扑 | 目标速率 | 实际迭代数/秒 | HTTP 请求数/秒 | 执行平均值(毫秒) | 执行 p95(毫秒) | 执行 p99(毫秒) | 成功率 | 丢弃数 | 最大 VU 数 |
|---|---|---|---|---|---|---|---|---|---|
| cluster-stateful | 100/s | 99.9 | 301.3 | 52.42 | 53.46 | 102.44 | 100% | 1 | 11 |
| cluster-stateful | 200/s | 134.5 | 3855.4 | 1359.67 | 4062.32 | 4119.07 | 100% | 3450 | 200 |
| cluster-stateless | 1000/s | 602.2 | 1609.8 | 1377.83 | 5443.24 | 8890.83 | 99.9% | 22267 | 1000 |
| cluster-stateless | 100/s | 99.9 | 299.8 | 51.49 | 52.6 | 53.34 | 100% | 0 | 10 |
| cluster-stateless | 200/s | 199.8 | 599.5 | 51.92 | 53.43 | 55.14 | 100% | 0 | 20 |
| cluster-stateless | 500/s | 499.6 | 1498.5 | 53.2 | 56.33 | 63.75 | 100% | 0 | 50 |
| single-stateful | 100/s | 51.3 | 1970.1 | 1849.35 | 2081.54 | 2103.18 | 100% | 2823 | 100 |
| single-stateful | 200/s | 46.6 | 3828.4 | 4074.71 | 4628.01 | 4673.8 | 100% | 9020 | 200 |
| single-stateless | 1000/s | 115.4 | 430.8 | 8174.65 | 13854.8 | 16506.74 | 90% | 52654 | 1000 |
| single-stateless | 100/s | 99.9 | 299.6 | 53.31 | 55.96 | 59.52 | 100% | 4 | 14 |
| single-stateless | 200/s | 198.2 | 589.1 | 68.52 | 126.81 | 337.57 | 100% | 50 | 69 |
| single-stateless | 500/s | 138.9 | 348.3 | 3460.41 | 7332.03 | 10000.8 | 99.7% | 21416 | 500 |
P95 延迟
| 拓扑 | 速率 | P95(毫秒) | |
|---|---|---|---|
| cluster-stateful | 100/s | 53.46 | ████████████ |
| cluster-stateful | 200/s | 4062.32 | ██████████████████████████ |
| cluster-stateless | 100/s | 52.6 | ████████████ |
| cluster-stateless | 200/s | 53.43 | ████████████ |
| cluster-stateless | 500/s | 56.33 | █████████████ |
| cluster-stateless | 1000/s | 5443.24 | ███████████████████████████ |
| single-stateful | 100/s | 2081.54 | ████████████████████████ |
| single-stateful | 200/s | 4628.01 | ███████████████████████████ |
| single-stateless | 100/s | 55.96 | █████████████ |
| single-stateless | 200/s | 126.81 | ███████████████ |
| single-stateless | 500/s | 7332.03 | ████████████████████████████ |
| single-stateless | 1000/s | 13854.8 | ██████████████████████████████ |
说明
- 目标速率:配置的 constant-arrival-rate(k6 尝试发送的请求数/秒)
- 实际迭代数/秒:实现的每秒迭代数(每次迭代 = 1 次 POST /api/exec)
- HTTP 请求数/秒:每秒 HTTP 请求总数(每次迭代 1 次)
- 丢弃数:因 VU 耗尽而无法调度的迭代(表明服务端已饱和)
- 拓扑:
single= 1 个 MCP-V8 节点;cluster= 3 个使用 Raft 的 MCP-V8 节点
无需重启即可注册动态 HTTP 处理程序。有关启用设置、条件更新、身份验证以及请求/响应约定,请参阅 HTTP 函数。
调度器和 HTTP 文档
Diátaxis 文档地图 链接到实验性调度器和动态 HTTP 函数的引导式教程、运维操作指南、参考文档和说明。 该地图记录了通过 #287、#293 和 #295 集成的 #284 加固工作,但不表示已合并到主分支或已部署。
可选的 DynamoDB 元数据后端
使用 --dynamodb-table TABLE --dynamodb-namespace NAME 存储共享会话历史记录、
堆标签以及文件系统标签/reflog 元数据。Sled 仍是默认后端;
快照内容仍存储在单独选择的 S3/目录后端中。有关表结构、
IAM、限制以及 LocalStack/Testcontainers 设置,请参阅
元数据后端和集成测试。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。