@adeu/mcp-server 1.18.0 → 1.18.2
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 +136 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/scripts/verify-reasoning-order.mjs +113 -0
- package/src/index.ts +170 -11
- package/src/mcp.bugs.test.ts +10 -1
- package/src/mcp.cloud.test.ts +4 -3
- package/src/mcp.schema-gaps.test.ts +4 -0
- package/src/parity_live.test.ts +181 -95
- package/src/repro.feedback.test.ts +24 -7
- package/src/response-builders.ts +21 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adeu/mcp-server",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.2",
|
|
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.18.
|
|
34
|
+
"@adeu/core": "^1.18.2",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
36
|
"@modelcontextprotocol/ext-apps": "^1.0.0",
|
|
37
37
|
"zod": "^3.23.8"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// FILE: node/packages/mcp-server/scripts/verify-reasoning-order.mjs
|
|
2
|
+
// Boots the compiled MCP server over stdio, runs initialize + tools/list, and
|
|
3
|
+
// asserts that EVERY tool declares `reasoning` as (a) the first property and
|
|
4
|
+
// (b) a required field. Exit code 0 = all good, 1 = at least one violation.
|
|
5
|
+
//
|
|
6
|
+
// Usage: node scripts/verify-reasoning-order.mjs
|
|
7
|
+
// Requires: npm run build (dist/index.js must exist).
|
|
8
|
+
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { resolve, dirname } from "node:path";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const serverPath = resolve(__dirname, "../dist/index.js");
|
|
16
|
+
|
|
17
|
+
if (!existsSync(serverPath)) {
|
|
18
|
+
console.error(
|
|
19
|
+
`❌ Server not built: ${serverPath}. Run 'npm run build' first.`,
|
|
20
|
+
);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const proc = spawn("node", [serverPath]);
|
|
25
|
+
const pending = new Map();
|
|
26
|
+
let rpcId = 100;
|
|
27
|
+
let buf = "";
|
|
28
|
+
|
|
29
|
+
proc.stdout.on("data", (data) => {
|
|
30
|
+
buf += data.toString();
|
|
31
|
+
let idx;
|
|
32
|
+
while ((idx = buf.indexOf("\n")) !== -1) {
|
|
33
|
+
const line = buf.slice(0, idx).trim();
|
|
34
|
+
buf = buf.slice(idx + 1);
|
|
35
|
+
if (!line.startsWith("{")) continue;
|
|
36
|
+
try {
|
|
37
|
+
const msg = JSON.parse(line);
|
|
38
|
+
if (msg.id !== undefined && pending.has(msg.id)) {
|
|
39
|
+
const cb = pending.get(msg.id);
|
|
40
|
+
pending.delete(msg.id);
|
|
41
|
+
cb(msg);
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
/* ignore partial/non-JSON */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function rpc(method, params) {
|
|
50
|
+
const id = ++rpcId;
|
|
51
|
+
return new Promise((res, rej) => {
|
|
52
|
+
const t = setTimeout(() => rej(new Error(`RPC timeout: ${method}`)), 15000);
|
|
53
|
+
pending.set(id, (m) => {
|
|
54
|
+
clearTimeout(t);
|
|
55
|
+
res(m);
|
|
56
|
+
});
|
|
57
|
+
proc.stdin.write(
|
|
58
|
+
JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function notify(method, params) {
|
|
64
|
+
proc.stdin.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await rpc("initialize", {
|
|
69
|
+
protocolVersion: "2024-11-05",
|
|
70
|
+
capabilities: {},
|
|
71
|
+
clientInfo: { name: "verify-reasoning", version: "0.0.0" },
|
|
72
|
+
});
|
|
73
|
+
notify("notifications/initialized", {});
|
|
74
|
+
|
|
75
|
+
const list = await rpc("tools/list", {});
|
|
76
|
+
const tools = list.result?.tools ?? [];
|
|
77
|
+
|
|
78
|
+
if (tools.length === 0) {
|
|
79
|
+
console.error("❌ tools/list returned no tools.");
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let failures = 0;
|
|
84
|
+
for (const tool of tools) {
|
|
85
|
+
const schema = tool.inputSchema ?? {};
|
|
86
|
+
const props = schema.properties ?? {};
|
|
87
|
+
const keys = Object.keys(props);
|
|
88
|
+
const required = schema.required ?? [];
|
|
89
|
+
|
|
90
|
+
const firstKey = keys[0];
|
|
91
|
+
const isFirst = firstKey === "reasoning";
|
|
92
|
+
const isRequired = required.includes("reasoning");
|
|
93
|
+
const isString = props.reasoning?.type === "string";
|
|
94
|
+
|
|
95
|
+
if (isFirst && isRequired && isString) {
|
|
96
|
+
console.log(`✅ ${tool.name}: reasoning is first + required (string)`);
|
|
97
|
+
} else {
|
|
98
|
+
failures++;
|
|
99
|
+
console.error(
|
|
100
|
+
`❌ ${tool.name}: reasoning check failed ` +
|
|
101
|
+
`(firstKey=${firstKey}, required=${isRequired}, string=${isString})`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.error(`\n${tools.length - failures}/${tools.length} tools passed.`);
|
|
107
|
+
proc.kill();
|
|
108
|
+
process.exit(failures === 0 ? 0 : 1);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.error(`❌ ${e.message}`);
|
|
111
|
+
proc.kill();
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -34,7 +34,56 @@ import {
|
|
|
34
34
|
list_available_mailboxes,
|
|
35
35
|
} from "./tools/email.js";
|
|
36
36
|
import { MARKDOWN_UI_URI, EMAIL_UI_URI } from "./shared.js";
|
|
37
|
+
// Parity with Python models.py `_infer_type_in_place` + `_coerce_match_mode_in_place`.
|
|
38
|
+
// The MCP boundary schema is permissive; these repairs let recoverable payloads
|
|
39
|
+
// (a missing `type` that's unambiguous from the key signature, or a non-canonical
|
|
40
|
+
// `match_mode`) succeed instead of failing the whole-array Zod parse with an
|
|
41
|
+
// opaque -32602. Anything still un-inferrable is caught by the handler guard
|
|
42
|
+
// below and reported per-index; anything that doesn't apply to the document is
|
|
43
|
+
// caught by the engine's validate_edits. Mirrors how Python repairs in a
|
|
44
|
+
// BeforeValidator ahead of its (strict) discriminated union.
|
|
45
|
+
const MATCH_MODE_SYNONYMS: Record<string, "strict" | "first" | "all"> = {
|
|
46
|
+
strict: "strict",
|
|
47
|
+
first: "first",
|
|
48
|
+
all: "all",
|
|
49
|
+
first_only: "first",
|
|
50
|
+
firstonly: "first",
|
|
51
|
+
"first-only": "first",
|
|
52
|
+
all_occurrences: "all",
|
|
53
|
+
alloccurrences: "all",
|
|
54
|
+
"all-occurrences": "all",
|
|
55
|
+
every: "all",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function coerceChangeItemInPlace(item: any): void {
|
|
59
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) return;
|
|
60
|
+
|
|
61
|
+
// Infer a missing `type` ONLY when exactly one variant fits unambiguously.
|
|
62
|
+
// Deliberately do NOT infer from `target_id` alone (accept vs reject is a
|
|
63
|
+
// semantic choice) or `target_text` alone (delete_row vs empty-new_text
|
|
64
|
+
// modify). Those stay absent and are rejected with a clear message.
|
|
65
|
+
if (!("type" in item) || item.type === undefined || item.type === null) {
|
|
66
|
+
if ("cells" in item) item.type = "insert_row";
|
|
67
|
+
else if ("text" in item && "target_id" in item) item.type = "reply";
|
|
68
|
+
else if ("target_text" in item && "new_text" in item) item.type = "modify";
|
|
69
|
+
}
|
|
37
70
|
|
|
71
|
+
// Normalize match_mode: canonical passes through, synonyms map, anything else
|
|
72
|
+
// (help-string echo "strict, first, or all", empty, non-string) is dropped so
|
|
73
|
+
// the engine's "strict" default applies. Never coerce junk to "all" — that
|
|
74
|
+
// would silently mass-edit; defaulting to strict fails safe with an
|
|
75
|
+
// ambiguity error instead.
|
|
76
|
+
if ("match_mode" in item) {
|
|
77
|
+
const raw = item.match_mode;
|
|
78
|
+
if (typeof raw !== "string") {
|
|
79
|
+
delete item.match_mode;
|
|
80
|
+
} else {
|
|
81
|
+
const mapped = MATCH_MODE_SYNONYMS[raw.trim().toLowerCase()];
|
|
82
|
+
if (mapped === undefined) delete item.match_mode;
|
|
83
|
+
else item.match_mode = mapped;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
38
87
|
function readFileBytesOrThrow(filePath: string): Buffer {
|
|
39
88
|
try {
|
|
40
89
|
return readFileSync(filePath);
|
|
@@ -223,6 +272,11 @@ registerAppTool(
|
|
|
223
272
|
title: "Read DOCX",
|
|
224
273
|
description: READ_DOCX_COMMON_DESC + READ_DOCX_TAIL,
|
|
225
274
|
inputSchema: z.object({
|
|
275
|
+
reasoning: z
|
|
276
|
+
.string()
|
|
277
|
+
.describe(
|
|
278
|
+
"Why do I need to read this docx document? State this reason before any other parameter.",
|
|
279
|
+
),
|
|
226
280
|
file_path: z.string().describe("Absolute path to the DOCX file."),
|
|
227
281
|
clean_view: z
|
|
228
282
|
.boolean()
|
|
@@ -270,6 +324,7 @@ registerAppTool(
|
|
|
270
324
|
_meta: { ui: { resourceUri: MARKDOWN_UI_URI } },
|
|
271
325
|
},
|
|
272
326
|
async ({
|
|
327
|
+
reasoning,
|
|
273
328
|
file_path,
|
|
274
329
|
clean_view,
|
|
275
330
|
mode,
|
|
@@ -281,6 +336,7 @@ registerAppTool(
|
|
|
281
336
|
search_case_sensitive,
|
|
282
337
|
}) => {
|
|
283
338
|
try {
|
|
339
|
+
void reasoning;
|
|
284
340
|
const buf = readFileBytesOrThrow(file_path);
|
|
285
341
|
|
|
286
342
|
if (mode === "outline") {
|
|
@@ -359,8 +415,9 @@ const CHANGE_ITEM_SCHEMA = z
|
|
|
359
415
|
.object({
|
|
360
416
|
type: z
|
|
361
417
|
.enum(["modify", "accept", "reject", "reply", "insert_row", "delete_row"])
|
|
418
|
+
.optional()
|
|
362
419
|
.describe(
|
|
363
|
-
"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).",
|
|
420
|
+
"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.",
|
|
364
421
|
),
|
|
365
422
|
target_text: z
|
|
366
423
|
.string()
|
|
@@ -417,6 +474,11 @@ server.registerTool(
|
|
|
417
474
|
{
|
|
418
475
|
description: PROCESS_BATCH_COMMON_DESC + PROCESS_BATCH_OPERATIONS_DESC,
|
|
419
476
|
inputSchema: {
|
|
477
|
+
reasoning: z
|
|
478
|
+
.string()
|
|
479
|
+
.describe(
|
|
480
|
+
"Why do I need to apply these changes to the document? State this reason before any other parameter.",
|
|
481
|
+
),
|
|
420
482
|
original_docx_path: z
|
|
421
483
|
.string()
|
|
422
484
|
.describe("Absolute path to the source file."),
|
|
@@ -439,6 +501,7 @@ server.registerTool(
|
|
|
439
501
|
},
|
|
440
502
|
},
|
|
441
503
|
async ({
|
|
504
|
+
reasoning,
|
|
442
505
|
original_docx_path,
|
|
443
506
|
author_name,
|
|
444
507
|
changes,
|
|
@@ -446,6 +509,7 @@ server.registerTool(
|
|
|
446
509
|
dry_run,
|
|
447
510
|
}) => {
|
|
448
511
|
try {
|
|
512
|
+
void reasoning;
|
|
449
513
|
if (!author_name || !author_name.trim())
|
|
450
514
|
return {
|
|
451
515
|
content: [
|
|
@@ -465,19 +529,63 @@ server.registerTool(
|
|
|
465
529
|
// bundled. Genuine objects and unparseable strings pass through
|
|
466
530
|
// untouched so validation surfaces a clear error rather than crashing.
|
|
467
531
|
const sanitizedChanges = changes.map((item: any) => {
|
|
532
|
+
let obj: any = item;
|
|
468
533
|
if (typeof item === "string") {
|
|
469
534
|
try {
|
|
470
535
|
const parsed = JSON.parse(item);
|
|
471
|
-
|
|
472
|
-
return parsed;
|
|
473
|
-
}
|
|
474
|
-
return item;
|
|
536
|
+
obj = parsed !== null && typeof parsed === "object" ? parsed : item;
|
|
475
537
|
} catch {
|
|
476
|
-
|
|
538
|
+
obj = item;
|
|
477
539
|
}
|
|
478
540
|
}
|
|
479
|
-
|
|
541
|
+
// Repair recoverable payloads (infer type, normalize match_mode) the
|
|
542
|
+
// same way Python does before its union validation.
|
|
543
|
+
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
|
|
544
|
+
coerceChangeItemInPlace(obj);
|
|
545
|
+
}
|
|
546
|
+
return obj;
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Boundary guard, scoped narrowly: after inference, reject only an OBJECT
|
|
550
|
+
// that still carries no resolvable `type`. Strings, nulls, and non-objects
|
|
551
|
+
// are intentionally left for the engine's validate_edits to report
|
|
552
|
+
// ("Invalid change format… received a primitive"), keeping the engine the
|
|
553
|
+
// single authority for those and avoiding a competing error surface.
|
|
554
|
+
// A typeless object is the one case the engine can't cleanly reject (with
|
|
555
|
+
// `type` now optional it would fall into the edits bucket as a no-op), so
|
|
556
|
+
// it is caught here with an actionable, per-index message.
|
|
557
|
+
const VALID_TYPES = new Set([
|
|
558
|
+
"modify",
|
|
559
|
+
"accept",
|
|
560
|
+
"reject",
|
|
561
|
+
"reply",
|
|
562
|
+
"insert_row",
|
|
563
|
+
"delete_row",
|
|
564
|
+
]);
|
|
565
|
+
const typeErrors: string[] = [];
|
|
566
|
+
sanitizedChanges.forEach((c: any, i: number) => {
|
|
567
|
+
if (
|
|
568
|
+
c !== null &&
|
|
569
|
+
typeof c === "object" &&
|
|
570
|
+
!Array.isArray(c) &&
|
|
571
|
+
(!c.type || !VALID_TYPES.has(c.type))
|
|
572
|
+
) {
|
|
573
|
+
typeErrors.push(
|
|
574
|
+
`- 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(", ")}].`,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
480
577
|
});
|
|
578
|
+
if (typeErrors.length > 0) {
|
|
579
|
+
return {
|
|
580
|
+
isError: true,
|
|
581
|
+
content: [
|
|
582
|
+
{
|
|
583
|
+
type: "text",
|
|
584
|
+
text: `Batch rejected. Some changes are malformed:\n\n${typeErrors.join("\n")}`,
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
};
|
|
588
|
+
}
|
|
481
589
|
|
|
482
590
|
let outPath = output_path;
|
|
483
591
|
if (!outPath) {
|
|
@@ -539,12 +647,18 @@ server.registerTool(
|
|
|
539
647
|
description:
|
|
540
648
|
"Accepts all tracked changes and removes all comments in a single operation.",
|
|
541
649
|
inputSchema: {
|
|
650
|
+
reasoning: z
|
|
651
|
+
.string()
|
|
652
|
+
.describe(
|
|
653
|
+
"Why do I need to accept all changes in this document? State this reason before any other parameter.",
|
|
654
|
+
),
|
|
542
655
|
docx_path: z.string().describe("Absolute path to the DOCX file."),
|
|
543
656
|
output_path: z.string().optional().describe("Optional output path."),
|
|
544
657
|
},
|
|
545
658
|
},
|
|
546
|
-
async ({ docx_path, output_path }) => {
|
|
659
|
+
async ({ reasoning, docx_path, output_path }) => {
|
|
547
660
|
try {
|
|
661
|
+
void reasoning;
|
|
548
662
|
let outPath = output_path;
|
|
549
663
|
if (!outPath) {
|
|
550
664
|
const ext = extname(docx_path);
|
|
@@ -582,6 +696,11 @@ server.registerTool(
|
|
|
582
696
|
{
|
|
583
697
|
description: DIFF_DOCX_DESC,
|
|
584
698
|
inputSchema: {
|
|
699
|
+
reasoning: z
|
|
700
|
+
.string()
|
|
701
|
+
.describe(
|
|
702
|
+
"Why do I need to diff these two documents? State this reason before any other parameter.",
|
|
703
|
+
),
|
|
585
704
|
original_path: z
|
|
586
705
|
.string()
|
|
587
706
|
.describe("Absolute path to the baseline DOCX file."),
|
|
@@ -596,8 +715,9 @@ server.registerTool(
|
|
|
596
715
|
),
|
|
597
716
|
},
|
|
598
717
|
},
|
|
599
|
-
async ({ original_path, modified_path, compare_clean }) => {
|
|
718
|
+
async ({ reasoning, original_path, modified_path, compare_clean }) => {
|
|
600
719
|
try {
|
|
720
|
+
void reasoning;
|
|
601
721
|
const origBuf = readFileBytesOrThrow(original_path);
|
|
602
722
|
const modBuf = readFileBytesOrThrow(modified_path);
|
|
603
723
|
|
|
@@ -629,6 +749,11 @@ server.registerTool(
|
|
|
629
749
|
description:
|
|
630
750
|
"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.",
|
|
631
751
|
inputSchema: {
|
|
752
|
+
reasoning: z
|
|
753
|
+
.string()
|
|
754
|
+
.describe(
|
|
755
|
+
"Why do I need to finalize this document? State this reason before any other parameter.",
|
|
756
|
+
),
|
|
632
757
|
file_path: z.string().describe("Absolute path to the DOCX file."),
|
|
633
758
|
output_path: z.string().optional().describe("Optional output path."),
|
|
634
759
|
sanitize_mode: z
|
|
@@ -659,6 +784,7 @@ server.registerTool(
|
|
|
659
784
|
},
|
|
660
785
|
},
|
|
661
786
|
async ({
|
|
787
|
+
reasoning,
|
|
662
788
|
file_path,
|
|
663
789
|
output_path,
|
|
664
790
|
sanitize_mode,
|
|
@@ -668,6 +794,7 @@ server.registerTool(
|
|
|
668
794
|
export_pdf,
|
|
669
795
|
}) => {
|
|
670
796
|
try {
|
|
797
|
+
void reasoning;
|
|
671
798
|
let outPath = output_path;
|
|
672
799
|
if (!outPath) {
|
|
673
800
|
const ext = extname(file_path);
|
|
@@ -726,6 +853,11 @@ if (!isDocxOnly) {
|
|
|
726
853
|
"FOLDER DEFAULT: omitting `folder` searches the Inbox only (matching what the user sees in their mail client). Use `folder='sent'` for sent items, `folder='all'` to include Deleted Items, Drafts, and other folders.\n\n" +
|
|
727
854
|
"ATTACHMENTS: attachments larger than `max_attachment_size_mb` (default 10) are listed in the response but NOT downloaded — raise the cap if you need them. Always set `working_directory` when calling from a project so attachments land alongside the user's other files.",
|
|
728
855
|
inputSchema: z.object({
|
|
856
|
+
reasoning: z
|
|
857
|
+
.string()
|
|
858
|
+
.describe(
|
|
859
|
+
"Why do I need to search or fetch these emails? State this reason before any other parameter.",
|
|
860
|
+
),
|
|
729
861
|
sender: z.string().optional(),
|
|
730
862
|
subject: z.string().optional(),
|
|
731
863
|
has_attachments: z.boolean().optional(),
|
|
@@ -776,6 +908,13 @@ if (!isDocxOnly) {
|
|
|
776
908
|
"- Signing in through ANY of the user's linked accounts authenticates the same Adeu user. Once logged in, the session can read from and draft in ALL of that user's linked accounts and ALL of their mailboxes — not just the one used to sign in.\n" +
|
|
777
909
|
"- The choice of which provider account to sign in through is purely an SSO mechanism; it does not select a 'current account' for the session.\n\n" +
|
|
778
910
|
"When the user asks which accounts or mailboxes are available, call `list_available_mailboxes` rather than naming a single account from the login response.",
|
|
911
|
+
inputSchema: {
|
|
912
|
+
reasoning: z
|
|
913
|
+
.string()
|
|
914
|
+
.describe(
|
|
915
|
+
"Why do I need to log in to Adeu Cloud? State this reason before any other parameter.",
|
|
916
|
+
),
|
|
917
|
+
},
|
|
779
918
|
},
|
|
780
919
|
async () => {
|
|
781
920
|
try {
|
|
@@ -788,7 +927,16 @@ if (!isDocxOnly) {
|
|
|
788
927
|
|
|
789
928
|
server.registerTool(
|
|
790
929
|
"logout_of_adeu_cloud",
|
|
791
|
-
{
|
|
930
|
+
{
|
|
931
|
+
description: "Logs out of the Adeu Cloud backend.",
|
|
932
|
+
inputSchema: {
|
|
933
|
+
reasoning: z
|
|
934
|
+
.string()
|
|
935
|
+
.describe(
|
|
936
|
+
"Why do I need to log out of Adeu Cloud? State this reason before any other parameter.",
|
|
937
|
+
),
|
|
938
|
+
},
|
|
939
|
+
},
|
|
792
940
|
async () => {
|
|
793
941
|
try {
|
|
794
942
|
return (await logout_of_adeu_cloud()) as any;
|
|
@@ -809,6 +957,11 @@ if (!isDocxOnly) {
|
|
|
809
957
|
"`body_markdown` is converted server-side to styled HTML with inlined CSS for email-client compatibility. Write the body in plain Markdown — do not pre-render HTML.\n\n" +
|
|
810
958
|
"`attachment_paths` takes absolute file paths on the user's local disk and uploads them with the draft. Useful right after search_and_fetch_emails downloaded attachments — those local paths can be passed directly here.",
|
|
811
959
|
inputSchema: {
|
|
960
|
+
reasoning: z
|
|
961
|
+
.string()
|
|
962
|
+
.describe(
|
|
963
|
+
"Why do I need to create this email draft? State this reason before any other parameter.",
|
|
964
|
+
),
|
|
812
965
|
body_markdown: z.string(),
|
|
813
966
|
reply_to_email_id: z.string().optional(),
|
|
814
967
|
subject: z.string().optional(),
|
|
@@ -837,7 +990,13 @@ if (!isDocxOnly) {
|
|
|
837
990
|
"Lists all personal and shared/delegated mailboxes the authenticated Adeu user has access to, across ALL of their linked provider accounts. Returns each mailbox's `email_address`, `display_name`, auto-processing settings, and write-back preference.\n\n" +
|
|
838
991
|
"This is the right tool to answer 'which accounts/mailboxes am I logged into?' — Adeu login is user-level, so a single MCP session can see every mailbox listed here regardless of which provider account was used for SSO.\n\n" +
|
|
839
992
|
"Call this FIRST when the user names a specific mailbox or shared inbox, to resolve the canonical `email_address`. Then pass that address as `mailbox_address` to `search_and_fetch_emails` or `create_email_draft` to scope the operation. Omitting `mailbox_address` on those tools targets the user's primary personal mailbox.",
|
|
840
|
-
inputSchema: {
|
|
993
|
+
inputSchema: {
|
|
994
|
+
reasoning: z
|
|
995
|
+
.string()
|
|
996
|
+
.describe(
|
|
997
|
+
"Why do I need to list available mailboxes? State this reason before any other parameter.",
|
|
998
|
+
),
|
|
999
|
+
},
|
|
841
1000
|
},
|
|
842
1001
|
async () => {
|
|
843
1002
|
try {
|
package/src/mcp.bugs.test.ts
CHANGED
|
@@ -94,6 +94,7 @@ describe("Resolved Bugs MCP Server Verification", () => {
|
|
|
94
94
|
{
|
|
95
95
|
name: "process_document_batch",
|
|
96
96
|
arguments: {
|
|
97
|
+
reasoning: "test",
|
|
97
98
|
original_docx_path: cleanDocPath,
|
|
98
99
|
author_name: "Agent",
|
|
99
100
|
changes: [],
|
|
@@ -114,6 +115,7 @@ describe("Resolved Bugs MCP Server Verification", () => {
|
|
|
114
115
|
{
|
|
115
116
|
name: "diff_docx_files",
|
|
116
117
|
arguments: {
|
|
118
|
+
reasoning: "test",
|
|
117
119
|
original_path: cleanDocPath,
|
|
118
120
|
modified_path: dirtyDocPath,
|
|
119
121
|
compare_clean: true,
|
|
@@ -132,6 +134,7 @@ describe("Resolved Bugs MCP Server Verification", () => {
|
|
|
132
134
|
{
|
|
133
135
|
name: "diff_docx_files",
|
|
134
136
|
arguments: {
|
|
137
|
+
reasoning: "test",
|
|
135
138
|
original_path: cleanDocPath,
|
|
136
139
|
modified_path: dirtyDocPath,
|
|
137
140
|
compare_clean: false,
|
|
@@ -148,7 +151,10 @@ describe("Resolved Bugs MCP Server Verification", () => {
|
|
|
148
151
|
"tools/call",
|
|
149
152
|
{
|
|
150
153
|
name: "read_docx",
|
|
151
|
-
arguments: {
|
|
154
|
+
arguments: {
|
|
155
|
+
reasoning: "test",
|
|
156
|
+
file_path: join(tmpdir(), "DEF_DOES_NOT_EXIST.docx"),
|
|
157
|
+
},
|
|
152
158
|
},
|
|
153
159
|
104,
|
|
154
160
|
);
|
|
@@ -173,6 +179,7 @@ describe("Resolved Bugs MCP Server Verification", () => {
|
|
|
173
179
|
{
|
|
174
180
|
name: "process_document_batch",
|
|
175
181
|
arguments: {
|
|
182
|
+
reasoning: "test",
|
|
176
183
|
original_docx_path: cleanDocPath,
|
|
177
184
|
author_name: "Agent",
|
|
178
185
|
changes: [
|
|
@@ -202,6 +209,7 @@ describe("Resolved Bugs MCP Server Verification", () => {
|
|
|
202
209
|
{
|
|
203
210
|
name: "process_document_batch",
|
|
204
211
|
arguments: {
|
|
212
|
+
reasoning: "test",
|
|
205
213
|
original_docx_path: cleanDocPath,
|
|
206
214
|
author_name: "Agent",
|
|
207
215
|
changes: [
|
|
@@ -227,6 +235,7 @@ describe("Resolved Bugs MCP Server Verification", () => {
|
|
|
227
235
|
{
|
|
228
236
|
name: "read_docx",
|
|
229
237
|
arguments: {
|
|
238
|
+
reasoning: "test",
|
|
230
239
|
file_path: cleanDocPath,
|
|
231
240
|
page: "1",
|
|
232
241
|
outline_max_level: "3",
|
package/src/mcp.cloud.test.ts
CHANGED
|
@@ -83,7 +83,7 @@ describe("Cloud Auth & Email Tools MCP Verification", () => {
|
|
|
83
83
|
"tools/call",
|
|
84
84
|
{
|
|
85
85
|
name: "search_and_fetch_emails",
|
|
86
|
-
arguments: { subject: "Invoice" },
|
|
86
|
+
arguments: { reasoning: "test", subject: "Invoice" },
|
|
87
87
|
},
|
|
88
88
|
201,
|
|
89
89
|
);
|
|
@@ -97,7 +97,7 @@ describe("Cloud Auth & Email Tools MCP Verification", () => {
|
|
|
97
97
|
"tools/call",
|
|
98
98
|
{
|
|
99
99
|
name: "list_available_mailboxes",
|
|
100
|
-
arguments: {},
|
|
100
|
+
arguments: { reasoning: "test" },
|
|
101
101
|
},
|
|
102
102
|
204,
|
|
103
103
|
);
|
|
@@ -116,6 +116,7 @@ describe("Cloud Auth & Email Tools MCP Verification", () => {
|
|
|
116
116
|
{
|
|
117
117
|
name: "create_email_draft",
|
|
118
118
|
arguments: {
|
|
119
|
+
reasoning: "test",
|
|
119
120
|
body_markdown: "Hello World",
|
|
120
121
|
// Missing reply_to_email_id AND subject/to_recipients
|
|
121
122
|
},
|
|
@@ -137,7 +138,7 @@ describe("Cloud Auth & Email Tools MCP Verification", () => {
|
|
|
137
138
|
"tools/call",
|
|
138
139
|
{
|
|
139
140
|
name: "logout_of_adeu_cloud",
|
|
140
|
-
arguments: {},
|
|
141
|
+
arguments: { reasoning: "test" },
|
|
141
142
|
},
|
|
142
143
|
203,
|
|
143
144
|
);
|
|
@@ -251,6 +251,7 @@ describe("MCP tools — advertised schema/docs match real capability", () => {
|
|
|
251
251
|
const res = await rpc("tools/call", {
|
|
252
252
|
name: "process_document_batch",
|
|
253
253
|
arguments: {
|
|
254
|
+
reasoning: "test",
|
|
254
255
|
original_docx_path: pdbFixture,
|
|
255
256
|
author_name: "Schema Gap Test",
|
|
256
257
|
changes: [
|
|
@@ -275,6 +276,7 @@ describe("MCP tools — advertised schema/docs match real capability", () => {
|
|
|
275
276
|
const res = await rpc("tools/call", {
|
|
276
277
|
name: "process_document_batch",
|
|
277
278
|
arguments: {
|
|
279
|
+
reasoning: "test",
|
|
278
280
|
original_docx_path: pdbFixture,
|
|
279
281
|
author_name: "Schema Gap Test",
|
|
280
282
|
changes: [
|
|
@@ -330,6 +332,7 @@ describe("MCP tools — advertised schema/docs match real capability", () => {
|
|
|
330
332
|
const res = await rpc("tools/call", {
|
|
331
333
|
name: "diff_docx_files",
|
|
332
334
|
arguments: {
|
|
335
|
+
reasoning: "test",
|
|
333
336
|
original_path: diffOrig,
|
|
334
337
|
modified_path: diffMod,
|
|
335
338
|
compare_clean: true,
|
|
@@ -368,6 +371,7 @@ describe("MCP tools — advertised schema/docs match real capability", () => {
|
|
|
368
371
|
const res = await rpc("tools/call", {
|
|
369
372
|
name: "finalize_document",
|
|
370
373
|
arguments: {
|
|
374
|
+
reasoning: "test",
|
|
371
375
|
file_path: finalizeInput,
|
|
372
376
|
output_path: tempOut("finalize_encrypt"),
|
|
373
377
|
protection_mode: "encrypt",
|