UNPKG

@ai-sdk/mcp

Version:
3,315 lines • 133 kB
import { EventSourceParserStream, asSchema, convertBase64ToUint8Array, convertUint8ArrayToBase64, delay, dynamicTool, fetchUntrustedUrl, fetchWithValidatedEndpoint, getRuntimeEnvironmentUserAgent, isRecord, jsonSchema, parseJSON, retryWithExponentialBackoff, safeParseJSON, safeValidateTypes, tool, validateDownloadUrl, withUserAgentSuffix } from "@ai-sdk/provider-utils";
import * as z4 from "zod/v4";
import { z } from "zod/v4";
import { AISDKError, isJSONObject } from "@ai-sdk/provider";
import pkceChallenge from "pkce-challenge";
//#region src/tool/types.ts
const LATEST_PROTOCOL_VERSION = "2026-07-28";
const LATEST_LEGACY_PROTOCOL_VERSION = "2025-11-25";
const SUPPORTED_PROTOCOL_VERSIONS = [
	LATEST_PROTOCOL_VERSION,
	LATEST_LEGACY_PROTOCOL_VERSION,
	"2025-06-18",
	"2025-03-26",
	"2024-11-05"
];
/** MCP tool metadata - keys should follow MCP _meta key format specification */
const ToolMetaSchema = z.optional(z.record(z.string(), z.unknown()));
const ClientOrServerImplementationSchema = z.looseObject({
	name: z.string(),
	version: z.string(),
	title: z.optional(z.string())
});
const BaseParamsSchema = z.looseObject({ _meta: z.optional(z.object({}).loose()) });
const ResultSchema = BaseParamsSchema.extend({ resultType: z.optional(z.string()) });
const RequestSchema = z.object({
	method: z.string(),
	params: z.optional(BaseParamsSchema)
});
/** @see https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation */
const ElicitationCapabilitySchema = z.object({ applyDefaults: z.optional(z.boolean()) }).loose();
const ServerCapabilitiesSchema = z.looseObject({
	events: z.looseObject({ listChanged: z.boolean().nullish() }).nullish(),
	experimental: z.optional(z.object({}).loose()),
	logging: z.optional(z.object({}).loose()),
	completions: z.optional(z.object({}).loose()),
	prompts: z.optional(z.looseObject({ listChanged: z.optional(z.boolean()) })),
	resources: z.optional(z.looseObject({
		subscribe: z.optional(z.boolean()),
		listChanged: z.optional(z.boolean())
	})),
	tools: z.optional(z.looseObject({ listChanged: z.optional(z.boolean()) })),
	elicitation: z.optional(ElicitationCapabilitySchema)
});
z.object({ elicitation: z.optional(ElicitationCapabilitySchema) }).loose();
const DiscoverResultSchema = ResultSchema.extend({
	supportedVersions: z.array(z.string()),
	capabilities: ServerCapabilitiesSchema,
	instructions: z.optional(z.string()),
	ttlMs: z.optional(z.number()),
	cacheScope: z.optional(z.union([z.literal("public"), z.literal("private")]))
});
const InitializeResultSchema = ResultSchema.extend({
	protocolVersion: z.string(),
	capabilities: ServerCapabilitiesSchema,
	serverInfo: ClientOrServerImplementationSchema,
	instructions: z.optional(z.string())
});
const PaginatedResultSchema = ResultSchema.extend({ nextCursor: z.optional(z.string()) });
const ToolSchema = z.object({
	name: z.string(),
	/**
	* @see https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool
	*/
	title: z.optional(z.string()),
	description: z.optional(z.string()),
	inputSchema: z.looseObject({
		type: z.optional(z.unknown()),
		properties: z.optional(z.object({}).loose())
	}),
	/**
	* @see https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
	*/
	outputSchema: z.optional(z.object({}).loose()),
	annotations: z.optional(z.object({
		title: z.optional(z.string()),
		readOnlyHint: z.optional(z.boolean()),
		destructiveHint: z.optional(z.boolean()),
		idempotentHint: z.optional(z.boolean()),
		openWorldHint: z.optional(z.boolean())
	}).loose()),
	_meta: ToolMetaSchema
}).loose();
const ListToolsResultSchema = PaginatedResultSchema.extend({ tools: z.array(ToolSchema) });
const TextContentSchema = z.object({
	type: z.literal("text"),
	text: z.string()
}).loose();
const ImageContentSchema = z.object({
	type: z.literal("image"),
	data: z.base64(),
	mimeType: z.string()
}).loose();
const ResourceSchema = z.object({
	uri: z.string(),
	name: z.string(),
	title: z.optional(z.string()),
	description: z.optional(z.string()),
	mimeType: z.optional(z.string()),
	size: z.optional(z.number())
}).loose();
const ListResourcesResultSchema = PaginatedResultSchema.extend({ resources: z.array(ResourceSchema) });
const ResourceContentsSchema = z.object({
	/**
	* The URI of this resource.
	*/
	uri: z.string(),
	/**
	* Optional display name of the resource content.
	*/
	name: z.optional(z.string()),
	/**
	* Optional human readable title.
	*/
	title: z.optional(z.string()),
	/**
	* The MIME type of this resource, if known.
	*/
	mimeType: z.optional(z.string())
}).loose();
const TextResourceContentsSchema = ResourceContentsSchema.extend({ text: z.string() });
const BlobResourceContentsSchema = ResourceContentsSchema.extend({ blob: z.base64() });
const EmbeddedResourceSchema = z.object({
	type: z.literal("resource"),
	resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema])
}).loose();
const ResourceLinkContentSchema = z.object({
	type: z.literal("resource_link"),
	uri: z.string(),
	name: z.string(),
	description: z.optional(z.string()),
	mimeType: z.optional(z.string())
}).loose();
const UnknownContentSchema = z.object({ type: z.string() }).loose().refine((content) => ![
	"text",
	"image",
	"resource",
	"resource_link"
].includes(content.type), { message: "Known content types must match their schema" });
const CallToolResultWithContentSchema = ResultSchema.extend({
	content: z.array(z.union([
		TextContentSchema,
		ImageContentSchema,
		EmbeddedResourceSchema,
		ResourceLinkContentSchema,
		UnknownContentSchema
	])),
	/**
	* @see https://modelcontextprotocol.io/specification/2026-07-28/server/tools#structured-content
	*/
	structuredContent: z.optional(z.unknown()),
	isError: z.boolean().default(false).optional()
});
const CallToolResultWithStructuredContentSchema = ResultSchema.extend({
	content: z.never().optional(),
	structuredContent: z.json(),
	isError: z.boolean().default(false).optional()
}).transform((result) => ({
	...result,
	content: [{
		type: "text",
		text: JSON.stringify(result.structuredContent)
	}]
})).pipe(CallToolResultWithContentSchema);
const CallToolResultSchema = CallToolResultWithContentSchema.or(CallToolResultWithStructuredContentSchema).or(ResultSchema.extend({ toolResult: z.unknown().nonoptional() }));
const ResourceTemplateSchema = z.object({
	uriTemplate: z.string(),
	name: z.string(),
	title: z.optional(z.string()),
	description: z.optional(z.string()),
	mimeType: z.optional(z.string())
}).loose();
const ListResourceTemplatesResultSchema = ResultSchema.extend({ resourceTemplates: z.array(ResourceTemplateSchema) });
const ReadResourceResultSchema = ResultSchema.extend({ contents: z.array(z.union([TextResourceContentsSchema, BlobResourceContentsSchema])) });
const PromptReferenceSchema = z.object({
	type: z.literal("ref/prompt"),
	name: z.string()
}).loose();
const ResourceReferenceSchema = z.object({
	type: z.literal("ref/resource"),
	uri: z.string()
}).loose();
const CompletionArgumentSchema = z.object({
	name: z.string(),
	value: z.string()
}).loose();
BaseParamsSchema.extend({
	ref: z.union([PromptReferenceSchema, ResourceReferenceSchema]),
	argument: CompletionArgumentSchema,
	context: z.optional(z.object({ arguments: z.record(z.string(), z.string()) }).loose())
});
const CompleteResultSchema = ResultSchema.extend({ completion: z.object({
	values: z.array(z.string()).max(100),
	total: z.optional(z.number().int()),
	hasMore: z.optional(z.boolean())
}).loose() });
const PromptArgumentSchema = z.object({
	name: z.string(),
	description: z.optional(z.string()),
	required: z.optional(z.boolean())
}).loose();
const PromptSchema = z.object({
	name: z.string(),
	title: z.optional(z.string()),
	description: z.optional(z.string()),
	arguments: z.optional(z.array(PromptArgumentSchema))
}).loose();
const ListPromptsResultSchema = PaginatedResultSchema.extend({ prompts: z.array(PromptSchema) });
const PromptMessageSchema = z.object({
	role: z.union([z.literal("user"), z.literal("assistant")]),
	content: z.union([
		TextContentSchema,
		ImageContentSchema,
		EmbeddedResourceSchema,
		ResourceLinkContentSchema
	])
}).loose();
const GetPromptResultSchema = ResultSchema.extend({
	description: z.optional(z.string()),
	messages: z.array(PromptMessageSchema)
});
const ElicitationRequestParamsSchema = BaseParamsSchema.extend({
	message: z.string(),
	requestedSchema: z.unknown()
});
const ElicitationRequestSchema = RequestSchema.extend({
	method: z.literal("elicitation/create"),
	params: ElicitationRequestParamsSchema
});
const ElicitResultSchema = ResultSchema.extend({
	action: z.union([
		z.literal("accept"),
		z.literal("decline"),
		z.literal("cancel")
	]),
	content: z.optional(z.record(z.string(), z.unknown()))
});
const InputRequiredResultSchema = ResultSchema.extend({
	resultType: z.literal("input_required"),
	inputRequests: z.record(z.string(), RequestSchema).optional(),
	requestState: z.string().optional()
}).refine((result) => result.inputRequests !== void 0 || result.requestState !== void 0, { message: "input_required must include inputRequests or requestState" });
const InputRequiredElicitationSchema = ElicitationRequestSchema.extend({ params: ElicitationRequestParamsSchema.extend({
	mode: z.literal("form").optional(),
	requestedSchema: z.looseObject({ type: z.literal("object") })
}) });
//#endregion
//#region src/tool/json-rpc-message.ts
const JSONRPC_VERSION = "2.0";
const JSONRPCRequestSchema = z.object({
	jsonrpc: z.literal(JSONRPC_VERSION),
	id: z.union([z.string(), z.number().int()])
}).merge(RequestSchema).strict();
const JSONRPCResponseSchema = z.object({
	jsonrpc: z.literal(JSONRPC_VERSION),
	id: z.union([z.string(), z.number().int()]),
	result: ResultSchema
}).strict();
const JSONRPCErrorSchema = z.object({
	jsonrpc: z.literal(JSONRPC_VERSION),
	id: z.optional(z.union([z.string(), z.number().int()])),
	error: z.object({
		code: z.number().int(),
		message: z.string(),
		data: z.optional(z.unknown())
	})
}).strict();
const JSONRPCNotificationSchema = z.object({ jsonrpc: z.literal(JSONRPC_VERSION) }).merge(z.object({
	method: z.string(),
	params: z.optional(BaseParamsSchema)
})).strict();
const JSONRPCMessageSchema = z.union([
	JSONRPCRequestSchema,
	JSONRPCNotificationSchema,
	JSONRPCResponseSchema,
	JSONRPCErrorSchema
]);
function validateJSONRPCMessage(message) {
	return JSONRPCMessageSchema.parse(message);
}
async function parseJSONRPCMessage(text) {
	return validateJSONRPCMessage(await parseJSON({ text }));
}
//#endregion
//#region src/error/mcp-client-error.ts
const marker$1 = `vercel.ai.error.AI_MCPClientError`;
const symbol$1 = Symbol.for(marker$1);
/**
* An error occurred with the MCP client.
*/
var MCPClientError = class extends AISDKError {
	constructor({ name = "MCPClientError", message, cause, data, code, statusCode, url, responseBody }) {
		super({
			name,
			message,
			cause
		});
		this[symbol$1] = true;
		this.data = data;
		this.code = code;
		this.statusCode = statusCode;
		this.url = url;
		this.responseBody = responseBody;
	}
	static isInstance(error) {
		return AISDKError.hasMarker(error, marker$1);
	}
};
//#endregion
//#region src/tool/mcp-event-signature.ts
function createMCPEventSecret() {
	return `whsec_${convertUint8ArrayToBase64(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)))}`;
}
function decodeMCPEventSecret(secret) {
	const encoded = secret.slice(6);
	if (!secret.startsWith("whsec_") || !/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) throw new MCPClientError({ message: "Invalid MCP event signing secret" });
	const bytes = convertBase64ToUint8Array(encoded);
	if (bytes.length < 24 || bytes.length > 64) throw new MCPClientError({ message: "MCP event signing secrets must contain 24–64 random bytes" });
	return bytes;
}
/** Standard Webhooks v1: authenticate the exact body bytes using Web Crypto. */
async function verifyMCPEventSignature({ headers, body, secret }) {
	const id = headers.get("webhook-id");
	const timestamp = headers.get("webhook-timestamp");
	const signatures = headers.get("webhook-signature");
	if (!id || !timestamp || !signatures || !/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1e3 - Number(timestamp)) > 300) return false;
	const key = await crypto.subtle.importKey("raw", new Uint8Array(decodeMCPEventSecret(secret)), {
		name: "HMAC",
		hash: "SHA-256"
	}, false, ["verify"]);
	const prefix = new TextEncoder().encode(`${id}.${timestamp}.`);
	const message = new Uint8Array(prefix.length + body.length);
	message.set(prefix);
	message.set(body, prefix.length);
	for (const signature of signatures.split(" ")) {
		const [version, value] = signature.split(",");
		if (version !== "v1" || !value) continue;
		try {
			if (await crypto.subtle.verify("HMAC", key, new Uint8Array(convertBase64ToUint8Array(value)), message)) return true;
		} catch {}
	}
	return false;
}
//#endregion
//#region src/tool/mcp-event-types.ts
const JsonSchemaSchema = z.union([z.boolean(), z.record(z.string(), z.unknown())]);
const MCPEventDefinitionSchema = z.looseObject({
	name: z.string().min(1),
	description: z.string().nullish(),
	delivery: z.array(z.enum([
		"webhook",
		"poll",
		"push"
	])).min(1),
	inputSchema: JsonSchemaSchema,
	payloadSchema: JsonSchemaSchema
});
const ListEventsResultSchema = ResultSchema.extend({
	events: z.array(MCPEventDefinitionSchema),
	nextCursor: z.string().nullish()
});
const SubscribeEventResultSchema = ResultSchema.extend({
	id: z.string().min(1),
	refreshBefore: z.iso.datetime({ offset: true }).nullable(),
	cursor: z.string().nullish().transform((cursor) => cursor ?? null),
	truncated: z.boolean()
});
const MCPEventSchema = z.looseObject({
	eventId: z.string().min(1),
	name: z.string().min(1),
	timestamp: z.iso.datetime({ offset: true }),
	data: z.record(z.string(), z.unknown()),
	cursor: z.string().nullish()
});
const MCPEventControlSchema = z.discriminatedUnion("type", [z.looseObject({
	type: z.literal("gap"),
	cursor: z.string()
}), z.looseObject({
	type: z.literal("terminated"),
	error: z.looseObject({
		code: z.number().int(),
		message: z.string(),
		data: z.unknown().nullish()
	})
})]).transform((control) => control.type === "gap" ? {
	...control,
	truncated: true
} : control);
//#endregion
//#region src/tool/mcp-events.ts
const MCP_EVENT_CALLBACK_KEY = "mcp_event_subscription";
function createMCPEvents({ request, store, validateArguments }) {
	function getStore() {
		if (!store) throw new MCPClientError({ message: "Configure experimental_events.store to manage direct event subscriptions" });
		return store;
	}
	async function getSubscription({ id, key }) {
		const storage = getStore();
		const subscription = id !== void 0 ? await storage.getById(id) : await storage.get(key);
		if (!subscription) throw new MCPClientError({ message: `Unknown MCP event subscription: ${id ?? key}` });
		return subscription;
	}
	async function subscribe(subscription, options) {
		const result = await request({
			request: {
				method: "events/subscribe",
				params: {
					name: subscription.name,
					arguments: subscription.arguments,
					delivery: subscription.delivery,
					cursor: subscription.cursor,
					...subscription.ttlMs === void 0 ? {} : { ttlMs: subscription.ttlMs },
					...subscription.maxAgeMs === void 0 ? {} : { maxAgeMs: subscription.maxAgeMs }
				}
			},
			resultSchema: SubscribeEventResultSchema,
			options
		});
		await getStore().update(subscription.key, {
			id: result.id,
			refreshBefore: result.refreshBefore,
			cursor: result.cursor,
			truncated: result.truncated,
			status: "active"
		});
		return result;
	}
	const events = {
		list({ params, options } = {}) {
			return request({
				request: {
					method: "events/list",
					params
				},
				resultSchema: ListEventsResultSchema,
				options
			});
		},
		async subscribe({ name, arguments: args = {}, delivery, cursor = null, ttlMs, maxAgeMs, options }) {
			const storage = getStore();
			const url = new URL(delivery.url);
			const localHttp = delivery.allowInsecureLocalhost === true && url.protocol === "http:" && (url.hostname === "127.0.0.1" || url.hostname === "[::1]");
			if (url.protocol !== "https:" && !localHttp || url.username || url.password || url.hash) throw new MCPClientError({ message: "MCP event callback URLs must use HTTPS without credentials or fragments" });
			if (delivery.mode !== "webhook") throw new MCPClientError({ message: "Only webhook event delivery is supported" });
			if (ttlMs != null && (!Number.isSafeInteger(ttlMs) || ttlMs <= 0) || maxAgeMs != null && (!Number.isSafeInteger(maxAgeMs) || maxAgeMs < 0)) throw new MCPClientError({ message: "Invalid MCP event subscription lifetime or replay age" });
			let pageCursor;
			const visited = /* @__PURE__ */ new Set();
			let definition;
			do {
				const page = await events.list({
					params: { cursor: pageCursor },
					options
				});
				definition = page.events.find((event) => event.name === name);
				if (definition) break;
				pageCursor = page.nextCursor ?? void 0;
				if (pageCursor != null) {
					if (visited.has(pageCursor)) throw new MCPClientError({ message: "Repeated MCP events pagination cursor" });
					visited.add(pageCursor);
				}
			} while (pageCursor != null);
			if (!definition) throw new MCPClientError({ message: `Unknown MCP event: ${name}` });
			if (!definition.delivery.includes("webhook")) throw new MCPClientError({ message: `MCP event ${name} does not support webhook delivery` });
			await validateArguments?.({
				definition,
				arguments: args
			});
			const secret = delivery.secret ?? createMCPEventSecret();
			decodeMCPEventSecret(secret);
			const key = crypto.randomUUID();
			url.searchParams.set(MCP_EVENT_CALLBACK_KEY, key);
			const subscription = {
				key,
				name,
				arguments: structuredClone(args),
				definition,
				delivery: {
					mode: "webhook",
					url: url.href,
					secret
				},
				cursor,
				truncated: false,
				ttlMs,
				maxAgeMs,
				status: "pending"
			};
			await storage.set(subscription);
			return subscribe(subscription, options);
		},
		async refresh(args) {
			const subscription = await getSubscription(args);
			await getStore().update(subscription.key, { status: "pending" });
			return subscribe(subscription, args.options);
		},
		async unsubscribe(args) {
			const subscription = await getSubscription(args);
			await request({
				request: {
					method: "events/unsubscribe",
					params: {
						name: subscription.name,
						arguments: subscription.arguments,
						delivery: {
							mode: "webhook",
							url: subscription.delivery.url
						}
					}
				},
				resultSchema: ResultSchema,
				options: args.options
			});
			await getStore().delete(subscription.key);
		}
	};
	return events;
}
//#endregion
//#region src/tool/mcp-events-adapter.ts
/** Validate JavaScript integrations before opening the MCP transport. */
function validateMCPEventOperations(operations) {
	if (operations != null && (typeof operations === "object" || typeof operations === "function") && "then" in operations && typeof operations.then === "function") {
		Promise.resolve(operations).catch(() => {});
		throw new MCPClientError({ message: "experimental_events.adapter.createAdapter() must return operations synchronously. Promise and thenable results are not supported." });
	}
	if (typeof operations !== "object" || operations === null || !("subscribe" in operations) || typeof operations.subscribe !== "function" || !("getSubscription" in operations) || typeof operations.getSubscription !== "function" || !("listSubscriptions" in operations) || typeof operations.listSubscriptions !== "function" || !("unsubscribe" in operations) || typeof operations.unsubscribe !== "function") throw new MCPClientError({ message: "experimental_events.adapter.createAdapter() must return an object implementing subscribe, getSubscription, listSubscriptions, and unsubscribe." });
}
function createManagedMCPEvents(operations, list) {
	return {
		list,
		subscribe: (input) => operations.subscribe(input),
		getSubscription: (input) => operations.getSubscription(input),
		listSubscriptions: (input) => operations.listSubscriptions(input),
		unsubscribe: (input) => operations.unsubscribe(input)
	};
}
//#endregion
//#region src/version.ts
const VERSION = "2.0.99";
//#endregion
//#region src/tool/oauth-types.ts
/**
* Reusable URL validation that disallows javascript: scheme
*/
const SafeUrlSchema = z.string().url().superRefine((val, ctx) => {
	if (!URL.canParse(val)) {
		ctx.addIssue({
			code: z.ZodIssueCode.custom,
			message: "URL must be parseable",
			fatal: true
		});
		return z.NEVER;
	}
}).refine((url) => {
	const parsedUrl = new URL(url);
	return parsedUrl.protocol !== "javascript:" && parsedUrl.protocol !== "data:" && parsedUrl.protocol !== "vbscript:";
}, { message: "URL cannot use javascript:, data:, or vbscript: scheme" });
/**
* OAuth 2.1 token response
*/
const OAuthTokensSchema = z.object({
	access_token: z.string(),
	id_token: z.string().optional(),
	token_type: z.string(),
	expires_in: z.number().optional(),
	scope: z.string().optional(),
	refresh_token: z.string().optional(),
	issuer: SafeUrlSchema.optional(),
	authorization_server: SafeUrlSchema.optional(),
	token_endpoint: SafeUrlSchema.optional()
}).strip();
const OAuthProtectedResourceMetadataSchema = z.looseObject({
	resource: z.string().url(),
	authorization_servers: z.array(SafeUrlSchema).optional(),
	jwks_uri: z.string().url().optional(),
	scopes_supported: z.array(z.string()).optional(),
	bearer_methods_supported: z.array(z.string()).optional(),
	resource_signing_alg_values_supported: z.array(z.string()).optional(),
	resource_name: z.string().optional(),
	resource_documentation: z.string().optional(),
	resource_policy_uri: z.string().url().optional(),
	resource_tos_uri: z.string().url().optional(),
	tls_client_certificate_bound_access_tokens: z.boolean().optional(),
	authorization_details_types_supported: z.array(z.string()).optional(),
	dpop_signing_alg_values_supported: z.array(z.string()).optional(),
	dpop_bound_access_tokens_required: z.boolean().optional()
});
const OAuthMetadataSchema = z.looseObject({
	issuer: z.string(),
	authorization_endpoint: SafeUrlSchema,
	token_endpoint: SafeUrlSchema,
	registration_endpoint: SafeUrlSchema.optional(),
	authorization_response_iss_parameter_supported: z.boolean().optional(),
	client_id_metadata_document_supported: z.boolean().optional(),
	scopes_supported: z.array(z.string()).optional(),
	response_types_supported: z.array(z.string()),
	grant_types_supported: z.array(z.string()).optional(),
	code_challenge_methods_supported: z.array(z.string()).optional(),
	token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
	token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional()
});
/**
* OpenID Connect Discovery metadata that may include OAuth 2.0 fields
* This schema represents the real-world scenario where OIDC providers
* return a mix of OpenID Connect and OAuth 2.0 metadata fields
*/
const OpenIdProviderDiscoveryMetadataSchema = z.looseObject({
	issuer: z.string(),
	authorization_endpoint: SafeUrlSchema,
	token_endpoint: SafeUrlSchema,
	userinfo_endpoint: SafeUrlSchema.optional(),
	jwks_uri: SafeUrlSchema,
	registration_endpoint: SafeUrlSchema.optional(),
	authorization_response_iss_parameter_supported: z.boolean().optional(),
	client_id_metadata_document_supported: z.boolean().optional(),
	scopes_supported: z.array(z.string()).optional(),
	response_types_supported: z.array(z.string()),
	grant_types_supported: z.array(z.string()).optional(),
	subject_types_supported: z.array(z.string()),
	id_token_signing_alg_values_supported: z.array(z.string()),
	claims_supported: z.array(z.string()).optional(),
	token_endpoint_auth_methods_supported: z.array(z.string()).optional()
}).merge(OAuthMetadataSchema.pick({ code_challenge_methods_supported: true }));
const OAuthClientInformationSchema = z.object({
	client_id: z.string(),
	client_secret: z.string().optional(),
	client_id_issued_at: z.number().optional(),
	client_secret_expires_at: z.number().optional(),
	issuer: SafeUrlSchema.optional(),
	authorization_server: SafeUrlSchema.optional(),
	token_endpoint: SafeUrlSchema.optional()
}).strip();
const OAuthClientMetadataSchema = z.object({
	redirect_uris: z.array(SafeUrlSchema),
	application_type: z.union([z.literal("native"), z.literal("web")]).optional(),
	token_endpoint_auth_method: z.string().optional(),
	grant_types: z.array(z.string()).optional(),
	response_types: z.array(z.string()).optional(),
	client_name: z.string().optional(),
	client_uri: SafeUrlSchema.optional(),
	logo_uri: SafeUrlSchema.optional(),
	scope: z.string().optional(),
	contacts: z.array(z.string()).optional(),
	tos_uri: SafeUrlSchema.optional(),
	policy_uri: z.string().optional(),
	jwks_uri: SafeUrlSchema.optional(),
	jwks: z.any().optional(),
	software_id: z.string().optional(),
	software_version: z.string().optional(),
	software_statement: z.string().optional()
}).strip();
const OAuthErrorResponseSchema = z.object({
	error: z.string(),
	error_description: z.string().optional(),
	error_uri: z.string().optional()
});
const OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge(OAuthClientInformationSchema);
//#endregion
//#region src/error/oauth-error.ts
const marker = `vercel.ai.error.AI_MCPClientOAuthError`;
const symbol = Symbol.for(marker);
/**
* An error occurred with the MCP client within the OAuth flow.
*/
var MCPClientOAuthError = class extends AISDKError {
	constructor({ name = "MCPClientOAuthError", message, cause }) {
		super({
			name,
			message,
			cause
		});
		this[symbol] = true;
	}
	static isInstance(error) {
		return AISDKError.hasMarker(error, marker);
	}
};
var ServerError = class extends MCPClientOAuthError {
	static {
		this.errorCode = "server_error";
	}
};
var InvalidClientError = class extends MCPClientOAuthError {
	static {
		this.errorCode = "invalid_client";
	}
};
var InvalidGrantError = class extends MCPClientOAuthError {
	static {
		this.errorCode = "invalid_grant";
	}
};
var UnauthorizedClientError = class extends MCPClientOAuthError {
	static {
		this.errorCode = "unauthorized_client";
	}
};
const mismatchMarker = `vercel.ai.error.AI_AuthorizationServerMismatchError`;
const mismatchSymbol = Symbol.for(mismatchMarker);
var AuthorizationServerMismatchError = class extends MCPClientOAuthError {
	static {
		this.errorCode = "authorization_server_mismatch";
	}
	constructor({ message, cause }) {
		super({
			name: "AuthorizationServerMismatchError",
			message,
			cause
		});
		this[mismatchSymbol] = true;
	}
	static isInstance(error) {
		return AISDKError.hasMarker(error, mismatchMarker);
	}
};
const OAUTH_ERRORS = {
	[ServerError.errorCode]: ServerError,
	[InvalidClientError.errorCode]: InvalidClientError,
	[InvalidGrantError.errorCode]: InvalidGrantError,
	[UnauthorizedClientError.errorCode]: UnauthorizedClientError
};
//#endregion
//#region src/util/oauth-util.ts
/**
* Utilities for handling OAuth resource URIs.
*/
/**
* Converts a server URL to a resource URL by removing the fragment.
* RFC 8707 section 2 states that resource URIs "MUST NOT include a fragment component".
* Keeps everything else unchanged (scheme, domain, port, path, query).
*/
function resourceUrlFromServerUrl(url) {
	const resourceURL = typeof url === "string" ? new URL(url) : new URL(url.href);
	resourceURL.hash = "";
	return resourceURL;
}
/**
* Serializes a resource URL to a string, removing the trailing slash that
* URL.href adds to pathless URLs. Per the MCP spec, implementations SHOULD
* use the form without the trailing slash for better interoperability.
*/
function resourceUrlStripSlash(resource) {
	const href = resource.href;
	if (resource.pathname === "/" && href.endsWith("/")) return href.slice(0, -1);
	return href;
}
/**
* Checks if a requested resource URL matches a configured resource URL.
* A requested resource matches if it has the same scheme, domain, port,
* and its path starts with the configured resource's path.
*
* @param requestedResource The resource URL being requested
* @param configuredResource The resource URL that has been configured
* @returns true if the requested resource matches the configured resource, false otherwise
*/
function checkResourceAllowed({ requestedResource, configuredResource }) {
	const requested = typeof requestedResource === "string" ? new URL(requestedResource) : new URL(requestedResource.href);
	const configured = typeof configuredResource === "string" ? new URL(configuredResource) : new URL(configuredResource.href);
	if (requested.origin !== configured.origin) return false;
	if (requested.pathname.length < configured.pathname.length) return false;
	const requestedPath = requested.pathname.endsWith("/") ? requested.pathname : requested.pathname + "/";
	const configuredPath = configured.pathname.endsWith("/") ? configured.pathname : configured.pathname + "/";
	return requestedPath.startsWith(configuredPath);
}
//#endregion
//#region src/tool/oauth.ts
var UnauthorizedError = class extends Error {
	constructor(message = "Unauthorized") {
		super(message);
		this.name = "UnauthorizedError";
	}
};
function normalizeUrl(url) {
	return new URL(url).href;
}
/** Allow loopback HTTP(S) for local MCP OAuth (RFC 8252 §7.3, RFC 6761 §6.3). */
function isOAuthLoopbackHost(hostname) {
	const normalized = hostname.toLowerCase().replace(/\.+$/, "");
	return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "127.0.0.1" || normalized === "[::1]" || normalized === "::1";
}
/**
* Guards metadata-derived OAuth URLs before they are requested. Loopback is
* allowed only when the caller has established that it belongs to a locally
* configured OAuth server; every other target uses the shared URL guard.
*
* Credential POSTs use `fetchWithValidatedEndpoint`, which enforces
* `redirect: 'error'` so the authorization code, PKCE verifier, and client
* secret are never replayed to a redirect target.
*/
function assertSafeOAuthEndpoint(endpointUrl, { allowLoopback = false } = {}) {
	if (allowLoopback && (endpointUrl.protocol === "http:" || endpointUrl.protocol === "https:") && isOAuthLoopbackHost(endpointUrl.hostname)) return;
	try {
		validateDownloadUrl(endpointUrl.href);
	} catch (error) {
		throw new MCPClientOAuthError({
			message: `OAuth endpoint URL is not allowed: ${endpointUrl.href}`,
			cause: error
		});
	}
}
function getTrustedLoopbackOrigin(authorizationServerUrl) {
	const url = new URL(authorizationServerUrl);
	return isOAuthLoopbackHost(url.hostname) ? url.origin : void 0;
}
function validateAuthorizationResponseIssuer({ callbackIssuer, expectedIssuer }) {
	if (callbackIssuer != null && callbackIssuer !== expectedIssuer) throw new MCPClientOAuthError({ message: `OAuth authorization response issuer ${callbackIssuer} does not match expected issuer ${expectedIssuer}` });
}
function createAuthorizationServerInformation(authorizationServerUrl, metadata) {
	return {
		issuer: metadata?.issuer ?? String(authorizationServerUrl),
		authorizationServerUrl: normalizeUrl(authorizationServerUrl),
		tokenEndpoint: normalizeUrl(metadata?.token_endpoint ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl))
	};
}
function addAuthorizationServerInformationToTokens(tokens, authorizationServerInformation) {
	return {
		...tokens,
		issuer: authorizationServerInformation.issuer,
		authorization_server: authorizationServerInformation.authorizationServerUrl,
		token_endpoint: authorizationServerInformation.tokenEndpoint
	};
}
function addAuthorizationServerInformationToClientInformation(clientInformation, authorizationServerInformation) {
	return {
		...clientInformation,
		issuer: authorizationServerInformation.issuer,
		authorization_server: authorizationServerInformation.authorizationServerUrl,
		token_endpoint: authorizationServerInformation.tokenEndpoint
	};
}
function getAuthorizationServerInformationFromCredentials(credentials) {
	if (!credentials?.authorization_server || !credentials.token_endpoint) return;
	return {
		issuer: credentials.issuer,
		authorizationServerUrl: normalizeUrl(credentials.authorization_server),
		tokenEndpoint: normalizeUrl(credentials.token_endpoint)
	};
}
async function getStoredAuthorizationServerInformation({ provider, clientInformation, tokens }) {
	const tokenAuthorizationServerInformation = getAuthorizationServerInformationFromCredentials(tokens);
	if (tokenAuthorizationServerInformation) return tokenAuthorizationServerInformation;
	const providerAuthorizationServerInformation = await provider.authorizationServerInformation?.();
	if (providerAuthorizationServerInformation) return {
		issuer: providerAuthorizationServerInformation.issuer,
		authorizationServerUrl: normalizeUrl(providerAuthorizationServerInformation.authorizationServerUrl),
		tokenEndpoint: normalizeUrl(providerAuthorizationServerInformation.tokenEndpoint)
	};
	return getAuthorizationServerInformationFromCredentials(clientInformation);
}
async function saveAuthorizationServerInformation({ provider, clientInformation, authorizationServerInformation }) {
	if (provider.saveAuthorizationServerInformation) {
		await provider.saveAuthorizationServerInformation(authorizationServerInformation);
		return true;
	}
	if (provider.saveClientInformation) {
		await provider.saveClientInformation(addAuthorizationServerInformationToClientInformation(clientInformation, authorizationServerInformation));
		return true;
	}
	return false;
}
function assertResourceMetadataUrlSameOrigin(serverUrl, resourceMetadataUrl) {
	if (!resourceMetadataUrl) return;
	const expectedOrigin = new URL(serverUrl).origin;
	if (resourceMetadataUrl.origin !== expectedOrigin) throw new MCPClientOAuthError({ message: `OAuth protected resource metadata URL ${resourceMetadataUrl.href} must have the same origin as the MCP server URL ${expectedOrigin}` });
}
function assertAuthorizationServerInformationMatches({ storedAuthorizationServerInformation, currentAuthorizationServerInformation }) {
	if (storedAuthorizationServerInformation.issuer != null && currentAuthorizationServerInformation.issuer != null && storedAuthorizationServerInformation.issuer !== currentAuthorizationServerInformation.issuer || storedAuthorizationServerInformation.authorizationServerUrl !== currentAuthorizationServerInformation.authorizationServerUrl || storedAuthorizationServerInformation.tokenEndpoint !== currentAuthorizationServerInformation.tokenEndpoint) throw new AuthorizationServerMismatchError({ message: "OAuth authorization server metadata does not match the metadata that issued the stored credentials" });
}
function extractWWWAuthenticateParams(response) {
	const header = response.headers.get("www-authenticate") ?? response.headers.get("WWW-Authenticate");
	if (!header) return {};
	const [type, scheme] = header.split(" ");
	if (type.toLowerCase() !== "bearer" || !scheme) return {};
	const resourceMetadataMatch = header.match(/(?:^|[,\s])resource_metadata="([^"]*)"/i);
	const scope = header.match(/(?:^|[,\s])scope="([^"]*)"/i)?.[1];
	let resourceMetadataUrl;
	try {
		resourceMetadataUrl = resourceMetadataMatch ? new URL(resourceMetadataMatch[1]) : void 0;
	} catch {}
	return {
		resourceMetadataUrl,
		scope
	};
}
function selectScope({ scope, resourceMetadata, clientMetadata }) {
	if (scope) return scope;
	const resourceScopes = resourceMetadata?.scopes_supported?.join(" ");
	if (resourceScopes) return resourceScopes;
	return clientMetadata.scope;
}
/**
* Constructs the well-known path for auth-related metadata discovery
*/
function buildWellKnownPath(wellKnownPrefix, pathname = "", options = {}) {
	if (pathname.endsWith("/")) pathname = pathname.slice(0, -1);
	return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
}
async function fetchWithCorsRetry(url, headers, fetchFn = fetch, trustedOrigin) {
	try {
		return await fetchUntrustedUrl({
			url: url.href,
			fetch: async (input, init) => fetchWithValidatedEndpoint({
				url: input,
				init: {
					...init,
					headers: new Headers(init?.headers).has("MCP-Protocol-Version") ? headers : void 0
				},
				fetch: fetchFn,
				trustedOrigin,
				redirect: "manual",
				requireDnsValidation: true
			}),
			headers,
			trustedOrigin,
			untrustedFirstHopHeaders: ["mcp-protocol-version"]
		});
	} catch (error) {
		if (error instanceof TypeError) {
			if (headers) return fetchWithCorsRetry(url, void 0, fetchFn, trustedOrigin);
			else return;
		}
		throw error;
	}
}
/**
* Tries to discover OAuth metadata at a specific URL
*/
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch, trustedOrigin) {
	return await fetchWithCorsRetry(url, { "MCP-Protocol-Version": protocolVersion }, fetchFn, trustedOrigin);
}
/**
* Determines if fallback to root discovery should be attempted
*/
function shouldAttemptFallback(response, pathname) {
	return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
}
/**
* Generic function for discovering OAuth metadata with fallback support
*/
async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) {
	const issuer = new URL(serverUrl);
	const protocolVersion = opts?.protocolVersion ?? "2026-07-28";
	let url;
	if (opts?.metadataUrl) url = new URL(opts.metadataUrl);
	else {
		const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname);
		url = new URL(wellKnownPath, opts?.metadataServerUrl ?? issuer);
		url.search = issuer.search;
	}
	let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn, opts?.trustedOrigin);
	if (!opts?.metadataUrl && shouldAttemptFallback(response, issuer.pathname)) response = await tryMetadataDiscovery(new URL(`/.well-known/${wellKnownType}`, issuer), protocolVersion, fetchFn, opts?.trustedOrigin);
	return response;
}
async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) {
	const response = await discoverMetadataWithFallback(serverUrl, "oauth-protected-resource", fetchFn, {
		protocolVersion: opts?.protocolVersion,
		metadataUrl: opts?.resourceMetadataUrl,
		trustedOrigin: new URL(serverUrl).origin
	});
	if (!response || response.status === 404) throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
	if (!response.ok) throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
	return OAuthProtectedResourceMetadataSchema.parse(await response.json());
}
/**
* 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. OAuth metadata at root (if URL has path)
* 3. OIDC metadata endpoints
*/
function buildDiscoveryUrls(authorizationServerUrl) {
	const url = typeof authorizationServerUrl === "string" ? new URL(authorizationServerUrl) : authorizationServerUrl;
	const hasPath = url.pathname !== "/";
	const rootIssuer = url.origin;
	const urlsToTry = [];
	if (!hasPath) {
		urlsToTry.push({
			url: new URL("/.well-known/oauth-authorization-server", url.origin),
			type: "oauth",
			expectedIssuer: rootIssuer
		});
		urlsToTry.push({
			url: new URL("/.well-known/openid-configuration", url.origin),
			type: "oidc",
			expectedIssuer: rootIssuer
		});
		return urlsToTry;
	}
	let pathname = url.pathname;
	if (pathname.endsWith("/")) pathname = pathname.slice(0, -1);
	const pathIssuer = `${url.origin}${pathname}`;
	urlsToTry.push({
		url: new URL(`/.well-known/oauth-authorization-server${pathname}`, url.origin),
		type: "oauth",
		expectedIssuer: pathIssuer
	});
	urlsToTry.push({
		url: new URL("/.well-known/oauth-authorization-server", url.origin),
		type: "oauth",
		expectedIssuer: rootIssuer
	});
	urlsToTry.push({
		url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
		type: "oidc",
		expectedIssuer: pathIssuer
	});
	const oidcDiscoveryUrl = new URL(url.origin);
	oidcDiscoveryUrl.pathname = `${pathname}/.well-known/openid-configuration`;
	urlsToTry.push({
		url: oidcDiscoveryUrl,
		type: "oidc",
		expectedIssuer: pathIssuer
	});
	return urlsToTry;
}
function assertMetadataIssuerMatches(metadata, expectedIssuer) {
	if (!(metadata.issuer === expectedIssuer || expectedIssuer === new URL(expectedIssuer).origin && metadata.issuer === `${expectedIssuer}/`)) throw new MCPClientOAuthError({ message: `OAuth authorization server metadata issuer ${metadata.issuer} does not match expected issuer ${expectedIssuer}` });
}
async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = LATEST_PROTOCOL_VERSION, trustedOrigin } = {}) {
	const headers = { "MCP-Protocol-Version": protocolVersion };
	const authorizationServerOrigin = new URL(authorizationServerUrl).origin;
	const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
	if (urlsToTry.some(({ url }) => url.origin !== authorizationServerOrigin)) throw new MCPClientOAuthError({ message: "OAuth discovery URL changed authorization-server origin" });
	for (const { url: endpointUrl, type, expectedIssuer } of urlsToTry) {
		const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn, trustedOrigin);
		if (!response)
 /**
		* CORS error occurred - don't throw as the endpoint may not allow CORS,
		* continue trying other possible endpoints
		*/
		continue;
		if (!response.ok) {
			if (response.status >= 400 && response.status < 500) continue;
			throw new Error(`HTTP ${response.status} trying to load ${type === "oauth" ? "OAuth" : "OpenID provider"} metadata from ${endpointUrl}`);
		}
		if (type === "oauth") {
			const metadata = OAuthMetadataSchema.parse(await response.json());
			assertMetadataIssuerMatches(metadata, expectedIssuer);
			return metadata;
		} else {
			const metadata = OpenIdProviderDiscoveryMetadataSchema.parse(await response.json());
			assertMetadataIssuerMatches(metadata, expectedIssuer);
			if (!metadata.code_challenge_methods_supported?.includes("S256")) throw new Error(`Incompatible OIDC provider at ${endpointUrl}: does not support S256 code challenge method required by MCP specification`);
			return metadata;
		}
	}
}
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(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(codeChallengeMethod)) throw new Error(`Incompatible auth server: does not support code challenge method ${codeChallengeMethod}`);
	} else authorizationUrl = new URL("/authorize", authorizationServerUrl);
	const challenge = await pkceChallenge();
	const codeVerifier = challenge.code_verifier;
	const codeChallenge = challenge.code_challenge;
	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);
	if (scope) authorizationUrl.searchParams.set("scope", scope);
	if (scope?.includes("offline_access")) authorizationUrl.searchParams.append("prompt", "consent");
	if (resource) authorizationUrl.searchParams.set("resource", resourceUrlStripSlash(resource));
	return {
		authorizationUrl,
		codeVerifier
	};
}
/**
* 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
*/
function selectClientAuthMethod(clientInformation, supportedMethods) {
	const hasClientSecret = clientInformation.client_secret !== void 0;
	if (supportedMethods.length === 0) return hasClientSecret ? "client_secret_post" : "none";
	if (hasClientSecret && supportedMethods.includes("client_secret_basic")) return "client_secret_basic";
	if (hasClientSecret && supportedMethods.includes("client_secret_post")) return "client_secret_post";
	if (supportedMethods.includes("none")) return "none";
	return hasClientSecret ? "client_secret_post" : "none";
}
/**
* Applies client authentication to the request based on the specified method.
*
* Implements OAuth 2.1 client authentication methods:
* - client_secret_basic: HTTP Basic authentication (RFC 6749 Section 2.3.1)
* - client_secret_post: Credentials in request body (RFC 6749 Section 2.3.1)
* - none: Public client authentication (RFC 6749 Section 2.1)
*
* @param method - The authentication method to use
* @param clientInformation - OAuth client information containing credentials
* @param headers - HTTP headers object to modify
* @param params - URL search parameters to modify
* @throws {Error} When required credentials are missing
*/
function applyClientAuthentication(method, clientInformation, headers, params) {
	const { client_id, client_secret } = clientInformation;
	switch (method) {
		case "client_secret_basic":
			applyBasicAuth(client_id, client_secret, headers);
			return;
		case "client_secret_post":
			applyPostAuth(client_id, client_secret, params);
			return;
		case "none":
			applyPublicAuth(client_id, params);
			return;
		default: throw new Error(`Unsupported client authentication method: ${method}`);
	}
}
function applyBasicAuth(clientId, clientSecret, headers) {
	if (!clientSecret) throw new Error("client_secret_basic authentication requires a client_secret");
	const credentials = btoa(`${clientId}:${clientSecret}`);
	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);
	if (clientSecret) params.set("client_secret", clientSecret);
}
/**
* Applies public client authentication (RFC 6749 Section 2.1)
*/
function applyPublicAuth(clientId, params) {
	params.set("client_id", clientId);
}
/**
* Parses an OAuth error response from a string or Response object.
*
* If the input is a standard OAuth2.0 error response, it will be parsed according to the spec
* and an instance of the appropriate OAuthError subclass will be returned.
* If parsing fails, it falls back to a generic ServerError that includes
* the response status (if available) and original content.
*
* @param input - A Response object or string containing the error response
* @returns A Promise that resolves to an OAuthError instance
*/
async function parseErrorResponse(input) {
	const statusCode = input instanceof Response ? input.status : void 0;
	const body = input instanceof Response ? await input.text() : input;
	try {
		const { error, error_description, error_uri } = OAuthErrorResponseSchema.parse(await parseJSON({ text: body }));
		return new (OAUTH_ERRORS[error] || ServerError)({
			message: error_description || "",
			cause: error_uri
		});
	} catch (error) {
		return new ServerError({ message: `${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error}. Raw body: ${body}` });
	}
}
/**
* Exchanges an authorization code for an access token with the given server.
*
* Supports multiple client authentication methods as specified in OAuth 2.1:
* - Automatically selects the best authentication method based on server support
* - Falls back to appropriate defaults when server metadata is unavailable
*
* @param authorizationServerUrl - The authorization server's base URL
* @param options - Configuration object containing client info, auth code, etc.
* @returns Promise resolving to OAuth tokens
* @throws {Error} When token exchange fails or authentication is invalid
*/
async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn, trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl) }) {
	const grantType = "authorization_code";
	const tokenUrl = metadata?.token_endpoint ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl);
	assertSafeOAuthEndpoint(tokenUrl, { allowLoopback: tokenUrl.origin === trustedOrigin });
	if (metadata?.grant_types_supported && !metadata.grant_types_supported.includes(grantType)) throw new Error(`Incompatible auth server: does not support grant type ${grantType}`);
	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) await addClientAuthentication(headers, params, authorizationServerUrl, metadata);
	else applyClientAuthentication(selectClientAuthMethod(clientInformation, metadata?.token_endpoint_auth_methods_supported ?? []), clientInformation, headers, params);
	if (resource) params.set("resource", resourceUrlStripSlash(resource));
	const response = await fetchWithValidatedEndpoint({
		url: tokenUrl,
		init: {
			method: "POST",
			headers,
			body: params
		},
		fetch: fetchFn,
		trustedOrigin,
		requireDnsValidation: true
	});
	if (!response.ok) throw await parseErrorResponse(response);
	return OAuthTokensSchema.parse(await response.json());
}
/**
* Exchange a refresh token for an updated access token.
*
* Supports multiple client authentication methods as specified in OAuth 2.1:
* - Automatically selects the best authentication method based on server support
* - Preserves the original refresh token if a new one is not returned
*
* @param authorizationServerUrl - The authorization server's base URL
* @param options - Configuration object containing client info, refresh token, etc.
* @returns Promise resolving to OAuth tokens (preserves original refresh_token if not replaced)
* @throws {Error} When token refresh fails or authentication is invalid
*/
async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn, trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl) }) {
	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}`);
	} else tokenUrl = new URL("/token", authorizationServerUrl);
	assertSafeOAuthEndpoint(tokenUrl, { allowLoopback: tokenUrl.origin === trustedOrigin });
	const headers = new Headers({
		"Content-Type": "application/x-www-form-urlencoded",
		Accept: "application/json"
	});
	const params = new URLSearchParams({
		grant_type: grantType,
		refresh_token: refreshToken
	});
	if (addClientAuthentication) await addClientAuthentication(headers, params, authorizationServerUrl, metadata);
	else applyClientAuthentication(selectClientAuthMethod(clientInformation, metadata?.token_endpoint_auth_methods_supported ?? []), clientInformation, headers, params);
	if (resource) params.set("resource", resourceUrlStripSlash(resource));
	const response = await fetchWithValidatedEndpoint({
		url: tokenUrl,
		init: {
			method: "POST",
			headers,
			body: params
		},
		fetch: fetchFn,
		trustedOrigin,
		requireDnsValidation: true
	});
	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.
*/
async function registerClient(authorizationServerUrl, { metadata, clientMetadata, fetchFn, trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl) }) {
	let registrationUrl;
	if (metadata) {
		if (!metadata.registration_endpoint) throw new Error("Incompatible auth server: does not support dynamic client registration");
		registrationUrl = new URL(metadata.registration_endpoint);
	} else registrationUrl = new URL("/register", authorizationServerUrl);
	assertSafeOAuthEndpoint(registrationUrl, { allowLoopback: registrationUrl.origin === trustedOrigin });
	const applicationType = clientMetadata.application_type ?? inferOAuthApplicationType(clientMetadata.redirect_uris);
	const response = await fetchWithValidatedEndpoint({
		url: registrationUrl,
		init: {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({
				...clientMetadata,
				application_type: applicationType
			})
		},
		fetch: fetchFn,
		trustedOrigin,
		requireDnsValidation: true
	});
	if (!response.ok) throw await parseErrorResponse(response);
	return OAuthClientInformationFullSchema.parse(await response.json());
}
function inferOAuthApplicationType(redirectUris) {
	const isNativeRedirectUri = (redirectUri) => {
		const url = new URL(redirectUri);
		return (url.protocol === "http:" || url.protocol === "https:") && isOAuthLoopbackHost(url.hostname) || url.protocol !== "http:" && url.protocol !== "https:";
	};
	return redirectUris.every(isNativeRedirectUri) ? "native" : "web";
}
async function auth(provider, options) {
	const refreshAttempt = {};
	try {
		return await authInternal(provider, options, refreshAttempt);
	} catch (error) {
		if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
			if (options.authorizationCode !== void 0 || !await provider.isClientInformationDynamicallyRegistered?.()) throw error;
			await provider.invalidateCredentials?.("all");
			return await authInternal(provider, options);
		} else if (error instanceof InvalidGrantError && refreshAttempt.tokens) {
			const retryAuthorizationServerInformation = getAuthorizationServerInformationFromCredentials(refreshAttempt.tokens);
			await provider.invalidateCredentials?.("tokens", { tokens: refreshAttempt.tokens });
			return await authInternal(provider, options, void 0, retryAuthorizationServerInformation);
		}
		throw error;
	}
}
async function selectResourceURL(serverUrl, provider, resourceMetadata) {
	const defaultResource = resourceUrlFromServerUrl(serverUrl);
	if (provider.validateResourceURL) return await provider.validateResourceURL(defaultResource, resourceMetadata?.resource) ?? defaultResource;
	if (!resourceMetadata) return defaultResource;
	if (!checkResourceAllowed({
		requestedResource: defaultResource,
		configuredResource: resourceMetadata.resource
	})) throw new Error(`Protected resource ${resourceMetadata.resource} does not match expected ${defaultResource} (or origin)`);
	return new URL(resourceMetadata.resource);
}
async function authInternal(provider, { serverUrl, authorizationCode, callbackState, callbackIssuer, scope, resourceMetadataUrl, fetchFn }, refreshAttempt, retryAuthorizationServerInformation) {
	let resourceMetadata;
	let authorizationServerUrl;
	let clientInformation;
	let callbackAuthorizationServerInformation;
	/** Reject Protected Resource Metadata URLs outside the configured MCP server origin. */
	assertResourceMetadataUrlSameOrigin(serverUrl, resourceMetadataUrl);
	/** Discover PRM and select its advertised authorization server. */
	try {
		resourceMetadata = await discoverOAuthProtectedResourceMetadata(serverUrl, { resourceMetadataUrl }, fetchFn);
		if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) authorizationServerUrl = resourceMetadata.authorization_servers[0];
	} catch {}
	/**
	* A callback may not have the original PRM URL from the authentication
	* challenge. Use the authorization server pinned before redirecting when
	* rediscovery does not select one.
	*/
	if (authorizationCode !== void 0) {
		clientInformation = await Promise.resolve(provider.clientInformation());
		if (clientInformation) callbackAuthorizationServerInformation = await getStoredAuthorizationServerInformation({
			provider,
			clientInformation
		});
	}
	/** Reuse the callback pin, then fall back to the legacy MCP-as-AS behavior. */
	if (!authorizationServerUrl) authorizationServerUrl = callbackAuthorizationServerInformation?.authorizationServerUrl ?? serverUrl;
	const parsedServerUrl = new URL(serverUrl);
	const parsedAuthorizationServerUrl = new URL(authorizationServerUrl);
	const serverOrigin = parsedServerUrl.origin;
	const authorizationServerOrigin = parsedAuthorizationServerUrl.origin;
	const trustedAuthorizationServerOrigin = authorizationServerOrigin === serverOrigin || isOAuthLoopbackHost(parsedServerUrl.hostname) && isOAuthLoopbackHost(parsedAuthorizationServerUrl.hostname) ? authorizationServerOrigin : void 0;
	if (!trustedAuthorizationServerOrigin) assertSafeOAuthEndpoint(new URL(authorizationServerUrl));
	/** Validate and select the resource value sent to the AS */
	const resource = await selectResourceURL(serverUrl, provider, resourceMetadata);
	/** Let applications constrain discovered AS URLs before metadata fetches. */
	await provider.validateAuthorizationServerURL?.(serverUrl, authorizationServerUrl);
	/** Discover AS metadata and derive the credential pin for this flow */
	const metadata = await discoverAuthorizationServerMetadata(authorizationServerUrl, {
		fetchFn,
		trustedOrigin: trustedAuthorizationServerOrigin
	});
	const currentAuthorizationServerInformation = createAuthorizationServerInformation(authorizationServerUrl, metadata);
	if (retryAuthorizationServerInformation) assertAuthorizationServerInformationMatches({
		storedAuthorizationServerInformation: retryAuthorizationServerInformation,
		currentAuthorizationServerInformation
	});
	const clientMetadata = provider.clientMetadata;
	const selectedScope = selectScope({
		scope,
		resourceMetadata,
		clientMetadata
	});
	/** Load or register client credentials with the AS pin attached. */
	if (authorizationCode === void 0) clientInformation = await Promise.resolve(provider.clientInformation());
	const tokens = authorizationCode === void 0 ? await provider.tokens() : void 0;
	if (clientInformation) {
		if (clientInformation.issuer != null && clientInformation.issuer !== currentAuthorizationServerInformation.issuer) throw new AuthorizationServerMismatchError({ message: "OAuth authorization server does not match the issuer of the client credentials" });
		const clientAuthorizationServerInformation = getAuthorizationServerInformationFromCredentials(clientInformation);
		if (clientAuthorizationServerInformation) assertAuthorizationServerInformationMatches({
			storedAuthorizationServerInformation: clientAuthorizationServerInformation,
			currentAuthorizationServerInformation
		});
		const storedAuthorizationServerInformation = callbackAuthorizationServerInformation ?? await getStoredAuthorizationServerInformation({
			provider,
			clientInformation,
			tokens
		}) ?? retryAuthorizationServerInformation;
		if (storedAuthorizationServerInformation) assertAuthorizationServerInformationMatches({
			storedAuthorizationServerInformation,
			currentAuthorizationServerInformation
		});
		if ((clientInformation.client_secret != null || provider.addClientAuthentication) && clientInformation.issuer == null && !storedAuthorizationServerInformation) throw new AuthorizationServerMismatchError({ message: "OAuth client credentials must be bound to an authorization server: configure their issuer or store authorization server information" });
	}
	const refreshAuthorizationServerInformation = tokens?.refresh_token ? await getStoredAuthorizationServerInformation({
		provider,
		clientInformation,
		tokens
	}) : void 0;
	if (tokens?.refresh_token) {
		if (refreshAuthorizationServerInformation) assertAuthorizationServerInformationMatches({
			storedAuthorizationServerInformation: refreshAuthorizationServerInformation,
			currentAuthorizationServerInformation
		});
		else {
			await provider.invalidateCredentials?.("tokens", { tokens: { ...tokens } });
			if ((await provider.tokens())?.refresh_token) throw new MCPClientOAuthError({ message: "Stored OAuth tokens without an authorization server binding must be cleared before starting authorization" });
		}
	}
	if (!clientInformation) {
		if (authorizationCode !== void 0) throw new Error("Existing OAuth client information is required when exchanging an authorization code");
		if (!provider.saveClientInformation) throw new Error("OAuth client information must be saveable for dynamic registration");
		clientInformation = addAuthorizationServerInformationToClientInformation(await registerClient(authorizationServerUrl, {
			metadata,
			clientMetadata: {
				...clientMetadata,
				scope: selectedScope
			},
			fetchFn,
			trustedOrigin: trustedAuthorizationServerOrigin
		}), currentAuthorizationServerInformation);
		await provider.saveClientInformation(clientInformation);
	}
	/** On callback, validate state and AS pin before code exchange */
	if (authorizationCode !== void 0) {
		if (provider.storedState) {
			const expectedState = await provider.storedState();
			if (expectedState !== void 0 && expectedState !== callbackState) throw new Error("OAuth state parameter mismatch - possible CSRF attack");
		}
		const storedAuthorizationServerInformation = callbackAuthorizationServerInformation ?? await getStoredAuthorizationServerInformation({
			provider,
			clientInformation
		});
		if (!storedAuthorizationServerInformation) throw new MCPClientOAuthError({ message: "Stored OAuth authorization server metadata is required when exchanging an authorization code" });
		validateAuthorizationResponseIssuer({
			callbackIssuer,
			expectedIssuer: storedAuthorizationServerInformation.issuer ?? metadata?.issuer ?? String(authorizationServerUrl)
		});
		assertAuthorizationServerInformationMatches({
			storedAuthorizationServerInformation,
			currentAuthorizationServerInformation
		});
		const codeVerifier = await provider.codeVerifier();
		const tokens = await exchangeAuthorization(authorizationServerUrl, {
			metadata,
			clientInformation,
			authorizationCode,
			codeVerifier,
			redirectUri: provider.redirectUrl,
			resource,
			addClientAuthentication: provider.addClientAuthentication,
			fetchFn,
			trustedOrigin: trustedAuthorizationServerOrigin
		});
		await provider.saveTokens(addAuthorizationServerInformationToTokens(tokens, currentAuthorizationServerInformation));
		return "AUTHORIZED";
	}
	/** Refresh only when stored credentials match the current AS pin */
	if (tokens?.refresh_token && refreshAuthorizationServerInformation) try {
		if (refreshAttempt) refreshAttempt.tokens = { ...tokens };
		const newTokens = await refreshAuthorization(authorizationServerUrl, {
			metadata,
			clientInformation,
			refreshToken: tokens.refresh_token,
			resource,
			addClientAuthentication: provider.addClientAuthentication,
			fetchFn,
			trustedOrigin: trustedAuthorizationServerOrigin
		});
		await provider.saveTokens(addAuthorizationServerInformationToTokens(newTokens, currentAuthorizationServerInformation));
		return "AUTHORIZED";
	} catch (error) {
		if (!(error instanceof MCPClientOAuthError) || error instanceof ServerError) {} else throw error;
	}
	/** Start authorization and persist the AS pin before redirecting */
	const state = provider.state ? await provider.state() : void 0;
	if (state && provider.saveState) await provider.saveState(state);
	const { authorizationUrl, codeVerifier } = await startAuthorization(authorizationServerUrl, {
		metadata,
		clientInformation,
		state,
		redirectUrl: provider.redirectUrl,
		scope: selectedScope,
		resource
	});
	if (!await saveAuthorizationServerInformation({
		provider,
		clientInformation,
		authorizationServerInformation: currentAuthorizationServerInformation
	})) throw new MCPClientOAuthError({ message: "OAuth authorization server metadata must be saveable before starting authorization" });
	await provider.saveCodeVerifier(codeVerifier);
	await provider.redirectToAuthorization(authorizationUrl);
	return "REDIRECT";
}
//#endregion
//#region src/tool/mcp-sse-transport.ts
function isMessageEvent$1(event) {
	return event === void 0 || event === "message";
}
var SseMCPTransport = class {
	constructor({ url, headers, authProvider, redirect = "error", fetch: fetchFn }) {
		this.connected = false;
		this.url = new URL(url);
		this.headers = headers;
		this.authProvider = authProvider;
		this.redirectMode = redirect;
		this.fetchFn = fetchFn ?? globalThis.fetch.bind(globalThis);
		this.customFetch = fetchFn;
	}
	setProtocolVersion(version) {
		this.protocolVersion = version;
	}
	async commonHeaders(base) {
		const headers = {
			...this.headers,
			...base,
			"mcp-protocol-version": this.protocolVersion ?? "2025-11-25"
		};
		if (this.authProvider) {
			const tokens = await this.authProvider.tokens();
			if (tokens?.access_token) headers["Authorization"] = `Bearer ${tokens.access_token}`;
		}
		return withUserAgentSuffix(headers, `ai-sdk/${VERSION}`, getRuntimeEnvironmentUserAgent());
	}
	/**
	* Runs a single OAuth recovery flow for concurrent 401 responses.
	*/
	authorizeOnce(resourceMetadataUrl, scope) {
		if (!this.authProvider) return Promise.resolve("REDIRECT");
		if (!this.authPromise) this.authPromise = auth(this.authProvider, {
			serverUrl: this.url,
			resourceMetadataUrl,
			scope,
			fetchFn: this.customFetch
		}).finally(() => {
			this.authPromise = void 0;
		});
		return this.authPromise;
	}
	async accessTokenChanged(requestAuthorization) {
		const accessToken = (await this.authProvider?.tokens())?.access_token;
		return accessToken != null && requestAuthorization !== `Bearer ${accessToken}`;
	}
	async start() {
		return new Promise((resolve, reject) => {
			if (this.connected) return resolve();
			this.abortController = new AbortController();
			const establishConnection = async (triedAuth = false) => {
				try {
					const headers = await this.commonHeaders({ Accept: "text/event-stream" });
					const response = await this.fetchFn(this.url.href, {
						headers,
						signal: this.abortController?.signal,
						redirect: this.redirectMode
					});
					if (response.status === 401 && this.authProvider && !triedAuth) {
						const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
						this.resourceMetadataUrl = resourceMetadataUrl;
						try {
							if (await this.authorizeOnce(this.resourceMetadataUrl, scope) !== "AUTHORIZED") {
								const error = new UnauthorizedError();
								this.onerror?.(error);
								return reject(error);
							}
						} catch (error) {
							this.onerror?.(error);
							return reject(error);
						}
						return establishConnection(true);
					}
					if (!response.ok || !response.body) {
						const text = await response.text().catch(() => null);
						let errorMessage = `MCP SSE Transport Error: ${response.status} ${response.statusText}${text ? `: ${text}` : ""}`;
						if (response.status === 405) errorMessage += ". This server does not support SSE transport. Try using `http` transport instead";
						const error = new MCPClientError({
							message: errorMessage,
							statusCode: response.status,
							url: this.url.href,
							responseBody: text ?? void 0
						});
						this.onerror?.(error);
						return reject(error);
					}
					const reader = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream()).getReader();
					const processEvents = async () => {
						try {
							while (true) {
								const { done, value } = await reader.read();
								if (done) {
									if (this.abortController?.signal.aborted) return;
									if (this.connected) {
										this.connected = false;
										throw new MCPClientError({ message: "MCP SSE Transport Error: Connection closed unexpectedly" });
									}
									throw new MCPClientError({ message: "MCP SSE Transport Error: Connection closed before endpoint event was received" });
								}
								const { event, data } = value;
								if (event === "endpoint") {
									if (this.endpoint) continue;
									const endpoint = new URL(data, this.url);
									if (endpoint.origin !== this.url.origin) {
										this.connected = false;
										this.endpoint = void 0;
										this.sseConnection?.close();
										this.abortController?.abort();
										throw new MCPClientError({ message: `MCP SSE Transport Error: Endpoint origin does not match connection origin: ${endpoint.origin}` });
									}
									this.endpoint = endpoint;
									this.connected = true;
									resolve();
								} else if (isMessageEvent$1(event)) try {
									const message = await parseJSONRPCMessage(data);
									this.onmessage?.(message);
								} catch (error) {
									const e = new MCPClientError({
										message: "MCP SSE Transport Error: Failed to parse message",
										cause: error
									});
									this.onerror?.(e);
								}
							}
						} catch (error) {
							if (error instanceof Error && error.name === "AbortError") return;
							this.onerror?.(error);
							reject(error);
						}
					};
					this.sseConnection = { close: () => reader.cancel() };
					processEvents();
				} catch (error) {
					if (error instanceof Error && error.name === "AbortError") return;
					this.onerror?.(error);
					reject(error);
				}
			};
			establishConnection();
		});
	}
	async close() {
		this.connected = false;
		this.endpoint = void 0;
		this.sseConnection?.close();
		this.abortController?.abort();
		this.onclose?.();
	}
	async send(message, options) {
		options?.signal?.throwIfAborted();
		if (!this.endpoint || !this.connected) throw new MCPClientError({ message: "MCP SSE Transport Error: Not connected" });
		const endpoint = this.endpoint;
		const transportSignal = this.abortController?.signal;
		const requestSignal = options?.signal == null ? transportSignal : transportSignal == null ? options.signal : AbortSignal.any([transportSignal, options.signal]);
		const attempt = async (triedAuth = false) => {
			try {
				const headers = await this.commonHeaders({ "Content-Type": "application/json" });
				const init = {
					method: "POST",
					headers,
					body: JSON.stringify(message),
					signal: requestSignal,
					redirect: this.redirectMode
				};
				const response = await this.fetchFn(endpoint.href, init);
				if (response.status === 401 && this.authProvider && !triedAuth) {
					if (await this.accessTokenChanged(new Headers(headers).get("authorization") ?? void 0)) return attempt(true);
					const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
					this.resourceMetadataUrl = resourceMetadataUrl;
					if (await this.authorizeOnce(this.resourceMetadataUrl, scope) !== "AUTHORIZED") throw new UnauthorizedError();
					return attempt(true);
				}
				if (!response.ok) {
					const text = await response.text().catch(() => null);
					throw new MCPClientError({
						message: `MCP SSE Transport Error: POSTing to endpoint (HTTP ${response.status}): ${text}`,
						statusCode: response.status,
						url: endpoint.href,
						responseBody: text ?? void 0
					});
				}
			} catch (error) {
				if (options?.signal?.aborted) throw error;
				this.onerror?.(error);
				throw error;
			}
		};
		await attempt();
	}
};
//#endregion
//#region src/tool/mcp-http-headers.ts
const HTTP_TOKEN_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
const BASE64_SENTINEL_PATTERN = /^=\?base64\?.*\?=$/;
function encodeMCPHeaderValue(value) {
	if ([...value].every((character) => {
		const code = character.charCodeAt(0);
		return code === 9 || code >= 32 && code <= 126;
	}) && value.trim() === value && !BASE64_SENTINEL_PATTERN.test(value)) return value;
	return `=?base64?${convertUint8ArrayToBase64(new TextEncoder().encode(value))}?=`;
}
function getMCPToolHeaderBindings(inputSchema) {
	if (!isRecord(inputSchema)) return {
		success: false,
		error: "inputSchema must be a JSON Schema object"
	};
	const bindings = [];
	const headerNames = /* @__PURE__ */ new Set();
	let error;
	const visit = (value, path, staticallyReachable) => {
		if (error != null || !isRecord(value)) return;
		if ("x-mcp-header" in value) {
			if (!staticallyReachable || path.length === 0) {
				error = "x-mcp-header is not on a statically reachable property";
				return;
			}
			const headerName = value["x-mcp-header"];
			if (typeof headerName !== "string" || !HTTP_TOKEN_PATTERN.test(headerName)) {
				error = "x-mcp-header must be a non-empty HTTP token";
				return;
			}
			const normalizedHeaderName = headerName.toLowerCase();
			if (headerNames.has(normalizedHeaderName)) {
				error = `x-mcp-header value "${headerName}" is not unique`;
				return;
			}
			const valueType = value.type;
			if (valueType !== "boolean" && valueType !== "integer" && valueType !== "string") {
				error = "x-mcp-header can only annotate boolean, integer, or string properties";
				return;
			}
			headerNames.add(normalizedHeaderName);
			bindings.push({
				headerName,
				path,
				valueType
			});
		}
		for (const [key, child] of Object.entries(value)) {
			if (key === "x-mcp-header") continue;
			if (key === "properties" && isRecord(child)) for (const [propertyName, propertySchema] of Object.entries(child)) visit(propertySchema, [...path, propertyName], staticallyReachable);
			else visit(child, path, false);
		}
	};
	visit(inputSchema, [], true);
	return error == null ? {
		success: true,
		bindings
	} : {
		success: false,
		error
	};
}
function getValueAtPath(value, path) {
	let current = value;
	for (const segment of path) {
		if (!isRecord(current)) return;
		current = current[segment];
	}
	return current;
}
function createMCPToolHeaders({ bindings, args }) {
	const headers = {};
	for (const binding of bindings) {
		const value = getValueAtPath(args, binding.path);
		if (value == null) continue;
		if (binding.valueType === "string" && typeof value !== "string" || binding.valueType === "boolean" && typeof value !== "boolean" || binding.valueType === "integer" && !Number.isSafeInteger(value)) throw new TypeError(`Tool argument "${binding.path.join(".")}" does not match its x-mcp-header type`);
		headers[`Mcp-Param-${binding.headerName}`] = encodeMCPHeaderValue(String(value));
	}
	return headers;
}
//#endregion
//#region src/tool/mcp-http-transport.ts
function isMessageEvent(event) {
	return event === void 0 || event === "message";
}
/**
* HTTP MCP transport implementing the Streamable HTTP style.
*
* Client transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
* It will connect to a server using HTTP POST for sending messages and HTTP GET with Server-Sent Events
* for receiving messages.
*/
var HttpMCPTransport = class {
	constructor({ url, headers, authProvider, redirect = "error", initialSessionId, initialProtocolVersion, onSessionIdChange, onSessionExpired, terminateSessionOnClose = true, fetch: fetchFn }) {
		this.supportsProtocolVersionDiscovery = true;
		this.waitsForProtocolVersionDiscovery = true;
		this.supportsMcpToolParameterHeaders = true;
		this.inboundReconnectAttempts = 0;
		this.reconnectionOptions = {
			initialReconnectionDelay: 1e3,
			maxReconnectionDelay: 3e4,
			reconnectionDelayGrowFactor: 1.5,
			maxRetries: 2
		};
		this.url = new URL(url);
		this.headers = headers;
		this.authProvider = authProvider;
		this.redirectMode = redirect;
		this.sessionId = initialSessionId;
		this.protocolVersion = initialProtocolVersion ?? "2025-11-25";
		this.onSessionIdChange = onSessionIdChange;
		this.onSessionExpired = onSessionExpired;
		this.terminateSessionOnClose = terminateSessionOnClose;
		this.fetchFn = fetchFn ?? globalThis.fetch.bind(globalThis);
		this.customFetch = fetchFn;
	}
	setProtocolVersion(version) {
		this.protocolVersion = version;
		if (!this.abortController) return;
		if (this.isModernProtocol()) {
			this.inboundSseConnection?.close();
			this.inboundSseConnection = void 0;
			return;
		}
		if (!this.inboundSseConnection) this.startInboundSse();
	}
	isModernProtocol() {
		return (this.protocolVersion ?? "2026-07-28") === LATEST_PROTOCOL_VERSION;
	}
	async commonHeaders({ base, includeSessionId = true }) {
		const headers = {
			...this.headers,
			...base,
			"mcp-protocol-version": this.protocolVersion ?? "2025-11-25"
		};
		if (!this.isModernProtocol() && includeSessionId && this.sessionId) headers["mcp-session-id"] = this.sessionId;
		if (this.authProvider) {
			const tokens = await this.authProvider.tokens();
			if (tokens?.access_token) headers["Authorization"] = `Bearer ${tokens.access_token}`;
		}
		return withUserAgentSuffix(headers, `ai-sdk/${VERSION}`, getRuntimeEnvironmentUserAgent());
	}
	setSessionId(sessionId) {
		if (this.sessionId === sessionId) return;
		this.sessionId = sessionId;
		this.onSessionIdChange?.(sessionId);
	}
	applySessionIdFromResponse(response) {
		if (this.isModernProtocol()) return;
		const sessionId = response.headers.get("mcp-session-id");
		if (sessionId) this.setSessionId(sessionId);
	}
	expireSessionId(sessionId) {
		if (this.sessionId === sessionId) this.setSessionId(void 0);
		this.onSessionExpired?.(sessionId);
	}
	/**
	* Runs a single OAuth recovery flow for concurrent 401 responses.
	*/
	authorizeOnce(resourceMetadataUrl, scope) {
		if (!this.authProvider) return Promise.resolve("REDIRECT");
		if (!this.authPromise) this.authPromise = auth(this.authProvider, {
			serverUrl: this.url,
			resourceMetadataUrl,
			scope,
			fetchFn: this.customFetch
		}).finally(() => {
			this.authPromise = void 0;
		});
		return this.authPromise;
	}
	async start() {
		if (this.abortController) throw new MCPClientError({ message: "MCP HTTP Transport Error: Transport already started. Note: client.connect() calls start() automatically." });
		this.abortController = new AbortController();
		if (this.protocolVersion != null && this.protocolVersion !== "2026-07-28") this.startInboundSse();
	}
	async close(options) {
		this.inboundSseConnection?.close();
		this.abortController?.abort();
		try {
			if (!this.isModernProtocol() && this.sessionId && this.terminateSessionOnClose && this.abortController) {
				options?.signal?.throwIfAborted();
				const headers = await this.commonHeaders({ base: {} });
				options?.signal?.throwIfAborted();
				await this.fetchFn(this.url.href, {
					method: "DELETE",
					headers,
					signal: options?.signal,
					redirect: this.redirectMode
				}).catch(() => void 0);
			}
		} catch {}
		this.onclose?.();
	}
	async send(message, options) {
		options?.signal?.throwIfAborted();
		const transportSignal = this.abortController?.signal;
		const requestSignal = options?.signal == null ? transportSignal : transportSignal == null ? options.signal : AbortSignal.any([transportSignal, options.signal]);
		const attempt = async (triedAuth = false) => {
			try {
				const isInitializeRequest = "method" in message && message.method === "initialize";
				const sessionIdForRequest = isInitializeRequest ? void 0 : this.sessionId;
				const init = {
					method: "POST",
					headers: await this.commonHeaders({
						base: {
							"Content-Type": "application/json",
							Accept: "application/json, text/event-stream",
							...this.isModernProtocol() ? options?.headers : {},
							...this.isModernProtocol() && "method" in message && "id" in message ? this.getStandardRequestHeaders(message) : {}
						},
						includeSessionId: !isInitializeRequest
					}),
					body: JSON.stringify(message),
					signal: requestSignal,
					redirect: this.redirectMode
				};
				const response = await this.fetchFn(this.url.href, init);
				this.applySessionIdFromResponse(response);
				if (response.status === 401 && this.authProvider && !triedAuth) {
					const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
					this.resourceMetadataUrl = resourceMetadataUrl;
					try {
						if (await this.authorizeOnce(this.resourceMetadataUrl, scope) !== "AUTHORIZED") throw new UnauthorizedError();
					} catch (error) {
						this.onerror?.(error);
						throw error;
					}
					return attempt(true);
				}
				if (response.status === 202) {
					if (!this.isModernProtocol() && !this.inboundSseConnection) this.startInboundSse();
					return;
				}
				if (!response.ok) {
					const text = await response.text().catch(() => null);
					if ("id" in message && text != null) {
						const jsonRpcMessage = await parseJSONRPCMessage(text).catch(() => void 0);
						if (jsonRpcMessage != null && "error" in jsonRpcMessage) {
							this.onmessage?.(jsonRpcMessage.id == null ? {
								...jsonRpcMessage,
								id: message.id
							} : jsonRpcMessage);
							return;
						}
					}
					let errorMessage = `MCP HTTP Transport Error: POSTing to endpoint (HTTP ${response.status}): ${text}`;
					if (response.status === 404) {
						if (!this.isModernProtocol() && sessionIdForRequest) {
							this.expireSessionId(sessionIdForRequest);
							errorMessage += ". The MCP session expired. Create a new client without `initialSessionId` to start a fresh session";
						} else if (!this.isModernProtocol()) errorMessage += ". This server does not support HTTP transport. Try using `sse` transport instead";
					}
					const error = new MCPClientError({
						message: errorMessage,
						statusCode: response.status,
						url: this.url.href,
						responseBody: text ?? void 0
					});
					this.onerror?.(error);
					throw error;
				}
				if (!("id" in message)) return;
				const contentType = response.headers.get("content-type") || "";
				if (contentType.includes("application/json")) {
					const data = await response.json();
					const messages = Array.isArray(data) ? data.map((message) => validateJSONRPCMessage(message)) : [validateJSONRPCMessage(data)];
					for (const jsonRpcMessage of messages) this.onmessage?.(jsonRpcMessage);
					return;
				}
				if (contentType.includes("text/event-stream")) {
					if (!response.body) {
						const error = new MCPClientError({
							message: "MCP HTTP Transport Error: text/event-stream response without body",
							statusCode: response.status,
							url: this.url.href
						});
						this.onerror?.(error);
						throw error;
					}
					const reader = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream()).getReader();
					const processEvents = async () => {
						try {
							while (true) {
								const { done, value } = await reader.read();
								if (done) return;
								const { event, data } = value;
								if (isMessageEvent(event)) try {
									const jsonRpcMessage = await parseJSONRPCMessage(data);
									this.onmessage?.(jsonRpcMessage);
								} catch (error) {
									const e = new MCPClientError({
										message: "MCP HTTP Transport Error: Failed to parse message",
										cause: error
									});
									this.onerror?.(e);
								}
							}
						} catch (error) {
							if (options?.signal?.aborted || error instanceof Error && error.name === "AbortError") return;
							this.onerror?.(error);
						}
					};
					processEvents().catch((error) => {
						if (options?.signal?.aborted || error instanceof Error && error.name === "AbortError") return;
						this.onerror?.(error);
					});
					return;
				}
				const error = new MCPClientError({
					message: `MCP HTTP Transport Error: Unexpected content type: ${contentType}`,
					statusCode: response.status,
					url: this.url.href
				});
				this.onerror?.(error);
				throw error;
			} catch (error) {
				if (options?.signal?.aborted) throw error;
				this.onerror?.(error);
				throw error;
			}
		};
		await attempt();
	}
	getStandardRequestHeaders(message) {
		const headers = { "Mcp-Method": message.method };
		const params = message.params;
		const name = message.method === "resources/read" ? params?.uri : message.method === "tools/call" || message.method === "prompts/get" ? params?.name : void 0;
		if (typeof name === "string") headers["Mcp-Name"] = encodeMCPHeaderValue(name);
		return headers;
	}
	getNextReconnectionDelay(attempt) {
		const { initialReconnectionDelay, reconnectionDelayGrowFactor, maxReconnectionDelay } = this.reconnectionOptions;
		return Math.min(initialReconnectionDelay * Math.pow(reconnectionDelayGrowFactor, attempt), maxReconnectionDelay);
	}
	scheduleInboundSseReconnection() {
		const { maxRetries } = this.reconnectionOptions;
		if (maxRetries > 0 && this.inboundReconnectAttempts >= maxRetries) {
			this.onerror?.(new MCPClientError({ message: `MCP HTTP Transport Error: Maximum reconnection attempts (${maxRetries}) exceeded.` }));
			return;
		}
		const delay = this.getNextReconnectionDelay(this.inboundReconnectAttempts);
		this.inboundReconnectAttempts += 1;
		setTimeout(() => {
			if (this.abortController?.signal.aborted) return;
			this.startInboundSse(false, this.lastInboundEventId);
		}, delay);
	}
	startInboundSse(triedAuth = false, resumeToken) {
		if (this.isModernProtocol()) return;
		this.openInboundSse(triedAuth, resumeToken).catch((error) => {
			if (error instanceof Error && error.name === "AbortError") return;
			this.onerror?.(error);
		});
	}
	async openInboundSse(triedAuth = false, resumeToken) {
		if (this.isModernProtocol()) return;
		try {
			const sessionIdForRequest = this.sessionId;
			const headers = await this.commonHeaders({ base: { Accept: "text/event-stream" } });
			if (resumeToken) headers["last-event-id"] = resumeToken;
			const response = await this.fetchFn(this.url.href, {
				method: "GET",
				headers,
				signal: this.abortController?.signal,
				redirect: this.redirectMode
			});
			this.applySessionIdFromResponse(response);
			if (response.status === 401 && this.authProvider && !triedAuth) {
				const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
				this.resourceMetadataUrl = resourceMetadataUrl;
				try {
					if (await this.authorizeOnce(this.resourceMetadataUrl, scope) !== "AUTHORIZED") {
						const error = new UnauthorizedError();
						this.onerror?.(error);
						return;
					}
				} catch (error) {
					this.onerror?.(error);
					return;
				}
				return this.openInboundSse(true, resumeToken);
			}
			if (response.status === 405) return;
			if (!response.ok || !response.body) {
				if (response.status === 404 && sessionIdForRequest) this.expireSessionId(sessionIdForRequest);
				const error = new MCPClientError({
					message: `MCP HTTP Transport Error: GET SSE failed: ${response.status} ${response.statusText}`,
					statusCode: response.status,
					url: this.url.href
				});
				this.onerror?.(error);
				return;
			}
			const reader = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream()).getReader();
			const processEvents = async () => {
				try {
					while (true) {
						const { done, value } = await reader.read();
						if (done) return;
						const { event, data, id } = value;
						if (id) this.lastInboundEventId = id;
						if (isMessageEvent(event)) try {
							const jsonRpcMessage = await parseJSONRPCMessage(data);
							this.onmessage?.(jsonRpcMessage);
						} catch (error) {
							const e = new MCPClientError({
								message: "MCP HTTP Transport Error: Failed to parse message",
								cause: error
							});
							this.onerror?.(e);
						}
					}
				} catch (error) {
					if (error instanceof Error && error.name === "AbortError") return;
					this.onerror?.(error);
					if (!this.abortController?.signal.aborted) this.scheduleInboundSseReconnection();
				}
			};
			this.inboundSseConnection = { close: () => {
				reader.cancel().catch((error) => {
					if (error instanceof Error && error.name === "AbortError") return;
					this.onerror?.(error);
				});
			} };
			this.inboundReconnectAttempts = 0;
			processEvents().catch((error) => {
				if (error instanceof Error && error.name === "AbortError") return;
				this.onerror?.(error);
				if (!this.abortController?.signal.aborted) this.scheduleInboundSseReconnection();
			});
		} catch (error) {
			if (error instanceof Error && error.name === "AbortError") return;
			this.onerror?.(error);
			if (!this.abortController?.signal.aborted) this.scheduleInboundSseReconnection();
		}
	}
};
//#endregion
//#region src/tool/mcp-transport.ts
function createMcpTransport(config) {
	switch (config.type) {
		case "sse": return new SseMCPTransport(config);
		case "http": return new HttpMCPTransport({
			...config,
			initialProtocolVersion: config.initialProtocolVersion ?? "2026-07-28"
		});
		default: throw new MCPClientError({ message: "Unsupported or invalid transport configuration. If you are using a custom transport, make sure it implements the MCPTransport interface." });
	}
}
function isCustomMcpTransport(transport) {
	return "start" in transport && typeof transport.start === "function" && "send" in transport && typeof transport.send === "function" && "close" in transport && typeof transport.close === "function";
}
//#endregion
//#region src/tool/mcp-apps.ts
/**
* MCP capability extension name used by hosts that can render MCP Apps.
*/
const MCP_APP_EXTENSION_NAME = "io.modelcontextprotocol/ui";
/**
* MIME type for HTML resources that are meant to be rendered as MCP Apps.
*/
const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
/**
* Client capabilities to pass to `createMCPClient` when the host supports MCP Apps.
*/
const mcpAppClientCapabilities = { extensions: { [MCP_APP_EXTENSION_NAME]: { mimeTypes: [MCP_APP_MIME_TYPE] } } };
function getToolUiMeta(meta) {
	const uiMeta = meta?.ui;
	return isJSONObject(uiMeta) ? uiMeta : void 0;
}
/**
* Filters a string array to its string elements, dropping the field entirely
* (rather than failing the parse) if the value is not an array.
*/
const optionalStringArray = z4.array(z4.unknown()).transform((items) => items.filter((v) => typeof v === "string")).optional().catch(void 0);
const MCPAppResourceCSPSchema = z4.looseObject({
	connectDomains: optionalStringArray,
	resourceDomains: optionalStringArray,
	frameDomains: optionalStringArray,
	baseUriDomains: optionalStringArray
});
/**
* Runtime schema for `_meta.ui` on an MCP App resource. Each known field is
* validated; a malformed field is dropped (`.catch(undefined)`) rather than
* failing the whole parse, and unknown keys pass through for forward-compat.
*/
const MCPAppResourceMetaSchema = z4.looseObject({
	prefersBorder: z4.boolean().optional().catch(void 0),
	csp: MCPAppResourceCSPSchema.optional().catch(void 0),
	permissions: z4.record(z4.string(), z4.unknown()).optional().catch(void 0)
});
function getResourceUiMeta(meta) {
	const rawUiMeta = (isJSONObject(meta) ? meta : void 0)?.ui;
	if (!isJSONObject(rawUiMeta)) return;
	const parsed = MCPAppResourceMetaSchema.safeParse(rawUiMeta);
	return parsed.success ? parsed.data : void 0;
}
function parseVisibility(value) {
	return Array.isArray(value) ? value.filter((v) => v === "model" || v === "app") : void 0;
}
/**
* Reads and validates MCP Apps metadata from a tool definition.
*/
function getMCPAppToolMeta(tool) {
	const uiMeta = getToolUiMeta(tool._meta);
	const resourceUri = uiMeta?.resourceUri ?? tool._meta?.["ui/resourceUri"];
	const visibility = parseVisibility(uiMeta?.visibility);
	if (resourceUri !== void 0) {
		if (typeof resourceUri !== "string" || !resourceUri.startsWith("ui://")) throw new Error(`Invalid MCP App resource URI: ${JSON.stringify(resourceUri)}`);
	} else if (uiMeta == null) return;
	return {
		...uiMeta,
		...resourceUri != null ? { resourceUri } : {},
		...visibility != null ? { visibility } : {}
	};
}
/**
* Splits tool definitions into model-visible tools and app-visible tools.
*/
function splitMCPAppTools(definitions) {
	const modelVisibleTools = [];
	const appVisibleTools = [];
	for (const tool of definitions.tools) {
		const visibility = getMCPAppToolMeta(tool)?.visibility;
		if (visibility == null || visibility.includes("model")) modelVisibleTools.push(tool);
		if (visibility?.includes("app") === true) appVisibleTools.push(tool);
	}
	return {
		modelVisible: {
			...definitions,
			tools: modelVisibleTools
		},
		appVisible: {
			...definitions,
			tools: appVisibleTools
		}
	};
}
/**
* Extracts app HTML and rendering metadata from a `resources/read` result.
*/
function getMCPAppResourceFromReadResult({ uri, resource }) {
	const content = resource.contents.find((content) => content.uri === uri);
	if (content == null) throw new Error(`MCP App resource not found in read result: ${uri}`);
	if (content.mimeType !== "text/html;profile=mcp-app") throw new Error(`Unsupported MCP App resource MIME type: ${content.mimeType}`);
	const html = "text" in content && typeof content.text === "string" ? content.text : "blob" in content && typeof content.blob === "string" ? new TextDecoder().decode(convertBase64ToUint8Array(content.blob)) : void 0;
	if (html == null) throw new Error(`Unsupported MCP App resource content format: ${uri}`);
	const meta = getResourceUiMeta(content._meta);
	return {
		uri,
		mimeType: MCP_APP_MIME_TYPE,
		html,
		meta
	};
}
/**
* Reads a `ui://` resource from an MCP server and normalizes it for rendering.
*/
async function readMCPAppResource({ client, uri, options }) {
	if (!uri.startsWith("ui://")) throw new Error(`Unsupported MCP App resource URI: ${uri}`);
	return getMCPAppResourceFromReadResult({
		uri,
		resource: await client.readResource({
			uri,
			options
		})
	});
}
//#endregion
//#region src/tool/mcp-client.ts
const CLIENT_VERSION = "1.0.0";
const DEFAULT_MAX_TOOL_CALL_RETRIES = 0;
const DEFAULT_PROTOCOL_DISCOVERY_TIMEOUT = 1e3;
const MAX_INPUT_REQUIRED_ROUNDS = 10;
const INPUT_REQUIRED_RETRY_DELAY = 250;
const MODERN_PROTOCOL_ERROR_CODES = [
	-32020,
	-32021,
	-32022
];
const DEFAULT_RETRY_ERROR_CODES = [
	"ConnectionRefused",
	"ConnectionClosed",
	"FailedToOpenSocket",
	"ECONNRESET",
	"ECONNREFUSED",
	"ETIMEDOUT",
	"EPIPE"
];
function getErrorStatusCode(error) {
	if (error != null && typeof error === "object" && "statusCode" in error && typeof error.statusCode === "number") return error.statusCode;
}
function getStringErrorCode(error) {
	if (error != null && typeof error === "object" && "code" in error && typeof error.code === "string") return error.code;
}
function isRetryableMCPToolCallError(error) {
	const statusCode = getErrorStatusCode(error);
	if (statusCode != null) return statusCode === 408 || statusCode === 409 || statusCode === 429 || statusCode >= 500;
	if (MCPClientError.isInstance(error) && error.code != null) return false;
	const errorCode = getStringErrorCode(error);
	return errorCode != null && DEFAULT_RETRY_ERROR_CODES.includes(errorCode);
}
function prepareMaxRetries(maxRetries) {
	if (maxRetries == null) return DEFAULT_MAX_TOOL_CALL_RETRIES;
	if (!Number.isInteger(maxRetries)) throw new MCPClientError({ message: "maxRetries must be an integer" });
	if (maxRetries < 0) throw new MCPClientError({ message: "maxRetries must be >= 0" });
	return maxRetries;
}
function getEffectiveTimeout({ timeout, maxTotalTimeout }) {
	if (timeout == null) return maxTotalTimeout;
	if (maxTotalTimeout == null) return timeout;
	return Math.min(timeout, maxTotalTimeout);
}
function waitForAbort(promise, signal) {
	if (signal == null) return promise;
	return new Promise((resolve, reject) => {
		const cleanup = () => {
			signal.removeEventListener("abort", onAbort);
		};
		const onAbort = () => {
			cleanup();
			reject(signal.reason);
		};
		if (signal.aborted) {
			onAbort();
			return;
		}
		signal.addEventListener("abort", onAbort, { once: true });
		promise.then((value) => {
			cleanup();
			resolve(value);
		}).catch((error) => {
			cleanup();
			reject(error);
		});
	});
}
function mcpToModelOutput({ output }) {
	if (typeof output === "string") return {
		type: "text",
		value: output
	};
	if (output == null || typeof output !== "object" || !("content" in output) || !Array.isArray(output.content)) return {
		type: "json",
		value: output ?? null
	};
	return {
		type: "content",
		value: output.content.map((part) => {
			if (part.type === "text" && "text" in part) return {
				type: "text",
				text: part.text
			};
			if (part.type === "image" && "data" in part && "mimeType" in part) return {
				type: "file",
				mediaType: part.mimeType,
				data: {
					type: "data",
					data: part.data
				}
			};
			return {
				type: "text",
				text: JSON.stringify(part)
			};
		})
	};
}
async function createMCPClient(config) {
	const client = new DefaultMCPClient(config);
	await client.init();
	return client;
}
/**
* A lightweight MCP Client implementation
*
* The primary purpose of this client is tool conversion between MCP<>AI SDK
* but can later be extended to support other MCP features
*
* Tool parameters are automatically inferred from the server's JSON schema
* if not explicitly provided in the tools configuration
*
* This client is meant to be used to communicate with a single server. To communicate and fetch tools across multiple servers, it's recommended to create a new client instance per server.
*
* Not supported:
* - Accepting in-band notifications (webhook events use a separate HTTP handler)
* - Automatic session persistence for Streamable HTTP transport
* - Resumable SSE streams
*/
var DefaultMCPClient = class {
	constructor({ experimental_events: events, transport: transportConfig, name, clientName = name ?? "ai-sdk-mcp-client", version = CLIENT_VERSION, onUncaughtError, maxRetries, capabilities, initialInitializeResult, initializationOptions, protocolVersionDiscovery = true }) {
		this.requestMessageId = 0;
		this.responseHandlers = /* @__PURE__ */ new Map();
		this.serverCapabilities = {};
		this._serverInfo = {
			name: "",
			version: ""
		};
		this._initializeResult = {
			protocolVersion: LATEST_LEGACY_PROTOCOL_VERSION,
			capabilities: {},
			serverInfo: this._serverInfo
		};
		this.protocolEra = "legacy";
		this.protocolVersion = LATEST_LEGACY_PROTOCOL_VERSION;
		this.toolHeaderBindings = /* @__PURE__ */ new Map();
		this.isClosed = true;
		this.closeController = new AbortController();
		if (events?.adapter !== void 0 && (events.store !== void 0 || events.validateArguments !== void 0)) throw new MCPClientError({ message: "Configure experimental_events with either an adapter or a store, not both. Managed adapters own argument validation." });
		if (events?.adapter !== void 0 && typeof events.adapter?.createAdapter !== "function") throw new MCPClientError({ message: "experimental_events.adapter must implement createAdapter. Wrap bound operations with { createAdapter: () => operations }." });
		const operations = events?.adapter?.createAdapter({ transport: isCustomMcpTransport(transportConfig) ? { type: "custom" } : {
			type: transportConfig.type,
			url: transportConfig.url
		} });
		if (events?.adapter !== void 0) validateMCPEventOperations(operations);
		this.onUncaughtError = onUncaughtError;
		this.maxRetries = prepareMaxRetries(maxRetries);
		this.clientCapabilities = capabilities ?? {};
		this.initialInitializeResult = initialInitializeResult;
		this.initializationOptions = initializationOptions;
		this.protocolVersionDiscovery = protocolVersionDiscovery;
		if (isCustomMcpTransport(transportConfig)) this.transport = transportConfig;
		else this.transport = createMcpTransport(transportConfig);
		this.transport.onclose = () => this.onClose();
		this.transport.onerror = (error) => this.onError(error);
		this.transport.onmessage = (message) => {
			if ("method" in message) {
				if ("id" in message) this.onRequestMessage(message);
				else this.onError(new MCPClientError({ message: "Unsupported message type" }));
				return;
			}
			this.onResponse(message);
		};
		this.clientInfo = {
			name: clientName,
			version
		};
		const directEvents = createMCPEvents({
			request: (args) => this.request(args),
			store: events?.store,
			validateArguments: events?.validateArguments
		});
		this.experimental_events = operations ? createManagedMCPEvents(operations, directEvents.list) : directEvents;
	}
	get serverInfo() {
		return this._serverInfo;
	}
	get initializeResult() {
		return this._initializeResult;
	}
	get instructions() {
		return this._serverInstructions;
	}
	async init() {
		const externalSignal = this.initializationOptions?.signal;
		const timeout = this.initializationOptions ? getEffectiveTimeout(this.initializationOptions) : void 0;
		const timeoutController = timeout == null ? void 0 : new AbortController();
		const signal = externalSignal == null ? timeoutController?.signal : timeoutController == null ? externalSignal : AbortSignal.any([externalSignal, timeoutController.signal]);
		let timeoutId;
		let timeoutError;
		if (timeout != null) timeoutId = setTimeout(() => {
			timeoutError = new MCPClientError({ message: `MCP client initialization timed out after ${timeout}ms` });
			timeoutController?.abort(timeoutError);
		}, timeout);
		try {
			this.isClosed = false;
			signal?.throwIfAborted();
			await waitForAbort(this.transport.start(), signal);
			if (this.initialInitializeResult) {
				const result = InitializeResultSchema.parse(this.initialInitializeResult);
				this.applyInitializeResult(result);
				return this;
			}
			if (this.protocolVersionDiscovery && this.transport.supportsProtocolVersionDiscovery) {
				if (await this.tryProtocolDiscovery(signal)) return this;
			}
			this.protocolEra = "legacy";
			this.protocolVersion = LATEST_LEGACY_PROTOCOL_VERSION;
			this.setTransportProtocolVersion(this.protocolVersion);
			const result = await this.request({
				request: {
					method: "initialize",
					params: {
						protocolVersion: LATEST_LEGACY_PROTOCOL_VERSION,
						capabilities: this.clientCapabilities,
						clientInfo: this.clientInfo
					}
				},
				resultSchema: InitializeResultSchema,
				options: { signal }
			});
			if (result === void 0) throw new MCPClientError({ message: "Server sent invalid initialize result" });
			this.applyInitializeResult(result);
			await this.notification({ method: "notifications/initialized" }, { signal });
			return this;
		} catch (error) {
			try {
				await waitForAbort(this.transport.close({ signal }), signal);
			} catch {}
			this.onClose();
			if (timeoutError != null) throw timeoutError;
			if (externalSignal?.aborted) throw new MCPClientError({
				message: "MCP client initialization was aborted",
				cause: externalSignal.reason
			});
			throw error;
		} finally {
			if (timeoutId != null) clearTimeout(timeoutId);
		}
	}
	async tryProtocolDiscovery(signal) {
		this.protocolEra = "modern";
		this.protocolVersion = LATEST_PROTOCOL_VERSION;
		this.setTransportProtocolVersion(this.protocolVersion);
		try {
			const result = await this.request({
				request: { method: "server/discover" },
				resultSchema: DiscoverResultSchema,
				options: {
					signal,
					timeout: this.transport.waitsForProtocolVersionDiscovery ? void 0 : DEFAULT_PROTOCOL_DISCOVERY_TIMEOUT
				}
			});
			this.applyDiscoverResult(result);
			return true;
		} catch (error) {
			if (MCPClientError.isInstance(error) && error.code != null && MODERN_PROTOCOL_ERROR_CODES.includes(error.code)) {
				if (error.code === -32022 && error.data != null && typeof error.data === "object" && "supported" in error.data && Array.isArray(error.data.supported) && error.data.supported.every((version) => typeof version === "string")) {
					const supportedVersions = error.data.supported;
					if (!supportedVersions.includes(this.protocolVersion) && supportedVersions.some((version) => version <= "2025-11-25" && SUPPORTED_PROTOCOL_VERSIONS.includes(version))) throw new MCPClientError({
						message: `MCP server rejected protocol version ${this.protocolVersion} during discovery. Server-supported versions: ${supportedVersions.join(", ")}. The client does not automatically switch to legacy initialization after this error. If this is a known legacy server, set protocolVersionDiscovery: false to use initialize.`,
						code: error.code,
						data: error.data,
						statusCode: error.statusCode,
						url: error.url,
						responseBody: error.responseBody,
						cause: error
					});
				}
				throw error;
			}
			if (this.transport.waitsForProtocolVersionDiscovery && !MCPClientError.isInstance(error)) throw error;
			return false;
		}
	}
	applyDiscoverResult(result) {
		if (!result.supportedVersions.includes(this.protocolVersion)) throw new MCPClientError({ message: `Server does not support the requested protocol version: ${this.protocolVersion}` });
		const serverInfo = result._meta?.["io.modelcontextprotocol/serverInfo"];
		if (serverInfo != null && typeof serverInfo === "object" && "name" in serverInfo && typeof serverInfo.name === "string" && "version" in serverInfo && typeof serverInfo.version === "string") this._serverInfo = serverInfo;
		this.serverCapabilities = result.capabilities;
		this._serverInstructions = result.instructions;
		this._initializeResult = {
			protocolVersion: this.protocolVersion,
			capabilities: result.capabilities,
			serverInfo: this._serverInfo,
			instructions: result.instructions
		};
	}
	setTransportProtocolVersion(version) {
		if (this.transport.setProtocolVersion) this.transport.setProtocolVersion(version);
		else this.transport.protocolVersion = version;
	}
	applyInitializeResult(result) {
		if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) throw new MCPClientError({ message: `Server's protocol version is not supported: ${result.protocolVersion}` });
		this.serverCapabilities = result.capabilities;
		this.protocolEra = "legacy";
		this.protocolVersion = result.protocolVersion;
		this._serverInfo = result.serverInfo;
		this._initializeResult = result;
		this.setTransportProtocolVersion(result.protocolVersion);
		this._serverInstructions = result.instructions;
	}
	async close() {
		if (this.isClosed) return;
		await this.transport?.close();
		this.onClose();
	}
	send(message, options) {
		return options == null ? this.transport.send(message) : this.transport.send(message, options);
	}
	assertCapability(method) {
		switch (method) {
			case "initialize":
			case "server/discover": break;
			case "completion/complete":
				if (!this.serverCapabilities.completions) throw new MCPClientError({ message: `Server does not support completions` });
				break;
			case "events/list":
			case "events/subscribe":
			case "events/unsubscribe":
				if (!this.serverCapabilities.events) throw new MCPClientError({ message: "Server does not support events" });
				break;
			case "tools/list":
			case "tools/call":
				if (!this.serverCapabilities.tools) throw new MCPClientError({ message: `Server does not support tools` });
				break;
			case "resources/list":
			case "resources/read":
			case "resources/templates/list":
				if (!this.serverCapabilities.resources) throw new MCPClientError({ message: `Server does not support resources` });
				break;
			case "prompts/list":
			case "prompts/get":
				if (!this.serverCapabilities.prompts) throw new MCPClientError({ message: `Server does not support prompts` });
				break;
			default: throw new MCPClientError({ message: `Unsupported method: ${method}` });
		}
	}
	async request({ request, resultSchema, options }) {
		if (this.isClosed || this.protocolEra !== "modern" || ![
			"tools/call",
			"prompts/get",
			"resources/read"
		].includes(request.method)) return this.requestOnce({
			request,
			resultSchema,
			options
		});
		options?.signal?.throwIfAborted();
		const startedAt = Date.now();
		let nextRequest = request;
		for (let round = 0;; round++) {
			const timeout = getEffectiveTimeout({
				timeout: options?.timeout,
				maxTotalTimeout: options?.maxTotalTimeout == null ? void 0 : Math.max(0, options.maxTotalTimeout - (Date.now() - startedAt))
			});
			const timeoutController = new AbortController();
			const signal = AbortSignal.any([
				this.closeController.signal,
				timeoutController.signal,
				...options?.signal == null ? [] : [options.signal]
			]);
			const timeoutError = new MCPClientError({ message: `Request timed out after ${timeout}ms` });
			const timeoutId = timeout == null ? void 0 : setTimeout(() => timeoutController.abort(timeoutError), timeout);
			try {
				signal.throwIfAborted();
				if (timeout === 0) throw timeoutError;
				const result = await this.requestOnce({
					request: nextRequest,
					resultSchema: ResultSchema,
					options: { signal },
					allowInputRequired: true
				});
				if (result.resultType !== "input_required") return resultSchema.parse(result);
				const { inputRequests, requestState } = InputRequiredResultSchema.parse(result);
				if (round === MAX_INPUT_REQUIRED_ROUNDS) throw new MCPClientError({ message: `Request still requires input after ${MAX_INPUT_REQUIRED_ROUNDS} rounds` });
				const responses = Object.create(null);
				for (const [key, inputRequest] of Object.entries(inputRequests ?? {})) {
					signal.throwIfAborted();
					if (inputRequest.method !== "elicitation/create" || inputRequest.params?.mode != null && inputRequest.params.mode !== "form") throw new MCPClientError({ message: `Unsupported input request: ${inputRequest.method}. Only form elicitation is supported.` });
					if (!this.elicitationRequestHandler) throw new MCPClientError({ message: "Server requested input, but no handler is registered. Use onElicitationRequest to handle elicitation." });
					const parsedRequest = InputRequiredElicitationSchema.parse(inputRequest);
					const answer = await waitForAbort(Promise.resolve(this.elicitationRequestHandler(parsedRequest)), signal);
					responses[key] = ElicitResultSchema.parse(answer);
				}
				const hasResponses = Object.keys(responses).length > 0;
				if (!hasResponses) await delay(INPUT_REQUIRED_RETRY_DELAY, { abortSignal: signal });
				signal.throwIfAborted();
				nextRequest = {
					...request,
					params: {
						...request.params,
						...hasResponses ? { inputResponses: responses } : {},
						...requestState !== void 0 ? { requestState } : {}
					}
				};
			} catch (error) {
				if (signal.aborted) throw options?.signal?.aborted ? new MCPClientError({
					message: "Request was aborted",
					cause: options.signal.reason
				}) : signal.reason;
				throw MCPClientError.isInstance(error) ? error : new MCPClientError({
					message: "Failed to process server response",
					cause: error
				});
			} finally {
				clearTimeout(timeoutId);
			}
		}
	}
	async requestOnce({ request, resultSchema, options, allowInputRequired = false }) {
		return new Promise((resolve, reject) => {
			if (this.isClosed) return reject(new MCPClientError({ message: "Attempted to send a request from a closed client" }));
			this.assertCapability(request.method);
			const signal = options?.signal;
			signal?.throwIfAborted();
			const timeout = options == null ? void 0 : getEffectiveTimeout(options);
			const timeoutController = timeout == null ? void 0 : new AbortController();
			const transportSignal = signal == null ? timeoutController?.signal : timeoutController == null ? signal : AbortSignal.any([signal, timeoutController.signal]);
			let timeoutId;
			const messageId = this.requestMessageId++;
			const preparedRequest = this.protocolEra === "modern" ? {
				...request,
				params: {
					...request.params,
					_meta: {
						...request.params?._meta,
						"io.modelcontextprotocol/protocolVersion": this.protocolVersion,
						"io.modelcontextprotocol/clientCapabilities": this.clientCapabilities,
						"io.modelcontextprotocol/clientInfo": this.clientInfo
					}
				}
			} : request;
			const jsonrpcRequest = {
				...preparedRequest,
				jsonrpc: "2.0",
				id: messageId
			};
			const headers = this.getToolRequestHeaders(preparedRequest);
			const rejectWithAbortError = () => {
				reject(new MCPClientError({
					message: "Request was aborted",
					cause: signal?.reason
				}));
			};
			const cleanup = () => {
				this.responseHandlers.delete(messageId);
				signal?.removeEventListener("abort", onAbort);
				if (timeoutId != null) clearTimeout(timeoutId);
			};
			const rejectAndCleanup = (error) => {
				cleanup();
				reject(error);
			};
			const onAbort = () => {
				cleanup();
				rejectWithAbortError();
			};
			const onTimeout = () => {
				const error = new MCPClientError({ message: `Request timed out after ${timeout}ms` });
				timeoutController?.abort(error);
				rejectAndCleanup(error);
			};
			this.responseHandlers.set(messageId, (response) => {
				if (signal?.aborted) {
					cleanup();
					return rejectWithAbortError();
				}
				if (response instanceof Error) return rejectAndCleanup(response);
				try {
					if (this.protocolEra === "modern" && response.result.resultType == null) throw new MCPClientError({ message: "Modern MCP result is missing resultType" });
					if (response.result.resultType === "input_required" && !allowInputRequired) throw new MCPClientError({ message: `Input-required results are not supported for ${request.method} on protocol ${this.protocolVersion}` });
					const result = resultSchema.parse(response.result);
					cleanup();
					resolve(result);
				} catch (error) {
					const parseError = MCPClientError.isInstance(error) ? error : new MCPClientError({
						message: "Failed to parse server response",
						cause: error
					});
					rejectAndCleanup(parseError);
				}
			});
			signal?.addEventListener("abort", onAbort, { once: true });
			if (timeout != null) timeoutId = setTimeout(onTimeout, timeout);
			const sendOptions = {
				...transportSignal == null ? {} : { signal: transportSignal },
				...headers == null ? {} : { headers }
			};
			(Object.keys(sendOptions).length === 0 ? this.send(jsonrpcRequest) : this.send(jsonrpcRequest, sendOptions)).catch((error) => {
				rejectAndCleanup(error);
			});
		});
	}
	async listTools({ params, options } = {}) {
		const result = await this.request({
			request: {
				method: "tools/list",
				params
			},
			resultSchema: ListToolsResultSchema,
			options
		});
		return this.prepareToolDefinitions(result, params?.cursor == null);
	}
	prepareToolDefinitions(definitions, resetHeaderBindings = false) {
		if (this.protocolEra !== "modern" || !this.transport.supportsMcpToolParameterHeaders) return definitions;
		if (resetHeaderBindings) this.toolHeaderBindings.clear();
		const tools = definitions.tools.filter((toolDefinition) => {
			const result = getMCPToolHeaderBindings(toolDefinition.inputSchema);
			if (!result.success) {
				this.onError(new MCPClientError({ message: `Ignoring MCP tool "${toolDefinition.name}": ${result.error}` }));
				return false;
			}
			this.toolHeaderBindings.set(toolDefinition.name, result.bindings);
			return true;
		});
		return {
			...definitions,
			tools
		};
	}
	getToolRequestHeaders(request) {
		if (this.protocolEra !== "modern" || request.method !== "tools/call" || typeof request.params?.name !== "string") return;
		const bindings = this.toolHeaderBindings.get(request.params.name);
		if (bindings == null || bindings.length === 0) return;
		const args = request.params.arguments;
		if (args == null || typeof args !== "object" || Array.isArray(args)) return;
		try {
			return createMCPToolHeaders({
				bindings,
				args
			});
		} catch (error) {
			throw new MCPClientError({
				message: `Failed to create MCP headers for tool "${request.params.name}"`,
				cause: error
			});
		}
	}
	async callToolWithRetry({ options, execute }) {
		if (this.maxRetries === 0) return execute();
		return retryWithExponentialBackoff({
			maxRetries: this.maxRetries,
			abortSignal: options?.signal,
			shouldRetry: isRetryableMCPToolCallError,
			createRetryError: ({ message, errors }) => new MCPClientError({
				message,
				cause: errors[errors.length - 1]
			})
		})(execute);
	}
	async callTool({ name, arguments: args = {}, options }) {
		return this.callToolWithRetry({
			options,
			execute: () => this.request({
				request: {
					method: "tools/call",
					params: {
						name,
						arguments: args
					}
				},
				resultSchema: CallToolResultSchema,
				options
			})
		});
	}
	async listResourcesInternal({ params, options } = {}) {
		return this.request({
			request: {
				method: "resources/list",
				params
			},
			resultSchema: ListResourcesResultSchema,
			options
		});
	}
	async readResourceInternal({ uri, options }) {
		return this.request({
			request: {
				method: "resources/read",
				params: { uri }
			},
			resultSchema: ReadResourceResultSchema,
			options
		});
	}
	async listResourceTemplatesInternal({ options } = {}) {
		return this.request({
			request: { method: "resources/templates/list" },
			resultSchema: ListResourceTemplatesResultSchema,
			options
		});
	}
	async listPromptsInternal({ params, options } = {}) {
		return this.request({
			request: {
				method: "prompts/list",
				params
			},
			resultSchema: ListPromptsResultSchema,
			options
		});
	}
	async getPromptInternal({ name, args, options }) {
		return this.request({
			request: {
				method: "prompts/get",
				params: {
					name,
					arguments: args
				}
			},
			resultSchema: GetPromptResultSchema,
			options
		});
	}
	async completeInternal({ options, ...params }) {
		return this.request({
			request: {
				method: "completion/complete",
				params
			},
			resultSchema: CompleteResultSchema,
			options
		});
	}
	async notification(notification, options) {
		const jsonrpcNotification = {
			...notification,
			jsonrpc: "2.0"
		};
		await waitForAbort(this.send(jsonrpcNotification, options?.signal == null ? void 0 : { signal: options.signal }), options?.signal);
	}
	/**
	* Returns a set of AI SDK tools from the MCP server.
	* This fetches tool definitions and wraps them with execute functions.
	* @returns A record of tool names to their implementations
	*/
	async tools({ schemas = "automatic" } = {}) {
		let definitions = await this.listTools();
		const tools = [...definitions.tools];
		while (definitions.nextCursor != null) {
			definitions = await this.listTools({ params: { cursor: definitions.nextCursor } });
			tools.push(...definitions.tools);
		}
		return this.toolsFromDefinitions({
			...definitions,
			tools
		}, { schemas });
	}
	/**
	* Creates AI SDK tools from tool definitions without fetching from the server.
	*/
	toolsFromDefinitions(definitions, { schemas = "automatic" } = {}) {
		definitions = this.prepareToolDefinitions(definitions);
		const tools = Object.create(null);
		for (const { name, title, description, inputSchema, annotations, _meta } of definitions.tools) {
			const resolvedTitle = title ?? annotations?.title;
			if (schemas !== "automatic" && !Object.prototype.hasOwnProperty.call(schemas, name)) continue;
			const self = this;
			const outputSchema = schemas !== "automatic" ? schemas[name]?.outputSchema : void 0;
			const appMeta = getMCPAppToolMeta({ _meta });
			const metadata = {
				clientName: this.clientInfo.name,
				toolName: name,
				...resolvedTitle != null ? { title: resolvedTitle } : {},
				...annotations != null ? { annotations: {
					...annotations.title != null ? { title: annotations.title } : {},
					...annotations.readOnlyHint != null ? { readOnlyHint: annotations.readOnlyHint } : {},
					...annotations.destructiveHint != null ? { destructiveHint: annotations.destructiveHint } : {},
					...annotations.idempotentHint != null ? { idempotentHint: annotations.idempotentHint } : {},
					...annotations.openWorldHint != null ? { openWorldHint: annotations.openWorldHint } : {}
				} } : {},
				...appMeta?.resourceUri != null ? { app: {
					...appMeta,
					mimeType: MCP_APP_MIME_TYPE
				} } : {}
			};
			const execute = async (args, options) => {
				options?.abortSignal?.throwIfAborted();
				const result = await self.callTool({
					name,
					arguments: args,
					options: { signal: options?.abortSignal }
				});
				if (result.isError) return result;
				if (outputSchema != null) return self.extractStructuredContent(result, outputSchema, name);
				return result;
			};
			tools[name] = {
				...schemas === "automatic" ? dynamicTool({
					description,
					title: resolvedTitle,
					metadata,
					inputSchema: jsonSchema({
						...inputSchema,
						properties: inputSchema.properties ?? {},
						additionalProperties: false
					}),
					execute,
					toModelOutput: mcpToModelOutput
				}) : tool({
					description,
					title: resolvedTitle,
					metadata,
					inputSchema: schemas[name].inputSchema,
					...outputSchema != null ? { outputSchema } : {},
					execute,
					toModelOutput: mcpToModelOutput
				}),
				_meta
			};
		}
		return tools;
	}
	/**
	* Extracts and validates structuredContent from a tool result.
	*/
	async extractStructuredContent(result, outputSchema, toolName) {
		if ("structuredContent" in result && result.structuredContent != null) {
			const validationResult = await safeValidateTypes({
				value: result.structuredContent,
				schema: asSchema(outputSchema)
			});
			if (!validationResult.success) throw new MCPClientError({
				message: `Tool "${toolName}" returned structuredContent that does not match the expected outputSchema`,
				cause: validationResult.error
			});
			return validationResult.value;
		}
		if ("content" in result && Array.isArray(result.content)) {
			const textContent = result.content.find((content) => content.type === "text");
			if (textContent && "text" in textContent) {
				const parseResult = await safeParseJSON({
					text: textContent.text,
					schema: outputSchema
				});
				if (!parseResult.success) throw new MCPClientError({
					message: `Tool "${toolName}" returned content that does not match the expected outputSchema`,
					cause: parseResult.error
				});
				return parseResult.value;
			}
		}
		throw new MCPClientError({ message: `Tool "${toolName}" did not return structuredContent or parseable text content` });
	}
	listResources({ params, options } = {}) {
		return this.listResourcesInternal({
			params,
			options
		});
	}
	readResource({ uri, options }) {
		return this.readResourceInternal({
			uri,
			options
		});
	}
	listResourceTemplates({ options } = {}) {
		return this.listResourceTemplatesInternal({ options });
	}
	listPrompts({ params, options } = {}) {
		return this.listPromptsInternal({
			params,
			options
		});
	}
	getPrompt({ name, arguments: args, options }) {
		return this.getPromptInternal({
			name,
			args,
			options
		});
	}
	/** @deprecated Use `listPrompts` instead. */
	experimental_listPrompts(options) {
		return this.listPrompts(options);
	}
	/** @deprecated Use `getPrompt` instead. */
	experimental_getPrompt(args) {
		return this.getPrompt(args);
	}
	complete(args) {
		return this.completeInternal(args);
	}
	onElicitationRequest(schema, handler) {
		if (schema !== ElicitationRequestSchema) throw new MCPClientError({ message: "Unsupported request schema. Only ElicitationRequestSchema is supported." });
		this.elicitationRequestHandler = handler;
	}
	async onRequestMessage(request) {
		try {
			if (request.method === "ping") {
				await this.transport.send({
					jsonrpc: "2.0",
					id: request.id,
					result: {}
				});
				return;
			}
			if (request.method !== "elicitation/create") {
				await this.transport.send({
					jsonrpc: "2.0",
					id: request.id,
					error: {
						code: -32601,
						message: `Unsupported request method: ${request.method}`
					}
				});
				return;
			}
			if (!this.elicitationRequestHandler) {
				await this.transport.send({
					jsonrpc: "2.0",
					id: request.id,
					error: {
						code: -32601,
						message: "No elicitation handler registered on client"
					}
				});
				return;
			}
			const parsedRequest = ElicitationRequestSchema.safeParse({
				method: request.method,
				params: request.params
			});
			if (!parsedRequest.success) {
				await this.transport.send({
					jsonrpc: "2.0",
					id: request.id,
					error: {
						code: -32602,
						message: `Invalid elicitation request: ${parsedRequest.error.message}`,
						data: parsedRequest.error.issues
					}
				});
				return;
			}
			try {
				const result = await this.elicitationRequestHandler(parsedRequest.data);
				const validatedResult = ElicitResultSchema.parse(result);
				await this.transport.send({
					jsonrpc: "2.0",
					id: request.id,
					result: validatedResult
				});
			} catch (error) {
				await this.transport.send({
					jsonrpc: "2.0",
					id: request.id,
					error: {
						code: -32603,
						message: error instanceof Error ? error.message : "Failed to handle elicitation request"
					}
				});
				this.onError(error);
			}
		} catch (error) {
			this.onError(error);
		}
	}
	onClose() {
		if (this.isClosed) return;
		this.isClosed = true;
		const error = new MCPClientError({ message: "Connection closed" });
		this.closeController.abort(error);
		for (const handler of this.responseHandlers.values()) handler(error);
		this.responseHandlers.clear();
	}
	onError(error) {
		if (this.onUncaughtError) this.onUncaughtError(error);
	}
	onResponse(response) {
		if (response.id == null) {
			this.onError(new MCPClientError({ message: `Protocol error: Received a response without a message ID: ${JSON.stringify(response)}` }));
			return;
		}
		const messageId = Number(response.id);
		const handler = this.responseHandlers.get(messageId);
		if (handler === void 0) throw new MCPClientError({ message: `Protocol error: Received a response for an unknown message ID: ${JSON.stringify(response)}` });
		this.responseHandlers.delete(messageId);
		handler("result" in response ? response : new MCPClientError({
			message: response.error.message,
			code: response.error.code,
			data: response.error.data,
			cause: response.error
		}));
	}
};
//#endregion
//#region src/tool/mcp-app-fingerprint.ts
const encoder = new TextEncoder();
/**
* Deterministic JSON serialization: object keys are sorted so two
* structurally-equal values always produce the same string regardless of key
* insertion order. Used as the stable input to content hashing.
*/
function canonicalJSON(value) {
	if (value == null || typeof value !== "object") return JSON.stringify(value ?? null);
	if (Array.isArray(value)) return `[${value.map(canonicalJSON).join(",")}]`;
	const record = value;
	return `{${Object.keys(record).sort().map((k) => `${JSON.stringify(k)}:${canonicalJSON(record[k])}`).join(",")}}`;
}
function toBase64url(bytes) {
	return convertUint8ArrayToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
/**
* Stable SHA-256 digest (base64url) over an MCP App resource's `html`, `csp`,
* and `permissions`.
*
* Capture a baseline on first load and compare later reads with
* {@link detectMCPAppResourceDrift} to detect a changed resource. Baseline
* storage and the response are the host's concern, mirroring `fingerprintTools`.
*/
async function fingerprintMCPAppResource(resource) {
	const digest = await crypto.subtle.digest("SHA-256", encoder.encode(canonicalJSON({
		html: resource.html,
		csp: resource.meta?.csp ?? null,
		permissions: resource.meta?.permissions ?? null
	})));
	return toBase64url(new Uint8Array(digest));
}
/**
* Compares two fingerprints from {@link fingerprintMCPAppResource}. Returns
* `true` when the current resource differs from its baseline.
*/
function detectMCPAppResourceDrift(current, baseline) {
	return current !== baseline;
}
//#endregion
//#region src/tool/mcp-event-webhook.ts
const MAX_BODY_BYTES = 262144;
const VerificationSchema = z.object({
	type: z.literal("verification"),
	challenge: z.string().min(1)
});
/**
* Create a Web Request/Response handler for MCP webhook deliveries. Mount it
* at the callback URL before subscribing. Verification is enabled automatically
* using the pending delivery secret saved by the client's subscribe method.
*/
function createMCPEventWebhook({ store, validatePayload, onEvent, onGap, onTerminated, onError }) {
	return async (request) => {
		if (request.method !== "POST") return new Response(null, {
			status: 405,
			headers: { Allow: "POST" }
		});
		if (request.headers.get("content-type")?.split(";")[0].trim().toLowerCase() !== "application/json") return new Response(null, { status: 415 });
		if (Number(request.headers.get("content-length")) > MAX_BODY_BYTES) return new Response(null, { status: 413 });
		try {
			const key = new URL(request.url).searchParams.get(MCP_EVENT_CALLBACK_KEY);
			const id = request.headers.get("X-MCP-Subscription-Id");
			if (!key || !id) return new Response(null, { status: 401 });
			const subscription = await store.get(key);
			if (!subscription || subscription.id != null && subscription.id !== id) return new Response(null, { status: 401 });
			const reader = request.body?.getReader();
			if (!reader) return new Response(null, { status: 400 });
			const chunks = [];
			let length = 0;
			try {
				while (true) {
					const { done, value } = await reader.read();
					if (done) break;
					length += value.byteLength;
					if (length > MAX_BODY_BYTES) {
						await reader.cancel();
						return new Response(null, { status: 413 });
					}
					chunks.push(value);
				}
			} finally {
				reader.releaseLock();
			}
			const body = new Uint8Array(length);
			let offset = 0;
			for (const chunk of chunks) {
				body.set(chunk, offset);
				offset += chunk.length;
			}
			if (!await verifyMCPEventSignature({
				headers: request.headers,
				body,
				secret: subscription.delivery.secret
			})) return new Response(null, { status: 401 });
			const parsed = await safeParseJSON({ text: new TextDecoder().decode(body) });
			if (!parsed.success) return new Response(null, { status: 400 });
			const value = parsed.value;
			let control;
			if (value != null && typeof value === "object" && "type" in value) {
				if (value.type === "verification") {
					const verification = VerificationSchema.safeParse(value);
					if (!verification.success) return new Response(null, { status: 400 });
					return Response.json({ challenge: verification.data.challenge });
				}
				const parsedControl = MCPEventControlSchema.safeParse(value);
				if (!parsedControl.success) return new Response(null, { status: 400 });
				control = parsedControl.data;
			}
			if (subscription.id == null || subscription.status !== "active" && control?.type !== "terminated") return new Response(null, { status: 503 });
			const subscriptionInfo = {
				id: subscription.id,
				name: subscription.name,
				arguments: subscription.arguments,
				delivery: {
					mode: "webhook",
					url: subscription.delivery.url
				},
				refreshBefore: subscription.refreshBefore ?? null
			};
			if (control) {
				const messageId = request.headers.get("webhook-id");
				if (control.type === "gap") {
					await onGap?.({
						subscription: subscriptionInfo,
						messageId,
						gap: control
					});
					await store.update(subscription.key, {
						cursor: control.cursor,
						truncated: true
					});
				} else {
					await onTerminated?.({
						subscription: subscriptionInfo,
						messageId,
						termination: control
					});
					await store.delete(subscription.key);
				}
				return new Response(null, { status: 204 });
			}
			if (subscription.refreshBefore != null && Date.parse(subscription.refreshBefore) <= Date.now()) return new Response(null, { status: 410 });
			const event = MCPEventSchema.safeParse(value);
			if (!event.success || event.data.name !== subscription.name || event.data.eventId !== request.headers.get("webhook-id")) return new Response(null, { status: 400 });
			try {
				await validatePayload?.({
					definition: subscription.definition,
					data: event.data.data
				});
			} catch (error) {
				onError?.(error);
				return new Response(null, { status: 400 });
			}
			await onEvent({
				subscription: subscriptionInfo,
				event: event.data
			});
			if (event.data.cursor !== void 0) await store.update(subscription.key, { cursor: event.data.cursor });
			return new Response(null, { status: 204 });
		} catch (error) {
			onError?.(error);
			return new Response(null, { status: 503 });
		}
	};
}
//#endregion
export { AuthorizationServerMismatchError, ElicitResultSchema, ElicitationRequestSchema, MCPClientError, MCPClientOAuthError, MCP_APP_MIME_TYPE, UnauthorizedError, auth, createMCPClient, createMCPClient as experimental_createMCPClient, detectMCPAppResourceDrift, createMCPEventWebhook as experimental_createMCPEventWebhook, fingerprintMCPAppResource, mcpAppClientCapabilities, readMCPAppResource, splitMCPAppTools, validateJSONRPCMessage };

//# sourceMappingURL=index.js.map