跳转到内容
~/tosaki
返回

让仅支持 API Key 的 MCP 服务成功"骗"过 Claude 和 ChatGPT

编辑页面

最近,我一直在为 OpenViking 贡献 MCP(Model Context Protocol)相关的代码。OpenViking 的愿景不仅仅是做一个 Coding 助手的记忆插件,它想成为一个独立、全面、统一的记忆平台

为了实现这个目标,我为它写了完整的 MCP Server,暴露了 searchreadstoreforget 等工具。因为项目还没完整支持 OAuth 认证,图省事起见,我直接沿用了其 REST API 的 Authorization: Bearer <api-key> Header 鉴权方案。

一开始,这完全不是问题。Cursor、Trae、Claude Code、Manus……市面上主流的 Coding 平台对 Header 鉴权都非常友好,配置文件里加一行就搞定了。但在 OpenViking 的蓝图里,只拿下 Coding 平台是不够的,我们更希望能无缝接入受众更广的 ChatGPT 和 Claude 的网页端及桌面端

然后,我就结结实实地撞墙了。

令人沮丧的“高墙”

调研了一圈后,我发现情况非常尴尬:

起初,我对 OAuth 并没有太深的概念,天真地以为它不过是“换个地方塞 Header Key”。等深入了解后才发现,OAuth 2.1 的认证流程和简单的 API Key 完全是两个维度的东西。

这就意味着:如果你的 MCP Server 只接受 API Key,大厂的官方客户端你根本连不上。

走不通的捷径

为了不改动后端,我脑子里闪过好几个“走捷径”的念头,但都被现实无情打脸:

最终解法:搭一座恰到好处的桥

既然捷径走不通,后端又不想动,那就只能在前面加一层代理了。思路很清晰:

  1. 面向 MCP 客户端(Claude/ChatGPT):实现一个极其标准的 OAuth 2.1 流程(PKCE、动态客户端注册、Token 端点一个不少),在客户端看来安全性毫无问题。
  2. 面向上游 MCP Server:拿到 Token 后,在代理层解密出用户的 API Key,重新组装成标准的 Bearer Token,透传给源服务器。

用户的 API Key 怎么来?在 OAuth 的授权流程中,我会弹出一个 Web 授权页面,让用户手动粘贴 API Key,然后把它加密塞进 OAuth Token 里。

这条路不仅走得通,而且可以做成一个通用工具! 任何用 API Key 鉴权的 MCP Server,只要套上这层代理,都能瞬间“伪装”成支持 OAuth 的服务。

于是,就有了这个项目:MCP-Key2OAuth

架构设计:像做短链接一样创建代理

为了让它足够好用,我采用了类似“短链接服务”的核心模型——Slug。 你只需要在网页上把的目标 MCP 服务器地址填进去:

MCP-Key2OAuth 控制台

系统会返回给你一个专属的代理端点(比如 .../a3x9k2m7p1q4/mcp)。把这个端点粘贴到 Claude 客户端里,接下来的一切都是全自动的:客户端会自动发现 OAuth 端点,弹出页面让用户输入 API Key,然后顺滑地建立连接。

整体链路如下:

MCP Client ──(走 OAuth 2.1)──> Key2OAuth Worker ──(转 Bearer Token)──> Your MCP Server

                            弹出一个 Web 页面
                            让用户输入 API Key

极简的技术选型与“动态路由”魔法

项目搭在 Cloudflare Workers 上(Serverless 边缘节点,冷启动极快)。核心依赖了 Cloudflare 开源的 @cloudflare/workers-oauth-provider,它帮我扛下了 PKCE 验证、DCR(动态注册)、CORS 等 80% 的脏活累活;再配合 Hono 处理路由和页面渲染。

开发过程中最大的挑战是路由问题。 每个 Slug 都有自己的端点路径(/{slug}/mcp),但 OAuth 库要求在初始化时就必须传入固定的 apiRoute 来做鉴权拦截。Slug 是用户随时动态创建的,代码启动时我上哪知道有哪些路由?

最后我用了一个看起来很“浪费”但极其有效的解法——每个请求到来时,动态实例化一个 OAuthProvider

// 伪代码演示
const slug = extractSlugFromPath(url.pathname);
let apiRoute = '/__internal_no_match__';

