Repository navigation
MCP OAuth: expired dynamic client registration is reused forever (invalid_client), no re-registration #5284
Description
Describe the bug
For remote HTTP MCP servers that use OAuth dynamic client registration (DCR), Copilot stores the registered client in ~/.copilot/mcp-oauth-config/<hash>.json and reuses it indefinitely. It ignores the registration's expiry and does not register again when the authorization server rejects the client.
GitBook-hosted docs MCP servers (https://<site>/~gitbook/mcp, authorization server https://sites.gitbook.com/oauth2/v1/<site_id>) issue registrations that expire after 7 days. The /register response includes client_id_issued_at and client_secret_expires_at, and the difference is exactly 604800 seconds. The cached JSON only stores issuedAt, not client_secret_expires_at. After 7 days, every sign-in attempt fails at /authorize (and /token) with:
{"error":"invalid_client","error_description":"Client authentication failed- Invalid client <client_id>"}
Clicking Sign in again (in the Copilot app: Customize → Installed → Sign in) gets the same error, because the expired client is reused. The only way to recover is to delete or move the cached <hash>.json and <hash>.verifier files by hand and restart. A new registration then works immediately.
Affected version
GitHub Copilot CLI 1.0.102-0.unstable.r40141230562.g130d689 (via the GitHub Copilot app)
Steps to reproduce the behavior
- Configure a GitBook-hosted docs MCP server, for example:
{ "mcpServers": { "traksys-docs": { "type": "http", "url": "https://docs.parsec-corp.com/~gitbook/mcp" } } } - Sign in. Copilot registers a client via DCR and caches it in
~/.copilot/mcp-oauth-config/. - Wait more than 7 days (or check the registration's
client_secret_expires_at). - Sign in again. Authorization fails with
invalid_client/Client authentication failed- Invalid client …, and retrying never recovers. - Move the cached
<hash>.json/<hash>.verifieraside and restart. Sign-in succeeds with a newly registered client.
To show that a new registration works while the cached one doesn't:
curl -s -X POST https://sites.gitbook.com/oauth2/v1/<site_id>/register \
-H "Content-Type: application/json" \
-d '{"client_name":"test","redirect_uris":["http://127.0.0.1:33418/callback"],"grant_types":["authorization_code","refresh_token"],"response_types":["code"],"token_endpoint_auth_method":"none"}'
# -> client_secret_expires_at - client_id_issued_at == 604800Expected behavior
- Store
client_secret_expires_atfrom the DCR response (RFC 7591 §3.2.1) and register again before using a client that has expired. - If the authorization or token endpoint returns
invalid_clientfor a dynamically registered (non-static) client, discard the cached registration, register again, and retry sign-in automatically. - At minimum, provide a way to clear one server's cached OAuth registration (for example, a "Reset sign-in" action in the app or a CLI command) so users don't have to find hash-named files by hand.
Additional context
- OS: Windows 11 (x64); GitHub Copilot app
- The cached file contains
serverUrl,authorizationServerUrl,clientId,clientSecret,redirectUri,resourceUrl,issuedAt,isStatic: false,httpsRedirect. There's no expiry field. - Possibly related to the other MCP OAuth caching issues, but distinct: MCP OAuth tokens for HTTP servers not reliably reused across sessions — duplicate cache-key entries force repeated re-auth #4695 (duplicate cache keys), MCP OAuth re-auth repeatedly fails on Windows when the cached loopback redirect port is in an excluded TCP port range #3973 (cached redirect port), ACP mode: expired MCP OAuth access token opens the browser authorization page instead of using the stored refresh token #5229 (refresh token not used).
- We've added a workaround script for our users that moves only the affected server's cache entries aside.
Activity