import { z } from 'zod'; /** * The MCP tool table (issue #105): names, English descriptions, and input * schemas — the JSON Schema served on tools/list is written by hand next * to the Zod schema that actually validates calls. Kept as plain data (and * validated in mcp.service.ts, not through the SDK's typed helpers): the * SDK's generic tool registration blows up TypeScript's inference when * combined with a program this size. */ const pond = z.string().min(1); const page = z.string().min(1); export const MCP_TOOL_INPUTS = { list_ponds: z.object({}), list_pages: z.object({ pond }), read_page: z.object({ pond, page }), search: z.object({ query: z.string().min(1), pond: pond.optional(), label: z.string().min(1).optional(), }), create_page: z.object({ pond, title: z.string().min(1).max(200), markdown: z.string().default(''), }), update_page: z .object({ pond, page, title: z.string().min(1).max(200).optional(), markdown: z.string().optional(), }) .refine((input) => input.title !== undefined || input.markdown !== undefined, { message: 'title or markdown required', }), add_comment: z.object({ pond, page, text: z.string().min(1) }), list_labels: z.object({ pond }), set_page_labels: z.object({ pond, page, labelIds: z.array(z.string()) }), export_pond: z.object({ pond }), } as const; export type McpToolName = keyof typeof MCP_TOOL_INPUTS; const pondProp = { type: 'string', description: 'Pond slug (from list_ponds)' } as const; const pageProp = { type: 'string', description: 'Page slug (from list_pages or search)' } as const; /** What tools/list advertises; must stay in sync with the Zod table above. */ export const MCP_TOOL_DEFINITIONS: { name: McpToolName; description: string; inputSchema: object; }[] = [ { name: 'list_ponds', description: 'List the ponds (workspaces) this token can reach through MCP. ' + 'Returns slugs — every other tool takes a pond slug.', inputSchema: { type: 'object', properties: {} }, }, { name: 'list_pages', description: 'List the readable pages of a pond: slug, title, labels, timestamps.', inputSchema: { type: 'object', properties: { pond: pondProp }, required: ['pond'] }, }, { name: 'read_page', description: 'Read one page as Markdown plus metadata (title, labels, timestamps).', inputSchema: { type: 'object', properties: { pond: pondProp, page: pageProp }, required: ['pond', 'page'], }, }, { name: 'search', description: 'Full-text search across the reachable ponds. Returns pond/page slugs and snippets ' + 'with matches wrapped in **…** — chain into read_page.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search terms' }, pond: { ...pondProp, description: 'Restrict to one pond (slug)' }, label: { type: 'string', description: 'Restrict to pages carrying this label id' }, }, required: ['query'], }, }, { name: 'create_page', description: 'Create a page from a title and Markdown content (requires a write-scope token and ' + 'editor rights in the pond). Returns the created page including its slug.', inputSchema: { type: 'object', properties: { pond: pondProp, title: { type: 'string', maxLength: 200 }, markdown: { type: 'string', description: 'Initial content as Markdown' }, }, required: ['pond', 'title'], }, }, { name: 'update_page', description: 'Update a page: set a new title and/or REPLACE the whole content with the given ' + 'Markdown (write scope required). The change is applied through the live ' + 'collaborative document, so open editors converge; the previous state stays in the ' + 'version history.', inputSchema: { type: 'object', properties: { pond: pondProp, page: pageProp, title: { type: 'string', maxLength: 200 }, markdown: { type: 'string', description: 'Replacement content as Markdown' }, }, required: ['pond', 'page'], }, }, { name: 'add_comment', description: "Comment on a page (write scope; the pond's comment policy applies). Markdown is " + 'supported.', inputSchema: { type: 'object', properties: { pond: pondProp, page: pageProp, text: { type: 'string', description: 'Comment body (Markdown)' }, }, required: ['pond', 'page', 'text'], }, }, { name: 'list_labels', description: "The pond's label tree (ids + names) — ids feed set_page_labels.", inputSchema: { type: 'object', properties: { pond: pondProp }, required: ['pond'] }, }, { name: 'set_page_labels', description: "Replace a page's labels with exactly the given label ids (write scope). An empty " + 'list removes all labels.', inputSchema: { type: 'object', properties: { pond: pondProp, page: pageProp, labelIds: { type: 'array', items: { type: 'string' }, description: 'Label ids from list_labels', }, }, required: ['pond', 'page', 'labelIds'], }, }, { name: 'export_pond', description: 'A download link for the whole pond as a Markdown ZIP. The link goes through the ' + 'public REST API, so the instance REST switch and the same token are needed to ' + 'fetch it.', inputSchema: { type: 'object', properties: { pond: pondProp }, required: ['pond'] }, }, ];