@adeu/mcp-server 1.10.1 → 1.12.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/README.md CHANGED
@@ -39,4 +39,18 @@ Once connected, your AI agent will have access to the following tools:
39
39
  - `finalize_document`: Prepares a document for signature by applying native OOXML read-only locking and deep metadata sanitization.
40
40
 
41
41
  ## Documentation & Support
42
- For full architectural details, prompt recommendations, and the project constitution, please visit the [main Adeu repository](https://github.com/dealfluence/adeu) or our [website](https://adeu.ai).
42
+ For full architectural details, prompt recommendations, and the project constitution, please visit the [main Adeu repository](https://github.com/dealfluence/adeu) or our [website](https://adeu.ai).
43
+
44
+ ---
45
+
46
+ ## Development Runbook Note: Workspace Linkage Trap
47
+
48
+ > [!WARNING]
49
+ > **Node MCP: a committed source fix is NOT a running fix.**
50
+ > `dist/` is gitignored and the server runs the compiled bundle. After ANY change to `node/packages/core` or `mcp-server`:
51
+ >
52
+ > 1. **Relink dependencies:** `cd node && npm install` (relinks `@adeu/core` in the workspace; a copied — not symlinked — core is the #1 cause of "fix didn't take").
53
+ > 2. **Rebuild the workspace:** `npm run build` (core builds before mcp-server; confirm `core/dist` timestamp is updated).
54
+ > 3. **Verify:** Check that the build verification sentinel check succeeds (automatically runs postbuild, or can be run manually via `npm run build:verify` under `packages/mcp-server`).
55
+ > 4. **Restart the MCP server:** Restart the MCP server AND reconnect in the client to flush any cached process states.
56
+ > 5. **Confirm the build stamp:** Verify that the build stamp in the live server (via `server_info` or startup logs) matches your built git HEAD.
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
7
7
  import { basename as basename2, resolve as resolve2, extname, dirname, join as join3 } from "path";
8
8
  import { z } from "zod";
9
9
  import {
10
- registerAppTool,
10
+ registerAppTool as origRegisterAppTool,
11
11
  registerAppResource,
12
12
  RESOURCE_MIME_TYPE
13
13
  } from "@modelcontextprotocol/ext-apps/server";
@@ -15,6 +15,7 @@ import fs from "fs";
15
15
  import {
16
16
  identifyEngine,
17
17
  extractTextFromBuffer,
18
+ _extractTextFromDoc,
18
19
  DocumentObject as DocumentObject2,
19
20
  RedlineEngine,
20
21
  BatchValidationError,
@@ -109,14 +110,15 @@ ${ui_markdown}`;
109
110
  }
110
111
  };
111
112
  }
112
- function build_outline_response(doc, projected_text, file_path, outline_max_level = 2, outline_verbose = false) {
113
+ function build_outline_response(doc, projected_text, file_path, outline_max_level = 2, outline_verbose = false, paragraph_offsets = null) {
113
114
  const [body] = split_structural_appendix(projected_text);
114
115
  const pagination_result = paginate(body, "");
115
116
  const nodes = extract_outline(
116
117
  doc,
117
118
  body,
118
119
  pagination_result.body_pages,
119
- pagination_result.body_page_offsets
120
+ pagination_result.body_page_offsets,
121
+ paragraph_offsets
120
122
  );
121
123
  const rendered = render_outline_tree(
122
124
  nodes,
@@ -608,62 +610,129 @@ function getUniqueFilepath(saveDir, filename) {
608
610
  async function search_and_fetch_emails(args) {
609
611
  const apiKey = await getCloudAuthToken();
610
612
  const maxAttachmentSizeMb = typeof args.max_attachment_size_mb === "number" && args.max_attachment_size_mb > 0 ? args.max_attachment_size_mb : 10;
611
- let realEmailId;
612
- try {
613
- realEmailId = args.email_id ? resolveEmailId(args.email_id) : void 0;
614
- } catch (err) {
615
- if (err instanceof StaleShortIdError) {
613
+ let data;
614
+ if (args.task_id) {
615
+ const pollUrl = `${BACKEND_URL}/api/v1/emails/tasks/${args.task_id}`;
616
+ let completedData = null;
617
+ for (let attempt = 0; attempt < 10; attempt++) {
618
+ let res;
619
+ try {
620
+ res = await fetch(pollUrl, {
621
+ headers: {
622
+ Authorization: `Bearer ${apiKey}`,
623
+ Accept: "application/json"
624
+ },
625
+ signal: AbortSignal.timeout(15e3)
626
+ });
627
+ } catch (err) {
628
+ if (isTimeoutError(err)) {
629
+ throw new Error("Checking task status timed out.");
630
+ }
631
+ throw err;
632
+ }
633
+ if (res.status === 401) {
634
+ DesktopAuthManager.clearApiKey();
635
+ throw new Error(
636
+ "Authentication expired. Please call `login_to_adeu_cloud` to re-authenticate."
637
+ );
638
+ }
639
+ if (!res.ok) {
640
+ throw new Error(formatBackendError(res.status, await res.text()));
641
+ }
642
+ const taskData = await res.json();
643
+ const status = taskData.status;
644
+ if (status === "COMPLETED") {
645
+ completedData = taskData;
646
+ break;
647
+ }
648
+ if (status === "FAILED") {
649
+ const errorMsg = taskData.error || "Unknown internal error";
650
+ throw new Error(`Validation task failed on the server: ${errorMsg}`);
651
+ }
652
+ await new Promise((resolve3) => setTimeout(resolve3, 5e3));
653
+ }
654
+ if (!completedData) {
655
+ const msg = `Task ${args.task_id} is still processing. Please call \`search_and_fetch_emails\` again with task_id=${args.task_id}.`;
616
656
  return {
617
- isError: true,
618
- content: [{ type: "text", text: err.message }]
657
+ content: [{ type: "text", text: msg }],
658
+ structuredContent: {
659
+ status: "pending",
660
+ task_id: args.task_id,
661
+ message: msg
662
+ }
619
663
  };
620
664
  }
621
- throw err;
622
- }
623
- const payload = {
624
- email_id: realEmailId,
625
- sender: args.sender,
626
- subject: args.subject,
627
- has_attachments: args.has_attachments,
628
- attachment_name: args.attachment_name,
629
- is_unread: args.is_unread,
630
- days_ago: args.days_ago,
631
- folder: args.folder,
632
- limit: args.limit ?? 10,
633
- offset: args.offset ?? 0,
634
- mailbox_address: args.mailbox_address
635
- };
636
- Object.keys(payload).forEach(
637
- (k) => payload[k] === void 0 && delete payload[k]
638
- );
639
- let res;
640
- try {
641
- res = await fetch(`${BACKEND_URL}/api/v1/emails/search`, {
642
- method: "POST",
643
- headers: {
644
- Authorization: `Bearer ${apiKey}`,
645
- "Content-Type": "application/json"
646
- },
647
- body: JSON.stringify(payload),
648
- signal: AbortSignal.timeout(45e3)
649
- });
650
- } catch (err) {
651
- if (isTimeoutError(err)) {
665
+ data = completedData;
666
+ } else {
667
+ let realEmailId;
668
+ try {
669
+ realEmailId = args.email_id ? resolveEmailId(args.email_id) : void 0;
670
+ } catch (err) {
671
+ if (err instanceof StaleShortIdError) {
672
+ return {
673
+ isError: true,
674
+ content: [{ type: "text", text: err.message }]
675
+ };
676
+ }
677
+ throw err;
678
+ }
679
+ const payload = {
680
+ email_id: realEmailId,
681
+ sender: args.sender,
682
+ subject: args.subject,
683
+ has_attachments: args.has_attachments,
684
+ attachment_name: args.attachment_name,
685
+ is_unread: args.is_unread,
686
+ days_ago: args.days_ago,
687
+ folder: args.folder,
688
+ limit: args.limit ?? 10,
689
+ offset: args.offset ?? 0,
690
+ mailbox_address: args.mailbox_address
691
+ };
692
+ Object.keys(payload).forEach(
693
+ (k) => payload[k] === void 0 && delete payload[k]
694
+ );
695
+ let res;
696
+ try {
697
+ res = await fetch(`${BACKEND_URL}/api/v1/emails/search`, {
698
+ method: "POST",
699
+ headers: {
700
+ Authorization: `Bearer ${apiKey}`,
701
+ "Content-Type": "application/json"
702
+ },
703
+ body: JSON.stringify(payload),
704
+ signal: AbortSignal.timeout(45e3)
705
+ });
706
+ } catch (err) {
707
+ if (isTimeoutError(err)) {
708
+ throw new Error(
709
+ "Email search timed out after 45s. The mail provider (Outlook/Gmail) may be slow. Try narrowing the search with more filters (sender, subject, days_ago), or retry shortly."
710
+ );
711
+ }
712
+ throw err;
713
+ }
714
+ if (res.status === 401) {
715
+ DesktopAuthManager.clearApiKey();
652
716
  throw new Error(
653
- "Email search timed out after 45s. The mail provider (Outlook/Gmail) may be slow. Try narrowing the search with more filters (sender, subject, days_ago), or retry shortly."
717
+ "Authentication expired. Please call `login_to_adeu_cloud` to re-authenticate."
654
718
  );
655
719
  }
656
- throw err;
657
- }
658
- if (res.status === 401) {
659
- DesktopAuthManager.clearApiKey();
660
- throw new Error(
661
- "Authentication expired. Please call `login_to_adeu_cloud` to re-authenticate."
662
- );
720
+ if (!res.ok)
721
+ throw new Error(formatBackendError(res.status, await res.text()));
722
+ data = await res.json();
723
+ if (res.status === 202 || data && (data.status === "pending" || data.task_id) && data.type === void 0) {
724
+ const newTaskId = data.task_id;
725
+ const msg = `Email processing task started successfully. Task ID: ${newTaskId}. Please call \`search_and_fetch_emails\` again immediately with task_id=${newTaskId} to monitor the progress.`;
726
+ return {
727
+ content: [{ type: "text", text: msg }],
728
+ structuredContent: {
729
+ status: "pending",
730
+ task_id: String(newTaskId),
731
+ message: msg
732
+ }
733
+ };
734
+ }
663
735
  }
664
- if (!res.ok)
665
- throw new Error(formatBackendError(res.status, await res.text()));
666
- const data = await res.json();
667
736
  const cache = loadIdCache();
668
737
  if (data.type === "previews") {
669
738
  const previews = data.previews || [];
@@ -983,7 +1052,9 @@ function readFileBytesOrThrow(filePath) {
983
1052
  return readFileSync3(filePath);
984
1053
  } catch (err) {
985
1054
  if (err.code === "ENOENT") {
986
- throw new Error(`File not found: ${filePath}`);
1055
+ throw new Error(
1056
+ `File not found: ${filePath}. Note: If you are running in a sandboxed/containerized environment, the host application or MCP server may not have access to your local workspace files. You can resolve this by installing Adeu directly inside your sandboxed environment using 'uv tool install adeu' and executing the commands via the CLI.`
1057
+ );
987
1058
  }
988
1059
  throw err;
989
1060
  }
@@ -1001,10 +1072,30 @@ var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use pa
1001
1072
  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";
1002
1073
  var PROCESS_BATCH_OPERATIONS_DESC = "Each item in `changes` must specify a `type`:\n1. 'modify': Search-and-replace. `target_text` must uniquely match \u2014 include surrounding context if the phrase is ambiguous. `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.\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.\n\n`author_name` is used for attribution on all tracked changes and comments, in both disk and Live Word modes.";
1003
1074
  var DIFF_DOCX_DESC = "Compares two DOCX files and returns a unified diff of their text content. Useful for analyzing differences between versions before editing.";
1075
+ var gitSha = "893d263";
1076
+ var packageVersion = "1.12.0";
1077
+ var buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;
1004
1078
  var server = new McpServer({
1005
1079
  name: "adeu-redlining-service",
1006
- version: "1.0.0"
1080
+ version: packageVersion
1007
1081
  });
1082
+ var originalRegisterTool = server.registerTool.bind(server);
1083
+ server.registerTool = (name, schema, handler) => {
1084
+ if (schema && typeof schema === "object") {
1085
+ if (schema.description) {
1086
+ schema.description = schema.description.trim() + buildTag;
1087
+ }
1088
+ }
1089
+ return originalRegisterTool(name, schema, handler);
1090
+ };
1091
+ var registerAppTool = (mcpServer, name, schema, handler) => {
1092
+ if (schema && typeof schema === "object") {
1093
+ if (schema.description) {
1094
+ schema.description = schema.description.trim() + buildTag;
1095
+ }
1096
+ }
1097
+ return origRegisterAppTool(mcpServer, name, schema, handler);
1098
+ };
1008
1099
  var UI_CSP = {
1009
1100
  connectDomains: ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
1010
1101
  resourceDomains: [
@@ -1097,21 +1188,26 @@ registerAppTool(
1097
1188
  }) => {
1098
1189
  try {
1099
1190
  const buf = readFileBytesOrThrow(file_path);
1100
- const text = await extractTextFromBuffer(buf, clean_view);
1101
1191
  if (mode === "outline") {
1102
1192
  const doc = await DocumentObject2.load(buf);
1103
- return build_outline_response(
1193
+ const extract_res = _extractTextFromDoc(doc, clean_view, true, true);
1194
+ const res2 = build_outline_response(
1104
1195
  doc,
1105
- text,
1196
+ extract_res.text,
1106
1197
  file_path,
1107
1198
  outline_max_level,
1108
- outline_verbose
1199
+ outline_verbose,
1200
+ extract_res.paragraph_offsets
1109
1201
  );
1202
+ return res2;
1110
1203
  }
1204
+ const text = await extractTextFromBuffer(buf, clean_view);
1111
1205
  if (mode === "appendix") {
1112
- return build_appendix_response(text, page, file_path);
1206
+ const res2 = build_appendix_response(text, page, file_path);
1207
+ return res2;
1113
1208
  }
1114
- return build_paginated_response(text, page, file_path);
1209
+ const res = build_paginated_response(text, page, file_path);
1210
+ return res;
1115
1211
  } catch (e) {
1116
1212
  return {
1117
1213
  isError: true,
@@ -1144,6 +1240,7 @@ registerAppTool(
1144
1240
  email_id: z.string().optional(),
1145
1241
  working_directory: z.string().optional(),
1146
1242
  mailbox_address: z.string().optional().describe("Optional target mailbox email address to search within."),
1243
+ task_id: z.string().optional().describe("If resuming a pending check, provide the task ID here."),
1147
1244
  max_attachment_size_mb: z.number().optional().describe(
1148
1245
  "Maximum attachment size in MB to download (default 10). Attachments larger than this are listed in the response but not downloaded. Raise this to fetch large files."
1149
1246
  )
@@ -1485,8 +1582,10 @@ ${stats.skipped_details.join("\n")}`;
1485
1582
  async function main() {
1486
1583
  const transport = new StdioServerTransport();
1487
1584
  await server.connect(transport);
1585
+ const gitSha2 = "893d263";
1586
+ const buildTs = "2026-06-18T14:35:42.322Z";
1488
1587
  console.error(
1489
- `Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio`
1588
+ `Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha2}@${buildTs}`
1490
1589
  );
1491
1590
  }
1492
1591
  main().catch(console.error);