inspector/core/auth/ema/resourceContext.ts
Bob Dickinson 5de4e911e3
feat(web,core): enterprise-managed authorization (EMA) (#1509) (#1512)
Adds enterprise-managed authorization (EMA): browser-side IdP login + RFC 8693 token-exchange and JWT-bearer resource-token mint, per-server EMA opt-in in Server Settings, IdP config in Client Settings, and IdP clientSecret keychain storage. Includes the v2/main merge and review follow-ups (400 on malformed client config; normalizeIdpIssuer import consolidation).
2026-06-24 22:15:12 -04:00

76 lines
2.1 KiB
TypeScript

import {
discoverAuthorizationServerMetadata,
discoverOAuthProtectedResourceMetadata,
selectResourceURL,
} from "@modelcontextprotocol/sdk/client/auth.js";
import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
import type { OAuthProtectedResourceMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
import { getAuthorizationServerUrl } from "../discovery.js";
export interface EmaResourceContext {
resourceMetadata: OAuthProtectedResourceMetadata;
resourceAsUrl: URL;
resourceUrl?: URL;
scope?: string;
}
export function resolveEmaScopes(
resourceMetadata: OAuthProtectedResourceMetadata | null | undefined,
configuredScope?: string,
): string | undefined {
const trimmed = configuredScope?.trim();
if (trimmed) return trimmed;
const supported = resourceMetadata?.scopes_supported;
if (supported && supported.length > 0) {
return supported.join(" ");
}
return undefined;
}
export async function discoverEmaResourceContext(
serverUrl: string,
configuredScope?: string,
fetchFn?: typeof fetch,
): Promise<EmaResourceContext> {
const resourceMetadata = await discoverOAuthProtectedResourceMetadata(
serverUrl,
undefined,
fetchFn,
);
if (!resourceMetadata.resource) {
throw new Error(
"EMA requires protected resource metadata with a resource identifier",
);
}
const resourceAsUrl = getAuthorizationServerUrl(serverUrl, resourceMetadata);
const resourceUrl = await selectResourceURL(
serverUrl,
{
clientMetadata: {
scope: configuredScope ?? "",
redirect_uris: [],
},
} as unknown as OAuthClientProvider,
resourceMetadata,
);
const scope = resolveEmaScopes(resourceMetadata, configuredScope);
return {
resourceMetadata,
resourceAsUrl,
resourceUrl: resourceUrl ?? undefined,
scope,
};
}
export async function discoverResourceAsMetadata(
resourceAsUrl: URL,
fetchFn?: typeof fetch,
) {
const metadata = await discoverAuthorizationServerMetadata(resourceAsUrl, {
fetchFn,
});
if (!metadata) {
throw new Error("Failed to discover resource authorization server metadata");
}
return metadata;
}