← API reference

OAuth

An OAuth 2.1 authorization server, scoped to one job: letting a hosted assistant obtain an MCP credential. PKCE S256 only, public clients only, exact redirect_uri matching, single-use codes.

7 of 7 · v0.120.0
GET /.well-known/oauth-protected-resource #
Public

RFC 9728 protected resource metadata.

Unauthenticated. The document a client is pointed at by WWW-Authenticate on a 401 from /mcp, and the first step of the whole flow. scopes_supported names the minimal set for basic functionality — a client asks for more through a step-up rather than being nudged into requesting everything at once.

Responses

200

The metadata document.

application/json

object

RFC 9728 protected resource metadata.

resource string required

This instance's /mcp.

authorization_servers array required
each item
string
scopes_supported array required

The minimal grant. More is asked for through a step-up.

each item
string
bearer_methods_supported array required
each item
string
resource_name string required
GET /.well-known/oauth-authorization-server #
Public

RFC 8414 authorization server metadata.

Unauthenticated. Advertises S256 as the only PKCE method — plain is in RFC 7636 and provides no protection against the code interception PKCE exists for — and authorization_response_iss_parameter_supported, because this server emits iss (RFC 9207) and a strict client will not check it otherwise.

Responses

200

The metadata document.

application/json

object

RFC 8414 authorization server metadata.

issuer string required
authorization_endpoint string required
token_endpoint string required
registration_endpoint string required
scopes_supported array required
each item
string
response_types_supported array required
each item
string
grant_types_supported array required
each item
string
code_challenge_methods_supported array required

S256 only.

each item
string
token_endpoint_auth_methods_supported array required

none — every client is public.

each item
string
authorization_response_iss_parameter_supported boolean required

RFC 9207: iss is sent on authorization responses.

GET /oauth/authorize #
Public

The authorization endpoint.

Unauthenticated at this step. Validates the request and redirects to the SPA consent screen at /authorize carrying the parameters; it does not render HTML, because this application serves one index.html and a server-rendered consent page would be the only thing that could drift from the app's own styling.

Failures concerning the redirect target itself are reported directly rather than bounced off it — an unvalidated redirect_uri is an open redirect and must not be used to deliver its own error. redirect_uri is matched exactly against one the client registered; a prefix match here would hand somebody else an authorization code.

PKCE is required and code_challenge_method must be S256.

Parameters

client_id query required
string
redirect_uri query required

Must match one the client registered exactly.

string
response_type query

code. Required by /oauth/authorize.

string
scope query

Space-separated grants. Unrecognised ones are dropped; none means the minimal default.

string
state query

Handed back unchanged.

string
code_challenge query

PKCE. Required by /oauth/authorize.

string
code_challenge_method query

S256, the only method accepted.

string
resource query

RFC 8707 — which resource the credential is for. Validated as naming this instance, not merely recorded.

string

Responses

303

To the consent screen, or back to the client with an error.

400

Unknown client, or a redirect_uri that does not match its registration.

application/json

object

An OAuth error, as RFC 6749 shapes it.

error string required

invalid_request, invalid_client, invalid_grant, invalid_target, invalid_redirect_uri, unsupported_grant_type, server_error.

error_description string required
POST /oauth/token #
Public

Exchange a code, or refresh.

Unauthenticated (public clients, token_endpoint_auth_method: none); form-encoded, as the RFC requires. Grants: authorization_code and refresh_token.

The code is single use, taken with DELETE … RETURNING so two simultaneous redemptions cannot both succeed. It is bound to the client, the redirect, the PKCE challenge and the resource — every one of those is re-checked here.

A refresh rotates both secrets, so a leaked access token stops working as soon as the legitimate client refreshes. Access credentials live 24 hours.

Request body required

application/x-www-form-urlencoded

object

Form-encoded, as the RFC requires.

grant_type string required

authorization_code or refresh_token.

code string | null
redirect_uri string | null
client_id string | null
code_verifier string | null

43–128 characters, per RFC 7636.

refresh_token string | null
resource string | null

Responses

200

A credential. Never cached.

application/json

object

An access credential. Served no-store.

access_token string required
token_type string required

Bearer.

expires_in integer · int64 required

Seconds. A day.

refresh_token string | null

Rotates on every refresh; the old pair stops working.

scope string | null

What was actually granted, which can differ from what was asked for. Absent on a refresh.

400

invalid_grant, invalid_request, invalid_target or unsupported_grant_type.

