Copilot: `runClear`'s double-click guard deduped by `server.id`, which is the same identity mismatch this PR fixes, one layer up. Two catalog entries against one URL share one credential blob, one grant and one revocation, so an id-keyed guard let exactly the race it exists to prevent through between them — concurrent store writes, concurrent RFC 7009 requests, and two contradictory teardown toasts. The guard now keys on `oauthClearKey`, a pure helper that resolves the OAuth storage key and falls back to the entry id for a config that has none (stdio), where there is no shared state to collide over. The id fallback keeps such entries distinct from each other rather than collapsing them onto one key, and both forms are prefixed so a user-supplied id can never impersonate a URL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VGbw6TezNWnXZSCSp7sqb4 Signed-off-by: cliffhall <cliff@futurescale.com>
23 lines
1.1 KiB
TypeScript
23 lines
1.1 KiB
TypeScript
import { getOAuthServerUrl } from "@inspector/core/mcp/config.js";
|
|
import type { MCPServerConfig } from "@inspector/core/mcp/types.js";
|
|
|
|
/**
|
|
* The identity a *clear* of stored OAuth state acts on.
|
|
*
|
|
* Persisted OAuth state is keyed by the server URL, and `core/mcp/serverList`
|
|
* enforces no URL uniqueness — so two catalog entries with distinct ids can
|
|
* point at one URL and therefore share one credential blob, one live grant and
|
|
* one revocation. Anything that dedupes, locks or compares *clears* has to key
|
|
* on that shared identity rather than on the entry id, which cannot see it
|
|
* (#2217).
|
|
*
|
|
* A config with no OAuth server URL (stdio, and anything else
|
|
* `getOAuthServerUrl` declines) has no shared blob to collide over, so it falls
|
|
* back to the entry id — which keeps every such entry distinct from every
|
|
* other, rather than collapsing them all onto one shared key.
|
|
*/
|
|
export function oauthClearKey(config: MCPServerConfig, id: string): string {
|
|
const url = getOAuthServerUrl(config);
|
|
// Prefixed so a URL-keyed entry can never collide with an id-keyed one.
|
|
return url !== undefined ? `url:${url}` : `id:${id}`;
|
|
}
|