两条 pipeline
huozi 支持两条平行的 pipeline,适配不同执行环境。两条最终都拿到 Bearer token 调 /mcp,但凭证来源、流程、token 形态都不同。按你 Agent 跑在哪决定走哪条。
并排对比
| 维度 | 选择二 · 本地终端 / GUI | 选择一 · Agent 自己装 |
|---|---|---|
| 标准 | RFC 8252 + PKCE | RFC 8628 |
| 适用场景 | 用户在自己电脑终端;有 TTY、有系统浏览器、能绑 localhost 回调。 | Chat-mode Agent;非 TTY shell 工具、可能 headless / 沙箱 / 远端,无 localhost 回调。 |
| 触发方式 | 客户端原生 mcp add CLI 或 GUI 的 Add connector 入口。 | Agent 读 /llms.txt 后自己跑;用户粘一段 prompt。 |
| 用户动作 | 点一次浏览器 Approve(本机弹起)。 | 点一次链接(任意设备),登录后 Approve。 |
| 拿到的凭证 | OAuth access_token(短期,1 小时)+ refresh_token(可轮转,30 天)。 | 静态 hz_* api_key(长期,直到撤销或 TTL 到期)。 |
| 凭证存哪 | 客户端自己的 token store(~/.hermes/mcp-tokens/、Cursor keychain 等),不进 config 文件。 | Host 的 MCP config 文件,Bearer 头明文。 |
RFC 8252 · OAuth-on-first-use
标准 OAuth 2.1 authorization code 流,带 PKCE-S256(RFC 7636)和 dynamic client registration(RFC 7591)。客户端不用预先注册,首次连 /mcp 失败时自动发现授权服务器、本机弹浏览器、收 localhost 回调。
流程
Step 1. Client → server: POST /mcp (no Authorization)
Step 2. Server → client: 401 + WWW-Authenticate: resource_metadata=/.well-known/oauth-protected-resource
Step 3. Client → server: GET /.well-known/oauth-protected-resource
Step 4. Client → server: GET /.well-known/oauth-authorization-server
Step 5. Client → server: POST /oauth/register (dynamic client registration)
Step 6. Client opens browser → /oauth/authorize?code_challenge=<S256>&...
Step 7. User signs in (OTP / password) + clicks Approve
Step 8. Browser → 127.0.0.1:<port>/callback?code=...
Step 9. Client → server: POST /oauth/token grant=authorization_code + code_verifier
Step 10. Server → client: { access_token, refresh_token, expires_in, scope }
Step 11. Client retries: POST /mcp Authorization: Bearer <access_token> → 200 OK支持这条路的客户端
Claude Code(claude mcp add)、OpenAI Codex CLI(codex mcp add)、Hermes(hermes mcp add --auth oauth)、Cursor(~/.cursor/mcp.json)、Claude Desktop / Cowork(Customize → Connectors)、OpenClaw(部分,WIP)。
RFC 8628 · Device Authorization Grant
为没浏览器、没 TTY、跨设备的客户端设计的 OAuth 扩展。Agent 调一个端点拿 device_code + 用户码,把验证 URL 给用户(用户在任意设备点开),Agent 后台轮询 token 端点直到拿到 access_token。彻底解开了 8252 的「必须能收 callback」假设。
流程
Step 1. Agent → server: POST /auth/device-code { client_name, agent_kind }
Step 2. Server → agent: { device_code, user_code, verification_url_complete, expires_in: 900, interval: 5 }
Step 3. Agent prints verification_url_complete to user
Step 4. User opens URL → /device?code=ABCD-1234 (any device)
Step 5. User signs in (Cloud OTP / Edge password) + clicks Approve
Step 6. Agent polls every 5s: POST /auth/token { device_code }
- 202 authorization_pending → keep polling
- 200 { api_key, key_id, workspace } → done
Step 7. Agent writes api_key to host MCP config
Step 8. Agent/host calls: POST /mcp Authorization: Bearer <api_key> → 200 OK标准 OAuth 端点也支持 device flow
原生支持 RFC 8628 的 MCP 客户端可以直接调 POST /oauth/token 用 form-encoded body grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=... ,服务端会按 OAuth 标准 envelope 返回 {access_token, token_type: "Bearer", scope}。这样客户端能从 /.well-known/oauth-authorization-server 自动发现,无需读这份 prompt。
/llms.txt 协议
Chat-mode Agent 不读这份 docs(它读的是机器友好的 /llms.txt)。 该端点跟着部署域名走 —— Cloud 是 cloud.huozi.app/llms.txt,Edge 是 <your-deploy>/llms.txt —— 内容由 route handler 按请求 host 模板生成。
?for=<agent_kind> 过滤
带 ?for=<kind> 查询参数访问 /llms.txt 会把 Step 4(写 host config 那段)过滤到只剩这一个 host 的 snippet,文档体积砍 ~70%。小 context window 模型(Haiku、本地小模型)就不会吃满。
可用 kind 值:
- claude-code
- cursor
- hermes
- openclaw
- codex
- cowork
- desktop
- generic
Self-check 段
/llms.txt 顶部有「Before you start」自检段:列出 Agent 需要的工具能力(HTTP request、用户链接打印、文件写),没全部具备的话直接告诉用户去 Choice 2 自己装,而不是闷头试到失败。
安全
- RFC 8252 OAuth token 由 MCP 客户端持有,不会进入对话上下文。
- RFC 8628 device flow 拿到的是静态 api_key,需要 Agent 守住 prompt 里的「don't print api_key」纪律。
- 所有 key 都可在 cloud.huozi.app/workspace(或 Edge 等价路径)随时撤销。
- RFC 8252 token 默认 1 小时过期,需要 refresh 续命。RFC 8628 api_key 默认无过期,但每个 connection 可独立设 sliding-window TTL。