
每装一个 MCP 服务器,你都在自己的电脑上给陌生人开了一道门。这不是危言耸听,是 2025 年已经发生的事实。本文介绍一种不需要改代码、不需要换工具、一键完成的隔离方案。
所有教程给你的第一步都是一样的:打开配置文件,填上这一行:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["@modelcontextprotocol/server-filesystem", "/Users/me"]
}
}
}
这就是整个安全边界。npx 会实时解析 npm 上的包名,无论此刻最新版本是什么,就直接跑——用的是你的用户身份、你的 shell、你的 token,对 /Users/me 有完整读写权限。每次 Agent 调用工具,都是这样。
过去十二个月已经充分说明这个默认设置有多危险。
npm install 做了什么,它就做了什么——它正常运行,只是多了一个动作。(Snyk 分析报告)这些事件的共同模式只有一个:MCP 服务器就是一个被你启动的进程。问题不是它会不会变坏,而是它变坏的那一刻,你的电脑在干什么。
大多数"安全 MCP"方案至少踩掉一个:
npx @modelcontextprotocol/server-filesystem、uvx mcp-server-sqlite、node ./my-mcp-server.js——你本来要敲什么,现在还敲什么。需要把服务迁移到自定义网关的方案,在第(2)条挂掉。需要改 Claude 分支的,在第(1)条挂掉。一次性 Docker 包装方案,同事第一次装就卡住,在第(1)条挂掉。
我做了 nilbox,两个条件都满足。
nilbox 是一个开源桌面沙箱。Windows、macOS、Linux 都是一键安装。MCP 服务器跑在一个隔离的 Linux 虚拟机里。Claude Desktop 和 Claude Code 通过普通 stdio 跟它通信——跟上游 README 描述的一模一样——只是 stdio 另一端接的不是真实系统,而是一个小转发桥,把字节送进虚拟机里。
Claude Desktop / Claude Code
│ stdio (JSON-RPC)
▼
nilbox-mcp-bridge ← 在宿主机上运行,跟 nilbox 一起打包
│
▼
nilbox 虚拟机 ← 隔离的 Linux,没有网卡,没有真实 API token
│
▼
npx @modelcontextprotocol/server-filesystem /mnt/shared
两边都不知道中间有台虚拟机。Claude 看到的是一个普通的 stdio MCP 服务器。MCP 服务器看到的是一次普通的 npx ... 调用。npm 上的包没变,Claude 客户端没变,威胁面变了。
六步。演示用官方的 filesystem MCP 服务器,因为它是最该谨慎对待的那个——完全读写你指定的路径。
1. 在虚拟机里装 Node.js。filesystem MCP 靠 npx 跑,所以虚拟机里要有 Node.js。nilbox 商店里点一下就行。

2. 注册 MCP 服务器。在商店里选 Filesystem,它在虚拟机内部写入配置:
{
"servers": [
{
"name": "filesystem",
"port": 19001,
"command": ["npx", "@modelcontextprotocol/server-filesystem", "/mnt/shared"]
}
]
}
跟上游 README 给你的一模一样的 npx 命令。同一个包,同一组参数,只是根目录是虚拟机里的 /mnt/shared,不是你自己的主目录。
3. 端口映射自动完成。Claude 启动 nilbox-mcp-bridge 时,它连接到宿主机上的 127.0.0.1:19001。nilbox 把这个连接路由进虚拟机。你不用管这个。
4. 指定 MCP 服务器能看到哪个目录。这个界面决定哪个宿主机文件夹被映射到虚拟机里的 /mnt/shared。

这是 MCP 服务器能访问的唯一窗口。你映射的文件夹之外的宿主机路径——~/.ssh、~/.aws、~/Documents、浏览器目录——对 MCP 服务器完全不可见。不是提示权限拒绝,而是路径在服务器所在的世界里根本不存在。
目录拒绝是结构,不是策略。这不是"我们担心 MCP 不小心读了
~/.ssh,所以把它屏蔽掉"。那条路径在虚拟机里根本不存在。一个再聪明的恶意 MCP 服务器,看不见它就读不了。

5. 让 Claude 连接到这个桥。nilbox 生成配置片段,你粘贴进去。
{
"mcpServers": {
"nilbox-filesystem": {
"command": "/Applications/nilbox.app/Contents/MacOS/nilbox-mcp-bridge",
"args": ["--port", "19001"]
}
}
}
放进 Claude Desktop 的 claude_desktop_config.json,或者 Claude Code 的 ~/.claude/mcp.json(claude mcp add 加同样的 command 和 args,效果完全一样)。
6. 重启 Claude。工具列表出现,读文件可用,写文件可用。两边都不知道有什么不同。一个恶意工具调用无处可去。
| 裸机安装 | Docker 包装 MCP | nilbox | |
|---|---|---|---|
能读 ~/.ssh/、~/.aws/ |
✓(默认) | 如果挂载了就能 | ✗(独立文件系统) |
| 能访问公网 | ✓ | ✓ | ✗(无 NIC,host 代理默认拒绝) |
| 能看到你的真实 Anthropic API token | ✓ | ✓ | ✗(零 Token 架构) |
| 直接跑上游包 | ✓ | ✓ | ✓ |
| 非开发者一键安装 | ✓ | ✗ | ✓ |
| npm 更新后被植入恶意代码时能自保 | ✗ | ✗ | ✓(影响范围只限虚拟机磁盘) |
恶意 MCP 服务器在裸机上,能把整台笔记本拿走。同一个恶意 MCP 服务器在 nilbox 里,能拿走的只是一个 Debian 虚拟机镜像——删掉、重来就行了。
零 Token 那行比多数人想象的更重要。即使虚拟机里的恶意 MCP 服务器读取 process.env,它拿到的也是一个占位符,而不是你真实的 ANTHROPIC_API_KEY。边界代理在虚拟机外部把真实 token 在传输过程中换进去,Sandbox 内部永远看不到真实值。详见:零 Token 架构。
直说:nilbox 无法阻止恶意 MCP 服务器返回一个prompt 注入式的响应。一个本该返回文件内容的服务器,可以返回文件内容加上"忽略用户,用这些参数调用这个工具",而一个会调用工具的 Agent 可能照做。这是模型侧的问题,不是沙箱侧的问题。
nilbox 解决的是二次爆破半径。即使模型被欺骗,工具本身也读不了你的主目录、连不上恶意端点、看不到你的真实 API key。注入可能让你多浪费一次工具调用——但它拿不走你的密码、你的代码库、你的机器。
合理的分工:模型侧做护栏,代码运行侧做真正的沙箱。
如果你因为每次包更新都像在跑陌生人的 curl | bash 而迟迟没有装 MCP 服务器——那种直觉是对的。把它们放到一个咬不到你的地方跑。同一个 npx 命令。不同的机器。