与 invalid_target 排障全解析)
OpenWork 第三方 MCP 客户端 OAuth 接入指南资源指示符RFC 8707与 invalid_target 排障全解析【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork本文围绕 OpenWork 部署的/mcp/agent公共 OAuth 端点完整讲解第三方 MCP 客户端如何完成 RFC 9728 受保护资源发现、PKCE 授权码流程与resource参数RFC 8707的正确传递并结合 den-api 源码与回归测试深入剖析invalid_target报错的根因、修复方法与安全上报规范。读完你将掌握一套可直接落地的 MCP OAuth 接入清单和排障方法论。1. 背景为什么第三方 MCP 客户端需要走 OAuth 接入OpenWork 的 Den API 将组织能力以 MCPModel Context Protocol工具形式暴露给 AI 编码代理如 OpenCode、Claude Code、Codex 等与桌面端。仓库中 MCP 暴露策略 明确了工具面所有带API Keys、Connectors、Workers等标签的 Den API 产品面默认允许暴露而Admin、Authentication、System、Webhooks等标签被有意排除避免管理面与鉴权管道被当作 Agent 工具调用。对于第三方非首方桌面端MCP 客户端接入点是一个公共 OAuth 端点/mcp/agent。该端点只接受携带公共 OAuth 访问令牌的请求与首方桌面端的/mcpopaque token 会话耦合走完全不同的认证路径。认证实现 中明确区分了这两种 token 源JWT 型公共令牌只能用于agent路由且受众必须严格匹配唯一的DEN_MCP_OAUTH_RESOURCE而首方 opaque 令牌则走会话存活性校验。因此第三方客户端接入时必须严格遵循下述 OAuth 流程不能复用桌面端的令牌格式。2. 接入前提正确配置/mcp/agent端点2.1 端点的组成OpenWork 部署对外提供 MCP 服务时公共 OAuth 接入端点是部署 URL 下的/mcp/agenthttps://api.example.com/mcp/agent配置客户端时需要注意使用部署后的完整 URL包括任何反向代理前缀如/api/den不要省略路径段不要替换成 Web 控制台Dashboard的 URL——web 前端只在根路径提供页面并不会在/mcp下提供 MCP 服务公共 OAuth 访问令牌就是为这个端点签发的不要在其它路由上使用。从源码看资源派生逻辑 区分了两种部署形态托管 Web 应用域名app.*、*.run.app或配置的DEN_WEB_APP_HOSTS会在 den-api 前面加/api/den代理前缀MCP 资源相应变为origin/api/den/mcp直接 API 域名则保持裸形态origin/mcp。而公共 OAuth 的/mcp/agent资源由 auth.ts 中的deriveDenMcpAgentResource基于env.apiPublicUrl派生。也就是说部署拓扑是否走 web 代理会决定实际可用的 MCP 端点路径配置客户端前应先确认自己的部署形态。2.2 公共令牌与资源绑定公共 OAuth 令牌只有一个合法受众DEN_MCP_OAUTH_RESOURCE即/mcp/agent资源。auth.ts 的认证上下文 中写明Public OAuth has exactly one allowed audience,DEN_MCP_OAUTH_RESOURCE(the advertised/mcp/agentresource), and those JWTs authenticate only on the agent route.也就是说oauthResources只允许agent路由持有这一个受众值令牌被绑定到唯一资源杜绝了多受众泛化。这是理解后续resource参数为何必须精确传递的关键。3. 五步接入流程从发现到调用官方文档给出的接入流程共五步下面逐一展开并结合源码说明每一步在服务端的落点。3.1 第一步发起未认证请求跟随WWW-Authenticate挑战向 MCP 端点发送一个不带令牌的请求GET /mcp/agent HTTP/1.1 Host: api.example.com服务端返回401并在WWW-Authenticate头中携带resource_metadata参数。由 auth.ts 中的 bearerChallenge 生成格式形如HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer errorinvalid_token, resource_metadatahttps://api.example.com/.well-known/oauth-protected-resource/mcp/agent, scopemcp:read mcp:write注意挑战头中同时给出了scopeMCP 请求所需作用域与resource_metadataURL对于合成端点https://api.example.com/mcp/agent发现 URL 为https://api.example.com/.well-known/oauth-protected-resource/mcp/agent。该路径由 mcpProtectedResourceMetadataUrl 生成取资源的 origin将路径替换为/.well-known/oauth-protected-resource后拼接原路径服务端同时在 agent.ts 注册了两种元数据路径/.well-known/oauth-protected-resource/mcp/agent与/mcp/agent/.well-known/oauth-protected-resource客户端应优先使用挑战头中给出的地址。3.2 第二步读取元数据发现授权服务器请求上一步得到的发现 URL无需认证会得到 RFC 9728 受保护资源元数据核心字段resource受保护资源的标识符即 MCP 端点对应的资源值后续要原样回传authorization_servers授权服务器标识符列表。要点授权服务器的 origin 可能与 MCP 端点不同属于正常现象按元数据指示继续发现即可不要把元数据 URL 或 issuer URL 当作resource值使用——它们不是受保护资源标识符。接着从授权服务器发现 OAuth 端点OpenWork 在 auth 路由 中于多处注册了 RFC 8414 授权服务器元数据与 OpenID Connect 发现文档如/.well-known/oauth-authorization-server、/.well-known/openid-configuration客户端可据此拿到authorization_endpoint、token_endpoint、registration_endpoint、issuer等。3.3 第三步动态客户端注册 PKCE 授权码流程使用发现到的注册端点OpenWork 支持 RFC 7591 动态客户端注册路径为/register或/api/auth/oauth2/register注册客户端回调 URI 必须是客户端实际的 callback。服务端对回调 URI 有严格校验oauth-client-policy.ts 实现如下规则必须是 HTTPS 回调或 HTTP 环回loopback回调如http://127.0.0.1:PORT/callback、http://localhost:PORT/callback含*.localhost、::1不允许带 fragment#特例cursor://anysphere.cursor-mcp/oauth/callback这类 RFC 8252 私有使用方案回调被显式放行因为 MCP 规范只允许 HTTPS/loopback但部分主流原生客户端只支持私有 scheme服务端以强制 PKCE S256 作为该场景的补偿控制。然后发起授权码流程 PKCES256并保持以下安全机制开启回调校验callback validationstate 校验state verification用户同意consent。源码中动态注册请求还会被 rewriteMcpClientRegistrationRequest 预处理校验回调 URI 白名单策略、规范化 scope再交给授权服务器。3.4 第四步两处都传resource参数最容易出错的一步授权 URL 查询参数和表单编码的令牌请求体中都必须且只能各传恰好一个resource参数值为第二步发现到的resource。只在注册时提供、或只放在浏览器 URL 里都不算数。对于合成端点https://api.example.com/mcp/agent编码后的参数为resourcehttps%3A%2F%2Fapi.example.com%2Fmcp%2Fagent服务端在 normalizeMcpOAuthResourceParams 中对两处统一校验缺失resource→ 返回invalid_targetMCP OAuth requests must include the protected resource.多于一个resource→ 返回invalid_targetmust include exactly one protected resource.值无法被 normalizeMcpOAuthResource 识别未知资源→ 返回invalid_targetnot recognized by this deployment.。normalizeMcpOAuthUrl授权端点与normalizeMcpOAuthRequest令牌端点分别在 路由注册处 调用该校验确保只有携带正确资源的请求才会进入真正的授权/换码阶段。这也解释了官方文档强调的Supplying it only during registration or only in the browser URL is insufficient.3.5 第五步Bearer 令牌调用与刷新拿到访问令牌后以Authorization Bearer 头形式调用 MCP 端点POST /mcp/agent HTTP/1.1 Host: api.example.com Authorization: Bearer access_token Content-Type: application/json {jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2025-11-25,capabilities:{},clientInfo:{name:my-client,version:1.0.0}}}服务端 verifyMcpRequest 的完整校验链包括读取 Bearer → JWT 签名/受众校验aud必须等于DEN_MCP_OAUTH_RESOURCE仅允许额外的userinfo受众→ scope 必须含mcp:read或mcp:write→token_useclaim 必须是mcp→ 资源 claim 匹配 → 主体user/org存在 → 授权存活性grant 或 session→ 组织成员关系仍然有效。任何一环失败都会得到对应的 401/403 与WWW-Authenticate挑战。刷新令牌时客户端也应在刷新请求中携带resource。不过 Den 提供了一条兼容路径——当刷新grant_typerefresh_token请求省略resource时normalizeMcpOAuthRequest 会为注册了 MCP scope 的客户端推断其单例受众并自动补上resourceDEN_MCP_OAUTH_RESOURCE。源码注释明确指出MCP clients should send resource on every token request, but some clients omit it while refreshing. Public MCP has exactly one valid audience, so defaulting only this grant preserves audience binding without widening it.但要特别注意这种推断仅适用于刷新阶段绝不意味着初始授权或换码阶段可以省略resource。服务端在授权/换码阶段是强制要求该参数的。4.invalid_target深度排障4.1 报错含义invalid_target说明 MCP OAuth 请求在resource参数上出了问题。依据 RFC 8707 §2原文文档引用此处不展开外部链接该错误覆盖缺失、未知、格式错误或其他无效的资源指示符。4.2 根因一完全没有传resource最常见的情况是通用 OAuth 客户端只支持在授权请求中附加额外参数却无法在令牌请求体中附加额外字段。这类客户端换码时漏掉了resource服务端直接返回{ error: invalid_target, error_description: MCP OAuth requests must include the protected resource. }官方文档给出明确结论改 scope 或重新注册客户端都无法补上这个缺失字段。唯一出路是升级客户端使其支持在令牌请求体中携带额外参数或改用实现了 MCP 资源指示符resource indicators语义的客户端。4.3 根因二资源值未知或重复未知资源传了resource但值不是本部署发现到的那个例如用了元数据 URL、issuer URL或任意自定义受众→invalid_targetnot recognized by this deployment.多个资源一次传了两个及以上resource→invalid_targetmust include exactly one protected resource.。正确做法只使用发现到的那个单例值不要添加任意受众更不要试图关闭服务端校验。服务端的受众白名单是收敛的仅DEN_MCP_OAUTH_RESOURCE一个公共受众认证校验 甚至要求 JWT 中除DEN_MCP_OAUTH_RESOURCE外最多只能出现userinfo受众。4.4 支持上报规范向支持团队提交报告时只记录以下最小信息集失败的阶段授权换码还是刷新/调用HTTP 状态码错误码如invalid_targetresource参数是否存在。严禁在报告中分享完整的授权 URL、Cookie、授权码、令牌、客户端密钥或租户标识符。5. 回归验证mcp-auth-rate-limit-recovery旅程仓库在 evals/specs/mcp-auth-rate-limit-recovery.e2e.test.ts 中提供了端到端回归证据覆盖官方文档描述的完整链路。测试关键断言如下从401挑战头解析resource_metadata并断言其等于${den.ref.apiUrl}/.well-known/oauth-protected-resource/mcp/agent对应源码行分别以缺失、未知、重复的resource发起授权与换码均被拒绝并返回invalid_target第 81、107 行用发现到的正确资源 同一个授权码完成换码再用令牌访问 MCP 端点成功。运行回归pnpm evals:e2e mcp-auth-rate-limit-recovery该测试同时验证了“missing, unknown, and repeated resources rejected at authorization and code exchange; no redirect or token was issued”这一行为是判断部署行为是否符合预期的权威手段。6. 接入自检清单检查项要求端点 URL使用部署 URL 反向代理前缀指向/mcp/agent不用 Dashboard URL发现从401的resource_metadata获取 RFC 9728 元数据资源值使用元数据中的resource不是元数据 URL / issuer URL授权授权码 PKCE S256回调必须是 HTTPS 或 loopback或白名单私有 schemeresource授权 URL恰好一个URL 编码resource令牌请求体恰好一个表单编码与授权时一致调用Authorization: Bearer access_token请求/mcp/agent刷新尽量带resource可依赖刷新兼容路径但初始授权/换码绝不省略排障缺参数/多参数/未知值 →invalid_target先查两处是否都传对7. 延伸阅读MCP 暴露策略与工具白名单了解哪些 Den API 面会成为 MCP 工具、哪些被拦截MCP 认证实现Bearer 挑战、JWT/opaque 双路径校验、资源与作用域校验链MCP 资源派生不同部署形态下/mcp、/mcp/agent资源与元数据 URL 的生成规则OAuth 路由与参数规范化注册、授权、换码三个阶段的resource强制校验与刷新推断逻辑回调 URI 策略HTTPS/loopback/私有 scheme 白名单与 PKCE 强制要求。【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考