> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clinkbill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent 接入 Clink MCP Server

> 为业务 Agent 配置 Clink MCP 的 OAuth 授权、Token 管理和 MCP 调用流程。

Clink MCP 让服务端 Agent 以已授权用户的身份调用 Clink 工具。

<Note>
  同一个服务端应用、部署、租户或安装实例复用一个 OAuth `client_id`。每位用户分别完成 OAuth 授权，服务端为该用户单独保存 Token。不要为每次请求或每个用户注册新的 Client。
</Note>

## 先选方案和环境

| 场景 | 方案 | 是否推荐 |
| :- | :- | :-: |
| 服务端 Agent，可托管公开 HTTPS 元数据 | **CIMD** | 推荐 |
| 服务端 Agent，固定企业或合作方 | **Pre-registration** | 推荐 |
| 客户端 Agent，可托管公开 HTTPS 元数据 | **CIMD** | 推荐 |
| 客户端 Agent，不能托管公开 HTTPS，或 Host 只支持动态注册 | **DCR** | 有条件推荐 |

本文使用 UAT 沙盒和 Production 环境。两套环境的 MCP resource、OAuth `resource`、Token audience 和元数据地址必须一一对应，不能混用。MCP 配置只填地址，不要填 Access Token、Refresh Token 或 API Key。

先声明两个环境的 `base_url`，后面的地址都只在其后拼接路径：

```text theme={null}
UAT_BASE_URL=https://uat-api.clinkbill.com
PRODUCTION_BASE_URL=https://api.clinkbill.com
```

### 环境地址

| 环境 | MCP resource | Protected Resource Metadata | Authorization Server Metadata |
| :- | :- | :- | :- |
| UAT（沙盒） | `${UAT_BASE_URL}/mcp` | `${UAT_BASE_URL}/.well-known/oauth-protected-resource/mcp` | `${UAT_BASE_URL}/.well-known/oauth-authorization-server` |
| Production | `${PRODUCTION_BASE_URL}/mcp` | `${PRODUCTION_BASE_URL}/.well-known/oauth-protected-resource/mcp` | `${PRODUCTION_BASE_URL}/.well-known/oauth-authorization-server` |

### OAuth Endpoint

下面的路径在两个环境相同，只需和所选环境的 `base_url` 拼接。授权地址和 Token 地址在可用时应以 Authorization Server Metadata 的返回值为准。

| 用途 | 路径 | HTTP 方法和条件 |
| :- | :- | :- |
| 用户授权 | `/agent/cwallet/oauth/authorize` | `GET` |
| 换取/刷新 Token | `/agent/cwallet/oauth/token` | `POST` |
| DCR 注册 | `/agent/cwallet/oauth/register` | Metadata 返回 `registration_endpoint` 时 `POST` |
| 撤销 Refresh Token | `/agent/cwallet/oauth/revoke` | `POST` |
| JWKS 公钥 | `/agent/cwallet/oauth/jwks` | `GET` |

<Warning>
  只有 Authorization Server Metadata 返回 `registration_endpoint` 时才调用 DCR 注册接口；Protected Resource Metadata 不提供 DCR 地址。没有返回时使用 CIMD 或 Pre-registration。
</Warning>

## 注册和授权的频率

| 对象 | 频率 | 说明 |
| :- | :- | :- |
| **OAuth Client** | 每个 Host、部署、租户或安装实例一次 | CIMD 不调用注册接口；Pre-registration 由 Clink 配置；DCR 注册后保存并复用 `client_id`。不要按用户重复注册。 |
| **用户 OAuth 授权** | 每个用户首次使用一次 | Token 按用户隔离保存并刷新；Token 失效、撤销或 Scope 变化时重新授权。 |

## 服务端集成

### 一次性准备 OAuth Client

无论用哪种方案，Client 注册的 Scope 都是之后每次授权请求的上限。申请超出这个范围的 Scope 会返回 `invalid_scope`，后续补充权限时也一样。所以注册时要写全 Agent 可能用到的 Scope。Clink MCP 使用下面这些 Scope，`offline_access` 用于签发 Refresh Token：

```text theme={null}
wallet:read payment:execute instruction:read instruction:write refund:read refund:write events:read events:consume offline_access
```

每次授权请求仍然可以只申请其中一部分。

#### CIMD

CIMD 适合能维护公开 HTTPS 元数据的服务端 Agent。在自己的 HTTPS 域名下托管公开 JSON 文件，并把文件 URL 作为 `client_id`：

```json theme={null}
{
  "client_id": "https://agent.example.com/.well-known/oauth-client.json",
  "client_name": "Example Agent",
  "redirect_uris": ["https://agent.example.com/oauth/clink/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "wallet:read payment:execute instruction:read instruction:write refund:read refund:write events:read events:consume offline_access"
}
```

