首页 / 资讯中心 / 文章详情

MCP Server 别急着上线,官方安全文档里的四件事你对照过没有

MCP Server 别急着上线,官方安全文档里的四件事你对照过没有 ★ FEATURED ARTICLE
本文是「Agent 工程实战」系列的第 19 篇属于工具链线。系列索引21 篇阅读路线Agent 放权前先补权限、出站、发现这三道口子摘要MCP Server 从本地 stdio 搬到服务器上之后“能跑和能给别人调是两回事。官方把授权标成可选但强烈建议”我照着规范盘了四件事鉴权、最小权限、审计、限流每件都有官方依据和落地动作文末附一张可直接对照的上线前检查清单。文章目录先交代我为什么写这个生产化四件事一张表先看清全貌第一关 鉴权token 校验的三件事第二关 最小权限scope 别一把梭第三关 审计谁在什么时间调了什么工具第四关 限流按用户算别按 IP 算上线前检查清单哪些场景可以不用这套回头看先交代我为什么写这个写完上一篇 MCP Server 改造笔记有读者问Server 接进去了然后呢然后就是上线。MCP Server 挂在本地 stdio 上跑的时候安全基本不用想——能碰到它的只有本机进程。一旦包成 HTTP 服务放到服务器上它就是个网络服务了谁连得上、能不能调工具都是要回答的问题。官方授权教程里有句话很关键我原样摘一下Authorization 教程原文授权对 MCP Server 来说是 optional但 strongly recommended后面列了五个场景——涉及用户数据、要审计谁干了什么、需要用户同意、企业环境、想按用户做限流或用量跟踪。我对照了一圈结论很直接只要你的 Server 打算让别人调几乎必然命中其中一两条。所以这篇不是讨论要不要安全而是把官方规范里跟生产环境相关的部分拆成四件事每件给出官方依据和落地动作。先说明这篇的性质内容基于 2026-07-28 版 MCP 规范文档官方文档站当前正式版快照链接里都带这个版本号注意协议握手字段里的 protocolVersion 是另一套编号SDK 1.30.0 实测返回 2025-11-25这是协议版本号跟文档快照版本是两个维度的东西正文后面用到协议版本号的地方以实测为准。官方表述都能在文档原文找到凡是我自己的做法或判断我会标出来不混着说。生产化四件事一张表先看清全貌关卡要解决的问题官方依据落地动作鉴权谁有资格连你的 ServerAuthorization 规范 OAuth 2.1401 引导授权流程token 校验三件事有效 / aud / scope最小权限连上来之后能干什么Scope Minimization 章节渐进式 scope别用通配审计干了什么、谁干的、什么时候“audit who performed which actions”记录工具调用 日志脱敏限流一个人能调多少“rate limiting or usage tracking per user”基于 token 用户维度做配额这张表是本文的地图。下面按顺序过。图为四件事的整体关卡示意跟上面表格一一对应下面的每一关都会讲官方怎么说 落地动作。第一关 鉴权token 校验的三件事MCP 的授权流程基于 OAuth 2.1官方把它拆成六步。完整链路画出来是这么个走向MCP Client 授权服务器 MCP Server │ │ │ ▼ ▼ ▼ 1.发起连接 ─────────────────────────►401 受保护资源元数据 resource_metadata │ ▼ 2.拉取受保护资源元数据找到授权服务器──────────────────► │ ▼ 3.拉取授权服务器元数据 ◄──────────────authorization_endpoint / token_endpoint │ ▼ 4.客户端注册预注册 / 动态注册 DCR │ ▼ 5.用户授权浏览器 consent PKCE→ 拿授权码换 token │ ▼ 6.带 token 请求 ──────────────────────────────────────────► 校验 token 后放行为什么要看这张图它把鉴权不是 Server 单独的事摆清楚了。Server 在最后一步接 token、验 token前面几步都是 OAuth 流程在铺垫。作为 Server 方你要处理的核心落在第 6 步——校验。如果你的 Server 还没接授权客户端第一次连上来你回 401 并告诉它去哪拿授权信息。官方给的响应是这个形状Authorization 教程原文HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realmmcp, resource_metadatahttps://your-server.com/.well-known/oauth-protected-resource为什么要看这段它是鉴权的入口协议——客户端靠 401 里带的 resource_metadata 去发现授权服务器、走后面的流程。Server 收到未认证请求如果直接返回别的状态码客户端不知道往哪走流程就断了。token 到了 Server 手里怎么验证有两条路官方文档都讲了验证方式怎么工作适合谁Token IntrospectionRFC 7662把 token 发回授权服务器查是否有效返回 active / scope / exp 等不想自己维护验签密钥的 Server本地 JWT 校验用授权服务器的公钥验签再校验 aud / exp / scope 等字段想省一次网络往返的 Server两条路都可以但无论走哪条有三件事逃不掉。第一件是 token 本身有效签名对、没过期。第二件是这个 token 是发给我的——也就是 audienceaud校验。第三件是权限够不够scope下一关说。第二件最容易被漏掉。官方把只收 token 不验证它是发给谁的点名成反模式叫 Token Passthrough是 MUST NOT 级别的要求Security Best Practices 原文原话“MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server.”翻译过来就是不是发给你这个 Server 的 token你不能收。为什么这么严重因为如果 Server 接受任意有效 token 并拿它去调下游 API等于让客户端绕过你这层的限流、审计和访问控制直接以你的身份访问下游——你成了被借道的代理。官方文档列的风险包括安全控制被绕开、审计失效、信任边界被打破。官方 TypeScript 教程里给了完整的校验实现我把最核心的一段贴出来官方示例仓库前面 introspection 验签那段略去了只留 aud 判断这一段代码顺序和官方一致——先查 aud 缺失再取 audiences最后逐个比对if(!data.aud){thrownewError(Resource indicator (aud) missing);}constaudiences:string[]Array.isArray(data.aud)?data.aud:[data.aud];constallowedaudiences.some((a){try{returncheckResourceAllowed({requestedResource:a,// token 里带的 audconfiguredResource:mcpServerUrl,// 我这个 Server 的地址});}catch{returnfalse;}});if(!allowed){thrownewError(None of the provided audiences are allowed. Expected${mcpServerUrl}, got:${audiences.join(, )});}为什么要看这段checkResourceAllowed是官方 SDK 自带的函数做的是精确匹配——token 的 aud 和 Server 地址对得上才放行。官方教程里签发的 tokendecoded 之后长这样照官方文档原样贴的{iss:http://localhost:8080/realms/master,aud:http://localhost:3000,sub:33ed6c6b-c6e0-4928-a161-f2f69c7a03b9,scope:mcp:tools,exp:1755540817}注意 aud 字段——http://localhost:3000是官方演示 Server 的地址。这个字段就是这 token 是发给谁的答案。下面这些字段的值都照官方文档原样展示时我略掉了 iat / auth_time / jti / azp / sid / typ 这些辅助字段想看完整结构的去官方教程翻。它的匹配逻辑是精确的三种情况一眼能看清token 的 audServer 配置的地址结果原因http://localhost:3000http://localhost:3000放行精确匹配http://localhost:4000http://localhost:3000拒绝是发给别的服务的没有 aud任意拒绝校验逻辑直接抛Resource indicator (aud) missing这张表不是编的是checkResourceAllowed的函数语义——精确匹配配不上就返回 false。// 运行行为按官方校验逻辑正常走查 陪跑文实测对照 带 audhttp://localhost:3000 的 token → 校验通过继续处理请求正常逻辑 带 audhttp://localhost:4000 的 token → 抛 None of the provided audiences are allowed 不带 aud 的 token → 抛 Resource indicator (aud) missing // 最后一行错误 aud 被拒绝的完整链路陪跑文里用两个 Server 实测跑过漏 aud 的返回 200 放行做 aud 校验的返回 401这里多说一句aud 校验这一步本地开发时最容易偷懒跳过因为你自己签的 token、自己验签怎么都能过。等 Server 真的放到公网别的客户端拿它自己的 token 来调你的服务缺了 aud 校验的 Server 才暴露问题——我把这个场景搭成实验实测了一遍结果在陪跑文里同一个 token 两个 Server一个 200 放行一个 401 拒绝这里先埋个钩子。第二关 最小权限scope 别一把梭token 校验的第三件事——scope——单独拎出来说因为它是能干什么的边界。官方 Scope Minimization 章节的核心主张是渐进式一开始只给最小 scope官方举例mcp:tools-basic这类只含只读/发现类操作等客户端真要干高风险操作时再通过 WWW-Authenticate 的 scope 参数做阶梯式升级。同时点名了几个反模式把 scopes_supported 里所有 scope 全列出来、用通配 scope*、all、full-access、把不相关的权限捆绑在一起。给 MCP Server 排权限的时候最容易图省事搞一个 scope 覆盖所有工具一个 token 全都能调。等你真到要回答某个操作是谁发起的这种问题时就会发现一个 scope 全包根本没法区分——日志里只能看到调了工具看不到用的是哪档权限。官方风险清单里点名了这条原话是 “single omnibus scope masks user intent per operation”——单一全量 scope 掩盖了每次操作的真实意图。所以 scope 要按工具类别拆开只读的归只读写的归写该升级时再升级。scope 这关的落地动作就一句别让一个 token 能干所有事。拆到什么粒度取决于你的 Server 暴露了哪些工具——纯查询类一个 scope写操作一个 scope管理类操作单独一个。第三关 审计谁在什么时间调了什么工具授权不是装个门就完事门里发生的事得能查。官方把audit who performed which actions直接列为该上授权的场景之一就是这个意思。落到 MCP Server 上审计就是三件事记录工具调用谁token 里的用户/客户端标识、什么时间、调了哪个工具、调用的入参是什么。这条是事故回溯的地基。入参里也可能混着敏感字段手机号、证件号这类记录前先过一遍评估——能去掉的去不能去的截断跟下面这条脱敏是一件事的两面。日志脱敏官方原话是Never log Authorization headers, tokens, codes, or secrets——Authorization 头、token、密钥一律不进日志。排查问题的时候最容易顺手把完整请求头打进日志token 就这么裸奔在日志文件里日志一旦外泄等于凭据外泄。错误信息分层返回给客户端的是通用错误“调用被拒绝”详细原因“aud 不匹配期望值 xxx实际值 yyy”只进内部日志并带一个 correlation ID 把前后端日志串起来。排查时拿一个 ID 就能把一次请求的完整链路捞出来又不把内部细节暴露给调用方。这三条是老 HTTP 服务的基本功放到 MCP 上一样适用——工具调用就是你的接口调用只是调用方从浏览器换成了 agent。第四关 限流按用户算别按 IP 算官方在什么时候该上授权的清单里明确写了你想按用户做限流或用量跟踪的时候。这句话点了一个常见误区——限流按 IP 做在 MCP 场景下基本没用。调用方是云上的 agentIP 可能是共享的、会变的按 IP 限流要么误伤一大片要么形同虚设。MCP 的限流要跟授权绑定token 里有用户身份sub限流按这个维度做——每个用户每段时间能调多少工具、能调多少次。这样才限得准。这块官方规范没有给现成代码——规范只给了应该这么做的边界具体实现各家自己来。一种不依赖外部网关的做法是在工具调用入口做一层配额检查从校验通过的 token 里取用户标识查一下这个窗口内的调用计数超了就拒绝。很朴素的计数限流够用就行// 一种实现思路不是官方推荐做法asyncfunctionrateLimit(token,action){constusertoken.sub;// 从校验通过的 token 里取用户constkeymcp:${user}:${action}:${nowWindow()};constcountawaitcounter.get(key);// 窗口内已调次数if(countLIMIT_PER_WINDOW){return{allowed:false,retryAfter:WINDOW_SECONDS};}awaitcounter.incr(key);return{allowed:true};}为什么要看这段MCP 场景下限流的关键是限到人token 里的 sub 是现成的用户标识窗口计数是最朴素的实现理解这段也就理解了这个思路。想上生产级的限流网关/API 管理那套方案可以直接套过来不用自己造轮子。// 运行行为按上面逻辑推导 窗口内计数未超限 → allowed: true继续处理 窗口内计数达到限额 → allowed: false并给出窗口剩余秒数让客户端稍后重试上线前检查清单前面四关讲完落到动作上就是一张表。按优先级排了顺序从上往下过一遍每一行都是是/否#检查项没做的后果1未认证请求返回 401 WWW-Authenticate含 resource_metadata客户端无法走授权流程2接入授权服务器自建/第三方走 OAuth 2.1 流程Server 裸奔谁都能连3token 校验三件事都做有效 aud 匹配 scope 够不是发给你的 token 也能进陪跑文已实测漏 aud 的 Server 对错发 token 返回 200 放行并建会话做 aud 校验的返回 4014scope 按工具类别拆分不用通配一个 token 全权限审计失效5工具调用进日志谁 / 何时 / 哪个工具 / 入参入参先做敏感字段评估出事了查不了6日志不含 Authorization 头 / token / 密钥日志外泄 凭据外泄7对外报通用错误内部记 correlation ID内部细节暴露给调用方8按 token 用户维度做限流 / 配额限流形同虚设9生产环境强制 HTTPStoken 在传输中被截获这张表可以直接拿走用。真到上线那天对着逐条打勾比临时翻规范快。哪些场景可以不用这套说完了也泼盆冷水不是所有 MCP Server 都要上全套。本地 stdio 单机自用Server 跑在本机、只有你自己的客户端连OAuth 这套直接跳过凭据走环境变量就行。官方对 stdio 的授权建议就是可以用环境变量/第三方库内嵌凭据不强制 OAuth。内网单一客户端公司内部就一个系统在调走内部网关 TLS 白名单更合适OAuth 的收益不明显。对外服务别人家的客户端要连你的 Server——这才是本文四件事的主场一条都别省。判断标准就一条这个 Server 被不归你管的调用方使用时四件事都要过只在自己地盘里转可以按需裁剪。再交代版本时效本文基于 2026-07-28 版 MCP 规范文档配套的协议版本号握手时的 protocolVersion由 SDK 决定实测 modelcontextprotocol/sdk 1.30.0 返回的是 2025-11-25。OAuth 授权和 audience 校验这套机制是稳定约定短期不会有颠覆性变化但 SDK 的 API 形态一直在迭代具体函数名、协议版本以你用的 SDK 当前版本为准。文末陪跑文的实验输出也是用这套环境Node 25 SDK 1.30.0实跑出来的。回头看官方安全文档给的是一套基线不是银弹。四件事里有三件鉴权、最小权限、审计官方都给了明确机制剩下限流只给了方向落地靠工程。对大多数把 MCP Server 放上线的团队来说最大的风险往往不是某一关没做而是以为做了——比如加了 token 校验但没验 aud门装上了却没确认钥匙是配给谁的。「Agent 工程实战」系列路线工具链线MCP/测试脚本上一篇18 Agent 调 MCP 一个都没碰到下一篇20 加 token 就安全了吗系列索引21 篇阅读路线Agent 放权前先补权限、出站、发现这三道口子专栏Agent 工程实战如果这篇对你有用欢迎收藏想追更可以关注「Agent 工程实战」专栏。
阅读完成 · 觉得有帮助?
咨询建站