@adeu/mcp-server 1.6.8 → 1.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,10 @@
3
3
  // src/index.ts
4
4
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema
9
+ } from "@modelcontextprotocol/sdk/types.js";
7
10
  import { readFileSync } from "fs";
8
11
  import { basename as basename2, resolve as resolve2, extname, dirname } from "path";
9
12
  import {
@@ -12,7 +15,7 @@ import {
12
15
  DocumentObject as DocumentObject2,
13
16
  RedlineEngine,
14
17
  BatchValidationError,
15
- create_unified_diff,
18
+ create_word_patch_diff,
16
19
  finalize_document
17
20
  } from "@adeu/core";
18
21
 
@@ -158,6 +161,16 @@ ${ui_markdown}`;
158
161
  }
159
162
 
160
163
  // src/index.ts
164
+ function readFileBytesOrThrow(filePath) {
165
+ try {
166
+ return readFileSync(filePath);
167
+ } catch (err) {
168
+ if (err.code === "ENOENT") {
169
+ throw new Error(`File not found: ${filePath}`);
170
+ }
171
+ throw err;
172
+ }
173
+ }
161
174
  var READ_DOCX_COMMON_DESC = "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";
162
175
  var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use page=N to navigate.\n- 'outline': heading map only \u2014 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.";
163
176
  var PROCESS_BATCH_COMMON_DESC = "Applies a batch of edits and review actions to a DOCX.\n\nAll changes evaluate against the ORIGINAL document state \u2014 do not chain dependent edits within one batch (e.g. rename X to Y, then modify Y). Apply the rename first, then send a second batch.\n\n";
@@ -183,12 +196,36 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
183
196
  inputSchema: {
184
197
  type: "object",
185
198
  properties: {
186
- file_path: { type: "string", description: "Absolute path to the DOCX file." },
187
- clean_view: { type: "boolean", description: "If False (default), returns the 'Raw' text with inline CriticMarkup. If True, returns 'Accepted' text.", default: false },
188
- mode: { type: "string", enum: ["full", "outline", "appendix"], description: "'full' returns body content. 'outline' returns a structural heading map. 'appendix' returns defined terms.", default: "full" },
189
- page: { type: "number", description: "Page number (1-indexed) for mode='full'. Defaults to 1.", default: 1 },
190
- outline_max_level: { type: "number", description: "For mode='outline' only: cap on heading depth.", default: 2 },
191
- outline_verbose: { type: "boolean", description: "For mode='outline' only: includes metadata.", default: false }
199
+ file_path: {
200
+ type: "string",
201
+ description: "Absolute path to the DOCX file."
202
+ },
203
+ clean_view: {
204
+ type: "boolean",
205
+ description: "If False (default), returns the 'Raw' text with inline CriticMarkup. If True, returns 'Accepted' text.",
206
+ default: false
207
+ },
208
+ mode: {
209
+ type: "string",
210
+ enum: ["full", "outline", "appendix"],
211
+ description: "'full' returns body content. 'outline' returns a structural heading map. 'appendix' returns defined terms.",
212
+ default: "full"
213
+ },
214
+ page: {
215
+ type: "number",
216
+ description: "Page number (1-indexed) for mode='full'. Defaults to 1.",
217
+ default: 1
218
+ },
219
+ outline_max_level: {
220
+ type: "number",
221
+ description: "For mode='outline' only: cap on heading depth.",
222
+ default: 2
223
+ },
224
+ outline_verbose: {
225
+ type: "boolean",
226
+ description: "For mode='outline' only: includes metadata.",
227
+ default: false
228
+ }
192
229
  },
193
230
  required: ["file_path"]
194
231
  }
@@ -199,14 +236,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
199
236
  inputSchema: {
200
237
  type: "object",
201
238
  properties: {
202
- original_docx_path: { type: "string", description: "Absolute path to the source file." },
203
- author_name: { type: "string", description: "Name to appear in Track Changes (e.g., 'Reviewer AI')." },
239
+ original_docx_path: {
240
+ type: "string",
241
+ description: "Absolute path to the source file."
242
+ },
243
+ author_name: {
244
+ type: "string",
245
+ description: "Name to appear in Track Changes (e.g., 'Reviewer AI')."
246
+ },
204
247
  changes: {
205
248
  type: "array",
206
249
  description: "List of changes to apply. Each change must specify 'type'.",
207
250
  items: { type: "object" }
208
251
  },
209
- output_path: { type: "string", description: "Optional output path." }
252
+ output_path: {
253
+ type: "string",
254
+ description: "Optional output path."
255
+ }
210
256
  },
211
257
  required: ["original_docx_path", "author_name", "changes"]
212
258
  }
@@ -217,8 +263,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
217
263
  inputSchema: {
218
264
  type: "object",
219
265
  properties: {
220
- docx_path: { type: "string", description: "Absolute path to the DOCX file." },
221
- output_path: { type: "string", description: "Optional output path." }
266
+ docx_path: {
267
+ type: "string",
268
+ description: "Absolute path to the DOCX file."
269
+ },
270
+ output_path: {
271
+ type: "string",
272
+ description: "Optional output path."
273
+ }
222
274
  },
223
275
  required: ["docx_path"]
224
276
  }
@@ -229,8 +281,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
229
281
  inputSchema: {
230
282
  type: "object",
231
283
  properties: {
232
- original_path: { type: "string", description: "Absolute path to the baseline DOCX file." },
233
- modified_path: { type: "string", description: "Absolute path to the modified DOCX file." }
284
+ original_path: {
285
+ type: "string",
286
+ description: "Absolute path to the baseline DOCX file."
287
+ },
288
+ modified_path: {
289
+ type: "string",
290
+ description: "Absolute path to the modified DOCX file."
291
+ },
292
+ compare_clean: {
293
+ type: "boolean",
294
+ description: "If True, compares 'Accepted' state. If False, compares raw text.",
295
+ default: true
296
+ }
234
297
  },
235
298
  required: ["original_path", "modified_path"]
236
299
  }
@@ -241,14 +304,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
241
304
  inputSchema: {
242
305
  type: "object",
243
306
  properties: {
244
- file_path: { type: "string", description: "Absolute path to the DOCX file." },
245
- output_path: { type: "string", description: "Optional output path." },
246
- sanitize_mode: { type: "string", enum: ["full", "keep-markup"], description: "full removes all markup, keep-markup redacts metadata but keeps comments/redlines." },
247
- accept_all: { type: "boolean", description: "If true, auto-accepts all unresolved track changes before finalizing." },
248
- protection_mode: { type: "string", enum: ["read_only", "encrypt"], description: "Native OOXML document locking. encrypt falls back to read_only in this environment." },
249
- password: { type: "string", description: "Ignored in this environment." },
250
- author: { type: "string", description: "Replace all remaining markup authorship with this name." },
251
- export_pdf: { type: "boolean", description: "Ignored in this environment." }
307
+ file_path: {
308
+ type: "string",
309
+ description: "Absolute path to the DOCX file."
310
+ },
311
+ output_path: {
312
+ type: "string",
313
+ description: "Optional output path."
314
+ },
315
+ sanitize_mode: {
316
+ type: "string",
317
+ enum: ["full", "keep-markup"],
318
+ description: "full removes all markup, keep-markup redacts metadata but keeps comments/redlines."
319
+ },
320
+ accept_all: {
321
+ type: "boolean",
322
+ description: "If true, auto-accepts all unresolved track changes before finalizing."
323
+ },
324
+ protection_mode: {
325
+ type: "string",
326
+ enum: ["read_only", "encrypt"],
327
+ description: "Native OOXML document locking. encrypt falls back to read_only in this environment."
328
+ },
329
+ password: {
330
+ type: "string",
331
+ description: "Ignored in this environment."
332
+ },
333
+ author: {
334
+ type: "string",
335
+ description: "Replace all remaining markup authorship with this name."
336
+ },
337
+ export_pdf: {
338
+ type: "boolean",
339
+ description: "Ignored in this environment."
340
+ }
252
341
  },
253
342
  required: ["file_path"]
254
343
  }
@@ -256,142 +345,191 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
256
345
  ]
257
346
  };
258
347
  });
259
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
260
- const { name, arguments: args } = request.params;
261
- try {
262
- if (name === "read_docx") {
263
- const filePath = args?.file_path;
264
- const cleanView = args?.clean_view ?? false;
265
- const mode = args?.mode ?? "full";
266
- const page = args?.page ?? 1;
267
- const outline_max_level = args?.outline_max_level ?? 2;
268
- const outline_verbose = args?.outline_verbose ?? false;
269
- const buf = readFileSync(filePath);
270
- const text = await extractTextFromBuffer(buf, cleanView);
271
- if (mode === "outline") {
272
- const doc = await DocumentObject2.load(buf);
273
- return build_outline_response(doc, text, filePath, outline_max_level, outline_verbose);
274
- }
275
- if (mode === "appendix") {
276
- return build_appendix_response(text, page, filePath);
277
- }
278
- return build_paginated_response(text, page, filePath);
279
- }
280
- if (name === "process_document_batch") {
281
- const origPath = args?.original_docx_path;
282
- const authorName = args?.author_name;
283
- const changes = args?.changes;
284
- let outPath = args?.output_path;
285
- if (!outPath) {
286
- const ext = extname(origPath);
287
- const base = basename2(origPath, ext);
288
- const dir = dirname(origPath);
289
- outPath = resolve2(dir, `${base}_processed${ext}`);
348
+ server.setRequestHandler(
349
+ CallToolRequestSchema,
350
+ async (request) => {
351
+ const { name, arguments: args } = request.params;
352
+ try {
353
+ if (name === "read_docx") {
354
+ const filePath = args?.file_path;
355
+ const cleanView = args?.clean_view ?? false;
356
+ const mode = args?.mode ?? "full";
357
+ const page = args?.page ?? 1;
358
+ const outline_max_level = args?.outline_max_level ?? 2;
359
+ const outline_verbose = args?.outline_verbose ?? false;
360
+ const buf = readFileBytesOrThrow(filePath);
361
+ const text = await extractTextFromBuffer(buf, cleanView);
362
+ if (mode === "outline") {
363
+ const doc = await DocumentObject2.load(buf);
364
+ return build_outline_response(
365
+ doc,
366
+ text,
367
+ filePath,
368
+ outline_max_level,
369
+ outline_verbose
370
+ );
371
+ }
372
+ if (mode === "appendix") {
373
+ return build_appendix_response(text, page, filePath);
374
+ }
375
+ return build_paginated_response(text, page, filePath);
290
376
  }
291
- const buf = readFileSync(origPath);
292
- const doc = await DocumentObject2.load(buf);
293
- const engine = new RedlineEngine(doc, authorName);
294
- let stats;
295
- try {
296
- stats = engine.process_batch(changes);
297
- } catch (e) {
298
- if (e instanceof BatchValidationError) {
377
+ if (name === "process_document_batch") {
378
+ const origPath = args?.original_docx_path;
379
+ const authorName = args?.author_name;
380
+ const changes = args?.changes;
381
+ let outPath = args?.output_path;
382
+ if (!authorName || !authorName.trim()) {
299
383
  return {
300
- content: [{ type: "text", text: `Batch rejected. Some edits failed validation:
301
-
302
- ${e.errors.join("\n\n")}` }],
303
- isError: true
384
+ content: [
385
+ { type: "text", text: "Error: author_name cannot be empty." }
386
+ ]
304
387
  };
305
388
  }
306
- throw e;
307
- }
308
- const outBuf = await doc.save();
309
- const fs = await import("fs");
310
- fs.writeFileSync(outPath, outBuf);
311
- let res = `Batch complete. Saved to: ${outPath}
389
+ if (!changes || changes.length === 0) {
390
+ return {
391
+ content: [{ type: "text", text: "Error: No changes provided." }]
392
+ };
393
+ }
394
+ if (!outPath) {
395
+ const ext = extname(origPath);
396
+ const base = basename2(origPath, ext);
397
+ const dir = dirname(origPath);
398
+ outPath = resolve2(dir, `${base}_processed${ext}`);
399
+ }
400
+ const buf = readFileBytesOrThrow(origPath);
401
+ const doc = await DocumentObject2.load(buf);
402
+ const engine = new RedlineEngine(doc, authorName);
403
+ let stats;
404
+ try {
405
+ stats = engine.process_batch(changes);
406
+ } catch (e) {
407
+ if (e instanceof BatchValidationError) {
408
+ return {
409
+ content: [
410
+ {
411
+ type: "text",
412
+ text: `Batch rejected. Some edits failed validation:
413
+
414
+ ${e.errors.join("\n\n")}`
415
+ }
416
+ ],
417
+ isError: true
418
+ };
419
+ }
420
+ throw e;
421
+ }
422
+ const outBuf = await doc.save();
423
+ const fs = await import("fs");
424
+ fs.writeFileSync(outPath, outBuf);
425
+ let res = `Batch complete. Saved to: ${outPath}
312
426
  Actions: ${stats.actions_applied} applied, ${stats.actions_skipped} skipped.
313
427
  Edits: ${stats.edits_applied} applied, ${stats.edits_skipped} skipped.`;
314
- if (stats.skipped_details?.length > 0) {
315
- res += `
428
+ if (stats.skipped_details?.length > 0) {
429
+ res += `
316
430
 
317
431
  Skipped Details:
318
432
  ${stats.skipped_details.join("\n")}`;
433
+ }
434
+ return {
435
+ content: [{ type: "text", text: res }]
436
+ };
319
437
  }
320
- return {
321
- content: [{ type: "text", text: res }]
322
- };
323
- }
324
- if (name === "accept_all_changes") {
325
- const docxPath = args?.docx_path;
326
- let outPath = args?.output_path;
327
- if (!outPath) {
328
- const ext = extname(docxPath);
329
- const base = basename2(docxPath, ext);
330
- const dir = dirname(docxPath);
331
- outPath = resolve2(dir, `${base}_clean${ext}`);
438
+ if (name === "accept_all_changes") {
439
+ const docxPath = args?.docx_path;
440
+ let outPath = args?.output_path;
441
+ if (!outPath) {
442
+ const ext = extname(docxPath);
443
+ const base = basename2(docxPath, ext);
444
+ const dir = dirname(docxPath);
445
+ outPath = resolve2(dir, `${base}_clean${ext}`);
446
+ }
447
+ const buf = readFileBytesOrThrow(docxPath);
448
+ const doc = await DocumentObject2.load(buf);
449
+ const engine = new RedlineEngine(doc);
450
+ engine.accept_all_revisions();
451
+ const outBuf = await doc.save();
452
+ const fs = await import("fs");
453
+ fs.writeFileSync(outPath, outBuf);
454
+ return {
455
+ content: [
456
+ {
457
+ type: "text",
458
+ text: `Accepted all changes. Saved to: ${outPath}`
459
+ }
460
+ ]
461
+ };
332
462
  }
333
- const buf = readFileSync(docxPath);
334
- const doc = await DocumentObject2.load(buf);
335
- const engine = new RedlineEngine(doc);
336
- engine.accept_all_revisions();
337
- const outBuf = await doc.save();
338
- const fs = await import("fs");
339
- fs.writeFileSync(outPath, outBuf);
340
- return {
341
- content: [{ type: "text", text: `Accepted all changes. Saved to: ${outPath}` }]
342
- };
343
- }
344
- if (name === "diff_docx_files") {
345
- const origPath = args?.original_path;
346
- const modPath = args?.modified_path;
347
- const origBuf = readFileSync(origPath);
348
- const modBuf = readFileSync(modPath);
349
- const origText = await extractTextFromBuffer(origBuf, true);
350
- const modText = await extractTextFromBuffer(modBuf, true);
351
- const diff = create_unified_diff(origText, modText);
352
- return {
353
- content: [{ type: "text", text: diff || "No differences found." }]
354
- };
355
- }
356
- if (name === "finalize_document") {
357
- const filePath = args?.file_path;
358
- let outPath = args?.output_path;
359
- if (!outPath) {
360
- const ext = extname(filePath);
361
- const base = basename2(filePath, ext);
362
- const dir = dirname(filePath);
363
- outPath = resolve2(dir, `${base}_final${ext}`);
463
+ if (name === "diff_docx_files") {
464
+ const origPath = args?.original_path;
465
+ const modPath = args?.modified_path;
466
+ const compareClean = args?.compare_clean ?? true;
467
+ const origBuf = readFileBytesOrThrow(origPath);
468
+ const modBuf = readFileBytesOrThrow(modPath);
469
+ const origText = await extractTextFromBuffer(origBuf, compareClean);
470
+ const modText = await extractTextFromBuffer(modBuf, compareClean);
471
+ const diff = create_word_patch_diff(
472
+ origText,
473
+ modText,
474
+ basename2(origPath),
475
+ basename2(modPath)
476
+ );
477
+ return {
478
+ content: [{ type: "text", text: diff || "No differences found." }]
479
+ };
364
480
  }
365
- const buf = readFileSync(filePath);
366
- const doc = await DocumentObject2.load(buf);
367
- const result = await finalize_document(doc, {
368
- filename: basename2(filePath),
369
- sanitize_mode: args?.sanitize_mode || "full",
370
- accept_all: args?.accept_all,
371
- protection_mode: args?.protection_mode,
372
- author: args?.author,
373
- export_pdf: args?.export_pdf
374
- });
375
- const fs = await import("fs");
376
- fs.writeFileSync(outPath, result.outBuffer);
377
- return {
378
- content: [{ type: "text", text: `Saved to: ${outPath}
481
+ if (name === "finalize_document") {
482
+ const filePath = args?.file_path;
483
+ let outPath = args?.output_path;
484
+ if (!outPath) {
485
+ const ext = extname(filePath);
486
+ const base = basename2(filePath, ext);
487
+ const dir = dirname(filePath);
488
+ outPath = resolve2(dir, `${base}_final${ext}`);
489
+ }
490
+ const buf = readFileBytesOrThrow(filePath);
491
+ const doc = await DocumentObject2.load(buf);
492
+ const result = await finalize_document(doc, {
493
+ filename: basename2(filePath),
494
+ sanitize_mode: args?.sanitize_mode || "full",
495
+ accept_all: args?.accept_all,
496
+ protection_mode: args?.protection_mode,
497
+ author: args?.author,
498
+ export_pdf: args?.export_pdf
499
+ });
500
+ const fs = await import("fs");
501
+ fs.writeFileSync(outPath, result.outBuffer);
502
+ return {
503
+ content: [
504
+ {
505
+ type: "text",
506
+ text: `Saved to: ${outPath}
379
507
 
380
- ${result.reportText}` }]
508
+ ${result.reportText}`
509
+ }
510
+ ]
511
+ };
512
+ }
513
+ throw new Error(`Unknown tool: ${name}`);
514
+ } catch (error) {
515
+ return {
516
+ content: [
517
+ {
518
+ type: "text",
519
+ text: `Error executing tool ${name}: ${error.message}`
520
+ }
521
+ ],
522
+ isError: true
381
523
  };
382
524
  }
383
- throw new Error(`Unknown tool: ${name}`);
384
- } catch (error) {
385
- return {
386
- content: [{ type: "text", text: `Error executing tool ${name}: ${error.message}` }],
387
- isError: true
388
- };
389
525
  }
390
- });
526
+ );
391
527
  async function main() {
392
528
  const transport = new StdioServerTransport();
393
529
  await server.connect(transport);
394
- console.error(`Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio`);
530
+ console.error(
531
+ `Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio`
532
+ );
395
533
  }
396
534
  main().catch(console.error);
397
535
  //# sourceMappingURL=index.js.map