Adds two optional per-server fields — `oauth.authorizationUrl` and `oauth.tokenUrl` — that override the endpoints authorization-server metadata discovery resolved, so a server can be pointed at a development or staging authorization server without changing what it advertises. SDK v2 routes both endpoints through the discovered metadata document and neither through OAuthClientProvider, so the override is applied by wrapping the client's base fetch and patching the metadata document in flight (core/auth/endpointOverrides.ts). Wrapping the base fetch — rather than only the auth fetch — also covers the discovery the SDK runs from inside the transport on the 401/refresh path. The fields round-trip through mcp.json, the /api/servers routes, the runner (CLI/TUI), and the Server Settings → Authorization panel, where a value that is not an absolute http(s) URL is flagged inline; at runtime such a value is dropped with a single warning rather than failing the connection. Signed-off-by: cliffhall <cliff@futurescale.com>
144 lines
4.4 KiB
TypeScript
144 lines
4.4 KiB
TypeScript
/**
|
|
* Install-level client config loading and InspectorClient auth option wiring
|
|
* for Node runners (TUI, CLI).
|
|
*/
|
|
|
|
import {
|
|
KeyringSecretStore,
|
|
type SecretStore,
|
|
} from "../auth/node/secret-store.js";
|
|
import type {
|
|
InspectorClientOptions,
|
|
InspectorServerSettings,
|
|
} from "../mcp/types.js";
|
|
import {
|
|
oauthAuthorizationParamsFromSettings,
|
|
oauthEndpointOverridesFromSettings,
|
|
} from "../mcp/serverList.js";
|
|
import { loadClientConfig } from "./config.js";
|
|
import type { ClientConfig } from "./types.js";
|
|
import {
|
|
getActiveCimdClientMetadataUrl,
|
|
getActiveEnterpriseManagedAuthIdp,
|
|
} from "./types.js";
|
|
|
|
export interface LoadRunnerClientConfigOptions {
|
|
/** Explicit path from `--client-config` (or MCP_CLIENT_CONFIG_PATH when unset). */
|
|
clientConfigPath?: string;
|
|
/** Secret store for the IdP clientSecret; defaults to the OS keychain. Tests
|
|
* inject an in-memory store for determinism. */
|
|
secretStore?: SecretStore;
|
|
}
|
|
|
|
/** Load install-level client.json with keychain-backed IdP secrets. */
|
|
export async function loadRunnerClientConfig(
|
|
options?: LoadRunnerClientConfigOptions,
|
|
): Promise<ClientConfig> {
|
|
const customPath =
|
|
options?.clientConfigPath?.trim() ||
|
|
process.env.MCP_CLIENT_CONFIG_PATH?.trim() ||
|
|
undefined;
|
|
const secretStore = options?.secretStore ?? new KeyringSecretStore();
|
|
return loadClientConfig({ filePath: customPath, secretStore });
|
|
}
|
|
|
|
export interface RunnerClientConfigOverrides {
|
|
clientId?: string;
|
|
clientSecret?: string;
|
|
clientMetadataUrl?: string;
|
|
}
|
|
|
|
/** HTTP transports (SSE, streamable-http) can use OAuth. */
|
|
export function isOAuthCapableServerConfig(
|
|
config: { type?: string } | null | undefined,
|
|
): boolean {
|
|
if (!config) return false;
|
|
return config.type === "sse" || config.type === "streamable-http";
|
|
}
|
|
|
|
/**
|
|
* Derive OAuth / EMA / CIMD InspectorClient options from install client.json,
|
|
* per-server settings, and CLI flag overrides (flags win over client.json).
|
|
*/
|
|
export function buildRunnerClientAuthOptions(
|
|
clientConfig: ClientConfig,
|
|
savedSettings?: InspectorServerSettings,
|
|
cliOverrides?: RunnerClientConfigOverrides,
|
|
): Pick<
|
|
InspectorClientOptions,
|
|
| "oauth"
|
|
| "enterpriseManagedAuth"
|
|
| "installEnterpriseManagedAuth"
|
|
| "directAuthRecovery"
|
|
> {
|
|
const activeIdp = getActiveEnterpriseManagedAuthIdp(clientConfig);
|
|
const activeCimdUrl = getActiveCimdClientMetadataUrl(clientConfig);
|
|
|
|
const serverAuthorizationParams = savedSettings
|
|
? oauthAuthorizationParamsFromSettings(savedSettings)
|
|
: undefined;
|
|
const serverEndpointOverrides = savedSettings
|
|
? oauthEndpointOverridesFromSettings(savedSettings)
|
|
: undefined;
|
|
|
|
const oauthFromServer =
|
|
savedSettings &&
|
|
(savedSettings.oauthClientId ||
|
|
savedSettings.oauthClientSecret ||
|
|
savedSettings.oauthScopes ||
|
|
serverAuthorizationParams ||
|
|
serverEndpointOverrides ||
|
|
savedSettings.enterpriseManaged)
|
|
? {
|
|
...(savedSettings.oauthClientId && {
|
|
clientId: savedSettings.oauthClientId,
|
|
}),
|
|
...(savedSettings.oauthClientSecret && {
|
|
clientSecret: savedSettings.oauthClientSecret,
|
|
}),
|
|
...(savedSettings.oauthScopes && {
|
|
scope: savedSettings.oauthScopes,
|
|
}),
|
|
...(serverAuthorizationParams && {
|
|
authorizationParams: serverAuthorizationParams,
|
|
}),
|
|
...serverEndpointOverrides,
|
|
...(savedSettings.enterpriseManaged && {
|
|
enterpriseManaged: true,
|
|
}),
|
|
}
|
|
: undefined;
|
|
|
|
const clientMetadataUrl =
|
|
cliOverrides?.clientMetadataUrl?.trim() || activeCimdUrl;
|
|
|
|
const oauthFromCli =
|
|
cliOverrides?.clientId || cliOverrides?.clientSecret || clientMetadataUrl
|
|
? {
|
|
...(cliOverrides?.clientId && { clientId: cliOverrides.clientId }),
|
|
...(cliOverrides?.clientSecret && {
|
|
clientSecret: cliOverrides.clientSecret,
|
|
}),
|
|
...(clientMetadataUrl && { clientMetadataUrl }),
|
|
}
|
|
: undefined;
|
|
|
|
const oauth =
|
|
oauthFromServer || oauthFromCli
|
|
? {
|
|
...(oauthFromServer ?? {}),
|
|
...(oauthFromCli ?? {}),
|
|
}
|
|
: undefined;
|
|
|
|
return {
|
|
...(oauth && { oauth }),
|
|
...(activeIdp && {
|
|
enterpriseManagedAuth: { idp: activeIdp },
|
|
}),
|
|
...(clientConfig.enterpriseManagedAuth && {
|
|
installEnterpriseManagedAuth: clientConfig.enterpriseManagedAuth,
|
|
}),
|
|
...(oauth && { directAuthRecovery: true }),
|
|
};
|
|
}
|