Skip to content

Delegated MCP token cache ignores platform disconnect — revoked grant keeps working for the token lifetime (~1h) #380

Description

@initializ-mk

Symptom

After a user disconnects a delegated MCP account (Connected Accounts / DELETE /mcp/connections/<ref> on the platform), the agent keeps acting as that user for up to the access-token lifetime (~1h for Atlassian) — a new execution kept returning Jira results well after the account was disconnected (field report).

Root cause

Forge's managed delegated path (forge-core/mcp/server.go case "user"newDelegatedTokenSource) caches the per-subject access token in an internal SubjectTokenStore, TTL = the platform's expires_in (platform_token.go store.Put(subject, tok, ttl)). Get returns it until expiry.

A platform disconnect only deletes the vault grant (agent-builder handleDeleteMCPConnectionDeleteUserGrant). It has no channel to forge's in-memory cache, and SubjectTokenStore.Evict is never called on the managed path. The provider's access token is also still valid at the provider, so nothing downstream fails either. Net: the cached token keeps working until its TTL lapses.

Note forge fetches from the platform token endpoint, which is the grant authority — after disconnect it returns 403/404 for that subject. Forge just isn't asking often enough.

Fix (this PR) — bound the window

Cap the delegated per-subject token cache TTL to a short revoke window (default 5 min), so forge re-validates against the platform within that bound regardless of the provider token's longer lifetime. After disconnect, the next re-fetch (≤ cap) gets ErrNoToken from the platform and the call is denied — and with #377 (call-time auth gate) it parks instead of erroring. Reduces the disconnect window from ~1h to ≤ the cap. Self-contained; no cross-repo change; transparent to connected users (just a more frequent, cheap platform token call for active user-mode subjects).

Follow-up — instant revocation (separate issue/PR)

A platform→forge POST /mcp/revoke {subject, server} (companion to /mcp/consent) evicting the token cache + pooled connection immediately on disconnect. Requires:

  • making the managed delegated source's SubjectTokenStore externally reachable (today it's a private per-source memStore; the standalone Standalone MCP consent delivery: build authorize URL + deliver login link (#317 follow-up) #332 path already uses the shared ManagerDeps.SubjectStore),
  • subjectConnPool.EvictSubject(subject) (today Evict reads the subject from ctx only),
  • a Manager.Revoke(server, subject) + the endpoint/handler/auth,
  • agent-builder fan-out on disconnect (mirrors signalConsentListAgentIDsUsingTool + a postAgentRevoke).
    Plus downstream 401/403 eviction for provider-side revocation (a different scenario from platform disconnect).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions