面向自定义 MCP 客户端的 OAuth
如果你正在开发自己的 MCP 客户端(CLI、SDK、IDE 插件),本页将介绍我们的 OAuth 实现:我们支持的标准、各个端点、redirect_uri 规则,以及如何调试常见错误。Claude.ai 和 ChatGPT 用户不需要这页 — 请改用 Quickstart。
我们实现的标准
mcp-telegram.com 完全符合 MCP 生态系统所依赖的 OAuth 规范 — 没有专有扩展,也没有写死在客户端中的共享密钥。
- RFC 6749 — OAuth 2.0 Authorization Framework — 带 refresh 令牌的基础 authorization code grant
- RFC 7591 — Dynamic Client Registration — 任何客户端都可以通过 /oauth/register 自行注册,无需事先协调
- RFC 7636 — PKCE (Proof Key for Code Exchange) — 公共客户端必须使用 S256 challenge
- RFC 8252 — OAuth 2.0 for Native Apps — 支持临时端口灵活性的 loopback IP redirect URI
- RFC 8414 — OAuth 2.0 Authorization Server Metadata — 位于 /.well-known/oauth-authorization-server 的发现文档
- RFC 9728 — OAuth 2.0 Protected Resource Metadata — MCP 资源边界的发现
端点
使用 PKCE 的 authorization code 流程
标准的 RFC 6749 §4.1 + RFC 7636 流程。没有意外 — 如果你的 OAuth 库能处理"authorization code + PKCE",它就能正常工作。
注册你的客户端
向 POST /oauth/register 发送 { redirect_uris: ["http://127.0.0.1:<port>/callback"], client_name: "your-client-name" }。服务器返回 client_id 和 client_secret (对公共 PKCE 客户端来说密钥是可选的,但收到也无妨)。
生成 PKCE 对
生成一个 43–128 字符的随机 code_verifier,然后计算 code_challenge = BASE64URL(SHA256(code_verifier))。把 verifier 保存在本地,供第 4 步使用。
在浏览器中打开 /oauth/authorize
将用户重定向到 /oauth/authorize?response_type=code&client_id=...&redirect_uri=...&state=...&code_challenge=...&code_challenge_method=S256。服务器会显示一个二维码;用户在 Telegram 应用中扫描以完成认证。
接收 authorization code
扫描二维码后,服务器会带着 ?code=...&state=... 重定向到你的 redirect_uri。请验证 state 参数与你发出的值一致。
用 code 交换令牌
向 POST /oauth/token 发送 grant_type=authorization_code、code、redirect_uri、code_verifier、client_id。你将收到一个 access_token(TTL 为 10 年 — 见下面的 Token Lifetime)和一个 refresh_token。在 /mcp 请求中将 access_token 用作 Authorization: Bearer ...。
redirect_uri 匹配规则
我们严格遵循 RFC 8252 §7.3 和 §8.4。匹配算法取决于注册的 URI 是否为 loopback IP 字面量。
Loopback (http://127.0.0.1 或 http://[::1])
如果你注册的是 loopback URI,那么 authorize 时的 redirect_uri 在 scheme、host、path 和 query 上必须完全匹配 — 但端口可以不同。RFC 8252 §7.3 要求如此,因为原生客户端绑定的是操作系统分配的临时端口,在不同的运行之间可能会变化。
- 已注册的 http://127.0.0.1:36201/callback 与 authorize 时的 http://127.0.0.1:50000/callback 匹配 ✅
- 已注册的 http://127.0.0.1:36201/callback 与 http://127.0.0.1:50000/different-path 不匹配 — path 必须完全相同
- 已注册的 http://[::1]:36201/cb 与 http://127.0.0.1:36201/cb 不匹配 — IPv4 和 IPv6 loopback 是不同的标识符
HTTPS (https://your-domain.example/...)
必须做到逐字节完全匹配,包括 path、query 和端口 (RFC 6749 §3.1.2)。没有任何灵活性。
localhost (不推荐)
我们把 http://localhost 当作普通主机名处理 — 必须完全匹配,端口也不能灵活。RFC 8252 §8.3 建议使用 IP 字面量 (127.0.0.1 / [::1]) 而不是 localhost,因为 DNS 解析的行为依赖于具体实现。
PKCE 是必需的
每个 authorize 请求都必须带 code_challenge_method=S256。Plain method 会被拒绝。如果你完全跳过 code_challenge,/token 交换将在 code_verifier 校验时失败。
令牌生命周期
access_token 的 TTL 有意设置得很长(10 年),因为有些 MCP 客户端不能可靠地持久化 refresh_token。即便如此,refresh_token 仍然会被签发,并在每次 refresh 时轮转 (RFC 6819 §5.2.2.3)。用户可以随时在 /my/sessions 撤销所有令牌,或通过退出 Telegram 来撤销。用户也可以显式向 /oauth/revoke (RFC 7009) 发起 POST 请求来终止一个会话。
常见错误
/oauth/authorize 返回 HTTP 400 "Invalid redirect_uri"
redirect_uri 查询参数与此 client_id 注册的任何 URI 都不匹配。对于 loopback 客户端:检查 scheme + host + path + query 是否完全一致(只有端口是灵活的)。对于 HTTPS 客户端:需要完整的逐字节相等 — 注意尾部斜杠和 URL 编码差异。
HTTP 400 "Unknown client"
client_id 在我们的数据库中不存在。可能是你跳过了 /oauth/register 步骤,或者注册响应在你保存 client_id 之前就丢失了,又或者用户通过 /my/clients 撤销了该客户端。
客户端反复提示 "Needs Auth"
如果你的客户端持久化了 access_token 但没有持久化 refresh_token,那么 access_token 过期时就会出现这种情况。我们的 access token 之所以使用 10 年的 TTL,就是为了缓解这个问题;但如果你的客户端自己在更短的窗口内让令牌过期,你就需要持久化 refresh_token,或在需要时调用 /oauth/revoke + 重新认证。
已测试的客户端
确认可与我们的服务器互操作。如果你做了新东西并且能跑通,请发 PR 把它加进来。
- Claude.ai web — 通过内置的 MCP connector
- ChatGPT Apps — 通过内置的 MCP connector
- Hermes Agent — 开源 CLI MCP 客户端 (RFC 8252 loopback flow)
- Cursor MCP — IDE 插件 (RFC 8252 loopback flow)
- 任何符合 RFC 6749 + RFC 7591 + RFC 7636 + RFC 8252 的客户端都应该可以在不修改我们这端代码的情况下正常工作。
在一个 OAuth 连接中管理多个 Telegram 账号
自 v2.32.0 起,单个 OAuth 连接可以同时持有多个 Telegram 身份,并通过一次工具调用在它们之间切换——无需 Disconnect/Connect 循环,也无需重新注册客户端。
查看已连接的账号
调用 telegram-accounts-list。如果您的 primary(绑定到 OAuth 的)账号处于活动状态,将带有 ⭐ 标记显示,同时也会列出您添加的所有副账号。
添加另一个账号
调用 telegram-accounts-add,可选传入标签(例如 "testing")。该工具会返回一个一次性 URL(TTL 10 分钟)——在任意设备上打开它,并使用您想添加的 Telegram 账号扫描 QR 码。
切换活动账号
调用 telegram-accounts-switch,identifier 可为 label、@username、account_id 或 'primary'。下一次 telegram-* 工具调用将立即使用该账号。无需重新连接。
分离副账号
对副账号调用 telegram-accounts-remove identifier=…。Telegram 账号本身不会被登出——仅解除此处的绑定。primary 无法通过此方式移除;如需完全登出,请使用客户端的 Disconnect 流程。
每个 OAuth 连接都是隔离的:您通过 Hermes 添加的账号在 Claude.ai 或 ChatGPT 中是不可见的,即使它们是同一个 Telegram 身份。若要将不同账号连接到不同的 MCP 客户端,请分别注册每个客户端,并在各自的会话中使用 telegram-accounts-add。