application/json

object

An OAuth error, as RFC 6749 shapes it.

error string required

invalid_request, invalid_client, invalid_grant, invalid_target, invalid_redirect_uri, unsupported_grant_type, server_error.

error_description string required
POST /oauth/register #
Public

RFC 7591 dynamic client registration.

Unauthenticated by design: this hands out an identifier and nothing else. Every client is public, there is no secret, and possession of a client_id gets you as far as an authorization request that a logged-in human then has to approve by hand.

redirect_uris must be https, a loopback http address, or a private-use scheme. Plain http anywhere else would put an authorization code on the network in the clear.

Request body required

application/json

object

RFC 7591 registration. Open by design.

client_name string | null

Up to 80 characters. Shown on the consent screen as unverified.

redirect_uris array

At least one. https, loopback http, or a private-use scheme; no fragment.

each item
string

Responses

201

Registered.

application/json

object

RFC 7591 registration response. The client_id is not a credential; it gets a client as far as a request a signed-in person has to approve.

client_id string required
client_name string required
redirect_uris array required
each item
string
token_endpoint_auth_method string required
grant_types array required
each item
string
response_types array required
each item
string
400

A redirect_uri was missing or not acceptable.

application/json

object

An OAuth error, as RFC 6749 shapes it.

error string required

invalid_request, invalid_client, invalid_grant, invalid_target, invalid_redirect_uri, unsupported_grant_type, server_error.

error_description string required
GET /api/v1/oauth/authorize-info #
Session

What the consent screen puts in front of the user.

Session-only. Scope descriptions travel with it rather than raw scope strings — listening:read is not something a person can consent to. verified says whether the client identified itself with a fetchable URL or merely registered a name it chose for itself; the consent screen warns on the latter.

Parameters

client_id query required
string
redirect_uri query required

Must match one the client registered exactly.

string
response_type query

code. Required by /oauth/authorize.

string
scope query

Space-separated grants. Unrecognised ones are dropped; none means the minimal default.

string
state query

Handed back unchanged.

string
code_challenge query

PKCE. Required by /oauth/authorize.

string
code_challenge_method query

S256, the only method accepted.

string
resource query

RFC 8707 — which resource the credential is for. Validated as naming this instance, not merely recorded.

string

Responses

200

The request, in the user's terms.

application/json

object

An authorization request, in the terms of the person approving it.

client_name string required
client_id string required
verified boolean required

The client identified itself with a URL that could be fetched, rather than registering a name of its own choosing — which could be anything.

redirect_uri string required
scopes array required

Every grant, with the requested ones marked. A client requests; the person whose listening it is decides.

each item
object
scope string required
description string required
requested boolean required
requested string required

The requested grants, space-separated, unrecognised ones dropped.

401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}
404

Unknown client.

application/json

object

An OAuth error, as RFC 6749 shapes it.

error string required

invalid_request, invalid_client, invalid_grant, invalid_target, invalid_redirect_uri, unsupported_grant_type, server_error.

error_description string required
POST /api/v1/oauth/approve #
Session

The user said yes.

Session-only, and that is the load-bearing part of the whole flow: minting a credential requires a logged-in human on this instance. Everything before this point is a request; this is the consent.

redirect_uri is re-checked here rather than trusted from the browser — this handler is reachable directly. The resource must name this server, or RFC 8707's audience binding would record an audience and enforce nothing. The response carries iss (RFC 9207) in the redirect.

Request body required

application/json

object

The authorization request being approved, and what the person ticked.

client_id string required
redirect_uri string required
code_challenge string required
scope string | null

What the person granted — which may be more or less than the client asked for.

state string | null
resource string | null

Responses

200

Where to send the browser next.

application/json

object
redirect_to string · uri required

The client's redirect_uri with code, iss and state added.

400

invalid_client, invalid_request or invalid_target.

application/json

object

An OAuth error, as RFC 6749 shapes it.

error string required

invalid_request, invalid_client, invalid_grant, invalid_target, invalid_redirect_uri, unsupported_grant_type, server_error.

error_description string required
401

No valid session cookie or token. Also returned when a token is presented to a session-only endpoint — the endpoint does not accept tokens at all, so the scope is irrelevant.

application/json

object

Every error body in the API has this shape.

code integer · int32 required

Mirrors the HTTP status.

error string required

Human-readable. Not a stable identifier — do not branch on it.

{
  "code": 401,
  "error": "Authentication required. Log in to access this endpoint."
}