@ai-sdk/mcp
Version:
462 lines (461 loc) • 14.6 kB
JavaScript
import { parseJSON } from "@ai-sdk/provider-utils";
import { z } from "zod/v4";
import { AISDKError } from "@ai-sdk/provider";
import spawn from "cross-spawn";
//#region src/tool/types.ts
/** 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();
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")]))
});
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();
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();
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);
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();
ResultSchema.extend({ resourceTemplates: z.array(ResourceTemplateSchema) });
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())
});
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();
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();
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
});
ResultSchema.extend({
action: z.union([
z.literal("accept"),
z.literal("decline"),
z.literal("cancel")
]),
content: z.optional(z.record(z.string(), z.unknown()))
});
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" });
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 = `vercel.ai.error.AI_MCPClientError`;
const symbol = Symbol.for(marker);
/**
* 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] = true;
this.data = data;
this.code = code;
this.statusCode = statusCode;
this.url = url;
this.responseBody = responseBody;
}
static isInstance(error) {
return AISDKError.hasMarker(error, marker);
}
};
//#endregion
//#region src/tool/mcp-stdio/get-environment.ts
/**
* Constructs the environment variables for the child process.
*
* @param customEnv - Custom environment variables to merge with default environment variables.
* @returns The environment variables for the child process.
*/
function getEnvironment(customEnv) {
const isWindows = globalThis.process.platform === "win32";
const DEFAULT_INHERITED_ENV_VARS = isWindows ? [
"APPDATA",
"HOMEDRIVE",
"HOMEPATH",
"LOCALAPPDATA",
"PATH",
"PROCESSOR_ARCHITECTURE",
"SYSTEMDRIVE",
"SYSTEMROOT",
"TEMP",
"USERNAME",
"USERPROFILE"
] : [
"HOME",
"LOGNAME",
"PATH",
"SHELL",
"TERM",
"USER"
];
const env = customEnv ? { ...customEnv } : {};
const customEnvKeys = new Set(Object.keys(env).map((key) => isWindows ? key.toUpperCase() : key));
for (const key of DEFAULT_INHERITED_ENV_VARS) {
if (customEnvKeys.has(key)) continue;
const value = globalThis.process.env[key];
if (value === void 0) continue;
if (value.startsWith("()")) continue;
env[key] = value;
}
return env;
}
//#endregion
//#region src/tool/mcp-stdio/create-child-process.ts
function createChildProcess(config, signal) {
if (globalThis.process.platform === "win32" && [config.command, ...config.args ?? []].some((value) => /[\r\n]/.test(value))) throw new TypeError("Stdio MCP commands and arguments must not contain line breaks on Windows.");
return spawn(config.command, config.args ?? [], {
env: getEnvironment(config.env),
stdio: [
"pipe",
"pipe",
config.stderr ?? "inherit"
],
shell: config.shell ?? false,
signal,
windowsHide: globalThis.process.platform === "win32" && isElectron(),
cwd: config.cwd
});
}
function isElectron() {
return "type" in globalThis.process;
}
//#endregion
//#region src/tool/mcp-stdio/mcp-stdio-transport.ts
var StdioMCPTransport = class {
constructor(server) {
this.supportsProtocolVersionDiscovery = true;
this.abortController = new AbortController();
this.readBuffer = new ReadBuffer();
this.serverParams = server;
}
async start() {
if (this.process) throw new MCPClientError({ message: "StdioMCPTransport already started." });
return new Promise((resolve, reject) => {
try {
const process = createChildProcess(this.serverParams, this.abortController.signal);
this.process = process;
this.process.on("error", (error) => {
if (error.name === "AbortError") {
this.onclose?.();
return;
}
reject(error);
this.onerror?.(error);
});
this.process.on("spawn", () => {
resolve();
});
this.process.on("close", (_code) => {
this.process = void 0;
this.onclose?.();
});
this.process.stdin?.on("error", (error) => {
this.onerror?.(error);
});
this.process.stdout?.on("data", (chunk) => {
this.readBuffer.append(chunk);
this.processReadBuffer();
});
this.process.stdout?.on("error", (error) => {
this.onerror?.(error);
});
} catch (error) {
reject(error);
this.onerror?.(error);
}
});
}
async processReadBuffer() {
while (true) {
const line = this.readBuffer.readLine();
if (line === null) break;
try {
const message = await deserializeMessage(line);
this.onmessage?.(message);
} catch (error) {
this.onerror?.(error);
}
}
}
async close() {
this.abortController.abort();
this.process = void 0;
this.readBuffer.clear();
}
send(message) {
return new Promise((resolve) => {
if (!this.process?.stdin) throw new MCPClientError({ message: "StdioClientTransport not connected" });
const json = serializeMessage(message);
if (this.process.stdin.write(json)) resolve();
else this.process.stdin.once("drain", resolve);
});
}
};
var ReadBuffer = class {
append(chunk) {
this.buffer = this.buffer ? Buffer.concat([this.buffer, chunk]) : chunk;
}
readLine() {
if (!this.buffer) return null;
const index = this.buffer.indexOf("\n");
if (index === -1) return null;
const line = this.buffer.toString("utf8", 0, index);
this.buffer = this.buffer.subarray(index + 1);
return line;
}
clear() {
this.buffer = void 0;
}
};
function serializeMessage(message) {
return JSON.stringify(message) + "\n";
}
async function deserializeMessage(line) {
return parseJSONRPCMessage(line);
}
//#endregion
//#region src/tool/mcp-stdio/index.ts
/**
* @deprecated Use `StdioMCPTransport` instead.
*/
const Experimental_StdioMCPTransport = StdioMCPTransport;
//#endregion
export { Experimental_StdioMCPTransport, StdioMCPTransport };
//# sourceMappingURL=index.js.map