@ai-sdk/mcp
Version:
499 lines (451 loc) • 14 kB
text/typescript
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') }),
}),
});