* refactor(web): update OAuth E2E tests to use new authentication method - Replaced `runGuidedAuth` with `authenticate` in the InspectorClient OAuth end-to-end tests to support quick authentication with CIMD pre-registration. - Updated comments to reflect the change in authentication method and its support for HTTP test metadata URLs. - Ensured that the tests continue to validate the expected authorization URL after the change. Additionally, imported `ensureCimdClientRegistration` in the OAuthManager to streamline client registration during the authentication process. * Guided-auth removal, OAuth smoke testing, and TUI/CLI support * Added CLI OAuth runner flags tests * Added clarifying comment on port 0 binding * README notes on TUI oauth callback port, prettier fixes for CLI tests * CIMD validation uses inline like EMA IdP * Fixed TUI test failures from merge * test: bring 5 OAuth files over the 90 per-file coverage gate The guided-auth-removal work added/touched several files that fell below the repo's >=90 per-file coverage gate (npm run coverage). CI doesn't run the gate (it's local-only), so validate + test:integration passed while these gaps slipped through. Bring them all back over the line: - core/auth/node/runner-oauth-callback.ts: test bad-URL/non-http-scheme throws, no-port->80 default, and IPv6 hostname bracketing. Annotate the provably-dead guards (empty host, out-of-range port, String(err) fallback, pathname "/" fallback) -- all rejected by new URL() upstream -- with justified v8 ignore comments instead of lowering the gate. - core/auth/cimd.ts: test the AS-doesn't-support-CIMD early return. - core/react/useClientSettingsDraft.ts: test the functional-updater path, the stale-onChange-after-close prev===null guard, and the timer-fires-after-close value===null guard. - ClientSettingsForm.tsx: test the CIMD checkbox toggle and CIMD URL clear. - clients/tui/src/utils/oauthDisplay.ts: test static/dcr registration kinds, the expired IdP session, and the configured-scope/empty-scope branches of formatScopes. npm run coverage now passes (exit 0) across web, cli, tui, launcher. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: cliffhall <cliff@futurescale.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
/**
|
||
* OAuth store factory using Zustand.
|
||
* Creates a store with any storage adapter (file, remote, sessionStorage).
|
||
*/
|
||
|
||
import { createStore } from "zustand/vanilla";
|
||
import { persist, createJSONStorage } from "zustand/middleware";
|
||
import type {
|
||
OAuthClientInformation,
|
||
OAuthTokens,
|
||
OAuthMetadata,
|
||
} from "@modelcontextprotocol/sdk/shared/auth.js";
|
||
import type { IdpSessionState, OAuthClientRegistrationKind } from "./storage.js";
|
||
|
||
/**
|
||
* OAuth state for a single server
|
||
*/
|
||
export interface ServerOAuthState {
|
||
clientInformation?: OAuthClientInformation;
|
||
/** Set when {@link clientInformation} is saved — DCR vs CIMD. */
|
||
clientRegistrationKind?: OAuthClientRegistrationKind;
|
||
preregisteredClientInformation?: OAuthClientInformation;
|
||
tokens?: OAuthTokens;
|
||
codeVerifier?: string;
|
||
scope?: string;
|
||
serverMetadata?: OAuthMetadata;
|
||
/** Set when resource tokens were minted via EMA (legs 2–3). */
|
||
enterpriseManaged?: boolean;
|
||
}
|
||
|
||
/**
|
||
* Zustand store state (all servers)
|
||
*/
|
||
export interface OAuthStoreState {
|
||
servers: Record<string, ServerOAuthState>;
|
||
idpSessions: Record<string, IdpSessionState>;
|
||
getServerState: (serverUrl: string) => ServerOAuthState;
|
||
setServerState: (serverUrl: string, state: Partial<ServerOAuthState>) => void;
|
||
clearServerState: (serverUrl: string) => void;
|
||
getIdpSession: (issuer: string) => IdpSessionState;
|
||
setIdpSession: (issuer: string, updates: Partial<IdpSessionState>) => void;
|
||
clearIdpSession: (issuer: string) => void;
|
||
clearEnterpriseManagedResourceServers: () => void;
|
||
}
|
||
|
||
/**
|
||
* Creates a Zustand store for OAuth state with the given storage adapter.
|
||
* The storage adapter handles persistence (file, remote HTTP, sessionStorage, etc.).
|
||
*
|
||
* @param storage - Zustand storage adapter (from createJSONStorage)
|
||
* @returns Zustand store instance
|
||
*/
|
||
export function createOAuthStore(
|
||
storage: ReturnType<typeof createJSONStorage>,
|
||
) {
|
||
return createStore<OAuthStoreState>()(
|
||
persist(
|
||
(set, get) => ({
|
||
servers: {},
|
||
idpSessions: {},
|
||
getServerState: (serverUrl: string) => {
|
||
return get().servers[serverUrl] || {};
|
||
},
|
||
setServerState: (
|
||
serverUrl: string,
|
||
updates: Partial<ServerOAuthState>,
|
||
) => {
|
||
set((state) => ({
|
||
servers: {
|
||
...state.servers,
|
||
[serverUrl]: {
|
||
...state.servers[serverUrl],
|
||
...updates,
|
||
},
|
||
},
|
||
}));
|
||
},
|
||
clearServerState: (serverUrl: string) => {
|
||
set((state) => {
|
||
const rest = { ...state.servers };
|
||
delete rest[serverUrl];
|
||
return { servers: rest };
|
||
});
|
||
},
|
||
getIdpSession: (issuer: string) => {
|
||
return get().idpSessions[issuer] || {};
|
||
},
|
||
setIdpSession: (issuer: string, updates: Partial<IdpSessionState>) => {
|
||
set((state) => ({
|
||
idpSessions: {
|
||
...state.idpSessions,
|
||
[issuer]: {
|
||
...state.idpSessions[issuer],
|
||
...updates,
|
||
},
|
||
},
|
||
}));
|
||
},
|
||
clearIdpSession: (issuer: string) => {
|
||
set((state) => {
|
||
const rest = { ...state.idpSessions };
|
||
delete rest[issuer];
|
||
return { idpSessions: rest };
|
||
});
|
||
},
|
||
clearEnterpriseManagedResourceServers: () => {
|
||
set((state) => {
|
||
const rest = { ...state.servers };
|
||
for (const [url, serverState] of Object.entries(state.servers)) {
|
||
if (serverState.enterpriseManaged === true) {
|
||
delete rest[url];
|
||
}
|
||
}
|
||
return { servers: rest };
|
||
});
|
||
},
|
||
}),
|
||
{
|
||
name: "mcp-inspector-oauth",
|
||
storage,
|
||
},
|
||
),
|
||
);
|
||
}
|