内容
# mcp-overlay
> **一个动态按需 MCP 网关 —— 配一次,多客户端共享,缓存各玩各的**
`mcp-overlay` 把多个 MCP server 聚合为单一 stdio 入口,对客户端暴露**瘦身后的 tool 列表**(剥离 `inputSchema` 的 description / examples 等冗余字段、保留参数结构降 token),并按需拉起 / 5 分钟空闲回收底层子进程。`tools/call` 路由时用**真实 schema** 做 Ajv 校验,失败时把完整 schema 注入错误消息(Bounce 模式),依赖 LLM 自纠。
## 为什么需要它?
你有多少个 AI 客户端(Claude Code / Cursor / Codex / Cline / Continue...)?每个都要配一份 MCP 服务列表。playwright 改个参数、context7 升个版——要同步 N 处。`mcp-overlay` 让通用配置单点维护:
```
~/.mcp-share/mcp_servers.common.json ← playwright, filesystem, git, context7...(所有客户端共享)
~/.claude/mcp_servers.json ← + claude-only MCP
~/.codex/mcp_servers.json ← + codex-only MCP
```
**首屏杀手锏**:通用 base + 个性化 overlay + 缓存隔离。
## 功能特性
- 🪶 **Schema 瘦身劫持**:剥离 `inputSchema.description` / `examples` 等冗余字段,大幅节省 token
- ⚡ **按需 lazy 加载**:首次 `tools/list` 拉起底层 server,5 分钟空闲自动回收
- 💾 **持久化工具缓存**:`.mcp_cache.json` 跳过冷启动,秒级返回
- 🎯 **Error-Bounce JIT**:参数校验失败时把真实 schema 注入错误消息,引导 LLM 自纠
- 🧩 **多客户端共享**:`MCP_CONFIG_COMMON` 通用配置 + `MCP_CONFIG` 个性化叠加
- 🛡️ **缓存隔离**:`MCP_CACHE_DIR` 让多客户端零冲突
- 🔧 **环境变量占位符**:配置中支持 `${ENV_VAR}` 引用环境变量
## 安装
### 方式 1:通过 npm 全局安装(推荐)
> ℹ️ npm 包尚未正式发布。发布后可直接运行此命令进行一键安装:
```bash
npm install -g mcp-overlay
```
### 方式 2:本地源码构建安装(开发或发布前使用)
```bash
# 克隆仓库
git clone https://github.com/auberstar587/mcp-overlay.git
cd mcp-overlay
# 安装依赖并编译
npm install && npm run build
# 将命令链接到全局,以便在任何地方直接使用 `mcp-overlay` 命令
npm link
```
## 快速开始
### 1. 准备配置文件
`mcp_servers.example.json` 是脱敏模板,复制为 `mcp_servers.json` 并填入你的真实参数:
```bash
cp mcp_servers.example.json mcp_servers.json
# 编辑 mcp_servers.json,填入 API key 等敏感信息(支持 ${ENV_VAR} 占位符)
```
### 2. 启动 gateway
```bash
# 如果已全局安装或通过 npm link 进行了全局链接,可直接执行:
mcp-overlay
# 自定义配置路径
MCP_CONFIG=/path/to/mcp_servers.json mcp-overlay
# 完整 v1.2 多客户端模式
MCP_CONFIG_COMMON=~/.mcp-share/mcp_servers.common.json \
MCP_CONFIG=~/.claude/mcp_servers.json \
MCP_CACHE_DIR=~/.claude/.cache \
mcp-overlay
```
*提示:如果你使用的是本地直接运行模式(未进行全局链接),可将上述命令中的 `mcp-overlay` 替换为 `node dist/index.js`。*
### 3. 在 AI 客户端中配置 stdio 启动
以 Claude Code `~/.claude.json` 为例:
#### 推荐方式(如果已全局安装或执行了 `npm link`)
```json
{
"mcpServers": {
"dynamic-gateway": {
"command": "mcp-overlay",
"env": {
"MCP_CONFIG_COMMON": "/Users/you/.mcp-share/mcp_servers.common.json",
"MCP_CONFIG": "/Users/you/.claude/mcp_servers.json",
"MCP_CACHE_DIR": "/Users/you/.claude/.cache"
},
"type": "stdio"
}
}
}
```
#### 备选方式(直接指向本地源码路径,无需全局安装)
```json
{
"mcpServers": {
"dynamic-gateway": {
"command": "node",
"args": ["/path/to/mcp-overlay/dist/index.js"],
"env": {
"MCP_CONFIG_COMMON": "/Users/you/.mcp-share/mcp_servers.common.json",
"MCP_CONFIG": "/Users/you/.claude/mcp_servers.json",
"MCP_CACHE_DIR": "/Users/you/.claude/.cache"
},
"type": "stdio"
}
}
}
```
## 环境变量
| 变量 | 必填 | 默认 | 作用 |
|---|---|---|---|
| `MCP_CONFIG` | 否 | `./mcp_servers.json` | 个性化配置路径 |
| `MCP_CONFIG_COMMON` | 否 | 无 | 通用配置路径(v1.2+)|
| `MCP_CACHE_DIR` | 否 | `dirname(MCP_CONFIG)` | 缓存目录(v1.2+)|
| `LOG_LEVEL` | 否 | `info` | 日志级别:debug / info / warn / error |
## 配置合并规则
```
mcpServers = { ...common.mcpServers, ...personal.mcpServers }
```
- 通用与个性化同名 server:**personal 覆盖 common**,启动日志会列出被覆盖的 server 名
- 通用文件不存在:warn 不阻断,回退到 personal-only
- 通用文件 JSON 语法错:抛错阻断启动(与 personal 一致,不降级)
## 安全注意事项
本网关会**执行配置文件中声明的任意命令**,并将环境变量传递给子进程。请遵循:
- **配置必须来自可信来源**:`mcp_servers.json` 中声明的 MCP server 等同于本地代码执行权限,不要加载来源不明的配置或 server。
- **最小化环境变量**:子进程只继承命令执行、用户目录、临时目录和系统兼容性所需的安全基础变量。如需额外变量,请在配置中通过 `env` 显式声明(如 `"KEY": "${ENV_VAR}"`),不要依赖父进程隐式继承。
- **缓存目录隔离**:多客户端各自指定 `MCP_CACHE_DIR`,避免缓存读写冲突。
- **版本锁定**:示例中用 `npx` 拉起的 server 建议锁定具体版本(如 `@scope/pkg@^x.y.z`)而非 `@latest`,避免供应链漂移。
- **凭据脱敏**:`mcp_servers.json` 已加入 `.gitignore`,使用 `${ENV_VAR}` 占位符引用环境变量,切勿硬编码 key。
## 命令
```bash
npm run build # tsc → dist/
npm run build:check # 类型检查(tsc --noEmit)
npm start # node dist/index.js
npm run dev # tsc --watch
npm test # vitest run(单元测试)
npm run test:e2e # 端到端(自动 build + mock server)
npm run check # 全量:clean + typecheck + build + unit + e2e
npm run prepack # 发布前钩子(等同于 npm run check)
```
## 架构
```
[AI Client (Claude Code / Cursor / Codex / ...)]
│ stdio (JSON-RPC 2.0)
▼
dist/index.js (StdioServerTransport)
│
▼
GatewayServer ──ConfigLoader──► MCP_CONFIG_COMMON? + MCP_CONFIG
│ ↓ merge
│ mcp_servers.json (合并后)
│
├─ tools/list ─► 首次 eager 拉起所有 server;后续走 MCP_CACHE_DIR/.mcp_cache.json
│ 劫持:剥离 description/examples,保留参数 key 与 type
│
└─ tools/call ─► toolToServerMap 路由 → Ajv 校验
├ 失败:抛 -32602,message 含完整 schema
└ 成功:ExecaClientTransport → 底层 MCP 子进程 (stdio)
空闲 5min → 优雅关闭 → 缓存保留
```
## 多客户端配置示例
```bash
# 1. 准备通用配置(所有客户端共享)
mkdir -p ~/.mcp-share
cat > ~/.mcp-share/mcp_servers.common.json <<'EOF'
{
"mcpServers": {
"playwright": { "command": "npx", "args": ["-y", "@playwright/mcp@0.0.76"], "type": "stdio" },
"context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp@3.2.2"], "type": "stdio" }
}
}
EOF
# 2. Claude Code 个性化(叠加 1 个 claude 专属 MCP)
cat > ~/.claude/mcp_servers.json <<'EOF'
{
"mcpServers": {
"serena": { "command": "uvx", "args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server"], "type": "stdio" }
}
}
EOF
# 3. Codex 个性化(叠加 1 个 codex 专属 MCP)
cat > ~/.codex/mcp_servers.json <<'EOF'
{
"mcpServers": {
"MiniMax": { "command": "uvx", "args": ["minimax-coding-plan-mcp", "-y"], "env": { "MINIMAX_API_KEY": "${MINIMAX_API_KEY}" }, "type": "stdio" }
}
}
EOF
# 4. 启动时指定各自的缓存目录(关键:避免冲突)
# Claude Desktop 启动参数
MCP_CONFIG_COMMON=~/.mcp-share/mcp_servers.common.json \
MCP_CONFIG=~/.claude/mcp_servers.json \
MCP_CACHE_DIR=~/.claude/.cache
# Codex 启动参数
MCP_CONFIG_COMMON=~/.mcp-share/mcp_servers.common.json \
MCP_CONFIG=~/.codex/mcp_servers.json \
MCP_CACHE_DIR=~/.codex/.cache
```
## 致谢 / Acknowledgements
本项目 `src/execa_transport.ts` 内联了 [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) 的 `ReadBuffer` / `serializeMessage` / `Transport` 接口实现。
```
@modelcontextprotocol/sdk
Copyright (c) 2024 Anthropic, PBC
Licensed under the MIT License
```
详见 [NOTICE](./NOTICE) 文件。
## License
[MIT](./LICENSE) © 2026 auberstar587