Clink 会拉取这个文件来校验授权请求，文件需要满足：

* URL 使用 HTTPS，带路径，不带 query 和 fragment。
* 文件中的 `client_id` 与文件 URL 完全一致。
* 直接返回 HTTP 200，`Content-Type` 为 `application/json`；Clink 不会跟随跳转。
* 文件不超过 5 KB。

CIMD URL 是公开的 Client 标识，文件里不要放 Client Secret。

#### Pre-registration

Pre-registration 适合固定企业或合作方。接入前把 `client_name`、固定 HTTPS `redirect_uri`、当前环境的 `resource` 和 Agent 可能用到的全部 Scope 提供给 Clink，由 Clink 完成配置并返回固定的 `client_id`。服务端不调用注册接口，也不需要托管 CIMD 文件；把这个 `client_id` 放在服务端配置中。

当前 Clink 的预注册 MCP Client 使用 public client，不需要 Client Secret，但仍必须使用 Authorization Code + PKCE（S256）。用户点击“连接 Clink”时，授权请求里的 `client_id` 使用这个固定值，每位用户的 Token 仍要单独保存。

```text theme={null}
CLINK_CLIENT_ID=clink-provided-client-id
CLINK_REDIRECT_URI=https://agent.example.com/oauth/clink/callback
CLINK_RESOURCE=${UAT_BASE_URL}/mcp
CLINK_SCOPES=wallet:read events:read events:consume offline_access
```

#### DCR

DCR 适合需要动态 Client ID、按租户或实例隔离 Client，或目标 Authorization Server 只支持动态注册的场景。先读取 Authorization Server Metadata；只有返回 `registration_endpoint` 时才调用注册。服务端应在部署、租户开通或首次安装时注册一次，保存返回的 `client_id`，后续所有用户复用它。

```http theme={null}
POST {registration_endpoint}
Content-Type: application/json

{
  "client_name": "Example Agent",
  "redirect_uris": ["https://agent.example.com/oauth/clink/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "wallet:read payment:execute instruction:read instruction:write refund:read refund:write events:read events:consume offline_access"
}
```

DCR 返回的 `client_id` 要持久化保存。它只代表 Client 注册成功，后续每位用户仍要单独登录和授权。当前 Clink 示例使用 public client（`token_endpoint_auth_method=none`），不需要 Client Secret。

Clink 不支持修改已注册的 Client。需要调整 redirect URI 或 Scope 时，要重新注册一个 Client，用户也要用新的 `client_id` 重新授权。

三种方案的区别在于 `client_id` 怎么取得，以及 redirect URI 和 Scope 在哪里登记。取得 `client_id` 后，用户授权、回调换 Token、调用 MCP、刷新 Token 的流程都按下面步骤执行。

### 用户点击“连接 Clink”时

<Steps>
  <Step title="读取 Protected Resource Metadata">
    请求当前环境的 Protected Resource Metadata，从 `authorization_servers` 取得 issuer。UAT 地址是 `${UAT_BASE_URL}/.well-known/oauth-protected-resource/mcp`。
  </Step>

  <Step title="读取 Authorization Server Metadata">
    请求 `{issuer}/.well-known/oauth-authorization-server`，读取 `authorization_endpoint` 和 `token_endpoint`。不要把端点写死在代码里。
  </Step>

  <Step title="生成 state 和 PKCE 参数">
    服务端为当前用户生成随机 `state` 和 PKCE `code_verifier`，临时保存它们与内部用户 ID 的绑定，用 S256 计算 `code_challenge`。
  </Step>

  <Step title="重定向到授权地址">
    将下面参数全部 URL 编码后带上。CIMD 使用公开 JSON 文件 URL；Pre-registration 使用 Clink 提供的固定 Client ID；DCR 使用注册接口返回并持久化的 Client ID。
  </Step>
</Steps>

```text theme={null}
response_type=code
client_id=https://agent.example.com/.well-known/oauth-client.json
redirect_uri=https://agent.example.com/oauth/clink/callback
scope=wallet:read events:read events:consume offline_access
resource=${UAT_BASE_URL}/mcp
state={random_state}
code_challenge={s256_challenge}
code_challenge_method=S256
```

`scope` 按当前需要申请，且不能超出 Client 注册的 Scope。MCP Server 首次返回的 `401` 会要求 `wallet:read events:read events:consume`；提前申请这两个 `events` Scope，可以避免 Operation 进行中再弹一次授权。需要 Refresh Token 时要包含 `offline_access`。UAT 和 Production 必须使用各自对应的 `resource`。

### 回调换 Token

