import { approvalPolicy } from "./capability-policy.ts"; import { z } from "zod"; export const MAX_MCP_CAPABILITIES = 500; export const MAX_MCP_CATALOG_BYTES = 256 * 1024; export const MAX_MCP_SCHEMA_BYTES = 32 * 1024; const name = z .string() .min(1) .max(200) .refine((value) => value.trim().length > 0); const fingerprint = z.string().regex(/^[0-9a-f]{64}$/); const schema = z .record(z.string(), z.json()) .refine( (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength <= MAX_MCP_SCHEMA_BYTES, ); // Discovery data and local choices share stable identities, but only local // writes own choices. A semantic change requires a new decision. export const mcpCapability = z.strictObject({ kind: z.enum(["tool", "resource", "prompt"]), name, description: z.string().max(4000).optional(), title: name.optional(), annotations: z .record(z.string(), z.json()) .refine( (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength <= 2048, ) .optional(), uri: z.string().max(2048).optional(), mimeType: z.string().max(200).optional(), id: fingerprint.optional(), serverId: z .string() .regex(/^mcp-[0-9a-f-]{36}$/) .optional(), fingerprint: fingerprint.optional(), inputSchema: schema.optional(), outputSchema: schema.optional(), arguments: z .array( z.strictObject({ name, description: z.string().max(4000).optional(), required: z.boolean().optional(), }), ) .max(100) .optional(), issue: z.literal("invalid_schema").optional(), settings: z .strictObject({ enabled: z.boolean(), approval: approvalPolicy.optional(), }) .optional(), }); export const mcpCatalog = z .array(mcpCapability) .max(MAX_MCP_CAPABILITIES) .refine( (items) => new Set(items.map((item) => `${item.kind}:${item.uri ?? item.name}`)) .size === items.length, ) .refine( (items) => new TextEncoder().encode(JSON.stringify(items)).byteLength <= MAX_MCP_CATALOG_BYTES, ); export const mcpCatalogRefresh = z.strictObject({ added: z.array(fingerprint).max(MAX_MCP_CAPABILITIES), removed: z.array(fingerprint).max(MAX_MCP_CAPABILITIES), changed: z.array(fingerprint).max(MAX_MCP_CAPABILITIES), omitted: z.number().int().nonnegative(), invalid: z.number().int().nonnegative(), }); export type McpCapability = z.infer; export type McpCatalogRefresh = z.infer;