@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adeu/mcp-server",
3
- "version": "1.18.0",
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.0",
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
- if (parsed !== null && typeof parsed === "object") {
472
- return parsed;
473
- }
474
- return item;
536
+ obj = parsed !== null && typeof parsed === "object" ? parsed : item;
475
537
  } catch {
476
- return item;
538
+ obj = item;
477
539
  }
478
540
  }
479
- return item;
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
- { description: "Logs out of the Adeu Cloud backend." },
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 {
@@ -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: { file_path: join(tmpdir(), "DEF_DOES_NOT_EXIST.docx") },
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",
@@ -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",