@ai-sdk/mcp
Version:
152 lines (138 loc) • 4.85 kB
text/typescript
import type { JSONObject } from '@ai-sdk/provider';
import type { MCPEventAdapter } from './mcp-events-adapter';
import { z } from 'zod/v4';
import { ResultSchema, type RequestOptions } from './types';
const JsonSchemaSchema = z.union([
z.boolean(),
z.record(z.string(), z.unknown()),
]);
export const MCPEventDefinitionSchema = z.looseObject({
name: z.string().min(1),
description: z.string().nullish(),
delivery: z.array(z.enum(['webhook', 'poll', 'push'])).min(1),
inputSchema: JsonSchemaSchema,
payloadSchema: JsonSchemaSchema,
});
export type MCPEventDefinition = z.infer<typeof MCPEventDefinitionSchema>;
export const ListEventsResultSchema = ResultSchema.extend({
events: z.array(MCPEventDefinitionSchema),
nextCursor: z.string().nullish(),
});
export type ListEventsResult = z.infer<typeof ListEventsResultSchema>;
export const SubscribeEventResultSchema = ResultSchema.extend({
id: z.string().min(1),
refreshBefore: z.iso.datetime({ offset: true }).nullable(),
cursor: z
.string()
.nullish()
.transform(cursor => cursor ?? null),
truncated: z.boolean(),
});
export type SubscribeEventResult = z.infer<typeof SubscribeEventResultSchema>;
export const MCPEventSchema = z.looseObject({
eventId: z.string().min(1),
name: z.string().min(1),
timestamp: z.iso.datetime({ offset: true }),
data: z.record(z.string(), z.unknown()),
cursor: z.string().nullish(),
});
export type MCPEvent = z.infer<typeof MCPEventSchema>;
export const MCPEventControlSchema = z
.discriminatedUnion('type', [
z.looseObject({ type: z.literal('gap'), cursor: z.string() }),
z.looseObject({
type: z.literal('terminated'),
error: z.looseObject({
code: z.number().int(),
message: z.string(),
data: z.unknown().nullish(),
}),
}),
])
.transform(control =>
control.type === 'gap' ? { ...control, truncated: true as const } : control,
);
export type MCPEventControl = z.infer<typeof MCPEventControlSchema>;
/** Persisted privately by the application, including the signing secret. */
export type MCPEventSubscription = {
key: string;
id?: string;
name: string;
arguments: JSONObject;
definition: MCPEventDefinition;
delivery: { mode: 'webhook'; url: string; secret: string };
refreshBefore?: string | null;
cursor: string | null;
truncated: boolean;
ttlMs?: number | null;
maxAgeMs?: number;
status: 'pending' | 'active';
};
/** Subscription metadata safe to pass to event handlers; excludes the secret. */
export type MCPEventSubscriptionInfo = {
id: string;
name: string;
arguments: JSONObject;
delivery: { mode: 'webhook'; url: string };
refreshBefore: string | null;
};
/**
* Use durable, private storage scoped to one MCP server and authenticated
* principal. update must atomically merge only the supplied fields. Never
* expose these records to a browser: delivery.secret authenticates webhooks.
*/
export interface MCPEventStore {
get(key: string): Promise<MCPEventSubscription | undefined>;
getById(id: string): Promise<MCPEventSubscription | undefined>;
set(subscription: MCPEventSubscription): Promise<void>;
update(key: string, patch: Partial<MCPEventSubscription>): Promise<void>;
delete(key: string): Promise<void>;
}
export type MCPEventsConfig =
| {
store: MCPEventStore;
adapter?: never;
/**
* Optionally validate subscription arguments using an application schema or
* the server's inputSchema. Throw to reject before persisting or subscribing.
*/
validateArguments?: (args: {
definition: MCPEventDefinition;
arguments: JSONObject;
}) => void | PromiseLike<void>;
}
| {
adapter: MCPEventAdapter;
store?: never;
validateArguments?: never;
};
export type SubscribeEventOptions = {
name: string;
arguments?: JSONObject;
delivery: {
mode: 'webhook';
/** HTTPS callback served by createMCPEventWebhook before subscribing. */
url: string;
/** Development only: allow HTTP callbacks to literal loopback addresses. */
allowInsecureLocalhost?: boolean;
/** Generated securely when omitted; persisted before the subscribe request. */
secret?: string;
};
cursor?: string | null;
ttlMs?: number | null;
maxAgeMs?: number;
options?: RequestOptions;
};
type MCPEventSubscriptionOptions = (
| { id: string; key?: never }
| { key: string; id?: never }
) & { options?: RequestOptions };
export interface MCPEvents {
list(options?: {
params?: { cursor?: string };
options?: RequestOptions;
}): Promise<ListEventsResult>;
subscribe(options: SubscribeEventOptions): Promise<SubscribeEventResult>;
refresh(options: MCPEventSubscriptionOptions): Promise<SubscribeEventResult>;
unsubscribe(options: MCPEventSubscriptionOptions): Promise<void>;
}