feat: revoke MCP server OAuth grants at the provider on disconnect (#27300)

Closes
[CODAGT-805](https://linear.app/codercom/issue/CODAGT-805/revoke-oauth-grants-at-the-source-for-mcp-servers).

The experimental MCP server OAuth2 disconnect endpoint previously
deleted only the stored token row, leaving the grant active at the OAuth
provider. This PR adds provider-side token revocation while keeping
local disconnect independent of provider availability.

## Changes

- Add `mcp_server_configs.oauth2_revocation_url` in migration `000547`.
The value can be configured manually, discovered from RFC 8414 metadata,
and managed through the MCP server settings UI. Non-admin responses
redact it with the other OAuth2 fields.
- Revoke the refresh token first through the RFC 7009 endpoint, then
fall back to the access token only for `unsupported_token_type`. Public
clients send `client_id`; confidential clients use
`client_secret_basic`.
- Delete the local token transactionally before best-effort provider
revocation. Callers without a token receive the same response for hidden
and nonexistent config IDs, and provider failures return a generic
warning without exposing provider response bodies.
- Require HTTPS revocation endpoints except for HTTP loopback URLs.
Redirects must preserve the POST and remain on the configured origin.
Redirect errors omit provider-controlled paths and query strings so
reflected token material cannot enter logs.
- Treat `200 OK` and `204 No Content` as completed revocations. `202
Accepted` remains a failure because it does not confirm completion.
- Prevent an in-flight refresh from recreating a token deleted by
disconnect. Refresh persistence now uses an optimistic update keyed by
token ID and `updated_at`; only the OAuth callback can create a token
row. Refresh conflicts reload the current row or clear in-memory auth
when disconnect deleted it.
- Return `{token_revoked, token_revocation_error}` from disconnect,
while retaining SDK compatibility with the legacy `204` response. The UI
surfaces provider revocation failures as warning toasts.
- Document revocation endpoint discovery, HTTPS requirements, and
best-effort disconnect behavior.

No token or no configured revocation URL returns `token_revoked: false`
without an error, so disconnect remains idempotent.

> Updated by Mux, an AI coding agent, on Mike's behalf.
This commit is contained in:
Michael Suchacz
2026-07-20 00:14:03 +02:00
committed by GitHub
parent 33fcc9de68
commit 9f4ddea571
31 changed files with 1810 additions and 200 deletions
@@ -75,10 +75,14 @@ each user independently completes the authorization flow.
Optional fields:
| Field | Description |
|------------------------|---------------------------------|
| `oauth2_client_secret` | OAuth2 client secret. |
| `oauth2_scopes` | Space-separated list of scopes. |
| Field | Description |
|-------------------------|-------------------------------------------|
| `oauth2_client_secret` | OAuth2 client secret. |
| `oauth2_scopes` | Space-separated list of scopes. |
| `oauth2_revocation_url` | Token revocation endpoint URL (RFC 7009). |
The revocation endpoint must use HTTPS.
Loopback URLs may use HTTP for local development and tests.
**Auto-discovery** — leave `oauth2_client_id`, `oauth2_auth_url`, and
`oauth2_token_url` empty. The server attempts discovery in this order:
@@ -87,9 +91,17 @@ Optional fields:
1. RFC 8414 — Authorization Server Metadata
1. RFC 7591 — Dynamic Client Registration
Auto-discovery also records the provider's `revocation_endpoint` from the
RFC 8414 metadata when advertised. An explicit `oauth2_revocation_url` in
the request takes precedence over the discovered value.
Users connect through a popup that redirects through the OAuth2 provider.
Tokens are stored per-user and refreshed automatically. Users can disconnect
via the UI or API to remove stored tokens.
via the UI or API to remove stored tokens. When a revocation endpoint is
configured, disconnecting also asks the provider to revoke the token
(RFC 7009). Provider revocation is best-effort: the stored token is always
deleted from Coder, and the disconnect response reports whether provider
revocation succeeded via `token_revoked` and `token_revocation_error`.
### API key