用户完成登录和授权后，回调会收到 `code`、`state` 和 `iss`；授权失败时会收到错误参数。如果 `state` 与保存的值不一致，或 `iss` 缺失、与 Authorization Server Metadata 中的 issuer 不一致，直接拒绝这次回调。

然后向 `token_endpoint` 发送 `POST` 请求，`Content-Type` 为 `application/x-www-form-urlencoded`。使用同一个回调地址和保存的 PKCE verifier，每个参数值都要 URL 编码：

```text theme={null}
grant_type=authorization_code
client_id=https://agent.example.com/.well-known/oauth-client.json
code={code}
redirect_uri=https://agent.example.com/oauth/clink/callback
code_verifier={code_verifier}
resource=${UAT_BASE_URL}/mcp
```

这是 public client，不需要 `client_secret` 或 `device_id`。按内部用户 ID、环境和 `client_id` 加密保存 `access_token`、`refresh_token`、过期时间和 Scope。授权码只能兑换一次。

### 用 Token 调 MCP

后端用当前用户的 Access Token 请求对应环境的 `/mcp`。Clink MCP 支持 `2026-07-28` 和 `2025-06-18` 两个协议版本。建议使用官方 MCP Client SDK：它会协商版本、发送必需的请求头并解析流式响应。让 SDK 指向 MCP 地址，并在运行时为当前用户带上 `Authorization: Bearer` 请求头。

如果直接发送 HTTP 请求，请使用 `2025-06-18` 流程。`2026-07-28` 没有 `initialize` 握手，并且每个请求都要附带协议元数据，建议交给 SDK 处理。

1. 发送 `initialize`，`protocolVersion` 设为 `2025-06-18`。
2. 发送 `notifications/initialized`。
3. 调用 `tools/list` 获取工具。
4. 调用 `tools/call` 执行业务。

```http theme={null}
POST ${UAT_BASE_URL}/mcp
Authorization: Bearer {user_access_token}
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example-agent","version":"1.0.0"}}}
```

每个请求都要带上面的 `Accept` 请求头，否则服务端返回 `406`。响应可能是 `text/event-stream` 流，其中每个 `data:` 行是一条 JSON-RPC 消息。`initialize` 之后的每个请求，都要在 `MCP-Protocol-Version` 请求头里带上协商出的版本：

```http theme={null}
POST ${UAT_BASE_URL}/mcp
Authorization: Bearer {user_access_token}
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2025-06-18

{"jsonrpc":"2.0","id":2,"method":"tools/list"}
```

### 处理需要用户打开的链接

部分工具结果会带 `next_action`，其 `type` 为 `REDIRECT_USER`，例如绑卡、完成 3DS 或为 Agent Pay 注册卡片。通过你自己的渠道把 `params.redirect_url` 发给用户。`params.actor` 为 `USER_DEVICE_ONLY` 时，必须由用户在自己的设备上打开，不要在 Agent 控制的浏览器里打开。用户从页面返回不代表这一步已经成功，要调用 `get_operation` 读取结果。

### 刷新和恢复

Access Token 过期时，向同一个 `token_endpoint` 提交 `grant_type=refresh_token`、原 `client_id`、当前 Refresh Token 和同一个 `resource`。

每次刷新都会返回新的 Refresh Token，旧的随即失效。先保存新的 Refresh Token，再使用新的 Access Token。如果再次提交已失效的 Refresh Token，Clink 会判定为重复使用，并撤销这次授权下的全部 Refresh Token，用户必须重新授权。刷新操作要按用户、环境和 `client_id` 串行，多实例部署时也要保证。

* 遇到 `401` 时刷新 Token；刷新失败则让用户重新授权。
* 遇到 `403` 且错误为 `insufficient_scope` 时，从 `WWW-Authenticate` 响应头的 `scope` 参数读取所需 Scope，与已授予的 Scope 合并后让用户重新授权。合并后的 Scope 不能超出 Client 注册的 Scope。
* 支付、退款或指令超时时调用 `get_operation` 查询，不能直接重复执行。

## 上线检查

* [ ] Client 注册只在初始化、部署、租户开通或首次安装时执行。
* [ ] Client 注册时写全 Agent 可能用到的 Scope，授权时只申请当前需要的 Scope。
* [ ] 每个用户的 Token、会话和 Operation 隔离保存。
* [ ] 回调地址与 Client 配置逐字符一致。
* [ ] Token、Client Secret 不放进配置、CIMD 文件或日志。
* [ ] UAT 和 Production 地址不混用。
* [ ] Refresh Token 按用户、环境和 `client_id` 串行使用。
* [ ] `REDIRECT_USER` 链接发给用户打开，不要在 Agent 控制的浏览器里打开。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.