@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
JavaScript
// 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,