MCP Clients
An MCP client is handed one thing: an App's MCP endpoint address. Everything else (which authorization server to use, how to register, where to send the user) it discovers. These are the endpoints that make that possible.
The authorization server itself is the one documented under Authorization Code Flow: the same /oauth/authorize and /oauth/access_token with PKCE. Nothing here replaces it.
Key Concepts
Discovery runs backwards from a 401. The MCP endpoint's 401 names its protected resource metadata; that document names the authorization server; the authorization server's metadata names the authorize, token, and (when enabled) registration endpoints.
Dynamic client registration lets a client create its own OAuth client record before any user is involved. It is off per tenant until an administrator enables it. When off, the registration endpoint answers 404 and the authorization server metadata omits it, so a client sees "this server does not offer registration" rather than "you are not allowed".
Resource binding (RFC 8707) ties an access token to a single App. A token minted for one App's MCP endpoint authorizes that endpoint and nothing else: not another App's endpoint, and not the rest of the API.
Discovery Endpoints
GET /.well-known/oauth-authorization-server
Authorization server metadata (RFC 8414). Served at the server root, not under /api.
Authentication: None.
Response: issuer, authorization_endpoint, token_endpoint, response_types_supported, grant_types_supported, code_challenge_methods_supported, token_endpoint_auth_methods_supported, and scopes_supported. registration_endpoint appears only while the tenant allows dynamic client registration.
GET /.well-known/oauth-protected-resource/api/apps/{id}/mcp
Protected resource metadata (RFC 9728) for one App's MCP endpoint. The path after the well-known prefix mirrors the resource's own path, per the specification.
Authentication: None.
Response: resource (the endpoint address), authorization_servers, bearer_methods_supported, and scopes_supported.
This document deliberately does not look the App up. It is served to anonymous callers and answers identically whether or not the App exists, so it cannot be used to enumerate Apps. Only an authenticated call to the endpoint itself reveals that.
Registration
POST /api/oauth/register
Dynamic client registration (RFC 7591).
Authentication: None, by design: a client registers before it has a user. Gated instead by the tenant's oauthDynamicRegistration setting (Administration > Settings > Security), which a Superuser controls through the Tenant settings API.
Payload: redirect_uris (required), client_name, client_uri, and token_endpoint_auth_method (none, client_secret_basic, or client_secret_post).
Response: 201 with client_id, client_id_issued_at, the echoed registration, and grant_types / response_types. A client_secret is returned only when the client asked for a secret-bearing auth method, and only in this response; it is stored hashed and is unrecoverable afterward.
Registered clients are constrained to the safest shape this server supports: PKCE is forced on, grants are limited to authorization_code and refresh_token, and the grantable scope is capped at mcp. A dynamically registered client that asks for anything else is refused with invalid_scope, even if the user consents.
redirect_uris must be absolute. https is accepted anywhere; http only on a loopback host (localhost, 127.0.0.1, [::1]) for native-app callback servers; private-use schemes (cursor://, vscode://) are accepted for native clients. javascript:, data:, blob:, vbscript:, file:, and about: are rejected outright.
Response when disabled: 404.
Binding a token to an App
The authorize and token endpoints accept a resource parameter carrying an App's MCP endpoint address.
At /oauth/authorize, the resource is resolved to its App as the consenting user, so a user can only authorize a client for an App that is shared with them; otherwise the redirect carries error=invalid_target. The consent page uses the resolved App to show its name and icon.
At /oauth/access_token, a resource that differs from the one the code was minted for is rejected. The issued token records the App, and the mcp scope authorizes only that App's MCP endpoint.
Because the binding names the App, renaming the App invalidates tokens addressed by its natural id until the user authorizes again. Addressing the endpoint by uuid avoids this.