@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 +278 -140
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +356 -204
- package/src/mcp.bugs.test.ts +162 -0
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 {
|
|
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
|
-
|
|
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: {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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: {
|
|
203
|
-
|
|
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: {
|
|
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: {
|
|
221
|
-
|
|
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: {
|
|
233
|
-
|
|
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: {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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: [
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
isError: true
|
|
384
|
+
content: [
|
|
385
|
+
{ type: "text", text: "Error: author_name cannot be empty." }
|
|
386
|
+
]
|
|
304
387
|
};
|
|
305
388
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
315
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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(
|
|
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
|