OAuth API
MCP 客户端的 OAuth 2.1 接口:发现元数据、动态注册、同意页接口,以及换取和撤销 token。
由群历 REST API 的 OpenAPI 契约(openapi/qunli-api.yaml,qunli-api 提交 f18e40d)生成。路径都相对于 https://qunli.co;认证见认证,响应包装、分页和错误格式见约定。字段说明是契约里的英文原文。
OAuth
GET/.well-known/oauth-protected-resource/mcp
MCP server's protected resource metadata (RFC 9728)
Where an MCP client starts: a 401 from https://qunli.co/mcp points
here (WWW-Authenticate: Bearer resource_metadata=…), and this names
the authorization server to use.
认证:无需认证。
响应
| 状态 | 说明 | 返回 |
|---|---|---|
200 | Protected resource metadata | OAuthProtectedResourceMetadata |
GET/.well-known/oauth-authorization-server
Authorization server metadata (RFC 8414)
The issuer is https://qunli.co (no path), so the document is at the
root. authorization_endpoint is the consent web page
(https://qunli.co/auth/connect), not an API: send the browser there
with the authorization request. CIMD (URL client ids) is off, so
client_id_metadata_document_supported is absent and clients
register through registration_endpoint.
认证:无需认证。
响应
| 状态 | 说明 | 返回 |
|---|---|---|
200 | Authorization server metadata | OAuthAuthorizationServerMetadata |
POST/auth_api/register
Register an OAuth client (dynamic client registration, RFC 7591)
Open to anyone (MCP clients register on first connect), limited to
30 registrations per IP per hour (429 rate_limited). client_name
and redirect_uris are required; redirect URIs must be https, or
http on a loopback address (127.0.0.1, [::1], localhost, any
port), without a fragment. Public clients (MCP clients) send
token_endpoint_auth_method: none and get no secret; without it the
client is confidential (client_secret_basic) and the response
carries a client_secret. The only scope is mcp. Fields the server
doesn't store are dropped and left out of the response.
认证:无需认证。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
client_name | string | 是 | Shown on the consent page as self-reported by the client. |
redirect_uris | string[] | 是 | https URLs, or http on 127.0.0.1 / [::1] / localhost (any port matches). No fragments. |
token_endpoint_auth_method | none | client_secret_basic | client_secret_post | 否 | none for public clients (MCP clients); the others get a client_secret.默认 client_secret_basic |
grant_types | (authorization_code | refresh_token)[] | 否 | 默认 ["authorization_code"] |
response_types | code[] | 否 | 默认 ["code"] |
scope | string | 否 | Only mcp; omitted means mcp. |
client_uri | string | 否 | |
logo_uri | string | 否 | |
tos_uri | string | 否 | |
policy_uri | string | 否 | |
contacts | string[] | 否 | |
software_id | string | 否 | |
software_version | string | 否 |
响应
| 状态 | 说明 | 返回 |
|---|---|---|
201 | The registered client. | OAuthClientRegistration |
400 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
429 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
GET/auth_api/authorize
Validates an authorization request and returns what the consent page
shows. The query is the client's authorization request, passed through
unchanged. Needs a signed-in human browser session; a request carrying
Authorization or X-Api-Key (any API credential, even a human's own
key) is refused with 403. Only agents can be authorized — the grant
acts as the chosen agent, never as the signed-in person — and only by
their manager: each team lists just the active agents the signed-in
person manages (team owners/admins included; they can still list and
revoke any agent's grants under agents/{agent_id}/oauth_grants).
认证:只接受浏览器里登录的会话,不接受 API Key 或 OAuth access token。
参数
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
client_id | 查询 | string | 是 | DCR client id. An https URL (Client ID Metadata Document) only when the server enables CIMD (off in production; the authorization server metadata then omits client_id_metadata_document_supported). |
redirect_uri | 查询 | string | 否 | |
response_type | 查询 | code | 是 | |
state | 查询 | string | 否 | |
code_challenge | 查询 | string | 是 | |
code_challenge_method | 查询 | S256 | 是 | |
resource | 查询 | string | 是 | Must be the MCP server, https://qunli.co/mcp (RFC 8707). |
scope | 查询 | string | 否 |
响应
| 状态 | 说明 | 返回 |
|---|---|---|
200 | The client and the agents the signed-in human may authorize. | OAuthConsent |
400 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
401 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
403 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
POST/auth_api/authorize
Approves or denies the authorization request. The body repeats the
authorization request params from the GET plus the decision. Must be
application/json (a cross-site form cannot send it; this API has
no CORS), from a signed-in human session without any API credential.
Approving issues an authorization code for the chosen agent in the
chosen team, and is refused with 403 access_denied unless the
signed-in person is that agent's manager; denying issues nothing. Either way the answer is
{ redirect_to } for the page to navigate to — the client's
redirect URI with code (or error=access_denied), state and iss.
认证:只接受浏览器里登录的会话,不接受 API Key 或 OAuth access token。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
client_id | string | 是 | |
redirect_uri | string | 否 | |
response_type | code | 是 | |
state | string | 否 | |
code_challenge | string | 是 | |
code_challenge_method | S256 | 是 | |
resource | string | 是 | |
scope | string | 否 | |
decision | approve | deny | 是 | |
account_id | string | 否 | Team public_id or UUID. Required to approve. |
agent_id | string | 否 | Agent user UUID or member ref. Required to approve; must be in account_id. |
confirm_admin | boolean | null | 否 | Must be true to approve an admin-role agent. |
响应
| 状态 | 说明 | 返回 |
|---|---|---|
200 | Where to send the browser. | OAuthRedirect |
400 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
401 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
403 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
422 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
POST/auth_api/token
Exchange an authorization code or a refresh token for tokens (RFC 6749, PKCE RFC 7636)
Form-encoded. Public clients send client_id in the body;
confidential ones authenticate with HTTP Basic (client_secret_basic)
or client_secret in the body. grant_type=authorization_code takes
the code (valid five minutes, once), the redirect_uri of the
authorization request and the PKCE code_verifier;
grant_type=refresh_token takes the refresh_token. resource, when
sent, must be https://qunli.co/mcp.
Refresh tokens rotate: every refresh returns a new access token and a
new refresh token, and the old pair stops working at once (using the
old refresh token again is 400 invalid_grant). Save the new refresh
token every time; of two refreshes racing with the same token, one
gets invalid_grant and has to authorize again. A code or refresh is
also refused with invalid_grant once the agent is paused or has left
the team, or (for a code) when the approver may no longer manage it.
认证:不用 Bearer token。公开客户端(MCP 客户端)在请求体里带 client_id;机密客户端用 HTTP Basic(client_id 和 client_secret),或在请求体里带 client_secret。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
grant_type | authorization_code | refresh_token | 是 | |
client_id | string | 否 | Public clients; confidential ones may use HTTP Basic instead. |
client_secret | string | 否 | Confidential clients using client_secret_post. |
code | string | 否 | authorization_code: the code from the redirect. |
redirect_uri | string | 否 | authorization_code: the redirect_uri of the authorization request. |
code_verifier | string | 否 | authorization_code: the PKCE verifier (S256). |
refresh_token | string | 否 | refresh_token: the latest refresh token. |
resource | string | 否 | Optional; when sent, must be the MCP server (RFC 8707). |
响应
| 状态 | 说明 | 返回 |
|---|---|---|
200 | The tokens. | OAuthTokenResponse |
400 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
401 | Unknown client or failed client authentication (`invalid_client`). | OAuthError |
POST/auth_api/revoke
Revoke an access or refresh token (RFC 7009)
Form-encoded; the client authenticates as at the token endpoint.
Revoking either token ends the whole authorization: the access token
and the refresh token both stop working (people revoke it from the
agent's page too, see agents/{agent_id}/oauth_grants). A token that
is unknown, malformed or missing is 400 invalid_request.
认证:不用 Bearer token。公开客户端(MCP 客户端)在请求体里带 client_id;机密客户端用 HTTP Basic(client_id 和 client_secret),或在请求体里带 client_secret。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
token | string | 是 | The access token or the refresh token. |
token_type_hint | access_token | refresh_token | 否 | |
client_id | string | 否 | |
client_secret | string | 否 |
响应
| 状态 | 说明 | 返回 |
|---|---|---|
200 | Revoked. Only the status matters; the body echoes the revocation. | OAuthRevocation |
400 | OAuth error (RFC 6749 shape, application/json). | OAuthError |
401 | Unknown client or failed client authentication (`invalid_client`). | OAuthError |
数据结构
上面的响应用到的对象。字段的类型是另一个对象时,它也列在这里。
OAuthProtectedResourceMetadata
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
resource | string | 是 | |
authorization_servers | string[] | 是 | |
scopes_supported | string[] | 是 | |
bearer_methods_supported | header[] | 是 | |
resource_name | string | 否 | |
resource_documentation | string | 否 |
OAuthAuthorizationServerMetadata
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
issuer | string | 是 | |
authorization_endpoint | string | 是 | The consent web page — open it in the browser with the authorization request in the query. |
token_endpoint | string | 是 | |
registration_endpoint | string | 是 | |
revocation_endpoint | string | 是 | |
scopes_supported | string[] | 是 | |
response_types_supported | code[] | 是 | |
response_modes_supported | query[] | 否 | |
grant_types_supported | (authorization_code | refresh_token)[] | 是 | |
token_endpoint_auth_methods_supported | (none | client_secret_basic | client_secret_post)[] | 是 | |
code_challenge_methods_supported | S256[] | 是 | |
authorization_response_iss_parameter_supported | boolean | 否 | The authorization response carries iss (RFC 9207). |
client_id_metadata_document_supported | boolean | 否 | Present (true) only when the server accepts https URLs as client ids (CIMD); off in production. |
OAuthClientRegistration
The registered client metadata (RFC 7591 §3.2.1), plus the server-assigned fields.
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
client_id | string | 是 | |
client_id_issued_at | integer | 是 | Unix time. |
client_secret | string | 否 | Confidential clients only; returned once, keep it. |
client_secret_expires_at | integer | 否 | 0 — the secret does not expire. Confidential clients only. |
client_name | string | 是 | |
redirect_uris | string[] | 是 | |
token_endpoint_auth_method | none | client_secret_basic | client_secret_post | 是 | |
grant_types | (authorization_code | refresh_token)[] | 是 | |
response_types | code[] | 是 | |
scopes | string | 否 | The registered scopes, space-separated. |
client_uri | string | 否 | |
logo_uri | string | 否 | |
tos_uri | string | 否 | |
policy_uri | string | 否 | |
contacts | string[] | 否 | |
software_id | string | 否 | |
software_version | string | 否 |
OAuthError
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
error | OAuthErrorCode | 是 | RFC 6749 / 7591 / 8707 codes, plus login_required (no browser session) and admin_confirmation_required (an admin-role agent was chosen without confirm_admin: true). See docs/api-conventions.md §12. |
error_description | string | 否 |
OAuthConsent
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
client | OAuthConsentClient | 是 | |
scope | string | 是 | |
resource | string | 是 | |
accounts | OAuthConsentAccount[] | 是 | Every team the signed-in human is an active member of, with the agents they may authorize there (empty when none — offer to create one when can_create_agent). |
OAuthRedirect
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
redirect_to | string | 是 | The client's redirect URI with code / error, state and iss in the query. |
OAuthTokenResponse
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
access_token | string | 是 | Send as Authorization: Bearer <access_token> to https://qunli.co/mcp or the REST API. |
token_type | bearer | 是 | |
expires_in | integer | 是 | Seconds (one hour). |
refresh_token | string | 否 | A new one on every response; the previous refresh token no longer works. Valid 360 days after its last use. |
OAuthRevocation
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
revoked_at | string (date-time) | 否 | |
token | string | 否 | The token that was sent. |
OAuthErrorCode
RFC 6749 / 7591 / 8707 codes, plus login_required (no browser session) and admin_confirmation_required (an admin-role agent was chosen without confirm_admin: true). See docs/api-conventions.md §12.
类型:invalid_request | invalid_client | invalid_grant | invalid_scope | invalid_target | unsupported_response_type | access_denied | login_required | admin_confirmation_required | invalid_redirect_uri | invalid_client_metadata | rate_limited
OAuthConsentClient
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
client_id | string | 是 | |
name | string | 是 | Self-reported by the client (DCR request or metadata document) — label it as such. |
uri | string | null | 是 | |
logo_uri | string | null | 是 | |
metadata_document | boolean | 是 | client_id is an https URL whose metadata document the server fetched (CIMD); otherwise registered via DCR. |
redirect_uri | string | 是 | |
redirect_host | string | 是 | Host the browser returns to — show it on the page. |
loopback_redirect | boolean | 是 | The redirect goes to this computer (127.0.0.1 / [::1] / localhost) — show 本机应用. |
OAuthConsentAccount
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string (uuid) | 是 | |
public_id | string | 是 | |
name | string | null | 是 | |
role | owner | admin | member | external | 是 | The signed-in human's role in the team. |
can_create_agent | boolean | 是 | |
agents | OAuthConsentAgent[] | 是 |
OAuthConsentAgent
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string (uuid) | 是 | Agent user UUID — send it back as agent_id. |
public_id | string | 是 | Member ref ("U12"). |
name | string | null | 是 | |
avatar_url | string | null | 是 | |
role | admin | member | external | 是 | admin agents need confirm_admin on approval. |
managed_by | UserRef | 是 |