UNPKG

@ai-sdk/mcp

Version:
499 lines (451 loc) • 14 kB
import { z } from 'zod/v4'; import type { JSONObject } from '@ai-sdk/provider'; import type { FlexibleSchema, Tool, ToolExecutionOptions, } from '@ai-sdk/provider-utils'; export const LATEST_PROTOCOL_VERSION = '2026-07-28'; export const LATEST_LEGACY_PROTOCOL_VERSION = '2025-11-25'; export const SUPPORTED_PROTOCOL_VERSIONS = [ LATEST_PROTOCOL_VERSION, LATEST_LEGACY_PROTOCOL_VERSION, '2025-06-18', '2025-03-26', '2024-11-05', ]; export type McpProviderMetadata = { clientName?: string; title?: string; toolName?: string; annotations?: McpToolAnnotations; app?: JSONObject; }; /** * Behavioral hints reported by an MCP server for a tool. * * These annotations are untrusted unless the server itself is trusted. * * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#toolannotations */ export type McpToolAnnotations = { title?: string; readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; }; /** MCP tool metadata - keys should follow MCP _meta key format specification */ const ToolMetaSchema = z.optional(z.record(z.string(), z.unknown())); export type ToolMeta = z.infer<typeof ToolMetaSchema>; export type ToolSchemas = | Record< string, { inputSchema: FlexibleSchema<JSONObject | unknown>; outputSchema?: FlexibleSchema<JSONObject | unknown>; } > | 'automatic' | undefined; /** Base MCP tool type with execute and _meta */ type McpToolBase<INPUT = unknown, OUTPUT = CallToolResult> = Tool< INPUT, OUTPUT > & { execute: ( input: INPUT, options?: ToolExecutionOptions<{}>, ) => ReturnType<NonNullable<Tool<INPUT, OUTPUT>['execute']>>; _meta?: ToolMeta; }; export type McpToolSet<TOOL_SCHEMAS extends ToolSchemas = 'automatic'> = TOOL_SCHEMAS extends Record< string, { inputSchema: FlexibleSchema<any>; outputSchema?: FlexibleSchema<any> } > ? { [K in keyof TOOL_SCHEMAS]: TOOL_SCHEMAS[K] extends { inputSchema: FlexibleSchema<infer INPUT>; outputSchema: FlexibleSchema<infer OUTPUT>; } ? McpToolBase<INPUT, OUTPUT> : TOOL_SCHEMAS[K] extends { inputSchema: FlexibleSchema<infer INPUT>; } ? McpToolBase<INPUT, CallToolResult> : never; } : Record<string, McpToolBase<unknown, CallToolResult>>; const ClientOrServerImplementationSchema = z.looseObject({ name: z.string(), version: z.string(), title: z.optional(z.string()), }); // Maps to `Implementation` in the MCP specification export type Configuration = z.infer<typeof ClientOrServerImplementationSchema>; export const BaseParamsSchema = z.looseObject({ _meta: z.optional(z.object({}).loose()), }); type BaseParams = z.infer<typeof BaseParamsSchema>; export const ResultSchema = BaseParamsSchema.extend({ resultType: z.optional(z.string()), }); export const RequestSchema = z.object({ method: z.string(), params: z.optional(BaseParamsSchema), }); export type Request = z.infer<typeof RequestSchema>; export type RequestOptions = { signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number; }; export type Notification = z.infer<typeof RequestSchema>; /** @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), }); export type ServerCapabilities = z.infer<typeof ServerCapabilitiesSchema>; export const ClientCapabilitiesSchema = z .object({ elicitation: z.optional(ElicitationCapabilitySchema), }) .loose(); export type ClientCapabilities = z.infer<typeof ClientCapabilitiesSchema>; export type ElicitationCapability = z.infer<typeof ElicitationCapabilitySchema>; export const DiscoverResultSchema = ResultSchema.extend({ supportedVersions: z.array(z.string()), capabilities: ServerCapabilitiesSchema, instructions: z.optional(z.string()), ttlMs: z.optional(z.number()), cacheScope: z.optional(z.union([z.literal('public'), z.literal('private')])), }); export type DiscoverResult = z.infer<typeof DiscoverResultSchema>; export const InitializeResultSchema = ResultSchema.extend({ protocolVersion: z.string(), capabilities: ServerCapabilitiesSchema, serverInfo: ClientOrServerImplementationSchema, instructions: z.optional(z.string()), }); export type InitializeResult = z.infer<typeof InitializeResultSchema>; export type PaginatedRequest = Request & { params?: BaseParams & { cursor?: 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(); export type MCPTool = z.infer<typeof ToolSchema>; export const ListToolsResultSchema = PaginatedResultSchema.extend({ tools: z.array(ToolSchema), }); export type ListToolsResult = z.infer<typeof ListToolsResultSchema>; 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(); export 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(); export type MCPResource = z.infer<typeof ResourceSchema>; export const ListResourcesResultSchema = PaginatedResultSchema.extend({ resources: z.array(ResourceSchema), }); export type ListResourcesResult = z.infer<typeof ListResourcesResultSchema>; 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): z.infer<typeof CallToolResultWithContentSchema> => ({ ...result, content: [ { type: 'text', text: JSON.stringify(result.structuredContent), }, ], }), ) .pipe(CallToolResultWithContentSchema); export const CallToolResultSchema = CallToolResultWithContentSchema.or( CallToolResultWithStructuredContentSchema, ).or( ResultSchema.extend({ toolResult: z.unknown().nonoptional(), }), ); export type CallToolResult = z.infer<typeof CallToolResultSchema>; 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(); export const ListResourceTemplatesResultSchema = ResultSchema.extend({ resourceTemplates: z.array(ResourceTemplateSchema), }); export type ListResourceTemplatesResult = z.infer< typeof ListResourceTemplatesResultSchema >; export const ReadResourceResultSchema = ResultSchema.extend({ contents: z.array( z.union([TextResourceContentsSchema, BlobResourceContentsSchema]), ), }); export type ReadResourceResult = z.infer<typeof ReadResourceResultSchema>; // Completions 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(); export const CompleteRequestParamsSchema = BaseParamsSchema.extend({ ref: z.union([PromptReferenceSchema, ResourceReferenceSchema]), argument: CompletionArgumentSchema, context: z.optional( z .object({ arguments: z.record(z.string(), z.string()), }) .loose(), ), }); export type CompleteRequestParams = z.infer<typeof CompleteRequestParamsSchema>; export const CompleteResultSchema = ResultSchema.extend({ completion: z .object({ values: z.array(z.string()).max(100), total: z.optional(z.number().int()), hasMore: z.optional(z.boolean()), }) .loose(), }); export type CompleteResult = z.infer<typeof CompleteResultSchema>; // Prompts const PromptArgumentSchema = z .object({ name: z.string(), description: z.optional(z.string()), required: z.optional(z.boolean()), }) .loose(); export const PromptSchema = z .object({ name: z.string(), title: z.optional(z.string()), description: z.optional(z.string()), arguments: z.optional(z.array(PromptArgumentSchema)), }) .loose(); export type MCPPrompt = z.infer<typeof PromptSchema>; export const ListPromptsResultSchema = PaginatedResultSchema.extend({ prompts: z.array(PromptSchema), }); export type ListPromptsResult = z.infer<typeof ListPromptsResultSchema>; const PromptMessageSchema = z .object({ role: z.union([z.literal('user'), z.literal('assistant')]), content: z.union([ TextContentSchema, ImageContentSchema, EmbeddedResourceSchema, ResourceLinkContentSchema, ]), }) .loose(); export type MCPPromptMessage = z.infer<typeof PromptMessageSchema>; export const GetPromptResultSchema = ResultSchema.extend({ description: z.optional(z.string()), messages: z.array(PromptMessageSchema), }); export type GetPromptResult = z.infer<typeof GetPromptResultSchema>; const ElicitationRequestParamsSchema = BaseParamsSchema.extend({ message: z.string(), requestedSchema: z.unknown(), }); export const ElicitationRequestSchema = RequestSchema.extend({ method: z.literal('elicitation/create'), params: ElicitationRequestParamsSchema, }); export type ElicitationRequest = z.infer<typeof ElicitationRequestSchema>; export const ElicitResultSchema = ResultSchema.extend({ action: z.union([ z.literal('accept'), z.literal('decline'), z.literal('cancel'), ]), content: z.optional(z.record(z.string(), z.unknown())), }); export type ElicitResult = z.infer<typeof ElicitResultSchema>; export const InputRequiredResultSchema = ResultSchema.extend({ resultType: z.literal('input_required'), inputRequests: z.record(z.string(), RequestSchema).optional(), requestState: z.string().optional(), }).refine( result => result.inputRequests !== undefined || result.requestState !== undefined, { message: 'input_required must include inputRequests or requestState' }, ); // Only form elicitation is supported by the client's existing handler API. export const InputRequiredElicitationSchema = ElicitationRequestSchema.extend({ params: ElicitationRequestParamsSchema.extend({ mode: z.literal('form').optional(), requestedSchema: z.looseObject({ type: z.literal('object') }), }), });