@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 +15 -1
- package/dist/index.js +160 -61
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/scripts/verify-bundle.js +50 -0
- package/src/index.test.ts +21 -2
- package/src/index.ts +59 -10
- package/src/mcp.bugs.test.ts +2 -0
- package/src/parity_live.test.ts +265 -0
- package/src/response-builders.ts +2 -0
- package/src/tools/email.test.ts +104 -1
- package/src/tools/email.ts +137 -51
- package/tests/fixtures/gap1_deleted_row_repro.docx +0 -0
- package/tests/fixtures/gap1_minimal_repro.docx +0 -0
- package/tests/fixtures/gap2_minimal_repro.docx +0 -0
- package/tests/fixtures/generate_fixtures.py +69 -0
- package/tsup.config.ts +24 -0
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
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
|
|
618
|
-
|
|
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
|
-
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
"
|
|
717
|
+
"Authentication expired. Please call `login_to_adeu_cloud` to re-authenticate."
|
|
654
718
|
);
|
|
655
719
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1206
|
+
const res2 = build_appendix_response(text, page, file_path);
|
|
1207
|
+
return res2;
|
|
1113
1208
|
}
|
|
1114
|
-
|
|
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);
|