你以为你装的是一组函数。模型看到的是一段别人写的、会被原样读进上下文的文本——而且在你调用任何工具之前就已经读完了。
前三期把 MCP 当能力讲:文档检索搬文本,浏览器自动化给出一双手。本期换个视角——每接一个 server,你就把一段陌生人写的文字接进了模型的系统提示,还顺手给了它一份你的凭据。
这一期没有「主角 skill」,主角是一条从装之前到跑起来的防线:先用 MCP Inspector 和 agent-scan 验货,再用 mcp-context-protector 或容器网关把它围起来,最后用客户端权限规则收口。十一个条目全部可装可读,判据在后半程。
# ① 先看它到底给了模型什么(官方调试器,Web UI / CLI / TUI 三种界面)
npx @modelcontextprotocol/inspector
# ② 再扫一遍机器上已装的所有 server 与 skill
export SNYK_TOKEN=...
uvx snyk-agent-scan@latest
# ③ 当场把爆炸半径缩一圈:只读 + 只开要用的工具组
github-mcp-server --read-only --toolsets repos,issues
第 ① 条的重点不是「连上了」,而是逐个读一遍 tools/list 返回的描述文本。那段文本会原样进入模型上下文;它由 server 作者写,不由你写。第 ② 条有个坑值得单独说:网上大量教程还在教 uvx mcp-scan@latest——那个仓库 invariantlabs-ai/mcp-scan 现在已经是 snyk/agent-scan,包名也换成了 snyk-agent-scan,并且要求 SNYK_TOKEN。抄命令前先看仓库现状,这条纪律上一期刚交过一次学费。
MCP 规范(2026-07-28 版)在 server/tools.mdx 里给了两句几乎是本期全部理论的话:「clients MUST consider tool annotations to be untrusted unless they come from trusted servers」,以及「there SHOULD always be a human in the loop with the ability to deny tool invocations」。注意第一句的措辞——不可信的不只是返回值,连 server 自己标注的「我这个工具是只读的」也不可信。
规范总则那一节把它归到三条原则:用户须明确同意并理解所有数据访问与操作;宿主在把用户数据暴露给 server 前须取得同意;「Tools represent arbitrary code execution and must be treated with appropriate caution」。工具等于任意代码执行——这是规范原话,不是本站的修辞。
npx @modelcontextprotocol/inspector,Web UI / CLI / TUI 三种界面,CLI 支持 --format json 可进 CI。什么时候别装:它是调试器不是防护,别指望它拦住什么——它只让你看见。invariantlabs-ai/mcp-scan)。替你干掉:逐个人肉读描述。怎么装:uvx snyk-agent-scan@latest(0.5.x 线为 uvx snyk-agent-scan@0.5.17),需 SNYK_TOKEN;它扫已装的 server 与 skill,标记提示注入、tool poisoning、tool shadowing、toxic flow、描述里的危险词、以及 skill 里的硬编码密钥与可疑下载地址。什么时候别装:不接受把本地配置清单送去做云端分析时——这是它换来覆盖面的代价。io.github.<user>/<server> 必须以该 GitHub 身份认证,自定义域名要过 DNS 或 HTTP 挑战。什么时候别依赖它:它至今仍是 preview(据仓库 README,预览版自 2025-09-08 起,官方明说可能有破坏性变更与数据重置),归属校验不等于代码审计。direct-poisoning.py、shadowing.py、whatsapp-takeover.py。什么时候别碰:别在日常工作机上跑,读代码就够。uv sync 后用 mcp-context-protector.sh --command "<下游 server 命令>" 把它包在中间;它对 server instructions、工具描述、入参 schema 做首次信任即固定,之后语义有变就拦下等你批;顺带过滤用于视觉欺骗的 ANSI 控制字符,加 --guardrail-provider LlamaFirewall 还能隔离可疑的工具返回值。什么时候别装:你只接官方自家 server 且客户端已有审批门时,这层收益有限。npx 起的进程裸跑在你的 home 目录里。怎么装:docker mcp gateway run(可加 --profile / --port / --transport),每个 server 一个容器,密钥走 docker mcp secret,可按 profile 逐个启停具体工具。什么时候别装:只接一两个本地 server 时,容器与镜像的运维税大于收益。mcp__<server>__<tool>(A-Z a-z 0-9 _ - 之外的字符换成 _;plugin 形态是 mcp__plugin_<plugin>_<server>__<tool>),权限规则、hook matcher、subagent 的 tools 用的都是这个名字。怎么用:"deny": ["mcp__*"] 一条关掉全部 MCP 工具;"deny": ["mcp__database__drop_table"] 点名封杀。mcp__github__get_* 有效,而 "*" / "mcp__*" 这类不锚定的 allow 会被直接跳过并告警。这个设计值得抄:允许必须点名到具体 server,新接进来的 server 蹭不进你的允许列表。另:settings 文件里带括号的 mcp__ 规则会被跳过,要按参数匹配得用 --disallowedTools。--strict-mcp-config 与 disabledMcpjsonServers。替你干掉:clone 一个仓库就顺手接上了它 .mcp.json 里的 server。交互式会话本来会弹批准框,但 -p、SDK 与云端会话不弹、直接加载;--strict-mcp-config 让它只用你显式传入的配置,disabledMcpjsonServers 按名字在所有会话类型里拒绝。什么时候用:所有 CI 与无人值守场景,无条件。requiresUserInteraction(server 侧的自我约束)。替你干掉:一个不可逆操作被 auto 模式一路放行。怎么用:写 server 的人在 tools/list 条目的 _meta["anthropic/requiresUserInteraction"] 里置 JSON 布尔 true,该工具每次调用都弹确认,acceptEdits / auto / bypassPermissions 都盖不住,allow 规则也跳不过。边界:dontAsk 模式下它变成直接拒绝;--permission-prompt-tool 给的 allow 会被转成 deny。--read-only(或 GITHUB_READ_ONLY)——README 写明写操作会被跳过,即使被显式请求;--toolsets(或 GITHUB_TOOLSETS)默认只给 context,repos,issues,pull_requests,users,all 才全开。什么时候别这么配:确实要它开 PR 那天再去掉 --read-only,然后加回来。如果你写 server 而不只是装,规范 basic/authorization/security-considerations.mdx 里那几条 MUST 是硬门槛:server MUST NOT 把从客户端收到的 token 直接透传给下游 API;server MUST 校验 token 的受众、只接受发给自己的 token;客户端 MUST 带上 resource 参数、MUST 实现 PKCE 并在无法确认对方支持时拒绝继续;用静态 client ID 的代理型 server,MUST 对每个动态注册的客户端单独取得用户同意(这条治的就是 confused deputy)。
/sandbox 就一起罩住了。.mcp.json 或 .claude/ 的命令,可以给自己加权限,或者加一个跑在沙箱之外的 hook 或 MCP server。换句话说,沙箱之所以要保护这些文件,正是因为 MCP server 本身不受它约束。你的隔离边界在容器或虚拟机,不在 /sandbox。requiresUserInteraction 每次都弹窗。安全措施最常见的死法不是被绕过,是被嫌烦关掉——所以顺序很重要:先做第三层那几条零成本的,第二层只在接外部 server 时才上。webapp-testing 用 Python 脚本替掉了一整个浏览器 server。脚本的权限由你写死,不由描述文本决定。--read-only 加 --toolsets 之后,剩下的攻击面通常只剩「返回值里有坏话」这一种。claude mcp remove。工具税和攻击面同时按分钟计费,这是最被低估的一招。先做这三件(今晚,零成本):① npx @modelcontextprotocol/inspector 逐个读一遍已装 server 的工具描述;② 在设置里写死 "deny": ["mcp__*"] 再按 mcp__<server>__<tool> 逐个放行,allow 一律锚定到具体 server;③ 给 github-mcp-server 加 --read-only --toolsets repos,issues,要写那天再摘。
接外部 server 那天再加:uvx snyk-agent-scan@latest(装前扫)· mcp-context-protector.sh --command ...(描述固定 + 变更告警)· docker mcp gateway run(容器隔离与密钥)· MCP Registry 查命名空间归属。
团队与 CI:--strict-mcp-config 和 disabledMcpjsonServers 无条件开(无人值守时批准框不会弹)· ToolHive 做集中管控与审计 · 写 server 就照 security-considerations.mdx 那几条 MUST 来。
明确别做:拿 star 数当可信度 · 照抄博客里的 uvx mcp-scan@latest(仓库已迁到 snyk/agent-scan)· 相信工具自己标注的 annotation · 以为 /sandbox 罩得住 MCP。
一句话判据:假设这个 server 每一个字都是攻击者写的——它能让我损失什么?答案够大就别让它和你的写权限同时在线。
tools/list 的返回内容在会话一开始就进了上下文。mcp-context-protector 的 README 把它列为首要防护目标。mcp__<server>__<tool> 是客户端侧的应对。npx @modelcontextprotocol/inspector、Web UI / CLI / TUI 三界面与 --format json;据 GitHub 仓库页 2026-09 约 1.09 万 star、MITinvariantlabs-ai/mcp-scan:uvx snyk-agent-scan@latest、SNYK_TOKEN、扫描覆盖的 MCP 与 skill 风险清单;据 GitHub 仓库页 2026-09 约 3.0 千 star、Apache-2.0--guardrail-provider LlamaFirewall 的一手说明;据 GitHub 仓库页 2026-09 约 224 star、Apache-2.0direct-poisoning.py / shadowing.py / whatsapp-takeover.py,本期攻击链描述的证据来源docker mcp gateway run、profile 与逐工具启停、docker mcp secret;据 GitHub 仓库页 2026-09 约 1.6 千 star、MIT--read-only / GITHUB_READ_ONLY、--toolsets / GITHUB_TOOLSETS 与默认工具组清单;据 GitHub 仓库页 2026-09 约 3.29 万 star、MIT.mcp.json 批准行为、--strict-mcp-config、requiresUserInteraction、组织对连接器工具的 ask / blocked 控制mcp__* deny glob、allow glob 必须锚定 server 段、带括号的 mcp__ 规则被跳过与 --disallowedTools.mcp.json 就能加一个跑在沙箱之外的 MCP server」一句的出处--allowed-origins / --isolated 那几个旋钮,本期第三层在客户端侧的对照--read-only 这种外部开关,不能来自一句标注。