@adeu/mcp-server 1.26.0 → 1.27.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 +54 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +7 -1
- package/src/repro.qa_report_v8.test.ts +134 -0
- package/src/response-builders.ts +43 -4
- package/src/shared.ts +33 -0
package/dist/index.js
CHANGED
|
@@ -34,6 +34,26 @@ import {
|
|
|
34
34
|
RegexTimeoutError,
|
|
35
35
|
userFindAllMatches
|
|
36
36
|
} from "@adeu/core";
|
|
37
|
+
var STYLE_MARKER_RE = /\*\*|(?<![\w])_(?=\S)|(?<=\S)_(?![\w])/g;
|
|
38
|
+
function emphasizedSnippet(prefix, match, suffix) {
|
|
39
|
+
const region = prefix + match + suffix;
|
|
40
|
+
const b1 = prefix.length;
|
|
41
|
+
const b2 = prefix.length + match.length;
|
|
42
|
+
const keep = new Array(region.length).fill(true);
|
|
43
|
+
for (const m of region.matchAll(STYLE_MARKER_RE)) {
|
|
44
|
+
for (let i = m.index; i < m.index + m[0].length; i++) keep[i] = false;
|
|
45
|
+
}
|
|
46
|
+
let strippedPrefix = "";
|
|
47
|
+
let strippedMatch = "";
|
|
48
|
+
let strippedSuffix = "";
|
|
49
|
+
for (let i = 0; i < region.length; i++) {
|
|
50
|
+
if (!keep[i]) continue;
|
|
51
|
+
if (i < b1) strippedPrefix += region[i];
|
|
52
|
+
else if (i < b2) strippedMatch += region[i];
|
|
53
|
+
else strippedSuffix += region[i];
|
|
54
|
+
}
|
|
55
|
+
return `${strippedPrefix}**${strippedMatch}**${strippedSuffix}`;
|
|
56
|
+
}
|
|
37
57
|
function _build_appendix_pointer(has_appendix) {
|
|
38
58
|
if (!has_appendix) return "";
|
|
39
59
|
return `
|
|
@@ -383,7 +403,11 @@ ${body_msg}`;
|
|
|
383
403
|
const p_num = pageOfOffset(m_start);
|
|
384
404
|
const snippet_start = Math.max(0, m_start - 100);
|
|
385
405
|
const snippet_end = Math.min(body.length, m_end + 100);
|
|
386
|
-
const snippet =
|
|
406
|
+
const snippet = emphasizedSnippet(
|
|
407
|
+
body.substring(snippet_start, m_start),
|
|
408
|
+
matched_str,
|
|
409
|
+
body.substring(m_end, snippet_end)
|
|
410
|
+
);
|
|
387
411
|
const snippet_lines = snippet.split("\n").filter((line) => line.trim().length > 0).map((line) => `> ${line}`).join("\n");
|
|
388
412
|
ui_parts.push("---");
|
|
389
413
|
ui_parts.push(`### Match ${i} (p${p_num})`);
|
|
@@ -415,6 +439,26 @@ ${ui_markdown}`;
|
|
|
415
439
|
|
|
416
440
|
// src/shared.ts
|
|
417
441
|
var MARKDOWN_UI_URI = "ui://adeu/markdown-ui";
|
|
442
|
+
function handleServerCliArgs(argv, packageVersion2) {
|
|
443
|
+
if (argv.includes("--version") || argv.includes("-v")) {
|
|
444
|
+
return `adeu-mcp-server ${packageVersion2}`;
|
|
445
|
+
}
|
|
446
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
447
|
+
return [
|
|
448
|
+
"Usage: adeu-mcp-server [options]",
|
|
449
|
+
"",
|
|
450
|
+
"Adeu MCP server (stdio transport, zero-dependency Node engine).",
|
|
451
|
+
"Started by MCP hosts such as Claude Desktop; it reads JSON-RPC on stdin.",
|
|
452
|
+
"",
|
|
453
|
+
"Options:",
|
|
454
|
+
" -h, --help Show this help and exit",
|
|
455
|
+
" -v, --version Print the server version and exit",
|
|
456
|
+
"",
|
|
457
|
+
"Docs: https://github.com/dealfluence/adeu"
|
|
458
|
+
].join("\n");
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
418
462
|
|
|
419
463
|
// src/index.ts
|
|
420
464
|
var MATCH_MODE_SYNONYMS = {
|
|
@@ -477,8 +521,8 @@ var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use pa
|
|
|
477
521
|
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";
|
|
478
522
|
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.";
|
|
479
523
|
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.";
|
|
480
|
-
var gitSha = "
|
|
481
|
-
var packageVersion = "1.
|
|
524
|
+
var gitSha = "7a0a821";
|
|
525
|
+
var packageVersion = "1.27.0";
|
|
482
526
|
var buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;
|
|
483
527
|
var server = new McpServer({
|
|
484
528
|
name: "adeu-redlining-service",
|
|
@@ -1062,10 +1106,15 @@ ${stats.skipped_details.join("\n")}`;
|
|
|
1062
1106
|
return res.trim();
|
|
1063
1107
|
}
|
|
1064
1108
|
async function main() {
|
|
1109
|
+
const cliOutput = handleServerCliArgs(process.argv.slice(2), packageVersion);
|
|
1110
|
+
if (cliOutput !== null) {
|
|
1111
|
+
process.stdout.write(cliOutput + "\n");
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1065
1114
|
const transport = new StdioServerTransport();
|
|
1066
1115
|
await server.connect(transport);
|
|
1067
|
-
const gitSha2 = "
|
|
1068
|
-
const buildTs = "2026-07-
|
|
1116
|
+
const gitSha2 = "7a0a821";
|
|
1117
|
+
const buildTs = "2026-07-19T14:47:17.873Z";
|
|
1069
1118
|
console.error(
|
|
1070
1119
|
`Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha2}@${buildTs}`
|
|
1071
1120
|
);
|
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 collect_media_difference_warnings,\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 // includeAppendix=false: the generated appendix (\"used N times\",\n // diagnostics) is not document content — diffing it produces phantom\n // changes no apply can consume (QA 2026-07-18 H1).\n const origText = await extractTextFromBuffer(origBuf, compare_clean, false);\n const modText = await extractTextFromBuffer(modBuf, compare_clean, false);\n\n const diff = create_word_patch_diff(\n origText,\n modText,\n basename(original_path),\n basename(modified_path),\n );\n\n // A text diff cannot see image bytes: when embedded media differ, an\n // empty diff must never read as \"the documents are identical\"\n // (QA 2026-07-19 F-04).\n const media_warnings = collect_media_difference_warnings(\n new Uint8Array(origBuf),\n new Uint8Array(modBuf),\n );\n const warning_text = media_warnings.length\n ? media_warnings.map((w) => `⚠️ ${w}`).join(\"\\n\") + \"\\n\\n\"\n : \"\";\n\n return {\n content: [\n {\n type: \"text\",\n text: warning_text + (diff || \"No differences found.\"),\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\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"]}
|
|
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 collect_media_difference_warnings,\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, handleServerCliArgs } 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 // includeAppendix=false: the generated appendix (\"used N times\",\n // diagnostics) is not document content — diffing it produces phantom\n // changes no apply can consume (QA 2026-07-18 H1).\n const origText = await extractTextFromBuffer(origBuf, compare_clean, false);\n const modText = await extractTextFromBuffer(modBuf, compare_clean, false);\n\n const diff = create_word_patch_diff(\n origText,\n modText,\n basename(original_path),\n basename(modified_path),\n );\n\n // A text diff cannot see image bytes: when embedded media differ, an\n // empty diff must never read as \"the documents are identical\"\n // (QA 2026-07-19 F-04).\n const media_warnings = collect_media_difference_warnings(\n new Uint8Array(origBuf),\n new Uint8Array(modBuf),\n );\n const warning_text = media_warnings.length\n ? media_warnings.map((w) => `⚠️ ${w}`).join(\"\\n\") + \"\\n\\n\"\n : \"\";\n\n return {\n content: [\n {\n type: \"text\",\n text: warning_text + (diff || \"No differences found.\"),\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\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 cliOutput = handleServerCliArgs(process.argv.slice(2), packageVersion);\n if (cliOutput !== null) {\n // stdout is safe here: the stdio transport was never started.\n process.stdout.write(cliOutput + \"\\n\");\n return;\n }\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\n// Projection style markers: `**bold**` always; `_italic_` only where the\n// underscore is not intra-word (identifiers like snake_case are literal text —\n// the projection's italics markers always hug non-whitespace at a word edge).\nconst STYLE_MARKER_RE = /\\*\\*|(?<![\\w])_(?=\\S)|(?<=\\S)_(?![\\w])/g;\n\n/**\n * Renders `prefix **match** suffix` with the document's own bold/italic\n * projection markers stripped first, so the highlight cannot collide with\n * markers already present — a regex match crossing styled runs used to\n * render as `**The **Supplier** _shall provide**_` (QA 2026-07-19 v8 F-10).\n * Markers are detected over the WHOLE region (a match boundary can cut a\n * marker away from its word-edge context), then each part is rebuilt from\n * the surviving characters. Mirrors Python's _emphasized_snippet.\n */\nexport function emphasizedSnippet(\n prefix: string,\n match: string,\n suffix: string,\n): string {\n const region = prefix + match + suffix;\n const b1 = prefix.length;\n const b2 = prefix.length + match.length;\n const keep = new Array<boolean>(region.length).fill(true);\n for (const m of region.matchAll(STYLE_MARKER_RE)) {\n for (let i = m.index!; i < m.index! + m[0].length; i++) keep[i] = false;\n }\n let strippedPrefix = \"\";\n let strippedMatch = \"\";\n let strippedSuffix = \"\";\n for (let i = 0; i < region.length; i++) {\n if (!keep[i]) continue;\n if (i < b1) strippedPrefix += region[i];\n else if (i < b2) strippedMatch += region[i];\n else strippedSuffix += region[i];\n }\n return `${strippedPrefix}**${strippedMatch}**${strippedSuffix}`;\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 = emphasizedSnippet(\n body.substring(snippet_start, m_start),\n matched_str,\n body.substring(m_end, snippet_end),\n );\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\n/**\n * Minimal CLI handling BEFORE the stdio server starts: `--help` and\n * `--version` must print and exit like every other executable instead of\n * silently starting the transport (QA 2026-07-19 v8 F-06). Returns the text\n * to print (caller exits without serving), or null to proceed with server\n * startup. Unknown arguments are tolerated — MCP hosts append their own\n * flags. Lives here (not index.ts) so tests can import it without booting\n * the server.\n */\nexport function handleServerCliArgs(\n argv: string[],\n packageVersion: string,\n): string | null {\n if (argv.includes(\"--version\") || argv.includes(\"-v\")) {\n return `adeu-mcp-server ${packageVersion}`;\n }\n if (argv.includes(\"--help\") || argv.includes(\"-h\")) {\n return [\n \"Usage: adeu-mcp-server [options]\",\n \"\",\n \"Adeu MCP server (stdio transport, zero-dependency Node engine).\",\n \"Started by MCP hosts such as Claude Desktop; it reads JSON-RPC on stdin.\",\n \"\",\n \"Options:\",\n \" -h, --help Show this help and exit\",\n \" -v, --version Print the server version and exit\",\n \"\",\n \"Docs: https://github.com/dealfluence/adeu\",\n ].join(\"\\n\");\n }\n return null;\n}\n"],"mappings":"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","names":["basename","resolve","DocumentObject","ui_markdown","llm_content","i","packageVersion","basename","DocumentObject","res","resolve","gitSha"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adeu/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.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.
|
|
34
|
+
"@adeu/core": "^1.27.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
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
build_search_response,
|
|
31
31
|
} from "./response-builders.js";
|
|
32
32
|
|
|
33
|
-
import { MARKDOWN_UI_URI } from "./shared.js";
|
|
33
|
+
import { MARKDOWN_UI_URI, handleServerCliArgs } from "./shared.js";
|
|
34
34
|
// Parity with Python models.py `_infer_type_in_place` + `_coerce_match_mode_in_place`.
|
|
35
35
|
// The MCP boundary schema is permissive; these repairs let recoverable payloads
|
|
36
36
|
// (a missing `type` that's unambiguous from the key signature, or a non-canonical
|
|
@@ -948,6 +948,12 @@ export function formatBatchResult(
|
|
|
948
948
|
|
|
949
949
|
// --- Startup ---
|
|
950
950
|
async function main() {
|
|
951
|
+
const cliOutput = handleServerCliArgs(process.argv.slice(2), packageVersion);
|
|
952
|
+
if (cliOutput !== null) {
|
|
953
|
+
// stdout is safe here: the stdio transport was never started.
|
|
954
|
+
process.stdout.write(cliOutput + "\n");
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
951
957
|
const transport = new StdioServerTransport();
|
|
952
958
|
await server.connect(transport);
|
|
953
959
|
const gitSha = process.env.GIT_SHA || "unknown";
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// FILE: node/packages/mcp-server/src/repro.qa_report_v8.test.ts
|
|
2
|
+
/**
|
|
3
|
+
* MCP-server repro tests for the 2026-07-19 black-box QA and UX report on
|
|
4
|
+
* 1.26.0 (adeu 1.26.0+0741eaf). Mirrors python/tests/test_repro_qa_report_v8.py
|
|
5
|
+
* for the findings that live on the server surface:
|
|
6
|
+
*
|
|
7
|
+
* F-06 `--help` / `--version` used to start the stdio server instead of
|
|
8
|
+
* printing and exiting — both the Python `adeu-server` and this
|
|
9
|
+
* Node bin share the defect
|
|
10
|
+
* F-10 search snippet highlighting collides with the document's own
|
|
11
|
+
* style markers (`**The **Supplier** _shall provide**_`)
|
|
12
|
+
*
|
|
13
|
+
* Every test fails against the commit preceding its fix.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect } from "vitest";
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
18
|
+
import { existsSync } from "node:fs";
|
|
19
|
+
import { resolve } from "node:path";
|
|
20
|
+
import { build_search_response, emphasizedSnippet } from "./response-builders.js";
|
|
21
|
+
import { handleServerCliArgs } from "./shared.js";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// F-06: --help/--version print and exit, never start the server
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
describe("QA v8 F-06: server --help/--version", () => {
|
|
28
|
+
it("returns version text without serving", () => {
|
|
29
|
+
expect(handleServerCliArgs(["--version"], "9.9.9")).toBe(
|
|
30
|
+
"adeu-mcp-server 9.9.9",
|
|
31
|
+
);
|
|
32
|
+
expect(handleServerCliArgs(["-v"], "9.9.9")).toContain("9.9.9");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("returns help text without serving", () => {
|
|
36
|
+
const help = handleServerCliArgs(["--help"], "9.9.9");
|
|
37
|
+
expect(help).toBeTruthy();
|
|
38
|
+
expect(help!.toLowerCase()).toContain("usage");
|
|
39
|
+
expect(help).toContain("--version");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns null for a normal server start", () => {
|
|
43
|
+
expect(handleServerCliArgs([], "9.9.9")).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("built bin exits immediately on --version instead of serving", async () => {
|
|
47
|
+
const serverPath = resolve(__dirname, "../dist/index.js");
|
|
48
|
+
if (!existsSync(serverPath)) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"MCP server not built. Run 'npm run build' before tests.",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = await new Promise<{ code: number | null; out: string }>(
|
|
55
|
+
(resolvePromise, rejectPromise) => {
|
|
56
|
+
const proc = spawn("node", [serverPath, "--version"], {
|
|
57
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
58
|
+
});
|
|
59
|
+
let out = "";
|
|
60
|
+
proc.stdout.on("data", (d) => (out += d.toString()));
|
|
61
|
+
const timer = setTimeout(() => {
|
|
62
|
+
proc.kill();
|
|
63
|
+
rejectPromise(
|
|
64
|
+
new Error("--version started the stdio server instead of exiting"),
|
|
65
|
+
);
|
|
66
|
+
}, 15_000);
|
|
67
|
+
proc.on("exit", (code) => {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
resolvePromise({ code, out });
|
|
70
|
+
});
|
|
71
|
+
proc.on("error", (err) => {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
rejectPromise(err);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(result.code).toBe(0);
|
|
79
|
+
expect(result.out).toContain("adeu-mcp-server");
|
|
80
|
+
}, 30_000);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// F-10: search highlighting must not collide with existing style markers
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
describe("QA v8 F-10: search snippet marker collisions", () => {
|
|
88
|
+
// The QA report's fixture projection: bold + italic runs around the match.
|
|
89
|
+
const BODY =
|
|
90
|
+
"# Master Services Agreement\n\n" +
|
|
91
|
+
"**The Supplier** _shall provide_ the Services with reasonable skill and care.\n\n" +
|
|
92
|
+
"Filler paragraph so the snippet has surrounding context.";
|
|
93
|
+
|
|
94
|
+
it("strips existing markers before emphasizing the match", () => {
|
|
95
|
+
const res = build_search_response(
|
|
96
|
+
BODY,
|
|
97
|
+
"Supplier.*provide",
|
|
98
|
+
true,
|
|
99
|
+
true,
|
|
100
|
+
undefined,
|
|
101
|
+
"doc.docx",
|
|
102
|
+
);
|
|
103
|
+
const md = res.structuredContent!.markdown as string;
|
|
104
|
+
|
|
105
|
+
expect(md).not.toContain("**The **");
|
|
106
|
+
expect(md).not.toContain("**_");
|
|
107
|
+
expect(md).not.toContain("_**");
|
|
108
|
+
expect(md).toContain("**Supplier shall provide**");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("keeps plain-document snippets unchanged", () => {
|
|
112
|
+
const res = build_search_response(
|
|
113
|
+
"The quick brown fox jumps over the lazy dog.",
|
|
114
|
+
"brown fox",
|
|
115
|
+
false,
|
|
116
|
+
true,
|
|
117
|
+
undefined,
|
|
118
|
+
"doc.docx",
|
|
119
|
+
);
|
|
120
|
+
const md = res.structuredContent!.markdown as string;
|
|
121
|
+
expect(md).toContain("**brown fox**");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("emphasizedSnippet handles a marker split across the match boundary", () => {
|
|
125
|
+
// Match ends right before the closing italic marker: the suffix starts
|
|
126
|
+
// with "_" whose word-edge context lives in the match fragment.
|
|
127
|
+
const out = emphasizedSnippet(
|
|
128
|
+
"**The Supplier** _",
|
|
129
|
+
"shall provide",
|
|
130
|
+
"_ the Services.",
|
|
131
|
+
);
|
|
132
|
+
expect(out).toBe("The Supplier **shall provide** the Services.");
|
|
133
|
+
});
|
|
134
|
+
});
|
package/src/response-builders.ts
CHANGED
|
@@ -16,6 +16,44 @@ export interface ToolResult {
|
|
|
16
16
|
[key: string]: unknown;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Projection style markers: `**bold**` always; `_italic_` only where the
|
|
20
|
+
// underscore is not intra-word (identifiers like snake_case are literal text —
|
|
21
|
+
// the projection's italics markers always hug non-whitespace at a word edge).
|
|
22
|
+
const STYLE_MARKER_RE = /\*\*|(?<![\w])_(?=\S)|(?<=\S)_(?![\w])/g;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Renders `prefix **match** suffix` with the document's own bold/italic
|
|
26
|
+
* projection markers stripped first, so the highlight cannot collide with
|
|
27
|
+
* markers already present — a regex match crossing styled runs used to
|
|
28
|
+
* render as `**The **Supplier** _shall provide**_` (QA 2026-07-19 v8 F-10).
|
|
29
|
+
* Markers are detected over the WHOLE region (a match boundary can cut a
|
|
30
|
+
* marker away from its word-edge context), then each part is rebuilt from
|
|
31
|
+
* the surviving characters. Mirrors Python's _emphasized_snippet.
|
|
32
|
+
*/
|
|
33
|
+
export function emphasizedSnippet(
|
|
34
|
+
prefix: string,
|
|
35
|
+
match: string,
|
|
36
|
+
suffix: string,
|
|
37
|
+
): string {
|
|
38
|
+
const region = prefix + match + suffix;
|
|
39
|
+
const b1 = prefix.length;
|
|
40
|
+
const b2 = prefix.length + match.length;
|
|
41
|
+
const keep = new Array<boolean>(region.length).fill(true);
|
|
42
|
+
for (const m of region.matchAll(STYLE_MARKER_RE)) {
|
|
43
|
+
for (let i = m.index!; i < m.index! + m[0].length; i++) keep[i] = false;
|
|
44
|
+
}
|
|
45
|
+
let strippedPrefix = "";
|
|
46
|
+
let strippedMatch = "";
|
|
47
|
+
let strippedSuffix = "";
|
|
48
|
+
for (let i = 0; i < region.length; i++) {
|
|
49
|
+
if (!keep[i]) continue;
|
|
50
|
+
if (i < b1) strippedPrefix += region[i];
|
|
51
|
+
else if (i < b2) strippedMatch += region[i];
|
|
52
|
+
else strippedSuffix += region[i];
|
|
53
|
+
}
|
|
54
|
+
return `${strippedPrefix}**${strippedMatch}**${strippedSuffix}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
19
57
|
function _build_appendix_pointer(has_appendix: boolean): string {
|
|
20
58
|
if (!has_appendix) return "";
|
|
21
59
|
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.`;
|
|
@@ -450,10 +488,11 @@ export function build_search_response(
|
|
|
450
488
|
|
|
451
489
|
const snippet_start = Math.max(0, m_start - 100);
|
|
452
490
|
const snippet_end = Math.min(body.length, m_end + 100);
|
|
453
|
-
const snippet =
|
|
454
|
-
body.substring(snippet_start, m_start)
|
|
455
|
-
|
|
456
|
-
body.substring(m_end, snippet_end)
|
|
491
|
+
const snippet = emphasizedSnippet(
|
|
492
|
+
body.substring(snippet_start, m_start),
|
|
493
|
+
matched_str,
|
|
494
|
+
body.substring(m_end, snippet_end),
|
|
495
|
+
);
|
|
457
496
|
|
|
458
497
|
const snippet_lines = snippet
|
|
459
498
|
.split("\n")
|
package/src/shared.ts
CHANGED
|
@@ -1,2 +1,35 @@
|
|
|
1
1
|
// FILE: node/packages/mcp-server/src/shared.ts
|
|
2
2
|
export const MARKDOWN_UI_URI = "ui://adeu/markdown-ui";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Minimal CLI handling BEFORE the stdio server starts: `--help` and
|
|
6
|
+
* `--version` must print and exit like every other executable instead of
|
|
7
|
+
* silently starting the transport (QA 2026-07-19 v8 F-06). Returns the text
|
|
8
|
+
* to print (caller exits without serving), or null to proceed with server
|
|
9
|
+
* startup. Unknown arguments are tolerated — MCP hosts append their own
|
|
10
|
+
* flags. Lives here (not index.ts) so tests can import it without booting
|
|
11
|
+
* the server.
|
|
12
|
+
*/
|
|
13
|
+
export function handleServerCliArgs(
|
|
14
|
+
argv: string[],
|
|
15
|
+
packageVersion: string,
|
|
16
|
+
): string | null {
|
|
17
|
+
if (argv.includes("--version") || argv.includes("-v")) {
|
|
18
|
+
return `adeu-mcp-server ${packageVersion}`;
|
|
19
|
+
}
|
|
20
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
21
|
+
return [
|
|
22
|
+
"Usage: adeu-mcp-server [options]",
|
|
23
|
+
"",
|
|
24
|
+
"Adeu MCP server (stdio transport, zero-dependency Node engine).",
|
|
25
|
+
"Started by MCP hosts such as Claude Desktop; it reads JSON-RPC on stdin.",
|
|
26
|
+
"",
|
|
27
|
+
"Options:",
|
|
28
|
+
" -h, --help Show this help and exit",
|
|
29
|
+
" -v, --version Print the server version and exit",
|
|
30
|
+
"",
|
|
31
|
+
"Docs: https://github.com/dealfluence/adeu",
|
|
32
|
+
].join("\n");
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|