Skip to content

Disconnect never revokes OAuth tokens at the authorization server (RFC 7009) #2144

Description

@jblz

Which client?

All / shared core

The problem

I inspect OAuth-protected MCP servers in the web client, mostly ones I'm building: authorize, poke at the server, disconnect, change something, authorize again, over and over.

Every one of those disconnects leaves a live grant behind. "Clear OAuth state and disconnect" deletes the Inspector's local copy of the tokens and stops there. The access token, and the refresh token when one was issued, stay valid at the authorization server until they expire on their own. From the AS's side nothing happened; the client just went quiet. By the end of the day the AS holds a pile of grants for sessions that ended hours ago, and nothing in the Inspector can see or kill them.

The other direction bites harder. The Inspector is how I check what my server actually does, and RFC 7009 revocation is the one auth behavior I can't exercise with it. If I implement revocation_endpoint, the Inspector will never call it, so I hand-roll a curl request to test it, outside the tool that exists to test exactly this. RFC 7009's introduction describes this scenario: a client invalidating its tokens when the end-user logs out or walks away, so the AS is not left holding a valid grant the user has no idea still exists. Disconnect is that moment.

Verified against v2/main at 45823ec: nothing in core/ or any client constructs a revocation request (git grep -i revok returns only URL.revokeObjectURL), and test-servers/src/test-server-oauth.ts advertises no revocation_endpoint, so there is nothing in-repo to test against either.

Solution you have in mind (optional)

Discover revocation_endpoint from the AS metadata on the clear-state path, and POST an RFC 7009 revocation there before wiping local state. Send the refresh token when there is one: the RFC asks the AS to also invalidate the access tokens issued under the same grant, so a single request covers both (§2.1).

It has to be best-effort. No advertised endpoint means do nothing, which keeps every AS without RFC 7009 support behaving exactly as today. A network error or a non-2xx gets a warning and nothing else, and a short timeout keeps a slow AS from hanging the teardown. The local clear runs regardless, so disconnect always finishes.

The three clear paths all bottom out in core, so this should only need writing once: web's clearServerOAuthState, the TUI AuthTab's "Clear OAuth State", and the CLI's --relogin all reach OAuthStorage.clear(). One of them should skip revocation — lost_authorization_state recovery in App.tsx clears a half-finished flow in order to retry it, and there is nothing valid to revoke. Testing any of this in-repo also needs test-servers/src/test-server-oauth.ts to grow a revocation_endpoint and an /oauth/revoke route.

I'd want a setting for it, and not only as an escape hatch. Disconnecting without revoking is a case I deliberately want to reproduce, to see how a server copes with a client that walks off still holding live tokens. In v1 I made it a toggle, defaulting to on.

Alternatives or workarounds you have tried

Revoking by hand with curl after each disconnect, or waiting for expiry. The first doesn't survive a fast iteration loop. The second is no answer for a refresh token, which is long-lived by design.

Already built it locally?

I built this for v1 in #1308, closed under the v1 deprecation sweep with a note from @cliffhall to re-raise it here if the gap persisted in v2. It does.

Two prompts produced the whole v1 change. The first:

I'm trying to make inspector RFC 7009 compliant. It seems like it should call the /revoke endpoint when the connection is intentionally severed via the Disconnect button.

As is, it's leaving "tombstones" of broken connection records for services to tidy up.

Confirm my understanding of the RFC and my expectation.

Some previous work around clearing the connection state was done in https://github.com/modelcontextprotocol/inspector/pull/280/changes but that code is no longer at those locations in the main branch here.

If my understanding is correct, let's work on a patch in a fresh worktree based off main

Then, for the opt-out setting:

consider if it would be worthwhile to add an option to override this behavior. This software is used to test MCP server behavior in numerous circumstances, after all. Look at the conventionality around such settings. Look at any guidelines in the codebase or in the discussion on issues / PRs.

I verified it end to end against a real OAuth-protected MCP server. The first run logged Skipping token revocation: authorization server metadata has no revocation_endpoint and did nothing.

Once the AS advertised one, disconnect POSTed to it and the token stopped being accepted. Keeping that no-op path is what makes this safe against every AS with no RFC 7009 support.

The v1 diff is on #1308 if it's useful as a reference.

Before you submit

  • I searched existing issues and this is not a duplicate.
  • This is a request for the Inspector itself, not for the MCP specification or an SDK.

Metadata

Metadata

Assignees

Labels

enhancementNew feature requestv2Issues and PRs for v2

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions