diff --git a/clients/cli/src/cli.ts b/clients/cli/src/cli.ts index ddd92d10..b5d8e42d 100644 --- a/clients/cli/src/cli.ts +++ b/clients/cli/src/cli.ts @@ -71,8 +71,8 @@ async function callMethod( serverSettings: InspectorServerSettings | undefined, args: MethodArgs & { method: string }, clientConfig: ClientConfig, - cliAuthOverrides?: RunnerClientConfigOverrides, - callbackUrlConfig?: RunnerOAuthCallbackConfig, + cliAuthOverrides: RunnerClientConfigOverrides, + callbackUrlConfig: RunnerOAuthCallbackConfig, ): Promise { const __dirname = dirname(fileURLToPath(import.meta.url)); const packageJsonPath = join(__dirname, "../package.json"); @@ -93,9 +93,8 @@ async function callMethod( }; const redirectUrlProvider = new MutableRedirectUrlProvider(); if (isOAuthCapableServerConfig(serverConfig)) { - redirectUrlProvider.redirectUrl = formatRunnerOAuthRedirectUrl( - callbackUrlConfig!, - ); + redirectUrlProvider.redirectUrl = + formatRunnerOAuthRedirectUrl(callbackUrlConfig); environment.oauth = { storage: new NodeOAuthStorage(), navigation: new ConsoleNavigation(), @@ -258,14 +257,14 @@ async function callMethod( inspectorClient, serverConfig, redirectUrlProvider, - callbackUrlConfig!, + callbackUrlConfig, serverSettings, ); const result = await withCliAuthRecoveryRetry( inspectorClient, redirectUrlProvider, - callbackUrlConfig!, + callbackUrlConfig, serverSettings, runMethod, ); diff --git a/core/mcp/oauthManager.ts b/core/mcp/oauthManager.ts index 63c8ca14..878495fa 100644 --- a/core/mcp/oauthManager.ts +++ b/core/mcp/oauthManager.ts @@ -10,6 +10,7 @@ import { EMPTY_OAUTH_FLOW_STATE } from "../auth/types.js"; import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js"; import type { OAuthClientInformation } from "@modelcontextprotocol/sdk/shared/auth.js"; import { mcpAuth } from "../auth/mcpAuth.js"; +import type { OAuthStorage } from "../auth/storage.js"; import { parseOAuthState } from "../auth/utils.js"; import type { EnterpriseManagedAuthIdpConfig } from "../client/types.js"; import type { ClientConfig } from "../client/types.js"; @@ -104,6 +105,19 @@ export class OAuthManager { return this.params.getServerUrl(); } + /** + * Return the configured OAuth storage, throwing a clear error if it is + * absent. Used on the EMA re-mint/persist paths, which are only reached for + * a fully-configured OAuth-capable server (storage present). + */ + private requireStorage(): OAuthStorage { + const storage = this.oauthConfig.storage; + if (!storage) { + throw new Error("OAuth storage is required for this operation."); + } + return storage; + } + private async createOAuthProvider(): Promise { if ( !this.oauthConfig.storage || @@ -284,7 +298,7 @@ export class OAuthManager { requestedScope, ); if (scopeToPersist) { - await this.oauthConfig.storage!.saveScope( + await this.requireStorage().saveScope( this.getServerUrl(), scopeToPersist, ); @@ -422,7 +436,7 @@ export class OAuthManager { protocol: protocolFromOAuthConfig(this.oauthConfig), configuredScope: this.oauthConfig.scope, enterpriseManagedAuth: this.params.enterpriseManagedAuth, - storage: this.oauthConfig.storage!, + storage, flowState: this.oauthFlowState ?? undefined, }); } @@ -618,7 +632,7 @@ export class OAuthManager { const silent = await trySilentEmaAuth(config); if (silent.status === "success") { if (await this.checkAuthChallengeSatisfied(enriched)) { - const minted = await this.oauthConfig.storage!.getTokens( + const minted = await this.requireStorage().getTokens( this.getServerUrl(), ); const scopeToPersist = resolvePersistedScopeAfterGrant( @@ -626,7 +640,7 @@ export class OAuthManager { enriched.authorizationScopes?.join(" "), ); if (scopeToPersist) { - await this.oauthConfig.storage!.saveScope( + await this.requireStorage().saveScope( this.getServerUrl(), scopeToPersist, ); diff --git a/test-servers/src/resolve-config.ts b/test-servers/src/resolve-config.ts index 921e54e1..331bb549 100644 --- a/test-servers/src/resolve-config.ts +++ b/test-servers/src/resolve-config.ts @@ -29,7 +29,7 @@ function resolvePresetRefs( type: "tool" | "resource" | "resourceTemplate" | "prompt", ): T[] { if (!refs || refs.length === 0) return []; - const result: T[] = []; + const result: Array<{ requiredScopes?: string[] }> = []; for (const entry of refs) { const items = Array.isArray(entry) ? entry : [entry]; for (const ref of items) { @@ -42,11 +42,13 @@ function resolvePresetRefs( const resolved = resolvePreset(type, presetName, ref.params); const arr = Array.isArray(resolved) ? resolved : [resolved]; for (const item of arr) { - result.push(mergeRequiredScopes(item as unknown as T, ref)); + result.push(mergeRequiredScopes(item, ref)); } } } - return result; + // The caller pairs `type` with the matching `T`, so each resolved preset is + // the requested definition kind; TS can't see that correspondence. + return result as T[]; } /**