MCP 客户端 OAuth
开发能让用户登录 TwexAPI 的 MCP 客户端时的参考:服务发现、动态客户端注册、PKCE、权限范围、token、续期与撤销。
TwexAPI 为其 MCP 服务器运行了一个 OAuth 2.1 授权服务器。Claude、ChatGPT、Cursor 等 MCP 客户端会自动使用它。如果你在开发自己的客户端,或者需要排查客户端问题,请阅读本页。
- 授权码流程加 PKCE(只支持
S256)。 - 只支持公开客户端,没有 client secret。通过动态客户端注册(RFC 7591)注册。
- 用户在
https://twexapi.io/oauth/consent用自己的 TwexAPI 账号登录并确认授权。 - token 是不透明字符串,只能用于
https://api.twexapi.io/mcp。它们不是 TwexAPI API 密钥,不能调用 REST API。 - 调用通过用户的 API 密钥从其额度中扣费,所以用户需要有一个可用、且有额度的密钥才能授权。
如果你只是想让脚本或后端调用 TwexAPI,请改用 API 密钥。
服务发现
不带凭证请求 https://api.twexapi.io/mcp 会返回 401,并带上:
WWW-Authenticate: Bearer realm="twexapi", resource_metadata="https://api.twexapi.io/.well-known/oauth-protected-resource/mcp", scope="mcp:read mcp:write"
受保护资源元数据
GET https://api.twexapi.io/.well-known/oauth-protected-resource/mcp(/.well-known/oauth-protected-resource 也返回同样内容):
{
"resource": "https://api.twexapi.io/mcp",
"authorization_servers": ["https://api.twexapi.io"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["mcp:read", "mcp:write"],
"resource_name": "TwexAPI"
}
授权服务器元数据
GET https://api.twexapi.io/.well-known/oauth-authorization-server:
{
"issuer": "https://api.twexapi.io",
"authorization_endpoint": "https://api.twexapi.io/oauth/authorize",
"token_endpoint": "https://api.twexapi.io/oauth/token",
"registration_endpoint": "https://api.twexapi.io/oauth/register",
"revocation_endpoint": "https://api.twexapi.io/oauth/revoke",
"response_types_supported": ["code"],
"response_modes_supported": ["query"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"revocation_endpoint_auth_methods_supported": ["none"],
"scopes_supported": ["mcp:read", "mcp:write"],
"authorization_response_iss_parameter_supported": true
}
权限范围
| 权限范围 | 授予 |
|---|---|
mcp:read |
explore 和 twexapi_request(只读接口)。始终授予。 |
mcp:write |
twexapi_write(发推和回复)。用户可以在授权页上拒绝。 |
请求 mcp:read mcp:write 即可同时申请两者。无法识别的权限范围会被忽略;如果请求中没有任何可识别的权限范围,则视为同时申请两者。token 响应中的 scope 字段会告诉你用户实际授予了哪些权限。
1. 注册客户端
POST https://api.twexapi.io/oauth/register
Content-Type: application/json
{
"client_name": "My Agent",
"redirect_uris": ["http://127.0.0.1:53682/callback"]
}
{
"client_id": "twx_client_...",
"client_id_issued_at": 1792600000,
"client_name": "My Agent",
"redirect_uris": ["http://127.0.0.1:53682/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
| 字段 | 规则 |
|---|---|
redirect_uris |
必填。1 到 10 个 URI,每个必须是以下之一:https://;localhost、127.0.0.1 或 [::1] 上的 http://;或者自定义协议(如 cursor://)。不能带片段(#)或用户信息。 |
client_name |
可选,最多 100 个字符。会显示在授权页和用户的 Authorized apps(已授权应用)列表中。 |
token_endpoint_auth_method |
可选。只接受 none。 |
grant_types |
可选。必须是 authorization_code 和 refresh_token 的子集。 |
response_types |
可选。只接受 code。 |
每个 IP 地址每小时最多注册 30 个客户端。客户端注册有效期为 180 天,每当有用户用它发起授权时都会自动延长,所以注册一次后请复用 client_id。
2. 生成 PKCE 参数
Node.js:
import crypto from "node:crypto";
const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
Python:
import base64, hashlib, secrets
verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode()
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
verifier 必须是 43 到 128 个字符,只能包含 A-Z a-z 0-9 - . _ ~。
3. 让用户前往授权
在用户的浏览器中打开这个 URL:
https://api.twexapi.io/oauth/authorize
?response_type=code
&client_id=twx_client_...
&redirect_uri=http%3A%2F%2F127.0.0.1%3A53682%2Fcallback
&code_challenge=CHALLENGE
&code_challenge_method=S256
&state=RANDOM_STATE
&scope=mcp%3Aread%20mcp%3Awrite
&resource=https%3A%2F%2Fapi.twexapi.io%2Fmcp
| 参数 | 必填 | 说明 |
|---|---|---|
response_type |
是 | code。 |
client_id |
是 | 注册时获得。 |
redirect_uri |
是 | 必须与已注册的 URI 完全一致。本机回环的 http URI 可以使用任意端口。 |
code_challenge、code_challenge_method |
是 | 只支持 S256。 |
state |
建议 | 原样返回;请校验它以防止 CSRF。 |
scope |
否 | 见权限范围。 |
resource |
否 | 如果传入,必须是 https://api.twexapi.io/mcp。 |
TwexAPI 会把用户带到 twexapi.io 的授权页。用户登录后,查看应用名称和授权后要返回的地址,选择是否授予写入权限,然后同意或拒绝。授权请求在 10 分钟后过期。
4. 接收授权码
用户同意后,浏览器会回到你的回调地址:
http://127.0.0.1:53682/callback?code=CODE&state=RANDOM_STATE&iss=https%3A%2F%2Fapi.twexapi.io
请校验 state 是否一致,以及 iss 是否为 https://api.twexapi.io。用户拒绝时,回调中会带 error=access_denied 而不是 code。授权码只能使用一次,2 分钟后过期。
如果 client_id 不存在,或 redirect_uri 没有注册过,TwexAPI 会直接显示 400 错误而不是跳转,这样配置错误的客户端永远不会把用户带到未经验证的地址。
5. 用授权码换取 token
POST https://api.twexapi.io/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=CODE
&redirect_uri=http%3A%2F%2F127.0.0.1%3A53682%2Fcallback
&client_id=twx_client_...
&code_verifier=VERIFIER
{
"access_token": "twx_at_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "twx_rt_...",
"scope": "mcp:read mcp:write"
}
redirect_uri 和 client_id 必须与授权请求一致。也可以传入 resource=https://api.twexapi.io/mcp。请求体也可以用 JSON。
6. 调用 MCP 服务器
POST https://api.twexapi.io/mcp
Authorization: Bearer twx_at_...
Content-Type: application/json
Accept: application/json, text/event-stream
访问 token 只能用于 /mcp。token 过期、被撤销或无法识别时,会返回 401,WWW-Authenticate 头中带 error="invalid_token";此时请用 refresh token 续期,或让用户重新登录。
续期 token
POST https://api.twexapi.io/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=twx_rt_...
&client_id=twx_client_...
每次续期都会返回新的访问 token 和新的 refresh token,旧的 refresh token 随即失效。请每次都保存新的一对。如果旧的 refresh token 被再次使用,TwexAPI 会认为它已泄露并撤销整个授权,该授权下的所有 token 都会失效,用户需要重新登录。
撤销
POST https://api.twexapi.io/oauth/revoke
Content-Type: application/x-www-form-urlencoded
token=twx_rt_...
撤销 refresh token 会结束整个授权;撤销访问 token 只会让该 token 失效。无论 token 是否存在,这个端点都返回 200。用户也可以在 控制台 的 Authorized apps(已授权应用)中撤销任意应用,立即生效。
有效期
| 项目 | 有效期 |
|---|---|
| 授权请求(授权页) | 10 分钟 |
| 授权码 | 2 分钟,只能使用一次 |
| 访问 token | 1 小时 |
| Refresh token | 30 天,每次使用都会轮换 |
| 连续 30 天未使用的授权 | 失效 |
| 客户端注册 | 180 天,每次授权都会延长 |
错误
错误采用标准的 OAuth 格式:
{
"error": "invalid_grant",
"error_description": "The authorization code is invalid, expired or already used"
}
| 端点 | error |
原因 |
|---|---|---|
| 注册 | invalid_redirect_uri |
回调地址缺失、格式错误,或使用了不允许的协议或主机。 |
| 注册 | invalid_client_metadata |
不支持的 token_endpoint_auth_method、grant_types 或 response_types。 |
| 注册 | temporarily_unavailable(429) |
你的 IP 地址注册次数过多。请复用 client_id。 |
| 授权 | invalid_client(400,不跳转) |
client_id 不存在,请先注册。 |
| 授权 | invalid_request(400,不跳转) |
redirect_uri 没有为该客户端注册过。 |
| 授权 | unsupported_response_type |
response_type 不是 code。 |
| 授权 | invalid_request |
PKCE 参数缺失或无效。 |
| 授权 | invalid_target |
resource 不是 https://api.twexapi.io/mcp。 |
| 授权 | access_denied |
用户拒绝了授权。 |
| Token | invalid_grant |
授权码或 refresh token 无效、已过期、已使用或属于其他客户端;PKCE 校验失败;或授权已被撤销。 |
| Token | invalid_target |
resource 不匹配。 |
| Token | unsupported_grant_type |
请使用 authorization_code 或 refresh_token。 |
如果用户的账号没有可用的 API 密钥或没有额度,授权页会在用户点击同意之前就拦下,所以你的客户端不会拿到无法调用的 token。