if (slug) {
  const config = await env.SLUG_KV.get(`slug:${slug}`);
  if (config) apiRoute = `/${slug}/mcp`;
}

// 动态创建实例
const provider = new OAuthProvider({
  apiRoute,
  // ... 其他配置
});
return provider.fetch(request, env, ctx);

实际上这并不浪费,因为 OAuthProvider 的构造函数是纯函数,没有 I/O 操作,所有持久化状态都在 Cloudflare KV 里,实例本身是完全无状态的。 通过这种方式,从客户端发起请求、自动注册、跳转授权页,到最后完成 Token 交换,大部分流程都交给了库自动处理。我只需要写那 20% 的“粘合代码”(授权页渲染和最后一步的代理透传逻辑)。

踩坑实录:纸上得来终觉浅

整个流程跑通前,我踩了几个极其搞心态的坑:

  1. UserId 里的冒号引发的血案
    OAuth 库生成的 Token 格式是 userId:grantId:secret,按冒号切分成 3 段。我一开始顺手把 UserId 设置成了 ${slug}:${keyHash}。好家伙,Token 变成 4 段了,Token Exchange 时直接抛错 Invalid authorization code format。改成下划线连接才搞定。这种坑,写单元测试根本测不出来,只有完整跑完真实链路才会暴露。

  2. 想省掉授权页?又被源码打脸
    我曾再次幻想:能不能让用户把 API Key 填在 Claude UI 的 Client Secret 字段里,我在 /token 端点把它偷出来,这样连授权页都不用弹了?
    翻了库的源码后我死心了:库在验证 client_secret 时,做完 SHA-256 Hash 比对后,直接就把原文丢弃了!回调函数里根本拿不到原文。而且动态注册时,secret 是库生成的随机字符串,用户压根没机会塞自己的 Key。

  3. Wrangler CLI 的玄学
    写一键部署脚本时,我想用 wrangler kv namespace create --json 解析返回的 namespace ID。结果文档里写着有的 --json flag,实际上根本不存在。最后只能靠粗暴的 grep '"id": "xxx"' 来提取。

安全模型:诚实比“吹牛”更重要

一开始在规划时,我想实现一个后端完全不存储 API Key 的“零信任”中转工具。但随着对协议的了解和开发的深入,我发现这几乎不可能。最终的方案更类似于:

因此,在页面上我挂出了一条极为醒目的免责声明:

“This is a shared public deployment. The operator can technically decrypt API keys in OAuth tokens. For sensitive keys, self-deploy your own instance in under 5 minutes (Cloudflare Workers free tier).”

诚实的信任模型远比虚幻的安全感更有价值。

当然,基础的防护我都做足了:防 SSRF(拦截内网 IP 请求)、防枚举(12位密码学随机 Slug ID)、即时撤销(代理不缓存,源站一撤销 Token 立马失效)。同时,我在 GitHub 提供了极为简单的一键部署到 Cloudflare 按钮,有顾虑的开发者完全可以在 5 分钟内建一个自己的专属代理。

总结与未来计划

整个项目算下来只有 520 行 TypeScript,8 个源文件,部署在一个 Cloudflare Worker 上。

调研下来,市面上虽然有 mcp-front 这类工具,但它们走的是 IdP(身份提供者)路线,更适合企业内部搞 SSO 登录。而我这个项目,生态位非常精准:帮个人和小团队,在不改动一行后端代码的前提下,让仅支持 API Key 的 MCP 服务快速上线,兼容各种严苛的客户端。

这算是个很棒的开始,目前它已经完美解决了 Claude、Cursor 等客户端的接入问题。

至于 ChatGPT?它的“开发人员模式”依然让人头疼。但有了 MCP-Key2OAuth 这个架构打底,接下来我们可以顺理成章地做一个 OpenViking 专有的代理产品。当代理不再是“通用工具”,而是有明确应用场景(比如“为 ChatGPT 提供长期记忆”)的产品时,我们就有极大的几率通过 ChatGPT Apps 平台的审核。

有时候,面对不兼容的两个世界,最好的解决方案不是重构现有系统,而是造一座足够轻巧、恰到好处的桥。

Screenshots

ChatGPT 接入演示

Claude 接入演示


编辑页面
分享到:

下一篇
台湾游记