跳到主要内容

ARC 开发者文档

授权客户端

从被拒绝的调用所返回的挑战走到 authorization server、注册客户端、取得凭证并使用它 —— 以及这份凭证打不开什么。

收到带 WWW-Authenticate 挑战的 401 之后,客户端已经拥有触达 authorization server 所需的一切。每一步都是对同一个 host 的普通 GETPOST。把 <host> 替换成该 blocklet 的 host。

步骤请求得到
1不带凭证 tools/call 一个写工具401 + 携带元数据 URL 的 WWW-Authenticate
2GET /.well-known/oauth-protected-resource哪个 authorization server 保护这个资源
3GET /.well-known/oauth-authorization-server端点、grant 类型、PKCE 方法
4POST /.well-known/service/oauth/register属于这个客户端的 client_id
5授权,然后换取凭证Authorization: Bearer blocklet-…

第 2 步 —— 受保护资源

bash
curl -s https://<host>/.well-known/oauth-protected-resource
json
{
  "resource": "https://<host>/mcp",
  "authorization_servers": ["https://<host>"]
}

authorization server 就是同一个 host。一个 blocklet 只保护自己的资源、只签发自己的凭证,因此要访问多个 blocklet 的客户端需要对每一个分别注册和授权。

第 3 步 —— authorization server 元数据

bash
curl -s https://<host>/.well-known/oauth-authorization-server
json
{
  "issuer": "https://<host>",
  "authorization_endpoint": "https://<host>/.well-known/service/oauth/authorize",
  "registration_endpoint": "https://<host>/.well-known/service/oauth/register",
  "device_authorization_endpoint": "https://<host>/.well-known/service/oauth/device_authorization",
  "token_endpoint": "https://<host>/.well-known/service/oauth/token",
  "response_types_supported": ["code"],
  "grant_types_supported": [
    "urn:ietf:params:oauth:grant-type:device_code",
    "authorization_code",
    "refresh_token"
  ],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "scopes_supported": ["mcp"]
}

token_endpoint_auth_methods_supportednone,所以客户端用 PKCE verifier 而不是 client secret 来认证 token 请求。scopes_supported 只有一项 —— 没有更窄或更宽的 scope 可选。

第 4 步 —— 注册客户端

客户端通过自我注册取得 client_id。系统不预先分发标识符。

bash
curl -s -X POST https://<host>/.well-known/service/oauth/register \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "my agent",
    "redirect_uris": ["http://127.0.0.1:3118/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "none"
  }'
json
{
  "client_id": "b665f46e-8fb5-4bf9-bd32-53df5b35f7f5",
  "redirect_uris": ["http://127.0.0.1:3118/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

响应回显整份注册内容,而不只是标识符。客户端会校验它,并拒绝漏掉自己所发字段的回复。

注册一次,把 client_id 存下来。 注册按来源地址限流;从未在一次完成的授权中被使用的注册,七天后被清除。

回调地址

跑在用户机器上的客户端监听的端口在运行时选定,所以请注册本次会话实际使用的 redirect_uris,不要指望某个固定值已被预先许可。loopback 地址、https URL 和 private-use scheme 被接受;其余一律拒绝 —— 见错误

第 5 步 —— 取得凭证

两种 grant 通向同一份凭证。通常你的工具会替你选好,见接入你的工具

你的客户端grant
能打开浏览器并监听本地端口(多数 MCP 客户端)带 PKCE 的 authorization_code,先 authorization_endpointtoken_endpoint
不能:CLI、CI 任务、无头 agentdevice grant,见无浏览器时取得凭证

两者都终结于同一个 token_endpoint,返回同样形状的凭证。

这份凭证是什么

token 端点返回一个 bearer token,它同时也是一把 access key

json
{
  "access_token": "blocklet-zEJYCdC9awCqxEPLhqreFbwXjqU6Y2BHvKVQAx6kMZfRh",
  "token_type": "Bearer",
  "scope": "mcp"
}

这个响应带出三件事,每件都重要:

  • blocklet- 前缀就是运行时识别它的方式。 OAuth token 和 access key 不是两套机制,是同一份凭证、同一个前缀。
  • 它携带同意界面上那个人所选的 roleowneradminmemberguest),作用域限于这个 blocklet 实例。见访问分档
  • device grant 不返回 expires_in,也不返回 refresh_token authorization server 把 refresh_token 列为支持的 grant,但 device 流程不发放它。

使用它

作为 bearer token 发给 /mcp

bash
curl -s -X POST https://<host>/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Authorization: Bearer blocklet-…' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

401 消失了:此前被拒的同一个 tools/call 现在能到达工具。

这份凭证打不开什么

到达工具,不等于操作成功。一次已认证的写入仍可能是这样 —— 传输层 200,内部被拒:

json
{
  "result": {
    "content": [{
      "type": "text",
      "text": "AFS_FORBIDDEN: Forbidden at /instance/notes.txt: network clients cannot write base paths; use a resolver overlay or internal code"
    }],
    "isError": true
  },
  "jsonrpc": "2.0",
  "id": 3
}

owner 身份的凭证得到同样的拒绝。从网络写入不是凭证能打开的东西 —— 该 blocklet 必须已经把那条路径向网络客户端开放,通过 resolver overlay 或运行在它内部的代码。

如果你的 agent 必须写入,那是 blocklet 拥有者的决定。围绕它做设计之前,先读访问分档