feat(planning): grille hebdomadaire complète avec API et filtres

- Connexion API via proxy Angular (résolution CORS, base path /api)
- Import CSS ng-zorro global pour les modales et composants
- Filtres Camion/Show câblés sur l'affichage de la grille
- Camions affichés via TrucksService (linkés au show du même créneau)
- Panneau de détails : spectacles + camions du jour sélectionné
- Modale de création de spectacle stylisée avec fond et centrage
- Positionnement précis des events à la minute dans leur créneau
- Auto-scroll vers l'heure courante au chargement
- Ligne "maintenant" sur la colonne du jour actuel
- Régénération des services OpenAPI (nouveaux noms de types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:36:03 +02:00
parent 150b97cd2e
commit 654b297e2e
3131 changed files with 149304 additions and 104334 deletions
+21 -141
View File
@@ -1,10 +1,6 @@
import { OAuthClientMetadata, OAuthClientInformationMixed, OAuthTokens, OAuthMetadata, OAuthClientInformationFull, OAuthProtectedResourceMetadata, AuthorizationServerMetadata } from '../shared/auth.js';
import { OAuthError } from '../server/auth/errors.js';
import { FetchLike } from '../shared/transport.js';
/**
* Function type for adding client authentication to token requests.
*/
export type AddClientAuthentication = (headers: Headers, params: URLSearchParams, url: string | URL, metadata?: AuthorizationServerMetadata) => void | Promise<void>;
import { OAuthClientMetadata, OAuthClientInformation, OAuthTokens, OAuthMetadata, OAuthClientInformationFull, OAuthProtectedResourceMetadata, AuthorizationServerMetadata } from "../shared/auth.js";
import { OAuthError } from "../server/auth/errors.js";
import { FetchLike } from "../shared/transport.js";
/**
* Implements an end-to-end OAuth client to be used with one MCP server.
*
@@ -15,14 +11,8 @@ export type AddClientAuthentication = (headers: Headers, params: URLSearchParams
export interface OAuthClientProvider {
/**
* The URL to redirect the user agent to after authorization.
* Return undefined for non-interactive flows that don't require user interaction
* (e.g., client_credentials, jwt-bearer).
*/
get redirectUrl(): string | URL | undefined;
/**
* External URL the server should use to fetch client metadata document
*/
clientMetadataUrl?: string;
get redirectUrl(): string | URL;
/**
* Metadata about this OAuth client.
*/
@@ -36,7 +26,7 @@ export interface OAuthClientProvider {
* server, or returns `undefined` if the client is not registered with the
* server.
*/
clientInformation(): OAuthClientInformationMixed | undefined | Promise<OAuthClientInformationMixed | undefined>;
clientInformation(): OAuthClientInformation | undefined | Promise<OAuthClientInformation | undefined>;
/**
* If implemented, this permits the OAuth client to dynamically register with
* the server. Client information saved this way should later be read via
@@ -45,7 +35,7 @@ export interface OAuthClientProvider {
* This method is not required to be implemented if client information is
* statically known (e.g., pre-registered).
*/
saveClientInformation?(clientInformation: OAuthClientInformationMixed): void | Promise<void>;
saveClientInformation?(clientInformation: OAuthClientInformationFull): void | Promise<void>;
/**
* Loads any existing OAuth tokens for the current session, or returns
* `undefined` if there are no saved tokens.
@@ -88,7 +78,7 @@ export interface OAuthClientProvider {
* @param url - The token endpoint URL being called
* @param metadata - Optional OAuth metadata for the server, which may include supported authentication methods
*/
addClientAuthentication?: AddClientAuthentication;
addClientAuthentication?(headers: Headers, params: URLSearchParams, url: string | URL, metadata?: AuthorizationServerMetadata): void | Promise<void>;
/**
* If defined, overrides the selection and validation of the
* RFC 8707 Resource Indicator. If left undefined, default
@@ -103,62 +93,11 @@ export interface OAuthClientProvider {
* This avoids requiring the user to intervene manually.
*/
invalidateCredentials?(scope: 'all' | 'client' | 'tokens' | 'verifier'): void | Promise<void>;
/**
* Prepares grant-specific parameters for a token request.
*
* This optional method allows providers to customize the token request based on
* the grant type they support. When implemented, it returns the grant type and
* any grant-specific parameters needed for the token exchange.
*
* If not implemented, the default behavior depends on the flow:
* - For authorization code flow: uses code, code_verifier, and redirect_uri
* - For client_credentials: detected via grant_types in clientMetadata
*
* @param scope - Optional scope to request
* @returns Grant type and parameters, or undefined to use default behavior
*
* @example
* // For client_credentials grant:
* prepareTokenRequest(scope) {
* return {
* grantType: 'client_credentials',
* params: scope ? { scope } : {}
* };
* }
*
* @example
* // For authorization_code grant (default behavior):
* async prepareTokenRequest() {
* return {
* grantType: 'authorization_code',
* params: {
* code: this.authorizationCode,
* code_verifier: await this.codeVerifier(),
* redirect_uri: String(this.redirectUrl)
* }
* };
* }
*/
prepareTokenRequest?(scope?: string): URLSearchParams | Promise<URLSearchParams | undefined> | undefined;
}
export type AuthResult = 'AUTHORIZED' | 'REDIRECT';
export type AuthResult = "AUTHORIZED" | "REDIRECT";
export declare class UnauthorizedError extends Error {
constructor(message?: string);
}
type ClientAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none';
/**
* Determines the best client authentication method to use based on server support and client configuration.
*
* Priority order (highest to lowest):
* 1. client_secret_basic (if client secret is available)
* 2. client_secret_post (if client secret is available)
* 3. none (for public clients)
*
* @param clientInformation - OAuth client information containing credentials
* @param supportedMethods - Authentication methods supported by the authorization server
* @returns The selected authentication method
*/
export declare function selectClientAuthMethod(clientInformation: OAuthClientInformationMixed, supportedMethods: string[]): ClientAuthMethod;
/**
* Parses an OAuth error response from a string or Response object.
*
@@ -184,23 +123,9 @@ export declare function auth(provider: OAuthClientProvider, options: {
resourceMetadataUrl?: URL;
fetchFn?: FetchLike;
}): Promise<AuthResult>;
/**
* SEP-991: URL-based Client IDs
* Validate that the client_id is a valid URL with https scheme
*/
export declare function isHttpsUrl(value?: string): boolean;
export declare function selectResourceURL(serverUrl: string | URL, provider: OAuthClientProvider, resourceMetadata?: OAuthProtectedResourceMetadata): Promise<URL | undefined>;
/**
* Extract resource_metadata, scope, and error from WWW-Authenticate header.
*/
export declare function extractWWWAuthenticateParams(res: Response): {
resourceMetadataUrl?: URL;
scope?: string;
error?: string;
};
/**
* Extract resource_metadata from response header.
* @deprecated Use `extractWWWAuthenticateParams` instead.
*/
export declare function extractResourceMetadataUrl(res: Response): URL | undefined;
/**
@@ -221,7 +146,7 @@ export declare function discoverOAuthProtectedResourceMetadata(serverUrl: string
*
* @deprecated This function is deprecated in favor of `discoverAuthorizationServerMetadata`.
*/
export declare function discoverOAuthMetadata(issuer: string | URL, { authorizationServerUrl, protocolVersion }?: {
export declare function discoverOAuthMetadata(issuer: string | URL, { authorizationServerUrl, protocolVersion, }?: {
authorizationServerUrl?: string | URL;
protocolVersion?: string;
}, fetchFn?: FetchLike): Promise<OAuthMetadata | undefined>;
@@ -229,7 +154,8 @@ export declare function discoverOAuthMetadata(issuer: string | URL, { authorizat
* Builds a list of discovery URLs to try for authorization server metadata.
* URLs are returned in priority order:
* 1. OAuth metadata at the given URL
* 2. OIDC metadata endpoints at the given URL
* 2. OAuth metadata at root (if URL has path)
* 3. OIDC metadata endpoints
*/
export declare function buildDiscoveryUrls(authorizationServerUrl: string | URL): {
url: URL;
@@ -251,16 +177,16 @@ export declare function buildDiscoveryUrls(authorizationServerUrl: string | URL)
* @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
* @returns Promise resolving to authorization server metadata, or undefined if discovery fails
*/
export declare function discoverAuthorizationServerMetadata(authorizationServerUrl: string | URL, { fetchFn, protocolVersion }?: {
export declare function discoverAuthorizationServerMetadata(authorizationServerUrl: string | URL, { fetchFn, protocolVersion, }?: {
fetchFn?: FetchLike;
protocolVersion?: string;
}): Promise<AuthorizationServerMetadata | undefined>;
/**
* Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
*/
export declare function startAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, redirectUrl, scope, state, resource }: {
export declare function startAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, redirectUrl, scope, state, resource, }: {
metadata?: AuthorizationServerMetadata;
clientInformation: OAuthClientInformationMixed;
clientInformation: OAuthClientInformation;
redirectUrl: string | URL;
scope?: string;
state?: string;
@@ -269,18 +195,6 @@ export declare function startAuthorization(authorizationServerUrl: string | URL,
authorizationUrl: URL;
codeVerifier: string;
}>;
/**
* Prepares token request parameters for an authorization code exchange.
*
* This is the default implementation used by fetchToken when the provider
* doesn't implement prepareTokenRequest.
*
* @param authorizationCode - The authorization code received from the authorization endpoint
* @param codeVerifier - The PKCE code verifier
* @param redirectUri - The redirect URI used in the authorization request
* @returns URLSearchParams for the authorization_code grant
*/
export declare function prepareAuthorizationCodeRequest(authorizationCode: string, codeVerifier: string, redirectUri: string | URL): URLSearchParams;
/**
* Exchanges an authorization code for an access token with the given server.
*
@@ -293,14 +207,14 @@ export declare function prepareAuthorizationCodeRequest(authorizationCode: strin
* @returns Promise resolving to OAuth tokens
* @throws {Error} When token exchange fails or authentication is invalid
*/
export declare function exchangeAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn }: {
export declare function exchangeAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn, }: {
metadata?: AuthorizationServerMetadata;
clientInformation: OAuthClientInformationMixed;
clientInformation: OAuthClientInformation;
authorizationCode: string;
codeVerifier: string;
redirectUri: string | URL;
resource?: URL;
addClientAuthentication?: OAuthClientProvider['addClientAuthentication'];
addClientAuthentication?: OAuthClientProvider["addClientAuthentication"];
fetchFn?: FetchLike;
}): Promise<OAuthTokens>;
/**
@@ -315,54 +229,20 @@ export declare function exchangeAuthorization(authorizationServerUrl: string | U
* @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
* @throws {Error} When token refresh fails or authentication is invalid
*/
export declare function refreshAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }: {
export declare function refreshAuthorization(authorizationServerUrl: string | URL, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn, }: {
metadata?: AuthorizationServerMetadata;
clientInformation: OAuthClientInformationMixed;
clientInformation: OAuthClientInformation;
refreshToken: string;
resource?: URL;
addClientAuthentication?: OAuthClientProvider['addClientAuthentication'];
fetchFn?: FetchLike;
}): Promise<OAuthTokens>;
/**
* Unified token fetching that works with any grant type via provider.prepareTokenRequest().
*
* This function provides a single entry point for obtaining tokens regardless of the
* OAuth grant type. The provider's prepareTokenRequest() method determines which grant
* to use and supplies the grant-specific parameters.
*
* @param provider - OAuth client provider that implements prepareTokenRequest()
* @param authorizationServerUrl - The authorization server's base URL
* @param options - Configuration for the token request
* @returns Promise resolving to OAuth tokens
* @throws {Error} When provider doesn't implement prepareTokenRequest or token fetch fails
*
* @example
* // Provider for client_credentials:
* class MyProvider implements OAuthClientProvider {
* prepareTokenRequest(scope) {
* const params = new URLSearchParams({ grant_type: 'client_credentials' });
* if (scope) params.set('scope', scope);
* return params;
* }
* // ... other methods
* }
*
* const tokens = await fetchToken(provider, authServerUrl, { metadata });
*/
export declare function fetchToken(provider: OAuthClientProvider, authorizationServerUrl: string | URL, { metadata, resource, authorizationCode, fetchFn }?: {
metadata?: AuthorizationServerMetadata;
resource?: URL;
/** Authorization code for the default authorization_code grant flow */
authorizationCode?: string;
addClientAuthentication?: OAuthClientProvider["addClientAuthentication"];
fetchFn?: FetchLike;
}): Promise<OAuthTokens>;
/**
* Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
*/
export declare function registerClient(authorizationServerUrl: string | URL, { metadata, clientMetadata, fetchFn }: {
export declare function registerClient(authorizationServerUrl: string | URL, { metadata, clientMetadata, fetchFn, }: {
metadata?: AuthorizationServerMetadata;
clientMetadata: OAuthClientMetadata;
fetchFn?: FetchLike;
}): Promise<OAuthClientInformationFull>;
export {};
//# sourceMappingURL=auth.d.ts.map
File diff suppressed because one or more lines are too long
+202 -327
View File
@@ -1,19 +1,14 @@
import pkceChallenge from 'pkce-challenge';
import { LATEST_PROTOCOL_VERSION } from '../types.js';
import { OAuthErrorResponseSchema, OpenIdProviderDiscoveryMetadataSchema } from '../shared/auth.js';
import { OAuthClientInformationFullSchema, OAuthMetadataSchema, OAuthProtectedResourceMetadataSchema, OAuthTokensSchema } from '../shared/auth.js';
import { checkResourceAllowed, resourceUrlFromServerUrl } from '../shared/auth-utils.js';
import { InvalidClientError, InvalidClientMetadataError, InvalidGrantError, OAUTH_ERRORS, OAuthError, ServerError, UnauthorizedClientError } from '../server/auth/errors.js';
import pkceChallenge from "pkce-challenge";
import { LATEST_PROTOCOL_VERSION } from "../types.js";
import { OAuthErrorResponseSchema, OpenIdProviderDiscoveryMetadataSchema } from "../shared/auth.js";
import { OAuthClientInformationFullSchema, OAuthMetadataSchema, OAuthProtectedResourceMetadataSchema, OAuthTokensSchema } from "../shared/auth.js";
import { checkResourceAllowed, resourceUrlFromServerUrl } from "../shared/auth-utils.js";
import { InvalidClientError, InvalidGrantError, OAUTH_ERRORS, OAuthError, ServerError, UnauthorizedClientError } from "../server/auth/errors.js";
export class UnauthorizedError extends Error {
constructor(message) {
super(message ?? 'Unauthorized');
super(message !== null && message !== void 0 ? message : "Unauthorized");
}
}
function isClientAuthMethod(method) {
return ['client_secret_basic', 'client_secret_post', 'none'].includes(method);
}
const AUTHORIZATION_CODE_RESPONSE_TYPE = 'code';
const AUTHORIZATION_CODE_CHALLENGE_METHOD = 'S256';
/**
* Determines the best client authentication method to use based on server support and client configuration.
*
@@ -26,31 +21,24 @@ const AUTHORIZATION_CODE_CHALLENGE_METHOD = 'S256';
* @param supportedMethods - Authentication methods supported by the authorization server
* @returns The selected authentication method
*/
export function selectClientAuthMethod(clientInformation, supportedMethods) {
function selectClientAuthMethod(clientInformation, supportedMethods) {
const hasClientSecret = clientInformation.client_secret !== undefined;
// If server doesn't specify supported methods, use RFC 6749 defaults
if (supportedMethods.length === 0) {
return hasClientSecret ? 'client_secret_post' : 'none';
}
// Prefer the method returned by the server during client registration if valid and supported
if ('token_endpoint_auth_method' in clientInformation &&
clientInformation.token_endpoint_auth_method &&
isClientAuthMethod(clientInformation.token_endpoint_auth_method) &&
supportedMethods.includes(clientInformation.token_endpoint_auth_method)) {
return clientInformation.token_endpoint_auth_method;
return hasClientSecret ? "client_secret_post" : "none";
}
// Try methods in priority order (most secure first)
if (hasClientSecret && supportedMethods.includes('client_secret_basic')) {
return 'client_secret_basic';
if (hasClientSecret && supportedMethods.includes("client_secret_basic")) {
return "client_secret_basic";
}
if (hasClientSecret && supportedMethods.includes('client_secret_post')) {
return 'client_secret_post';
if (hasClientSecret && supportedMethods.includes("client_secret_post")) {
return "client_secret_post";
}
if (supportedMethods.includes('none')) {
return 'none';
if (supportedMethods.includes("none")) {
return "none";
}
// Fallback: use what we have
return hasClientSecret ? 'client_secret_post' : 'none';
return hasClientSecret ? "client_secret_post" : "none";
}
/**
* Applies client authentication to the request based on the specified method.
@@ -69,13 +57,13 @@ export function selectClientAuthMethod(clientInformation, supportedMethods) {
function applyClientAuthentication(method, clientInformation, headers, params) {
const { client_id, client_secret } = clientInformation;
switch (method) {
case 'client_secret_basic':
case "client_secret_basic":
applyBasicAuth(client_id, client_secret, headers);
return;
case 'client_secret_post':
case "client_secret_post":
applyPostAuth(client_id, client_secret, params);
return;
case 'none':
case "none":
applyPublicAuth(client_id, params);
return;
default:
@@ -87,25 +75,25 @@ function applyClientAuthentication(method, clientInformation, headers, params) {
*/
function applyBasicAuth(clientId, clientSecret, headers) {
if (!clientSecret) {
throw new Error('client_secret_basic authentication requires a client_secret');
throw new Error("client_secret_basic authentication requires a client_secret");
}
const credentials = btoa(`${clientId}:${clientSecret}`);
headers.set('Authorization', `Basic ${credentials}`);
headers.set("Authorization", `Basic ${credentials}`);
}
/**
* Applies POST body authentication (RFC 6749 Section 2.3.1)
*/
function applyPostAuth(clientId, clientSecret, params) {
params.set('client_id', clientId);
params.set("client_id", clientId);
if (clientSecret) {
params.set('client_secret', clientSecret);
params.set("client_secret", clientSecret);
}
}
/**
* Applies public client authentication (RFC 6749 Section 2.1)
*/
function applyPublicAuth(clientId, params) {
params.set('client_id', clientId);
params.set("client_id", clientId);
}
/**
* Parses an OAuth error response from a string or Response object.
@@ -140,24 +128,25 @@ export async function parseErrorResponse(input) {
* instead of linking together the other lower-level functions in this module.
*/
export async function auth(provider, options) {
var _a, _b;
try {
return await authInternal(provider, options);
}
catch (error) {
// Handle recoverable error types by invalidating credentials and retrying
if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
await provider.invalidateCredentials?.('all');
await ((_a = provider.invalidateCredentials) === null || _a === void 0 ? void 0 : _a.call(provider, 'all'));
return await authInternal(provider, options);
}
else if (error instanceof InvalidGrantError) {
await provider.invalidateCredentials?.('tokens');
await ((_b = provider.invalidateCredentials) === null || _b === void 0 ? void 0 : _b.call(provider, 'tokens'));
return await authInternal(provider, options);
}
// Throw otherwise
throw error;
}
}
async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) {
async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn, }) {
let resourceMetadata;
let authorizationServerUrl;
try {
@@ -166,69 +155,56 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
authorizationServerUrl = resourceMetadata.authorization_servers[0];
}
}
catch {
catch (_a) {
// Ignore errors and fall back to /.well-known/oauth-authorization-server
}
/**
* If we don't get a valid authorization server metadata from protected resource metadata,
* fallback to the legacy MCP spec's implementation (version 2025-03-26): MCP server base URL acts as the Authorization server.
* fallback to the legacy MCP spec's implementation (version 2025-03-26): MCP server acts as the Authorization server.
*/
if (!authorizationServerUrl) {
authorizationServerUrl = new URL('/', serverUrl);
authorizationServerUrl = serverUrl;
}
const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
const metadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, {
fetchFn
fetchFn,
});
// Handle client registration if needed
let clientInformation = await Promise.resolve(provider.clientInformation());
if (!clientInformation) {
if (authorizationCode !== undefined) {
throw new Error('Existing OAuth client information is required when exchanging an authorization code');
throw new Error("Existing OAuth client information is required when exchanging an authorization code");
}
const supportsUrlBasedClientId = metadata?.client_id_metadata_document_supported === true;
const clientMetadataUrl = provider.clientMetadataUrl;
if (clientMetadataUrl && !isHttpsUrl(clientMetadataUrl)) {
throw new InvalidClientMetadataError(`clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: ${clientMetadataUrl}`);
if (!provider.saveClientInformation) {
throw new Error("OAuth client information must be saveable for dynamic registration");
}
const shouldUseUrlBasedClientId = supportsUrlBasedClientId && clientMetadataUrl;
if (shouldUseUrlBasedClientId) {
// SEP-991: URL-based Client IDs
clientInformation = {
client_id: clientMetadataUrl
};
await provider.saveClientInformation?.(clientInformation);
}
else {
// Fallback to dynamic registration
if (!provider.saveClientInformation) {
throw new Error('OAuth client information must be saveable for dynamic registration');
}
const fullInformation = await registerClient(authorizationServerUrl, {
metadata,
clientMetadata: provider.clientMetadata,
fetchFn
});
await provider.saveClientInformation(fullInformation);
clientInformation = fullInformation;
}
}
// Non-interactive flows (e.g., client_credentials, jwt-bearer) don't need a redirect URL
const nonInteractiveFlow = !provider.redirectUrl;
// Exchange authorization code for tokens, or fetch tokens directly for non-interactive flows
if (authorizationCode !== undefined || nonInteractiveFlow) {
const tokens = await fetchToken(provider, authorizationServerUrl, {
const fullInformation = await registerClient(authorizationServerUrl, {
metadata,
resource,
clientMetadata: provider.clientMetadata,
fetchFn,
});
await provider.saveClientInformation(fullInformation);
clientInformation = fullInformation;
}
// Exchange authorization code for tokens
if (authorizationCode !== undefined) {
const codeVerifier = await provider.codeVerifier();
const tokens = await exchangeAuthorization(authorizationServerUrl, {
metadata,
clientInformation,
authorizationCode,
fetchFn
codeVerifier,
redirectUri: provider.redirectUrl,
resource,
addClientAuthentication: provider.addClientAuthentication,
fetchFn: fetchFn,
});
await provider.saveTokens(tokens);
return 'AUTHORIZED';
return "AUTHORIZED";
}
const tokens = await provider.tokens();
// Handle token refresh or new authorization
if (tokens?.refresh_token) {
if (tokens === null || tokens === void 0 ? void 0 : tokens.refresh_token) {
try {
// Attempt to refresh the token
const newTokens = await refreshAuthorization(authorizationServerUrl, {
@@ -237,10 +213,10 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
refreshToken: tokens.refresh_token,
resource,
addClientAuthentication: provider.addClientAuthentication,
fetchFn
fetchFn,
});
await provider.saveTokens(newTokens);
return 'AUTHORIZED';
return "AUTHORIZED";
}
catch (error) {
// If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry.
@@ -260,33 +236,18 @@ async function authInternal(provider, { serverUrl, authorizationCode, scope, res
clientInformation,
state,
redirectUrl: provider.redirectUrl,
scope: scope || resourceMetadata?.scopes_supported?.join(' ') || provider.clientMetadata.scope,
resource
scope: scope || provider.clientMetadata.scope,
resource,
});
await provider.saveCodeVerifier(codeVerifier);
await provider.redirectToAuthorization(authorizationUrl);
return 'REDIRECT';
}
/**
* SEP-991: URL-based Client IDs
* Validate that the client_id is a valid URL with https scheme
*/
export function isHttpsUrl(value) {
if (!value)
return false;
try {
const url = new URL(value);
return url.protocol === 'https:' && url.pathname !== '/';
}
catch {
return false;
}
return "REDIRECT";
}
export async function selectResourceURL(serverUrl, provider, resourceMetadata) {
const defaultResource = resourceUrlFromServerUrl(serverUrl);
// If provider has custom validation, delegate to it
if (provider.validateResourceURL) {
return await provider.validateResourceURL(defaultResource, resourceMetadata?.resource);
return await provider.validateResourceURL(defaultResource, resourceMetadata === null || resourceMetadata === void 0 ? void 0 : resourceMetadata.resource);
}
// Only include resource parameter when Protected Resource Metadata is present
if (!resourceMetadata) {
@@ -299,62 +260,11 @@ export async function selectResourceURL(serverUrl, provider, resourceMetadata) {
// Prefer the resource from metadata since it's what the server is telling us to request
return new URL(resourceMetadata.resource);
}
/**
* Extract resource_metadata, scope, and error from WWW-Authenticate header.
*/
export function extractWWWAuthenticateParams(res) {
const authenticateHeader = res.headers.get('WWW-Authenticate');
if (!authenticateHeader) {
return {};
}
const [type, scheme] = authenticateHeader.split(' ');
if (type.toLowerCase() !== 'bearer' || !scheme) {
return {};
}
const resourceMetadataMatch = extractFieldFromWwwAuth(res, 'resource_metadata') || undefined;
let resourceMetadataUrl;
if (resourceMetadataMatch) {
try {
resourceMetadataUrl = new URL(resourceMetadataMatch);
}
catch {
// Ignore invalid URL
}
}
const scope = extractFieldFromWwwAuth(res, 'scope') || undefined;
const error = extractFieldFromWwwAuth(res, 'error') || undefined;
return {
resourceMetadataUrl,
scope,
error
};
}
/**
* Extracts a specific field's value from the WWW-Authenticate header string.
*
* @param response The HTTP response object containing the headers.
* @param fieldName The name of the field to extract (e.g., "realm", "nonce").
* @returns The field value
*/
function extractFieldFromWwwAuth(response, fieldName) {
const wwwAuthHeader = response.headers.get('WWW-Authenticate');
if (!wwwAuthHeader) {
return null;
}
const pattern = new RegExp(`${fieldName}=(?:"([^"]+)"|([^\\s,]+))`);
const match = wwwAuthHeader.match(pattern);
if (match) {
// Pattern matches: field_name="value" or field_name=value (unquoted)
return match[1] || match[2];
}
return null;
}
/**
* Extract resource_metadata from response header.
* @deprecated Use `extractWWWAuthenticateParams` instead.
*/
export function extractResourceMetadataUrl(res) {
const authenticateHeader = res.headers.get('WWW-Authenticate');
const authenticateHeader = res.headers.get("WWW-Authenticate");
if (!authenticateHeader) {
return undefined;
}
@@ -370,7 +280,7 @@ export function extractResourceMetadataUrl(res) {
try {
return new URL(match[1]);
}
catch {
catch (_a) {
return undefined;
}
}
@@ -382,15 +292,13 @@ export function extractResourceMetadataUrl(res) {
*/
export async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
const response = await discoverMetadataWithFallback(serverUrl, 'oauth-protected-resource', fetchFn, {
protocolVersion: opts?.protocolVersion,
metadataUrl: opts?.resourceMetadataUrl
protocolVersion: opts === null || opts === void 0 ? void 0 : opts.protocolVersion,
metadataUrl: opts === null || opts === void 0 ? void 0 : opts.resourceMetadataUrl,
});
if (!response || response.status === 404) {
await response?.body?.cancel();
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
}
if (!response.ok) {
await response.body?.cancel();
throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
}
return OAuthProtectedResourceMetadataSchema.parse(await response.json());
@@ -424,14 +332,16 @@ function buildWellKnownPath(wellKnownPrefix, pathname = '', options = {}) {
if (pathname.endsWith('/')) {
pathname = pathname.slice(0, -1);
}
return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
return options.prependPathname
? `${pathname}/.well-known/${wellKnownPrefix}`
: `/.well-known/${wellKnownPrefix}${pathname}`;
}
/**
* Tries to discover OAuth metadata at a specific URL
*/
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
const headers = {
'MCP-Protocol-Version': protocolVersion
"MCP-Protocol-Version": protocolVersion
};
return await fetchWithCorsRetry(url, headers, fetchFn);
}
@@ -439,27 +349,28 @@ async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
* Determines if fallback to root discovery should be attempted
*/
function shouldAttemptFallback(response, pathname) {
return !response || (response.status >= 400 && response.status < 500 && pathname !== '/');
return !response || response.status === 404 && pathname !== '/';
}
/**
* Generic function for discovering OAuth metadata with fallback support
*/
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
var _a, _b;
const issuer = new URL(serverUrl);
const protocolVersion = opts?.protocolVersion ?? LATEST_PROTOCOL_VERSION;
const protocolVersion = (_a = opts === null || opts === void 0 ? void 0 : opts.protocolVersion) !== null && _a !== void 0 ? _a : LATEST_PROTOCOL_VERSION;
let url;
if (opts?.metadataUrl) {
if (opts === null || opts === void 0 ? void 0 : opts.metadataUrl) {
url = new URL(opts.metadataUrl);
}
else {
// Try path-aware discovery first
const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
url = new URL(wellKnownPath, opts?.metadataServerUrl ?? issuer);
url = new URL(wellKnownPath, (_b = opts === null || opts === void 0 ? void 0 : opts.metadataServerUrl) !== null && _b !== void 0 ? _b : issuer);
url.search = issuer.search;
}
let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn);
// If path-aware discovery fails with 404 and we're not already at root, try fallback to root discovery
if (!opts?.metadataUrl && shouldAttemptFallback(response, issuer.pathname)) {
if (!(opts === null || opts === void 0 ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) {
const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn);
}
@@ -473,7 +384,7 @@ async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, o
*
* @deprecated This function is deprecated in favor of `discoverAuthorizationServerMetadata`.
*/
export async function discoverOAuthMetadata(issuer, { authorizationServerUrl, protocolVersion } = {}, fetchFn = fetch) {
export async function discoverOAuthMetadata(issuer, { authorizationServerUrl, protocolVersion, } = {}, fetchFn = fetch) {
if (typeof issuer === 'string') {
issuer = new URL(issuer);
}
@@ -483,17 +394,15 @@ export async function discoverOAuthMetadata(issuer, { authorizationServerUrl, pr
if (typeof authorizationServerUrl === 'string') {
authorizationServerUrl = new URL(authorizationServerUrl);
}
protocolVersion ?? (protocolVersion = LATEST_PROTOCOL_VERSION);
protocolVersion !== null && protocolVersion !== void 0 ? protocolVersion : (protocolVersion = LATEST_PROTOCOL_VERSION);
const response = await discoverMetadataWithFallback(authorizationServerUrl, 'oauth-authorization-server', fetchFn, {
protocolVersion,
metadataServerUrl: authorizationServerUrl
metadataServerUrl: authorizationServerUrl,
});
if (!response || response.status === 404) {
await response?.body?.cancel();
return undefined;
}
if (!response.ok) {
await response.body?.cancel();
throw new Error(`HTTP ${response.status} trying to load well-known OAuth metadata`);
}
return OAuthMetadataSchema.parse(await response.json());
@@ -502,7 +411,8 @@ export async function discoverOAuthMetadata(issuer, { authorizationServerUrl, pr
* Builds a list of discovery URLs to try for authorization server metadata.
* URLs are returned in priority order:
* 1. OAuth metadata at the given URL
* 2. OIDC metadata endpoints at the given URL
* 2. OAuth metadata at root (if URL has path)
* 3. OIDC metadata endpoints
*/
export function buildDiscoveryUrls(authorizationServerUrl) {
const url = typeof authorizationServerUrl === 'string' ? new URL(authorizationServerUrl) : authorizationServerUrl;
@@ -532,7 +442,12 @@ export function buildDiscoveryUrls(authorizationServerUrl) {
url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url.origin),
type: 'oauth'
});
// 2. OIDC metadata endpoints
// Root path: https://example.com/.well-known/oauth-authorization-server
urlsToTry.push({
url: new URL('/.well-known/oauth-authorization-server', url.origin),
type: 'oauth'
});
// 3. OIDC metadata endpoints
// RFC 8414 style: Insert /.well-known/openid-configuration before the path
urlsToTry.push({
url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
@@ -561,11 +476,9 @@ export function buildDiscoveryUrls(authorizationServerUrl) {
* @param options.protocolVersion - MCP protocol version to use, defaults to LATEST_PROTOCOL_VERSION
* @returns Promise resolving to authorization server metadata, or undefined if discovery fails
*/
export async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = LATEST_PROTOCOL_VERSION } = {}) {
const headers = {
'MCP-Protocol-Version': protocolVersion,
Accept: 'application/json'
};
export async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = LATEST_PROTOCOL_VERSION, } = {}) {
var _a;
const headers = { 'MCP-Protocol-Version': protocolVersion };
// Get the list of URLs to try
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
// Try each URL in order
@@ -579,7 +492,6 @@ export async function discoverAuthorizationServerMetadata(authorizationServerUrl
continue;
}
if (!response.ok) {
await response.body?.cancel();
// Continue looking for any 4xx response code.
if (response.status >= 400 && response.status < 500) {
continue; // Try next URL
@@ -591,7 +503,12 @@ export async function discoverAuthorizationServerMetadata(authorizationServerUrl
return OAuthMetadataSchema.parse(await response.json());
}
else {
return OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
const metadata = OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
// MCP spec requires OIDC providers to support S256 PKCE
if (!((_a = metadata.code_challenge_methods_supported) === null || _a === void 0 ? void 0 : _a.includes('S256'))) {
throw new Error(`Incompatible OIDC provider at ${endpointUrl}: does not support S256 code challenge method required by MCP specification`);
}
return metadata;
}
}
return undefined;
@@ -599,97 +516,49 @@ export async function discoverAuthorizationServerMetadata(authorizationServerUrl
/**
* Begins the authorization flow with the given server, by generating a PKCE challenge and constructing the authorization URL.
*/
export async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource }) {
export async function startAuthorization(authorizationServerUrl, { metadata, clientInformation, redirectUrl, scope, state, resource, }) {
const responseType = "code";
const codeChallengeMethod = "S256";
let authorizationUrl;
if (metadata) {
authorizationUrl = new URL(metadata.authorization_endpoint);
if (!metadata.response_types_supported.includes(AUTHORIZATION_CODE_RESPONSE_TYPE)) {
throw new Error(`Incompatible auth server: does not support response type ${AUTHORIZATION_CODE_RESPONSE_TYPE}`);
if (!metadata.response_types_supported.includes(responseType)) {
throw new Error(`Incompatible auth server: does not support response type ${responseType}`);
}
if (metadata.code_challenge_methods_supported &&
!metadata.code_challenge_methods_supported.includes(AUTHORIZATION_CODE_CHALLENGE_METHOD)) {
throw new Error(`Incompatible auth server: does not support code challenge method ${AUTHORIZATION_CODE_CHALLENGE_METHOD}`);
if (!metadata.code_challenge_methods_supported ||
!metadata.code_challenge_methods_supported.includes(codeChallengeMethod)) {
throw new Error(`Incompatible auth server: does not support code challenge method ${codeChallengeMethod}`);
}
}
else {
authorizationUrl = new URL('/authorize', authorizationServerUrl);
authorizationUrl = new URL("/authorize", authorizationServerUrl);
}
// Generate PKCE challenge
const challenge = await pkceChallenge();
const codeVerifier = challenge.code_verifier;
const codeChallenge = challenge.code_challenge;
authorizationUrl.searchParams.set('response_type', AUTHORIZATION_CODE_RESPONSE_TYPE);
authorizationUrl.searchParams.set('client_id', clientInformation.client_id);
authorizationUrl.searchParams.set('code_challenge', codeChallenge);
authorizationUrl.searchParams.set('code_challenge_method', AUTHORIZATION_CODE_CHALLENGE_METHOD);
authorizationUrl.searchParams.set('redirect_uri', String(redirectUrl));
authorizationUrl.searchParams.set("response_type", responseType);
authorizationUrl.searchParams.set("client_id", clientInformation.client_id);
authorizationUrl.searchParams.set("code_challenge", codeChallenge);
authorizationUrl.searchParams.set("code_challenge_method", codeChallengeMethod);
authorizationUrl.searchParams.set("redirect_uri", String(redirectUrl));
if (state) {
authorizationUrl.searchParams.set('state', state);
authorizationUrl.searchParams.set("state", state);
}
if (scope) {
authorizationUrl.searchParams.set('scope', scope);
authorizationUrl.searchParams.set("scope", scope);
}
if (scope?.includes('offline_access')) {
if (scope === null || scope === void 0 ? void 0 : scope.includes("offline_access")) {
// if the request includes the OIDC-only "offline_access" scope,
// we need to set the prompt to "consent" to ensure the user is prompted to grant offline access
// https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
authorizationUrl.searchParams.append('prompt', 'consent');
authorizationUrl.searchParams.append("prompt", "consent");
}
if (resource) {
authorizationUrl.searchParams.set('resource', resource.href);
authorizationUrl.searchParams.set("resource", resource.href);
}
return { authorizationUrl, codeVerifier };
}
/**
* Prepares token request parameters for an authorization code exchange.
*
* This is the default implementation used by fetchToken when the provider
* doesn't implement prepareTokenRequest.
*
* @param authorizationCode - The authorization code received from the authorization endpoint
* @param codeVerifier - The PKCE code verifier
* @param redirectUri - The redirect URI used in the authorization request
* @returns URLSearchParams for the authorization_code grant
*/
export function prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri) {
return new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
code_verifier: codeVerifier,
redirect_uri: String(redirectUri)
});
}
/**
* Internal helper to execute a token request with the given parameters.
* Used by exchangeAuthorization, refreshAuthorization, and fetchToken.
*/
async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequestParams, clientInformation, addClientAuthentication, resource, fetchFn }) {
const tokenUrl = metadata?.token_endpoint ? new URL(metadata.token_endpoint) : new URL('/token', authorizationServerUrl);
const headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json'
});
if (resource) {
tokenRequestParams.set('resource', resource.href);
}
if (addClientAuthentication) {
await addClientAuthentication(headers, tokenRequestParams, tokenUrl, metadata);
}
else if (clientInformation) {
const supportedMethods = metadata?.token_endpoint_auth_methods_supported ?? [];
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
applyClientAuthentication(authMethod, clientInformation, headers, tokenRequestParams);
}
const response = await (fetchFn ?? fetch)(tokenUrl, {
method: 'POST',
headers,
body: tokenRequestParams
});
if (!response.ok) {
throw await parseErrorResponse(response);
}
return OAuthTokensSchema.parse(await response.json());
}
/**
* Exchanges an authorization code for an access token with the given server.
*
@@ -702,16 +571,48 @@ async function executeTokenRequest(authorizationServerUrl, { metadata, tokenRequ
* @returns Promise resolving to OAuth tokens
* @throws {Error} When token exchange fails or authentication is invalid
*/
export async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn }) {
const tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, redirectUri);
return executeTokenRequest(authorizationServerUrl, {
metadata,
tokenRequestParams,
clientInformation,
addClientAuthentication,
resource,
fetchFn
export async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn, }) {
var _a;
const grantType = "authorization_code";
const tokenUrl = (metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint)
? new URL(metadata.token_endpoint)
: new URL("/token", authorizationServerUrl);
if ((metadata === null || metadata === void 0 ? void 0 : metadata.grant_types_supported) &&
!metadata.grant_types_supported.includes(grantType)) {
throw new Error(`Incompatible auth server: does not support grant type ${grantType}`);
}
// Exchange code for tokens
const headers = new Headers({
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
});
const params = new URLSearchParams({
grant_type: grantType,
code: authorizationCode,
code_verifier: codeVerifier,
redirect_uri: String(redirectUri),
});
if (addClientAuthentication) {
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
}
else {
// Determine and apply client authentication method
const supportedMethods = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint_auth_methods_supported) !== null && _a !== void 0 ? _a : [];
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
applyClientAuthentication(authMethod, clientInformation, headers, params);
}
if (resource) {
params.set("resource", resource.href);
}
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(tokenUrl, {
method: "POST",
headers,
body: params,
});
if (!response.ok) {
throw await parseErrorResponse(response);
}
return OAuthTokensSchema.parse(await response.json());
}
/**
* Exchange a refresh token for an updated access token.
@@ -725,96 +626,70 @@ export async function exchangeAuthorization(authorizationServerUrl, { metadata,
* @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
* @throws {Error} When token refresh fails or authentication is invalid
*/
export async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) {
const tokenRequestParams = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken
});
const tokens = await executeTokenRequest(authorizationServerUrl, {
metadata,
tokenRequestParams,
clientInformation,
addClientAuthentication,
resource,
fetchFn
});
// Preserve original refresh token if server didn't return a new one
return { refresh_token: refreshToken, ...tokens };
}
/**
* Unified token fetching that works with any grant type via provider.prepareTokenRequest().
*
* This function provides a single entry point for obtaining tokens regardless of the
* OAuth grant type. The provider's prepareTokenRequest() method determines which grant
* to use and supplies the grant-specific parameters.
*
* @param provider - OAuth client provider that implements prepareTokenRequest()
* @param authorizationServerUrl - The authorization server's base URL
* @param options - Configuration for the token request
* @returns Promise resolving to OAuth tokens
* @throws {Error} When provider doesn't implement prepareTokenRequest or token fetch fails
*
* @example
* // Provider for client_credentials:
* class MyProvider implements OAuthClientProvider {
* prepareTokenRequest(scope) {
* const params = new URLSearchParams({ grant_type: 'client_credentials' });
* if (scope) params.set('scope', scope);
* return params;
* }
* // ... other methods
* }
*
* const tokens = await fetchToken(provider, authServerUrl, { metadata });
*/
export async function fetchToken(provider, authorizationServerUrl, { metadata, resource, authorizationCode, fetchFn } = {}) {
const scope = provider.clientMetadata.scope;
// Use provider's prepareTokenRequest if available, otherwise fall back to authorization_code
let tokenRequestParams;
if (provider.prepareTokenRequest) {
tokenRequestParams = await provider.prepareTokenRequest(scope);
}
// Default to authorization_code grant if no custom prepareTokenRequest
if (!tokenRequestParams) {
if (!authorizationCode) {
throw new Error('Either provider.prepareTokenRequest() or authorizationCode is required');
export async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn, }) {
var _a;
const grantType = "refresh_token";
let tokenUrl;
if (metadata) {
tokenUrl = new URL(metadata.token_endpoint);
if (metadata.grant_types_supported &&
!metadata.grant_types_supported.includes(grantType)) {
throw new Error(`Incompatible auth server: does not support grant type ${grantType}`);
}
if (!provider.redirectUrl) {
throw new Error('redirectUrl is required for authorization_code flow');
}
const codeVerifier = await provider.codeVerifier();
tokenRequestParams = prepareAuthorizationCodeRequest(authorizationCode, codeVerifier, provider.redirectUrl);
}
const clientInformation = await provider.clientInformation();
return executeTokenRequest(authorizationServerUrl, {
metadata,
tokenRequestParams,
clientInformation: clientInformation ?? undefined,
addClientAuthentication: provider.addClientAuthentication,
resource,
fetchFn
else {
tokenUrl = new URL("/token", authorizationServerUrl);
}
// Exchange refresh token
const headers = new Headers({
"Content-Type": "application/x-www-form-urlencoded",
});
const params = new URLSearchParams({
grant_type: grantType,
refresh_token: refreshToken,
});
if (addClientAuthentication) {
addClientAuthentication(headers, params, authorizationServerUrl, metadata);
}
else {
// Determine and apply client authentication method
const supportedMethods = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.token_endpoint_auth_methods_supported) !== null && _a !== void 0 ? _a : [];
const authMethod = selectClientAuthMethod(clientInformation, supportedMethods);
applyClientAuthentication(authMethod, clientInformation, headers, params);
}
if (resource) {
params.set("resource", resource.href);
}
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(tokenUrl, {
method: "POST",
headers,
body: params,
});
if (!response.ok) {
throw await parseErrorResponse(response);
}
return OAuthTokensSchema.parse({ refresh_token: refreshToken, ...(await response.json()) });
}
/**
* Performs OAuth 2.0 Dynamic Client Registration according to RFC 7591.
*/
export async function registerClient(authorizationServerUrl, { metadata, clientMetadata, fetchFn }) {
export async function registerClient(authorizationServerUrl, { metadata, clientMetadata, fetchFn, }) {
let registrationUrl;
if (metadata) {
if (!metadata.registration_endpoint) {
throw new Error('Incompatible auth server: does not support dynamic client registration');
throw new Error("Incompatible auth server: does not support dynamic client registration");
}
registrationUrl = new URL(metadata.registration_endpoint);
}
else {
registrationUrl = new URL('/register', authorizationServerUrl);
registrationUrl = new URL("/register", authorizationServerUrl);
}
const response = await (fetchFn ?? fetch)(registrationUrl, {
method: 'POST',
const response = await (fetchFn !== null && fetchFn !== void 0 ? fetchFn : fetch)(registrationUrl, {
method: "POST",
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json",
},
body: JSON.stringify(clientMetadata)
body: JSON.stringify(clientMetadata),
});
if (!response.ok) {
throw await parseErrorResponse(response);
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
+71 -407
View File
@@ -1,73 +1,6 @@
import { mergeCapabilities, Protocol } from '../shared/protocol.js';
import { CallToolResultSchema, CompleteResultSchema, EmptyResultSchema, ErrorCode, GetPromptResultSchema, InitializeResultSchema, LATEST_PROTOCOL_VERSION, ListPromptsResultSchema, ListResourcesResultSchema, ListResourceTemplatesResultSchema, ListToolsResultSchema, McpError, ReadResourceResultSchema, SUPPORTED_PROTOCOL_VERSIONS, ElicitResultSchema, ElicitRequestSchema, CreateTaskResultSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, ToolListChangedNotificationSchema, PromptListChangedNotificationSchema, ResourceListChangedNotificationSchema, ListChangedOptionsBaseSchema } from '../types.js';
import { AjvJsonSchemaValidator } from '../validation/ajv-provider.js';
import { getObjectShape, isZ4Schema, safeParse } from '../server/zod-compat.js';
import { ExperimentalClientTasks } from '../experimental/tasks/client.js';
import { assertToolsCallTaskCapability, assertClientRequestTaskCapability } from '../experimental/tasks/helpers.js';
/**
* Elicitation default application helper. Applies defaults to the data based on the schema.
*
* @param schema - The schema to apply defaults to.
* @param data - The data to apply defaults to.
*/
function applyElicitationDefaults(schema, data) {
if (!schema || data === null || typeof data !== 'object')
return;
// Handle object properties
if (schema.type === 'object' && schema.properties && typeof schema.properties === 'object') {
const obj = data;
const props = schema.properties;
for (const key of Object.keys(props)) {
const propSchema = props[key];
// If missing or explicitly undefined, apply default if present
if (obj[key] === undefined && Object.prototype.hasOwnProperty.call(propSchema, 'default')) {
obj[key] = propSchema.default;
}
// Recurse into existing nested objects/arrays
if (obj[key] !== undefined) {
applyElicitationDefaults(propSchema, obj[key]);
}
}
}
if (Array.isArray(schema.anyOf)) {
for (const sub of schema.anyOf) {
// Skip boolean schemas (true/false are valid JSON Schemas but have no defaults)
if (typeof sub !== 'boolean') {
applyElicitationDefaults(sub, data);
}
}
}
// Combine schemas
if (Array.isArray(schema.oneOf)) {
for (const sub of schema.oneOf) {
// Skip boolean schemas (true/false are valid JSON Schemas but have no defaults)
if (typeof sub !== 'boolean') {
applyElicitationDefaults(sub, data);
}
}
}
}
/**
* Determines which elicitation modes are supported based on declared client capabilities.
*
* According to the spec:
* - An empty elicitation capability object defaults to form mode support (backwards compatibility)
* - URL mode is only supported if explicitly declared
*
* @param capabilities - The client's elicitation capabilities
* @returns An object indicating which modes are supported
*/
export function getSupportedElicitationModes(capabilities) {
if (!capabilities) {
return { supportsFormMode: false, supportsUrlMode: false };
}
const hasFormCapability = capabilities.form !== undefined;
const hasUrlCapability = capabilities.url !== undefined;
// If neither form nor url are explicitly declared, form mode is supported (backwards compatibility)
const supportsFormMode = hasFormCapability || (!hasFormCapability && !hasUrlCapability);
const supportsUrlMode = hasUrlCapability;
return { supportsFormMode, supportsUrlMode };
}
import { mergeCapabilities, Protocol, } from "../shared/protocol.js";
import { CallToolResultSchema, CompleteResultSchema, EmptyResultSchema, GetPromptResultSchema, InitializeResultSchema, LATEST_PROTOCOL_VERSION, ListPromptsResultSchema, ListResourcesResultSchema, ListResourceTemplatesResultSchema, ListToolsResultSchema, ReadResourceResultSchema, SUPPORTED_PROTOCOL_VERSIONS, ErrorCode, McpError, } from "../types.js";
import Ajv from "ajv";
/**
* An MCP client on top of a pluggable transport.
*
@@ -98,59 +31,12 @@ export class Client extends Protocol {
* Initializes this client with the given name and version information.
*/
constructor(_clientInfo, options) {
var _a;
super(options);
this._clientInfo = _clientInfo;
this._cachedToolOutputValidators = new Map();
this._cachedKnownTaskTools = new Set();
this._cachedRequiredTaskTools = new Set();
this._listChangedDebounceTimers = new Map();
this._capabilities = options?.capabilities ?? {};
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator();
// Store list changed config for setup after connection (when we know server capabilities)
if (options?.listChanged) {
this._pendingListChangedConfig = options.listChanged;
}
}
/**
* Set up handlers for list changed notifications based on config and server capabilities.
* This should only be called after initialization when server capabilities are known.
* Handlers are silently skipped if the server doesn't advertise the corresponding listChanged capability.
* @internal
*/
_setupListChangedHandlers(config) {
if (config.tools && this._serverCapabilities?.tools?.listChanged) {
this._setupListChangedHandler('tools', ToolListChangedNotificationSchema, config.tools, async () => {
const result = await this.listTools();
return result.tools;
});
}
if (config.prompts && this._serverCapabilities?.prompts?.listChanged) {
this._setupListChangedHandler('prompts', PromptListChangedNotificationSchema, config.prompts, async () => {
const result = await this.listPrompts();
return result.prompts;
});
}
if (config.resources && this._serverCapabilities?.resources?.listChanged) {
this._setupListChangedHandler('resources', ResourceListChangedNotificationSchema, config.resources, async () => {
const result = await this.listResources();
return result.resources;
});
}
}
/**
* Access experimental features.
*
* WARNING: These APIs are experimental and may change without notice.
*
* @experimental
*/
get experimental() {
if (!this._experimental) {
this._experimental = {
tasks: new ExperimentalClientTasks(this)
};
}
return this._experimental;
this._capabilities = (_a = options === null || options === void 0 ? void 0 : options.capabilities) !== null && _a !== void 0 ? _a : {};
this._ajv = new Ajv();
}
/**
* Registers new capabilities. This can only be called before connecting to a transport.
@@ -159,126 +45,13 @@ export class Client extends Protocol {
*/
registerCapabilities(capabilities) {
if (this.transport) {
throw new Error('Cannot register capabilities after connecting to transport');
throw new Error("Cannot register capabilities after connecting to transport");
}
this._capabilities = mergeCapabilities(this._capabilities, capabilities);
}
/**
* Override request handler registration to enforce client-side validation for elicitation.
*/
setRequestHandler(requestSchema, handler) {
const shape = getObjectShape(requestSchema);
const methodSchema = shape?.method;
if (!methodSchema) {
throw new Error('Schema is missing a method literal');
}
// Extract literal value using type-safe property access
let methodValue;
if (isZ4Schema(methodSchema)) {
const v4Schema = methodSchema;
const v4Def = v4Schema._zod?.def;
methodValue = v4Def?.value ?? v4Schema.value;
}
else {
const v3Schema = methodSchema;
const legacyDef = v3Schema._def;
methodValue = legacyDef?.value ?? v3Schema.value;
}
if (typeof methodValue !== 'string') {
throw new Error('Schema method literal must be a string');
}
const method = methodValue;
if (method === 'elicitation/create') {
const wrappedHandler = async (request, extra) => {
const validatedRequest = safeParse(ElicitRequestSchema, request);
if (!validatedRequest.success) {
// Type guard: if success is false, error is guaranteed to exist
const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid elicitation request: ${errorMessage}`);
}
const { params } = validatedRequest.data;
params.mode = params.mode ?? 'form';
const { supportsFormMode, supportsUrlMode } = getSupportedElicitationModes(this._capabilities.elicitation);
if (params.mode === 'form' && !supportsFormMode) {
throw new McpError(ErrorCode.InvalidParams, 'Client does not support form-mode elicitation requests');
}
if (params.mode === 'url' && !supportsUrlMode) {
throw new McpError(ErrorCode.InvalidParams, 'Client does not support URL-mode elicitation requests');
}
const result = await Promise.resolve(handler(request, extra));
// When task creation is requested, validate and return CreateTaskResult
if (params.task) {
const taskValidationResult = safeParse(CreateTaskResultSchema, result);
if (!taskValidationResult.success) {
const errorMessage = taskValidationResult.error instanceof Error
? taskValidationResult.error.message
: String(taskValidationResult.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
}
return taskValidationResult.data;
}
// For non-task requests, validate against ElicitResultSchema
const validationResult = safeParse(ElicitResultSchema, result);
if (!validationResult.success) {
// Type guard: if success is false, error is guaranteed to exist
const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid elicitation result: ${errorMessage}`);
}
const validatedResult = validationResult.data;
const requestedSchema = params.mode === 'form' ? params.requestedSchema : undefined;
if (params.mode === 'form' && validatedResult.action === 'accept' && validatedResult.content && requestedSchema) {
if (this._capabilities.elicitation?.form?.applyDefaults) {
try {
applyElicitationDefaults(requestedSchema, validatedResult.content);
}
catch {
// gracefully ignore errors in default application
}
}
}
return validatedResult;
};
// Install the wrapped handler
return super.setRequestHandler(requestSchema, wrappedHandler);
}
if (method === 'sampling/createMessage') {
const wrappedHandler = async (request, extra) => {
const validatedRequest = safeParse(CreateMessageRequestSchema, request);
if (!validatedRequest.success) {
const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid sampling request: ${errorMessage}`);
}
const { params } = validatedRequest.data;
const result = await Promise.resolve(handler(request, extra));
// When task creation is requested, validate and return CreateTaskResult
if (params.task) {
const taskValidationResult = safeParse(CreateTaskResultSchema, result);
if (!taskValidationResult.success) {
const errorMessage = taskValidationResult.error instanceof Error
? taskValidationResult.error.message
: String(taskValidationResult.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
}
return taskValidationResult.data;
}
// For non-task requests, validate against appropriate schema based on tools presence
const hasTools = params.tools || params.toolChoice;
const resultSchema = hasTools ? CreateMessageResultWithToolsSchema : CreateMessageResultSchema;
const validationResult = safeParse(resultSchema, result);
if (!validationResult.success) {
const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
throw new McpError(ErrorCode.InvalidParams, `Invalid sampling result: ${errorMessage}`);
}
return validationResult.data;
};
// Install the wrapped handler
return super.setRequestHandler(requestSchema, wrappedHandler);
}
// Other handlers use default behavior
return super.setRequestHandler(requestSchema, handler);
}
assertCapability(capability, method) {
if (!this._serverCapabilities?.[capability]) {
var _a;
if (!((_a = this._serverCapabilities) === null || _a === void 0 ? void 0 : _a[capability])) {
throw new Error(`Server does not support ${capability} (required for ${method})`);
}
}
@@ -291,12 +64,12 @@ export class Client extends Protocol {
}
try {
const result = await this.request({
method: 'initialize',
method: "initialize",
params: {
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: this._capabilities,
clientInfo: this._clientInfo
}
clientInfo: this._clientInfo,
},
}, InitializeResultSchema, options);
if (result === undefined) {
throw new Error(`Server sent invalid initialize result: ${result}`);
@@ -312,13 +85,8 @@ export class Client extends Protocol {
}
this._instructions = result.instructions;
await this.notification({
method: 'notifications/initialized'
method: "notifications/initialized",
});
// Set up list changed handlers now that we know server capabilities
if (this._pendingListChangedConfig) {
this._setupListChangedHandlers(this._pendingListChangedConfig);
this._pendingListChangedConfig = undefined;
}
}
catch (error) {
// Disconnect if initialization fails.
@@ -345,154 +113,124 @@ export class Client extends Protocol {
return this._instructions;
}
assertCapabilityForMethod(method) {
var _a, _b, _c, _d, _e;
switch (method) {
case 'logging/setLevel':
if (!this._serverCapabilities?.logging) {
case "logging/setLevel":
if (!((_a = this._serverCapabilities) === null || _a === void 0 ? void 0 : _a.logging)) {
throw new Error(`Server does not support logging (required for ${method})`);
}
break;
case 'prompts/get':
case 'prompts/list':
if (!this._serverCapabilities?.prompts) {
case "prompts/get":
case "prompts/list":
if (!((_b = this._serverCapabilities) === null || _b === void 0 ? void 0 : _b.prompts)) {
throw new Error(`Server does not support prompts (required for ${method})`);
}
break;
case 'resources/list':
case 'resources/templates/list':
case 'resources/read':
case 'resources/subscribe':
case 'resources/unsubscribe':
if (!this._serverCapabilities?.resources) {
case "resources/list":
case "resources/templates/list":
case "resources/read":
case "resources/subscribe":
case "resources/unsubscribe":
if (!((_c = this._serverCapabilities) === null || _c === void 0 ? void 0 : _c.resources)) {
throw new Error(`Server does not support resources (required for ${method})`);
}
if (method === 'resources/subscribe' && !this._serverCapabilities.resources.subscribe) {
if (method === "resources/subscribe" &&
!this._serverCapabilities.resources.subscribe) {
throw new Error(`Server does not support resource subscriptions (required for ${method})`);
}
break;
case 'tools/call':
case 'tools/list':
if (!this._serverCapabilities?.tools) {
case "tools/call":
case "tools/list":
if (!((_d = this._serverCapabilities) === null || _d === void 0 ? void 0 : _d.tools)) {
throw new Error(`Server does not support tools (required for ${method})`);
}
break;
case 'completion/complete':
if (!this._serverCapabilities?.completions) {
case "completion/complete":
if (!((_e = this._serverCapabilities) === null || _e === void 0 ? void 0 : _e.completions)) {
throw new Error(`Server does not support completions (required for ${method})`);
}
break;
case 'initialize':
case "initialize":
// No specific capability required for initialize
break;
case 'ping':
case "ping":
// No specific capability required for ping
break;
}
}
assertNotificationCapability(method) {
var _a;
switch (method) {
case 'notifications/roots/list_changed':
if (!this._capabilities.roots?.listChanged) {
case "notifications/roots/list_changed":
if (!((_a = this._capabilities.roots) === null || _a === void 0 ? void 0 : _a.listChanged)) {
throw new Error(`Client does not support roots list changed notifications (required for ${method})`);
}
break;
case 'notifications/initialized':
case "notifications/initialized":
// No specific capability required for initialized
break;
case 'notifications/cancelled':
case "notifications/cancelled":
// Cancellation notifications are always allowed
break;
case 'notifications/progress':
case "notifications/progress":
// Progress notifications are always allowed
break;
}
}
assertRequestHandlerCapability(method) {
// Task handlers are registered in Protocol constructor before _capabilities is initialized
// Skip capability check for task methods during initialization
if (!this._capabilities) {
return;
}
switch (method) {
case 'sampling/createMessage':
case "sampling/createMessage":
if (!this._capabilities.sampling) {
throw new Error(`Client does not support sampling capability (required for ${method})`);
}
break;
case 'elicitation/create':
case "elicitation/create":
if (!this._capabilities.elicitation) {
throw new Error(`Client does not support elicitation capability (required for ${method})`);
}
break;
case 'roots/list':
case "roots/list":
if (!this._capabilities.roots) {
throw new Error(`Client does not support roots capability (required for ${method})`);
}
break;
case 'tasks/get':
case 'tasks/list':
case 'tasks/result':
case 'tasks/cancel':
if (!this._capabilities.tasks) {
throw new Error(`Client does not support tasks capability (required for ${method})`);
}
break;
case 'ping':
case "ping":
// No specific capability required for ping
break;
}
}
assertTaskCapability(method) {
assertToolsCallTaskCapability(this._serverCapabilities?.tasks?.requests, method, 'Server');
}
assertTaskHandlerCapability(method) {
// Task handlers are registered in Protocol constructor before _capabilities is initialized
// Skip capability check for task methods during initialization
if (!this._capabilities) {
return;
}
assertClientRequestTaskCapability(this._capabilities.tasks?.requests, method, 'Client');
}
async ping(options) {
return this.request({ method: 'ping' }, EmptyResultSchema, options);
return this.request({ method: "ping" }, EmptyResultSchema, options);
}
async complete(params, options) {
return this.request({ method: 'completion/complete', params }, CompleteResultSchema, options);
return this.request({ method: "completion/complete", params }, CompleteResultSchema, options);
}
async setLoggingLevel(level, options) {
return this.request({ method: 'logging/setLevel', params: { level } }, EmptyResultSchema, options);
return this.request({ method: "logging/setLevel", params: { level } }, EmptyResultSchema, options);
}
async getPrompt(params, options) {
return this.request({ method: 'prompts/get', params }, GetPromptResultSchema, options);
return this.request({ method: "prompts/get", params }, GetPromptResultSchema, options);
}
async listPrompts(params, options) {
return this.request({ method: 'prompts/list', params }, ListPromptsResultSchema, options);
return this.request({ method: "prompts/list", params }, ListPromptsResultSchema, options);
}
async listResources(params, options) {
return this.request({ method: 'resources/list', params }, ListResourcesResultSchema, options);
return this.request({ method: "resources/list", params }, ListResourcesResultSchema, options);
}
async listResourceTemplates(params, options) {
return this.request({ method: 'resources/templates/list', params }, ListResourceTemplatesResultSchema, options);
return this.request({ method: "resources/templates/list", params }, ListResourceTemplatesResultSchema, options);
}
async readResource(params, options) {
return this.request({ method: 'resources/read', params }, ReadResourceResultSchema, options);
return this.request({ method: "resources/read", params }, ReadResourceResultSchema, options);
}
async subscribeResource(params, options) {
return this.request({ method: 'resources/subscribe', params }, EmptyResultSchema, options);
return this.request({ method: "resources/subscribe", params }, EmptyResultSchema, options);
}
async unsubscribeResource(params, options) {
return this.request({ method: 'resources/unsubscribe', params }, EmptyResultSchema, options);
return this.request({ method: "resources/unsubscribe", params }, EmptyResultSchema, options);
}
/**
* Calls a tool and waits for the result. Automatically validates structured output if the tool has an outputSchema.
*
* For task-based execution with streaming behavior, use client.experimental.tasks.callToolStream() instead.
*/
async callTool(params, resultSchema = CallToolResultSchema, options) {
// Guard: required-task tools need experimental API
if (this.isToolTaskRequired(params.name)) {
throw new McpError(ErrorCode.InvalidRequest, `Tool "${params.name}" requires task-based execution. Use client.experimental.tasks.callToolStream() instead.`);
}
const result = await this.request({ method: 'tools/call', params }, resultSchema, options);
const result = await this.request({ method: "tools/call", params }, resultSchema, options);
// Check if the tool has an outputSchema
const validator = this.getToolOutputValidator(params.name);
if (validator) {
@@ -503,10 +241,10 @@ export class Client extends Protocol {
// Only validate structured content if present (not when there's an error)
if (result.structuredContent) {
try {
// Validate the structured content against the schema
const validationResult = validator(result.structuredContent);
if (!validationResult.valid) {
throw new McpError(ErrorCode.InvalidParams, `Structured content does not match the tool's output schema: ${validationResult.errorMessage}`);
// Validate the structured content (which is already an object) against the schema
const isValid = validator(result.structuredContent);
if (!isValid) {
throw new McpError(ErrorCode.InvalidParams, `Structured content does not match the tool's output schema: ${this._ajv.errorsText(validator.errors)}`);
}
}
catch (error) {
@@ -519,106 +257,32 @@ export class Client extends Protocol {
}
return result;
}
isToolTask(toolName) {
if (!this._serverCapabilities?.tasks?.requests?.tools?.call) {
return false;
}
return this._cachedKnownTaskTools.has(toolName);
}
/**
* Check if a tool requires task-based execution.
* Unlike isToolTask which includes 'optional' tools, this only checks for 'required'.
*/
isToolTaskRequired(toolName) {
return this._cachedRequiredTaskTools.has(toolName);
}
/**
* Cache validators for tool output schemas.
* Called after listTools() to pre-compile validators for better performance.
*/
cacheToolMetadata(tools) {
cacheToolOutputSchemas(tools) {
this._cachedToolOutputValidators.clear();
this._cachedKnownTaskTools.clear();
this._cachedRequiredTaskTools.clear();
for (const tool of tools) {
// If the tool has an outputSchema, create and cache the validator
// If the tool has an outputSchema, create and cache the Ajv validator
if (tool.outputSchema) {
const toolValidator = this._jsonSchemaValidator.getValidator(tool.outputSchema);
this._cachedToolOutputValidators.set(tool.name, toolValidator);
}
// If the tool supports task-based execution, cache that information
const taskSupport = tool.execution?.taskSupport;
if (taskSupport === 'required' || taskSupport === 'optional') {
this._cachedKnownTaskTools.add(tool.name);
}
if (taskSupport === 'required') {
this._cachedRequiredTaskTools.add(tool.name);
try {
const validator = this._ajv.compile(tool.outputSchema);
this._cachedToolOutputValidators.set(tool.name, validator);
}
catch (_a) {
// Ignore schema compilation errors
}
}
}
}
/**
* Get cached validator for a tool
*/
getToolOutputValidator(toolName) {
return this._cachedToolOutputValidators.get(toolName);
}
async listTools(params, options) {
const result = await this.request({ method: 'tools/list', params }, ListToolsResultSchema, options);
const result = await this.request({ method: "tools/list", params }, ListToolsResultSchema, options);
// Cache the tools and their output schemas for future validation
this.cacheToolMetadata(result.tools);
this.cacheToolOutputSchemas(result.tools);
return result;
}
/**
* Set up a single list changed handler.
* @internal
*/
_setupListChangedHandler(listType, notificationSchema, options, fetcher) {
// Validate options using Zod schema (validates autoRefresh and debounceMs)
const parseResult = ListChangedOptionsBaseSchema.safeParse(options);
if (!parseResult.success) {
throw new Error(`Invalid ${listType} listChanged options: ${parseResult.error.message}`);
}
// Validate callback
if (typeof options.onChanged !== 'function') {
throw new Error(`Invalid ${listType} listChanged options: onChanged must be a function`);
}
const { autoRefresh, debounceMs } = parseResult.data;
const { onChanged } = options;
const refresh = async () => {
if (!autoRefresh) {
onChanged(null, null);
return;
}
try {
const items = await fetcher();
onChanged(null, items);
}
catch (e) {
const error = e instanceof Error ? e : new Error(String(e));
onChanged(error, null);
}
};
const handler = () => {
if (debounceMs) {
// Clear any pending debounce timer for this list type
const existingTimer = this._listChangedDebounceTimers.get(listType);
if (existingTimer) {
clearTimeout(existingTimer);
}
// Set up debounced refresh
const timer = setTimeout(refresh, debounceMs);
this._listChangedDebounceTimers.set(listType, timer);
}
else {
// No debounce, refresh immediately
refresh();
}
};
// Register notification handler
this.setNotificationHandler(notificationSchema, handler);
}
async sendRootsListChanged() {
return this.notification({ method: 'notifications/roots/list_changed' });
return this.notification({ method: "notifications/roots/list_changed" });
}
}
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
+4 -7
View File
@@ -1,7 +1,7 @@
import { type ErrorEvent, type EventSourceInit } from 'eventsource';
import { Transport, FetchLike } from '../shared/transport.js';
import { JSONRPCMessage } from '../types.js';
import { OAuthClientProvider } from './auth.js';
import { type ErrorEvent, type EventSourceInit } from "eventsource";
import { Transport, FetchLike } from "../shared/transport.js";
import { JSONRPCMessage } from "../types.js";
import { OAuthClientProvider } from "./auth.js";
export declare class SseError extends Error {
readonly code: number | undefined;
readonly event: ErrorEvent;
@@ -47,7 +47,6 @@ export type SSEClientTransportOptions = {
/**
* Client transport for SSE: this will connect to a server using Server-Sent Events for receiving
* messages and make separate POST requests for sending messages.
* @deprecated SSEClientTransport is deprecated. Prefer to use StreamableHTTPClientTransport where possible instead. Note that because some servers are still using SSE, clients may need to support both transports during the migration period.
*/
export declare class SSEClientTransport implements Transport {
private _eventSource?;
@@ -55,12 +54,10 @@ export declare class SSEClientTransport implements Transport {
private _abortController?;
private _url;
private _resourceMetadataUrl?;
private _scope?;
private _eventSourceInit?;
private _requestInit?;
private _authProvider?;
private _fetch?;
private _fetchWithInit;
private _protocolVersion?;
onclose?: () => void;
onerror?: (error: Error) => void;
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/client/sse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAyC,MAAM,wBAAwB,CAAC;AACrG,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AACnE,OAAO,EAAkD,mBAAmB,EAAqB,MAAM,WAAW,CAAC;AAEnH,qBAAa,QAAS,SAAQ,KAAK;aAEX,IAAI,EAAE,MAAM,GAAG,SAAS;aAExB,KAAK,EAAE,UAAU;gBAFjB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxC,OAAO,EAAE,MAAM,GAAG,SAAS,EACX,KAAK,EAAE,UAAU;CAIxC;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACpC;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAEnC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAChD,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,SAAS,CAAC,CAAM;IACxB,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,oBAAoB,CAAC,CAAM;IACnC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,cAAc,CAAY;IAClC,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAElC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,yBAAyB;YAWxC,cAAc;YAyBd,cAAc;IAoB5B,OAAO,CAAC,YAAY;IAyEd,KAAK;IAQX;;OAEG;IACG,UAAU,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAkDlD,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG5C"}
{"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../../src/client/sse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AACnE,OAAO,EAAgD,mBAAmB,EAAqB,MAAM,WAAW,CAAC;AAEjH,qBAAa,QAAS,SAAQ,KAAK;aAEf,IAAI,EAAE,MAAM,GAAG,SAAS;aAExB,KAAK,EAAE,UAAU;gBAFjB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxC,OAAO,EAAE,MAAM,GAAG,SAAS,EACX,KAAK,EAAE,UAAU;CAIpC;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAEnC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAClD,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,SAAS,CAAC,CAAM;IACxB,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,oBAAoB,CAAC,CAAM;IACnC,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAG5C,GAAG,EAAE,GAAG,EACR,IAAI,CAAC,EAAE,yBAAyB;YAUpB,cAAc;YAoBd,cAAc;IAiB5B,OAAO,CAAC,YAAY;IA6Ed,KAAK;IAUX;;OAEG;IACG,UAAU,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA0ClD,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG1C"}
+51 -73
View File
@@ -1,7 +1,6 @@
import { EventSource } from 'eventsource';
import { createFetchWithInit, normalizeHeaders } from '../shared/transport.js';
import { JSONRPCMessageSchema } from '../types.js';
import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js';
import { EventSource } from "eventsource";
import { JSONRPCMessageSchema } from "../types.js";
import { auth, extractResourceMetadataUrl, UnauthorizedError } from "./auth.js";
export class SseError extends Error {
constructor(code, message, event) {
super(`SSE error: ${message}`);
@@ -12,92 +11,83 @@ export class SseError extends Error {
/**
* Client transport for SSE: this will connect to a server using Server-Sent Events for receiving
* messages and make separate POST requests for sending messages.
* @deprecated SSEClientTransport is deprecated. Prefer to use StreamableHTTPClientTransport where possible instead. Note that because some servers are still using SSE, clients may need to support both transports during the migration period.
*/
export class SSEClientTransport {
constructor(url, opts) {
this._url = url;
this._resourceMetadataUrl = undefined;
this._scope = undefined;
this._eventSourceInit = opts?.eventSourceInit;
this._requestInit = opts?.requestInit;
this._authProvider = opts?.authProvider;
this._fetch = opts?.fetch;
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
this._eventSourceInit = opts === null || opts === void 0 ? void 0 : opts.eventSourceInit;
this._requestInit = opts === null || opts === void 0 ? void 0 : opts.requestInit;
this._authProvider = opts === null || opts === void 0 ? void 0 : opts.authProvider;
this._fetch = opts === null || opts === void 0 ? void 0 : opts.fetch;
}
async _authThenStart() {
var _a;
if (!this._authProvider) {
throw new UnauthorizedError('No auth provider');
throw new UnauthorizedError("No auth provider");
}
let result;
try {
result = await auth(this._authProvider, {
serverUrl: this._url,
resourceMetadataUrl: this._resourceMetadataUrl,
scope: this._scope,
fetchFn: this._fetchWithInit
});
result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
}
catch (error) {
this.onerror?.(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
throw error;
}
if (result !== 'AUTHORIZED') {
if (result !== "AUTHORIZED") {
throw new UnauthorizedError();
}
return await this._startOrAuth();
}
async _commonHeaders() {
var _a;
const headers = {};
if (this._authProvider) {
const tokens = await this._authProvider.tokens();
if (tokens) {
headers['Authorization'] = `Bearer ${tokens.access_token}`;
headers["Authorization"] = `Bearer ${tokens.access_token}`;
}
}
if (this._protocolVersion) {
headers['mcp-protocol-version'] = this._protocolVersion;
headers["mcp-protocol-version"] = this._protocolVersion;
}
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
return new Headers({
...headers,
...extraHeaders
});
return new Headers({ ...headers, ...(_a = this._requestInit) === null || _a === void 0 ? void 0 : _a.headers });
}
_startOrAuth() {
const fetchImpl = (this?._eventSourceInit?.fetch ?? this._fetch ?? fetch);
var _a, _b, _c;
const fetchImpl = ((_c = (_b = (_a = this === null || this === void 0 ? void 0 : this._eventSourceInit) === null || _a === void 0 ? void 0 : _a.fetch) !== null && _b !== void 0 ? _b : this._fetch) !== null && _c !== void 0 ? _c : fetch);
return new Promise((resolve, reject) => {
this._eventSource = new EventSource(this._url.href, {
...this._eventSourceInit,
fetch: async (url, init) => {
const headers = await this._commonHeaders();
headers.set('Accept', 'text/event-stream');
headers.set("Accept", "text/event-stream");
const response = await fetchImpl(url, {
...init,
headers
headers,
});
if (response.status === 401 && response.headers.has('www-authenticate')) {
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
this._resourceMetadataUrl = resourceMetadataUrl;
this._scope = scope;
this._resourceMetadataUrl = extractResourceMetadataUrl(response);
}
return response;
}
},
});
this._abortController = new AbortController();
this._eventSource.onerror = event => {
this._eventSource.onerror = (event) => {
var _a;
if (event.code === 401 && this._authProvider) {
this._authThenStart().then(resolve, reject);
return;
}
const error = new SseError(event.code, event.message, event);
reject(error);
this.onerror?.(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
};
this._eventSource.onopen = () => {
// The connection is open, but we need to wait for the endpoint to be received.
};
this._eventSource.addEventListener('endpoint', (event) => {
this._eventSource.addEventListener("endpoint", (event) => {
var _a;
const messageEvent = event;
try {
this._endpoint = new URL(messageEvent.data, this._url);
@@ -107,29 +97,30 @@ export class SSEClientTransport {
}
catch (error) {
reject(error);
this.onerror?.(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
void this.close();
return;
}
resolve();
});
this._eventSource.onmessage = (event) => {
var _a, _b;
const messageEvent = event;
let message;
try {
message = JSONRPCMessageSchema.parse(JSON.parse(messageEvent.data));
}
catch (error) {
this.onerror?.(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
return;
}
this.onmessage?.(message);
(_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, message);
};
});
}
async start() {
if (this._eventSource) {
throw new Error('SSEClientTransport already started! If using Client class, note that connect() calls start() automatically.');
throw new Error("SSEClientTransport already started! If using Client class, note that connect() calls start() automatically.");
}
return await this._startOrAuth();
}
@@ -138,64 +129,51 @@ export class SSEClientTransport {
*/
async finishAuth(authorizationCode) {
if (!this._authProvider) {
throw new UnauthorizedError('No auth provider');
throw new UnauthorizedError("No auth provider");
}
const result = await auth(this._authProvider, {
serverUrl: this._url,
authorizationCode,
resourceMetadataUrl: this._resourceMetadataUrl,
scope: this._scope,
fetchFn: this._fetchWithInit
});
if (result !== 'AUTHORIZED') {
throw new UnauthorizedError('Failed to authorize');
const result = await auth(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
if (result !== "AUTHORIZED") {
throw new UnauthorizedError("Failed to authorize");
}
}
async close() {
this._abortController?.abort();
this._eventSource?.close();
this.onclose?.();
var _a, _b, _c;
(_a = this._abortController) === null || _a === void 0 ? void 0 : _a.abort();
(_b = this._eventSource) === null || _b === void 0 ? void 0 : _b.close();
(_c = this.onclose) === null || _c === void 0 ? void 0 : _c.call(this);
}
async send(message) {
var _a, _b, _c;
if (!this._endpoint) {
throw new Error('Not connected');
throw new Error("Not connected");
}
try {
const headers = await this._commonHeaders();
headers.set('content-type', 'application/json');
headers.set("content-type", "application/json");
const init = {
...this._requestInit,
method: 'POST',
method: "POST",
headers,
body: JSON.stringify(message),
signal: this._abortController?.signal
signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal,
};
const response = await (this._fetch ?? fetch)(this._endpoint, init);
const response = await ((_b = this._fetch) !== null && _b !== void 0 ? _b : fetch)(this._endpoint, init);
if (!response.ok) {
const text = await response.text().catch(() => null);
if (response.status === 401 && this._authProvider) {
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
this._resourceMetadataUrl = resourceMetadataUrl;
this._scope = scope;
const result = await auth(this._authProvider, {
serverUrl: this._url,
resourceMetadataUrl: this._resourceMetadataUrl,
scope: this._scope,
fetchFn: this._fetchWithInit
});
if (result !== 'AUTHORIZED') {
this._resourceMetadataUrl = extractResourceMetadataUrl(response);
const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
if (result !== "AUTHORIZED") {
throw new UnauthorizedError();
}
// Purposely _not_ awaited, so we don't call onerror twice
return this.send(message);
}
const text = await response.text().catch(() => null);
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
}
// Release connection - POST responses don't have content we need
await response.body?.cancel();
}
catch (error) {
this.onerror?.(error);
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
throw error;
}
}
File diff suppressed because one or more lines are too long
+5 -4
View File
@@ -1,7 +1,7 @@
import { IOType } from 'node:child_process';
import { Stream } from 'node:stream';
import { Transport } from '../shared/transport.js';
import { JSONRPCMessage } from '../types.js';
import { IOType } from "node:child_process";
import { Stream } from "node:stream";
import { Transport } from "../shared/transport.js";
import { JSONRPCMessage } from "../types.js";
export type StdioServerParameters = {
/**
* The executable to run to start the server.
@@ -45,6 +45,7 @@ export declare function getDefaultEnvironment(): Record<string, string>;
*/
export declare class StdioClientTransport implements Transport {
private _process?;
private _abortController;
private _readBuffer;
private _serverParams;
private _stderrStream;
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../../src/client/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAE,MAAM,EAAe,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,qBAAqB,GAAG;IAChC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAElC;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,UAiBuB,CAAC;AAE/D;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkB9D;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,SAAS;IAClD,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,aAAa,CAA4B;IAEjD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAElC,MAAM,EAAE,qBAAqB;IAOzC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqD5B;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,CAM1B;IAED;;;;OAIG;IACH,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,CAEvB;IAED,OAAO,CAAC,iBAAiB;IAenB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC5B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAc/C"}
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../../src/client/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAE,MAAM,EAAe,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAElC;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,UAiBmB,CAAC;AAE3D;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkB9D;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,SAAS;IACpD,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,gBAAgB,CAA0C;IAClE,OAAO,CAAC,WAAW,CAAgC;IACnD,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,aAAa,CAA4B;IAEjD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAElC,MAAM,EAAE,qBAAqB;IAOzC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgE5B;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,CAM1B;IAED;;;;OAIG;IACH,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,CAEvB;IAED,OAAO,CAAC,iBAAiB;IAenB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAc7C"}
+63 -78
View File
@@ -1,27 +1,27 @@
import spawn from 'cross-spawn';
import process from 'node:process';
import { PassThrough } from 'node:stream';
import { ReadBuffer, serializeMessage } from '../shared/stdio.js';
import spawn from "cross-spawn";
import process from "node:process";
import { PassThrough } from "node:stream";
import { ReadBuffer, serializeMessage } from "../shared/stdio.js";
/**
* Environment variables to inherit by default, if an environment is not explicitly given.
*/
export const DEFAULT_INHERITED_ENV_VARS = process.platform === 'win32'
export const DEFAULT_INHERITED_ENV_VARS = process.platform === "win32"
? [
'APPDATA',
'HOMEDRIVE',
'HOMEPATH',
'LOCALAPPDATA',
'PATH',
'PROCESSOR_ARCHITECTURE',
'SYSTEMDRIVE',
'SYSTEMROOT',
'TEMP',
'USERNAME',
'USERPROFILE',
'PROGRAMFILES'
"APPDATA",
"HOMEDRIVE",
"HOMEPATH",
"LOCALAPPDATA",
"PATH",
"PROCESSOR_ARCHITECTURE",
"SYSTEMDRIVE",
"SYSTEMROOT",
"TEMP",
"USERNAME",
"USERPROFILE",
"PROGRAMFILES",
]
: /* list inspired by the default env inheritance of sudo */
['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"];
/**
* Returns a default environment object including only environment variables deemed safe to inherit.
*/
@@ -32,7 +32,7 @@ export function getDefaultEnvironment() {
if (value === undefined) {
continue;
}
if (value.startsWith('()')) {
if (value.startsWith("()")) {
// Skip functions, which are a security risk.
continue;
}
@@ -47,10 +47,11 @@ export function getDefaultEnvironment() {
*/
export class StdioClientTransport {
constructor(server) {
this._abortController = new AbortController();
this._readBuffer = new ReadBuffer();
this._stderrStream = null;
this._serverParams = server;
if (server.stderr === 'pipe' || server.stderr === 'overlapped') {
if (server.stderr === "pipe" || server.stderr === "overlapped") {
this._stderrStream = new PassThrough();
}
}
@@ -59,40 +60,51 @@ export class StdioClientTransport {
*/
async start() {
if (this._process) {
throw new Error('StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.');
throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
}
return new Promise((resolve, reject) => {
this._process = spawn(this._serverParams.command, this._serverParams.args ?? [], {
var _a, _b, _c, _d, _e;
this._process = spawn(this._serverParams.command, (_a = this._serverParams.args) !== null && _a !== void 0 ? _a : [], {
// merge default env with server env because mcp server needs some env vars
env: {
...getDefaultEnvironment(),
...this._serverParams.env
...this._serverParams.env,
},
stdio: ['pipe', 'pipe', this._serverParams.stderr ?? 'inherit'],
stdio: ["pipe", "pipe", (_b = this._serverParams.stderr) !== null && _b !== void 0 ? _b : "inherit"],
shell: false,
windowsHide: process.platform === 'win32' && isElectron(),
cwd: this._serverParams.cwd
signal: this._abortController.signal,
windowsHide: process.platform === "win32" && isElectron(),
cwd: this._serverParams.cwd,
});
this._process.on('error', error => {
this._process.on("error", (error) => {
var _a, _b;
if (error.name === "AbortError") {
// Expected when close() is called.
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
return;
}
reject(error);
this.onerror?.(error);
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
});
this._process.on('spawn', () => {
this._process.on("spawn", () => {
resolve();
});
this._process.on('close', _code => {
this._process.on("close", (_code) => {
var _a;
this._process = undefined;
this.onclose?.();
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
});
this._process.stdin?.on('error', error => {
this.onerror?.(error);
(_c = this._process.stdin) === null || _c === void 0 ? void 0 : _c.on("error", (error) => {
var _a;
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
});
this._process.stdout?.on('data', chunk => {
(_d = this._process.stdout) === null || _d === void 0 ? void 0 : _d.on("data", (chunk) => {
this._readBuffer.append(chunk);
this.processReadBuffer();
});
this._process.stdout?.on('error', error => {
this.onerror?.(error);
(_e = this._process.stdout) === null || _e === void 0 ? void 0 : _e.on("error", (error) => {
var _a;
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
});
if (this._stderrStream && this._process.stderr) {
this._process.stderr.pipe(this._stderrStream);
@@ -107,10 +119,11 @@ export class StdioClientTransport {
* error output emitted by the child process.
*/
get stderr() {
var _a, _b;
if (this._stderrStream) {
return this._stderrStream;
}
return this._process?.stderr ?? null;
return (_b = (_a = this._process) === null || _a === void 0 ? void 0 : _a.stderr) !== null && _b !== void 0 ? _b : null;
}
/**
* The child process pid spawned by this transport.
@@ -118,74 +131,46 @@ export class StdioClientTransport {
* This is only available after the transport has been started.
*/
get pid() {
return this._process?.pid ?? null;
var _a, _b;
return (_b = (_a = this._process) === null || _a === void 0 ? void 0 : _a.pid) !== null && _b !== void 0 ? _b : null;
}
processReadBuffer() {
var _a, _b;
while (true) {
try {
const message = this._readBuffer.readMessage();
if (message === null) {
break;
}
this.onmessage?.(message);
(_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
}
catch (error) {
this.onerror?.(error);
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
}
}
}
async close() {
if (this._process) {
const processToClose = this._process;
this._process = undefined;
const closePromise = new Promise(resolve => {
processToClose.once('close', () => {
resolve();
});
});
try {
processToClose.stdin?.end();
}
catch {
// ignore
}
await Promise.race([closePromise, new Promise(resolve => setTimeout(resolve, 2000).unref())]);
if (processToClose.exitCode === null) {
try {
processToClose.kill('SIGTERM');
}
catch {
// ignore
}
await Promise.race([closePromise, new Promise(resolve => setTimeout(resolve, 2000).unref())]);
}
if (processToClose.exitCode === null) {
try {
processToClose.kill('SIGKILL');
}
catch {
// ignore
}
}
}
this._abortController.abort();
this._process = undefined;
this._readBuffer.clear();
}
send(message) {
return new Promise(resolve => {
if (!this._process?.stdin) {
throw new Error('Not connected');
return new Promise((resolve) => {
var _a;
if (!((_a = this._process) === null || _a === void 0 ? void 0 : _a.stdin)) {
throw new Error("Not connected");
}
const json = serializeMessage(message);
if (this._process.stdin.write(json)) {
resolve();
}
else {
this._process.stdin.once('drain', resolve);
this._process.stdin.once("drain", resolve);
}
});
}
}
function isElectron() {
return 'type' in process;
return "type" in process;
}
//# sourceMappingURL=stdio.js.map
File diff suppressed because one or more lines are too long
+8 -23
View File
@@ -1,6 +1,6 @@
import { Transport, FetchLike } from '../shared/transport.js';
import { JSONRPCMessage } from '../types.js';
import { OAuthClientProvider } from './auth.js';
import { Transport, FetchLike } from "../shared/transport.js";
import { JSONRPCMessage } from "../types.js";
import { OAuthClientProvider } from "./auth.js";
export declare class StreamableHTTPError extends Error {
readonly code: number | undefined;
constructor(code: number | undefined, message: string | undefined);
@@ -22,9 +22,9 @@ export interface StartSSEOptions {
*/
onresumptiontoken?: (token: string) => void;
/**
* Override Message ID to associate with the replay message
* so that response can be associate with the new resumed request.
*/
* Override Message ID to associate with the replay message
* so that response can be associate with the new resumed request.
*/
replayMessageId?: string | number;
}
/**
@@ -98,18 +98,12 @@ export declare class StreamableHTTPClientTransport implements Transport {
private _abortController?;
private _url;
private _resourceMetadataUrl?;
private _scope?;
private _requestInit?;
private _authProvider?;
private _fetch?;
private _fetchWithInit;
private _sessionId?;
private _reconnectionOptions;
private _protocolVersion?;
private _hasCompletedAuthFlow;
private _lastUpscopingHeader?;
private _serverRetryMs?;
private _reconnectionTimeout?;
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
@@ -124,8 +118,9 @@ export declare class StreamableHTTPClientTransport implements Transport {
* @returns Time to wait in milliseconds before next reconnection attempt
*/
private _getNextReconnectionDelay;
private _normalizeHeaders;
/**
* Schedule a reconnection attempt using server-provided retry interval or backoff
* Schedule a reconnection attempt with exponential backoff
*
* @param lastEventId The ID of the last received event for resumability
* @param attemptCount Current reconnection attempt count for this specific stream
@@ -157,15 +152,5 @@ export declare class StreamableHTTPClientTransport implements Transport {
terminateSession(): Promise<void>;
setProtocolVersion(version: string): void;
get protocolVersion(): string | undefined;
/**
* Resume an SSE stream from a previous event ID.
* Opens a GET SSE connection with Last-Event-ID header to replay missed events.
*
* @param lastEventId The event ID to resume from
* @param options Optional callback to receive new resumption tokens
*/
resumeStream(lastEventId: string, options?: {
onresumptiontoken?: (token: string) => void;
}): Promise<void>;
}
//# sourceMappingURL=streamableHttp.d.ts.map
@@ -1 +1 @@
{"version":3,"file":"streamableHttp.d.ts","sourceRoot":"","sources":["../../../src/client/streamableHttp.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"streamableHttp.d.ts","sourceRoot":"","sources":["../../../src/client/streamableHttp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAkE,cAAc,EAAwB,MAAM,aAAa,CAAC;AACnI,OAAO,EAAgD,mBAAmB,EAAqB,MAAM,WAAW,CAAC;AAWjH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAAE,MAAM,GAAG,SAAS;gBAAxB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxC,OAAO,EAAE,MAAM,GAAG,SAAS;CAI9B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAE5C;;;MAGE;IACF,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,iCAAiC;IAChD;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,wBAAwB,EAAE,MAAM,CAAC;IAEjC;;;OAGG;IACH,2BAA2B,EAAE,MAAM,CAAC;IAEpC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,oCAAoC,GAAG;IACjD;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAEnC;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAE1B;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAElB;;OAEG;IACH,mBAAmB,CAAC,EAAE,iCAAiC,CAAC;IAExD;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,6BAA8B,YAAW,SAAS;IAC7D,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,oBAAoB,CAAC,CAAM;IACnC,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,oBAAoB,CAAoC;IAChE,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAG5C,GAAG,EAAE,GAAG,EACR,IAAI,CAAC,EAAE,oCAAoC;YAW/B,cAAc;YAoBd,cAAc;YAyBd,eAAe;IA6C7B;;;;;OAKG;IACH,OAAO,CAAC,yBAAyB;IAW/B,OAAO,CAAC,iBAAiB;IAc3B;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IAwB7B,OAAO,CAAC,gBAAgB;IA0ElB,KAAK;IAUX;;OAEG;IACG,UAAU,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkG1J,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED;;;;;;;;;;OAUG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCvC,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAGzC,IAAI,eAAe,IAAI,MAAM,GAAG,SAAS,CAExC;CACF"}
+94 -196
View File
@@ -1,13 +1,12 @@
import { createFetchWithInit, normalizeHeaders } from '../shared/transport.js';
import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js';
import { EventSourceParserStream } from 'eventsource-parser/stream';
import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResponse, JSONRPCMessageSchema } from "../types.js";
import { auth, extractResourceMetadataUrl, UnauthorizedError } from "./auth.js";
import { EventSourceParserStream } from "eventsource-parser/stream";
// Default reconnection options for StreamableHTTP connections
const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS = {
initialReconnectionDelay: 1000,
maxReconnectionDelay: 30000,
reconnectionDelayGrowFactor: 1.5,
maxRetries: 2
maxRetries: 2,
};
export class StreamableHTTPError extends Error {
constructor(code, message) {
@@ -22,77 +21,72 @@ export class StreamableHTTPError extends Error {
*/
export class StreamableHTTPClientTransport {
constructor(url, opts) {
this._hasCompletedAuthFlow = false; // Circuit breaker: detect auth success followed by immediate 401
var _a;
this._url = url;
this._resourceMetadataUrl = undefined;
this._scope = undefined;
this._requestInit = opts?.requestInit;
this._authProvider = opts?.authProvider;
this._fetch = opts?.fetch;
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
this._sessionId = opts?.sessionId;
this._reconnectionOptions = opts?.reconnectionOptions ?? DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
this._requestInit = opts === null || opts === void 0 ? void 0 : opts.requestInit;
this._authProvider = opts === null || opts === void 0 ? void 0 : opts.authProvider;
this._fetch = opts === null || opts === void 0 ? void 0 : opts.fetch;
this._sessionId = opts === null || opts === void 0 ? void 0 : opts.sessionId;
this._reconnectionOptions = (_a = opts === null || opts === void 0 ? void 0 : opts.reconnectionOptions) !== null && _a !== void 0 ? _a : DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS;
}
async _authThenStart() {
var _a;
if (!this._authProvider) {
throw new UnauthorizedError('No auth provider');
throw new UnauthorizedError("No auth provider");
}
let result;
try {
result = await auth(this._authProvider, {
serverUrl: this._url,
resourceMetadataUrl: this._resourceMetadataUrl,
scope: this._scope,
fetchFn: this._fetchWithInit
});
result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
}
catch (error) {
this.onerror?.(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
throw error;
}
if (result !== 'AUTHORIZED') {
if (result !== "AUTHORIZED") {
throw new UnauthorizedError();
}
return await this._startOrAuthSse({ resumptionToken: undefined });
}
async _commonHeaders() {
var _a;
const headers = {};
if (this._authProvider) {
const tokens = await this._authProvider.tokens();
if (tokens) {
headers['Authorization'] = `Bearer ${tokens.access_token}`;
headers["Authorization"] = `Bearer ${tokens.access_token}`;
}
}
if (this._sessionId) {
headers['mcp-session-id'] = this._sessionId;
headers["mcp-session-id"] = this._sessionId;
}
if (this._protocolVersion) {
headers['mcp-protocol-version'] = this._protocolVersion;
headers["mcp-protocol-version"] = this._protocolVersion;
}
const extraHeaders = normalizeHeaders(this._requestInit?.headers);
const extraHeaders = this._normalizeHeaders((_a = this._requestInit) === null || _a === void 0 ? void 0 : _a.headers);
return new Headers({
...headers,
...extraHeaders
...extraHeaders,
});
}
async _startOrAuthSse(options) {
var _a, _b, _c;
const { resumptionToken } = options;
try {
// Try to open an initial SSE stream with GET to listen for server messages
// This is optional according to the spec - server may not support it
const headers = await this._commonHeaders();
headers.set('Accept', 'text/event-stream');
headers.set("Accept", "text/event-stream");
// Include Last-Event-ID header for resumable streams if provided
if (resumptionToken) {
headers.set('last-event-id', resumptionToken);
headers.set("last-event-id", resumptionToken);
}
const response = await (this._fetch ?? fetch)(this._url, {
method: 'GET',
const response = await ((_a = this._fetch) !== null && _a !== void 0 ? _a : fetch)(this._url, {
method: "GET",
headers,
signal: this._abortController?.signal
signal: (_b = this._abortController) === null || _b === void 0 ? void 0 : _b.signal,
});
if (!response.ok) {
await response.body?.cancel();
if (response.status === 401 && this._authProvider) {
// Need to authenticate
return await this._authThenStart();
@@ -107,7 +101,7 @@ export class StreamableHTTPClientTransport {
this._handleSseStream(response.body, options, true);
}
catch (error) {
this.onerror?.(error);
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
throw error;
}
}
@@ -118,38 +112,47 @@ export class StreamableHTTPClientTransport {
* @returns Time to wait in milliseconds before next reconnection attempt
*/
_getNextReconnectionDelay(attempt) {
// Use server-provided retry value if available
if (this._serverRetryMs !== undefined) {
return this._serverRetryMs;
}
// Fall back to exponential backoff
// Access default values directly, ensuring they're never undefined
const initialDelay = this._reconnectionOptions.initialReconnectionDelay;
const growFactor = this._reconnectionOptions.reconnectionDelayGrowFactor;
const maxDelay = this._reconnectionOptions.maxReconnectionDelay;
// Cap at maximum delay
return Math.min(initialDelay * Math.pow(growFactor, attempt), maxDelay);
}
_normalizeHeaders(headers) {
if (!headers)
return {};
if (headers instanceof Headers) {
return Object.fromEntries(headers.entries());
}
if (Array.isArray(headers)) {
return Object.fromEntries(headers);
}
return { ...headers };
}
/**
* Schedule a reconnection attempt using server-provided retry interval or backoff
* Schedule a reconnection attempt with exponential backoff
*
* @param lastEventId The ID of the last received event for resumability
* @param attemptCount Current reconnection attempt count for this specific stream
*/
_scheduleReconnection(options, attemptCount = 0) {
var _a;
// Use provided options or default options
const maxRetries = this._reconnectionOptions.maxRetries;
// Check if we've exceeded maximum retry attempts
if (attemptCount >= maxRetries) {
this.onerror?.(new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
if (maxRetries > 0 && attemptCount >= maxRetries) {
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
return;
}
// Calculate next delay based on current attempt count
const delay = this._getNextReconnectionDelay(attemptCount);
// Schedule the reconnection
this._reconnectionTimeout = setTimeout(() => {
setTimeout(() => {
// Use the last event ID to resume where we left off
this._startOrAuthSse(options).catch(error => {
this.onerror?.(new Error(`Failed to reconnect SSE stream: ${error instanceof Error ? error.message : String(error)}`));
var _a;
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`Failed to reconnect SSE stream: ${error instanceof Error ? error.message : String(error)}`));
// Schedule another attempt if this one failed, incrementing the attempt counter
this._scheduleReconnection(options, attemptCount + 1);
});
@@ -161,25 +164,15 @@ export class StreamableHTTPClientTransport {
}
const { onresumptiontoken, replayMessageId } = options;
let lastEventId;
// Track whether we've received a priming event (event with ID)
// Per spec, server SHOULD send a priming event with ID before closing
let hasPrimingEvent = false;
// Track whether we've received a response - if so, no need to reconnect
// Reconnection is for when server disconnects BEFORE sending response
let receivedResponse = false;
const processStream = async () => {
var _a, _b, _c, _d;
// this is the closest we can get to trying to catch network errors
// if something happens reader will throw
try {
// Create a pipeline: binary stream -> text decoder -> SSE parser
const reader = stream
.pipeThrough(new TextDecoderStream())
.pipeThrough(new EventSourceParserStream({
onRetry: (retryMs) => {
// Capture server-provided retry value for reconnection timing
this._serverRetryMs = retryMs;
}
}))
.pipeThrough(new EventSourceParserStream())
.getReader();
while (true) {
const { value: event, done } = await reader.read();
@@ -189,54 +182,29 @@ export class StreamableHTTPClientTransport {
// Update last event ID if provided
if (event.id) {
lastEventId = event.id;
// Mark that we've received a priming event - stream is now resumable
hasPrimingEvent = true;
onresumptiontoken?.(event.id);
onresumptiontoken === null || onresumptiontoken === void 0 ? void 0 : onresumptiontoken(event.id);
}
// Skip events with no data (priming events, keep-alives)
if (!event.data) {
continue;
}
if (!event.event || event.event === 'message') {
if (!event.event || event.event === "message") {
try {
const message = JSONRPCMessageSchema.parse(JSON.parse(event.data));
if (isJSONRPCResultResponse(message)) {
// Mark that we received a response - no need to reconnect for this request
receivedResponse = true;
if (replayMessageId !== undefined) {
message.id = replayMessageId;
}
if (replayMessageId !== undefined && isJSONRPCResponse(message)) {
message.id = replayMessageId;
}
this.onmessage?.(message);
(_a = this.onmessage) === null || _a === void 0 ? void 0 : _a.call(this, message);
}
catch (error) {
this.onerror?.(error);
(_b = this.onerror) === null || _b === void 0 ? void 0 : _b.call(this, error);
}
}
}
// Handle graceful server-side disconnect
// Server may close connection after sending event ID and retry field
// Reconnect if: already reconnectable (GET stream) OR received a priming event (POST stream with event ID)
// BUT don't reconnect if we already received a response - the request is complete
const canResume = isReconnectable || hasPrimingEvent;
const needsReconnect = canResume && !receivedResponse;
if (needsReconnect && this._abortController && !this._abortController.signal.aborted) {
this._scheduleReconnection({
resumptionToken: lastEventId,
onresumptiontoken,
replayMessageId
}, 0);
}
}
catch (error) {
// Handle stream errors - likely a network disconnect
this.onerror?.(new Error(`SSE stream disconnected: ${error}`));
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, new Error(`SSE stream disconnected: ${error}`));
// Attempt to reconnect if the stream disconnects unexpectedly and we aren't closing
// Reconnect if: already reconnectable (GET stream) OR received a priming event (POST stream with event ID)
// BUT don't reconnect if we already received a response - the request is complete
const canResume = isReconnectable || hasPrimingEvent;
const needsReconnect = canResume && !receivedResponse;
if (needsReconnect && this._abortController && !this._abortController.signal.aborted) {
if (isReconnectable &&
this._abortController &&
!this._abortController.signal.aborted) {
// Use the exponential backoff reconnection strategy
try {
this._scheduleReconnection({
@@ -246,7 +214,7 @@ export class StreamableHTTPClientTransport {
}, 0);
}
catch (error) {
this.onerror?.(new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, new Error(`Failed to reconnect: ${error instanceof Error ? error.message : String(error)}`));
}
}
}
@@ -255,7 +223,7 @@ export class StreamableHTTPClientTransport {
}
async start() {
if (this._abortController) {
throw new Error('StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.');
throw new Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");
}
this._abortController = new AbortController();
}
@@ -264,153 +232,96 @@ export class StreamableHTTPClientTransport {
*/
async finishAuth(authorizationCode) {
if (!this._authProvider) {
throw new UnauthorizedError('No auth provider');
throw new UnauthorizedError("No auth provider");
}
const result = await auth(this._authProvider, {
serverUrl: this._url,
authorizationCode,
resourceMetadataUrl: this._resourceMetadataUrl,
scope: this._scope,
fetchFn: this._fetchWithInit
});
if (result !== 'AUTHORIZED') {
throw new UnauthorizedError('Failed to authorize');
const result = await auth(this._authProvider, { serverUrl: this._url, authorizationCode, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
if (result !== "AUTHORIZED") {
throw new UnauthorizedError("Failed to authorize");
}
}
async close() {
if (this._reconnectionTimeout) {
clearTimeout(this._reconnectionTimeout);
this._reconnectionTimeout = undefined;
}
this._abortController?.abort();
this.onclose?.();
var _a, _b;
// Abort any pending requests
(_a = this._abortController) === null || _a === void 0 ? void 0 : _a.abort();
(_b = this.onclose) === null || _b === void 0 ? void 0 : _b.call(this);
}
async send(message, options) {
var _a, _b, _c, _d;
try {
const { resumptionToken, onresumptiontoken } = options || {};
if (resumptionToken) {
// If we have at last event ID, we need to reconnect the SSE stream
this._startOrAuthSse({ resumptionToken, replayMessageId: isJSONRPCRequest(message) ? message.id : undefined }).catch(err => this.onerror?.(err));
this._startOrAuthSse({ resumptionToken, replayMessageId: isJSONRPCRequest(message) ? message.id : undefined }).catch(err => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err); });
return;
}
const headers = await this._commonHeaders();
headers.set('content-type', 'application/json');
headers.set('accept', 'application/json, text/event-stream');
headers.set("content-type", "application/json");
headers.set("accept", "application/json, text/event-stream");
const init = {
...this._requestInit,
method: 'POST',
method: "POST",
headers,
body: JSON.stringify(message),
signal: this._abortController?.signal
signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal,
};
const response = await (this._fetch ?? fetch)(this._url, init);
const response = await ((_b = this._fetch) !== null && _b !== void 0 ? _b : fetch)(this._url, init);
// Handle session ID received during initialization
const sessionId = response.headers.get('mcp-session-id');
const sessionId = response.headers.get("mcp-session-id");
if (sessionId) {
this._sessionId = sessionId;
}
if (!response.ok) {
const text = await response.text().catch(() => null);
if (response.status === 401 && this._authProvider) {
// Prevent infinite recursion when server returns 401 after successful auth
if (this._hasCompletedAuthFlow) {
throw new StreamableHTTPError(401, 'Server returned 401 after successful authentication');
}
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
this._resourceMetadataUrl = resourceMetadataUrl;
this._scope = scope;
const result = await auth(this._authProvider, {
serverUrl: this._url,
resourceMetadataUrl: this._resourceMetadataUrl,
scope: this._scope,
fetchFn: this._fetchWithInit
});
if (result !== 'AUTHORIZED') {
this._resourceMetadataUrl = extractResourceMetadataUrl(response);
const result = await auth(this._authProvider, { serverUrl: this._url, resourceMetadataUrl: this._resourceMetadataUrl, fetchFn: this._fetch });
if (result !== "AUTHORIZED") {
throw new UnauthorizedError();
}
// Mark that we completed auth flow
this._hasCompletedAuthFlow = true;
// Purposely _not_ awaited, so we don't call onerror twice
return this.send(message);
}
if (response.status === 403 && this._authProvider) {
const { resourceMetadataUrl, scope, error } = extractWWWAuthenticateParams(response);
if (error === 'insufficient_scope') {
const wwwAuthHeader = response.headers.get('WWW-Authenticate');
// Check if we've already tried upscoping with this header to prevent infinite loops.
if (this._lastUpscopingHeader === wwwAuthHeader) {
throw new StreamableHTTPError(403, 'Server returned 403 after trying upscoping');
}
if (scope) {
this._scope = scope;
}
if (resourceMetadataUrl) {
this._resourceMetadataUrl = resourceMetadataUrl;
}
// Mark that upscoping was tried.
this._lastUpscopingHeader = wwwAuthHeader ?? undefined;
const result = await auth(this._authProvider, {
serverUrl: this._url,
resourceMetadataUrl: this._resourceMetadataUrl,
scope: this._scope,
fetchFn: this._fetch
});
if (result !== 'AUTHORIZED') {
throw new UnauthorizedError();
}
return this.send(message);
}
}
throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`);
const text = await response.text().catch(() => null);
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
}
// Reset auth loop flag on successful response
this._hasCompletedAuthFlow = false;
this._lastUpscopingHeader = undefined;
// If the response is 202 Accepted, there's no body to process
if (response.status === 202) {
await response.body?.cancel();
// if the accepted notification is initialized, we start the SSE stream
// if it's supported by the server
if (isInitializedNotification(message)) {
// Start without a lastEventId since this is a fresh connection
this._startOrAuthSse({ resumptionToken: undefined }).catch(err => this.onerror?.(err));
this._startOrAuthSse({ resumptionToken: undefined }).catch(err => { var _a; return (_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, err); });
}
return;
}
// Get original message(s) for detecting request IDs
const messages = Array.isArray(message) ? message : [message];
const hasRequests = messages.filter(msg => 'method' in msg && 'id' in msg && msg.id !== undefined).length > 0;
const hasRequests = messages.filter(msg => "method" in msg && "id" in msg && msg.id !== undefined).length > 0;
// Check the response type
const contentType = response.headers.get('content-type');
const contentType = response.headers.get("content-type");
if (hasRequests) {
if (contentType?.includes('text/event-stream')) {
if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("text/event-stream")) {
// Handle SSE stream responses for requests
// We use the same handler as standalone streams, which now supports
// reconnection with the last event ID
this._handleSseStream(response.body, { onresumptiontoken }, false);
}
else if (contentType?.includes('application/json')) {
else if (contentType === null || contentType === void 0 ? void 0 : contentType.includes("application/json")) {
// For non-streaming servers, we might get direct JSON responses
const data = await response.json();
const responseMessages = Array.isArray(data)
? data.map(msg => JSONRPCMessageSchema.parse(msg))
: [JSONRPCMessageSchema.parse(data)];
for (const msg of responseMessages) {
this.onmessage?.(msg);
(_c = this.onmessage) === null || _c === void 0 ? void 0 : _c.call(this, msg);
}
}
else {
await response.body?.cancel();
throw new StreamableHTTPError(-1, `Unexpected content type: ${contentType}`);
}
}
else {
// No requests in message but got 200 OK - still need to release connection
await response.body?.cancel();
}
}
catch (error) {
this.onerror?.(error);
(_d = this.onerror) === null || _d === void 0 ? void 0 : _d.call(this, error);
throw error;
}
}
@@ -429,6 +340,7 @@ export class StreamableHTTPClientTransport {
* the server does not allow clients to terminate sessions.
*/
async terminateSession() {
var _a, _b, _c;
if (!this._sessionId) {
return; // No session to terminate
}
@@ -436,12 +348,11 @@ export class StreamableHTTPClientTransport {
const headers = await this._commonHeaders();
const init = {
...this._requestInit,
method: 'DELETE',
method: "DELETE",
headers,
signal: this._abortController?.signal
signal: (_a = this._abortController) === null || _a === void 0 ? void 0 : _a.signal,
};
const response = await (this._fetch ?? fetch)(this._url, init);
await response.body?.cancel();
const response = await ((_b = this._fetch) !== null && _b !== void 0 ? _b : fetch)(this._url, init);
// We specifically handle 405 as a valid response according to the spec,
// meaning the server does not support explicit session termination
if (!response.ok && response.status !== 405) {
@@ -450,7 +361,7 @@ export class StreamableHTTPClientTransport {
this._sessionId = undefined;
}
catch (error) {
this.onerror?.(error);
(_c = this.onerror) === null || _c === void 0 ? void 0 : _c.call(this, error);
throw error;
}
}
@@ -460,18 +371,5 @@ export class StreamableHTTPClientTransport {
get protocolVersion() {
return this._protocolVersion;
}
/**
* Resume an SSE stream from a previous event ID.
* Opens a GET SSE connection with Last-Event-ID header to replay missed events.
*
* @param lastEventId The event ID to resume from
* @param options Optional callback to receive new resumption tokens
*/
async resumeStream(lastEventId, options) {
await this._startOrAuthSse({
resumptionToken: lastEventId,
onresumptiontoken: options?.onresumptiontoken
});
}
}
//# sourceMappingURL=streamableHttp.js.map
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,5 +1,5 @@
import { Transport } from '../shared/transport.js';
import { JSONRPCMessage } from '../types.js';
import { Transport } from "../shared/transport.js";
import { JSONRPCMessage } from "../types.js";
/**
* Client transport for WebSocket: this will connect to a server over the WebSocket protocol.
*/
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../../src/client/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AAInE;;GAEG;AACH,qBAAa,wBAAyB,YAAW,SAAS;IACtD,OAAO,CAAC,OAAO,CAAC,CAAY;IAC5B,OAAO,CAAC,IAAI,CAAM;IAElB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAElC,GAAG,EAAE,GAAG;IAIpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsChB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAW/C"}
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../../src/client/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAwB,MAAM,aAAa,CAAC;AAInE;;GAEG;AACH,qBAAa,wBAAyB,YAAW,SAAS;IACxD,OAAO,CAAC,OAAO,CAAC,CAAY;IAC5B,OAAO,CAAC,IAAI,CAAM;IAElB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAElC,GAAG,EAAE,GAAG;IAIpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyChB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;CAW7C"}
+19 -12
View File
@@ -1,5 +1,5 @@
import { JSONRPCMessageSchema } from '../types.js';
const SUBPROTOCOL = 'mcp';
import { JSONRPCMessageSchema } from "../types.js";
const SUBPROTOCOL = "mcp";
/**
* Client transport for WebSocket: this will connect to a server over the WebSocket protocol.
*/
@@ -9,44 +9,51 @@ export class WebSocketClientTransport {
}
start() {
if (this._socket) {
throw new Error('WebSocketClientTransport already started! If using Client class, note that connect() calls start() automatically.');
throw new Error("WebSocketClientTransport already started! If using Client class, note that connect() calls start() automatically.");
}
return new Promise((resolve, reject) => {
this._socket = new WebSocket(this._url, SUBPROTOCOL);
this._socket.onerror = event => {
const error = 'error' in event ? event.error : new Error(`WebSocket error: ${JSON.stringify(event)}`);
this._socket.onerror = (event) => {
var _a;
const error = "error" in event
? event.error
: new Error(`WebSocket error: ${JSON.stringify(event)}`);
reject(error);
this.onerror?.(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
};
this._socket.onopen = () => {
resolve();
};
this._socket.onclose = () => {
this.onclose?.();
var _a;
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
};
this._socket.onmessage = (event) => {
var _a, _b;
let message;
try {
message = JSONRPCMessageSchema.parse(JSON.parse(event.data));
}
catch (error) {
this.onerror?.(error);
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
return;
}
this.onmessage?.(message);
(_b = this.onmessage) === null || _b === void 0 ? void 0 : _b.call(this, message);
};
});
}
async close() {
this._socket?.close();
var _a;
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.close();
}
send(message) {
return new Promise((resolve, reject) => {
var _a;
if (!this._socket) {
reject(new Error('Not connected'));
reject(new Error("Not connected"));
return;
}
this._socket?.send(JSON.stringify(message));
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify(message));
resolve();
});
}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../../src/client/websocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,WAAW,GAAG,KAAK,CAAC;AAE1B;;GAEG;AACH,MAAM,OAAO,wBAAwB;IAQjC,YAAY,GAAQ;QAChB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;IACpB,CAAC;IAED,KAAK;QACD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACX,mHAAmH,CACtH,CAAC;QACN,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,IAAI,CAAC,OAAO,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAErD,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,EAAE;gBAC3B,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,CAAC,CAAC,CAAE,KAAK,CAAC,KAAe,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACjH,MAAM,CAAC,KAAK,CAAC,CAAC;gBACd,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YAC1B,CAAC,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE;gBACvB,OAAO,EAAE,CAAC;YACd,CAAC,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE;gBACxB,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACrB,CAAC,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;gBAC7C,IAAI,OAAuB,CAAC;gBAC5B,IAAI,CAAC;oBACD,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBACjE,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACb,IAAI,CAAC,OAAO,EAAE,CAAC,KAAc,CAAC,CAAC;oBAC/B,OAAO;gBACX,CAAC;gBAED,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC;YAC9B,CAAC,CAAC;QACN,CAAC,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,KAAK;QACP,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC,OAAuB;QACxB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAChB,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;gBACnC,OAAO;YACX,CAAC;YAED,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;YAC5C,OAAO,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;IACP,CAAC;CACJ"}
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../../src/client/websocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,WAAW,GAAG,KAAK,CAAC;AAE1B;;GAEG;AACH,MAAM,OAAO,wBAAwB;IAQnC,YAAY,GAAQ;QAClB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;IAClB,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,mHAAmH,CACpH,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,OAAO,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAErD,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,EAAE;;gBAC/B,MAAM,KAAK,GACT,OAAO,IAAI,KAAK;oBACd,CAAC,CAAE,KAAK,CAAC,KAAe;oBACxB,CAAC,CAAC,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAC7D,MAAM,CAAC,KAAK,CAAC,CAAC;gBACd,MAAA,IAAI,CAAC,OAAO,qDAAG,KAAK,CAAC,CAAC;YACxB,CAAC,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE;gBACzB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE;;gBAC1B,MAAA,IAAI,CAAC,OAAO,oDAAI,CAAC;YACnB,CAAC,CAAC;YAEF,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;;gBAC/C,IAAI,OAAuB,CAAC;gBAC5B,IAAI,CAAC;oBACH,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC/D,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAA,IAAI,CAAC,OAAO,qDAAG,KAAc,CAAC,CAAC;oBAC/B,OAAO;gBACT,CAAC;gBAED,MAAA,IAAI,CAAC,SAAS,qDAAG,OAAO,CAAC,CAAC;YAC5B,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK;;QACT,MAAA,IAAI,CAAC,OAAO,0CAAE,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,IAAI,CAAC,OAAuB;QAC1B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;;YACrC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;gBACnC,OAAO;YACT,CAAC;YAED,MAAA,IAAI,CAAC,OAAO,0CAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;YAC5C,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}