UNPKG

@ai-sdk/mcp

Version:

The **Model Context Protocol (MCP) client** for the [AI SDK](https://ai-sdk.dev/docs) lets you connect to MCP servers and use their tools with AI SDK functions like `generateText` and `streamText`.

1,514 lines (1,500 loc) 71.4 kB
// src/tool/mcp-client.ts import { asSchema, dynamicTool, jsonSchema, safeParseJSON, safeValidateTypes, tool } from "@ai-sdk/provider-utils"; // src/error/mcp-client-error.ts import { AISDKError } from "@ai-sdk/provider"; var name = "AI_MCPClientError"; var marker = `vercel.ai.error.${name}`; var symbol = Symbol.for(marker); var _a, _b; var MCPClientError = class extends (_b = AISDKError, _a = symbol, _b) { constructor({ name: name3 = "MCPClientError", message, cause, data, code, statusCode, url, responseBody }) { super({ name: name3, message, cause }); this[_a] = true; this.data = data; this.code = code; this.statusCode = statusCode; this.url = url; this.responseBody = responseBody; } static isInstance(error) { return AISDKError.hasMarker(error, marker); } }; // src/tool/mcp-sse-transport.ts import { EventSourceParserStream, withUserAgentSuffix, getRuntimeEnvironmentUserAgent } from "@ai-sdk/provider-utils"; // src/tool/json-rpc-message.ts import { parseJSON } from "@ai-sdk/provider-utils"; import { z as z2 } from "zod/v4"; // src/tool/types.ts import { z } from "zod/v4"; var LATEST_PROTOCOL_VERSION = "2025-11-25"; var SUPPORTED_PROTOCOL_VERSIONS = [ LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05" ]; var ToolMetaSchema = z.optional(z.record(z.string(), z.unknown())); var ClientOrServerImplementationSchema = z.looseObject({ name: z.string(), version: z.string(), title: z.optional(z.string()) }); var BaseParamsSchema = z.looseObject({ _meta: z.optional(z.object({}).loose()) }); var ResultSchema = BaseParamsSchema; var RequestSchema = z.object({ method: z.string(), params: z.optional(BaseParamsSchema) }); var ElicitationCapabilitySchema = z.object({ applyDefaults: z.optional(z.boolean()) }).loose(); var ServerCapabilitiesSchema = z.looseObject({ experimental: z.optional(z.object({}).loose()), logging: 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) }); var ClientCapabilitiesSchema = z.object({ elicitation: z.optional(ElicitationCapabilitySchema) }).loose(); var InitializeResultSchema = ResultSchema.extend({ protocolVersion: z.string(), capabilities: ServerCapabilitiesSchema, serverInfo: ClientOrServerImplementationSchema, instructions: z.optional(z.string()) }); var PaginatedResultSchema = ResultSchema.extend({ nextCursor: z.optional(z.string()) }); var 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.object({ type: z.literal("object"), properties: z.optional(z.object({}).loose()) }).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()) }).loose() ), _meta: ToolMetaSchema }).loose(); var ListToolsResultSchema = PaginatedResultSchema.extend({ tools: z.array(ToolSchema) }); var TextContentSchema = z.object({ type: z.literal("text"), text: z.string() }).loose(); var ImageContentSchema = z.object({ type: z.literal("image"), data: z.base64(), mimeType: z.string() }).loose(); var 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(); var ListResourcesResultSchema = PaginatedResultSchema.extend({ resources: z.array(ResourceSchema) }); var 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(); var TextResourceContentsSchema = ResourceContentsSchema.extend({ text: z.string() }); var BlobResourceContentsSchema = ResourceContentsSchema.extend({ blob: z.base64() }); var EmbeddedResourceSchema = z.object({ type: z.literal("resource"), resource: z.union([TextResourceContentsSchema, BlobResourceContentsSchema]) }).loose(); var 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(); var CallToolResultSchema = ResultSchema.extend({ content: z.array( z.union([ TextContentSchema, ImageContentSchema, EmbeddedResourceSchema, ResourceLinkContentSchema ]) ), /** * @see https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content */ structuredContent: z.optional(z.unknown()), isError: z.boolean().default(false).optional() }).or( ResultSchema.extend({ toolResult: z.unknown() }) ); var 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(); var ListResourceTemplatesResultSchema = ResultSchema.extend({ resourceTemplates: z.array(ResourceTemplateSchema) }); var ReadResourceResultSchema = ResultSchema.extend({ contents: z.array( z.union([TextResourceContentsSchema, BlobResourceContentsSchema]) ) }); var PromptArgumentSchema = z.object({ name: z.string(), description: z.optional(z.string()), required: z.optional(z.boolean()) }).loose(); var PromptSchema = z.object({ name: z.string(), title: z.optional(z.string()), description: z.optional(z.string()), arguments: z.optional(z.array(PromptArgumentSchema)) }).loose(); var ListPromptsResultSchema = PaginatedResultSchema.extend({ prompts: z.array(PromptSchema) }); var PromptMessageSchema = z.object({ role: z.union([z.literal("user"), z.literal("assistant")]), content: z.union([ TextContentSchema, ImageContentSchema, EmbeddedResourceSchema, ResourceLinkContentSchema ]) }).loose(); var GetPromptResultSchema = ResultSchema.extend({ description: z.optional(z.string()), messages: z.array(PromptMessageSchema) }); var ElicitationRequestParamsSchema = BaseParamsSchema.extend({ message: z.string(), requestedSchema: z.unknown() }); var ElicitationRequestSchema = RequestSchema.extend({ method: z.literal("elicitation/create"), params: ElicitationRequestParamsSchema }); var ElicitResultSchema = ResultSchema.extend({ action: z.union([ z.literal("accept"), z.literal("decline"), z.literal("cancel") ]), content: z.optional(z.record(z.string(), z.unknown())) }); // src/tool/json-rpc-message.ts var JSONRPC_VERSION = "2.0"; var JSONRPCRequestSchema = z2.object({ jsonrpc: z2.literal(JSONRPC_VERSION), id: z2.union([z2.string(), z2.number().int()]) }).merge(RequestSchema).strict(); var JSONRPCResponseSchema = z2.object({ jsonrpc: z2.literal(JSONRPC_VERSION), id: z2.union([z2.string(), z2.number().int()]), result: ResultSchema }).strict(); var JSONRPCErrorSchema = z2.object({ jsonrpc: z2.literal(JSONRPC_VERSION), id: z2.union([z2.string(), z2.number().int()]), error: z2.object({ code: z2.number().int(), message: z2.string(), data: z2.optional(z2.unknown()) }) }).strict(); var JSONRPCNotificationSchema = z2.object({ jsonrpc: z2.literal(JSONRPC_VERSION) }).merge( z2.object({ method: z2.string(), params: z2.optional(BaseParamsSchema) }) ).strict(); var JSONRPCMessageSchema = z2.union([ JSONRPCRequestSchema, JSONRPCNotificationSchema, JSONRPCResponseSchema, JSONRPCErrorSchema ]); async function parseJSONRPCMessage(text) { return JSONRPCMessageSchema.parse(await parseJSON({ text })); } // src/version.ts var VERSION = typeof __PACKAGE_VERSION__ !== "undefined" ? __PACKAGE_VERSION__ : "0.0.0-test"; // src/tool/oauth.ts import pkceChallenge from "pkce-challenge"; // src/tool/oauth-types.ts import { z as z3 } from "zod/v4"; var OAuthTokensSchema = z3.object({ access_token: z3.string(), id_token: z3.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect token_type: z3.string(), expires_in: z3.number().optional(), scope: z3.string().optional(), refresh_token: z3.string().optional() }).strip(); var SafeUrlSchema = z3.string().url().superRefine((val, ctx) => { if (!URL.canParse(val)) { ctx.addIssue({ code: z3.ZodIssueCode.custom, message: "URL must be parseable", fatal: true }); return z3.NEVER; } }).refine( (url) => { const u = new URL(url); return u.protocol !== "javascript:" && u.protocol !== "data:" && u.protocol !== "vbscript:"; }, { message: "URL cannot use javascript:, data:, or vbscript: scheme" } ); var OAuthProtectedResourceMetadataSchema = z3.object({ resource: z3.string().url(), authorization_servers: z3.array(SafeUrlSchema).optional(), jwks_uri: z3.string().url().optional(), scopes_supported: z3.array(z3.string()).optional(), bearer_methods_supported: z3.array(z3.string()).optional(), resource_signing_alg_values_supported: z3.array(z3.string()).optional(), resource_name: z3.string().optional(), resource_documentation: z3.string().optional(), resource_policy_uri: z3.string().url().optional(), resource_tos_uri: z3.string().url().optional(), tls_client_certificate_bound_access_tokens: z3.boolean().optional(), authorization_details_types_supported: z3.array(z3.string()).optional(), dpop_signing_alg_values_supported: z3.array(z3.string()).optional(), dpop_bound_access_tokens_required: z3.boolean().optional() }).passthrough(); var OAuthMetadataSchema = z3.object({ issuer: z3.string(), authorization_endpoint: SafeUrlSchema, token_endpoint: SafeUrlSchema, registration_endpoint: SafeUrlSchema.optional(), scopes_supported: z3.array(z3.string()).optional(), response_types_supported: z3.array(z3.string()), grant_types_supported: z3.array(z3.string()).optional(), code_challenge_methods_supported: z3.array(z3.string()), token_endpoint_auth_methods_supported: z3.array(z3.string()).optional(), token_endpoint_auth_signing_alg_values_supported: z3.array(z3.string()).optional() }).passthrough(); var OpenIdProviderMetadataSchema = z3.object({ issuer: z3.string(), authorization_endpoint: SafeUrlSchema, token_endpoint: SafeUrlSchema, userinfo_endpoint: SafeUrlSchema.optional(), jwks_uri: SafeUrlSchema, registration_endpoint: SafeUrlSchema.optional(), scopes_supported: z3.array(z3.string()).optional(), response_types_supported: z3.array(z3.string()), grant_types_supported: z3.array(z3.string()).optional(), subject_types_supported: z3.array(z3.string()), id_token_signing_alg_values_supported: z3.array(z3.string()), claims_supported: z3.array(z3.string()).optional(), token_endpoint_auth_methods_supported: z3.array(z3.string()).optional() }).passthrough(); var OpenIdProviderDiscoveryMetadataSchema = OpenIdProviderMetadataSchema.merge( OAuthMetadataSchema.pick({ code_challenge_methods_supported: true }) ); var OAuthClientInformationSchema = z3.object({ client_id: z3.string(), client_secret: z3.string().optional(), client_id_issued_at: z3.number().optional(), client_secret_expires_at: z3.number().optional() }).strip(); var OAuthClientMetadataSchema = z3.object({ redirect_uris: z3.array(SafeUrlSchema), token_endpoint_auth_method: z3.string().optional(), grant_types: z3.array(z3.string()).optional(), response_types: z3.array(z3.string()).optional(), client_name: z3.string().optional(), client_uri: SafeUrlSchema.optional(), logo_uri: SafeUrlSchema.optional(), scope: z3.string().optional(), contacts: z3.array(z3.string()).optional(), tos_uri: SafeUrlSchema.optional(), policy_uri: z3.string().optional(), jwks_uri: SafeUrlSchema.optional(), jwks: z3.any().optional(), software_id: z3.string().optional(), software_version: z3.string().optional(), software_statement: z3.string().optional() }).strip(); var OAuthErrorResponseSchema = z3.object({ error: z3.string(), error_description: z3.string().optional(), error_uri: z3.string().optional() }); var OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge( OAuthClientInformationSchema ); // src/error/oauth-error.ts import { AISDKError as AISDKError2 } from "@ai-sdk/provider"; var name2 = "AI_MCPClientOAuthError"; var marker2 = `vercel.ai.error.${name2}`; var symbol2 = Symbol.for(marker2); var _a2, _b2; var MCPClientOAuthError = class extends (_b2 = AISDKError2, _a2 = symbol2, _b2) { constructor({ name: name3 = "MCPClientOAuthError", message, cause }) { super({ name: name3, message, cause }); this[_a2] = true; } static isInstance(error) { return AISDKError2.hasMarker(error, marker2); } }; var ServerError = class extends MCPClientOAuthError { }; ServerError.errorCode = "server_error"; var InvalidClientError = class extends MCPClientOAuthError { }; InvalidClientError.errorCode = "invalid_client"; var InvalidGrantError = class extends MCPClientOAuthError { }; InvalidGrantError.errorCode = "invalid_grant"; var UnauthorizedClientError = class extends MCPClientOAuthError { }; UnauthorizedClientError.errorCode = "unauthorized_client"; var OAUTH_ERRORS = { [ServerError.errorCode]: ServerError, [InvalidClientError.errorCode]: InvalidClientError, [InvalidGrantError.errorCode]: InvalidGrantError, [UnauthorizedClientError.errorCode]: UnauthorizedClientError }; // src/util/oauth-util.ts function resourceUrlFromServerUrl(url) { const resourceURL = typeof url === "string" ? new URL(url) : new URL(url.href); resourceURL.hash = ""; return resourceURL; } function resourceUrlStripSlash(resource) { const href = resource.href; if (resource.pathname === "/" && href.endsWith("/")) { return href.slice(0, -1); } return href; } 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); } // src/tool/oauth.ts import { parseJSON as parseJSON2 } from "@ai-sdk/provider-utils"; var UnauthorizedError = class extends Error { constructor(message = "Unauthorized") { super(message); this.name = "UnauthorizedError"; } }; function extractResourceMetadataUrl(response) { var _a3; const header = (_a3 = response.headers.get("www-authenticate")) != null ? _a3 : response.headers.get("WWW-Authenticate"); if (!header) { return void 0; } const [type, scheme] = header.split(" "); if (type.toLowerCase() !== "bearer" || !scheme) { return void 0; } const regex = /resource_metadata="([^"]*)"/; const match = header.match(regex); if (!match) { return void 0; } try { return new URL(match[1]); } catch (e) { return void 0; } } 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) { try { return await fetchFn(url, { headers }); } catch (error) { if (error instanceof TypeError) { if (headers) { return fetchWithCorsRetry(url, void 0, fetchFn); } else { return void 0; } } throw error; } } async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) { const headers = { "MCP-Protocol-Version": protocolVersion }; return await fetchWithCorsRetry(url, headers, fetchFn); } function shouldAttemptFallback(response, pathname) { return !response || response.status >= 400 && response.status < 500 && pathname !== "/"; } async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, opts) { var _a3, _b3; const issuer = new URL(serverUrl); const protocolVersion = (_a3 = opts == null ? void 0 : opts.protocolVersion) != null ? _a3 : LATEST_PROTOCOL_VERSION; let url; if (opts == null ? void 0 : opts.metadataUrl) { url = new URL(opts.metadataUrl); } else { const wellKnownPath = buildWellKnownPath(wellKnownType, issuer.pathname); url = new URL(wellKnownPath, (_b3 = opts == null ? void 0 : opts.metadataServerUrl) != null ? _b3 : issuer); url.search = issuer.search; } let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn); if (!(opts == null ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) { const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer); response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn); } return response; } async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn = fetch) { const response = await discoverMetadataWithFallback( serverUrl, "oauth-protected-resource", fetchFn, { protocolVersion: opts == null ? void 0 : opts.protocolVersion, metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl } ); 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()); } function buildDiscoveryUrls(authorizationServerUrl) { const url = typeof authorizationServerUrl === "string" ? new URL(authorizationServerUrl) : authorizationServerUrl; const hasPath = url.pathname !== "/"; const urlsToTry = []; if (!hasPath) { urlsToTry.push({ url: new URL("/.well-known/oauth-authorization-server", url.origin), type: "oauth" }); urlsToTry.push({ url: new URL("/.well-known/openid-configuration", url.origin), type: "oidc" }); return urlsToTry; } let pathname = url.pathname; if (pathname.endsWith("/")) { pathname = pathname.slice(0, -1); } urlsToTry.push({ url: new URL( `/.well-known/oauth-authorization-server${pathname}`, url.origin ), type: "oauth" }); urlsToTry.push({ url: new URL("/.well-known/oauth-authorization-server", url.origin), type: "oauth" }); urlsToTry.push({ url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin), type: "oidc" }); urlsToTry.push({ url: new URL(`${pathname}/.well-known/openid-configuration`, url.origin), type: "oidc" }); return urlsToTry; } async function discoverAuthorizationServerMetadata(authorizationServerUrl, { fetchFn = fetch, protocolVersion = LATEST_PROTOCOL_VERSION } = {}) { var _a3; const headers = { "MCP-Protocol-Version": protocolVersion }; const urlsToTry = buildDiscoveryUrls(authorizationServerUrl); for (const { url: endpointUrl, type } of urlsToTry) { const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn); if (!response) { 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") { return OAuthMetadataSchema.parse(await response.json()); } else { const metadata = OpenIdProviderDiscoveryMetadataSchema.parse( await response.json() ); if (!((_a3 = metadata.code_challenge_methods_supported) == null ? void 0 : _a3.includes("S256"))) { throw new Error( `Incompatible OIDC provider at ${endpointUrl}: does not support S256 code challenge method required by MCP specification` ); } return metadata; } } return void 0; } 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 == null ? void 0 : scope.includes("offline_access")) { authorizationUrl.searchParams.append("prompt", "consent"); } if (resource) { authorizationUrl.searchParams.set( "resource", resourceUrlStripSlash(resource) ); } return { authorizationUrl, codeVerifier }; } 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"; } 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}`); } function applyPostAuth(clientId, clientSecret, params) { params.set("client_id", clientId); if (clientSecret) { params.set("client_secret", clientSecret); } } function applyPublicAuth(clientId, params) { params.set("client_id", clientId); } async function parseErrorResponse(input) { const statusCode = input instanceof Response ? input.status : void 0; const body = input instanceof Response ? await input.text() : input; try { const result = OAuthErrorResponseSchema.parse( await parseJSON2({ text: body }) ); const { error, error_description, error_uri } = result; const errorClass = OAUTH_ERRORS[error] || ServerError; return new errorClass({ message: error_description || "", cause: error_uri }); } catch (error) { const errorMessage = `${statusCode ? `HTTP ${statusCode}: ` : ""}Invalid OAuth error response: ${error}. Raw body: ${body}`; return new ServerError({ message: errorMessage }); } } async function exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier, redirectUri, resource, addClientAuthentication, fetchFn }) { var _a3; const grantType = "authorization_code"; const tokenUrl = (metadata == null ? void 0 : metadata.token_endpoint) ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl); if ((metadata == null ? void 0 : 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 { const supportedMethods = (_a3 = metadata == null ? void 0 : metadata.token_endpoint_auth_methods_supported) != null ? _a3 : []; const authMethod = selectClientAuthMethod( clientInformation, supportedMethods ); applyClientAuthentication(authMethod, clientInformation, headers, params); } if (resource) { params.set("resource", resourceUrlStripSlash(resource)); } const response = await (fetchFn != null ? fetchFn : fetch)(tokenUrl, { method: "POST", headers, body: params }); if (!response.ok) { throw await parseErrorResponse(response); } return OAuthTokensSchema.parse(await response.json()); } async function refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken, resource, addClientAuthentication, fetchFn }) { var _a3; 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); } 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 { const supportedMethods = (_a3 = metadata == null ? void 0 : metadata.token_endpoint_auth_methods_supported) != null ? _a3 : []; const authMethod = selectClientAuthMethod( clientInformation, supportedMethods ); applyClientAuthentication(authMethod, clientInformation, headers, params); } if (resource) { params.set("resource", resourceUrlStripSlash(resource)); } const response = await (fetchFn != null ? fetchFn : fetch)(tokenUrl, { method: "POST", headers, body: params }); if (!response.ok) { throw await parseErrorResponse(response); } return OAuthTokensSchema.parse({ refresh_token: refreshToken, ...await response.json() }); } async function registerClient(authorizationServerUrl, { metadata, clientMetadata, fetchFn }) { let registrationUrl; if (metadata) { if (!metadata.registration_endpoint) { throw new Error( "Incompatible auth server: does not support dynamic client registration" ); } registrationUrl = new URL(metadata.registration_endpoint); } else { registrationUrl = new URL("/register", authorizationServerUrl); } const response = await (fetchFn != null ? fetchFn : fetch)(registrationUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(clientMetadata) }); if (!response.ok) { throw await parseErrorResponse(response); } return OAuthClientInformationFullSchema.parse(await response.json()); } async function auth(provider, options) { var _a3, _b3; try { return await authInternal(provider, options); } catch (error) { if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) { await ((_a3 = provider.invalidateCredentials) == null ? void 0 : _a3.call(provider, "all")); return await authInternal(provider, options); } else if (error instanceof InvalidGrantError) { await ((_b3 = provider.invalidateCredentials) == null ? void 0 : _b3.call(provider, "tokens")); return await authInternal(provider, options); } throw error; } } async function selectResourceURL(serverUrl, provider, resourceMetadata) { const defaultResource = resourceUrlFromServerUrl(serverUrl); if (provider.validateResourceURL) { return await provider.validateResourceURL( defaultResource, resourceMetadata == null ? void 0 : resourceMetadata.resource ); } if (!resourceMetadata) { return void 0; } 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, scope, resourceMetadataUrl, fetchFn }) { let resourceMetadata; let authorizationServerUrl; try { resourceMetadata = await discoverOAuthProtectedResourceMetadata( serverUrl, { resourceMetadataUrl }, fetchFn ); if (resourceMetadata.authorization_servers && resourceMetadata.authorization_servers.length > 0) { authorizationServerUrl = resourceMetadata.authorization_servers[0]; } } catch (e) { } if (!authorizationServerUrl) { authorizationServerUrl = serverUrl; } const resource = await selectResourceURL( serverUrl, provider, resourceMetadata ); const metadata = await discoverAuthorizationServerMetadata( authorizationServerUrl, { fetchFn } ); let clientInformation = await Promise.resolve(provider.clientInformation()); 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" ); } const fullInformation = await registerClient(authorizationServerUrl, { metadata, clientMetadata: provider.clientMetadata, fetchFn }); await provider.saveClientInformation(fullInformation); clientInformation = fullInformation; } 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 codeVerifier2 = await provider.codeVerifier(); const tokens2 = await exchangeAuthorization(authorizationServerUrl, { metadata, clientInformation, authorizationCode, codeVerifier: codeVerifier2, redirectUri: provider.redirectUrl, resource, addClientAuthentication: provider.addClientAuthentication, fetchFn }); await provider.saveTokens(tokens2); return "AUTHORIZED"; } const tokens = await provider.tokens(); if (tokens == null ? void 0 : tokens.refresh_token) { try { const newTokens = await refreshAuthorization(authorizationServerUrl, { metadata, clientInformation, refreshToken: tokens.refresh_token, resource, addClientAuthentication: provider.addClientAuthentication, fetchFn }); await provider.saveTokens(newTokens); return "AUTHORIZED"; } catch (error) { if ( // If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry. !(error instanceof MCPClientOAuthError) || error instanceof ServerError ) { } else { throw error; } } } 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: scope || provider.clientMetadata.scope, resource } ); await provider.saveCodeVerifier(codeVerifier); await provider.redirectToAuthorization(authorizationUrl); return "REDIRECT"; } // src/tool/mcp-sse-transport.ts var SseMCPTransport = class { constructor({ url, headers, authProvider, redirect = "follow", fetch: fetchFn }) { this.connected = false; this.url = new URL(url); this.headers = headers; this.authProvider = authProvider; this.redirectMode = redirect; this.fetchFn = fetchFn != null ? fetchFn : globalThis.fetch; } async commonHeaders(base) { var _a3; const headers = { ...this.headers, ...base, "mcp-protocol-version": (_a3 = this.protocolVersion) != null ? _a3 : LATEST_PROTOCOL_VERSION }; if (this.authProvider) { const tokens = await this.authProvider.tokens(); if (tokens == null ? void 0 : tokens.access_token) { headers["Authorization"] = `Bearer ${tokens.access_token}`; } } return withUserAgentSuffix( headers, `ai-sdk/${VERSION}`, getRuntimeEnvironmentUserAgent() ); } async start() { return new Promise((resolve, reject) => { if (this.connected) { return resolve(); } this.abortController = new AbortController(); const establishConnection = async (triedAuth = false) => { var _a3, _b3, _c, _d, _e; try { const headers = await this.commonHeaders({ Accept: "text/event-stream" }); const response = await this.fetchFn(this.url.href, { headers, signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal, redirect: this.redirectMode }); if (response.status === 401 && this.authProvider && !triedAuth) { this.resourceMetadataUrl = extractResourceMetadataUrl(response); try { const result = await auth(this.authProvider, { serverUrl: this.url, resourceMetadataUrl: this.resourceMetadataUrl, fetchFn: this.fetchFn }); if (result !== "AUTHORIZED") { const error = new UnauthorizedError(); (_b3 = this.onerror) == null ? void 0 : _b3.call(this, error); return reject(error); } } catch (error) { (_c = this.onerror) == null ? void 0 : _c.call(this, error); return reject(error); } return establishConnection(true); } if (!response.ok || !response.body) { let errorMessage = `MCP SSE Transport Error: ${response.status} ${response.statusText}`; if (response.status === 405) { errorMessage += ". This server does not support SSE transport. Try using `http` transport instead"; } const error = new MCPClientError({ message: errorMessage }); (_d = this.onerror) == null ? void 0 : _d.call(this, error); return reject(error); } const stream = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream()); const reader = stream.getReader(); const processEvents = async () => { var _a4, _b4, _c2; try { while (true) { const { done, value } = await reader.read(); if (done) { if (this.connected) { this.connected = false; throw new MCPClientError({ message: "MCP SSE Transport Error: Connection closed unexpectedly" }); } return; } const { event, data } = value; if (event === "endpoint") { this.endpoint = new URL(data, this.url); if (this.endpoint.origin !== this.url.origin) { throw new MCPClientError({ message: `MCP SSE Transport Error: Endpoint origin does not match connection origin: ${this.endpoint.origin}` }); } this.connected = true; resolve(); } else if (event === "message") { try { const message = await parseJSONRPCMessage(data); (_a4 = this.onmessage) == null ? void 0 : _a4.call(this, message); } catch (error) { const e = new MCPClientError({ message: "MCP SSE Transport Error: Failed to parse message", cause: error }); (_b4 = this.onerror) == null ? void 0 : _b4.call(this, e); } } } } catch (error) { if (error instanceof Error && error.name === "AbortError") { return; } (_c2 = this.onerror) == null ? void 0 : _c2.call(this, error); reject(error); } }; this.sseConnection = { close: () => reader.cancel() }; processEvents(); } catch (error) { if (error instanceof Error && error.name === "AbortError") { return; } (_e = this.onerror) == null ? void 0 : _e.call(this, error); reject(error); } }; void establishConnection(); }); } async close() { var _a3, _b3, _c; this.connected = false; (_a3 = this.sseConnection) == null ? void 0 : _a3.close(); (_b3 = this.abortController) == null ? void 0 : _b3.abort(); (_c = this.onclose) == null ? void 0 : _c.call(this); } async send(message) { if (!this.endpoint || !this.connected) { throw new MCPClientError({ message: "MCP SSE Transport Error: Not connected" }); } const endpoint = this.endpoint; const attempt = async (triedAuth = false) => { var _a3, _b3, _c, _d, _e; try { const headers = await this.commonHeaders({ "Content-Type": "application/json" }); const init = { method: "POST", headers, body: JSON.stringify(message), signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal, redirect: this.redirectMode }; const response = await this.fetchFn(endpoint.href, init); if (response.status === 401 && this.authProvider && !triedAuth) { this.resourceMetadataUrl = extractResourceMetadataUrl(response); try { const result = await auth(this.authProvider, { serverUrl: this.url, resourceMetadataUrl: this.resourceMetadataUrl, fetchFn: this.fetchFn }); if (result !== "AUTHORIZED") { const error = new UnauthorizedError(); (_b3 = this.onerror) == null ? void 0 : _b3.call(this, error); return; } } catch (error) { (_c = this.onerror) == null ? void 0 : _c.call(this, error); return; } return attempt(true); } if (!response.ok) { const text = await response.text().catch(() => null); const error = new MCPClientError({ message: `MCP SSE Transport Error: POSTing to endpoint (HTTP ${response.status}): ${text}` }); (_d = this.onerror) == null ? void 0 : _d.call(this, error); return; } } catch (error) { (_e = this.onerror) == null ? void 0 : _e.call(this, error); return; } }; await attempt(); } }; // src/tool/mcp-http-transport.ts import { EventSourceParserStream as EventSourceParserStream2, withUserAgentSuffix as withUserAgentSuffix2, getRuntimeEnvironmentUserAgent as getRuntimeEnvironmentUserAgent2 } from "@ai-sdk/provider-utils"; var HttpMCPTransport = class { constructor({ url, headers, authProvider, redirect = "follow", fetch: fetchFn }) { 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.fetchFn = fetchFn != null ? fetchFn : globalThis.fetch; } async commonHeaders(base) { var _a3; const headers = { ...this.headers, ...base, "mcp-protocol-version": (_a3 = this.protocolVersion) != null ? _a3 : LATEST_PROTOCOL_VERSION }; if (this.sessionId) { headers["mcp-session-id"] = this.sessionId; } if (this.authProvider) { const tokens = await this.authProvider.tokens(); if (tokens == null ? void 0 : tokens.access_token) { headers["Authorization"] = `Bearer ${tokens.access_token}`; } } return withUserAgentSuffix2( headers, `ai-sdk/${VERSION}`, getRuntimeEnvironmentUserAgent2() ); } /** * Runs a single OAuth recovery flow for concurrent 401 responses. */ authorizeOnce(resourceMetadataUrl) { if (!this.authProvider) { return Promise.resolve("REDIRECT"); } if (!this.authPromise) { this.authPromise = auth(this.authProvider, { serverUrl: this.url, resourceMetadataUrl, fetchFn: this.fetchFn }).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(); void this.openInboundSse(); } async close() { var _a3, _b3, _c; (_a3 = this.inboundSseConnection) == null ? void 0 : _a3.close(); try { if (this.sessionId && this.abortController && !this.abortController.signal.aborted) { const headers = await this.commonHeaders({}); await this.fetchFn(this.url.href, { method: "DELETE", headers, signal: this.abortController.signal, redirect: this.redirectMode }).catch(() => void 0); } } catch (e) { } (_b3 = this.abortController) == null ? void 0 : _b3.abort(); (_c = this.onclose) == null ? void 0 : _c.call(this); } async send(message) { const attempt = async (triedAuth = false) => { var _a3, _b3, _c, _d, _e, _f, _g; try { const headers = await this.commonHeaders({ "Content-Type": "application/json", Accept: "application/json, text/event-stream" }); const init = { method: "POST", headers, body: JSON.stringify(message), signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal, redirect: this.redirectMode }; const response = await this.fetchFn(this.url.href, init); const sessionId = response.headers.get("mcp-session-id"); if (sessionId) { this.sessionId = sessionId; } if (response.status === 401 && this.authProvider && !triedAuth) { this.resourceMetadataUrl = extractResourceMetadataUrl(response); try { const result = await this.authorizeOnce(this.resourceMetadataUrl); if (result !== "AUTHORIZED") { const error2 = new UnauthorizedError(); throw error2; } } catch (error2) { (_b3 = this.onerror) == null ? void 0 : _b3.call(this, error2); throw error2; } return attempt(true); } if (response.status === 202) { if (!this.inboundSseConnection) { void this.openInboundSse(); } return; } if (!response.ok) { const text = await response.text().catch(() => null); let errorMessage = `MCP HTTP Transport Error: POSTing to endpoint (HTTP ${response.status}): ${text}`; if (response.status === 404) { errorMessage += ". This server does not support HTTP transport. Try using `sse` transport instead"; } const error2 = new MCPClientError({ message: errorMessage, statusCode: response.status, url: this.url.href, responseBody: text != null ? text : void 0 }); (_c = this.onerror) == null ? void 0 : _c.call(this, error2); throw error2; } const isNotification = !("id" in message); if (isNotification) { 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((m) => JSONRPCMessageSchema.parse(m)) : [JSONRPCMessageSchema.parse(data)]; for (const m of messages) (_d = this.onmessage) == null ? void 0 : _d.call(this, m); return; } if (contentType.includes("text/event-stream")) { if (!response.body) { const error2 = new MCPClientError({ message: "MCP HTTP Transport Error: text/event-stream response without body", statusCode: response.status, url: this.url.href }); (_e = this.onerror) == null ? void 0 : _e.call(this, error2); throw error2; } const stream = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream2()); const reader = stream.getReader(); const processEvents = async () => { var _a4, _b4, _c2; try { while (true) { const { done, value } = await reader.read(); if (done) return; const { event, data } = value; if (event === "message") { try { const msg = await parseJSONRPCMessage(data); (_a4 = this.onmessage) == null ? void 0 : _a4.call(this, msg); } catch (error2) { const e = new MCPClientError({ message: "MCP HTTP Transport Error: Failed to parse message", cause: error2 }); (_b4 = this.onerror) == null ? void 0 : _b4.call(this, e); } } } } catch (error2) { if (error2 instanceof Error && error2.name === "AbortError") { return; } (_c2 = this.onerror) == null ? void 0 : _c2.call(this, error2); } }; processEvents(); return; } const error = new MCPClientError({ message: `MCP HTTP Transport Error: Unexpected content type: ${contentType}`, statusCode: response.status, url: this.url.href }); (_f = this.onerror) == null ? void 0 : _f.call(this, error); throw error; } catch (error) { (_g = this.onerror) == null ? void 0 : _g.call(this, error); throw error; } }; await attempt(); } getNextReconnectionDelay(attempt) { const { initialReconnectionDelay,