UNPKG

@ai-sdk/mcp

Version:
462 lines (461 loc) • 14.6 kB
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