From cfc47a6dee1ed8f54e8e51fb62a50fb484cfa70f Mon Sep 17 00:00:00 2001 From: aorlov Date: Wed, 29 Apr 2026 02:52:24 +0200 Subject: [PATCH 1/2] fix(mcp): update package configuration and enhance server functionality - Added Node.js engine requirement to package.json, specifying a minimum version of 20. - Replaced the usage of `getMcpPrompt` with a direct import of `MCP_SYSTEM_PROMPT` in server.ts for improved prompt management. - Refactored `registerAllTools` and `registerIntentTools` functions to remove unnecessary async/await, enhancing performance. - Introduced new test suite for MCP dist bundle to validate server functionality and tool interactions. - Generated new catalog and prompt files to support the updated toolset and ensure proper integration. --- apps/mcp/package.json | 6 +- apps/mcp/src/__tests__/dist-bundle.test.ts | 66 + apps/mcp/src/generated/catalog.ts | 3759 +++++++++++++++++ .../generated/intent-dispatch.generated.ts | 155 + apps/mcp/src/generated/mcp-prompt.ts | 416 ++ apps/mcp/src/server.ts | 8 +- apps/mcp/src/tools/index.ts | 4 +- apps/mcp/src/tools/intent.ts | 9 +- packages/sdk/codegen/src/generate-all.mjs | 3 + .../sdk/codegen/src/generate-intent-tools.mjs | 26 +- packages/sdk/scripts/sdk-generate.mjs | 20 +- 11 files changed, 4455 insertions(+), 17 deletions(-) create mode 100644 apps/mcp/src/__tests__/dist-bundle.test.ts create mode 100644 apps/mcp/src/generated/catalog.ts create mode 100644 apps/mcp/src/generated/intent-dispatch.generated.ts create mode 100644 apps/mcp/src/generated/mcp-prompt.ts diff --git a/apps/mcp/package.json b/apps/mcp/package.json index 951df7ddd1..1bd118e03c 100644 --- a/apps/mcp/package.json +++ b/apps/mcp/package.json @@ -2,6 +2,9 @@ "name": "@superdoc-dev/mcp", "version": "0.2.0", "type": "module", + "engines": { + "node": ">=20" + }, "bin": { "superdoc-mcp": "./dist/index.js" }, @@ -15,12 +18,11 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@superdoc-dev/sdk": "workspace:*", - "@superdoc/document-api": "workspace:*", "@modelcontextprotocol/sdk": "^1.26.0", "zod": "^4.3.6" }, "devDependencies": { + "@superdoc/document-api": "workspace:*", "@superdoc/super-editor": "workspace:*", "superdoc": "workspace:*", "@types/bun": "catalog:", diff --git a/apps/mcp/src/__tests__/dist-bundle.test.ts b/apps/mcp/src/__tests__/dist-bundle.test.ts new file mode 100644 index 0000000000..f85d0874bf --- /dev/null +++ b/apps/mcp/src/__tests__/dist-bundle.test.ts @@ -0,0 +1,66 @@ +import { beforeAll, describe, expect, it } from 'bun:test'; +import { execFile } from 'node:child_process'; +import { resolve } from 'node:path'; +import { promisify } from 'node:util'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; + +const execFileAsync = promisify(execFile); + +const MCP_ROOT = resolve(import.meta.dir, '../..'); +const BLANK_DOCX = resolve(import.meta.dir, '../../../../shared/common/data/blank.docx'); +const DIST_ENTRY = resolve(MCP_ROOT, 'dist/index.js'); + +function textContent(result: Awaited>): string { + const content = 'content' in result ? result.content : []; + const first = (content as Array<{ type: string; text?: string }>)[0]; + return first?.text ?? ''; +} + +function parseContent(result: Awaited>): unknown { + return JSON.parse(textContent(result)); +} + +describe('MCP dist bundle', () => { + beforeAll(async () => { + await execFileAsync('bun', ['build', 'src/index.ts', '--outdir', 'dist', '--target', 'node', '--format', 'esm'], { + cwd: MCP_ROOT, + }); + }); + + it('starts the bundled Node server and runs open/read/close over stdio', async () => { + const transport = new StdioClientTransport({ + command: 'node', + args: [DIST_ENTRY], + stderr: 'pipe', + }); + const client = new Client({ name: 'dist-bundle-test-client', version: '1.0.0' }); + + await client.connect(transport); + try { + const { tools } = await client.listTools(); + expect(tools.map((tool) => tool.name)).toContain('superdoc_open'); + expect(tools.map((tool) => tool.name)).toContain('superdoc_get_content'); + + const openResult = await client.callTool({ name: 'superdoc_open', arguments: { path: BLANK_DOCX } }); + expect(openResult).not.toHaveProperty('isError'); + const opened = parseContent(openResult) as { session_id: string }; + expect(opened.session_id).toBeString(); + + const infoResult = await client.callTool({ + name: 'superdoc_get_content', + arguments: { session_id: opened.session_id, action: 'info' }, + }); + expect(infoResult).not.toHaveProperty('isError'); + expect(textContent(infoResult)).toBeTruthy(); + + const closeResult = await client.callTool({ + name: 'superdoc_close', + arguments: { session_id: opened.session_id }, + }); + expect(parseContent(closeResult)).toEqual({ closed: true }); + } finally { + await transport.close(); + } + }); +}); diff --git a/apps/mcp/src/generated/catalog.ts b/apps/mcp/src/generated/catalog.ts new file mode 100644 index 0000000000..a8d727edd6 --- /dev/null +++ b/apps/mcp/src/generated/catalog.ts @@ -0,0 +1,3759 @@ +// Auto-generated from packages/sdk/tools/catalog.json +// Do not edit manually — re-run generate:all to update. +export const MCP_TOOL_CATALOG = { + contractVersion: '0.1.0', + generatedAt: null, + toolCount: 9, + tools: [ + { + toolName: 'superdoc_get_content', + description: + 'Read document content in various formats. Call this first in any workflow to understand document structure before making edits. Action "blocks" returns structured block data with nodeId, nodeType, textPreview, optional full text when includeText:true, formatting properties (fontFamily, fontSize, color, bold, underline, alignment), and ref handles for immediate use with superdoc_edit or superdoc_format. When you need to evaluate or rewrite existing paragraphs or clauses, prefer action "blocks" with includeText:true so you can identify the correct block and then target it by nodeId. Action "text" and "markdown" return the full document as plain text or Markdown. Action "html" returns HTML. Action "info" returns document metadata: word count, paragraph count, page count, outline, available styles, and capability flags. The "blocks" action supports pagination via "offset" and "limit", and filtering via "nodeTypes". Other actions ignore these parameters. This tool never modifies the document. Do NOT call superdoc_edit or superdoc_format without first reading blocks to get valid refs and formatting reference values.', + inputSchema: { + type: 'object', + properties: { + action: { + type: 'string', + enum: ['blocks', 'extract', 'html', 'info', 'markdown', 'text'], + description: 'The action to perform. One of: blocks, extract, html, info, markdown, text.', + }, + unflattenLists: { + type: 'boolean', + description: + "When true, flattens nested list structures in output. Default: false. Only for action 'html'. Omit for other actions.", + }, + offset: { + type: 'number', + description: "Number of blocks to skip. Default: 0. Only for action 'blocks'. Omit for other actions.", + }, + limit: { + type: 'number', + description: + "Maximum blocks to return. Omit for all blocks. Only for action 'blocks'. Omit for other actions.", + }, + nodeTypes: { + type: 'array', + items: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + description: + "Filter by block types (e.g. ['paragraph', 'heading']). Omit for all types. Only for action 'blocks'. Omit for other actions.", + }, + includeText: { + type: 'boolean', + description: + "When true, includes the full flattened block text in each block entry. Only for action 'blocks'. Omit for other actions.", + }, + }, + required: ['action'], + additionalProperties: false, + }, + mutates: false, + operations: [ + { + operationId: 'doc.getText', + intentAction: 'text', + }, + { + operationId: 'doc.getMarkdown', + intentAction: 'markdown', + }, + { + operationId: 'doc.getHtml', + intentAction: 'html', + }, + { + operationId: 'doc.info', + intentAction: 'info', + }, + { + operationId: 'doc.extract', + intentAction: 'extract', + }, + { + operationId: 'doc.blocks.list', + intentAction: 'blocks', + }, + ], + }, + { + toolName: 'superdoc_edit', + description: + 'The primary tool for inserting content into documents. ALWAYS use action "insert" with type "markdown" to create headings, paragraphs, or any block content — this is faster and creates proper document structure in one call. Do NOT use superdoc_create for headings or paragraphs. The markdown parser creates headings from # markers (# = Heading1, ## = Heading2), bold from **text**, italic from *text*, and numbered/bullet lists. Position markdown inserts with "target" (a BlockNodeAddress like {kind:"block", nodeType, nodeId}) and "placement" (before, after, insideStart, insideEnd). Without a target, content appends at the end of the document. IMPORTANT: After a markdown insert, analyze the document context (what kind of document, how titles and body text are styled) and follow up with ONE superdoc_mutations call to format inserted blocks so they look like they belong. Each format.apply step accepts "inline" (fontFamily, fontSize, bold, underline, color), "alignment", and "scope" in the same step. Use scope: "block" so formatting covers the entire paragraph. Copy the exact property values from the existing get_content blocks (fontFamily, fontSize, color, alignment, bold, underline). Do NOT invent values — use what the blocks show. Also supports replace, delete, and undo/redo. For replace and delete, pass a "ref" from superdoc_search or superdoc_get_content blocks. A search ref covers only the matched substring; a block ref covers the entire block text, so use block refs when rewriting or shortening whole paragraphs. For multi-step redlines or whole-clause rewrites, prefer superdoc_mutations with where:{by:"block", nodeType, nodeId} from superdoc_get_content action "blocks" includeText:true rather than relying on text selectors. Refs expire after any mutation; always re-search before the next edit. For 2+ edits that must succeed or fail atomically, use superdoc_mutations instead. Supports "dryRun" to preview changes and "changeMode: tracked" to record edits as tracked changes (not supported for markdown/html inserts). Do NOT build "target" objects manually when a ref is available; prefer "ref" for simpler, more reliable targeting.', + inputSchema: { + type: 'object', + properties: { + action: { + type: 'string', + enum: ['delete', 'insert', 'redo', 'replace', 'undo'], + description: 'The action to perform. One of: delete, insert, redo, replace, undo.', + }, + force: { + type: 'boolean', + description: 'Bypass confirmation checks.', + }, + changeMode: { + type: 'string', + enum: ['direct', 'tracked'], + description: 'Edit mode: "direct" applies changes immediately, "tracked" records as suggestions.', + }, + dryRun: { + type: 'boolean', + description: 'Preview the result without applying changes.', + }, + target: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'selection', + type: 'string', + }, + start: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: ['paragraph', 'heading', 'table', 'tableOfContents', 'sdt', 'image'], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + end: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: ['paragraph', 'heading', 'table', 'tableOfContents', 'sdt', 'image'], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + }, + required: ['kind', 'start', 'end'], + }, + { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + ], + description: + "Target address. For inline/set_style: prefer 'ref' from superdoc_search, or use {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. For paragraph actions (set_alignment, set_indentation, set_spacing, set_direction, set_flow_options): use {kind:'block', nodeType:'paragraph'|'heading'|'listItem', nodeId:''}.", + }, + value: { + type: 'string', + description: "Text content to insert. Only for action 'insert'. Omit for other actions.", + }, + type: { + type: 'string', + description: + "Content format: 'text' (default), 'markdown', or 'html'. Only for action 'insert'. Omit for other actions.", + enum: ['text', 'markdown', 'html'], + }, + ref: { + type: 'string', + description: + 'Handle ref from superdoc_search result (pass handle.ref value directly). Preferred over building a target object.', + }, + content: { + oneOf: [ + { + type: 'object', + properties: {}, + }, + { + type: 'array', + items: { + type: 'object', + properties: {}, + }, + }, + ], + description: + "Document fragment to insert (structured content). Only for actions 'insert', 'replace'. Omit for other actions.", + }, + placement: { + type: 'string', + description: + "Where to place content relative to target: 'before', 'after', 'insideStart', or 'insideEnd'. Only for action 'insert'. Omit for other actions.", + enum: ['before', 'after', 'insideStart', 'insideEnd'], + }, + nestingPolicy: { + type: 'object', + properties: { + tables: { + enum: ['forbid', 'allow'], + }, + }, + description: + "Controls nesting behavior. tables: 'allow' permits inserting tables inside other tables. Only for actions 'insert', 'replace'. Omit for other actions.", + }, + text: { + type: 'string', + description: "Replacement text content. Only for action 'replace'. Omit for other actions.", + }, + behavior: { + type: 'string', + enum: ['selection', 'exact'], + description: + "Delete behavior: 'selection' (default) or 'exact'. Only for action 'delete'. Omit for other actions.", + }, + }, + required: ['action'], + additionalProperties: false, + }, + mutates: true, + operations: [ + { + operationId: 'doc.insert', + intentAction: 'insert', + requiredOneOf: [['target', 'value'], ['ref', 'value'], ['value'], ['content']], + }, + { + operationId: 'doc.replace', + intentAction: 'replace', + requiredOneOf: [ + ['target', 'text'], + ['ref', 'text'], + ['target', 'content'], + ['ref', 'content'], + ], + }, + { + operationId: 'doc.delete', + intentAction: 'delete', + requiredOneOf: [['target'], ['ref']], + }, + { + operationId: 'doc.history.undo', + intentAction: 'undo', + }, + { + operationId: 'doc.history.redo', + intentAction: 'redo', + }, + ], + }, + { + toolName: 'superdoc_format', + description: + 'Change text and paragraph formatting. To format multiple items at once, use superdoc_mutations with format.apply steps instead of calling this tool repeatedly. Use require "all" with a node selector to format every heading or paragraph in one batch. Use this tool for single-item formatting when you have a valid ref or nodeId. Action "inline" applies character formatting (bold, italic, underline, color, fontSize, fontFamily, highlight, strike, vertAlign) to a text range via "ref". Action "set_style" applies a named paragraph style by styleId (get available styles from superdoc_get_content info). Actions "set_alignment", "set_indentation", "set_spacing", "set_direction", and "set_flow_options" change paragraph-level properties and require a block target: {kind:"block", nodeType:"paragraph", nodeId:""}, NOT a ref. Use "set_flow_options" with pageBreakBefore:true to start a paragraph on a new page. Supports "dryRun" and "changeMode: tracked" for inline formatting. Paragraph-level actions do NOT support tracked changes. Do NOT use a search ref for paragraph-level actions; they require a block target with nodeId. Do NOT use {kind:"block", start:{kind:"nodeEdge",...}} or selection-like structures for paragraph actions. ONLY {kind:"block", nodeType, nodeId} is accepted. Do NOT issue multiple superdoc_format calls in parallel; each call invalidates refs for subsequent calls.', + inputSchema: { + type: 'object', + properties: { + action: { + type: 'string', + enum: [ + 'inline', + 'set_alignment', + 'set_direction', + 'set_flow_options', + 'set_indentation', + 'set_spacing', + 'set_style', + ], + description: + 'The action to perform. One of: inline, set_alignment, set_direction, set_flow_options, set_indentation, set_spacing, set_style.', + }, + force: { + type: 'boolean', + description: 'Bypass confirmation checks.', + }, + changeMode: { + type: 'string', + enum: ['direct', 'tracked'], + description: 'Edit mode: "direct" applies changes immediately, "tracked" records as suggestions.', + }, + dryRun: { + type: 'boolean', + description: 'Preview the result without applying changes.', + }, + target: { + type: 'object', + properties: { + kind: { + const: 'selection', + type: 'string', + }, + start: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: ['paragraph', 'heading', 'table', 'tableOfContents', 'sdt', 'image'], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + end: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: ['paragraph', 'heading', 'table', 'tableOfContents', 'sdt', 'image'], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + }, + required: ['kind', 'start', 'end'], + description: + "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle. Required for actions 'set_style', 'set_alignment', 'set_indentation', 'set_spacing', 'set_flow_options', 'set_direction'.", + }, + inline: { + type: 'object', + properties: { + bold: { + type: 'boolean', + }, + italic: { + type: 'boolean', + }, + strike: { + type: 'boolean', + }, + underline: { + oneOf: [ + { + type: 'boolean', + }, + { + type: 'object', + properties: { + style: { + type: 'string', + }, + color: { + type: 'string', + }, + themeColor: { + type: 'string', + }, + }, + }, + ], + }, + highlight: { + type: 'string', + }, + color: { + type: 'string', + }, + fontSize: { + type: 'number', + }, + fontFamily: { + type: 'string', + }, + letterSpacing: { + type: 'number', + }, + vertAlign: { + enum: ['superscript', 'subscript', 'baseline'], + }, + position: { + type: 'number', + }, + dstrike: { + type: 'boolean', + }, + smallCaps: { + type: 'boolean', + }, + caps: { + type: 'boolean', + }, + shading: { + type: 'object', + properties: { + fill: { + type: 'string', + }, + color: { + type: 'string', + }, + val: { + type: 'string', + }, + }, + }, + border: { + type: 'object', + properties: { + val: { + type: 'string', + }, + sz: { + type: 'number', + }, + color: { + type: 'string', + }, + space: { + type: 'number', + }, + }, + }, + outline: { + type: 'boolean', + }, + shadow: { + type: 'boolean', + }, + emboss: { + type: 'boolean', + }, + imprint: { + type: 'boolean', + }, + charScale: { + type: 'number', + }, + kerning: { + type: 'number', + }, + vanish: { + type: 'boolean', + }, + webHidden: { + type: 'boolean', + }, + specVanish: { + type: 'boolean', + }, + rtl: { + type: 'boolean', + }, + cs: { + type: 'boolean', + }, + bCs: { + type: 'boolean', + }, + iCs: { + type: 'boolean', + }, + eastAsianLayout: { + type: 'object', + properties: { + id: { + type: 'string', + }, + combine: { + type: 'boolean', + }, + combineBrackets: { + type: 'string', + }, + vert: { + type: 'boolean', + }, + vertCompress: { + type: 'boolean', + }, + }, + }, + em: { + type: 'string', + }, + fitText: { + type: 'object', + properties: { + val: { + type: 'number', + }, + id: { + type: 'string', + }, + }, + }, + snapToGrid: { + type: 'boolean', + }, + lang: { + type: 'object', + properties: { + val: { + type: 'string', + }, + eastAsia: { + type: 'string', + }, + bidi: { + type: 'string', + }, + }, + }, + oMath: { + type: 'boolean', + }, + rStyle: { + type: 'string', + }, + rFonts: { + type: 'object', + properties: { + ascii: { + type: 'string', + }, + hAnsi: { + type: 'string', + }, + eastAsia: { + type: 'string', + }, + cs: { + type: 'string', + }, + asciiTheme: { + type: 'string', + }, + hAnsiTheme: { + type: 'string', + }, + eastAsiaTheme: { + type: 'string', + }, + csTheme: { + type: 'string', + }, + hint: { + type: 'string', + }, + }, + }, + fontSizeCs: { + type: 'number', + }, + ligatures: { + type: 'string', + }, + numForm: { + type: 'string', + }, + numSpacing: { + type: 'string', + }, + stylisticSets: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number', + }, + val: { + type: 'boolean', + }, + }, + required: ['id'], + }, + }, + contextualAlternates: { + type: 'boolean', + }, + }, + description: + "Inline formatting properties to apply. Set a property to apply it, use null to clear it. Example: {bold: true, italic: true} or {bold: null} to remove bold. Only for action 'inline'. Omit for other actions.", + }, + ref: { + type: 'string', + description: + "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting. Only for action 'inline'. Omit for other actions.", + }, + styleId: { + type: 'string', + description: + "Named paragraph style ID (e.g. 'Normal', 'Heading1', 'BodyText'). Use superdoc_search to find a nearby paragraph, then inspect its style to determine the correct styleId. Required for action 'set_style'.", + }, + alignment: { + type: 'string', + enum: ['left', 'center', 'right', 'justify'], + description: "Required for action 'set_alignment'.", + }, + left: { + type: 'number', + description: + "Left indentation in twips (1440 = 1 inch). Only for action 'set_indentation'. Omit for other actions.", + }, + right: { + type: 'number', + description: + "Right indentation in twips (1440 = 1 inch). Only for action 'set_indentation'. Omit for other actions.", + }, + firstLine: { + type: 'number', + description: + "First line indent in twips. Cannot be combined with hanging. Only for action 'set_indentation'. Omit for other actions.", + }, + hanging: { + type: 'number', + description: + "Hanging indent in twips. Cannot be combined with firstLine. Only for action 'set_indentation'. Omit for other actions.", + }, + before: { + type: 'number', + description: + "Space before paragraph in twips (20 twips = 1pt). Only for action 'set_spacing'. Omit for other actions.", + }, + after: { + type: 'number', + description: + "Space after paragraph in twips (20 twips = 1pt). Only for action 'set_spacing'. Omit for other actions.", + }, + line: { + type: 'number', + description: + "Line spacing value. Meaning depends on lineRule. Must be provided together with lineRule. Only for action 'set_spacing'. Omit for other actions.", + }, + lineRule: { + type: 'string', + description: + "Line spacing rule. Required when 'line' is set. Only for action 'set_spacing'. Omit for other actions.", + enum: ['auto', 'exact', 'atLeast'], + }, + contextualSpacing: { + type: 'boolean', + description: "Only for action 'set_flow_options'. Omit for other actions.", + }, + pageBreakBefore: { + type: 'boolean', + description: "Only for action 'set_flow_options'. Omit for other actions.", + }, + suppressAutoHyphens: { + type: 'boolean', + description: "Only for action 'set_flow_options'. Omit for other actions.", + }, + direction: { + type: 'string', + enum: ['ltr', 'rtl'], + description: "Required for action 'set_direction'.", + }, + alignmentPolicy: { + type: 'string', + enum: ['preserve', 'matchDirection'], + description: "Only for action 'set_direction'. Omit for other actions.", + }, + }, + required: ['action'], + additionalProperties: false, + }, + mutates: true, + operations: [ + { + operationId: 'doc.format.apply', + intentAction: 'inline', + requiredOneOf: [ + ['target', 'inline'], + ['ref', 'inline'], + ], + }, + { + operationId: 'doc.styles.paragraph.setStyle', + intentAction: 'set_style', + required: ['target', 'styleId'], + }, + { + operationId: 'doc.format.paragraph.setAlignment', + intentAction: 'set_alignment', + required: ['target', 'alignment'], + }, + { + operationId: 'doc.format.paragraph.setIndentation', + intentAction: 'set_indentation', + required: ['target'], + }, + { + operationId: 'doc.format.paragraph.setSpacing', + intentAction: 'set_spacing', + required: ['target'], + }, + { + operationId: 'doc.format.paragraph.setFlowOptions', + intentAction: 'set_flow_options', + requiredOneOf: [ + ['target', 'contextualSpacing'], + ['target', 'pageBreakBefore'], + ['target', 'suppressAutoHyphens'], + ], + }, + { + operationId: 'doc.format.paragraph.setDirection', + intentAction: 'set_direction', + required: ['target', 'direction'], + }, + ], + }, + { + toolName: 'superdoc_create', + description: + 'IMPORTANT: For headings and paragraphs, use superdoc_edit with type "markdown" instead — it is faster, creates proper styles, and handles positioning via target + placement. Only use superdoc_create for tables or when markdown cannot express the content. Creates a single paragraph, heading, or table. Returns nodeId and ref for the created block. After creating, the returned ref is valid for ONE immediate superdoc_format call. For subsequent operations, re-fetch blocks with superdoc_get_content to get fresh refs (refs expire after any mutation). When the user asks for a "heading", use action "heading" with a level (default 1). Use action "paragraph" for regular body text. Position with "at": {kind:"documentEnd"} (default), {kind:"documentStart"}, or {kind:"after"/"before", target:{kind:"block", nodeType, nodeId}} for relative placement. When creating multiple items in sequence, use the previous response nodeId as the next "at" target to maintain correct ordering. Do NOT use newlines in "text" to create multiple paragraphs; call this tool separately for each one.', + inputSchema: { + type: 'object', + properties: { + action: { + type: 'string', + enum: ['heading', 'paragraph', 'table'], + description: 'The action to perform. One of: heading, paragraph, table.', + }, + force: { + type: 'boolean', + description: 'Bypass confirmation checks.', + }, + changeMode: { + type: 'string', + enum: ['direct', 'tracked'], + description: 'Edit mode: "direct" applies changes immediately, "tracked" records as suggestions.', + }, + dryRun: { + type: 'boolean', + description: 'Preview the result without applying changes.', + }, + at: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'documentStart', + type: 'string', + }, + }, + required: ['kind'], + }, + { + type: 'object', + properties: { + kind: { + const: 'documentEnd', + type: 'string', + }, + }, + required: ['kind'], + }, + { + type: 'object', + properties: { + kind: { + const: 'before', + type: 'string', + }, + target: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + }, + required: ['kind', 'target'], + }, + { + type: 'object', + properties: { + kind: { + const: 'after', + type: 'string', + }, + target: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + }, + required: ['kind', 'target'], + }, + ], + description: + "Position: {kind:'documentEnd'} to append, {kind:'documentStart'} to prepend, or {kind:'before'|'after', target:{kind:'block', nodeType:'...', nodeId:'...'}} for relative placement.", + }, + text: { + type: 'string', + description: + 'Paragraph text content. Each call creates ONE paragraph. For multiple items (e.g. list items), call superdoc_create separately for each item — do NOT use newlines to put multiple items in one paragraph.', + }, + input: { + type: 'object', + description: 'Full paragraph input as JSON (alternative to individual text/at params).', + }, + level: { + type: 'number', + description: "Heading level (1-6). Required for action 'heading'.", + }, + rows: { + type: 'number', + description: "Required for action 'table'.", + }, + columns: { + type: 'number', + description: "Required for action 'table'.", + }, + }, + required: ['action'], + additionalProperties: false, + }, + mutates: true, + operations: [ + { + operationId: 'doc.create.paragraph', + intentAction: 'paragraph', + }, + { + operationId: 'doc.create.heading', + intentAction: 'heading', + required: ['level'], + }, + { + operationId: 'doc.create.table', + intentAction: 'table', + required: ['rows', 'columns'], + }, + ], + }, + { + toolName: 'superdoc_list', + description: + 'Create and manipulate bullet and numbered lists. Most actions require a list-item target: {kind:"block", nodeType:"listItem", nodeId:""}. Exceptions: "create" and "attach" operate on paragraph targets (they turn paragraphs into list items). Find nodeIds via superdoc_get_content({action:"blocks"}) — pick listItem blocks for most actions, paragraph blocks for create/attach.\n\nCREATE & CONVERT:\n• "create" — make a NEW list from paragraphs. Two modes: mode:"empty" with at:{kind:"block", nodeType:"paragraph", nodeId} converts a single paragraph; mode:"fromParagraphs" with target:{from:{...paragraph block address}, to:{...paragraph block address}} converts a range — ALL paragraphs between from and to become items, so make sure no other content sits between them. Pass a preset ("disc"|"circle"|"square"|"dash" for bullets; "decimal"|"decimalParenthesis"|"lowerLetter"|"upperLetter"|"lowerRoman"|"upperRoman" for ordered) or a custom style. Use "create" to start a fresh list — NOT to extend an existing one (use "attach" for that).\n• "attach" — add paragraphs to an EXISTING list, inheriting its numbering definition. Pass target:{paragraph block address} (or {from, to} range of paragraphs) + attachTo:{kind:"block", nodeType:"listItem", nodeId:""} + optional level:0..8. Use this to extend a list or as the second half of a merge workflow (see "join" below).\n• "set_type" — convert an existing list between ordered and bullet. Pass target:{listItem} + kind:"ordered" or "bullet". Adjacent compatible sequences are merged automatically to preserve continuous numbering.\n• "detach" — convert a list item back to a plain paragraph. Pass target:{listItem}.\n\nITEMS & NESTING:\n• "insert" — add a new list item adjacent to an existing item in the same list. Pass target:{listItem} + position:"before"|"after" + optional text. Use this (NOT superdoc_create) to add items to an existing list.\n• "indent" / "outdent" — bump the target item\'s nesting level by one (0-8 range). Pass target:{listItem}.\n• "set_level" — jump the target item to an explicit level. Pass target:{listItem} + level:0..8.\n\nNUMBERING (ordered lists):\n• "set_value" — restart numbering at the target. Pass target:{listItem} + value: (e.g. value:1 to start over) or value:null to clear a previous override. Mid-sequence targets are atomically split off into their own sequence.\n• "continue_previous" — make the target\'s sequence continue numbering from the nearest compatible previous sequence (same abstract definition). Pass target:{listItem of the sequence you want to renumber}. Fails with NO_COMPATIBLE_PREVIOUS or INCOMPATIBLE_DEFINITIONS if no matching prior sequence exists.\n\nSEQUENCE SHAPE (merge / split):\n• "merge" — merge the target\'s sequence with an adjacent one into one continuous list. Pass target:{listItem} + direction:"withPrevious" or "withNext". Absorbed items adopt the absorbing sequence\'s numbering definition, and empty paragraphs between the two sequences are removed so numbering flows continuously.\n• "split" — split the target\'s sequence at the target item into two independent lists. The target and everything after become a new sequence that restarts numbering at 1. Pass target:{listItem}; add restartNumbering:false to keep the count continuing instead of restarting.', + inputSchema: { + type: 'object', + properties: { + action: { + type: 'string', + enum: [ + 'attach', + 'continue_previous', + 'create', + 'detach', + 'indent', + 'insert', + 'merge', + 'outdent', + 'set_level', + 'set_type', + 'set_value', + 'split', + ], + description: + 'The action to perform. One of: attach, continue_previous, create, detach, indent, insert, merge, outdent, set_level, set_type, set_value, split.', + }, + force: { + type: 'boolean', + description: 'Bypass confirmation checks.', + }, + changeMode: { + type: 'string', + enum: ['direct', 'tracked'], + description: 'Edit mode: "direct" applies changes immediately, "tracked" records as suggestions.', + }, + dryRun: { + type: 'boolean', + description: 'Preview the result without applying changes.', + }, + target: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + const: 'listItem', + type: 'string', + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + description: + "The target list item. For 'insert': the item to insert relative to. For 'create' with mode 'fromParagraphs': use nodeType 'paragraph' instead. Format: {kind:'block', nodeType:'listItem', nodeId:''}. Required for actions 'insert', 'attach', 'detach', 'indent', 'outdent', 'merge', 'split', 'set_level', 'set_value', 'continue_previous', 'set_type'.", + }, + position: { + type: 'string', + description: + "Required. Insert position relative to target: 'before' or 'after'. Required for action 'insert'.", + enum: ['before', 'after'], + }, + text: { + type: 'string', + description: "Text content for the new list item. Only for action 'insert'. Omit for other actions.", + }, + input: { + type: 'object', + description: 'Operation input as JSON object.', + }, + nodeId: { + type: 'string', + description: 'Node ID of the target list item.', + }, + mode: { + type: 'string', + description: + "Required. 'fromParagraphs' converts existing paragraphs into list items — each paragraph becomes one item, so create one paragraph per item first. 'empty' creates a new empty list at 'at'. Required for action 'create'.", + enum: ['empty', 'fromParagraphs'], + }, + at: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + const: 'paragraph', + type: 'string', + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + description: + "Required when mode is 'empty'. The paragraph to create the list at. Format: {kind:'block', nodeType:'paragraph', nodeId:''}. Only for action 'create'. Omit for other actions.", + }, + kind: { + type: 'string', + description: + "List type: 'bullet' for bullet points, 'ordered' for numbered lists. Required for action 'set_type'.", + enum: ['ordered', 'bullet'], + }, + level: { + type: 'number', + description: "List nesting level (0-8). 0 is the top level. Required for action 'set_level'.", + }, + preset: { + type: 'string', + description: + "Predefined list style preset. Overrides 'kind' with a specific numbering or bullet format. Only for action 'create'. Omit for other actions.", + enum: [ + 'decimal', + 'decimalParenthesis', + 'lowerLetter', + 'upperLetter', + 'lowerRoman', + 'upperRoman', + 'disc', + 'circle', + 'square', + 'dash', + ], + }, + style: { + type: 'object', + properties: { + version: { + const: 1, + type: 'number', + }, + levels: { + type: 'array', + items: { + type: 'object', + properties: { + level: { + type: 'number', + }, + numFmt: { + type: 'string', + }, + lvlText: { + type: 'string', + }, + start: { + type: 'number', + }, + alignment: { + enum: ['left', 'center', 'right'], + }, + indents: { + type: 'object', + properties: { + left: { + type: 'number', + }, + hanging: { + type: 'number', + }, + firstLine: { + type: 'number', + }, + }, + }, + trailingCharacter: { + enum: ['tab', 'space', 'nothing'], + }, + markerFont: { + type: 'string', + }, + pictureBulletId: { + type: 'number', + }, + tabStopAt: {}, + }, + required: ['level'], + }, + }, + }, + required: ['version', 'levels'], + description: "Only for action 'create'. Omit for other actions.", + }, + sequence: { + oneOf: [ + { + type: 'object', + properties: { + mode: { + const: 'new', + type: 'string', + }, + startAt: { + type: 'number', + }, + }, + required: ['mode'], + }, + { + type: 'object', + properties: { + mode: { + const: 'continuePrevious', + type: 'string', + }, + }, + required: ['mode'], + }, + ], + description: "Only for action 'create'. Omit for other actions.", + }, + attachTo: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + const: 'listItem', + type: 'string', + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + description: "Required for action 'attach'.", + }, + direction: { + type: 'string', + enum: ['withPrevious', 'withNext'], + description: "Required for action 'merge'.", + }, + restartNumbering: { + type: 'boolean', + description: "Only for action 'split'. Omit for other actions.", + }, + value: { + type: 'object', + description: "Required for action 'set_value'.", + }, + continuity: { + type: 'string', + description: + "Numbering continuity: 'preserve' keeps numbering; 'none' restarts. Only for action 'set_type'. Omit for other actions.", + enum: ['preserve', 'none'], + }, + }, + required: ['action'], + additionalProperties: false, + }, + mutates: true, + operations: [ + { + operationId: 'doc.lists.insert', + intentAction: 'insert', + required: ['target', 'position'], + }, + { + operationId: 'doc.lists.create', + intentAction: 'create', + required: ['mode'], + }, + { + operationId: 'doc.lists.attach', + intentAction: 'attach', + required: ['target', 'attachTo'], + }, + { + operationId: 'doc.lists.detach', + intentAction: 'detach', + required: ['target'], + }, + { + operationId: 'doc.lists.indent', + intentAction: 'indent', + required: ['target'], + }, + { + operationId: 'doc.lists.outdent', + intentAction: 'outdent', + required: ['target'], + }, + { + operationId: 'doc.lists.merge', + intentAction: 'merge', + required: ['target', 'direction'], + }, + { + operationId: 'doc.lists.split', + intentAction: 'split', + required: ['target'], + }, + { + operationId: 'doc.lists.setLevel', + intentAction: 'set_level', + required: ['target', 'level'], + }, + { + operationId: 'doc.lists.setValue', + intentAction: 'set_value', + required: ['target', 'value'], + }, + { + operationId: 'doc.lists.continuePrevious', + intentAction: 'continue_previous', + required: ['target'], + }, + { + operationId: 'doc.lists.setType', + intentAction: 'set_type', + required: ['target', 'kind'], + }, + ], + }, + { + toolName: 'superdoc_comment', + description: + 'Manage document comment threads: create, read, update, and delete. To create a comment, first use superdoc_search to find the target text, then pass action "create" with the comment text and a target: {kind:"text", blockId:"", range:{start:, end:}} using the blockId and highlightRange from the search result. For threaded replies, pass "parentId" with the parent comment ID. Action "list" returns all comments with optional pagination (limit, offset) and filtering (includeResolved:true to include resolved). Action "get" retrieves a single comment by ID. Action "update" changes status to "resolved" or marks as internal. Action "delete" removes a comment or reply by ID. Do NOT pass "ref", "id", or "parentId" when creating a new top-level comment; only "action", "text", and "target" are needed.', + inputSchema: { + type: 'object', + properties: { + action: { + type: 'string', + enum: ['create', 'delete', 'get', 'list', 'update'], + description: 'The action to perform. One of: create, delete, get, list, update.', + }, + force: { + type: 'boolean', + description: 'Bypass confirmation checks.', + }, + changeMode: { + type: 'string', + enum: ['direct', 'tracked'], + description: 'Edit mode: "direct" applies changes immediately, "tracked" records as suggestions.', + }, + text: { + type: 'string', + description: "Comment text content. Required for action 'create'.", + }, + target: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + range: { + type: 'object', + properties: { + start: { + type: 'number', + }, + end: { + type: 'number', + }, + }, + required: ['start', 'end'], + }, + }, + required: ['kind', 'blockId', 'range'], + }, + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + segments: { + type: 'array', + items: { + type: 'object', + properties: { + blockId: { + type: 'string', + }, + range: { + type: 'object', + properties: { + start: { + type: 'number', + }, + end: { + type: 'number', + }, + }, + required: ['start', 'end'], + }, + }, + required: ['blockId', 'range'], + }, + }, + }, + required: ['kind', 'segments'], + }, + ], + description: + "Text range to anchor the comment. Accepts either a single-block TextAddress {kind:'text', blockId, range} or a multi-segment TextTarget {kind:'text', segments:[{blockId, range}, ...]} for selections that span blocks. Only for actions 'create', 'update'. Omit for other actions.", + }, + parentId: { + type: 'string', + description: + "Parent comment ID for creating a threaded reply. Only for action 'create'. Omit for other actions.", + }, + id: { + type: 'string', + description: "Required for actions 'delete', 'get'.", + }, + status: { + type: 'string', + description: + "Set comment status. Use 'resolved' to mark as resolved. Only for action 'update'. Omit for other actions.", + enum: ['resolved'], + }, + isInternal: { + type: 'boolean', + description: + "When true, marks the comment as internal (hidden from external collaborators). Only for action 'update'. Omit for other actions.", + }, + includeResolved: { + type: 'boolean', + description: + "When true, includes resolved comments in results. Default: false. Only for action 'list'. Omit for other actions.", + }, + limit: { + type: 'number', + description: "Maximum number of comments to return. Only for action 'list'. Omit for other actions.", + }, + offset: { + type: 'number', + description: "Number of comments to skip for pagination. Only for action 'list'. Omit for other actions.", + }, + }, + required: ['action'], + additionalProperties: false, + }, + mutates: true, + operations: [ + { + operationId: 'doc.comments.create', + intentAction: 'create', + required: ['text'], + }, + { + operationId: 'doc.comments.patch', + intentAction: 'update', + }, + { + operationId: 'doc.comments.delete', + intentAction: 'delete', + required: ['id'], + }, + { + operationId: 'doc.comments.get', + intentAction: 'get', + required: ['id'], + }, + { + operationId: 'doc.comments.list', + intentAction: 'list', + }, + ], + }, + { + toolName: 'superdoc_track_changes', + description: + 'Review and resolve tracked changes (insertions, deletions, format changes) in the document. Action "list" returns all tracked changes with optional filtering by type (insert, delete, format) and pagination (limit, offset). Each change includes an ID, type, author, timestamp, and content preview. Action "decide" accepts or rejects changes. Pass decision:"accept" to apply the change permanently, or decision:"reject" to discard it. Target a single change with {id:""} or all changes at once with {scope:"all"}. Do NOT use this tool unless the document has tracked changes. Use superdoc_get_content info to check the tracked change count first.', + inputSchema: { + type: 'object', + properties: { + action: { + type: 'string', + enum: ['decide', 'list'], + description: 'The action to perform. One of: decide, list.', + }, + limit: { + type: 'number', + description: "Maximum number of tracked changes to return. Only for action 'list'. Omit for other actions.", + }, + offset: { + type: 'number', + description: + "Number of tracked changes to skip for pagination. Only for action 'list'. Omit for other actions.", + }, + type: { + type: 'string', + description: + "Filter by change type: 'insert', 'delete', or 'format'. Only for action 'list'. Omit for other actions.", + enum: ['insert', 'delete', 'format'], + }, + force: { + type: 'boolean', + description: "Bypass confirmation checks. Only for action 'decide'. Omit for other actions.", + }, + changeMode: { + type: 'string', + enum: ['direct', 'tracked'], + description: + 'Edit mode: "direct" applies changes immediately, "tracked" records as suggestions. Only for action \'decide\'. Omit for other actions.', + }, + decision: { + type: 'string', + enum: ['accept', 'reject'], + description: "Required for action 'decide'.", + }, + target: { + oneOf: [ + { + type: 'object', + properties: { + id: { + type: 'string', + }, + story: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'story', + type: 'string', + }, + storyType: { + const: 'body', + type: 'string', + }, + }, + required: ['kind', 'storyType'], + }, + { + type: 'object', + properties: { + kind: { + const: 'story', + type: 'string', + }, + storyType: { + const: 'headerFooterSlot', + type: 'string', + }, + section: { + type: 'object', + properties: { + kind: { + const: 'section', + type: 'string', + }, + sectionId: { + type: 'string', + }, + }, + required: ['kind', 'sectionId'], + }, + headerFooterKind: { + enum: ['header', 'footer'], + }, + variant: { + enum: ['default', 'first', 'even'], + }, + resolution: { + enum: ['effective', 'explicit'], + }, + onWrite: { + enum: ['materializeIfInherited', 'editResolvedPart', 'error'], + }, + }, + required: ['kind', 'storyType', 'section', 'headerFooterKind', 'variant'], + }, + { + type: 'object', + properties: { + kind: { + const: 'story', + type: 'string', + }, + storyType: { + const: 'headerFooterPart', + type: 'string', + }, + refId: { + type: 'string', + }, + }, + required: ['kind', 'storyType', 'refId'], + }, + { + type: 'object', + properties: { + kind: { + const: 'story', + type: 'string', + }, + storyType: { + const: 'footnote', + type: 'string', + }, + noteId: { + type: 'string', + }, + }, + required: ['kind', 'storyType', 'noteId'], + }, + { + type: 'object', + properties: { + kind: { + const: 'story', + type: 'string', + }, + storyType: { + const: 'endnote', + type: 'string', + }, + noteId: { + type: 'string', + }, + }, + required: ['kind', 'storyType', 'noteId'], + }, + ], + description: + "Story scope. Defaults to document body when omitted. Use {kind:'story', storyType:'body'} for body, or other storyType values for headers, footers, footnotes, endnotes.", + }, + }, + required: ['id'], + }, + { + type: 'object', + properties: { + scope: { + enum: ['all'], + }, + }, + required: ['scope'], + }, + ], + description: "Required for action 'decide'.", + }, + }, + required: ['action'], + additionalProperties: false, + }, + mutates: true, + operations: [ + { + operationId: 'doc.trackChanges.list', + intentAction: 'list', + }, + { + operationId: 'doc.trackChanges.decide', + intentAction: 'decide', + required: ['decision', 'target'], + }, + ], + }, + { + toolName: 'superdoc_search', + description: + 'Find text patterns or nodes in the document and get ref handles for targeting edits and formatting. Refs expire after any mutation that changes the document. Re-search before the next edit when using individual tools (superdoc_edit, superdoc_format). Within a superdoc_mutations batch, selectors in "where" clauses resolve automatically at compile time; no manual re-searching needed between steps. Text search returns handle.ref covering only the matched substring. Node search finds blocks by type (paragraph, heading, table, listItem, etc.). The "require" parameter controls match cardinality: "first" returns one match, "all" returns every match, "exactlyOne" fails if not exactly one match. Supports scoping via "within" to search inside a single block. Do NOT use regex or markdown formatting markers (#, **, etc.) in search patterns; patterns are plain text only. Do NOT use this tool when you already have a ref from superdoc_get_content blocks or superdoc_create; use that ref directly.', + inputSchema: { + type: 'object', + properties: { + select: { + oneOf: [ + { + type: 'object', + properties: { + type: { + const: 'text', + description: "Must be 'text' for text pattern search.", + type: 'string', + }, + pattern: { + type: 'string', + description: 'Text or regex pattern to match.', + }, + mode: { + description: "Match mode: 'contains' (substring) or 'regex'.", + enum: ['contains', 'regex'], + }, + caseSensitive: { + type: 'boolean', + description: 'Case-sensitive matching. Default: false.', + }, + }, + required: ['type', 'pattern'], + }, + { + type: 'object', + properties: { + type: { + const: 'node', + description: "Must be 'node' for node type search.", + type: 'string', + }, + nodeType: { + description: 'Block type to match (paragraph, heading, table, listItem, etc.).', + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + 'run', + 'bookmark', + 'comment', + 'hyperlink', + 'footnoteRef', + 'endnoteRef', + 'crossRef', + 'indexEntry', + 'citation', + 'authorityEntry', + 'sequenceField', + 'tab', + 'lineBreak', + ], + }, + kind: { + description: "Filter: 'block' or 'inline'.", + enum: ['block', 'inline'], + }, + }, + required: ['type'], + }, + ], + description: + "Search selector. Use {type:'text', pattern:'...'} for text search or {type:'node', nodeType:'paragraph'|'heading'|...} for node search.", + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + description: "Limit search scope to within a specific block: {kind:'block', nodeType:'...', nodeId:'...'}.", + }, + require: { + type: 'string', + description: + "Match cardinality: 'any' (all matches), 'first' (only first), 'exactlyOne' (fail if != 1), 'all' (fail if 0).", + enum: ['any', 'first', 'exactlyOne', 'all'], + }, + mode: { + type: 'string', + description: + "Search mode: 'strict' (default, exact matching) or 'candidates' (returns scored potential matches).", + enum: ['strict', 'candidates'], + }, + includeNodes: { + type: 'boolean', + description: 'When true, includes full node data in results. Default: false.', + }, + limit: { + type: 'number', + description: 'Maximum number of matches to return.', + }, + offset: { + type: 'number', + description: 'Number of matches to skip for pagination.', + }, + }, + required: ['select'], + additionalProperties: false, + }, + mutates: false, + operations: [ + { + operationId: 'doc.query.match', + intentAction: 'match', + required: ['select'], + }, + ], + }, + { + toolName: 'superdoc_mutations', + description: + 'All steps succeed or all fail; no partial application. Execute multiple operations atomically in one batch. Use this for any workflow needing 2+ changes. Supported step types: text (text.rewrite, text.insert, text.delete), format (format.apply), create (create.heading, create.paragraph, create.table), assert. Each step has an id, an op, a "where" clause for targeting ({by:"select", select:{...}, require:"first"|"exactlyOne"|"all"} or {by:"ref", ref:"..."} or {by:"block", nodeType:"paragraph", nodeId:"..."}), and "args" with operation-specific parameters. Use {by:"block", nodeType, nodeId} when you want to rewrite, delete, format, or anchor against a whole known block from superdoc_get_content action "blocks" without relying on text matching. For full-paragraph or full-clause rewrites, first call superdoc_get_content with action:"blocks" and includeText:true, then rewrite the matching block by nodeId. Use {by:"select"} only for substring edits, discovery, or insertion relative to a sentence fragment; do NOT use a shortened text selector to replace an entire known block. For create steps, "where" targets an existing anchor block and args.position ("before" or "after") controls placement. Sequential creates targeting the same anchor maintain correct order via internal position mapping. For format.apply with require "all", use a node selector to format every heading or paragraph at once: {by:"select", select:{type:"node", nodeType:"heading"}, require:"all"}. Selectors resolve at compile time (before execution). This means format.apply steps CANNOT target content created by earlier create steps in the same batch. Split creates and formatting into separate batches: first a mutations call with creates, then a mutations call with format.apply. Action "preview" dry-runs the plan. Action "apply" executes it. If a selector matches nothing, the failure reports the step id plus selector details so you can retry with a shorter or more distinctive anchor. Do NOT create two steps that target overlapping text in the same block; combine them into a single text.rewrite step.', + inputSchema: { + type: 'object', + properties: { + action: { + type: 'string', + enum: ['apply', 'preview'], + description: 'The action to perform. One of: apply, preview.', + }, + expectedRevision: { + type: 'string', + description: + "Document revision for optimistic concurrency. Mutation fails if document was modified since this revision. Only for action 'preview'. Omit for other actions.", + }, + atomic: { + type: 'boolean', + description: 'Must be true. All steps execute as one atomic transaction.', + }, + changeMode: { + type: 'string', + description: + "Required. Use 'direct' for immediate edits or 'tracked' for suggestions. Must always be provided.", + enum: ['direct', 'tracked'], + }, + steps: { + type: 'array', + items: { + oneOf: [ + { + type: 'object', + properties: { + id: { + type: 'string', + }, + op: { + const: 'text.rewrite', + type: 'string', + }, + where: { + oneOf: [ + { + type: 'object', + properties: { + by: { + const: 'select', + type: 'string', + }, + select: { + oneOf: [ + { + type: 'object', + properties: { + type: { + const: 'text', + description: "Must be 'text' for text pattern search.", + type: 'string', + }, + pattern: { + type: 'string', + description: 'Text or regex pattern to match.', + }, + mode: { + description: "Match mode: 'contains' (substring) or 'regex'.", + enum: ['contains', 'regex'], + }, + caseSensitive: { + type: 'boolean', + description: 'Case-sensitive matching. Default: false.', + }, + }, + required: ['type', 'pattern'], + }, + { + type: 'object', + properties: { + type: { + const: 'node', + description: "Must be 'node' for node type search.", + type: 'string', + }, + nodeType: { + description: 'Block type to match (paragraph, heading, table, listItem, etc.).', + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + 'run', + 'bookmark', + 'comment', + 'hyperlink', + 'footnoteRef', + 'endnoteRef', + 'crossRef', + 'indexEntry', + 'citation', + 'authorityEntry', + 'sequenceField', + 'tab', + 'lineBreak', + ], + }, + kind: { + description: "Filter: 'block' or 'inline'.", + enum: ['block', 'inline'], + }, + }, + required: ['type'], + }, + ], + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + require: { + enum: ['first', 'exactlyOne', 'all'], + }, + }, + required: ['by', 'select', 'require'], + }, + { + type: 'object', + properties: { + by: { + const: 'ref', + type: 'string', + }, + ref: { + type: 'string', + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + }, + required: ['by', 'ref'], + }, + { + type: 'object', + properties: { + by: { + const: 'target', + type: 'string', + }, + target: { + type: 'object', + properties: { + kind: { + const: 'selection', + type: 'string', + }, + start: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'table', + 'tableOfContents', + 'sdt', + 'image', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + end: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'table', + 'tableOfContents', + 'sdt', + 'image', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + }, + required: ['kind', 'start', 'end'], + }, + }, + required: ['by', 'target'], + }, + { + type: 'object', + properties: { + by: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['by', 'nodeType', 'nodeId'], + }, + ], + }, + args: { + type: 'object', + properties: { + replacement: { + oneOf: [ + { + type: 'object', + properties: { + text: { + type: 'string', + }, + }, + required: ['text'], + }, + { + type: 'object', + properties: { + blocks: { + type: 'array', + items: { + type: 'object', + properties: { + text: { + type: 'string', + }, + }, + required: ['text'], + }, + }, + }, + required: ['blocks'], + }, + ], + }, + style: { + type: 'object', + properties: { + inline: { + type: 'object', + properties: { + mode: { + enum: ['preserve', 'set', 'clear', 'merge'], + }, + requireUniform: { + type: 'boolean', + }, + onNonUniform: { + enum: ['error', 'useLeadingRun', 'majority', 'union'], + }, + setMarks: { + type: 'object', + properties: { + bold: { + enum: ['on', 'off', 'clear'], + }, + italic: { + enum: ['on', 'off', 'clear'], + }, + underline: { + enum: ['on', 'off', 'clear'], + }, + strike: { + enum: ['on', 'off', 'clear'], + }, + }, + }, + }, + required: ['mode'], + }, + paragraph: { + type: 'object', + properties: { + mode: { + enum: ['preserve', 'set', 'clear'], + }, + }, + required: ['mode'], + }, + }, + required: ['inline'], + }, + }, + required: ['replacement'], + }, + }, + required: ['id', 'op', 'where', 'args'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + }, + op: { + const: 'text.insert', + type: 'string', + }, + where: { + oneOf: [ + { + type: 'object', + properties: { + by: { + const: 'select', + type: 'string', + }, + select: { + oneOf: [ + { + type: 'object', + properties: { + type: { + const: 'text', + description: "Must be 'text' for text pattern search.", + type: 'string', + }, + pattern: { + type: 'string', + description: 'Text or regex pattern to match.', + }, + mode: { + description: "Match mode: 'contains' (substring) or 'regex'.", + enum: ['contains', 'regex'], + }, + caseSensitive: { + type: 'boolean', + description: 'Case-sensitive matching. Default: false.', + }, + }, + required: ['type', 'pattern'], + }, + { + type: 'object', + properties: { + type: { + const: 'node', + description: "Must be 'node' for node type search.", + type: 'string', + }, + nodeType: { + description: 'Block type to match (paragraph, heading, table, listItem, etc.).', + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + 'run', + 'bookmark', + 'comment', + 'hyperlink', + 'footnoteRef', + 'endnoteRef', + 'crossRef', + 'indexEntry', + 'citation', + 'authorityEntry', + 'sequenceField', + 'tab', + 'lineBreak', + ], + }, + kind: { + description: "Filter: 'block' or 'inline'.", + enum: ['block', 'inline'], + }, + }, + required: ['type'], + }, + ], + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + require: { + enum: ['first', 'exactlyOne'], + }, + }, + required: ['by', 'select', 'require'], + }, + { + type: 'object', + properties: { + by: { + const: 'ref', + type: 'string', + }, + ref: { + type: 'string', + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + }, + required: ['by', 'ref'], + }, + { + type: 'object', + properties: { + by: { + const: 'target', + type: 'string', + }, + target: { + type: 'object', + properties: { + kind: { + const: 'selection', + type: 'string', + }, + start: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'table', + 'tableOfContents', + 'sdt', + 'image', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + end: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'table', + 'tableOfContents', + 'sdt', + 'image', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + }, + required: ['kind', 'start', 'end'], + }, + }, + required: ['by', 'target'], + }, + { + type: 'object', + properties: { + by: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['by', 'nodeType', 'nodeId'], + }, + ], + }, + args: { + type: 'object', + properties: { + position: { + enum: ['before', 'after'], + }, + content: { + type: 'object', + properties: { + text: { + type: 'string', + }, + }, + required: ['text'], + }, + style: { + type: 'object', + properties: { + inline: { + type: 'object', + properties: { + mode: { + enum: ['inherit', 'set', 'clear'], + }, + setMarks: { + type: 'object', + properties: { + bold: { + enum: ['on', 'off', 'clear'], + }, + italic: { + enum: ['on', 'off', 'clear'], + }, + underline: { + enum: ['on', 'off', 'clear'], + }, + strike: { + enum: ['on', 'off', 'clear'], + }, + }, + }, + }, + required: ['mode'], + }, + }, + required: ['inline'], + }, + }, + required: ['position', 'content'], + }, + }, + required: ['id', 'op', 'where', 'args'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + }, + op: { + const: 'text.delete', + type: 'string', + }, + where: { + oneOf: [ + { + type: 'object', + properties: { + by: { + const: 'select', + type: 'string', + }, + select: { + oneOf: [ + { + type: 'object', + properties: { + type: { + const: 'text', + description: "Must be 'text' for text pattern search.", + type: 'string', + }, + pattern: { + type: 'string', + description: 'Text or regex pattern to match.', + }, + mode: { + description: "Match mode: 'contains' (substring) or 'regex'.", + enum: ['contains', 'regex'], + }, + caseSensitive: { + type: 'boolean', + description: 'Case-sensitive matching. Default: false.', + }, + }, + required: ['type', 'pattern'], + }, + { + type: 'object', + properties: { + type: { + const: 'node', + description: "Must be 'node' for node type search.", + type: 'string', + }, + nodeType: { + description: 'Block type to match (paragraph, heading, table, listItem, etc.).', + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + 'run', + 'bookmark', + 'comment', + 'hyperlink', + 'footnoteRef', + 'endnoteRef', + 'crossRef', + 'indexEntry', + 'citation', + 'authorityEntry', + 'sequenceField', + 'tab', + 'lineBreak', + ], + }, + kind: { + description: "Filter: 'block' or 'inline'.", + enum: ['block', 'inline'], + }, + }, + required: ['type'], + }, + ], + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + require: { + enum: ['first', 'exactlyOne', 'all'], + }, + }, + required: ['by', 'select', 'require'], + }, + { + type: 'object', + properties: { + by: { + const: 'ref', + type: 'string', + }, + ref: { + type: 'string', + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + }, + required: ['by', 'ref'], + }, + { + type: 'object', + properties: { + by: { + const: 'target', + type: 'string', + }, + target: { + type: 'object', + properties: { + kind: { + const: 'selection', + type: 'string', + }, + start: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'table', + 'tableOfContents', + 'sdt', + 'image', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + end: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'table', + 'tableOfContents', + 'sdt', + 'image', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + }, + required: ['kind', 'start', 'end'], + }, + }, + required: ['by', 'target'], + }, + { + type: 'object', + properties: { + by: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['by', 'nodeType', 'nodeId'], + }, + ], + }, + args: { + type: 'object', + properties: { + behavior: { + enum: ['selection', 'exact'], + }, + }, + }, + }, + required: ['id', 'op', 'where', 'args'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + }, + op: { + const: 'format.apply', + type: 'string', + }, + where: { + oneOf: [ + { + type: 'object', + properties: { + by: { + const: 'select', + type: 'string', + }, + select: { + oneOf: [ + { + type: 'object', + properties: { + type: { + const: 'text', + description: "Must be 'text' for text pattern search.", + type: 'string', + }, + pattern: { + type: 'string', + description: 'Text or regex pattern to match.', + }, + mode: { + description: "Match mode: 'contains' (substring) or 'regex'.", + enum: ['contains', 'regex'], + }, + caseSensitive: { + type: 'boolean', + description: 'Case-sensitive matching. Default: false.', + }, + }, + required: ['type', 'pattern'], + }, + { + type: 'object', + properties: { + type: { + const: 'node', + description: "Must be 'node' for node type search.", + type: 'string', + }, + nodeType: { + description: 'Block type to match (paragraph, heading, table, listItem, etc.).', + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + 'run', + 'bookmark', + 'comment', + 'hyperlink', + 'footnoteRef', + 'endnoteRef', + 'crossRef', + 'indexEntry', + 'citation', + 'authorityEntry', + 'sequenceField', + 'tab', + 'lineBreak', + ], + }, + kind: { + description: "Filter: 'block' or 'inline'.", + enum: ['block', 'inline'], + }, + }, + required: ['type'], + }, + ], + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + require: { + enum: ['first', 'exactlyOne', 'all'], + }, + }, + required: ['by', 'select', 'require'], + }, + { + type: 'object', + properties: { + by: { + const: 'ref', + type: 'string', + }, + ref: { + type: 'string', + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + }, + required: ['by', 'ref'], + }, + { + type: 'object', + properties: { + by: { + const: 'target', + type: 'string', + }, + target: { + type: 'object', + properties: { + kind: { + const: 'selection', + type: 'string', + }, + start: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'table', + 'tableOfContents', + 'sdt', + 'image', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + end: { + oneOf: [ + { + type: 'object', + properties: { + kind: { + const: 'text', + type: 'string', + }, + blockId: { + type: 'string', + }, + offset: { + type: 'number', + }, + }, + required: ['kind', 'blockId', 'offset'], + }, + { + type: 'object', + properties: { + kind: { + const: 'nodeEdge', + type: 'string', + }, + node: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'table', + 'tableOfContents', + 'sdt', + 'image', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + edge: { + enum: ['before', 'after'], + }, + }, + required: ['kind', 'node', 'edge'], + }, + ], + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", + }, + }, + required: ['kind', 'start', 'end'], + }, + }, + required: ['by', 'target'], + }, + { + type: 'object', + properties: { + by: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['by', 'nodeType', 'nodeId'], + }, + ], + }, + args: { + type: 'object', + properties: { + inline: { + type: 'object', + properties: { + bold: { + type: 'boolean', + }, + italic: { + type: 'boolean', + }, + strike: { + type: 'boolean', + }, + underline: { + oneOf: [ + { + type: 'boolean', + }, + { + type: 'object', + properties: { + style: { + type: 'string', + }, + color: { + type: 'string', + }, + themeColor: { + type: 'string', + }, + }, + }, + ], + }, + highlight: { + type: 'string', + }, + color: { + type: 'string', + }, + fontSize: { + type: 'number', + }, + fontFamily: { + type: 'string', + }, + letterSpacing: { + type: 'number', + }, + vertAlign: { + enum: ['superscript', 'subscript', 'baseline'], + }, + position: { + type: 'number', + }, + dstrike: { + type: 'boolean', + }, + smallCaps: { + type: 'boolean', + }, + caps: { + type: 'boolean', + }, + shading: { + type: 'object', + properties: { + fill: { + type: 'string', + }, + color: { + type: 'string', + }, + val: { + type: 'string', + }, + }, + }, + border: { + type: 'object', + properties: { + val: { + type: 'string', + }, + sz: { + type: 'number', + }, + color: { + type: 'string', + }, + space: { + type: 'number', + }, + }, + }, + outline: { + type: 'boolean', + }, + shadow: { + type: 'boolean', + }, + emboss: { + type: 'boolean', + }, + imprint: { + type: 'boolean', + }, + charScale: { + type: 'number', + }, + kerning: { + type: 'number', + }, + vanish: { + type: 'boolean', + }, + webHidden: { + type: 'boolean', + }, + specVanish: { + type: 'boolean', + }, + rtl: { + type: 'boolean', + }, + cs: { + type: 'boolean', + }, + bCs: { + type: 'boolean', + }, + iCs: { + type: 'boolean', + }, + eastAsianLayout: { + type: 'object', + properties: { + id: { + type: 'string', + }, + combine: { + type: 'boolean', + }, + combineBrackets: { + type: 'string', + }, + vert: { + type: 'boolean', + }, + vertCompress: { + type: 'boolean', + }, + }, + }, + em: { + type: 'string', + }, + fitText: { + type: 'object', + properties: { + val: { + type: 'number', + }, + id: { + type: 'string', + }, + }, + }, + snapToGrid: { + type: 'boolean', + }, + lang: { + type: 'object', + properties: { + val: { + type: 'string', + }, + eastAsia: { + type: 'string', + }, + bidi: { + type: 'string', + }, + }, + }, + oMath: { + type: 'boolean', + }, + rStyle: { + type: 'string', + }, + rFonts: { + type: 'object', + properties: { + ascii: { + type: 'string', + }, + hAnsi: { + type: 'string', + }, + eastAsia: { + type: 'string', + }, + cs: { + type: 'string', + }, + asciiTheme: { + type: 'string', + }, + hAnsiTheme: { + type: 'string', + }, + eastAsiaTheme: { + type: 'string', + }, + csTheme: { + type: 'string', + }, + hint: { + type: 'string', + }, + }, + }, + fontSizeCs: { + type: 'number', + }, + ligatures: { + type: 'string', + }, + numForm: { + type: 'string', + }, + numSpacing: { + type: 'string', + }, + stylisticSets: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number', + }, + val: { + type: 'boolean', + }, + }, + required: ['id'], + }, + }, + contextualAlternates: { + type: 'boolean', + }, + }, + }, + alignment: { + description: + 'Set paragraph alignment on the target block(s). Can be combined with inline formatting in the same step.', + enum: ['left', 'center', 'right', 'justify'], + }, + scope: { + description: + 'When "block", inline formatting expands to cover the entire parent paragraph(s), not just the matched text. Use "block" after markdown inserts to format whole paragraphs with a short identifying pattern. Default: "match".', + enum: ['match', 'block'], + }, + }, + }, + }, + required: ['id', 'op', 'where', 'args'], + }, + { + type: 'object', + properties: { + id: { + type: 'string', + }, + op: { + const: 'assert', + type: 'string', + }, + where: { + type: 'object', + properties: { + by: { + const: 'select', + type: 'string', + }, + select: { + oneOf: [ + { + type: 'object', + properties: { + type: { + const: 'text', + description: "Must be 'text' for text pattern search.", + type: 'string', + }, + pattern: { + type: 'string', + description: 'Text or regex pattern to match.', + }, + mode: { + description: "Match mode: 'contains' (substring) or 'regex'.", + enum: ['contains', 'regex'], + }, + caseSensitive: { + type: 'boolean', + description: 'Case-sensitive matching. Default: false.', + }, + }, + required: ['type', 'pattern'], + }, + { + type: 'object', + properties: { + type: { + const: 'node', + description: "Must be 'node' for node type search.", + type: 'string', + }, + nodeType: { + description: 'Block type to match (paragraph, heading, table, listItem, etc.).', + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + 'run', + 'bookmark', + 'comment', + 'hyperlink', + 'footnoteRef', + 'endnoteRef', + 'crossRef', + 'indexEntry', + 'citation', + 'authorityEntry', + 'sequenceField', + 'tab', + 'lineBreak', + ], + }, + kind: { + description: "Filter: 'block' or 'inline'.", + enum: ['block', 'inline'], + }, + }, + required: ['type'], + }, + ], + }, + within: { + type: 'object', + properties: { + kind: { + const: 'block', + type: 'string', + }, + nodeType: { + enum: [ + 'paragraph', + 'heading', + 'listItem', + 'table', + 'tableRow', + 'tableCell', + 'tableOfContents', + 'image', + 'sdt', + ], + }, + nodeId: { + type: 'string', + }, + }, + required: ['kind', 'nodeType', 'nodeId'], + }, + }, + required: ['by', 'select'], + }, + args: { + type: 'object', + properties: { + expectCount: { + type: 'number', + }, + }, + required: ['expectCount'], + }, + }, + required: ['id', 'op', 'where', 'args'], + }, + ], + }, + description: + "Ordered array of mutation steps. Each step needs 'op' (text.rewrite, text.insert, text.delete, format.apply, or assert) and a 'where' targeting clause.", + }, + force: { + type: 'boolean', + description: "Bypass confirmation checks. Only for action 'apply'. Omit for other actions.", + }, + }, + required: ['action', 'atomic', 'changeMode', 'steps'], + additionalProperties: false, + }, + mutates: true, + operations: [ + { + operationId: 'doc.mutations.preview', + intentAction: 'preview', + required: ['atomic', 'changeMode', 'steps'], + }, + { + operationId: 'doc.mutations.apply', + intentAction: 'apply', + required: ['atomic', 'steps', 'changeMode'], + }, + ], + }, + ], +} as const; diff --git a/apps/mcp/src/generated/intent-dispatch.generated.ts b/apps/mcp/src/generated/intent-dispatch.generated.ts new file mode 100644 index 0000000000..53b134d02a --- /dev/null +++ b/apps/mcp/src/generated/intent-dispatch.generated.ts @@ -0,0 +1,155 @@ +// Auto-generated by generate-intent-tools.mjs — do not edit. +// MCP-local copy bundled with the generated tool catalog. + +export function dispatchIntentTool( + toolName: string, + args: Record, + execute: (operationId: string, input: Record) => unknown, +): unknown { + switch (toolName) { + case 'superdoc_get_content': { + const { action, ...rest } = args; + switch (action) { + case 'text': + return execute('doc.getText', rest); + case 'markdown': + return execute('doc.getMarkdown', rest); + case 'html': + return execute('doc.getHtml', rest); + case 'info': + return execute('doc.info', rest); + case 'extract': + return execute('doc.extract', rest); + case 'blocks': + return execute('doc.blocks.list', rest); + default: + throw new Error(`Unknown action for superdoc_get_content: ${action}`); + } + } + case 'superdoc_edit': { + const { action, ...rest } = args; + switch (action) { + case 'insert': + return execute('doc.insert', rest); + case 'replace': + return execute('doc.replace', rest); + case 'delete': + return execute('doc.delete', rest); + case 'undo': + return execute('doc.history.undo', rest); + case 'redo': + return execute('doc.history.redo', rest); + default: + throw new Error(`Unknown action for superdoc_edit: ${action}`); + } + } + case 'superdoc_format': { + const { action, ...rest } = args; + switch (action) { + case 'inline': + return execute('doc.format.apply', rest); + case 'set_style': + return execute('doc.styles.paragraph.setStyle', rest); + case 'set_alignment': + return execute('doc.format.paragraph.setAlignment', rest); + case 'set_indentation': + return execute('doc.format.paragraph.setIndentation', rest); + case 'set_spacing': + return execute('doc.format.paragraph.setSpacing', rest); + case 'set_flow_options': + return execute('doc.format.paragraph.setFlowOptions', rest); + case 'set_direction': + return execute('doc.format.paragraph.setDirection', rest); + default: + throw new Error(`Unknown action for superdoc_format: ${action}`); + } + } + case 'superdoc_create': { + const { action, ...rest } = args; + switch (action) { + case 'paragraph': + return execute('doc.create.paragraph', rest); + case 'heading': + return execute('doc.create.heading', rest); + case 'table': + return execute('doc.create.table', rest); + default: + throw new Error(`Unknown action for superdoc_create: ${action}`); + } + } + case 'superdoc_list': { + const { action, ...rest } = args; + switch (action) { + case 'insert': + return execute('doc.lists.insert', rest); + case 'create': + return execute('doc.lists.create', rest); + case 'attach': + return execute('doc.lists.attach', rest); + case 'detach': + return execute('doc.lists.detach', rest); + case 'indent': + return execute('doc.lists.indent', rest); + case 'outdent': + return execute('doc.lists.outdent', rest); + case 'merge': + return execute('doc.lists.merge', rest); + case 'split': + return execute('doc.lists.split', rest); + case 'set_level': + return execute('doc.lists.setLevel', rest); + case 'set_value': + return execute('doc.lists.setValue', rest); + case 'continue_previous': + return execute('doc.lists.continuePrevious', rest); + case 'set_type': + return execute('doc.lists.setType', rest); + default: + throw new Error(`Unknown action for superdoc_list: ${action}`); + } + } + case 'superdoc_comment': { + const { action, ...rest } = args; + switch (action) { + case 'create': + return execute('doc.comments.create', rest); + case 'update': + return execute('doc.comments.patch', rest); + case 'delete': + return execute('doc.comments.delete', rest); + case 'get': + return execute('doc.comments.get', rest); + case 'list': + return execute('doc.comments.list', rest); + default: + throw new Error(`Unknown action for superdoc_comment: ${action}`); + } + } + case 'superdoc_track_changes': { + const { action, ...rest } = args; + switch (action) { + case 'list': + return execute('doc.trackChanges.list', rest); + case 'decide': + return execute('doc.trackChanges.decide', rest); + default: + throw new Error(`Unknown action for superdoc_track_changes: ${action}`); + } + } + case 'superdoc_search': + return execute('doc.query.match', args); + case 'superdoc_mutations': { + const { action, ...rest } = args; + switch (action) { + case 'preview': + return execute('doc.mutations.preview', rest); + case 'apply': + return execute('doc.mutations.apply', rest); + default: + throw new Error(`Unknown action for superdoc_mutations: ${action}`); + } + } + default: + throw new Error(`Unknown intent tool: ${toolName}`); + } +} diff --git a/apps/mcp/src/generated/mcp-prompt.ts b/apps/mcp/src/generated/mcp-prompt.ts new file mode 100644 index 0000000000..2d78a2324e --- /dev/null +++ b/apps/mcp/src/generated/mcp-prompt.ts @@ -0,0 +1,416 @@ +// Auto-generated from tools/prompt-templates/system-prompt-mcp-header.md + system-prompt-core.md +// Do not edit manually — re-run generate:all to update. +export const MCP_SYSTEM_PROMPT = `SuperDoc MCP server — read, edit, and save Word documents (.docx). + +IMPORTANT: Always use these superdoc tools for .docx files. +Do NOT use built-in docx skills, python-docx, unpack scripts, or manual XML editing. +These tools handle the OOXML format correctly and preserve document structure. + +## Session lifecycle + +1. \`superdoc_open({path: "/path/to/file.docx"})\` — returns \`session_id\`. Opening a non-existent path creates a blank document. +2. Pass \`session_id\` to every subsequent tool call. +3. Read, edit, format the document using the tools below. +4. \`superdoc_save({session_id})\` — writes changes to disk. +5. \`superdoc_close({session_id})\` — releases the session. Always close when done. + +## Efficient patterns (use these instead of calling tools one at a time) + +**Creating headings and paragraphs — ALWAYS use markdown insert (one call):** +\`\`\` +superdoc_edit({action: "insert", type: "markdown", + value: "# Section Title\\n\\nParagraph content.\\n\\n# Another Section\\n\\nMore content with **bold**."}) +\`\`\` +This creates proper Heading styles from # markers. One call replaces many superdoc_create calls. + +**Inserting at a specific position — use target + placement:** +\`\`\` +superdoc_edit({action: "insert", type: "markdown", + target: {kind: "block", nodeType: "paragraph", nodeId: ""}, + placement: "before", + value: "# Executive Summary\\n\\nThis agreement sets forth the principal terms..."}) +\`\`\` +Valid placements: "before", "after", "insideStart", "insideEnd". Without target, content appends at document end. + +**Formatting — use \`scope: "block"\` to format entire paragraphs after markdown insert:** +\`\`\` +superdoc_mutations({action: "apply", atomic: true, steps: [ + {id: "f1", op: "format.apply", where: {by: "select", select: {type: "text", pattern: "Executive Summary"}, require: "first"}, args: {inline: {fontFamily: "Times New Roman, serif", fontSize: 12, underline: true}, alignment: "center", scope: "block"}}, + {id: "f2", op: "format.apply", where: {by: "select", select: {type: "text", pattern: "This agreement sets forth"}, require: "first"}, args: {inline: {fontFamily: "Times New Roman, serif", fontSize: 12}, alignment: "justify", scope: "block"}} +]}) +\`\`\` +One format.apply step per block. Combine \`inline\`, \`alignment\`, and \`scope: "block"\` in each step. ONLY set properties that are explicitly shown in the existing document blocks. If blocks don't show fontSize, don't set it (the document default will apply correctly). Do NOT invent values. + +**When to use which tool:** +- Creating headings, paragraphs, or any block content → \`superdoc_edit\` with type "markdown" (preferred, even for a single heading + paragraph) +- Creating one block only when markdown is insufficient → \`superdoc_create\` +- ALL formatting after insert → \`superdoc_mutations\` with format.apply (inline + alignment in one step per block) +- Single quick format (no insert before it) → \`superdoc_format\` +- Multiple text edits → \`superdoc_mutations\` +- Single text edit → \`superdoc_edit\` + +## Tools overview + +| Tool | Purpose | Mutates | +|------|---------|---------| +| superdoc_get_content | Read document content (blocks, text, markdown, html, info) | No | +| superdoc_search | Find text or nodes, get ref handles for targeting | No | +| superdoc_edit | Insert, replace, delete text, undo/redo | Yes | +| superdoc_create | Create paragraphs, headings, or tables | Yes | +| superdoc_format | Apply inline and paragraph formatting, set named styles | Yes | +| superdoc_list | Create and manipulate bullet/numbered lists | Yes | +| superdoc_comment | Create, update, delete, and list comment threads | Yes | +| superdoc_track_changes | List, accept, or reject tracked changes | Yes | +| superdoc_mutations | Execute multi-step atomic edits in a single batch | Yes | + +## How targeting works + +Every editing tool needs a **target** telling the API *where* to apply the change. There are three ways to get one: + +- **From blocks data**: Each block has a \`ref\` (pass directly to superdoc_edit or superdoc_format), a \`nodeId\` (for building \`at\` positions with superdoc_create or \`where: {by: "block", ...}\` in superdoc_mutations), and optional full \`text\` when you call \`superdoc_get_content({action: "blocks", includeText: true})\`. +- **From superdoc_search**: Returns \`handle.ref\` covering the matched text. Use search when you need to find text patterns, not when you already know which block to target. +- **From superdoc_create**: Returns \`nodeId\` and \`ref\`. The ref is valid for one immediate format call. For subsequent operations, re-fetch blocks to get fresh refs. + +**Refs expire after any mutation** between separate tool calls. Within a superdoc_mutations batch, selectors resolve automatically — no manual re-searching between steps. + +**Critical targeting rule:** when rewriting an entire paragraph, clause, or other known block, first read \`superdoc_get_content({action: "blocks", includeText: true})\`, identify the block's \`nodeId\`, then use \`where: {by: "block", nodeType, nodeId}\` in \`superdoc_mutations\`. Do NOT use a shortened text selector to rewrite a whole clause. + +## Common workflows + +### Replace a word everywhere + +\`\`\` +superdoc_search({select: {type: "text", pattern: "old word"}, require: "all"}) +superdoc_edit({action: "replace", ref: "", text: "new word"}) +\`\`\` + +Use \`require: "all"\` with a single edit, not multiple steps targeting the same pattern. + +### Rewrite a full paragraph + +\`\`\` +superdoc_get_content({action: "blocks", includeText: true}) +// Find the paragraph/clause by its full text, then use its nodeId +superdoc_mutations({ + action: "apply", atomic: true, + steps: [ + { + id: "r1", + op: "text.rewrite", + where: {by: "block", nodeType: "paragraph", nodeId: ""}, + args: {replacement: {text: "Entirely new paragraph text."}} + } + ] +}) +\`\`\` + +Use \`includeText:true\` so you can identify the right block from one read call. A block ref from superdoc_get_content covers the entire block text, but for multi-step rewrites and contract redlines, prefer \`where: {by: "block", ...}\` in \`superdoc_mutations\` because it is stable and avoids brittle text matching. A search ref covers only the matched substring. Do NOT use a shortened search/text selector to replace an entire known block. + +### Redline a contract clause + +\`\`\` +superdoc_get_content({action: "blocks", includeText: true}) +// Identify the clause block using blocks[i].text and blocks[i].nodeId +superdoc_mutations({ + action: "apply", atomic: true, changeMode: "tracked", + steps: [ + { + id: "clause1", + op: "text.rewrite", + where: {by: "block", nodeType: "listItem", nodeId: ""}, + args: {replacement: {text: "Customer agrees to ..."}} + } + ] +}) +\`\`\` + +If you only know a short anchor, use \`superdoc_search\` to locate the clause, then convert that result to the containing block \`nodeId\` before calling \`text.rewrite\`. Use \`by:"select"\` for discovery, not for whole-clause replacement. + +### Add a new paragraph after a heading + +\`\`\` +superdoc_search({select: {type: "text", pattern: "Introduction"}, require: "first"}) +// Get blockId from result.items[0].blocks[0].blockId +superdoc_create({action: "paragraph", text: "New content here.", at: {kind: "after", target: {kind: "block", nodeType: "heading", nodeId: ""}}}) +// Re-fetch blocks to get a fresh ref for the new paragraph +superdoc_get_content({action: "blocks", offset: 0, limit: 5}) +// Find the new paragraph in the response, use its ref and nodeId +// Read formatting from BODY TEXT paragraphs (non-title, alignment "justify" or "left"), not from headings +superdoc_format({action: "inline", ref: "", inline: {fontFamily: "", fontSize: , color: "", bold: false}}) +superdoc_format({action: "set_alignment", target: {kind: "block", nodeType: "paragraph", nodeId: ""}, alignment: ""}) +\`\`\` + +### Create multiple paragraphs in sequence + +Create all paragraphs first (chaining nodeIds), then re-fetch blocks once and format them all: + +\`\`\` +// Step 1: Create all paragraphs, chaining with nodeId +superdoc_create({action: "paragraph", text: "First item.", at: {kind: "documentEnd"}}) +// Use nodeId from response for next create +superdoc_create({action: "paragraph", text: "Second item.", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: ""}}}) +superdoc_create({action: "paragraph", text: "Third item.", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: ""}}}) + +// Step 2: Re-fetch blocks to get fresh refs for all new paragraphs +superdoc_get_content({action: "blocks", offset: 0, limit: 10}) + +// Step 3: Format each paragraph using fresh refs from blocks +// Read formatting from BODY TEXT paragraphs (alignment "justify" or "left", not titles) +superdoc_format({action: "inline", ref: "", inline: {fontFamily: "", fontSize: , color: "", bold: false}}) +superdoc_format({action: "set_alignment", target: {kind: "block", nodeType: "paragraph", nodeId: ""}, alignment: ""}) +// Repeat for each paragraph... +\`\`\` + +### Write content into a blank document + +Do not use \`superdoc_search\` to find empty initial paragraphs — search matches text, and blank blocks have none. Use \`superdoc_get_content\` for blank-block discovery. + +\`\`\` +// Step 1: First create — omit positional \`at\` targeting on a blank document +superdoc_create({action: "paragraph", text: "First paragraph."}) + +// Step 2: Fetch blocks to get nodeIds for subsequent relative inserts +superdoc_get_content({action: "blocks"}) + +// Step 3: Chain further creates using nodeIds from blocks +superdoc_create({action: "paragraph", text: "Second paragraph.", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: ""}}}) +\`\`\` + +### Bold or format existing text + +\`\`\` +superdoc_search({select: {type: "text", pattern: "important phrase"}, require: "first"}) +superdoc_format({action: "inline", ref: "", inline: {bold: true}}) +\`\`\` + +### Set paragraph alignment, spacing, or page breaks + +Paragraph-level actions require a **block target with nodeId**, not a ref: + +\`\`\` +superdoc_format({action: "set_alignment", target: {kind: "block", nodeType: "paragraph", nodeId: ""}, alignment: "center"}) +superdoc_format({action: "set_flow_options", target: {kind: "block", nodeType: "paragraph", nodeId: ""}, pageBreakBefore: true}) +superdoc_format({action: "set_spacing", target: {kind: "block", nodeType: "paragraph", nodeId: ""}, lineSpacing: {rule: "auto", value: 1.5}}) +\`\`\` + +### Create a bullet or numbered list + +1. Create all paragraphs at the SAME location, chaining with previous nodeId: +\`\`\` +superdoc_create({action: "paragraph", text: "Item one", at: {kind: "documentEnd"}}) +superdoc_create({action: "paragraph", text: "Item two", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: ""}}}) +superdoc_create({action: "paragraph", text: "Item three", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: ""}}}) +\`\`\` + +2. Convert the consecutive paragraphs to a list in one call: +\`\`\` +superdoc_list({action: "create", mode: "fromParagraphs", preset: "disc", target: {from: {kind: "block", nodeType: "paragraph", nodeId: ""}, to: {kind: "block", nodeType: "paragraph", nodeId: ""}}}) +\`\`\` + +Use preset "disc" for bullets, "decimal" for numbered. WARNING: the range converts ALL paragraphs between from and to. Make sure no other content exists between them. + +3. To change a bullet list to numbered: \`superdoc_list({action: "set_type", target: {kind: "block", nodeType: "listItem", nodeId: ""}, kind: "ordered"})\` + +### Add items to an existing list + +To add a new item adjacent to an existing list item, use \`superdoc_list({action: "insert"})\`, NOT \`superdoc_create({action: "paragraph"})\` — the latter creates a standalone paragraph that is not part of the list: + +\`\`\` +superdoc_get_content({action: "blocks"}) // find the listItem nodeId you want to insert next to +superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: ""}, position: "after", text: "New item text"}) +\`\`\` + +**Level inheritance.** The new item inherits the target's nesting level. Insert after a level-0 item → new item is level 0. Insert after a level-2 item → new item is level 2. To change the level, chain \`indent\` / \`outdent\` / \`set_level\` on the nodeId returned in the insert response. + +**Use the nodeId from the response directly.** \`superdoc_list({action: "insert"})\` returns \`{item: {nodeId: ""}}\` — that id is ready for subsequent \`indent\`, \`outdent\`, \`set_level\`, or text edits. You do NOT need to re-fetch blocks between the insert and the follow-up operation. + +### Add a sub-point under an existing item + +Insert a peer, then indent it one level: + +\`\`\` +// 1. Insert a peer item after the parent — new item is at the parent's level +const resp = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: ""}, position: "after", text: "Sub-point"}) + +// 2. Indent using the nodeId from resp.item.nodeId +superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: ""}}) +\`\`\` + +### Build a nested list with mixed levels + +\`lists.create\` produces a flat list. Add nesting by chaining \`insert\` + \`indent\` / \`set_level\`, using the nodeId returned by each insert to target the next step: + +\`\`\` +// Starting point: a list item at level 0 ("Parent" with nodeId ) + +// Sibling at level 0 +const r1 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: ""}, position: "after", text: "Sibling"}) + +// Child at level 1 (insert after r1, then indent) +const r2 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: ""}, position: "after", text: "Child"}) +superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: ""}}) + +// Grandchild at level 3 (insert after r2, then jump to level 3 directly) +const r3 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: ""}, position: "after", text: "Deep"}) +superdoc_list({action: "set_level", target: {kind: "block", nodeType: "listItem", nodeId: ""}, level: 3}) +\`\`\` + +\`indent\` bumps the level by one (bounded 0–8). \`set_level\` jumps directly to any level 0–8. Markers update automatically based on the list's definition for each level (e.g. \`1.\` / \`a.\` / \`i.\` for an ordered list). + +### Merge two adjacent lists into one + +Use \`merge\` — it handles the common case where two ordered or bulleted lists sit next to each other and should become one continuous list. Absorbed items adopt the absorbing sequence's definition, and any empty paragraphs between the two lists are removed so numbering flows continuously. + +\`\`\` +superdoc_get_content({action: "blocks"}) // find a listItem in either sequence +// To merge with the previous sequence: +superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: ""}, direction: "withPrevious"}) +// Or with the next sequence: +superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: ""}, direction: "withNext"}) +\`\`\` + +### Split a list into two + +Use \`split\` to break one list into two independent lists at a specific item. The target and everything after become a new sequence that restarts numbering at 1: + +\`\`\` +superdoc_list({action: "split", target: {kind: "block", nodeType: "listItem", nodeId: ""}}) +\`\`\` + +Pass \`restartNumbering: false\` if you want the new half to keep counting from where the original left off. + +### Restart numbering at a specific item + +For ordered lists. To make item N restart from a chosen number (commonly 1): + +\`\`\` +superdoc_list({action: "set_value", target: {kind: "block", nodeType: "listItem", nodeId: ""}, value: 1}) +\`\`\` + +Pass \`value: null\` to clear a previously-set restart override and let the item resume natural numbering. + +### Continue numbering across a break + +For ordered lists. When two sibling sequences should be numbered as one (e.g. numbering jumps back to 1 and you want it to continue from where the previous list left off), target the FIRST item of the second sequence: + +\`\`\` +superdoc_list({action: "continue_previous", target: {kind: "block", nodeType: "listItem", nodeId: ""}}) +\`\`\` + +Fails with \`NO_COMPATIBLE_PREVIOUS\` or \`INCOMPATIBLE_DEFINITIONS\` if no prior sequence shares the same abstract definition. In that case, use \`merge\` instead — it handles mismatched definitions, removes empty gap paragraphs, and produces one continuous list. + +### Insert content into a document (new or existing) + +Markdown insert creates block structure but uses default formatting. You MUST follow up with formatting so inserted content looks like it belongs in the document. + +**Step 1: Understand the document context** from the get_content blocks response. Before inserting anything, analyze: +- What kind of document is this? (contract, letter, certificate, report, etc.) +- How are titles/headings styled? (centered? left? bold? underlined? what fontSize?) +- Are titles UPPERCASE? (e.g., "EMPLOYMENT AGREEMENT", "RECITALS" → your heading must also be UPPERCASE) +- How is body text styled? (fontFamily, fontSize, alignment, color) +- What formatting conventions does the document follow? + +Your inserted content must be indistinguishable from the existing content. If titles are ALL CAPS centered 10pt, your heading text must also be ALL CAPS centered 10pt. If body text is justified 12pt, your paragraphs must be justified 12pt. + +**Step 2: Insert content with markdown:** + +\`\`\` +superdoc_edit({action: "insert", type: "markdown", + target: {kind: "block", nodeType: "paragraph", nodeId: ""}, + placement: "before", + value: "# Executive Summary\\n\\nThis agreement sets forth the principal terms..."}) +\`\`\` + +**Step 3: Format ALL inserted blocks in ONE superdoc_mutations call.** Each format.apply step accepts \`inline\`, \`alignment\`, and \`scope: "block"\`. + +Use \`scope: "block"\` so formatting covers the entire paragraph (not just the matched text). The text pattern only needs to identify which block. Copy the exact property values from the existing blocks in the get_content response. Do NOT invent values. + +Example: document blocks show fontFamily, fontSize: 10, color, titles centered: +\`\`\` +superdoc_mutations({action: "apply", atomic: true, steps: [ + {id: "f1", op: "format.apply", where: {by: "select", select: {type: "text", pattern: "Executive Summary"}, require: "first"}, args: {inline: {fontFamily: "Times New Roman, serif", fontSize: 10, color: "#000000"}, alignment: "center", scope: "block"}}, + {id: "f2", op: "format.apply", where: {by: "select", select: {type: "text", pattern: "This agreement sets forth"}, require: "first"}, args: {inline: {fontFamily: "Times New Roman, serif", fontSize: 10, color: "#000000"}, scope: "block"}} +]}) +\`\`\` + +Total: 3 calls (read + insert + format-all-in-one-batch). Never more. + +### Batch multiple text edits atomically + +Use superdoc_mutations for 2+ text changes, format changes, or a combination: + +\`\`\` +superdoc_get_content({action: "blocks", includeText: true}) +superdoc_mutations({ + action: "apply", atomic: true, changeMode: "direct", + steps: [ + {id: "s1", op: "text.rewrite", where: {by: "block", nodeType: "paragraph", nodeId: ""}, args: {replacement: {text: "Updated full paragraph text."}}}, + {id: "s2", op: "text.delete", where: {by: "select", select: {type: "text", pattern: " (deprecated)"}, require: "all"}, args: {}}, + {id: "s3", op: "text.insert", where: {by: "select", select: {type: "text", pattern: "Section Title"}, require: "first"}, args: {position: "after", content: {text: " (Updated)"}}} + ] +}) +\`\`\` + +Use \`by:"block"\` for whole-paragraph / whole-clause rewrites. Use \`by:"select"\` only for substring edits, discovery, or insertion relative to a sentence fragment. + +Selectors resolve at compile time (before execution). This means format.apply steps CANNOT target content created by create steps in the same batch — the new content does not exist yet when selectors compile. Split creates and formatting into separate batches. + +Never create two steps targeting overlapping text in the same block. Combine them into a single text.rewrite instead. + +### Add a comment on specific text + +\`\`\` +superdoc_search({select: {type: "text", pattern: "target phrase"}, require: "first"}) +superdoc_comment({ + action: "create", + text: "Please review this section.", + target: {kind: "text", blockId: "", range: {start: , end: }} +}) +\`\`\` + +Only pass \`action\`, \`text\`, and \`target\` when creating a new top-level comment. For threaded replies, add \`parentId\`. + +### Accept or reject tracked changes + +\`\`\` +superdoc_track_changes({action: "list"}) +// Review changes, then accept or reject +superdoc_track_changes({action: "decide", decision: "accept", target: {id: ""}}) +// Or accept all at once +superdoc_track_changes({action: "decide", decision: "accept", target: {scope: "all"}}) +\`\`\` + +### Match existing document formatting (CRITICAL) + +When creating content "like" or "similar to" existing content: + +1. Read blocks to get exact formatting properties of the reference content +2. Use the same nodeType. Title blocks are often bold+underline paragraphs, not heading nodes. Check the blocks data. +3. Copy ALL formatting exactly: bold, underline, fontSize, fontFamily, color, alignment + +### Choosing formatting values (CRITICAL) + +When formatting newly created content, use the right source: + +- **Body text** (paragraphs, lorem ipsum, regular content): Read fontFamily, fontSize, color from non-empty, non-title paragraphs with alignment "justify" or "left". Always set \`bold: false\` and \`underline: false\` for body text. Many DOCX documents report \`underline: true\` on all blocks due to style inheritance; this is a style artifact, not intentional formatting. Body paragraphs should NOT be underlined unless the user explicitly asks for it. +- **Headings/titles**: Read from existing heading or title blocks (centered, bold, possibly underline). Scale fontSize up from body text. +- **Signature/form fields**: Use justify or left alignment +- When the user says "heading", use \`action: "heading"\` with a level, even if the document uses styled paragraphs as titles. + +## Constraints + +- **Format calls must be sequential.** Each format call bumps the document revision and invalidates all outstanding refs. Do NOT issue multiple superdoc_format calls in parallel. Format one block, then re-fetch if needed for the next block. +- **set_alignment target must be \`{kind: "block", nodeType, nodeId}\`.** NEVER use \`{kind: "block", start: {kind: "nodeEdge", ...}}\` or any selection-like structure. Only the flat block target with nodeType and nodeId is accepted. +- **Always format ALL created items.** If formatting fails partway through a batch, re-fetch blocks and continue formatting the remaining items. Do not stop after a partial failure. +- **Search patterns are plain text.** Do not include \`#\`, \`**\`, or formatting markers. +- **\`select.type\` must be "text" or "node".** To find headings: \`{type: "node", nodeType: "heading"}\`, NOT \`{type: "heading"}\`. +- **\`within\` scopes to a single block**, not a section. To find text in a section, search the full document. +- **Table cells are separate blocks.** Search for individual cell values, not patterns spanning multiple cells. +- **Do NOT combine \`limit\`/\`offset\` with \`require: "first"\` or \`require: "exactlyOne"\`.** Use \`require: "any"\` with \`limit\` for paginated results. +- **Do NOT hardcode formatting values.** Always read from blocks data and replicate. +- **Do NOT copy heading/title formatting onto body paragraphs.** Read from body text blocks (alignment "justify" or "left"), not title blocks. +- **Pass structured objects, not JSON-encoded strings.** Fields like \`at\`, \`target\`, and \`inline\` expect objects, not serialized JSON strings. +- **Only pass \`dryRun\` when the action's schema explicitly lists it.** Do not assume every action accepts it. Prefer a real call over a preview for destructive actions unless dryRun is documented for that action. +- **If blocks still report \`underline: true\` after you explicitly removed it, treat it as a style inheritance artifact.** Do not retry formatting to fix it. +- **On "Unknown field" errors, drop the unrecognized field and retry.** Use the narrowest working call shape rather than guessing alternative field names. +`; diff --git a/apps/mcp/src/server.ts b/apps/mcp/src/server.ts index 753948f79e..d771ac6111 100644 --- a/apps/mcp/src/server.ts +++ b/apps/mcp/src/server.ts @@ -2,28 +2,26 @@ import { createRequire } from 'node:module'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { getMcpPrompt } from '@superdoc-dev/sdk'; +import { MCP_SYSTEM_PROMPT } from './generated/mcp-prompt.js'; import { SessionManager } from './session-manager.js'; import { registerAllTools } from './tools/index.js'; const require = createRequire(import.meta.url); const { version } = require('../package.json'); -const mcpInstructions = await getMcpPrompt(); - const server = new McpServer( { name: 'superdoc', version, }, { - instructions: mcpInstructions, + instructions: MCP_SYSTEM_PROMPT, }, ); const sessions = new SessionManager(); -await registerAllTools(server, sessions); +registerAllTools(server, sessions); const transport = new StdioServerTransport(); diff --git a/apps/mcp/src/tools/index.ts b/apps/mcp/src/tools/index.ts index a22dac99bf..566c966464 100644 --- a/apps/mcp/src/tools/index.ts +++ b/apps/mcp/src/tools/index.ts @@ -3,7 +3,7 @@ import type { SessionManager } from '../session-manager.js'; import { registerLifecycleTools } from './lifecycle.js'; import { registerIntentTools } from './intent.js'; -export async function registerAllTools(server: McpServer, sessions: SessionManager): Promise { +export function registerAllTools(server: McpServer, sessions: SessionManager): void { registerLifecycleTools(server, sessions); - await registerIntentTools(server, sessions); + registerIntentTools(server, sessions); } diff --git a/apps/mcp/src/tools/intent.ts b/apps/mcp/src/tools/intent.ts index e69753ab8c..eaca2a6c89 100644 --- a/apps/mcp/src/tools/intent.ts +++ b/apps/mcp/src/tools/intent.ts @@ -1,7 +1,7 @@ /** * Register intent-based tools from the generated catalog. * - * Reads catalog.json and registers each intent tool with the MCP server. + * Registers each intent tool from the MCP-local generated catalog. * Tool dispatch is handled by the generated dispatchIntentTool function, * routing through DocumentApi.invoke(). */ @@ -10,7 +10,8 @@ import { z } from 'zod'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { SessionManager } from '../session-manager.js'; import type { DocumentApi, DynamicInvokeRequest } from '@superdoc/document-api'; -import { dispatchIntentTool, getToolCatalog } from '@superdoc-dev/sdk'; +import { MCP_TOOL_CATALOG } from '../generated/catalog.js'; +import { dispatchIntentTool } from '../generated/intent-dispatch.generated.js'; // --------------------------------------------------------------------------- // Types for the generated catalog @@ -116,8 +117,8 @@ function executeOperation(api: DocumentApi, operationId: string, input: Record { - const catalog = (await getToolCatalog()) as unknown as Catalog; +export function registerIntentTools(server: McpServer, sessions: SessionManager): void { + const catalog = MCP_TOOL_CATALOG as unknown as Catalog; for (const tool of catalog.tools) { const zodSchema = buildZodSchema(tool); diff --git a/packages/sdk/codegen/src/generate-all.mjs b/packages/sdk/codegen/src/generate-all.mjs index 40c59a7e35..49c0451b90 100644 --- a/packages/sdk/codegen/src/generate-all.mjs +++ b/packages/sdk/codegen/src/generate-all.mjs @@ -27,6 +27,9 @@ function redirectedWriteGeneratedFile(filePath, content) { } else if (relToRepo.startsWith(path.join('packages', 'sdk', 'tools'))) { const relPart = path.relative(path.join(REPO_ROOT, 'packages/sdk/tools'), filePath); destPath = path.join(outputRoot, 'tools', relPart); + } else if (relToRepo.startsWith(path.join('apps', 'mcp', 'src', 'generated'))) { + const relPart = path.relative(path.join(REPO_ROOT, 'apps/mcp/src/generated'), filePath); + destPath = path.join(outputRoot, 'mcp-generated', relPart); } else { destPath = path.join(outputRoot, 'other', path.basename(filePath)); } diff --git a/packages/sdk/codegen/src/generate-intent-tools.mjs b/packages/sdk/codegen/src/generate-intent-tools.mjs index 6c30b7b7f1..cc5b5a893e 100644 --- a/packages/sdk/codegen/src/generate-intent-tools.mjs +++ b/packages/sdk/codegen/src/generate-intent-tools.mjs @@ -4,6 +4,7 @@ import { loadContract, REPO_ROOT, stripBoundParams, writeGeneratedFile } from '. const TOOLS_OUTPUT_DIR = path.join(REPO_ROOT, 'packages/sdk/tools'); const BROWSER_SDK_DIR = path.join(REPO_ROOT, 'packages/sdk/langs/browser/src'); +const MCP_GENERATED_DIR = path.join(REPO_ROOT, 'apps/mcp/src/generated'); // --------------------------------------------------------------------------- // Schema sanitization — ensure JSON Schema 2020-12 compliance @@ -600,6 +601,21 @@ export async function generateIntentTools(contract) { const writes = [ writeGeneratedFile(path.join(TOOLS_OUTPUT_DIR, 'catalog.json'), JSON.stringify(catalog, null, 2) + '\n'), writeGeneratedFile(path.join(TOOLS_OUTPUT_DIR, 'tools-policy.json'), JSON.stringify(policy, null, 2) + '\n'), + writeGeneratedFile( + path.join(MCP_GENERATED_DIR, 'catalog.ts'), + '// Auto-generated from packages/sdk/tools/catalog.json\n' + + '// Do not edit manually — re-run generate:all to update.\n' + + `export const MCP_TOOL_CATALOG = ${JSON.stringify(catalog, null, 2)} as const;\n`, + ), + // MCP is a self-contained bundle and does not keep @superdoc-dev/sdk as a runtime dependency. + // Emit a local copy of dispatch so the generated catalog and dispatch stay pinned together. + writeGeneratedFile( + path.join(MCP_GENERATED_DIR, 'intent-dispatch.generated.ts'), + dispatchTs.replace( + '// Auto-generated by generate-intent-tools.mjs — do not edit', + '// Auto-generated by generate-intent-tools.mjs — do not edit.\n// MCP-local copy bundled with the generated tool catalog.', + ), + ), writeGeneratedFile( path.join(REPO_ROOT, 'packages/sdk/langs/node/src/generated/intent-dispatch.generated.ts'), dispatchTs, @@ -633,7 +649,7 @@ export async function generateIntentTools(contract) { // Node SDK and Python SDK read this file at runtime via getSystemPrompt(). writes.push(writeGeneratedFile(path.join(TOOLS_OUTPUT_DIR, 'system-prompt.md'), sdkPrompt)); - // Write assembled MCP prompt. MCP server reads this at runtime via getMcpPrompt(). + // Write assembled MCP prompt for SDK consumers that call getMcpPrompt(). writes.push(writeGeneratedFile(path.join(TOOLS_OUTPUT_DIR, 'system-prompt-mcp.md'), mcpPrompt)); // Browser SDK: embed SDK prompt as a TypeScript string constant @@ -643,6 +659,14 @@ export async function generateIntentTools(contract) { '// Do not edit manually — re-run generate:all to update.\n' + 'export const SYSTEM_PROMPT = `' + escaped + '`;\n'; writes.push(writeGeneratedFile(path.join(BROWSER_SDK_DIR, 'system-prompt.ts'), promptTs)); + + // MCP server: embed MCP prompt into the bundle to avoid runtime asset path relocation. + const mcpEscaped = mcpPrompt.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$'); + const mcpPromptTs = + '// Auto-generated from tools/prompt-templates/system-prompt-mcp-header.md + system-prompt-core.md\n' + + '// Do not edit manually — re-run generate:all to update.\n' + + 'export const MCP_SYSTEM_PROMPT = `' + mcpEscaped + '`;\n'; + writes.push(writeGeneratedFile(path.join(MCP_GENERATED_DIR, 'mcp-prompt.ts'), mcpPromptTs)); } catch { // prompt template source files may not exist yet during initial bootstrap } diff --git a/packages/sdk/scripts/sdk-generate.mjs b/packages/sdk/scripts/sdk-generate.mjs index f36ad2dea7..978d754cbf 100644 --- a/packages/sdk/scripts/sdk-generate.mjs +++ b/packages/sdk/scripts/sdk-generate.mjs @@ -53,21 +53,35 @@ async function collectFiles(dir) { return files.sort(); } +function shouldSkipGeneratedArtifact(relPath, { skipPythonToolDispatch = false } = {}) { + const normalized = relPath.split(path.sep).join('/'); + return ( + normalized === '__init__.py' || + normalized.startsWith('__pycache__/') || + normalized.includes('/__pycache__/') || + normalized.startsWith('prompt-templates/') || + (skipPythonToolDispatch && normalized === 'intent_dispatch_generated.py') + ); +} + /** * Compare generated artifacts against checked-in versions. * Returns an array of mismatched relative paths. */ async function diffGeneratedArtifacts(tempRoot) { const drifted = []; + const toolsRepoDir = path.join(REPO_ROOT, 'packages/sdk/tools'); // Artifact groups: [tempSubDir, repoSubDir] const artifactDirs = [ [path.join(tempRoot, 'node-generated'), path.join(REPO_ROOT, 'packages/sdk/langs/node/src/generated')], [path.join(tempRoot, 'python-generated'), path.join(REPO_ROOT, 'packages/sdk/langs/python/superdoc/generated')], - [path.join(tempRoot, 'tools'), path.join(REPO_ROOT, 'packages/sdk/tools')], + [path.join(tempRoot, 'tools'), toolsRepoDir], + [path.join(tempRoot, 'mcp-generated'), path.join(REPO_ROOT, 'apps/mcp/src/generated')], ]; for (const [tempDir, repoDir] of artifactDirs) { + const skipPythonToolDispatch = repoDir === toolsRepoDir; let tempFiles = []; let repoFiles = []; try { @@ -84,7 +98,7 @@ async function diffGeneratedArtifacts(tempRoot) { // Forward check: every generated file must match repo for (const relPath of tempFiles) { // Skip manually maintained files that live alongside generated artifacts - if (relPath === '__init__.py' || relPath.split(path.sep).join('/').startsWith('prompt-templates/')) continue; + if (shouldSkipGeneratedArtifact(relPath, { skipPythonToolDispatch })) continue; const tempFile = path.join(tempDir, relPath); const repoFile = path.join(repoDir, relPath); @@ -108,7 +122,7 @@ async function diffGeneratedArtifacts(tempRoot) { // Reverse check: repo files absent from generated output are stale const tempFileSet = new Set(tempFiles); for (const relPath of repoFiles) { - if (relPath === '__init__.py' || relPath.split(path.sep).join('/').startsWith('prompt-templates/')) continue; + if (shouldSkipGeneratedArtifact(relPath, { skipPythonToolDispatch })) continue; if (!tempFileSet.has(relPath)) { drifted.push(`${relPath} (stale — no longer generated)`); } From c61a1308a9f204d1c4ff03f0286c3724116baf7a Mon Sep 17 00:00:00 2001 From: Nick Bernal Date: Wed, 29 Apr 2026 06:51:34 -0700 Subject: [PATCH 2/2] chore: update lock file --- pnpm-lock.yaml | 227 +++++++++---------------------------------------- 1 file changed, 42 insertions(+), 185 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23609164e2..98f63f36ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -555,7 +555,7 @@ importers: version: 14.0.3 mintlify: specifier: 4.2.531 - version: 4.2.531(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) + version: 4.2.531(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/node@25.6.0)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) remark-mdx: specifier: ^3.1.1 version: 3.1.1 @@ -574,16 +574,13 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.26.0 version: 1.28.0(zod@4.3.6) - '@superdoc-dev/sdk': - specifier: workspace:* - version: link:../../packages/sdk/langs/node - '@superdoc/document-api': - specifier: workspace:* - version: link:../../packages/document-api zod: specifier: ^4.3.6 version: 4.3.6 devDependencies: + '@superdoc/document-api': + specifier: workspace:* + version: link:../../packages/document-api '@superdoc/super-editor': specifier: workspace:* version: link:../../packages/super-editor @@ -2338,7 +2335,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: specifier: 3.5.32 version: 3.5.32(typescript@5.9.3) @@ -2363,7 +2360,7 @@ importers: version: 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) concurrently: specifier: 'catalog:' version: 9.2.1 @@ -2387,7 +2384,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) packages/document-api: {} @@ -26439,16 +26436,6 @@ snapshots: chalk: 4.1.2 figures: 3.2.0 - '@inquirer/checkbox@4.3.2(@types/node@22.19.2)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.19.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/checkbox@4.3.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 @@ -26474,13 +26461,6 @@ snapshots: '@inquirer/type': 1.5.5 chalk: 4.1.2 - '@inquirer/confirm@5.1.21(@types/node@22.19.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/confirm@5.1.21(@types/node@25.6.0)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.6.0) @@ -26495,19 +26475,6 @@ snapshots: optionalDependencies: '@types/node': 18.19.130 - '@inquirer/core@10.3.2(@types/node@22.19.2)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.19.2) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/core@10.3.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 @@ -26574,14 +26541,6 @@ snapshots: chalk: 4.1.2 external-editor: 3.1.0 - '@inquirer/editor@4.2.23(@types/node@22.19.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/external-editor': 1.0.3(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/editor@4.2.23(@types/node@25.6.0)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.6.0) @@ -26605,14 +26564,6 @@ snapshots: chalk: 4.1.2 figures: 3.2.0 - '@inquirer/expand@4.0.23(@types/node@22.19.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/expand@4.0.23(@types/node@25.6.0)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.6.0) @@ -26621,13 +26572,6 @@ snapshots: optionalDependencies: '@types/node': 25.6.0 - '@inquirer/external-editor@1.0.3(@types/node@22.19.2)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/external-editor@1.0.3(@types/node@25.6.0)': dependencies: chardet: 2.1.1 @@ -26652,13 +26596,6 @@ snapshots: '@inquirer/type': 1.5.5 chalk: 4.1.2 - '@inquirer/input@4.3.1(@types/node@22.19.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/input@4.3.1(@types/node@25.6.0)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.6.0) @@ -26673,13 +26610,6 @@ snapshots: optionalDependencies: '@types/node': 18.19.130 - '@inquirer/number@3.0.23(@types/node@22.19.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/number@3.0.23(@types/node@25.6.0)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.6.0) @@ -26694,14 +26624,6 @@ snapshots: ansi-escapes: 4.3.2 chalk: 4.1.2 - '@inquirer/password@4.0.23(@types/node@22.19.2)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/password@4.0.23(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 @@ -26722,21 +26644,6 @@ snapshots: '@inquirer/rawlist': 1.2.16 '@inquirer/select': 1.3.3 - '@inquirer/prompts@7.10.1(@types/node@22.19.2)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@22.19.2) - '@inquirer/confirm': 5.1.21(@types/node@22.19.2) - '@inquirer/editor': 4.2.23(@types/node@22.19.2) - '@inquirer/expand': 4.0.23(@types/node@22.19.2) - '@inquirer/input': 4.3.1(@types/node@22.19.2) - '@inquirer/number': 3.0.23(@types/node@22.19.2) - '@inquirer/password': 4.0.23(@types/node@22.19.2) - '@inquirer/rawlist': 4.1.11(@types/node@22.19.2) - '@inquirer/search': 3.2.2(@types/node@22.19.2) - '@inquirer/select': 4.4.2(@types/node@22.19.2) - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/prompts@7.10.1(@types/node@25.6.0)': dependencies: '@inquirer/checkbox': 4.3.2(@types/node@25.6.0) @@ -26752,20 +26659,20 @@ snapshots: optionalDependencies: '@types/node': 25.6.0 - '@inquirer/prompts@7.9.0(@types/node@22.19.2)': + '@inquirer/prompts@7.9.0(@types/node@25.6.0)': dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@22.19.2) - '@inquirer/confirm': 5.1.21(@types/node@22.19.2) - '@inquirer/editor': 4.2.23(@types/node@22.19.2) - '@inquirer/expand': 4.0.23(@types/node@22.19.2) - '@inquirer/input': 4.3.1(@types/node@22.19.2) - '@inquirer/number': 3.0.23(@types/node@22.19.2) - '@inquirer/password': 4.0.23(@types/node@22.19.2) - '@inquirer/rawlist': 4.1.11(@types/node@22.19.2) - '@inquirer/search': 3.2.2(@types/node@22.19.2) - '@inquirer/select': 4.4.2(@types/node@22.19.2) + '@inquirer/checkbox': 4.3.2(@types/node@25.6.0) + '@inquirer/confirm': 5.1.21(@types/node@25.6.0) + '@inquirer/editor': 4.2.23(@types/node@25.6.0) + '@inquirer/expand': 4.0.23(@types/node@25.6.0) + '@inquirer/input': 4.3.1(@types/node@25.6.0) + '@inquirer/number': 3.0.23(@types/node@25.6.0) + '@inquirer/password': 4.0.23(@types/node@25.6.0) + '@inquirer/rawlist': 4.1.11(@types/node@25.6.0) + '@inquirer/search': 3.2.2(@types/node@25.6.0) + '@inquirer/select': 4.4.2(@types/node@25.6.0) optionalDependencies: - '@types/node': 22.19.2 + '@types/node': 25.6.0 '@inquirer/rawlist@1.2.16': dependencies: @@ -26773,14 +26680,6 @@ snapshots: '@inquirer/type': 1.5.5 chalk: 4.1.2 - '@inquirer/rawlist@4.1.11(@types/node@22.19.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/rawlist@4.1.11(@types/node@25.6.0)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.6.0) @@ -26789,15 +26688,6 @@ snapshots: optionalDependencies: '@types/node': 25.6.0 - '@inquirer/search@3.2.2(@types/node@22.19.2)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.19.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/search@3.2.2(@types/node@25.6.0)': dependencies: '@inquirer/core': 10.3.2(@types/node@25.6.0) @@ -26815,16 +26705,6 @@ snapshots: chalk: 4.1.2 figures: 3.2.0 - '@inquirer/select@4.4.2(@types/node@22.19.2)': - dependencies: - '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@22.19.2) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/select@4.4.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 @@ -26848,10 +26728,6 @@ snapshots: dependencies: mute-stream: 1.0.0 - '@inquirer/type@3.0.10(@types/node@22.19.2)': - optionalDependencies: - '@types/node': 22.19.2 - '@inquirer/type@3.0.10(@types/node@25.6.0)': optionalDependencies: '@types/node': 25.6.0 @@ -27416,11 +27292,11 @@ snapshots: '@microsoft/tsdoc@0.16.0': {} - '@mintlify/cli@4.0.1134(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)': + '@mintlify/cli@4.0.1134(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/node@25.6.0)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)': dependencies: - '@inquirer/prompts': 7.9.0(@types/node@22.19.2) + '@inquirer/prompts': 7.9.0(@types/node@25.6.0) '@mintlify/common': 1.0.865(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/link-rot': 3.0.1043(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) + '@mintlify/link-rot': 3.0.1043(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) '@mintlify/prebuild': 1.0.1008(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) '@mintlify/previewing': 4.0.1069(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) '@mintlify/validation': 0.1.676(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(typescript@5.9.3) @@ -27431,7 +27307,7 @@ snapshots: front-matter: 4.0.2 fs-extra: 11.2.0 ink: 6.3.0(@types/react@19.2.14)(react@19.2.3) - inquirer: 12.3.0(@types/node@22.19.2) + inquirer: 12.3.0(@types/node@25.6.0) js-yaml: 4.1.0 mdast-util-mdx-jsx: 3.2.0 open: 8.4.2 @@ -27463,7 +27339,7 @@ snapshots: - utf-8-validate - yaml - '@mintlify/common@1.0.661(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(typescript@5.9.3)': + '@mintlify/common@1.0.661(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@asyncapi/parser': 3.4.0 '@mintlify/mdx': 3.0.4(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(typescript@5.9.3) @@ -27503,7 +27379,7 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.1 remark-stringify: 11.0.0 - tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3)) + tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) unified: 11.0.5 unist-builder: 4.0.0 unist-util-map: 4.0.0 @@ -27587,13 +27463,13 @@ snapshots: - typescript - yaml - '@mintlify/link-rot@3.0.1043(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)': + '@mintlify/link-rot@3.0.1043(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)': dependencies: '@mintlify/common': 1.0.865(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) '@mintlify/models': 0.0.296 '@mintlify/prebuild': 1.0.1008(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) '@mintlify/previewing': 4.0.1069(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) - '@mintlify/scraping': 4.0.522(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(typescript@5.9.3) + '@mintlify/scraping': 4.0.522(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(typescript@5.9.3) '@mintlify/validation': 0.1.676(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(typescript@5.9.3) fs-extra: 11.1.0 unist-util-visit: 4.1.2 @@ -27738,9 +27614,9 @@ snapshots: - utf-8-validate - yaml - '@mintlify/scraping@4.0.522(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(typescript@5.9.3)': + '@mintlify/scraping@4.0.522(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(typescript@5.9.3)': dependencies: - '@mintlify/common': 1.0.661(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(typescript@5.9.3) + '@mintlify/common': 1.0.661(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(typescript@5.9.3) '@mintlify/openapi-parser': 0.0.8 fs-extra: 11.1.1 hast-util-to-mdast: 10.1.0 @@ -33249,25 +33125,6 @@ snapshots: vite: rolldown-vite@7.3.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.32(typescript@5.9.3) - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 1.0.2 - ast-v8-to-istanbul: 0.3.12 - debug: 4.4.3(supports-color@5.5.0) - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.2.0 - magic-string: 0.30.21 - magicast: 0.3.5 - std-env: 3.10.0 - test-exclude: 7.0.2 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - transitivePeerDependencies: - - supports-color - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@ampproject/remapping': 2.3.0 @@ -38979,12 +38836,12 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - inquirer@12.3.0(@types/node@22.19.2): + inquirer@12.3.0(@types/node@25.6.0): dependencies: - '@inquirer/core': 10.3.2(@types/node@22.19.2) - '@inquirer/prompts': 7.10.1(@types/node@22.19.2) - '@inquirer/type': 3.0.10(@types/node@22.19.2) - '@types/node': 22.19.2 + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/prompts': 7.10.1(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + '@types/node': 25.6.0 ansi-escapes: 4.3.2 mute-stream: 2.0.0 run-async: 3.0.0 @@ -41354,9 +41211,9 @@ snapshots: dependencies: minipass: 7.1.3 - mintlify@4.2.531(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3): + mintlify@4.2.531(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/node@25.6.0)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3): dependencies: - '@mintlify/cli': 4.0.1134(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) + '@mintlify/cli': 4.0.1134(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(react@19.2.3))(@types/node@25.6.0)(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.3))(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3))(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) transitivePeerDependencies: - '@radix-ui/react-popover' - '@types/node' @@ -43324,13 +43181,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.8 - postcss-load-config@4.0.2(postcss@8.5.10)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3)): + postcss-load-config@4.0.2(postcss@8.5.10)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)): dependencies: lilconfig: 3.1.3 yaml: 2.8.3 optionalDependencies: postcss: 8.5.10 - ts-node: 10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3) + ts-node: 10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3) postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.3): dependencies: @@ -46459,7 +46316,7 @@ snapshots: - tsx - yaml - tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3)): + tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -46478,7 +46335,7 @@ snapshots: postcss: 8.5.10 postcss-import: 15.1.0(postcss@8.5.10) postcss-js: 4.1.0(postcss@8.5.10) - postcss-load-config: 4.0.2(postcss@8.5.10)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3)) + postcss-load-config: 4.0.2(postcss@8.5.10)(ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3)) postcss-nested: 6.2.0(postcss@8.5.10) postcss-selector-parser: 6.1.2 resolve: 1.22.11 @@ -46848,14 +46705,14 @@ snapshots: '@swc/core': 1.15.21 optional: true - ts-node@10.9.2(@swc/core@1.15.21)(@types/node@22.19.2)(typescript@5.9.3): + ts-node@10.9.2(@swc/core@1.15.21)(@types/node@25.6.0)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.19.2 + '@types/node': 25.6.0 acorn: 8.16.0 acorn-walk: 8.3.5 arg: 4.1.3