内容
# 安全环境引发工具
**请停止将敏感信息放入 `mcp.json` 文件中。** 该MCP服务器是一个包装器,用于为您启动其他MCP服务器:它们的配置存放在此软件包的配置文件中,使用`${secure:…}`占位符代替实际值。当服务器需要启动时,通过浏览器表单(MCP URL引发)收集缺少的值,并使用**AES-256-GCM加密存储在内存中** - 永远不会写入磁盘,永远不会对模型或MCP客户端可见。
```
Claude / MCP 客户端 ──stdio──▶ 安全环境引发工具 ──stdio──▶ 您真正的MCP服务器
│ (使用已解析的环境启动)
└──▶ https://127.0.0.1:48910/auth (您在此输入值)
```
## 为什么
MCP客户端配置文件(`mcp.json`、`claude_desktop_config.json`等)是纯文本文件,最终会出现在点文件仓库、截图和备份中。使用此包装器,客户端配置文件包含**零敏感信息**,而包装服务器的配置文件只包含**占位符**。
## 快速开始
**1.** 将包装器添加到您的MCP客户端(例如,Claude Code的`.mcp.json`):
```json
{
"mcpServers": {
"secure-env": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@grec0/mcp-secure-env-elicit", "--config", "C:/path/to/mcp-secure-env.config.json"]
}
}
}
```
**2.** 编写包装器配置文件(`mcp-secure-env.config.json`),描述要包装的服务器:
```json
{
"theme": "dark",
"servers": {
"oracle": {
"command": "npx",
"args": ["-y", "@grec0/mcp-oracle-db"],
"env": {
"ORACLE_CONNECTION_STRING": "${secure:ORACLE_USER}/${secure:ORACLE_PASSWORD}@//db.example.com:1521/XEPDB1"
},
"autoStart": true
}
},
"secrets": {
"ORACLE_USER": { "description": "Oracle schema user", "input": "text" },
"ORACLE_PASSWORD": { "description": "Oracle schema password" }
}
}
```
**3.** 使用它。要求您的客户端调用 `secure_env_start` 并传入 `{"server": "oracle"}` (或让 `autoStart` 自动执行)。如果缺少值,您将获得一个类似 `https://127.0.0.1:48910/auth?token=…` 的链接 - 打开它,填写表单,提交,然后再次运行命令。完成:oracle 工具现在显示为 `oracle__<tool>`。
## 引发机制
- 当服务器需要内存中不存在的敏感信息时,包装器会引发一个 **URL模式引发**。支持它的客户端会弹出一个对话框,打开表单;不支持的客户端(文本备用)会收到工具结果中的 URL - 手动打开并在提交后重新运行命令。
- 链接是 **一次性** 的,并且在 **整个10分钟窗口** 内保持有效,即使工具调用已经返回。
- 提交的值直接从浏览器表单进入加密的内存保险库, 通过环回 HTTPS。它们永远不会通过 MCP 协议传输,因此模型或客户端都不会看到它们。
- 值在 **进程生命周期内** 有效:重启您的 MCP 客户端,您将被再次询问(这是不持久的代价)。
## 共享配置库
除了本地路径,`--config` 接受一个 **git URL** - 因此一个仓库(私有库也可以)保存团队的包装器配置,每个开发人员只需指向它:
```json
"args": ["-y", "@grec0/mcp-secure-env-elicit", "--config", "https://git.example.com/org/mcp-configs.git#teams/backend.json"]
```
- `#path/inside/repo.json` 片段命名文件(或使用 `--config-file <path>`);如果没有它,则默认为仓库根目录下的 `mcp-secure-env.config.json`。
- 仓库在 **默认分支** 上进行浅克隆到 `~/.mcp-secure-env-elicit/repos/`。只有首次启动才会支付网络费用:之后,缓存副本立即使用,并在 **后台** 刷新,以便启动始终在 MCP 客户端连接超时内完成,并且配置更改在开发人员的 **下一次** 重启时生效。
- 提示:MCP 客户端通常允许服务器启动时间约为 30 秒,而冷 `npx` 下载加上首次克隆可能超过此时间。如果首次连接超时,只需重新连接 - 到那时一切都已缓存。固定确切版本(`@grec0/mcp-secure-env-elicit@x.y.z`)也会使 npx 在慢速网络上明显更快。
- 身份验证依赖于 **系统git**:Windows 上的 Git Credential Manager、SSH 密钥、缓存的 HTTPS 凭证 - 任何已经让开发人员能够 `git clone` 该仓库的凭证在此处也适用。无需额外的令牌:授予仓库的读取访问权限即可。(首次在机器上使用时,运行一次 `git clone <url>` 或让凭证管理器的窗口出现,以便缓存凭证。)
- **支持离线使用**:如果远程仓库无法访问,但存在缓存的克隆副本,则使用缓存的副本并显示警告 - 即使不在 VPN 上,启动也不会被阻止。
- 请记住,配置仅包含 **占位符**,因此对仓库的读取访问权限不会泄露任何敏感值;每个开发人员仍然通过本地登录表单提供自己的敏感信息。
## 远程服务器(HTTP / SSE)
并非所有MCP服务器都是本地进程。类型为 `http`、`https` 或 `sse` 的条目连接到 **远程** MCP端点 而不是启动一个,并接受相同的占位符:
```json
{
"servers": {
"sonarqube": {
"type": "https",
"url": "https://tools.example.com/sonar-mcp-server/mcp",
"headers": { "SONARQUBE_TOKEN": "${secure:SONARQUBE_TOKEN}" }
},
"db": {
"type": "sse",
"url": "https://tools.example.com/mcp-sse-db/sse",
"headers": { "X-Api-Key": "${secure:DB_API_KEY}" },
"insecureTls": true
}
}
}
```
- `http` / `https` 使用现代 **Streamable HTTP** 传输协议,并自动回落到 SSE,如果端点结果证明是遗留的 - 因此 `"type": "https"` 适用于两者,您无需了解。
- `sse` 强制使用遗留 SSE 传输协议(标头也会发送到流请求上)。
- `insecureTls: true` 接受一个非公开信任的服务器证书(自签名或内部CA),仅限于该服务器的请求 - 等同于 npm 的 `strict-ssl=false`,适用于您的内部工具主机。
- 没有 `type`(或 `"type": "stdio"`)保持启动行为:`command`、`args`、`env`、`cwd`。
> **TLS 和 stdio 子进程:** `insecureTls` 仅在远程条目中存在,因为包装器在此处发出 HTTP 请求。当 **stdio** 子进程与内部主机通信时,请在子进程的环境中配置 TLS。对于基于 Node 的子进程(Node ≥ 22.15),干净的选择是 OS 证书存储:
>
> ```json
> "env": { "NODE_USE_SYSTEM_CA": "1" }
> ```
>
> (或 `"NODE_EXTRA_CA_CERTS": "C:/path/ca.pem"`,或作为最后的手段 `"NODE_TLS_REJECT_UNAUTHORIZED": "0"`)。
## 占位符
`${secure:NAME}` 在 `env` 值、`args` 字符串(用于 stdio 服务器)以及 `headers` 值或远程服务器的 `url` 中 - 包括嵌入在较大的字符串中(连接 URL、DSN、`Bearer …` 前缀)。相同的 `NAME` 跨多个服务器使用时,只会询问 **一次** 并共享。
可选选择表单小部件:`${secure:NAME:type}`,支持 `text`、`password`、`email`、`number`、`url` 或 `tel` 类型。类型也可以在 `secrets` 元数据中设置(`"input": "email"`)。如果没有显式设置,则根据名称推断小部件:变量匹配 `PASSWORD`、`PASS`、`PWD`、`SECRET`、`TOKEN`、`API_KEY`、`ACCESS_KEY`、`PRIVATE_KEY`、`CREDENTIAL`、`AUTH` 渲染为密码输入;包含 `MAIL` 的名称渲染为电子邮件输入。
## 让浏览器记住值
表单故意 **支持自动填充**:每个输入都有一个稳定的 `name`/`id`(变量名本身)和一个真正的 `autocomplete` 令牌 - 没有 `autocomplete="off"`。这意味着您的浏览器(或密码管理器)提供保存您提交的内容,并在下次同一服务器请求时再次提出建议,因此重新输入值通常只需两次点击。
保持自动填充功能正常工作的三个关键点:
- **受信任的证书。** 浏览器拒绝在具有证书错误的页面上 **保存** 密码 - 如果锁图标损坏,Chrome 会静默跳过“保存密码?”提示。按照 [Trusted TLS](#trusted-tls-no-browser-warning) 部分的说明一次设置,保存功能就会开始正常工作。
- **稳定的端口。** 浏览器根据页面来源(协议 + 主机 + 端口)记录已保存的值。默认端口是 `48910`;如果端口被占用,包装器会回落到该运行的临时端口(已保存的值不会被提供)。可以通过 `PORT` 环境变量覆盖。
- **稳定的 URL 路径。** 表单始终位于 `/auth`;令牌在查询字符串中传递,不会影响自动填充。
## 工具
| 工具 | 描述 |
|---|---|
| `secure_env_status` | 每个配置服务器的状态:`stopped`/`starting`/`running`/`error`,缺少的敏感信息 **名称** (而非值),待处理登录 URL,工具数量。 |
| `secure_env_start` | 启动服务器。如果缺少值,则返回登录 URL;再次调用后提交。 |
| `secure_env_stop` | 停止正在运行的服务器并删除其工具。 |
| `<server>__<tool>` | 每个正在运行的子服务器的每个工具,原样转发(包括模式)。工具列表通过 `notifications/tools/list_changed` 刷新。 |
## 主题
登录页面提供多个主题:`light`(默认)、`dark`、`ocean`、`forest`、`terminal`、`sunset`。通过以下方式选择一个:
- 命令行上的 `--theme dark`,
- `MCP_SECURE_ENV_THEME=dark`,
- 配置文件中的 `"theme": "dark"`。
## 受信任的 TLS(无浏览器警告)
登录页面必须是 HTTPS(MCP 客户端仅打开 `https:` URL 进行 URL 引发)。开箱即用,包装器生成一个 **自签名** 证书 - 浏览器无法验证谁签发了它,因此它会显示“您的连接不安全”,更烦人的是,它 **拒绝保存您的密码**,只要证书不受信任。
该证书保存在 `~/.mcp-secure-env-elicit/tls/cert.pem` - 在运行之间保持稳定,有效期为 825 天,正确使用 `serverAuth` - 正是如此,您可以 **一次** 信任它,然后完成。请选择以下任意一种方法:
1. **一条命令(推荐)。** 无需仓库,无需路径 - 相同的 npx 包即可完成:
```
npx -y @grec0/mcp-secure-env-elicit trust-cert
```
如果需要,它会生成证书并将其注册到您的 OS 信任存储中(在 Windows 上,请接受确认对话框;在 macOS 上,它可能会要求您的密码;在 Linux 上,它会打印要运行的两个 `sudo` 命令)。然后重启您的浏览器。
为什么有效:“不安全”仅意味着“由 OS 不知道的人签名”。将证书添加到您的用户信任存储中,使 **您** 成为担保该证书有效性的授权人 - 在这里是合理的,因为密钥从未离开您的机器,服务器仅监听 `127.0.0.1`。
2. **使用 [mkcert](https://github.com/FiloSottile/mkcert)。** `mkcert -install && mkcert 127.0.0.1 localhost` 颁发一个本地受信任的对,并指向配置:
```json
{ "tls": { "certPath": "C:/certs/127.0.0.1+1.pem", "keyPath": "C:/certs/127.0.0.1+1-key.pem" } }
```
3. **使用您自己的域名。** 指向一个真实的 DNS 名称(例如 `secure-env.yourdomain.com`)到 `127.0.0.1`,为其获取一个真实的证书(Let's Encrypt DNS-01 适用于环回名称),设置 `HOST=secure-env.yourdomain.com` 和 tls 路径。完全绿色的锁图标,无需更改任何信任存储。
要验证信任后:重新打开登录页面 - 没有警告,第一次提交后浏览器会提供保存值。
## 配置参考
| 位置 | 内容 |
|---|---|
| `--config <path or git url>` / `MCP_SECURE_ENV_CONFIG` | 配置文件位置 - 本地路径或 `…repo.git[#file]` URL(参见 *共享配置库中的 Git 仓库*)。默认值:`./mcp-secure-env.config.json`,然后是 `~/.mcp-secure-env-elicit/config.json`。 |
| `--config-file <path>` | 当 `--config` 是 git URL 且没有 `#fragment` 时,仓库内的文件。 |
| `--theme <name>` / `MCP_SECURE_ENV_THEME` / `"theme"` | 登录页面主题。 |
| `HOST` / `PORT` | 环回服务器绑定。默认为 `127.0.0.1:48910`。保持端口稳定以便浏览器自动填充。 |
| `"servers"`(stdio) | `command`、`args`、`env`(带有占位符),可选 `cwd`,可选 `autoStart`。服务器名称不能包含 `_`。 |
| `"servers"`(远程) | `type`(`http`/`https`/`sse`),`url`,`headers`(带有占位符),可选 `insecureTls`,可选 `autoStart`。 |
| `"secrets"` | 每个秘密的可选 `description` 和 `input`。 |
| `"tls"` | 可选的 `certPath`/`keyPath`,用于受信任的证书。 |
关于 `autoStart` 的说明:其秘密仍然缺失的服务器在启动时 **不会** 被提示 - 一个包装器承载多个服务器,在启动时询问所有内容将是一堵提示墙。它们安静地等待,并在首次启动时引发(`secure_env_start`)。
## 安全模型,诚实
- 秘密值使用进程生成的密钥以 **AES-256-GCM** 加密存储在包装器的内存中。它们仅在启动时解密,直接进入子进程的环境。
- 包装器除了 TLS 材料(不包含任何秘密)之外,永远不会将任何内容写入磁盘。
- 值永远不会进入 MCP 对话:故意 **没有** “设置秘密”工具,因此模型甚至不会意外地被传递秘密。唯一的输入路径是环回表单。
- 错误消息是 **编辑过的**:传输和获取错误可以回声请求 URL 或上游响应正文,因此每个秘密值(纯文本和百分比编码)都被清理为 `[secret]`,然后在 stderr、`secure_env_status` 或工具结果中到达错误。
- 子进程接收秘密作为纯环境变量 - 这是被包装的服务器的约定。任何能够检查子进程环境(相同的 OS 用户)的人都可以读取它们,就像您直接配置服务器一样。
- 在 `env` 中优先使用占位符而不是 `args`:命令行在进程列表中可见。
## 限制
- 目前仅支持 **工具** 代理(不支持资源或提示)。
## 开发
```bash
npm install
npm run check # lint + typecheck + tests + build
```
## 许可证
MIT