@adeu/mcp-server 1.21.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22,13 +22,16 @@ import {
22
22
  create_word_patch_diff,
23
23
  finalize_document
24
24
  } from "@adeu/core";
25
+ import { describe_illegal_control_chars } from "@adeu/core";
25
26
 
26
27
  // src/response-builders.ts
27
28
  import { resolve, basename } from "path";
28
29
  import {
29
30
  paginate,
30
31
  split_structural_appendix,
31
- extract_outline
32
+ extract_outline,
33
+ RegexTimeoutError,
34
+ userFindAllMatches
32
35
  } from "@adeu/core";
33
36
  function _build_appendix_pointer(has_appendix) {
34
37
  if (!has_appendix) return "";
@@ -79,6 +82,21 @@ Document has ${nodes.length} headings, all at deeper levels. Call read_docx with
79
82
  }
80
83
  return lines.join("\n");
81
84
  }
85
+ function build_full_document_response(text, file_path) {
86
+ const [body] = split_structural_appendix(text);
87
+ const ui_markdown = body;
88
+ const llm_content = `> **File Path:** \`${resolve(file_path)}\`
89
+
90
+ ${ui_markdown}`;
91
+ return {
92
+ content: [{ type: "text", text: llm_content }],
93
+ structuredContent: {
94
+ markdown: ui_markdown,
95
+ file_path: resolve(file_path),
96
+ title: basename(file_path)
97
+ }
98
+ };
99
+ }
82
100
  function build_paginated_response(text, page, file_path) {
83
101
  const [body, appendix] = split_structural_appendix(text);
84
102
  const has_appendix = Boolean(appendix.trim());
@@ -207,9 +225,11 @@ function build_search_response(text, search_query, search_regex, search_case_sen
207
225
  const flags = search_case_sensitive ? "g" : "gi";
208
226
  let regexDowngradedNote = "";
209
227
  let regex;
228
+ let isUserRegex = false;
210
229
  if (search_regex) {
211
230
  try {
212
231
  regex = new RegExp(search_query, flags);
232
+ isUserRegex = true;
213
233
  } catch (e) {
214
234
  regexDowngradedNote = `> **Note:** \`${search_query}\` is not a valid regular expression (${e.message}), so it was searched as literal text instead. If you meant a regex, fix the pattern; if you meant literal text, set \`search_regex\` to false.`;
215
235
  regex = new RegExp(escapeRegExp(search_query), flags);
@@ -217,7 +237,21 @@ function build_search_response(text, search_query, search_regex, search_case_sen
217
237
  } else {
218
238
  regex = new RegExp(escapeRegExp(search_query), flags);
219
239
  }
220
- const allMatches = Array.from(body.matchAll(regex));
240
+ let allMatches;
241
+ if (isUserRegex) {
242
+ try {
243
+ allMatches = userFindAllMatches(search_query, body, flags).map((m) => ({
244
+ 0: body.slice(m.start, m.end),
245
+ index: m.start
246
+ }));
247
+ } catch (e) {
248
+ if (!(e instanceof RegexTimeoutError)) throw e;
249
+ regexDowngradedNote = `> **Note:** \`${search_query}\` was searched as literal text instead of as a regular expression: ${e.message}`;
250
+ allMatches = Array.from(body.matchAll(new RegExp(escapeRegExp(search_query), flags)));
251
+ }
252
+ } else {
253
+ allMatches = Array.from(body.matchAll(regex));
254
+ }
221
255
  const pag_res = paginate(body, "");
222
256
  const page_offsets = pag_res.body_page_offsets;
223
257
  const total_doc_pages = pag_res.total_pages;
@@ -442,8 +476,8 @@ var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use pa
442
476
  var PROCESS_BATCH_COMMON_DESC = "Applies a batch of edits and review actions to a DOCX.\n\nBatches apply SEQUENTIALLY: each change is validated and applied against the document state produced by the changes before it, so you may chain dependent edits within one batch (e.g. rename X to Y, then modify Y \u2014 the second edit must target Y, the text as it reads after the rename). Validation failures reject the whole batch transactionally: nothing is applied until every change resolves.\n\n";
443
477
  var PROCESS_BATCH_OPERATIONS_DESC = "Each item in `changes` must specify a `type`:\n1. 'modify': Search-and-replace. By default `target_text` must match uniquely (`match_mode`:'strict') \u2014 add surrounding context to disambiguate, or set `match_mode`:'first'/'all' to edit the first or every occurrence. Set `regex`:true to treat `target_text` as a regular expression (capture groups available in `new_text` as $1, $2\u2026). `new_text` supports Markdown: '# Heading 1' through '###### Heading 6', '**bold**', '_italic_', and '\\n\\n' to split into multiple paragraphs. Empty `new_text` deletes. Do NOT write CriticMarkup tags ({++, {--, {>>) manually \u2014 use the `comment` parameter for comments.\n \u2022 EMPTY/FORM TABLE CELLS: a blank cell has no text to match. `read_docx` renders each cell with a trailing `{#cell:<id>}` anchor \u2014 to fill a blank cell, set `target_text` to that exact anchor (e.g. '{#cell:0000005E}') and put the value in `new_text`. Do NOT try to match the pipe layout ('Date | | |'); the pipes are display separators, not editable text.\n2. 'accept' / 'reject': Finalize or revert a tracked change by `target_id` (e.g. 'Chg:12').\n3. 'reply': Reply to a comment by `target_id` (e.g. 'Com:5') with `text`.\n4. 'insert_row' / 'delete_row': Table edits. Disk mode only \u2014 not supported on Live Word canvas.\n\nID VOLATILITY: 'Chg:N' and 'Com:N' shift between document states. Always call `read_docx` immediately before any accept/reject/reply \u2014 do not reuse IDs from earlier in the conversation. The `{#cell:<id>}` anchors are stable (Word-assigned) and safe to reuse across reads.\n\n`author_name` is used for attribution on all tracked changes and comments, in both disk and Live Word modes.";
444
478
  var DIFF_DOCX_DESC = "Compares two DOCX files and returns a compact `@@ Word Patch @@` diff \u2014 Adeu's token-level, sub-word patch format \u2014 of their text content. Useful for analyzing differences between versions before editing.";
445
- var gitSha = "5bece24";
446
- var packageVersion = "1.21.0";
479
+ var gitSha = "4947f97";
480
+ var packageVersion = "1.22.0";
447
481
  var buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;
448
482
  var server = new McpServer({
449
483
  name: "adeu-redlining-service",
@@ -580,6 +614,10 @@ registerAppTool(
580
614
  );
581
615
  return res2;
582
616
  }
617
+ if (mode === "full" && page !== void 0 && page !== null && String(page).trim().toLowerCase() === "all") {
618
+ const res2 = build_full_document_response(text, file_path);
619
+ return res2;
620
+ }
583
621
  let resolvedPage = 1;
584
622
  if (page !== void 0 && page !== null) {
585
623
  const parsed = typeof page === "number" ? page : parseInt(String(page).trim(), 10);
@@ -589,7 +627,7 @@ registerAppTool(
589
627
  content: [
590
628
  {
591
629
  type: "text",
592
- text: `Invalid page value: '${page}'. Provide a positive integer (pages are 1-indexed; 'all' is only valid together with search_query).`
630
+ text: `Invalid page value: '${page}'. Provide a positive integer (pages are 1-indexed; 'all' is valid for mode='full' and together with search_query).`
593
631
  }
594
632
  ]
595
633
  };
@@ -678,6 +716,16 @@ server.registerTool(
678
716
  { type: "text", text: "Error: author_name cannot be empty." }
679
717
  ]
680
718
  };
719
+ const author_ctrl = describe_illegal_control_chars(author_name);
720
+ if (author_ctrl)
721
+ return {
722
+ content: [
723
+ {
724
+ type: "text",
725
+ text: `Error: author_name contains control character(s) (${author_ctrl}) that cannot be stored in a DOCX. Remove them and retry.`
726
+ }
727
+ ]
728
+ };
681
729
  if (!changes || changes.length === 0)
682
730
  return {
683
731
  content: [{ type: "text", text: "Error: No changes provided." }]
@@ -1005,8 +1053,8 @@ ${stats.skipped_details.join("\n")}`;
1005
1053
  async function main() {
1006
1054
  const transport = new StdioServerTransport();
1007
1055
  await server.connect(transport);
1008
- const gitSha2 = "5bece24";
1009
- const buildTs = "2026-07-17T19:54:28.986Z";
1056
+ const gitSha2 = "4947f97";
1057
+ const buildTs = "2026-07-18T07:52:19.202Z";
1010
1058
  console.error(
1011
1059
  `Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha2}@${buildTs}`
1012
1060
  );
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/response-builders.ts","../src/shared.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { basename, resolve, extname, dirname, join } from \"node:path\";\nimport { z } from \"zod\";\nimport {\n registerAppTool as origRegisterAppTool,\n registerAppResource,\n RESOURCE_MIME_TYPE,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport fs from \"node:fs\";\nimport {\n identifyEngine,\n extractTextFromBuffer,\n _extractTextFromDoc,\n DocumentObject,\n RedlineEngine,\n BatchValidationError,\n create_word_patch_diff,\n finalize_document,\n} from \"@adeu/core\";\n\nimport {\n build_paginated_response,\n build_outline_response,\n build_appendix_response,\n build_search_response,\n} from \"./response-builders.js\";\n\nimport { MARKDOWN_UI_URI } from \"./shared.js\";\n// Parity with Python models.py `_infer_type_in_place` + `_coerce_match_mode_in_place`.\n// The MCP boundary schema is permissive; these repairs let recoverable payloads\n// (a missing `type` that's unambiguous from the key signature, or a non-canonical\n// `match_mode`) succeed instead of failing the whole-array Zod parse with an\n// opaque -32602. Anything still un-inferrable is caught by the handler guard\n// below and reported per-index; anything that doesn't apply to the document is\n// caught by the engine's validate_edits. Mirrors how Python repairs in a\n// BeforeValidator ahead of its (strict) discriminated union.\nconst MATCH_MODE_SYNONYMS: Record<string, \"strict\" | \"first\" | \"all\"> = {\n strict: \"strict\",\n first: \"first\",\n all: \"all\",\n first_only: \"first\",\n firstonly: \"first\",\n \"first-only\": \"first\",\n all_occurrences: \"all\",\n alloccurrences: \"all\",\n \"all-occurrences\": \"all\",\n every: \"all\",\n};\n\nfunction coerceChangeItemInPlace(item: any): void {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) return;\n\n // Infer a missing `type` ONLY when exactly one variant fits unambiguously.\n // Deliberately do NOT infer from `target_id` alone (accept vs reject is a\n // semantic choice) or `target_text` alone (delete_row vs empty-new_text\n // modify). Those stay absent and are rejected with a clear message.\n if (!(\"type\" in item) || item.type === undefined || item.type === null) {\n if (\"cells\" in item) item.type = \"insert_row\";\n else if (\"text\" in item && \"target_id\" in item) item.type = \"reply\";\n else if (\"target_text\" in item && \"new_text\" in item) item.type = \"modify\";\n }\n\n // Normalize match_mode: canonical passes through, synonyms map, anything else\n // (help-string echo \"strict, first, or all\", empty, non-string) is dropped so\n // the engine's \"strict\" default applies. Never coerce junk to \"all\" — that\n // would silently mass-edit; defaulting to strict fails safe with an\n // ambiguity error instead.\n if (\"match_mode\" in item) {\n const raw = item.match_mode;\n if (typeof raw !== \"string\") {\n delete item.match_mode;\n } else {\n const mapped = MATCH_MODE_SYNONYMS[raw.trim().toLowerCase()];\n if (mapped === undefined) delete item.match_mode;\n else item.match_mode = mapped;\n }\n }\n}\nfunction readFileBytesOrThrow(filePath: string): Buffer {\n try {\n return readFileSync(filePath);\n } catch (err: any) {\n if (err.code === \"ENOENT\") {\n // Lean, agent-appropriate error: list sibling .docx files so the model\n // can self-correct a wrong filename (e.g. a guessed `-processed` suffix)\n // in one turn, instead of being handed CLI install instructions that are\n // irrelevant inside an agent loop and pure token waste.\n let available = \"\";\n try {\n const dir = dirname(filePath);\n const docs = fs\n .readdirSync(dir)\n .filter((f) => f.toLowerCase().endsWith(\".docx\"));\n available = docs.length\n ? ` available files: [${docs.join(\", \")}]`\n : ` (no .docx files found in ${dir})`;\n } catch {\n // Directory unreadable — omit the listing rather than fail.\n }\n throw new Error(`file not found: ${basename(filePath)};${available}`);\n }\n throw err;\n }\n}\n\n// --- Asset Loaders for UI ---\nconst DIST_DIR = import.meta.dirname;\n\nfunction getAssetContent(\n folder: \"templates\" | \"assets\",\n filename: string,\n fallbackMessage: string,\n): string {\n const filePath = join(DIST_DIR, folder, filename);\n if (existsSync(filePath)) {\n return readFileSync(filePath, \"utf-8\");\n }\n return fallbackMessage;\n}\n\n// --- Tool Description Constants ---\nconst READ_DOCX_COMMON_DESC =\n \"Reads a DOCX file. Returns text with inline CriticMarkup for Tracked Changes and Comments: {++inserted++}, {--deleted--}, {==highlighted==}{>>comment<<}. Set clean_view=True for the finalized 'Accepted' text without markup.\\n\\n\";\nconst READ_DOCX_TAIL =\n \"Modes:\\n- 'full' (default): paginated body content. Use page=N to navigate.\\n- 'outline': heading map only — start here for large docs to plan targeted reads. Defaults to L1-L2 headings; pass outline_max_level=3-6 to see deeper structure.\\n- 'appendix': defined terms, anchors, and cross-reference targets. Consult before editing legal/technical docs to avoid breaking references.\";\n\nconst PROCESS_BATCH_COMMON_DESC =\n \"Applies a batch of edits and review actions to a DOCX.\\n\\nBatches apply SEQUENTIALLY: each change is validated and applied against the document state produced by the changes before it, so you may chain dependent edits within one batch (e.g. rename X to Y, then modify Y — the second edit must target Y, the text as it reads after the rename). Validation failures reject the whole batch transactionally: nothing is applied until every change resolves.\\n\\n\";\nconst PROCESS_BATCH_OPERATIONS_DESC =\n \"Each item in `changes` must specify a `type`:\\n1. 'modify': Search-and-replace. By default `target_text` must match uniquely (`match_mode`:'strict') — add surrounding context to disambiguate, or set `match_mode`:'first'/'all' to edit the first or every occurrence. Set `regex`:true to treat `target_text` as a regular expression (capture groups available in `new_text` as $1, $2…). `new_text` supports Markdown: '# Heading 1' through '###### Heading 6', '**bold**', '_italic_', and '\\\\n\\\\n' to split into multiple paragraphs. Empty `new_text` deletes. Do NOT write CriticMarkup tags ({++, {--, {>>) manually — use the `comment` parameter for comments.\\n • EMPTY/FORM TABLE CELLS: a blank cell has no text to match. `read_docx` renders each cell with a trailing `{#cell:<id>}` anchor — to fill a blank cell, set `target_text` to that exact anchor (e.g. '{#cell:0000005E}') and put the value in `new_text`. Do NOT try to match the pipe layout ('Date | | |'); the pipes are display separators, not editable text.\\n2. 'accept' / 'reject': Finalize or revert a tracked change by `target_id` (e.g. 'Chg:12').\\n3. 'reply': Reply to a comment by `target_id` (e.g. 'Com:5') with `text`.\\n4. 'insert_row' / 'delete_row': Table edits. Disk mode only — not supported on Live Word canvas.\\n\\nID VOLATILITY: 'Chg:N' and 'Com:N' shift between document states. Always call `read_docx` immediately before any accept/reject/reply — do not reuse IDs from earlier in the conversation. The `{#cell:<id>}` anchors are stable (Word-assigned) and safe to reuse across reads.\\n\\n`author_name` is used for attribution on all tracked changes and comments, in both disk and Live Word modes.\";\n\nconst DIFF_DOCX_DESC =\n \"Compares two DOCX files and returns a compact `@@ Word Patch @@` diff — Adeu's token-level, sub-word patch format — of their text content. Useful for analyzing differences between versions before editing.\";\n\nconst gitSha = process.env.GIT_SHA || \"unknown\";\nconst packageVersion = process.env.PACKAGE_VERSION || \"unknown\";\nconst buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;\n\n// --- Server Setup ---\nconst server = new McpServer({\n name: \"adeu-redlining-service\",\n version: packageVersion,\n});\n\n// Wrap server.registerTool to inject buildTag into descriptions\nconst originalRegisterTool = server.registerTool.bind(server);\nserver.registerTool = (name: string, schema: any, handler?: any) => {\n if (schema && typeof schema === \"object\") {\n // Idempotent: UI tools route through BOTH this wrapper and the\n // registerAppTool wrapper, so guard against stamping the tag twice.\n if (schema.description && !schema.description.includes(buildTag.trim())) {\n schema.description = schema.description.trim() + buildTag;\n }\n }\n return originalRegisterTool(name, schema, handler);\n};\n\n// Wrap registerAppTool to inject buildTag into descriptions\nconst registerAppTool: typeof origRegisterAppTool = (\n mcpServer,\n name,\n schema,\n handler,\n) => {\n if (schema && typeof schema === \"object\") {\n if (schema.description) {\n schema.description = schema.description.trim() + buildTag;\n }\n }\n return origRegisterAppTool(mcpServer, name, schema, handler);\n};\n\n// Common CSP allowing Google Fonts used by Adeu UI templates\nconst UI_CSP = {\n connectDomains: [\"https://fonts.googleapis.com\", \"https://fonts.gstatic.com\"],\n resourceDomains: [\n \"https://fonts.googleapis.com\",\n \"https://fonts.gstatic.com\",\n ],\n};\n\n// ==========================================\n// 1. UI RESOURCES\n// ==========================================\n\nregisterAppResource(\n server,\n MARKDOWN_UI_URI,\n MARKDOWN_UI_URI,\n { mimeType: RESOURCE_MIME_TYPE, description: \"Adeu Markdown Viewer UI\" },\n async () => {\n let html = getAssetContent(\n \"templates\",\n \"markdown_ui.html\",\n \"<html><body>UI Template Not Found</body></html>\",\n );\n const markedJs = getAssetContent(\n \"assets\",\n \"marked.min.js\",\n \"window.__MARKED_ERROR = 'marked.min.js not found';\",\n );\n const svg = getAssetContent(\"assets\", \"adeu.svg\", \"\");\n\n html = html\n .replace(\"[[marked_js_code | safe]]\", markedJs)\n .replace(\"[[ adeu_svg_code ]]\", svg);\n\n return {\n contents: [\n {\n uri: MARKDOWN_UI_URI,\n mimeType: RESOURCE_MIME_TYPE,\n text: html,\n _meta: { ui: { csp: UI_CSP } },\n },\n ],\n };\n },\n);\n\n// ==========================================\n// 2. UI-ENABLED TOOLS\n// ==========================================\nregisterAppTool(\n server,\n \"read_docx\",\n {\n title: \"Read DOCX\",\n description: READ_DOCX_COMMON_DESC + READ_DOCX_TAIL,\n inputSchema: z.object({\n reasoning: z\n .string()\n .describe(\n \"Why do I need to read this docx document? State this reason before any other parameter.\",\n ),\n file_path: z.string().describe(\"Absolute path to the DOCX file.\"),\n clean_view: z\n .boolean()\n .default(false)\n .describe(\n \"If False (default), returns the 'Raw' text with inline CriticMarkup. If True, returns 'Accepted' text.\",\n ),\n mode: z\n .enum([\"full\", \"outline\", \"appendix\"])\n .default(\"full\")\n .describe(\n \"'full' returns body content. 'outline' returns a structural heading map. 'appendix' returns defined terms.\",\n ),\n page: z\n .union([z.number(), z.string()])\n .optional()\n .describe(\n \"Without `search_query`: 1-indexed document page to display (defaults to 1). With `search_query`: restricts matches to that document page (defaults to searching all pages; pass `page='all'` to be explicit). Note: pages are synthetic, length-based content chunks sized for LLM consumption — they do NOT correspond to printed Word pages or explicit page breaks.\",\n ),\n outline_max_level: z.coerce\n .number()\n .default(2)\n .describe(\"For mode='outline' only: cap on heading depth.\"),\n outline_verbose: z\n .boolean()\n .default(false)\n .describe(\"For mode='outline' only: includes metadata.\"),\n search_query: z\n .string()\n .optional()\n .describe(\n \"The substring or regex pattern to search for. When provided, filters results to matching paragraphs.\",\n ),\n search_regex: z\n .boolean()\n .default(false)\n .describe(\n \"Set to true to interpret search_query as a regular expression.\",\n ),\n search_case_sensitive: z\n .boolean()\n .default(true)\n .describe(\"Set to false to perform case-insensitive matching.\"),\n }),\n _meta: { ui: { resourceUri: MARKDOWN_UI_URI } },\n },\n async ({\n reasoning,\n file_path,\n clean_view,\n mode,\n page,\n outline_max_level,\n outline_verbose,\n search_query,\n search_regex,\n search_case_sensitive,\n }) => {\n try {\n void reasoning;\n const buf = readFileBytesOrThrow(file_path);\n\n if (mode === \"outline\") {\n const doc = await DocumentObject.load(buf);\n const extract_res = _extractTextFromDoc(\n doc,\n clean_view,\n true,\n true,\n ) as {\n text: string;\n paragraph_offsets: Map<any, [number, number]>;\n };\n const res = build_outline_response(\n doc,\n extract_res.text,\n file_path,\n outline_max_level,\n outline_verbose,\n extract_res.paragraph_offsets,\n );\n return res as any;\n }\n\n const text = await extractTextFromBuffer(buf, clean_view);\n if (search_query !== undefined && search_query !== null) {\n // In search mode, undefined `page` means \"search all document pages\".\n const res = build_search_response(\n text,\n search_query,\n search_regex,\n search_case_sensitive,\n page,\n file_path,\n );\n return res as any;\n }\n // In non-search mode, `page` defaults to 1 (show document page 1).\n // Non-numeric values must error, not silently fall back to page 1\n // (QA L1 parity with the Python CLI).\n let resolvedPage = 1;\n if (page !== undefined && page !== null) {\n const parsed =\n typeof page === \"number\" ? page : parseInt(String(page).trim(), 10);\n if (!Number.isFinite(parsed)) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text:\n `Invalid page value: '${page}'. Provide a positive integer ` +\n `(pages are 1-indexed; 'all' is only valid together with search_query).`,\n },\n ],\n };\n }\n resolvedPage = parsed;\n }\n if (mode === \"appendix\") {\n const res = build_appendix_response(text, resolvedPage, file_path);\n return res as any;\n }\n const res = build_paginated_response(text, resolvedPage, file_path);\n return res as any;\n } catch (e: any) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `Error executing tool read_docx: ${e.message}`,\n },\n ],\n };\n }\n },\n);\n\n// ==========================================\n// 3. HEADLESS TOOLS (No UI)\n// ==========================================\n\n// Typed shape for a single `process_document_batch` change. This makes the six\n// DocumentChange variants — and the modify-only `match_mode`/`regex` options —\n// discoverable from the tool schema itself, instead of prose alone. A bare\n// string is still accepted (and normalized in-handler) so double-serialized\n// payloads from some LLM clients keep working; only `type` is required, all\n// other fields are optional, and unknown keys pass through untouched.\nconst CHANGE_ITEM_SCHEMA = z\n .object({\n type: z\n .enum([\"modify\", \"accept\", \"reject\", \"reply\", \"insert_row\", \"delete_row\"])\n .optional()\n .describe(\n \"Change kind: 'modify' (search-and-replace), 'accept'/'reject' (resolve a tracked change by id), 'reply' (reply to a comment by id), 'insert_row'/'delete_row' (table edits; disk mode only). If omitted it is inferred when unambiguous from the other fields.\",\n ),\n target_text: z\n .string()\n .optional()\n .describe(\n \"modify / insert_row / delete_row: the existing text to locate (interpreted as a regex when regex=true).\",\n ),\n new_text: z\n .string()\n .optional()\n .describe(\n \"modify: replacement text. Supports Markdown (headings, **bold**, _italic_, '\\\\n\\\\n' paragraph splits); empty string deletes. Regex capture groups are available as $1, $2…\",\n ),\n target_id: z\n .string()\n .optional()\n .describe(\n \"accept / reject / reply: the 'Chg:N' or 'Com:N' id taken from a fresh read_docx.\",\n ),\n text: z.string().optional().describe(\"reply: the reply body.\"),\n comment: z\n .string()\n .optional()\n .describe(\n \"modify / accept / reject: attach a margin comment to the change (no manual CriticMarkup).\",\n ),\n match_mode: z\n .enum([\"strict\", \"first\", \"all\"])\n .optional()\n .describe(\n \"modify only: 'strict' (default — target must match uniquely), 'first' (first occurrence), or 'all' (every occurrence).\",\n ),\n regex: z\n .boolean()\n .optional()\n .describe(\n \"modify only: treat target_text as a regular expression (default false).\",\n ),\n position: z\n .enum([\"above\", \"below\"])\n .optional()\n .describe(\n \"insert_row: place the new row above or below the matched row.\",\n ),\n cells: z\n .array(z.string())\n .optional()\n .describe(\"insert_row: the cell values for the new row, left to right.\"),\n })\n .passthrough();\n\nserver.registerTool(\n \"process_document_batch\",\n {\n description: PROCESS_BATCH_COMMON_DESC + PROCESS_BATCH_OPERATIONS_DESC,\n inputSchema: {\n reasoning: z\n .string()\n .describe(\n \"Why do I need to apply these changes to the document? State this reason before any other parameter.\",\n ),\n original_docx_path: z\n .string()\n .describe(\"Absolute path to the source file.\"),\n author_name: z\n .string()\n .describe(\"Name to appear in Track Changes (e.g., 'Reviewer AI').\"),\n changes: z\n .array(z.union([z.string(), CHANGE_ITEM_SCHEMA]))\n .describe(\n \"Ordered list of changes to apply. Each item is an object carrying a `type` discriminator plus that type's fields (see the per-field docs and the tool description). Items apply SEQUENTIALLY: each one evaluates against the document state produced by the items before it, so later items may target text an earlier item introduced.\",\n ),\n output_path: z.string().optional().describe(\"Optional output path.\"),\n dry_run: z\n .boolean()\n .optional()\n .default(false)\n .describe(\n \"If True, simulates the changes and returns a detailed preview report without modifying any files.\",\n ),\n },\n },\n async ({\n reasoning,\n original_docx_path,\n author_name,\n changes,\n output_path,\n dry_run,\n }) => {\n try {\n void reasoning;\n if (!author_name || !author_name.trim())\n return {\n content: [\n { type: \"text\", text: \"Error: author_name cannot be empty.\" },\n ],\n };\n if (!changes || changes.length === 0)\n return {\n content: [{ type: \"text\", text: \"Error: No changes provided.\" }],\n };\n\n // Defensive sanitization at the MCP boundary: some LLM clients\n // \"double-serialize\" nested arrays, delivering each element of `changes`\n // as a JSON string instead of an object. The core engine also guards\n // against this, but we normalize here too so the tool layer never hands\n // raw string primitives downstream regardless of the engine version\n // bundled. Genuine objects and unparseable strings pass through\n // untouched so validation surfaces a clear error rather than crashing.\n const sanitizedChanges = changes.map((item: any) => {\n let obj: any = item;\n if (typeof item === \"string\") {\n try {\n const parsed = JSON.parse(item);\n obj = parsed !== null && typeof parsed === \"object\" ? parsed : item;\n } catch {\n obj = item;\n }\n }\n // Repair recoverable payloads (infer type, normalize match_mode) the\n // same way Python does before its union validation.\n if (obj !== null && typeof obj === \"object\" && !Array.isArray(obj)) {\n coerceChangeItemInPlace(obj);\n }\n return obj;\n });\n\n // Boundary guard, scoped narrowly: after inference, reject only an OBJECT\n // that still carries no resolvable `type`. Strings, nulls, and non-objects\n // are intentionally left for the engine's validate_edits to report\n // (\"Invalid change format… received a primitive\"), keeping the engine the\n // single authority for those and avoiding a competing error surface.\n // A typeless object is the one case the engine can't cleanly reject (with\n // `type` now optional it would fall into the edits bucket as a no-op), so\n // it is caught here with an actionable, per-index message.\n const VALID_TYPES = new Set([\n \"modify\",\n \"accept\",\n \"reject\",\n \"reply\",\n \"insert_row\",\n \"delete_row\",\n ]);\n const typeErrors: string[] = [];\n sanitizedChanges.forEach((c: any, i: number) => {\n if (\n c !== null &&\n typeof c === \"object\" &&\n !Array.isArray(c) &&\n (!c.type || !VALID_TYPES.has(c.type))\n ) {\n typeErrors.push(\n `- Change ${i + 1}: missing or unrecognized \"type\". Use one of: modify (needs target_text + new_text), accept/reject (needs target_id like \"Chg:12\"), reply (needs target_id like \"Com:5\" + text), insert_row (needs target_text + cells), delete_row (needs target_text). Received keys: [${Object.keys(c).join(\", \")}].`,\n );\n }\n });\n if (typeErrors.length > 0) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `Batch rejected. Some changes are malformed:\\n\\n${typeErrors.join(\"\\n\")}`,\n },\n ],\n };\n }\n\n let outPath = output_path;\n if (!outPath) {\n const ext = extname(original_docx_path);\n const base = basename(original_docx_path, ext);\n const dir = dirname(original_docx_path);\n // Idempotency guard (parity with Python document.py): if the input is\n // already a processed artifact, write back to it instead of compounding\n // the suffix into contract_processed_processed.docx, which fragments the\n // agent's document state across files.\n if (base.endsWith(\"_processed\") || base.endsWith(\"_redlined\")) {\n outPath = resolve(dir, `${base}${ext}`);\n } else {\n outPath = resolve(dir, `${base}_processed${ext}`);\n }\n }\n\n const buf = readFileBytesOrThrow(original_docx_path);\n const doc = await DocumentObject.load(buf);\n const engine = new RedlineEngine(doc, author_name);\n\n let stats;\n try {\n stats = engine.process_batch(sanitizedChanges, dry_run);\n } catch (e: any) {\n if (e instanceof BatchValidationError) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `Batch rejected. Some edits failed validation:\\n\\n${e.errors.join(\"\\n\\n\")}`,\n },\n ],\n };\n }\n throw e;\n }\n\n if (!dry_run) {\n const outBuf = await doc.save();\n try {\n fs.writeFileSync(outPath, outBuf);\n } catch (e: any) {\n // Filesystem failures (name too long, missing directory, perms)\n // must surface as a clear, actionable error (QA H3 parity).\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `Could not write output file '${outPath}': ${e.message}`,\n },\n ],\n };\n }\n }\n\n let res = formatBatchResult(stats, outPath, !!dry_run);\n if (sanitizedChanges.length === 0) {\n res =\n `⚠️ 0 changes provided — nothing to do. The output is an unmodified copy of the original.\\n\\n` +\n res;\n }\n return { content: [{ type: \"text\", text: res }] };\n } catch (e: any) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `Error: ${e.message}` }],\n };\n }\n },\n);\n\nserver.registerTool(\n \"accept_all_changes\",\n {\n description:\n \"Accepts all tracked changes and removes all comments in a single operation.\",\n inputSchema: {\n reasoning: z\n .string()\n .describe(\n \"Why do I need to accept all changes in this document? State this reason before any other parameter.\",\n ),\n docx_path: z.string().describe(\"Absolute path to the DOCX file.\"),\n output_path: z.string().optional().describe(\"Optional output path.\"),\n },\n },\n async ({ reasoning, docx_path, output_path }) => {\n try {\n void reasoning;\n let outPath = output_path;\n if (!outPath) {\n const ext = extname(docx_path);\n const base = basename(docx_path, ext);\n const dir = dirname(docx_path);\n outPath = resolve(dir, `${base}_clean${ext}`);\n }\n\n const buf = readFileBytesOrThrow(docx_path);\n const doc = await DocumentObject.load(buf);\n const engine = new RedlineEngine(doc);\n\n engine.accept_all_revisions();\n\n const outBuf = await doc.save();\n\n fs.writeFileSync(outPath, outBuf);\n\n return {\n content: [\n { type: \"text\", text: `Accepted all changes. Saved to: ${outPath}` },\n ],\n };\n } catch (e: any) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `Error: ${e.message}` }],\n };\n }\n },\n);\n\nserver.registerTool(\n \"diff_docx_files\",\n {\n description: DIFF_DOCX_DESC,\n inputSchema: {\n reasoning: z\n .string()\n .describe(\n \"Why do I need to diff these two documents? State this reason before any other parameter.\",\n ),\n original_path: z\n .string()\n .describe(\"Absolute path to the baseline DOCX file.\"),\n modified_path: z\n .string()\n .describe(\"Absolute path to the modified DOCX file.\"),\n compare_clean: z\n .boolean()\n .default(true)\n .describe(\n \"If True, compares 'Accepted' state. If False, compares raw text.\",\n ),\n },\n },\n async ({ reasoning, original_path, modified_path, compare_clean }) => {\n try {\n void reasoning;\n const origBuf = readFileBytesOrThrow(original_path);\n const modBuf = readFileBytesOrThrow(modified_path);\n\n const origText = await extractTextFromBuffer(origBuf, compare_clean);\n const modText = await extractTextFromBuffer(modBuf, compare_clean);\n\n const diff = create_word_patch_diff(\n origText,\n modText,\n basename(original_path),\n basename(modified_path),\n );\n\n return {\n content: [{ type: \"text\", text: diff || \"No differences found.\" }],\n };\n } catch (e: any) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `Error: ${e.message}` }],\n };\n }\n },\n);\n\nserver.registerTool(\n \"finalize_document\",\n {\n description:\n \"Prepares a document for external distribution or e-signature. Note: in this zero-dependency environment, protection_mode='encrypt' is unsupported and falls back to a native read-only lock; export_pdf and password are ignored.\",\n inputSchema: {\n reasoning: z\n .string()\n .describe(\n \"Why do I need to finalize this document? State this reason before any other parameter.\",\n ),\n file_path: z.string().describe(\"Absolute path to the DOCX file.\"),\n output_path: z.string().optional().describe(\"Optional output path.\"),\n sanitize_mode: z\n .enum([\"full\", \"keep-markup\"])\n .optional()\n .describe(\"full removes all markup, keep-markup redacts metadata.\"),\n accept_all: z\n .boolean()\n .optional()\n .describe(\n \"If true, auto-accepts all unresolved track changes before finalizing.\",\n ),\n protection_mode: z\n .enum([\"read_only\", \"encrypt\"])\n .optional()\n .describe(\n \"Native OOXML document locking. Note: 'encrypt' is unsupported in this zero-dependency build and falls back to 'read_only'.\",\n ),\n password: z.string().optional().describe(\"Ignored in this environment.\"),\n author: z\n .string()\n .optional()\n .describe(\"Replace all remaining markup authorship with this name.\"),\n export_pdf: z\n .boolean()\n .optional()\n .describe(\"Ignored in this environment.\"),\n },\n },\n async ({\n reasoning,\n file_path,\n output_path,\n sanitize_mode,\n accept_all,\n protection_mode,\n author,\n export_pdf,\n }) => {\n try {\n void reasoning;\n let outPath = output_path;\n if (!outPath) {\n const ext = extname(file_path);\n const base = basename(file_path, ext);\n const dir = dirname(file_path);\n outPath = resolve(dir, `${base}_final${ext}`);\n }\n\n const buf = readFileBytesOrThrow(file_path);\n const doc = await DocumentObject.load(buf);\n\n const result = await finalize_document(doc, {\n filename: basename(file_path),\n sanitize_mode: (sanitize_mode as any) || \"full\",\n accept_all: accept_all as boolean,\n protection_mode: protection_mode as any,\n author: author as string,\n export_pdf: export_pdf as boolean,\n });\n\n fs.writeFileSync(outPath, result.outBuffer!);\n\n return {\n content: [\n {\n type: \"text\",\n text: `Saved to: ${outPath}\\n\\n${result.reportText}`,\n },\n ],\n };\n } catch (e: any) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `Error: ${e.message}` }],\n };\n }\n },\n);\n\n// --- Formatter for process_document_batch ---\nexport function formatBatchResult(\n stats: any,\n outPath: string,\n dry_run: boolean,\n): string {\n let res = \"\";\n if (dry_run) {\n res = `Dry-run simulation complete.\\n`;\n } else {\n res = `Batch complete. Saved to: ${outPath}\\n`;\n }\n const total_occurrences = stats.edits\n ? stats.edits.reduce(\n (acc: number, e: any) =>\n acc + (e.status === \"applied\" ? e.occurrences_modified || 1 : 0),\n 0,\n )\n : 0;\n const occ_text =\n total_occurrences > stats.edits_applied\n ? ` (${total_occurrences} occurrences)`\n : \"\";\n\n res += `Actions: ${stats.actions_applied} applied, ${stats.actions_skipped} skipped.\\n`;\n res += `Edits: ${stats.edits_applied} applied${occ_text}, ${stats.edits_skipped} skipped.\\n`;\n\n if (stats.edits && stats.edits.length > 0) {\n res += \"\\nDetailed Edit Reports:\\n\";\n for (let i = 0; i < stats.edits.length; i++) {\n const report = stats.edits[i];\n const status_indicator =\n report.status === \"applied\" ? \"✅ [applied]\" : \"❌ [failed]\";\n\n const pagesStr =\n report.pages && report.pages.length > 0\n ? ` (p${report.pages.join(\", p\")})`\n : \"\";\n\n res += `### Edit ${i + 1} ${status_indicator}${pagesStr}\\n`;\n\n if (report.heading_path) {\n res += `**Path:** \\`${report.heading_path}\\`\\n`;\n }\n\n if (report.match_mode) {\n const occ =\n report.occurrences_modified || (report.status === \"applied\" ? 1 : 0);\n res += `**Mode:** \\`${report.match_mode}\\` (${occ} occurrence${occ !== 1 ? \"s\" : \"\"} modified)\\n`;\n }\n\n if (report.error) {\n res += `*Error:* ${report.error}\\n`;\n }\n if (report.warning) {\n res += `*Warning:* ${report.warning}\\n`;\n }\n\n if (report.critic_markup) {\n res += `*Preview (CriticMarkup):*\\n> ${report.critic_markup.split(\"\\\\n\").join(\"\\\\n> \")}\\n`;\n }\n if (report.clean_text) {\n res += `*Preview (Clean):*\\n> ${report.clean_text.split(\"\\\\n\").join(\"\\\\n> \")}\\n`;\n }\n res += \"\\n\";\n }\n }\n\n if (stats.skipped_details && stats.skipped_details.length > 0) {\n res += `Skipped Details:\\n${stats.skipped_details.join(\"\\n\")}`;\n }\n return res.trim();\n}\n\n// --- Startup ---\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n const gitSha = process.env.GIT_SHA || \"unknown\";\n const buildTs = process.env.BUILD_TIMESTAMP || \"unknown\";\n console.error(\n `Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha}@${buildTs}`,\n );\n}\n\nmain().catch(console.error);\n","import { resolve, basename } from \"node:path\";\nimport {\n DocumentObject,\n paginate,\n split_structural_appendix,\n extract_outline,\n OutlineNode,\n} from \"@adeu/core\";\n\nexport interface ToolResult {\n content: { type: \"text\"; text: string }[];\n structuredContent?: any;\n isError?: boolean;\n [key: string]: unknown;\n}\n\nfunction _build_appendix_pointer(has_appendix: boolean): string {\n if (!has_appendix) return \"\";\n return `\\n\\n---\\n\\n> **Appendix available.** This document has structural metadata (defined terms, cross-references, bookmarks, diagnostics) that may be relevant when editing. Call \\`read_docx\\` with \\`mode='appendix'\\` to load it before submitting edits.`;\n}\n\nfunction _build_page_banner(page: number, total: number): string {\n if (total <= 1) return \"\";\n return `> **Page ${page} of ${total}** — call \\`read_docx\\` with \\`mode='outline'\\` for a heading map of the full document.\\n\\n---\\n\\n`;\n}\n\nfunction _build_page_footer(\n page: number,\n total: number,\n has_next: boolean,\n): string {\n if (total <= 1 || !has_next) return \"\";\n return `\\n\\n---\\n\\n> **Continues on page ${page + 1} of ${total}.**`;\n}\n\nexport function render_outline_tree(\n nodes: OutlineNode[],\n max_level: number = 2,\n verbose: boolean = false,\n): string {\n if (!nodes || nodes.length === 0) {\n return \"# (No headings detected)\\n\\nThis document has no detectable headings.\";\n }\n\n const visible = nodes.filter((n) => n.level <= max_level);\n\n if (visible.length === 0) {\n return `# (No headings at level <= ${max_level})\\n\\nDocument has ${nodes.length} headings, all at deeper levels. Call read_docx with mode='outline' and outline_max_level=N (up to 6) to see them.`;\n }\n\n const lines: string[] = [];\n for (const node of visible) {\n const prefix = \"#\".repeat(node.level);\n if (verbose) {\n const meta_parts = [`p${node.page}`, node.style];\n if (node.has_table) meta_parts.push(\"has table\");\n if (node.footnote_ids && node.footnote_ids.length > 0)\n meta_parts.push(\"fn:\" + node.footnote_ids.join(\",\"));\n lines.push(`${prefix} ${node.text} (${meta_parts.join(\", \")})`);\n } else {\n lines.push(`${prefix} ${node.text} (p${node.page})`);\n }\n }\n return lines.join(\"\\n\");\n}\n\nexport function build_paginated_response(\n text: string,\n page: number,\n file_path: string,\n): ToolResult {\n const [body, appendix] = split_structural_appendix(text);\n const has_appendix = Boolean(appendix.trim());\n\n const result = paginate(body, \"\");\n\n if (page < 1 || page > result.total_pages) {\n throw new Error(\n `Page ${page} out of range (doc has ${result.total_pages} pages).`,\n );\n }\n\n const selected = result.pages[page - 1];\n const banner = _build_page_banner(selected.page, selected.total_pages);\n const footer = _build_page_footer(\n selected.page,\n selected.total_pages,\n selected.has_next,\n );\n const appendix_pointer = _build_appendix_pointer(has_appendix);\n\n const ui_markdown =\n banner + selected.page_content + footer + appendix_pointer;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n\n return {\n content: [{ type: \"text\", text: llm_content }],\n // Include structuredContent for the UI to render the markdown\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: basename(file_path),\n },\n };\n}\n\nexport function build_outline_response(\n doc: DocumentObject,\n projected_text: string,\n file_path: string,\n outline_max_level: number = 2,\n outline_verbose: boolean = false,\n paragraph_offsets: Map<any, [number, number]> | null = null,\n): ToolResult {\n // Levels outside 1-6 are meaningless (0/negative used to render a\n // nonsensical \"L1-L0\" range label, QA L2). Clamp to the nearest sensible\n // depth, mirroring the Python builder.\n outline_max_level = Math.max(1, Math.min(outline_max_level, 6));\n\n const [body] = split_structural_appendix(projected_text);\n const pagination_result = paginate(body, \"\");\n\n const nodes = extract_outline(\n doc,\n body,\n pagination_result.body_pages,\n pagination_result.body_page_offsets,\n paragraph_offsets,\n );\n\n const rendered = render_outline_tree(\n nodes,\n outline_max_level,\n outline_verbose,\n );\n\n const visible_count = nodes.filter(\n (n) => n.level <= outline_max_level,\n ).length;\n const deeper_count = nodes.length - visible_count;\n const deeper_hint =\n deeper_count > 0\n ? ` (${deeper_count} more at deeper levels, raise outline_max_level to see)`\n : \"\";\n\n const header = `> **Outline view** — showing ${visible_count} of ${nodes.length} headings (L1-L${outline_max_level}${deeper_hint}) across ${pagination_result.total_pages} page(s). Call \\`read_docx\\` with \\`mode='full'\\` and \\`page=N\\` to read a section.\\n\\n---\\n\\n`;\n const ui_markdown = header + rendered;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: `Outline: ${basename(file_path)}`,\n },\n };\n}\n\nexport function build_appendix_response(\n text: string,\n page: number,\n file_path: string,\n): ToolResult {\n const [, appendix] = split_structural_appendix(text);\n\n if (!appendix.trim()) {\n const ui_markdown =\n \"# Appendix\\n\\nThis document has no structural appendix (no defined terms, named anchors, or diagnostics detected).\";\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: `Appendix: ${basename(file_path)}`,\n },\n };\n }\n\n const result = paginate(appendix, \"\");\n\n if (page < 1 || page > result.total_pages) {\n throw new Error(\n `Appendix page ${page} out of range (appendix has ${result.total_pages} pages).`,\n );\n }\n\n const selected = result.pages[page - 1];\n\n let banner = \"\";\n let footer = \"\";\n\n if (selected.total_pages > 1) {\n banner = `> **Appendix page ${selected.page} of ${selected.total_pages}** — structural metadata for this document.\\n\\n---\\n\\n`;\n footer = selected.has_next\n ? `\\n\\n---\\n\\n> **Continues on appendix page ${selected.page + 1} of ${selected.total_pages}.**`\n : \"\";\n } else {\n banner =\n \"> **Appendix** — structural metadata for this document.\\n\\n---\\n\\n\";\n }\n\n const ui_markdown = banner + selected.page_content + footer;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: `Appendix: ${basename(file_path)}`,\n },\n };\n}\n\nexport function build_search_response(\n text: string,\n search_query: string,\n search_regex: boolean,\n search_case_sensitive: boolean,\n page: number | string | undefined,\n file_path: string,\n): ToolResult {\n const [body] = split_structural_appendix(text);\n const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const flags = search_case_sensitive ? \"g\" : \"gi\";\n\n // When the caller asked for a regex but supplied something the engine can't\n // compile (e.g. an unterminated character class `\\[`, or an inline-flag group\n // `(?i)...` that JS RegExp rejects), do NOT hard-error and burn the turn.\n // Downgrade to a literal search of the raw string and tell the model, so it\n // can either accept the literal hits or fix its pattern — instead of retrying\n // the same broken regex.\n let regexDowngradedNote = \"\";\n let regex: RegExp;\n if (search_regex) {\n try {\n regex = new RegExp(search_query, flags);\n } catch (e: any) {\n regexDowngradedNote =\n `> **Note:** \\`${search_query}\\` is not a valid regular expression ` +\n `(${e.message}), so it was searched as literal text instead. ` +\n `If you meant a regex, fix the pattern; if you meant literal text, set \\`search_regex\\` to false.`;\n regex = new RegExp(escapeRegExp(search_query), flags);\n }\n } else {\n regex = new RegExp(escapeRegExp(search_query), flags);\n }\n\n const allMatches = Array.from(body.matchAll(regex));\n\n // Compute document pagination once — needed for both annotation and filtering.\n const pag_res = paginate(body, \"\");\n const page_offsets = pag_res.body_page_offsets;\n const total_doc_pages = pag_res.total_pages;\n\n // Resolve `page` parameter to either \"all\" or a concrete document-page number.\n // Undefined → \"all\" (search across the whole document).\n // \"all\" (case-insensitive) → \"all\".\n // A positive integer N → filter matches to document page N.\n // Anything else → hard error.\n let filter_doc_page: number | null = null; // null means \"all\"\n if (page !== undefined && page !== null) {\n const pageStr = String(page).toLowerCase();\n if (pageStr !== \"all\") {\n const parsed = parseInt(pageStr, 10);\n if (isNaN(parsed) || parsed < 1) {\n throw new Error(\n `Invalid page value: \\`${page}\\`. Pass a positive integer to restrict the search to that document page, omit \\`page\\` to search all pages, or pass \\`page='all'\\` explicitly.`,\n );\n }\n if (parsed > total_doc_pages) {\n throw new Error(\n `Document page ${parsed} is out of range — the document has ${total_doc_pages} page(s). In search mode, \\`page\\` filters matches to a specific document page; omit it or pass \\`page='all'\\` to search the whole document.`,\n );\n }\n filter_doc_page = parsed;\n }\n }\n\n // Helper: which document page does an offset live on?\n const pageOfOffset = (offset: number): number => {\n let p = 1;\n for (let j = 0; j < page_offsets.length; j++) {\n if (offset >= page_offsets[j]) p = j + 1;\n else break;\n }\n return p;\n };\n\n // Apply the filter (if any), but keep a record of all pages that had hits\n // so we can show a useful summary even when filtered.\n const pagesWithHits = new Set<number>();\n for (const m of allMatches) {\n pagesWithHits.add(pageOfOffset(m.index!));\n }\n\n const matches =\n filter_doc_page === null\n ? allMatches\n : allMatches.filter((m) => pageOfOffset(m.index!) === filter_doc_page);\n\n // --- Empty result ---\n if (matches.length === 0) {\n let body_msg: string;\n if (filter_doc_page !== null) {\n if (allMatches.length === 0) {\n body_msg = `> **Search Results** — No matches found for query \\`${search_query}\\` in \\`${basename(file_path)}\\`.\\n\\nVerify your search spelling, or try setting \\`search_case_sensitive\\` to false or enabling \\`search_regex\\` if you used pattern wildcards.`;\n } else {\n const hitPages = Array.from(pagesWithHits).sort((a, b) => a - b);\n body_msg = `> **Search Results** — No matches for \\`${search_query}\\` on document page ${filter_doc_page}.\\n\\nThe query DOES appear elsewhere in the document (${allMatches.length} match${allMatches.length !== 1 ? \"es\" : \"\"} on page${hitPages.length !== 1 ? \"s\" : \"\"} ${hitPages.join(\", \")}). Omit \\`page\\` or pass \\`page='all'\\` to see them.`;\n }\n } else {\n body_msg = `> **Search Results** — No matches found for query \\`${search_query}\\` in \\`${basename(file_path)}\\`.\\n\\nVerify your search spelling, or try setting \\`search_case_sensitive\\` to false or enabling \\`search_regex\\` if you used pattern wildcards.`;\n }\n if (regexDowngradedNote) body_msg = `${regexDowngradedNote}\\n\\n${body_msg}`;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${body_msg}`;\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: body_msg,\n title: `Search: ${basename(file_path)}`,\n file_path: resolve(file_path),\n },\n };\n }\n\n // --- Build the response ---\n const ui_parts: string[] = [];\n\n if (filter_doc_page !== null) {\n ui_parts.push(\n `> **Search Results** — Found ${matches.length} match${matches.length !== 1 ? \"es\" : \"\"} for \\`${search_query}\\` on document page ${filter_doc_page} of ${total_doc_pages} in \\`${basename(file_path)}\\`.`,\n );\n const otherPages = Array.from(pagesWithHits)\n .filter((p) => p !== filter_doc_page)\n .sort((a, b) => a - b);\n if (otherPages.length > 0) {\n ui_parts.push(\n `> Additional matches exist on page${otherPages.length !== 1 ? \"s\" : \"\"} ${otherPages.join(\", \")} — omit \\`page\\` or pass \\`page='all'\\` to see them.`,\n );\n }\n } else {\n ui_parts.push(\n `> **Search Results** — Found ${matches.length} match${matches.length !== 1 ? \"es\" : \"\"} for \\`${search_query}\\` in \\`${basename(file_path)}\\`.`,\n );\n if (total_doc_pages > 1) {\n // Build a per-page hit distribution: \"p1: 3, p3: 1, p7: 12\"\n const counts = new Map<number, number>();\n for (const m of allMatches) {\n const p = pageOfOffset(m.index!);\n counts.set(p, (counts.get(p) || 0) + 1);\n }\n const distribution = Array.from(counts.entries())\n .sort((a, b) => a[0] - b[0])\n .map(([p, n]) => `p${p}: ${n}`)\n .join(\", \");\n ui_parts.push(\n `> Distribution across ${total_doc_pages} document pages — ${distribution}. Pass \\`page=N\\` to filter to a specific document page.`,\n );\n }\n }\n\n // Per-match occurrence counts use the FULL match set, not the filtered one —\n // this gives the LLM accurate global counts even when filtering.\n const occurrences_map: Record<string, number> = {};\n for (const m of allMatches) {\n const matched_str = m[0];\n occurrences_map[matched_str] = (occurrences_map[matched_str] || 0) + 1;\n }\n\n function get_heading(idx: number, txt: string): string {\n const txtBefore = txt.substring(0, idx);\n const lines = txtBefore.split(\"\\n\");\n const path: string[] = [];\n let current_level = 999;\n\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i];\n const m = line.match(/^(#{1,6})\\s+(.*)/);\n if (m) {\n const level = m[1].length;\n if (level < current_level) {\n let cleanHeading = m[2]\n .replace(/\\*\\*|__|[*_]/g, \"\")\n .replace(/\\{#[^}]+\\}/g, \"\")\n .trim();\n if (cleanHeading.length > 80) {\n cleanHeading = cleanHeading.substring(0, 80) + \"...\";\n }\n path.unshift(cleanHeading);\n current_level = level;\n if (level === 1) break;\n }\n }\n }\n return path.join(\" > \");\n }\n\n let i = 1;\n for (const m of matches) {\n const matched_str = m[0];\n const m_start = m.index!;\n const m_end = m_start + matched_str.length;\n const p_num = pageOfOffset(m_start);\n\n const snippet_start = Math.max(0, m_start - 100);\n const snippet_end = Math.min(body.length, m_end + 100);\n const snippet =\n body.substring(snippet_start, m_start) +\n `**${matched_str}**` +\n body.substring(m_end, snippet_end);\n\n const snippet_lines = snippet\n .split(\"\\n\")\n .filter((line) => line.trim().length > 0)\n .map((line) => `> ${line}`)\n .join(\"\\n\");\n\n ui_parts.push(\"---\");\n ui_parts.push(`### Match ${i} (p${p_num})`);\n\n const h_path = get_heading(m_start, body);\n if (h_path) {\n ui_parts.push(`**Path:** \\`${h_path}\\``);\n }\n\n const count = occurrences_map[matched_str];\n ui_parts.push(snippet_lines);\n ui_parts.push(\n `*Occurrences:* This exact phrasing appears ${count} time${count !== 1 ? \"s\" : \"\"} in the document.`,\n );\n\n i++;\n }\n\n if (regexDowngradedNote) ui_parts.unshift(regexDowngradedNote);\n const ui_markdown = ui_parts.join(\"\\n\\n\");\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n title: `Search: ${basename(file_path)}`,\n file_path: resolve(file_path),\n },\n };\n}\n","// FILE: node/packages/mcp-server/src/shared.ts\nexport const MARKDOWN_UI_URI = \"ui://adeu/markdown-ui\";\n"],"mappings":"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","names":["basename","resolve","DocumentObject","ui_markdown","llm_content","i","basename","DocumentObject","res","resolve","gitSha"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/response-builders.ts","../src/shared.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { basename, resolve, extname, dirname, join } from \"node:path\";\nimport { z } from \"zod\";\nimport {\n registerAppTool as origRegisterAppTool,\n registerAppResource,\n RESOURCE_MIME_TYPE,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport fs from \"node:fs\";\nimport {\n identifyEngine,\n extractTextFromBuffer,\n _extractTextFromDoc,\n DocumentObject,\n RedlineEngine,\n BatchValidationError,\n create_word_patch_diff,\n finalize_document,\n} from \"@adeu/core\";\nimport { describe_illegal_control_chars } from \"@adeu/core\";\n\nimport {\n build_paginated_response,\n build_full_document_response,\n build_outline_response,\n build_appendix_response,\n build_search_response,\n} from \"./response-builders.js\";\n\nimport { MARKDOWN_UI_URI } from \"./shared.js\";\n// Parity with Python models.py `_infer_type_in_place` + `_coerce_match_mode_in_place`.\n// The MCP boundary schema is permissive; these repairs let recoverable payloads\n// (a missing `type` that's unambiguous from the key signature, or a non-canonical\n// `match_mode`) succeed instead of failing the whole-array Zod parse with an\n// opaque -32602. Anything still un-inferrable is caught by the handler guard\n// below and reported per-index; anything that doesn't apply to the document is\n// caught by the engine's validate_edits. Mirrors how Python repairs in a\n// BeforeValidator ahead of its (strict) discriminated union.\nconst MATCH_MODE_SYNONYMS: Record<string, \"strict\" | \"first\" | \"all\"> = {\n strict: \"strict\",\n first: \"first\",\n all: \"all\",\n first_only: \"first\",\n firstonly: \"first\",\n \"first-only\": \"first\",\n all_occurrences: \"all\",\n alloccurrences: \"all\",\n \"all-occurrences\": \"all\",\n every: \"all\",\n};\n\nfunction coerceChangeItemInPlace(item: any): void {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) return;\n\n // Infer a missing `type` ONLY when exactly one variant fits unambiguously.\n // Deliberately do NOT infer from `target_id` alone (accept vs reject is a\n // semantic choice) or `target_text` alone (delete_row vs empty-new_text\n // modify). Those stay absent and are rejected with a clear message.\n if (!(\"type\" in item) || item.type === undefined || item.type === null) {\n if (\"cells\" in item) item.type = \"insert_row\";\n else if (\"text\" in item && \"target_id\" in item) item.type = \"reply\";\n else if (\"target_text\" in item && \"new_text\" in item) item.type = \"modify\";\n }\n\n // Normalize match_mode: canonical passes through, synonyms map, anything else\n // (help-string echo \"strict, first, or all\", empty, non-string) is dropped so\n // the engine's \"strict\" default applies. Never coerce junk to \"all\" — that\n // would silently mass-edit; defaulting to strict fails safe with an\n // ambiguity error instead.\n if (\"match_mode\" in item) {\n const raw = item.match_mode;\n if (typeof raw !== \"string\") {\n delete item.match_mode;\n } else {\n const mapped = MATCH_MODE_SYNONYMS[raw.trim().toLowerCase()];\n if (mapped === undefined) delete item.match_mode;\n else item.match_mode = mapped;\n }\n }\n}\nfunction readFileBytesOrThrow(filePath: string): Buffer {\n try {\n return readFileSync(filePath);\n } catch (err: any) {\n if (err.code === \"ENOENT\") {\n // Lean, agent-appropriate error: list sibling .docx files so the model\n // can self-correct a wrong filename (e.g. a guessed `-processed` suffix)\n // in one turn, instead of being handed CLI install instructions that are\n // irrelevant inside an agent loop and pure token waste.\n let available = \"\";\n try {\n const dir = dirname(filePath);\n const docs = fs\n .readdirSync(dir)\n .filter((f) => f.toLowerCase().endsWith(\".docx\"));\n available = docs.length\n ? ` available files: [${docs.join(\", \")}]`\n : ` (no .docx files found in ${dir})`;\n } catch {\n // Directory unreadable — omit the listing rather than fail.\n }\n throw new Error(`file not found: ${basename(filePath)};${available}`);\n }\n throw err;\n }\n}\n\n// --- Asset Loaders for UI ---\nconst DIST_DIR = import.meta.dirname;\n\nfunction getAssetContent(\n folder: \"templates\" | \"assets\",\n filename: string,\n fallbackMessage: string,\n): string {\n const filePath = join(DIST_DIR, folder, filename);\n if (existsSync(filePath)) {\n return readFileSync(filePath, \"utf-8\");\n }\n return fallbackMessage;\n}\n\n// --- Tool Description Constants ---\nconst READ_DOCX_COMMON_DESC =\n \"Reads a DOCX file. Returns text with inline CriticMarkup for Tracked Changes and Comments: {++inserted++}, {--deleted--}, {==highlighted==}{>>comment<<}. Set clean_view=True for the finalized 'Accepted' text without markup.\\n\\n\";\nconst READ_DOCX_TAIL =\n \"Modes:\\n- 'full' (default): paginated body content. Use page=N to navigate.\\n- 'outline': heading map only — start here for large docs to plan targeted reads. Defaults to L1-L2 headings; pass outline_max_level=3-6 to see deeper structure.\\n- 'appendix': defined terms, anchors, and cross-reference targets. Consult before editing legal/technical docs to avoid breaking references.\";\n\nconst PROCESS_BATCH_COMMON_DESC =\n \"Applies a batch of edits and review actions to a DOCX.\\n\\nBatches apply SEQUENTIALLY: each change is validated and applied against the document state produced by the changes before it, so you may chain dependent edits within one batch (e.g. rename X to Y, then modify Y — the second edit must target Y, the text as it reads after the rename). Validation failures reject the whole batch transactionally: nothing is applied until every change resolves.\\n\\n\";\nconst PROCESS_BATCH_OPERATIONS_DESC =\n \"Each item in `changes` must specify a `type`:\\n1. 'modify': Search-and-replace. By default `target_text` must match uniquely (`match_mode`:'strict') — add surrounding context to disambiguate, or set `match_mode`:'first'/'all' to edit the first or every occurrence. Set `regex`:true to treat `target_text` as a regular expression (capture groups available in `new_text` as $1, $2…). `new_text` supports Markdown: '# Heading 1' through '###### Heading 6', '**bold**', '_italic_', and '\\\\n\\\\n' to split into multiple paragraphs. Empty `new_text` deletes. Do NOT write CriticMarkup tags ({++, {--, {>>) manually — use the `comment` parameter for comments.\\n • EMPTY/FORM TABLE CELLS: a blank cell has no text to match. `read_docx` renders each cell with a trailing `{#cell:<id>}` anchor — to fill a blank cell, set `target_text` to that exact anchor (e.g. '{#cell:0000005E}') and put the value in `new_text`. Do NOT try to match the pipe layout ('Date | | |'); the pipes are display separators, not editable text.\\n2. 'accept' / 'reject': Finalize or revert a tracked change by `target_id` (e.g. 'Chg:12').\\n3. 'reply': Reply to a comment by `target_id` (e.g. 'Com:5') with `text`.\\n4. 'insert_row' / 'delete_row': Table edits. Disk mode only — not supported on Live Word canvas.\\n\\nID VOLATILITY: 'Chg:N' and 'Com:N' shift between document states. Always call `read_docx` immediately before any accept/reject/reply — do not reuse IDs from earlier in the conversation. The `{#cell:<id>}` anchors are stable (Word-assigned) and safe to reuse across reads.\\n\\n`author_name` is used for attribution on all tracked changes and comments, in both disk and Live Word modes.\";\n\nconst DIFF_DOCX_DESC =\n \"Compares two DOCX files and returns a compact `@@ Word Patch @@` diff — Adeu's token-level, sub-word patch format — of their text content. Useful for analyzing differences between versions before editing.\";\n\nconst gitSha = process.env.GIT_SHA || \"unknown\";\nconst packageVersion = process.env.PACKAGE_VERSION || \"unknown\";\nconst buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;\n\n// --- Server Setup ---\nconst server = new McpServer({\n name: \"adeu-redlining-service\",\n version: packageVersion,\n});\n\n// Wrap server.registerTool to inject buildTag into descriptions\nconst originalRegisterTool = server.registerTool.bind(server);\nserver.registerTool = (name: string, schema: any, handler?: any) => {\n if (schema && typeof schema === \"object\") {\n // Idempotent: UI tools route through BOTH this wrapper and the\n // registerAppTool wrapper, so guard against stamping the tag twice.\n if (schema.description && !schema.description.includes(buildTag.trim())) {\n schema.description = schema.description.trim() + buildTag;\n }\n }\n return originalRegisterTool(name, schema, handler);\n};\n\n// Wrap registerAppTool to inject buildTag into descriptions\nconst registerAppTool: typeof origRegisterAppTool = (\n mcpServer,\n name,\n schema,\n handler,\n) => {\n if (schema && typeof schema === \"object\") {\n if (schema.description) {\n schema.description = schema.description.trim() + buildTag;\n }\n }\n return origRegisterAppTool(mcpServer, name, schema, handler);\n};\n\n// Common CSP allowing Google Fonts used by Adeu UI templates\nconst UI_CSP = {\n connectDomains: [\"https://fonts.googleapis.com\", \"https://fonts.gstatic.com\"],\n resourceDomains: [\n \"https://fonts.googleapis.com\",\n \"https://fonts.gstatic.com\",\n ],\n};\n\n// ==========================================\n// 1. UI RESOURCES\n// ==========================================\n\nregisterAppResource(\n server,\n MARKDOWN_UI_URI,\n MARKDOWN_UI_URI,\n { mimeType: RESOURCE_MIME_TYPE, description: \"Adeu Markdown Viewer UI\" },\n async () => {\n let html = getAssetContent(\n \"templates\",\n \"markdown_ui.html\",\n \"<html><body>UI Template Not Found</body></html>\",\n );\n const markedJs = getAssetContent(\n \"assets\",\n \"marked.min.js\",\n \"window.__MARKED_ERROR = 'marked.min.js not found';\",\n );\n const svg = getAssetContent(\"assets\", \"adeu.svg\", \"\");\n\n html = html\n .replace(\"[[marked_js_code | safe]]\", markedJs)\n .replace(\"[[ adeu_svg_code ]]\", svg);\n\n return {\n contents: [\n {\n uri: MARKDOWN_UI_URI,\n mimeType: RESOURCE_MIME_TYPE,\n text: html,\n _meta: { ui: { csp: UI_CSP } },\n },\n ],\n };\n },\n);\n\n// ==========================================\n// 2. UI-ENABLED TOOLS\n// ==========================================\nregisterAppTool(\n server,\n \"read_docx\",\n {\n title: \"Read DOCX\",\n description: READ_DOCX_COMMON_DESC + READ_DOCX_TAIL,\n inputSchema: z.object({\n reasoning: z\n .string()\n .describe(\n \"Why do I need to read this docx document? State this reason before any other parameter.\",\n ),\n file_path: z.string().describe(\"Absolute path to the DOCX file.\"),\n clean_view: z\n .boolean()\n .default(false)\n .describe(\n \"If False (default), returns the 'Raw' text with inline CriticMarkup. If True, returns 'Accepted' text.\",\n ),\n mode: z\n .enum([\"full\", \"outline\", \"appendix\"])\n .default(\"full\")\n .describe(\n \"'full' returns body content. 'outline' returns a structural heading map. 'appendix' returns defined terms.\",\n ),\n page: z\n .union([z.number(), z.string()])\n .optional()\n .describe(\n \"Without `search_query`: 1-indexed document page to display (defaults to 1). With `search_query`: restricts matches to that document page (defaults to searching all pages; pass `page='all'` to be explicit). Note: pages are synthetic, length-based content chunks sized for LLM consumption — they do NOT correspond to printed Word pages or explicit page breaks.\",\n ),\n outline_max_level: z.coerce\n .number()\n .default(2)\n .describe(\"For mode='outline' only: cap on heading depth.\"),\n outline_verbose: z\n .boolean()\n .default(false)\n .describe(\"For mode='outline' only: includes metadata.\"),\n search_query: z\n .string()\n .optional()\n .describe(\n \"The substring or regex pattern to search for. When provided, filters results to matching paragraphs.\",\n ),\n search_regex: z\n .boolean()\n .default(false)\n .describe(\n \"Set to true to interpret search_query as a regular expression.\",\n ),\n search_case_sensitive: z\n .boolean()\n .default(true)\n .describe(\"Set to false to perform case-insensitive matching.\"),\n }),\n _meta: { ui: { resourceUri: MARKDOWN_UI_URI } },\n },\n async ({\n reasoning,\n file_path,\n clean_view,\n mode,\n page,\n outline_max_level,\n outline_verbose,\n search_query,\n search_regex,\n search_case_sensitive,\n }) => {\n try {\n void reasoning;\n const buf = readFileBytesOrThrow(file_path);\n\n if (mode === \"outline\") {\n const doc = await DocumentObject.load(buf);\n const extract_res = _extractTextFromDoc(\n doc,\n clean_view,\n true,\n true,\n ) as {\n text: string;\n paragraph_offsets: Map<any, [number, number]>;\n };\n const res = build_outline_response(\n doc,\n extract_res.text,\n file_path,\n outline_max_level,\n outline_verbose,\n extract_res.paragraph_offsets,\n );\n return res as any;\n }\n\n const text = await extractTextFromBuffer(buf, clean_view);\n if (search_query !== undefined && search_query !== null) {\n // In search mode, undefined `page` means \"search all document pages\".\n const res = build_search_response(\n text,\n search_query,\n search_regex,\n search_case_sensitive,\n page,\n file_path,\n );\n return res as any;\n }\n // In full mode, page='all' returns the entire document without page\n // chrome — the round-trip artifact for text-based apply/diff\n // (QA 2026-07-17 F1 parity with the Python CLI's --page all).\n if (\n mode === \"full\" &&\n page !== undefined &&\n page !== null &&\n String(page).trim().toLowerCase() === \"all\"\n ) {\n const res = build_full_document_response(text, file_path);\n return res as any;\n }\n // In non-search mode, `page` defaults to 1 (show document page 1).\n // Non-numeric values must error, not silently fall back to page 1\n // (QA L1 parity with the Python CLI).\n let resolvedPage = 1;\n if (page !== undefined && page !== null) {\n const parsed =\n typeof page === \"number\" ? page : parseInt(String(page).trim(), 10);\n if (!Number.isFinite(parsed)) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text:\n `Invalid page value: '${page}'. Provide a positive integer ` +\n `(pages are 1-indexed; 'all' is valid for mode='full' and together with search_query).`,\n },\n ],\n };\n }\n resolvedPage = parsed;\n }\n if (mode === \"appendix\") {\n const res = build_appendix_response(text, resolvedPage, file_path);\n return res as any;\n }\n const res = build_paginated_response(text, resolvedPage, file_path);\n return res as any;\n } catch (e: any) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `Error executing tool read_docx: ${e.message}`,\n },\n ],\n };\n }\n },\n);\n\n// ==========================================\n// 3. HEADLESS TOOLS (No UI)\n// ==========================================\n\n// Typed shape for a single `process_document_batch` change. This makes the six\n// DocumentChange variants — and the modify-only `match_mode`/`regex` options —\n// discoverable from the tool schema itself, instead of prose alone. A bare\n// string is still accepted (and normalized in-handler) so double-serialized\n// payloads from some LLM clients keep working; only `type` is required, all\n// other fields are optional, and unknown keys pass through untouched.\nconst CHANGE_ITEM_SCHEMA = z\n .object({\n type: z\n .enum([\"modify\", \"accept\", \"reject\", \"reply\", \"insert_row\", \"delete_row\"])\n .optional()\n .describe(\n \"Change kind: 'modify' (search-and-replace), 'accept'/'reject' (resolve a tracked change by id), 'reply' (reply to a comment by id), 'insert_row'/'delete_row' (table edits; disk mode only). If omitted it is inferred when unambiguous from the other fields.\",\n ),\n target_text: z\n .string()\n .optional()\n .describe(\n \"modify / insert_row / delete_row: the existing text to locate (interpreted as a regex when regex=true).\",\n ),\n new_text: z\n .string()\n .optional()\n .describe(\n \"modify: replacement text. Supports Markdown (headings, **bold**, _italic_, '\\\\n\\\\n' paragraph splits); empty string deletes. Regex capture groups are available as $1, $2…\",\n ),\n target_id: z\n .string()\n .optional()\n .describe(\n \"accept / reject / reply: the 'Chg:N' or 'Com:N' id taken from a fresh read_docx.\",\n ),\n text: z.string().optional().describe(\"reply: the reply body.\"),\n comment: z\n .string()\n .optional()\n .describe(\n \"modify / accept / reject: attach a margin comment to the change (no manual CriticMarkup).\",\n ),\n match_mode: z\n .enum([\"strict\", \"first\", \"all\"])\n .optional()\n .describe(\n \"modify only: 'strict' (default — target must match uniquely), 'first' (first occurrence), or 'all' (every occurrence).\",\n ),\n regex: z\n .boolean()\n .optional()\n .describe(\n \"modify only: treat target_text as a regular expression (default false).\",\n ),\n position: z\n .enum([\"above\", \"below\"])\n .optional()\n .describe(\n \"insert_row: place the new row above or below the matched row.\",\n ),\n cells: z\n .array(z.string())\n .optional()\n .describe(\"insert_row: the cell values for the new row, left to right.\"),\n })\n .passthrough();\n\nserver.registerTool(\n \"process_document_batch\",\n {\n description: PROCESS_BATCH_COMMON_DESC + PROCESS_BATCH_OPERATIONS_DESC,\n inputSchema: {\n reasoning: z\n .string()\n .describe(\n \"Why do I need to apply these changes to the document? State this reason before any other parameter.\",\n ),\n original_docx_path: z\n .string()\n .describe(\"Absolute path to the source file.\"),\n author_name: z\n .string()\n .describe(\"Name to appear in Track Changes (e.g., 'Reviewer AI').\"),\n changes: z\n .array(z.union([z.string(), CHANGE_ITEM_SCHEMA]))\n .describe(\n \"Ordered list of changes to apply. Each item is an object carrying a `type` discriminator plus that type's fields (see the per-field docs and the tool description). Items apply SEQUENTIALLY: each one evaluates against the document state produced by the items before it, so later items may target text an earlier item introduced.\",\n ),\n output_path: z.string().optional().describe(\"Optional output path.\"),\n dry_run: z\n .boolean()\n .optional()\n .default(false)\n .describe(\n \"If True, simulates the changes and returns a detailed preview report without modifying any files.\",\n ),\n },\n },\n async ({\n reasoning,\n original_docx_path,\n author_name,\n changes,\n output_path,\n dry_run,\n }) => {\n try {\n void reasoning;\n if (!author_name || !author_name.trim())\n return {\n content: [\n { type: \"text\", text: \"Error: author_name cannot be empty.\" },\n ],\n };\n const author_ctrl = describe_illegal_control_chars(author_name);\n if (author_ctrl)\n return {\n content: [\n {\n type: \"text\",\n text:\n `Error: author_name contains control character(s) (${author_ctrl}) ` +\n `that cannot be stored in a DOCX. Remove them and retry.`,\n },\n ],\n };\n if (!changes || changes.length === 0)\n return {\n content: [{ type: \"text\", text: \"Error: No changes provided.\" }],\n };\n\n // Defensive sanitization at the MCP boundary: some LLM clients\n // \"double-serialize\" nested arrays, delivering each element of `changes`\n // as a JSON string instead of an object. The core engine also guards\n // against this, but we normalize here too so the tool layer never hands\n // raw string primitives downstream regardless of the engine version\n // bundled. Genuine objects and unparseable strings pass through\n // untouched so validation surfaces a clear error rather than crashing.\n const sanitizedChanges = changes.map((item: any) => {\n let obj: any = item;\n if (typeof item === \"string\") {\n try {\n const parsed = JSON.parse(item);\n obj = parsed !== null && typeof parsed === \"object\" ? parsed : item;\n } catch {\n obj = item;\n }\n }\n // Repair recoverable payloads (infer type, normalize match_mode) the\n // same way Python does before its union validation.\n if (obj !== null && typeof obj === \"object\" && !Array.isArray(obj)) {\n coerceChangeItemInPlace(obj);\n }\n return obj;\n });\n\n // Boundary guard, scoped narrowly: after inference, reject only an OBJECT\n // that still carries no resolvable `type`. Strings, nulls, and non-objects\n // are intentionally left for the engine's validate_edits to report\n // (\"Invalid change format… received a primitive\"), keeping the engine the\n // single authority for those and avoiding a competing error surface.\n // A typeless object is the one case the engine can't cleanly reject (with\n // `type` now optional it would fall into the edits bucket as a no-op), so\n // it is caught here with an actionable, per-index message.\n const VALID_TYPES = new Set([\n \"modify\",\n \"accept\",\n \"reject\",\n \"reply\",\n \"insert_row\",\n \"delete_row\",\n ]);\n const typeErrors: string[] = [];\n sanitizedChanges.forEach((c: any, i: number) => {\n if (\n c !== null &&\n typeof c === \"object\" &&\n !Array.isArray(c) &&\n (!c.type || !VALID_TYPES.has(c.type))\n ) {\n typeErrors.push(\n `- Change ${i + 1}: missing or unrecognized \"type\". Use one of: modify (needs target_text + new_text), accept/reject (needs target_id like \"Chg:12\"), reply (needs target_id like \"Com:5\" + text), insert_row (needs target_text + cells), delete_row (needs target_text). Received keys: [${Object.keys(c).join(\", \")}].`,\n );\n }\n });\n if (typeErrors.length > 0) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `Batch rejected. Some changes are malformed:\\n\\n${typeErrors.join(\"\\n\")}`,\n },\n ],\n };\n }\n\n let outPath = output_path;\n if (!outPath) {\n const ext = extname(original_docx_path);\n const base = basename(original_docx_path, ext);\n const dir = dirname(original_docx_path);\n // Idempotency guard (parity with Python document.py): if the input is\n // already a processed artifact, write back to it instead of compounding\n // the suffix into contract_processed_processed.docx, which fragments the\n // agent's document state across files.\n if (base.endsWith(\"_processed\") || base.endsWith(\"_redlined\")) {\n outPath = resolve(dir, `${base}${ext}`);\n } else {\n outPath = resolve(dir, `${base}_processed${ext}`);\n }\n }\n\n const buf = readFileBytesOrThrow(original_docx_path);\n const doc = await DocumentObject.load(buf);\n const engine = new RedlineEngine(doc, author_name);\n\n let stats;\n try {\n stats = engine.process_batch(sanitizedChanges, dry_run);\n } catch (e: any) {\n if (e instanceof BatchValidationError) {\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `Batch rejected. Some edits failed validation:\\n\\n${e.errors.join(\"\\n\\n\")}`,\n },\n ],\n };\n }\n throw e;\n }\n\n if (!dry_run) {\n const outBuf = await doc.save();\n try {\n fs.writeFileSync(outPath, outBuf);\n } catch (e: any) {\n // Filesystem failures (name too long, missing directory, perms)\n // must surface as a clear, actionable error (QA H3 parity).\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: `Could not write output file '${outPath}': ${e.message}`,\n },\n ],\n };\n }\n }\n\n let res = formatBatchResult(stats, outPath, !!dry_run);\n if (sanitizedChanges.length === 0) {\n res =\n `⚠️ 0 changes provided — nothing to do. The output is an unmodified copy of the original.\\n\\n` +\n res;\n }\n return { content: [{ type: \"text\", text: res }] };\n } catch (e: any) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `Error: ${e.message}` }],\n };\n }\n },\n);\n\nserver.registerTool(\n \"accept_all_changes\",\n {\n description:\n \"Accepts all tracked changes and removes all comments in a single operation.\",\n inputSchema: {\n reasoning: z\n .string()\n .describe(\n \"Why do I need to accept all changes in this document? State this reason before any other parameter.\",\n ),\n docx_path: z.string().describe(\"Absolute path to the DOCX file.\"),\n output_path: z.string().optional().describe(\"Optional output path.\"),\n },\n },\n async ({ reasoning, docx_path, output_path }) => {\n try {\n void reasoning;\n let outPath = output_path;\n if (!outPath) {\n const ext = extname(docx_path);\n const base = basename(docx_path, ext);\n const dir = dirname(docx_path);\n outPath = resolve(dir, `${base}_clean${ext}`);\n }\n\n const buf = readFileBytesOrThrow(docx_path);\n const doc = await DocumentObject.load(buf);\n const engine = new RedlineEngine(doc);\n\n engine.accept_all_revisions();\n\n const outBuf = await doc.save();\n\n fs.writeFileSync(outPath, outBuf);\n\n return {\n content: [\n { type: \"text\", text: `Accepted all changes. Saved to: ${outPath}` },\n ],\n };\n } catch (e: any) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `Error: ${e.message}` }],\n };\n }\n },\n);\n\nserver.registerTool(\n \"diff_docx_files\",\n {\n description: DIFF_DOCX_DESC,\n inputSchema: {\n reasoning: z\n .string()\n .describe(\n \"Why do I need to diff these two documents? State this reason before any other parameter.\",\n ),\n original_path: z\n .string()\n .describe(\"Absolute path to the baseline DOCX file.\"),\n modified_path: z\n .string()\n .describe(\"Absolute path to the modified DOCX file.\"),\n compare_clean: z\n .boolean()\n .default(true)\n .describe(\n \"If True, compares 'Accepted' state. If False, compares raw text.\",\n ),\n },\n },\n async ({ reasoning, original_path, modified_path, compare_clean }) => {\n try {\n void reasoning;\n const origBuf = readFileBytesOrThrow(original_path);\n const modBuf = readFileBytesOrThrow(modified_path);\n\n const origText = await extractTextFromBuffer(origBuf, compare_clean);\n const modText = await extractTextFromBuffer(modBuf, compare_clean);\n\n const diff = create_word_patch_diff(\n origText,\n modText,\n basename(original_path),\n basename(modified_path),\n );\n\n return {\n content: [{ type: \"text\", text: diff || \"No differences found.\" }],\n };\n } catch (e: any) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `Error: ${e.message}` }],\n };\n }\n },\n);\n\nserver.registerTool(\n \"finalize_document\",\n {\n description:\n \"Prepares a document for external distribution or e-signature. Note: in this zero-dependency environment, protection_mode='encrypt' is unsupported and falls back to a native read-only lock; export_pdf and password are ignored.\",\n inputSchema: {\n reasoning: z\n .string()\n .describe(\n \"Why do I need to finalize this document? State this reason before any other parameter.\",\n ),\n file_path: z.string().describe(\"Absolute path to the DOCX file.\"),\n output_path: z.string().optional().describe(\"Optional output path.\"),\n sanitize_mode: z\n .enum([\"full\", \"keep-markup\"])\n .optional()\n .describe(\"full removes all markup, keep-markup redacts metadata.\"),\n accept_all: z\n .boolean()\n .optional()\n .describe(\n \"If true, auto-accepts all unresolved track changes before finalizing.\",\n ),\n protection_mode: z\n .enum([\"read_only\", \"encrypt\"])\n .optional()\n .describe(\n \"Native OOXML document locking. Note: 'encrypt' is unsupported in this zero-dependency build and falls back to 'read_only'.\",\n ),\n password: z.string().optional().describe(\"Ignored in this environment.\"),\n author: z\n .string()\n .optional()\n .describe(\"Replace all remaining markup authorship with this name.\"),\n export_pdf: z\n .boolean()\n .optional()\n .describe(\"Ignored in this environment.\"),\n },\n },\n async ({\n reasoning,\n file_path,\n output_path,\n sanitize_mode,\n accept_all,\n protection_mode,\n author,\n export_pdf,\n }) => {\n try {\n void reasoning;\n let outPath = output_path;\n if (!outPath) {\n const ext = extname(file_path);\n const base = basename(file_path, ext);\n const dir = dirname(file_path);\n outPath = resolve(dir, `${base}_final${ext}`);\n }\n\n const buf = readFileBytesOrThrow(file_path);\n const doc = await DocumentObject.load(buf);\n\n const result = await finalize_document(doc, {\n filename: basename(file_path),\n sanitize_mode: (sanitize_mode as any) || \"full\",\n accept_all: accept_all as boolean,\n protection_mode: protection_mode as any,\n author: author as string,\n export_pdf: export_pdf as boolean,\n });\n\n fs.writeFileSync(outPath, result.outBuffer!);\n\n return {\n content: [\n {\n type: \"text\",\n text: `Saved to: ${outPath}\\n\\n${result.reportText}`,\n },\n ],\n };\n } catch (e: any) {\n return {\n isError: true,\n content: [{ type: \"text\", text: `Error: ${e.message}` }],\n };\n }\n },\n);\n\n// --- Formatter for process_document_batch ---\nexport function formatBatchResult(\n stats: any,\n outPath: string,\n dry_run: boolean,\n): string {\n let res = \"\";\n if (dry_run) {\n res = `Dry-run simulation complete.\\n`;\n } else {\n res = `Batch complete. Saved to: ${outPath}\\n`;\n }\n const total_occurrences = stats.edits\n ? stats.edits.reduce(\n (acc: number, e: any) =>\n acc + (e.status === \"applied\" ? e.occurrences_modified || 1 : 0),\n 0,\n )\n : 0;\n const occ_text =\n total_occurrences > stats.edits_applied\n ? ` (${total_occurrences} occurrences)`\n : \"\";\n\n res += `Actions: ${stats.actions_applied} applied, ${stats.actions_skipped} skipped.\\n`;\n res += `Edits: ${stats.edits_applied} applied${occ_text}, ${stats.edits_skipped} skipped.\\n`;\n\n if (stats.edits && stats.edits.length > 0) {\n res += \"\\nDetailed Edit Reports:\\n\";\n for (let i = 0; i < stats.edits.length; i++) {\n const report = stats.edits[i];\n const status_indicator =\n report.status === \"applied\" ? \"✅ [applied]\" : \"❌ [failed]\";\n\n const pagesStr =\n report.pages && report.pages.length > 0\n ? ` (p${report.pages.join(\", p\")})`\n : \"\";\n\n res += `### Edit ${i + 1} ${status_indicator}${pagesStr}\\n`;\n\n if (report.heading_path) {\n res += `**Path:** \\`${report.heading_path}\\`\\n`;\n }\n\n if (report.match_mode) {\n const occ =\n report.occurrences_modified || (report.status === \"applied\" ? 1 : 0);\n res += `**Mode:** \\`${report.match_mode}\\` (${occ} occurrence${occ !== 1 ? \"s\" : \"\"} modified)\\n`;\n }\n\n if (report.error) {\n res += `*Error:* ${report.error}\\n`;\n }\n if (report.warning) {\n res += `*Warning:* ${report.warning}\\n`;\n }\n\n if (report.critic_markup) {\n res += `*Preview (CriticMarkup):*\\n> ${report.critic_markup.split(\"\\\\n\").join(\"\\\\n> \")}\\n`;\n }\n if (report.clean_text) {\n res += `*Preview (Clean):*\\n> ${report.clean_text.split(\"\\\\n\").join(\"\\\\n> \")}\\n`;\n }\n res += \"\\n\";\n }\n }\n\n if (stats.skipped_details && stats.skipped_details.length > 0) {\n res += `Skipped Details:\\n${stats.skipped_details.join(\"\\n\")}`;\n }\n return res.trim();\n}\n\n// --- Startup ---\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n const gitSha = process.env.GIT_SHA || \"unknown\";\n const buildTs = process.env.BUILD_TIMESTAMP || \"unknown\";\n console.error(\n `Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha}@${buildTs}`,\n );\n}\n\nmain().catch(console.error);\n","import { resolve, basename } from \"node:path\";\nimport {\n DocumentObject,\n paginate,\n split_structural_appendix,\n extract_outline,\n OutlineNode,\n RegexTimeoutError,\n userFindAllMatches,\n} from \"@adeu/core\";\n\nexport interface ToolResult {\n content: { type: \"text\"; text: string }[];\n structuredContent?: any;\n isError?: boolean;\n [key: string]: unknown;\n}\n\nfunction _build_appendix_pointer(has_appendix: boolean): string {\n if (!has_appendix) return \"\";\n return `\\n\\n---\\n\\n> **Appendix available.** This document has structural metadata (defined terms, cross-references, bookmarks, diagnostics) that may be relevant when editing. Call \\`read_docx\\` with \\`mode='appendix'\\` to load it before submitting edits.`;\n}\n\nfunction _build_page_banner(page: number, total: number): string {\n if (total <= 1) return \"\";\n return `> **Page ${page} of ${total}** — call \\`read_docx\\` with \\`mode='outline'\\` for a heading map of the full document.\\n\\n---\\n\\n`;\n}\n\nfunction _build_page_footer(\n page: number,\n total: number,\n has_next: boolean,\n): string {\n if (total <= 1 || !has_next) return \"\";\n return `\\n\\n---\\n\\n> **Continues on page ${page + 1} of ${total}.**`;\n}\n\nexport function render_outline_tree(\n nodes: OutlineNode[],\n max_level: number = 2,\n verbose: boolean = false,\n): string {\n if (!nodes || nodes.length === 0) {\n return \"# (No headings detected)\\n\\nThis document has no detectable headings.\";\n }\n\n const visible = nodes.filter((n) => n.level <= max_level);\n\n if (visible.length === 0) {\n return `# (No headings at level <= ${max_level})\\n\\nDocument has ${nodes.length} headings, all at deeper levels. Call read_docx with mode='outline' and outline_max_level=N (up to 6) to see them.`;\n }\n\n const lines: string[] = [];\n for (const node of visible) {\n const prefix = \"#\".repeat(node.level);\n if (verbose) {\n const meta_parts = [`p${node.page}`, node.style];\n if (node.has_table) meta_parts.push(\"has table\");\n if (node.footnote_ids && node.footnote_ids.length > 0)\n meta_parts.push(\"fn:\" + node.footnote_ids.join(\",\"));\n lines.push(`${prefix} ${node.text} (${meta_parts.join(\", \")})`);\n } else {\n lines.push(`${prefix} ${node.text} (p${node.page})`);\n }\n }\n return lines.join(\"\\n\");\n}\n\nexport function build_full_document_response(\n text: string,\n file_path: string,\n): ToolResult {\n // The ENTIRE document body with no page banner, continuation footer, or\n // appendix pointer — the round-trip artifact for text-based apply/diff\n // (QA 2026-07-17 F1; mirrors Python's build_full_document_response).\n const [body] = split_structural_appendix(text);\n const ui_markdown = body;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: basename(file_path),\n },\n };\n}\n\nexport function build_paginated_response(\n text: string,\n page: number,\n file_path: string,\n): ToolResult {\n const [body, appendix] = split_structural_appendix(text);\n const has_appendix = Boolean(appendix.trim());\n\n const result = paginate(body, \"\");\n\n if (page < 1 || page > result.total_pages) {\n throw new Error(\n `Page ${page} out of range (doc has ${result.total_pages} pages).`,\n );\n }\n\n const selected = result.pages[page - 1];\n const banner = _build_page_banner(selected.page, selected.total_pages);\n const footer = _build_page_footer(\n selected.page,\n selected.total_pages,\n selected.has_next,\n );\n const appendix_pointer = _build_appendix_pointer(has_appendix);\n\n const ui_markdown =\n banner + selected.page_content + footer + appendix_pointer;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n\n return {\n content: [{ type: \"text\", text: llm_content }],\n // Include structuredContent for the UI to render the markdown\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: basename(file_path),\n },\n };\n}\n\nexport function build_outline_response(\n doc: DocumentObject,\n projected_text: string,\n file_path: string,\n outline_max_level: number = 2,\n outline_verbose: boolean = false,\n paragraph_offsets: Map<any, [number, number]> | null = null,\n): ToolResult {\n // Levels outside 1-6 are meaningless (0/negative would render a\n // nonsensical \"L1-L0\" range label, QA L2). Clamp to the nearest sensible\n // depth, mirroring the Python builder.\n outline_max_level = Math.max(1, Math.min(outline_max_level, 6));\n\n const [body] = split_structural_appendix(projected_text);\n const pagination_result = paginate(body, \"\");\n\n const nodes = extract_outline(\n doc,\n body,\n pagination_result.body_pages,\n pagination_result.body_page_offsets,\n paragraph_offsets,\n );\n\n const rendered = render_outline_tree(\n nodes,\n outline_max_level,\n outline_verbose,\n );\n\n const visible_count = nodes.filter(\n (n) => n.level <= outline_max_level,\n ).length;\n const deeper_count = nodes.length - visible_count;\n const deeper_hint =\n deeper_count > 0\n ? ` (${deeper_count} more at deeper levels, raise outline_max_level to see)`\n : \"\";\n\n const header = `> **Outline view** — showing ${visible_count} of ${nodes.length} headings (L1-L${outline_max_level}${deeper_hint}) across ${pagination_result.total_pages} page(s). Call \\`read_docx\\` with \\`mode='full'\\` and \\`page=N\\` to read a section.\\n\\n---\\n\\n`;\n const ui_markdown = header + rendered;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: `Outline: ${basename(file_path)}`,\n },\n };\n}\n\nexport function build_appendix_response(\n text: string,\n page: number,\n file_path: string,\n): ToolResult {\n const [, appendix] = split_structural_appendix(text);\n\n if (!appendix.trim()) {\n const ui_markdown =\n \"# Appendix\\n\\nThis document has no structural appendix (no defined terms, named anchors, or diagnostics detected).\";\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: `Appendix: ${basename(file_path)}`,\n },\n };\n }\n\n const result = paginate(appendix, \"\");\n\n if (page < 1 || page > result.total_pages) {\n throw new Error(\n `Appendix page ${page} out of range (appendix has ${result.total_pages} pages).`,\n );\n }\n\n const selected = result.pages[page - 1];\n\n let banner = \"\";\n let footer = \"\";\n\n if (selected.total_pages > 1) {\n banner = `> **Appendix page ${selected.page} of ${selected.total_pages}** — structural metadata for this document.\\n\\n---\\n\\n`;\n footer = selected.has_next\n ? `\\n\\n---\\n\\n> **Continues on appendix page ${selected.page + 1} of ${selected.total_pages}.**`\n : \"\";\n } else {\n banner =\n \"> **Appendix** — structural metadata for this document.\\n\\n---\\n\\n\";\n }\n\n const ui_markdown = banner + selected.page_content + footer;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n file_path: resolve(file_path),\n title: `Appendix: ${basename(file_path)}`,\n },\n };\n}\n\nexport function build_search_response(\n text: string,\n search_query: string,\n search_regex: boolean,\n search_case_sensitive: boolean,\n page: number | string | undefined,\n file_path: string,\n): ToolResult {\n const [body] = split_structural_appendix(text);\n const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const flags = search_case_sensitive ? \"g\" : \"gi\";\n\n // When the caller asked for a regex but supplied something the engine can't\n // compile (e.g. an unterminated character class `\\[`, or an inline-flag group\n // `(?i)...` that JS RegExp rejects), do NOT hard-error and burn the turn.\n // Downgrade to a literal search of the raw string and tell the model, so it\n // can either accept the literal hits or fix its pattern — instead of retrying\n // the same broken regex.\n let regexDowngradedNote = \"\";\n let regex: RegExp;\n let isUserRegex = false;\n if (search_regex) {\n try {\n regex = new RegExp(search_query, flags);\n isUserRegex = true;\n } catch (e: any) {\n regexDowngradedNote =\n `> **Note:** \\`${search_query}\\` is not a valid regular expression ` +\n `(${e.message}), so it was searched as literal text instead. ` +\n `If you meant a regex, fix the pattern; if you meant literal text, set \\`search_regex\\` to false.`;\n regex = new RegExp(escapeRegExp(search_query), flags);\n }\n } else {\n regex = new RegExp(escapeRegExp(search_query), flags);\n }\n\n // Patterns that blow the matching time budget (catastrophic backtracking,\n // QA 2026-07-17 F5) get the same literal downgrade as invalid patterns —\n // for a read-only search, degraded results beat a hung event loop.\n let allMatches: Array<{ 0: string; index?: number }>;\n if (isUserRegex) {\n try {\n allMatches = userFindAllMatches(search_query, body, flags).map((m) => ({\n 0: body.slice(m.start, m.end),\n index: m.start,\n }));\n } catch (e: any) {\n if (!(e instanceof RegexTimeoutError)) throw e;\n regexDowngradedNote =\n `> **Note:** \\`${search_query}\\` was searched as literal text instead of as ` +\n `a regular expression: ${e.message}`;\n allMatches = Array.from(body.matchAll(new RegExp(escapeRegExp(search_query), flags)));\n }\n } else {\n allMatches = Array.from(body.matchAll(regex));\n }\n\n // Compute document pagination once — needed for both annotation and filtering.\n const pag_res = paginate(body, \"\");\n const page_offsets = pag_res.body_page_offsets;\n const total_doc_pages = pag_res.total_pages;\n\n // Resolve `page` parameter to either \"all\" or a concrete document-page number.\n // Undefined → \"all\" (search across the whole document).\n // \"all\" (case-insensitive) → \"all\".\n // A positive integer N → filter matches to document page N.\n // Anything else → hard error.\n let filter_doc_page: number | null = null; // null means \"all\"\n if (page !== undefined && page !== null) {\n const pageStr = String(page).toLowerCase();\n if (pageStr !== \"all\") {\n const parsed = parseInt(pageStr, 10);\n if (isNaN(parsed) || parsed < 1) {\n throw new Error(\n `Invalid page value: \\`${page}\\`. Pass a positive integer to restrict the search to that document page, omit \\`page\\` to search all pages, or pass \\`page='all'\\` explicitly.`,\n );\n }\n if (parsed > total_doc_pages) {\n throw new Error(\n `Document page ${parsed} is out of range — the document has ${total_doc_pages} page(s). In search mode, \\`page\\` filters matches to a specific document page; omit it or pass \\`page='all'\\` to search the whole document.`,\n );\n }\n filter_doc_page = parsed;\n }\n }\n\n // Helper: which document page does an offset live on?\n const pageOfOffset = (offset: number): number => {\n let p = 1;\n for (let j = 0; j < page_offsets.length; j++) {\n if (offset >= page_offsets[j]) p = j + 1;\n else break;\n }\n return p;\n };\n\n // Apply the filter (if any), but keep a record of all pages that had hits\n // so we can show a useful summary even when filtered.\n const pagesWithHits = new Set<number>();\n for (const m of allMatches) {\n pagesWithHits.add(pageOfOffset(m.index!));\n }\n\n const matches =\n filter_doc_page === null\n ? allMatches\n : allMatches.filter((m) => pageOfOffset(m.index!) === filter_doc_page);\n\n // --- Empty result ---\n if (matches.length === 0) {\n let body_msg: string;\n if (filter_doc_page !== null) {\n if (allMatches.length === 0) {\n body_msg = `> **Search Results** — No matches found for query \\`${search_query}\\` in \\`${basename(file_path)}\\`.\\n\\nVerify your search spelling, or try setting \\`search_case_sensitive\\` to false or enabling \\`search_regex\\` if you used pattern wildcards.`;\n } else {\n const hitPages = Array.from(pagesWithHits).sort((a, b) => a - b);\n body_msg = `> **Search Results** — No matches for \\`${search_query}\\` on document page ${filter_doc_page}.\\n\\nThe query DOES appear elsewhere in the document (${allMatches.length} match${allMatches.length !== 1 ? \"es\" : \"\"} on page${hitPages.length !== 1 ? \"s\" : \"\"} ${hitPages.join(\", \")}). Omit \\`page\\` or pass \\`page='all'\\` to see them.`;\n }\n } else {\n body_msg = `> **Search Results** — No matches found for query \\`${search_query}\\` in \\`${basename(file_path)}\\`.\\n\\nVerify your search spelling, or try setting \\`search_case_sensitive\\` to false or enabling \\`search_regex\\` if you used pattern wildcards.`;\n }\n if (regexDowngradedNote) body_msg = `${regexDowngradedNote}\\n\\n${body_msg}`;\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${body_msg}`;\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: body_msg,\n title: `Search: ${basename(file_path)}`,\n file_path: resolve(file_path),\n },\n };\n }\n\n // --- Build the response ---\n const ui_parts: string[] = [];\n\n if (filter_doc_page !== null) {\n ui_parts.push(\n `> **Search Results** — Found ${matches.length} match${matches.length !== 1 ? \"es\" : \"\"} for \\`${search_query}\\` on document page ${filter_doc_page} of ${total_doc_pages} in \\`${basename(file_path)}\\`.`,\n );\n const otherPages = Array.from(pagesWithHits)\n .filter((p) => p !== filter_doc_page)\n .sort((a, b) => a - b);\n if (otherPages.length > 0) {\n ui_parts.push(\n `> Additional matches exist on page${otherPages.length !== 1 ? \"s\" : \"\"} ${otherPages.join(\", \")} — omit \\`page\\` or pass \\`page='all'\\` to see them.`,\n );\n }\n } else {\n ui_parts.push(\n `> **Search Results** — Found ${matches.length} match${matches.length !== 1 ? \"es\" : \"\"} for \\`${search_query}\\` in \\`${basename(file_path)}\\`.`,\n );\n if (total_doc_pages > 1) {\n // Build a per-page hit distribution: \"p1: 3, p3: 1, p7: 12\"\n const counts = new Map<number, number>();\n for (const m of allMatches) {\n const p = pageOfOffset(m.index!);\n counts.set(p, (counts.get(p) || 0) + 1);\n }\n const distribution = Array.from(counts.entries())\n .sort((a, b) => a[0] - b[0])\n .map(([p, n]) => `p${p}: ${n}`)\n .join(\", \");\n ui_parts.push(\n `> Distribution across ${total_doc_pages} document pages — ${distribution}. Pass \\`page=N\\` to filter to a specific document page.`,\n );\n }\n }\n\n // Per-match occurrence counts use the FULL match set, not the filtered one —\n // this gives the LLM accurate global counts even when filtering.\n const occurrences_map: Record<string, number> = {};\n for (const m of allMatches) {\n const matched_str = m[0];\n occurrences_map[matched_str] = (occurrences_map[matched_str] || 0) + 1;\n }\n\n function get_heading(idx: number, txt: string): string {\n const txtBefore = txt.substring(0, idx);\n const lines = txtBefore.split(\"\\n\");\n const path: string[] = [];\n let current_level = 999;\n\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i];\n const m = line.match(/^(#{1,6})\\s+(.*)/);\n if (m) {\n const level = m[1].length;\n if (level < current_level) {\n let cleanHeading = m[2]\n .replace(/\\*\\*|__|[*_]/g, \"\")\n .replace(/\\{#[^}]+\\}/g, \"\")\n .trim();\n if (cleanHeading.length > 80) {\n cleanHeading = cleanHeading.substring(0, 80) + \"...\";\n }\n path.unshift(cleanHeading);\n current_level = level;\n if (level === 1) break;\n }\n }\n }\n return path.join(\" > \");\n }\n\n let i = 1;\n for (const m of matches) {\n const matched_str = m[0];\n const m_start = m.index!;\n const m_end = m_start + matched_str.length;\n const p_num = pageOfOffset(m_start);\n\n const snippet_start = Math.max(0, m_start - 100);\n const snippet_end = Math.min(body.length, m_end + 100);\n const snippet =\n body.substring(snippet_start, m_start) +\n `**${matched_str}**` +\n body.substring(m_end, snippet_end);\n\n const snippet_lines = snippet\n .split(\"\\n\")\n .filter((line) => line.trim().length > 0)\n .map((line) => `> ${line}`)\n .join(\"\\n\");\n\n ui_parts.push(\"---\");\n ui_parts.push(`### Match ${i} (p${p_num})`);\n\n const h_path = get_heading(m_start, body);\n if (h_path) {\n ui_parts.push(`**Path:** \\`${h_path}\\``);\n }\n\n const count = occurrences_map[matched_str];\n ui_parts.push(snippet_lines);\n ui_parts.push(\n `*Occurrences:* This exact phrasing appears ${count} time${count !== 1 ? \"s\" : \"\"} in the document.`,\n );\n\n i++;\n }\n\n if (regexDowngradedNote) ui_parts.unshift(regexDowngradedNote);\n const ui_markdown = ui_parts.join(\"\\n\\n\");\n const llm_content = `> **File Path:** \\`${resolve(file_path)}\\`\\n\\n${ui_markdown}`;\n\n return {\n content: [{ type: \"text\", text: llm_content }],\n structuredContent: {\n markdown: ui_markdown,\n title: `Search: ${basename(file_path)}`,\n file_path: resolve(file_path),\n },\n };\n}\n","// FILE: node/packages/mcp-server/src/shared.ts\nexport const MARKDOWN_UI_URI = \"ui://adeu/markdown-ui\";\n"],"mappings":"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","names":["basename","resolve","DocumentObject","ui_markdown","llm_content","i","basename","DocumentObject","res","resolve","gitSha"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adeu/mcp-server",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "",
5
5
  "mcpName": "ai.adeu/adeu",
6
6
  "main": "./dist/index.js",
@@ -31,7 +31,7 @@
31
31
  "license": "MIT",
32
32
  "type": "module",
33
33
  "dependencies": {
34
- "@adeu/core": "^1.21.0",
34
+ "@adeu/core": "^1.22.0",
35
35
  "@modelcontextprotocol/sdk": "^1.0.0",
36
36
  "@modelcontextprotocol/ext-apps": "^1.7.4",
37
37
  "zod": "4.4.3"
package/src/index.ts CHANGED
@@ -19,9 +19,11 @@ import {
19
19
  create_word_patch_diff,
20
20
  finalize_document,
21
21
  } from "@adeu/core";
22
+ import { describe_illegal_control_chars } from "@adeu/core";
22
23
 
23
24
  import {
24
25
  build_paginated_response,
26
+ build_full_document_response,
25
27
  build_outline_response,
26
28
  build_appendix_response,
27
29
  build_search_response,
@@ -332,6 +334,18 @@ registerAppTool(
332
334
  );
333
335
  return res as any;
334
336
  }
337
+ // In full mode, page='all' returns the entire document without page
338
+ // chrome — the round-trip artifact for text-based apply/diff
339
+ // (QA 2026-07-17 F1 parity with the Python CLI's --page all).
340
+ if (
341
+ mode === "full" &&
342
+ page !== undefined &&
343
+ page !== null &&
344
+ String(page).trim().toLowerCase() === "all"
345
+ ) {
346
+ const res = build_full_document_response(text, file_path);
347
+ return res as any;
348
+ }
335
349
  // In non-search mode, `page` defaults to 1 (show document page 1).
336
350
  // Non-numeric values must error, not silently fall back to page 1
337
351
  // (QA L1 parity with the Python CLI).
@@ -347,7 +361,7 @@ registerAppTool(
347
361
  type: "text",
348
362
  text:
349
363
  `Invalid page value: '${page}'. Provide a positive integer ` +
350
- `(pages are 1-indexed; 'all' is only valid together with search_query).`,
364
+ `(pages are 1-indexed; 'all' is valid for mode='full' and together with search_query).`,
351
365
  },
352
366
  ],
353
367
  };
@@ -489,6 +503,18 @@ server.registerTool(
489
503
  { type: "text", text: "Error: author_name cannot be empty." },
490
504
  ],
491
505
  };
506
+ const author_ctrl = describe_illegal_control_chars(author_name);
507
+ if (author_ctrl)
508
+ return {
509
+ content: [
510
+ {
511
+ type: "text",
512
+ text:
513
+ `Error: author_name contains control character(s) (${author_ctrl}) ` +
514
+ `that cannot be stored in a DOCX. Remove them and retry.`,
515
+ },
516
+ ],
517
+ };
492
518
  if (!changes || changes.length === 0)
493
519
  return {
494
520
  content: [{ type: "text", text: "Error: No changes provided." }],
@@ -5,6 +5,8 @@ import {
5
5
  split_structural_appendix,
6
6
  extract_outline,
7
7
  OutlineNode,
8
+ RegexTimeoutError,
9
+ userFindAllMatches,
8
10
  } from "@adeu/core";
9
11
 
10
12
  export interface ToolResult {
@@ -64,6 +66,26 @@ export function render_outline_tree(
64
66
  return lines.join("\n");
65
67
  }
66
68
 
69
+ export function build_full_document_response(
70
+ text: string,
71
+ file_path: string,
72
+ ): ToolResult {
73
+ // The ENTIRE document body with no page banner, continuation footer, or
74
+ // appendix pointer — the round-trip artifact for text-based apply/diff
75
+ // (QA 2026-07-17 F1; mirrors Python's build_full_document_response).
76
+ const [body] = split_structural_appendix(text);
77
+ const ui_markdown = body;
78
+ const llm_content = `> **File Path:** \`${resolve(file_path)}\`\n\n${ui_markdown}`;
79
+ return {
80
+ content: [{ type: "text", text: llm_content }],
81
+ structuredContent: {
82
+ markdown: ui_markdown,
83
+ file_path: resolve(file_path),
84
+ title: basename(file_path),
85
+ },
86
+ };
87
+ }
88
+
67
89
  export function build_paginated_response(
68
90
  text: string,
69
91
  page: number,
@@ -112,7 +134,7 @@ export function build_outline_response(
112
134
  outline_verbose: boolean = false,
113
135
  paragraph_offsets: Map<any, [number, number]> | null = null,
114
136
  ): ToolResult {
115
- // Levels outside 1-6 are meaningless (0/negative used to render a
137
+ // Levels outside 1-6 are meaningless (0/negative would render a
116
138
  // nonsensical "L1-L0" range label, QA L2). Clamp to the nearest sensible
117
139
  // depth, mirroring the Python builder.
118
140
  outline_max_level = Math.max(1, Math.min(outline_max_level, 6));
@@ -234,9 +256,11 @@ export function build_search_response(
234
256
  // the same broken regex.
235
257
  let regexDowngradedNote = "";
236
258
  let regex: RegExp;
259
+ let isUserRegex = false;
237
260
  if (search_regex) {
238
261
  try {
239
262
  regex = new RegExp(search_query, flags);
263
+ isUserRegex = true;
240
264
  } catch (e: any) {
241
265
  regexDowngradedNote =
242
266
  `> **Note:** \`${search_query}\` is not a valid regular expression ` +
@@ -248,7 +272,26 @@ export function build_search_response(
248
272
  regex = new RegExp(escapeRegExp(search_query), flags);
249
273
  }
250
274
 
251
- const allMatches = Array.from(body.matchAll(regex));
275
+ // Patterns that blow the matching time budget (catastrophic backtracking,
276
+ // QA 2026-07-17 F5) get the same literal downgrade as invalid patterns —
277
+ // for a read-only search, degraded results beat a hung event loop.
278
+ let allMatches: Array<{ 0: string; index?: number }>;
279
+ if (isUserRegex) {
280
+ try {
281
+ allMatches = userFindAllMatches(search_query, body, flags).map((m) => ({
282
+ 0: body.slice(m.start, m.end),
283
+ index: m.start,
284
+ }));
285
+ } catch (e: any) {
286
+ if (!(e instanceof RegexTimeoutError)) throw e;
287
+ regexDowngradedNote =
288
+ `> **Note:** \`${search_query}\` was searched as literal text instead of as ` +
289
+ `a regular expression: ${e.message}`;
290
+ allMatches = Array.from(body.matchAll(new RegExp(escapeRegExp(search_query), flags)));
291
+ }
292
+ } else {
293
+ allMatches = Array.from(body.matchAll(regex));
294
+ }
252
295
 
253
296
  // Compute document pagination once — needed for both annotation and filtering.
254
297
  const pag_res = paginate(body, "");