---
title: "MCP 客户端 OAuth"
description: "开发能让用户登录 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 密钥](/authentication)。

## 服务发现

不带凭证请求 `https://api.twexapi.io/mcp` 会返回 `401`，并带上：

```http
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` 也返回同样内容）：

```json
{
  "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`：

```json
{
  "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
}
```

<a id="scopes"></a>

## 权限范围

| 权限范围 | 授予 |
| --- | --- |
| `mcp:read` | `explore` 和 `twexapi_request`（只读接口）。始终授予。 |
| `mcp:write` | `twexapi_write`（发推和回复）。用户可以在授权页上拒绝。 |

请求 `mcp:read mcp:write` 即可同时申请两者。无法识别的权限范围会被忽略；如果请求中没有任何可识别的权限范围，则视为同时申请两者。token 响应中的 `scope` 字段会告诉你用户实际授予了哪些权限。

## 1. 注册客户端

```http
POST https://api.twexapi.io/oauth/register
Content-Type: application/json

{
  "client_name": "My Agent",
  "redirect_uris": ["http://127.0.0.1:53682/callback"]
}
```

```json
{
  "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：

```js
import crypto from "node:crypto";

const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
```

Python：

```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：

```txt
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` | 否 | 见[权限范围](#scopes)。 |
| `resource` | 否 | 如果传入，必须是 `https://api.twexapi.io/mcp`。 |

TwexAPI 会把用户带到 `twexapi.io` 的授权页。用户登录后，查看应用名称和授权后要返回的地址，选择是否授予写入权限，然后同意或拒绝。授权请求在 10 分钟后过期。

## 4. 接收授权码

用户同意后，浏览器会回到你的回调地址：

```txt
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

```http
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
```

```json
{
  "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 服务器

```http
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

```http
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 都会失效，用户需要重新登录。

## 撤销

```http
POST https://api.twexapi.io/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=twx_rt_...
```

撤销 refresh token 会结束整个授权；撤销访问 token 只会让该 token 失效。无论 token 是否存在，这个端点都返回 `200`。用户也可以在 [控制台](https://twexapi.io/dashboard) 的 **Authorized apps**（已授权应用）中撤销任意应用，立即生效。

## 有效期

| 项目 | 有效期 |
| --- | --- |
| 授权请求（授权页） | 10 分钟 |
| 授权码 | 2 分钟，只能使用一次 |
| 访问 token | 1 小时 |
| Refresh token | 30 天，每次使用都会轮换 |
| 连续 30 天未使用的授权 | 失效 |
| 客户端注册 | 180 天，每次授权都会延长 |

## 错误

错误采用标准的 OAuth 格式：

```json
{
  "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。
