1. 从一次 403 说起:Claude Code 调 MCP 工具为什么被拒
如果你正在用 Claude Code 作为 MCP 客户端,去调用自己写的待办事项 MCP Server,然后发现read:todos、create:todos都能跑,唯独delete:todos一直返回 403,那你不是一个人。我最近帮朋友排查这个问题,前后折腾了两个多小时,最后发现根因既不是令牌没带,也不是 Server 写错了,而是认证和授权这两层被混在一起看了。
先把概念拆清楚。MCP Server 的权限控制分两层:认证(Authentication)解决“你是谁”,授权(Authorization)解决“你能做什么”。未认证的请求应该返回 401,并在WWW-Authenticate头里指向资源元数据地址(通常是/.well-known/oauth-protected-resource);令牌本身要验签名、有效期和 audience;而工具调用前还要校验 scope,比如read:todos、create:todos、delete:todos,scope 不够才回 403。
问题就出在这里:401 和 403 是两种完全不同的失败。401 说明令牌压根没被认可,403 说明令牌被认可了,但它没有delete:todos这个权限。很多人看到 403 第一反应是“令牌是不是没配对”,于是反复检查 Key、Base URL、请求头,结果方向从一开始就偏了。
这篇就按“先分清 401 和 403,再逐层排查”的思路,把 Claude Code 的 MCP 配置、TaoToken 的模型通道接入、以及 MCP Server 侧的 scope 校验串起来讲一遍。适合已经在写 MCP Server、并且用 Claude Code 当客户端调工具的同学。读完之后,你应该能自己判断:这次 403 到底是令牌没带对,还是 scope 没授。
2. 前置准备:TaoToken 通道与 MCP Server 的角色分工
在动手改配置之前,先把两件事分清楚,不然后面会一直绕。
第一件事是模型通道。Claude Code 本身要调用大模型来推理、决定调哪个工具,这条链路走的是模型 API。你可以打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册账号,然后在控制台创建一个 Key。这个 Key 是给 Claude Code 调模型用的,Base URL 填https://taotoken.net/api,注意不带/v1,也不要加任何 UTM 参数。Key 由 TaoToken 发放,负责的是“模型能不能被调用”这件事。
第二件事是MCP Server 的授权体系。你的待办 MCP Server 是资源服务器(Resource Server),它不负责颁发令牌,只负责验证和使用授权服务器发出来的访问令牌。令牌里带着 scopes,Server 在工具处理逻辑里检查 scope 是否包含delete:todos。这一层跟模型通道是独立的,别把 TaoToken 的 Key 当成 MCP 的访问令牌用,两者不是一回事。
所以整体链路是这样的:Claude Code 用 TaoToken 的 Key 调模型 → 模型决定调用delete:todos工具 → Claude Code 作为 MCP 客户端,带着 MCP 访问令牌去请求你的 MCP Server → Server 验令牌、验 scope → 通过则执行,不通过则 401 或 403。
把这条链路画清楚之后,403 的定位就简单了:模型通道是通的(否则工具根本不会被触发),问题一定在 MCP 令牌或 scope 上。
| 层面 | 负责方 | 关键配置 | 失败表现 |
|---|---|---|---|
| 模型通道 | TaoToken | Base URLhttps://taotoken.net/api+ Key | 模型无响应、鉴权失败 |
| MCP 认证 | 授权服务器 + MCP Server | 令牌签名、有效期、audience | 401 |
| MCP 授权 | MCP Server | scope 校验(如delete:todos) | 403 |
3. 可复制配置:Claude Code 的 MCP 与模型通道怎么填
这一节给可直接抄的配置。先配模型通道,再配 MCP Server,顺序别反。
3.1 配置 TaoToken 模型通道
Claude Code 读取环境变量来定位模型服务。你可以在 shell 配置文件里加上:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="你的TaoToken Key"如果你用的是 Claude Code 的配置文件方式,也可以写进对应的 settings 里,核心就是 Base URL 指向https://taotoken.net/api,Key 用 TaoToken 控制台创建的那一串。这里再强调一次:不要写成https://taotoken.net/api/v1,多这一层路径会导致请求打不到正确的端点。
配好之后,先单独验证模型通道能不能通,别急着上 MCP。跑一个最简单的对话请求,确认模型有正常返回,再往下走。
3.2 配置 MCP Server 连接
Claude Code 的 MCP 配置一般放在项目或用户级的配置里,形如:
{ "mcpServers": { "todo-server": { "url": "https://your-mcp-server.example.com/mcp", "headers": { "Authorization": "Bearer ${MCP_ACCESS_TOKEN}" } } } }这里的MCP_ACCESS_TOKEN是从你的授权服务器拿到的访问令牌,不是 TaoToken 的 Key。很多人第一次配的时候会把两个 Key 搞混,结果 MCP Server 收到一个模型通道的 Key,验签名直接失败,返回 401。如果你现在拿到的是 403,说明令牌至少通过了认证层,方向是对的,问题在 scope。
3.3 MCP Server 侧的 scope 校验逻辑
Server 端在工具处理函数里做校验,伪代码大概是这样:
def handle_delete_todo(token, todo_id): claims = verify_token(token) # 验签名、有效期、audience if "delete:todos" not in claims.get("scope", "").split(): audit_log(claims["sub"], "delete:todos", todo_id, "denied") return Response(status=403, body={"error": "insufficient_scope"}) audit_log(claims["sub"], "delete:todos", todo_id, "allowed") return do_delete(todo_id)注意audience的校验:令牌的 audience 必须和你的 MCP Server 标识一致。如果授权服务器发令牌时 audience 填的是别的服务,即使 scope 里有delete:todos,认证层也应该拒绝。这一步是防令牌被挪用到其他服务的。
4. 验证请求:从 401 到 403 再到 200 的完整过程
配置改完,按顺序验证,别跳步。
第一步,先发一个不带令牌的请求,确认 Server 返回 401,并且WWW-Authenticate头里指向了资源元数据地址:
curl -i https://your-mcp-server.example.com/mcp \ -H "Content-Type: application/json" \ -d '{"method":"tools/call","params":{"name":"delete:todos"}}'预期看到HTTP/1.1 401 Unauthorized,以及类似WWW-Authenticate: Bearer resource_metadata="https://your-mcp-server.example.com/.well-known/oauth-protected-resource"的头。这一步确认认证入口是对的。
第二步,访问资源元数据地址,确认authorization_servers和scopes_supported里确实包含delete:todos:
curl https://your-mcp-server.example.com/.well-known/oauth-protected-resource如果scopes_supported里没有delete:todos,那客户端根本申请不到这个 scope,后面必然 403。这是最容易被忽略的一环。
第三步,带上令牌请求,观察返回。如果还是 403,去看 Server 的审计日志,日志里应该记录了用户、操作、参数、时间戳。对照日志确认:令牌的 scopes 里到底有没有delete:todos,audience 是不是本 Server 的标识。
第四步,如果 scope 缺失,回到授权服务器,用最小权限原则重新申请一个包含delete:todos的令牌,再重试。成功时应该返回 200 和删除结果。
整个过程中,401 和 403 的边界要记牢:401 是“令牌没被认可”,403 是“令牌被认可但权限不够”。分清楚这两个,排查效率会高很多。
5. 本篇常见错排查:delete:todos 一直 403 的几种原因
把踩过的坑列一下,对照着查。
原因一:scope 字符串拼写或分隔符不对。有的授权服务器用空格分隔 scope,有的用逗号。如果你的校验代码用split()默认按空白分,而令牌里是逗号分隔,delete:todos就永远匹配不上。先打印出令牌里的原始 scope 字符串看一眼。
原因二:audience 不匹配。令牌的 audience 指向了另一个服务,认证层可能放过了(如果校验不严),但授权层按本 Server 的规则判定时发现不对,回 403。检查授权服务器发令牌时 audience 参数填的是什么。
原因三:资源元数据里没声明 delete:todos。客户端申请 scope 时是照着scopes_supported来的,元数据里没有,客户端就不会申请,令牌里自然没有。
原因四:把 TaoToken 的 Key 当成了 MCP 访问令牌。这种通常表现为 401,但如果你 Server 的认证逻辑写得宽松,可能漏到授权层变成 403。确认Authorization头里带的是授权服务器发的令牌。
原因五:令牌过期但缓存没刷新。有效期过了,认证层应该回 401,但如果客户端缓存了旧令牌且 Server 校验有漏洞,可能表现异常。清缓存重新取令牌。
原因六:审计日志没开,排查全靠猜。没有日志你根本不知道令牌里有什么。先把用户、操作、参数、时间戳的审计记录补上,再排查。
提示:排查顺序永远是先看 401 阶段的资源元数据,再看 403 阶段的 scope 和 audience,最后才动配置。顺序反了会浪费大量时间。
6. 收窄权限与后续接入
跑通之后,别急着把 scope 开大。按最小权限原则,给不同角色分配不同 scope:普通用户只给read:todos和create:todos,删除操作单独授权。这样即使令牌泄露,影响面也可控。
如果你还想继续调模型通道、验证不同模型在工具调用上的表现,可以到模型对话页面直接试;如果是要长期跑编码任务或 Agent 流程,Coding Plan 更适合;接入过程中遇到 Key 或端点问题,去 API Keys 页面和接入文档对照检查。这几个入口都在 TaoToken 站内,按需取用即可。
最后留一个实用习惯:每次改完 MCP 配置,先用 curl 手动走一遍 401 → 元数据 → 带令牌请求这三步,确认链路通了再让 Claude Code 去调。手动验证花两分钟,能省掉后面半小时的瞎猜。