- Web 后端可以启动进程。 API 令牌是该能力与所有能够访问端口的对象之间的屏障。
- 有操作系统钥匙串时,机密信息会进入钥匙串。 没有钥匙串时,机密信息会进入一个除非提供密钥否则为明文的文件,而且这是自动发生的。
- stdio 服务器以你的身份运行。 Inspector 能读取的内容,所启动的服务器通常也能读取。
- mcpdo 守护进程是持有活动连接的长期进程,由令牌和同用户文件权限保护。
Web 后端及其 API 令牌
Web 客户端是由 Node 服务器支持的浏览器应用,该服务器负责 MCP 连接。它可以按请求启动 stdio 进程,因此每个/api/* 路由都需要每次启动专用的 bearer 令牌(x-mcp-remote-auth: Bearer <token>)。浏览器如何获取令牌,请参阅会话令牌。
令牌能保护和不能保护的内容如下:
- 令牌是非浏览器客户端的真正守门机制。 来源允许列表(
ALLOWED_ORIGINS)可以阻止其他网页驱动后端,但不带Origin请求头的请求(curl、脚本以及任何非浏览器客户端)会完全跳过该检查。 GET /会披露令牌。 后端会将令牌注入所提供的 HTML,使重新加载或书签继续工作。因此任何能够加载该页面的人都可以读取令牌,这就是绑定地址比令牌值更重要的原因。设置自定义MCP_INSPECTOR_API_TOKEN不会改变这一点,因为自定义令牌也会以同样方式披露。DANGEROUSLY_OMIT_AUTH=true会完全移除守门机制。 任何能够访问端口的对象都可以以你的身份启动进程,并使用 Inspector 持有的任何 OAuth 令牌。只有true或1会关闭身份验证;包括false在内的其他值都会保持开启。- 只有
/api/*受到令牌保护。 页面本身、静态资源以及GET /healthz无需令牌即可提供。/healthz只返回{"status":"ok"},供容器和编排器探测。
后端监听位置
后端默认绑定127.0.0.1。除非设置 DANGEROUSLY_BIND_ALL_INTERFACES=true,否则绑定所有接口(0.0.0.0、:: 以及等效写法)会被拒绝。绑定某一个特定地址无需该选择加入,因为它只暴露一个有意指定的地址,而不是一次性暴露所有地址。请参阅在网络上托管。
Docker
Docker 镜像会改变上面描述的两个方面。仅在环回地址上发布端口
容器内的 Inspector 必须绑定0.0.0.0 才能通过 -p 访问,因此镜像设置了 DANGEROUSLY_BIND_ALL_INTERFACES=true。这个选择加入只影响容器接口,不影响主机接口。哪些主机接口可以看到 Inspector,取决于发布端口的方式:
裸写
-p 会将能够创建进程的后端以及会披露令牌的页面暴露到本地网络。发布每个端口(6274,以及 MCP Apps 监听器的 6275 / 6278)时都应保留 127.0.0.1: 前缀。
数据卷会将机密信息放到磁盘
容器没有操作系统钥匙串。没有挂载/home/node/.mcp-inspector 卷时,机密信息会在会话期间保留在内存中,容器退出后丢失。为了保留服务器列表而挂载该目录,会同时将机密信息切换到卷上的 secrets.json 文件;该文件除非提供密钥否则为明文。root、主机上 docker 组的每个成员(其权限等同于 root),以及获得该卷备份、快照或副本的任何人都可以读取它。
请通过 MCP_INSPECTOR_SECRET_KEY_FILE 以文件形式提供密钥(使用 Docker 或 Compose secret),不要使用环境变量:通过 -e MCP_INSPECTOR_SECRET_KEY=… 传入的密钥可被能够运行 docker inspect 或 docker exec 的任何人看到。配方页展示了两种方式。
机密信息存储
Inspector 不会将凭据写入mcp.json、client.json 和 oauth.json,这样共享、提交或同步这些文件就不会泄露凭据。以下内容会作为机密信息存储:
- 已获取的 OAuth 令牌(访问、刷新和 ID 令牌),受
MCP_INSPECTOR_PERSIST_TOKENS限制,以及企业管理授权中的 IdP 会话令牌; - 每个服务器的 OAuth 客户端密钥和企业 IdP 客户端密钥;
- 动态注册的客户端密钥及其注册访问令牌;
- 每个 stdio 服务器的
env:值。
headers 不是机密信息。 它们会按原样保存到 mcp.json,因此携带凭据(API key 或静态 Authorization 值)的请求头仍会保留在文件中。
存储方式如何选择以及如何更改,请参阅机密信息存储位置。本节说明相关风险。
自动回退到明文
这应被视为风险,而不仅是配置事实。文件以0600 权限写入,这只能阻止其他非 root 用户读取。要关闭这一风险,请执行以下任一操作:
- 恢复钥匙串(安装 libsecret 并运行 Secret Service,例如
gnome-keyring,或在桌面会话中运行); - 使用
MCP_INSPECTOR_SECRET_KEY_FILE中的生成密钥加密文件; - 设置
MCP_INSPECTOR_SECRET_STORE=memory,每次会话重新输入机密信息。
文件存储可以防范什么
文件存储用于没有钥匙串的机器,安全性弱于钥匙串。即使经过加密,也应将把机密信息保存在其中视为中等风险。 没有密钥(明文,权限0600):
有密钥(AES-256-GCM,使用每次写入随机生成的盐通过 scrypt 拉伸密钥):
密钥存放位置决定第二行的风险。使用
MCP_INSPECTOR_SECRET_KEY 时,密钥位于 Inspector 环境中,同用户或 root 可以通过 /proc/<pid>/environ 读取;容器中可以通过 docker inspect / docker exec 读取,也可能存在于用于启动它的 shell profile、.env 文件或 Compose 文件中。使用 MCP_INSPECTOR_SECRET_KEY_FILE 会将风险缩小到能够读取密钥文件的人,但 Inspector 必须读取它,因此同一用户仍然可以读取。如果密钥与机密文件位于同一目录、备份、卷或仓库中,加密就没有实际收益。
简而言之,加密会将“文件泄露”变成“文件和密钥同时泄露”。它无法防范已经拥有机器或容器 root 权限,或已经拥有 Inspector 用户权限的人。如果这不可接受,请使用钥匙串或内存存储。
以下两种失败模式会明确报错,而不是静默处理:
- 如果密钥文件缺失、不可读或为空,如果
MCP_INSPECTOR_SECRET_KEY_FILE设置为空值,或者同时设置了两个密钥变量,存储方式会拒绝读写,而不是回退到明文。 - 如果口令发生变化或丢失,现有文件将无法解密。Inspector 会将其读取为空,并拒绝覆盖,因此请恢复口令,或删除文件后重新输入值。
stdio 服务器以你的身份运行
stdio MCP 服务器是 Inspector 以你的用户权限启动的进程,与任何 MCP 主机的行为相同。- **环境:**Inspector 不会将自己的整个环境传给服务器。stdio 服务器只会获得短的允许列表(macOS 和 Linux 上的
HOME、LOGNAME、PATH、SHELL、TERM、USER)以及配置的env:。因此MCP_INSPECTOR_SECRET_KEY中的密钥不会直接传给它。 - **但它使用同一用户:**服务器可以自行打开
secrets.json,读取oauth.json和目录,通常也可以通过/proc/<pid>/environ读取 Inspector 的环境。环境允许列表是卫生措施,不是隔离边界。
docker run -i --rm --network none <image> 作为 stdio 命令。HTTP 和 SSE 服务器不会运行本地代码,因此不需要进程隔离。
mcpdo 连接守护进程
mcpdo 通过将连接交给后台守护进程mcpdod,在命令之间保持连接。它会在第一次需要命令时自动启动(mcpdo connect 或任何针对连接的命令),不需要单独的“启动守护进程”步骤。本节说明该进程暴露的内容。
客户端如何访问它,以及谁还能访问
没有 TCP 端口,因此机器外部无法访问守护进程。在 Unix 上,守护进程目录会创建为或收紧为权限
0700、由你拥有,并且必须是真实目录而不能是符号链接。里面的 socket 和锁文件权限为 0600,因此其他非 root 用户无法访问;root 以及以你的身份运行的进程可以访问。
目录按以下顺序选择:MCP_INSPECTOR_DAEMON_DIR、MCP_STORAGE_DIR、~/.mcp-inspector。如果 socket 路径超过平台限制(macOS 约 104 字节、Linux 108 字节),会提前拒绝,并在错误中指出要缩短的变量。
如何验证命令
每个请求都必须携带 bearer 令牌,不存在未授权请求路径。- **共享模式(默认):**启动守护进程的
mcpdo命令生成随机的 256 位令牌,并通过环境传给守护进程。守护进程将其发布到目录中权限为0600的mcpdod.token,因此同一用户运行的任意mcpdo命令都可以读取。该文件系统权限边界与 socket 的同用户边界相同。 - 私有模式:
eval "$(mcpdo private)"会在$TMPDIR/mcp-conn-<uid>/下创建新的0700目录,并向当前 shell 导出MCP_INSPECTOR_DAEMON_DIR和MCP_INSPECTOR_DAEMON_TOKEN,使它拥有自己的守护进程和连接。由于$TMPDIR可能共享,使用前会检查父目录的所有权和符号链接。
私有模式可以隔离不同 shell 之间的连接和守护进程状态,但它不是针对同一用户其他进程的安全边界:任何拥有你的 UID 且知道守护进程目录的进程都可以读取令牌。需要硬边界时,请使用单独的用户账户或容器。
它在内存中持有什么
在守护进程运行期间,每个打开的连接都会持有:- 活动中的 MCP 连接,以及对于 stdio 服务器由守护进程作为父进程启动的子进程;
- 连接解析后的配置,包括从机密信息存储中读取的 stdio
env:值; - HTTP 连接正在使用的 OAuth 令牌;重新拨号断开的传输时,它还会从存储中重新读取令牌;
- 非交互式命令停放的 elicitation,直到回答或 10 分钟后过期。
mcpdo 命令的环境启动。它会继承该 shell 的变量(包括设置时的 MCP_INSPECTOR_SECRET_KEY 和自己的 IPC 令牌 MCP_INSPECTOR_DAEMON_TOKEN),并在整个生命周期内保留,即使你之后在 shell 中修改了这些变量。它启动的 stdio 服务器仍然只会收到上文所述的允许列表,以及从运行 mcpdo connect 的 shell 快照出的 env:。
在没有钥匙串的主机上,mcpdo 不会自动回退到内存存储。它的前端命令和守护进程是不同进程,按进程存储无法在二者之间传递令牌,因此 mcpdo 使用共享的 secrets.json 文件,并遵循上文“除非提供密钥否则为明文”的规则。显式设置 MCP_INSPECTOR_SECRET_STORE=memory 仍然优先。mcpdo 每次 connect 打印一次存储警告,而不是每条命令都打印。
生命周期
- **启动:**第一次需要命令时启动。如果两个命令同时启动,
O_EXCL锁(mcpdod.lock)保证只有一个成功。已退出守护进程留下的锁会被回收,运行中的守护进程永远不会被接管。 - **停止:**运行
mcpdo daemon stop或发送SIGINT/SIGTERM。最后一个连接关闭约 60 秒后也会自动退出。没有最大生命周期:只要有连接打开,守护进程就会保持运行。 - **正常停止:**拒绝新工作,为正在执行的请求提供短暂宽限期,关闭每个连接,并删除 socket、令牌文件和锁。
- **崩溃:**它持有的所有连接都会丢失,stdio 服务器会失去 stdin,通常随即退出。已存储的 OAuth 令牌和机密信息不受影响。下一条
mcpdo命令会启动新的守护进程,清理旧 socket、回收锁并写入新令牌;必须重新运行mcpdo connect建立连接。
pgrep mcpdod 查找遗留的守护进程。它会将进程标题设置为 mcpdod。
写入磁盘的内容
所有内容都位于权限为0700 的守护进程目录中:
守护进程通过与其他客户端相同的
oauth.json 和机密信息存储写入 OAuth 状态和机密信息,因此机密信息存储中的所有内容同样适用。