@adeu/mcp-server 1.6.8 → 1.7.1
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 +42 -0
- package/dist/index.js +772 -143
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/desktop-auth.ts +127 -0
- package/src/index.ts +418 -204
- package/src/mcp.bugs.test.ts +162 -0
- package/src/mcp.cloud.test.ts +138 -0
- package/src/shared.ts +7 -0
- package/src/tools/auth.ts +49 -0
- package/src/tools/email.ts +313 -0
package/src/index.ts
CHANGED
|
@@ -1,43 +1,62 @@
|
|
|
1
|
-
import { Server } from
|
|
2
|
-
import { StdioServerTransport } from
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ListToolsRequestSchema,
|
|
6
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { basename, resolve, extname, dirname } from "node:path";
|
|
9
|
+
import {
|
|
10
|
+
identifyEngine,
|
|
11
|
+
extractTextFromBuffer,
|
|
12
|
+
DocumentObject,
|
|
13
|
+
RedlineEngine,
|
|
11
14
|
BatchValidationError,
|
|
12
|
-
|
|
13
|
-
finalize_document
|
|
14
|
-
} from
|
|
15
|
-
import {
|
|
16
|
-
build_paginated_response,
|
|
17
|
-
build_outline_response,
|
|
18
|
-
build_appendix_response
|
|
19
|
-
} from
|
|
20
|
-
|
|
15
|
+
create_word_patch_diff,
|
|
16
|
+
finalize_document,
|
|
17
|
+
} from "@adeu/core";
|
|
18
|
+
import {
|
|
19
|
+
build_paginated_response,
|
|
20
|
+
build_outline_response,
|
|
21
|
+
build_appendix_response,
|
|
22
|
+
} from "./response-builders.js";
|
|
23
|
+
import { login_to_adeu_cloud, logout_of_adeu_cloud } from "./tools/auth.js";
|
|
24
|
+
import { search_and_fetch_emails, create_email_draft } from "./tools/email.js";
|
|
25
|
+
function readFileBytesOrThrow(filePath: string): Buffer {
|
|
26
|
+
try {
|
|
27
|
+
return readFileSync(filePath);
|
|
28
|
+
} catch (err: any) {
|
|
29
|
+
if (err.code === "ENOENT") {
|
|
30
|
+
throw new Error(`File not found: ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
21
35
|
// --- Tool Description Constants (Parity with Python) ---
|
|
22
|
-
const READ_DOCX_COMMON_DESC =
|
|
23
|
-
|
|
36
|
+
const READ_DOCX_COMMON_DESC =
|
|
37
|
+
"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";
|
|
38
|
+
const READ_DOCX_TAIL =
|
|
39
|
+
"Modes:\n- 'full' (default): paginated body content. Use page=N to navigate.\n- 'outline': heading map only — 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.";
|
|
24
40
|
|
|
25
|
-
const PROCESS_BATCH_COMMON_DESC =
|
|
26
|
-
|
|
41
|
+
const PROCESS_BATCH_COMMON_DESC =
|
|
42
|
+
"Applies a batch of edits and review actions to a DOCX.\n\nAll changes evaluate against the ORIGINAL document state — 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";
|
|
43
|
+
const PROCESS_BATCH_OPERATIONS_DESC =
|
|
44
|
+
"Each item in `changes` must specify a `type`:\n1. 'modify': Search-and-replace. `target_text` must uniquely match — 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 — 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 — 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 — 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.";
|
|
27
45
|
|
|
28
|
-
const DIFF_DOCX_DESC =
|
|
46
|
+
const DIFF_DOCX_DESC =
|
|
47
|
+
"Compares two DOCX files and returns a unified diff of their text content. Useful for analyzing differences between versions before editing.";
|
|
29
48
|
|
|
30
49
|
// --- Server Setup ---
|
|
31
50
|
const server = new Server(
|
|
32
51
|
{
|
|
33
|
-
name:
|
|
34
|
-
version:
|
|
52
|
+
name: "adeu-redlining-service",
|
|
53
|
+
version: "1.0.0",
|
|
35
54
|
},
|
|
36
55
|
{
|
|
37
56
|
capabilities: {
|
|
38
57
|
tools: {},
|
|
39
58
|
},
|
|
40
|
-
}
|
|
59
|
+
},
|
|
41
60
|
);
|
|
42
61
|
|
|
43
62
|
// --- Tool Registration ---
|
|
@@ -45,246 +64,441 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
45
64
|
return {
|
|
46
65
|
tools: [
|
|
47
66
|
{
|
|
48
|
-
name:
|
|
67
|
+
name: "read_docx",
|
|
49
68
|
description: READ_DOCX_COMMON_DESC + READ_DOCX_TAIL,
|
|
50
69
|
inputSchema: {
|
|
51
|
-
type:
|
|
70
|
+
type: "object",
|
|
52
71
|
properties: {
|
|
53
|
-
file_path: {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
file_path: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Absolute path to the DOCX file.",
|
|
75
|
+
},
|
|
76
|
+
clean_view: {
|
|
77
|
+
type: "boolean",
|
|
78
|
+
description:
|
|
79
|
+
"If False (default), returns the 'Raw' text with inline CriticMarkup. If True, returns 'Accepted' text.",
|
|
80
|
+
default: false,
|
|
81
|
+
},
|
|
82
|
+
mode: {
|
|
83
|
+
type: "string",
|
|
84
|
+
enum: ["full", "outline", "appendix"],
|
|
85
|
+
description:
|
|
86
|
+
"'full' returns body content. 'outline' returns a structural heading map. 'appendix' returns defined terms.",
|
|
87
|
+
default: "full",
|
|
88
|
+
},
|
|
89
|
+
page: {
|
|
90
|
+
type: "number",
|
|
91
|
+
description:
|
|
92
|
+
"Page number (1-indexed) for mode='full'. Defaults to 1.",
|
|
93
|
+
default: 1,
|
|
94
|
+
},
|
|
95
|
+
outline_max_level: {
|
|
96
|
+
type: "number",
|
|
97
|
+
description: "For mode='outline' only: cap on heading depth.",
|
|
98
|
+
default: 2,
|
|
99
|
+
},
|
|
100
|
+
outline_verbose: {
|
|
101
|
+
type: "boolean",
|
|
102
|
+
description: "For mode='outline' only: includes metadata.",
|
|
103
|
+
default: false,
|
|
104
|
+
},
|
|
59
105
|
},
|
|
60
|
-
required: [
|
|
61
|
-
}
|
|
106
|
+
required: ["file_path"],
|
|
107
|
+
},
|
|
62
108
|
},
|
|
63
109
|
{
|
|
64
|
-
name:
|
|
110
|
+
name: "process_document_batch",
|
|
65
111
|
description: PROCESS_BATCH_COMMON_DESC + PROCESS_BATCH_OPERATIONS_DESC,
|
|
66
112
|
inputSchema: {
|
|
67
|
-
type:
|
|
113
|
+
type: "object",
|
|
68
114
|
properties: {
|
|
69
|
-
original_docx_path: {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
115
|
+
original_docx_path: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Absolute path to the source file.",
|
|
118
|
+
},
|
|
119
|
+
author_name: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description:
|
|
122
|
+
"Name to appear in Track Changes (e.g., 'Reviewer AI').",
|
|
123
|
+
},
|
|
124
|
+
changes: {
|
|
125
|
+
type: "array",
|
|
126
|
+
description:
|
|
127
|
+
"List of changes to apply. Each change must specify 'type'.",
|
|
128
|
+
items: { type: "object" },
|
|
129
|
+
},
|
|
130
|
+
output_path: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Optional output path.",
|
|
75
133
|
},
|
|
76
|
-
output_path: { type: 'string', description: 'Optional output path.' }
|
|
77
134
|
},
|
|
78
|
-
required: [
|
|
79
|
-
}
|
|
135
|
+
required: ["original_docx_path", "author_name", "changes"],
|
|
136
|
+
},
|
|
80
137
|
},
|
|
81
138
|
{
|
|
82
|
-
name:
|
|
83
|
-
description:
|
|
139
|
+
name: "accept_all_changes",
|
|
140
|
+
description:
|
|
141
|
+
"Accepts all tracked changes and removes all comments in a single operation, producing a finalized clean document. Use this when a document review is entirely complete and you want to clear all redlines.",
|
|
84
142
|
inputSchema: {
|
|
85
|
-
type:
|
|
143
|
+
type: "object",
|
|
86
144
|
properties: {
|
|
87
|
-
docx_path: {
|
|
88
|
-
|
|
145
|
+
docx_path: {
|
|
146
|
+
type: "string",
|
|
147
|
+
description: "Absolute path to the DOCX file.",
|
|
148
|
+
},
|
|
149
|
+
output_path: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "Optional output path.",
|
|
152
|
+
},
|
|
89
153
|
},
|
|
90
|
-
required: [
|
|
91
|
-
}
|
|
154
|
+
required: ["docx_path"],
|
|
155
|
+
},
|
|
92
156
|
},
|
|
93
157
|
{
|
|
94
|
-
name:
|
|
158
|
+
name: "diff_docx_files",
|
|
95
159
|
description: DIFF_DOCX_DESC,
|
|
96
160
|
inputSchema: {
|
|
97
|
-
type:
|
|
161
|
+
type: "object",
|
|
98
162
|
properties: {
|
|
99
|
-
original_path: {
|
|
100
|
-
|
|
163
|
+
original_path: {
|
|
164
|
+
type: "string",
|
|
165
|
+
description: "Absolute path to the baseline DOCX file.",
|
|
166
|
+
},
|
|
167
|
+
modified_path: {
|
|
168
|
+
type: "string",
|
|
169
|
+
description: "Absolute path to the modified DOCX file.",
|
|
170
|
+
},
|
|
171
|
+
compare_clean: {
|
|
172
|
+
type: "boolean",
|
|
173
|
+
description:
|
|
174
|
+
"If True, compares 'Accepted' state. If False, compares raw text.",
|
|
175
|
+
default: true,
|
|
176
|
+
},
|
|
101
177
|
},
|
|
102
|
-
required: [
|
|
103
|
-
}
|
|
178
|
+
required: ["original_path", "modified_path"],
|
|
179
|
+
},
|
|
104
180
|
},
|
|
105
181
|
{
|
|
106
|
-
name:
|
|
107
|
-
description:
|
|
182
|
+
name: "finalize_document",
|
|
183
|
+
description:
|
|
184
|
+
"Prepares a document for external distribution or e-signature. This tool combines metadata sanitization, document locking (protection), and markup resolution into a single step. NOTE: PDF export and AES encryption are disabled in this environment.",
|
|
108
185
|
inputSchema: {
|
|
109
|
-
type:
|
|
186
|
+
type: "object",
|
|
110
187
|
properties: {
|
|
111
|
-
file_path: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
188
|
+
file_path: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Absolute path to the DOCX file.",
|
|
191
|
+
},
|
|
192
|
+
output_path: {
|
|
193
|
+
type: "string",
|
|
194
|
+
description: "Optional output path.",
|
|
195
|
+
},
|
|
196
|
+
sanitize_mode: {
|
|
197
|
+
type: "string",
|
|
198
|
+
enum: ["full", "keep-markup"],
|
|
199
|
+
description:
|
|
200
|
+
"full removes all markup, keep-markup redacts metadata but keeps comments/redlines.",
|
|
201
|
+
},
|
|
202
|
+
accept_all: {
|
|
203
|
+
type: "boolean",
|
|
204
|
+
description:
|
|
205
|
+
"If true, auto-accepts all unresolved track changes before finalizing.",
|
|
206
|
+
},
|
|
207
|
+
protection_mode: {
|
|
208
|
+
type: "string",
|
|
209
|
+
enum: ["read_only", "encrypt"],
|
|
210
|
+
description:
|
|
211
|
+
"Native OOXML document locking. encrypt falls back to read_only in this environment.",
|
|
212
|
+
},
|
|
213
|
+
password: {
|
|
214
|
+
type: "string",
|
|
215
|
+
description: "Ignored in this environment.",
|
|
216
|
+
},
|
|
217
|
+
author: {
|
|
218
|
+
type: "string",
|
|
219
|
+
description:
|
|
220
|
+
"Replace all remaining markup authorship with this name.",
|
|
221
|
+
},
|
|
222
|
+
export_pdf: {
|
|
223
|
+
type: "boolean",
|
|
224
|
+
description: "Ignored in this environment.",
|
|
225
|
+
},
|
|
119
226
|
},
|
|
120
|
-
required: [
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
227
|
+
required: ["file_path"],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "login_to_adeu_cloud",
|
|
232
|
+
description:
|
|
233
|
+
"Logs the user into the Adeu Cloud backend. Securely opens a browser window for authentication.",
|
|
234
|
+
inputSchema: { type: "object", properties: {} },
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "logout_of_adeu_cloud",
|
|
238
|
+
description:
|
|
239
|
+
"Logs out of the Adeu Cloud backend by clearing the local API key.",
|
|
240
|
+
inputSchema: { type: "object", properties: {} },
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "search_and_fetch_emails",
|
|
244
|
+
description:
|
|
245
|
+
"Searches the user's live email inbox. By default, searches only the Inbox folder. Returns a list of lightweight previews. Call again with `email_id` to fetch the full body and download attachments.",
|
|
246
|
+
inputSchema: {
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: {
|
|
249
|
+
sender: { type: "string" },
|
|
250
|
+
subject: { type: "string" },
|
|
251
|
+
has_attachments: { type: "boolean" },
|
|
252
|
+
attachment_name: { type: "string" },
|
|
253
|
+
is_unread: { type: "boolean" },
|
|
254
|
+
days_ago: { type: "number" },
|
|
255
|
+
folder: { type: "string", enum: ["inbox", "sent", "all"] },
|
|
256
|
+
limit: { type: "number", default: 10 },
|
|
257
|
+
offset: { type: "number", default: 0 },
|
|
258
|
+
email_id: { type: "string" },
|
|
259
|
+
working_directory: { type: "string" },
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "create_email_draft",
|
|
265
|
+
description:
|
|
266
|
+
"Creates an email draft in the user's native draft box. Provide `reply_to_email_id` to reply, or `subject` and `to_recipients` for a new email.",
|
|
267
|
+
inputSchema: {
|
|
268
|
+
type: "object",
|
|
269
|
+
properties: {
|
|
270
|
+
body_markdown: { type: "string" },
|
|
271
|
+
reply_to_email_id: { type: "string" },
|
|
272
|
+
subject: { type: "string" },
|
|
273
|
+
to_recipients: { type: "array", items: { type: "string" } },
|
|
274
|
+
attachment_paths: { type: "array", items: { type: "string" } },
|
|
275
|
+
},
|
|
276
|
+
required: ["body_markdown"],
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
],
|
|
124
280
|
};
|
|
125
281
|
});
|
|
126
282
|
|
|
127
283
|
// --- Tool Execution ---
|
|
128
|
-
server.setRequestHandler(
|
|
129
|
-
|
|
284
|
+
server.setRequestHandler(
|
|
285
|
+
CallToolRequestSchema,
|
|
286
|
+
async (request): Promise<any> => {
|
|
287
|
+
const { name, arguments: args } = request.params;
|
|
130
288
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (mode === 'outline') {
|
|
144
|
-
const doc = await DocumentObject.load(buf);
|
|
145
|
-
return build_outline_response(doc, text, filePath, outline_max_level, outline_verbose);
|
|
146
|
-
}
|
|
147
|
-
if (mode === 'appendix') {
|
|
148
|
-
return build_appendix_response(text, page, filePath);
|
|
149
|
-
}
|
|
150
|
-
return build_paginated_response(text, page, filePath);
|
|
151
|
-
}
|
|
289
|
+
try {
|
|
290
|
+
if (name === "read_docx") {
|
|
291
|
+
const filePath = args?.file_path as string;
|
|
292
|
+
const cleanView = (args?.clean_view as boolean) ?? false;
|
|
293
|
+
const mode = (args?.mode as string) ?? "full";
|
|
294
|
+
const page = (args?.page as number) ?? 1;
|
|
295
|
+
const outline_max_level = (args?.outline_max_level as number) ?? 2;
|
|
296
|
+
const outline_verbose = (args?.outline_verbose as boolean) ?? false;
|
|
297
|
+
|
|
298
|
+
const buf = readFileBytesOrThrow(filePath);
|
|
299
|
+
const text = await extractTextFromBuffer(buf, cleanView);
|
|
152
300
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
301
|
+
if (mode === "outline") {
|
|
302
|
+
const doc = await DocumentObject.load(buf);
|
|
303
|
+
return build_outline_response(
|
|
304
|
+
doc,
|
|
305
|
+
text,
|
|
306
|
+
filePath,
|
|
307
|
+
outline_max_level,
|
|
308
|
+
outline_verbose,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
if (mode === "appendix") {
|
|
312
|
+
return build_appendix_response(text, page, filePath);
|
|
313
|
+
}
|
|
314
|
+
return build_paginated_response(text, page, filePath);
|
|
164
315
|
}
|
|
316
|
+
if (name === "process_document_batch") {
|
|
317
|
+
const origPath = args?.original_docx_path as string;
|
|
318
|
+
const authorName = args?.author_name as string;
|
|
319
|
+
const changes = args?.changes as any[];
|
|
320
|
+
let outPath = args?.output_path as string;
|
|
165
321
|
|
|
166
|
-
|
|
167
|
-
const doc = await DocumentObject.load(buf);
|
|
168
|
-
const engine = new RedlineEngine(doc, authorName);
|
|
169
|
-
|
|
170
|
-
let stats;
|
|
171
|
-
try {
|
|
172
|
-
stats = engine.process_batch(changes);
|
|
173
|
-
} catch (e) {
|
|
174
|
-
if (e instanceof BatchValidationError) {
|
|
322
|
+
if (!authorName || !authorName.trim()) {
|
|
175
323
|
return {
|
|
176
|
-
content: [
|
|
177
|
-
|
|
324
|
+
content: [
|
|
325
|
+
{ type: "text", text: "Error: author_name cannot be empty." },
|
|
326
|
+
],
|
|
178
327
|
};
|
|
179
328
|
}
|
|
180
|
-
throw e;
|
|
181
|
-
}
|
|
182
329
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
330
|
+
if (!changes || changes.length === 0) {
|
|
331
|
+
return {
|
|
332
|
+
content: [{ type: "text", text: "Error: No changes provided." }],
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (!outPath) {
|
|
336
|
+
const ext = extname(origPath);
|
|
337
|
+
const base = basename(origPath, ext);
|
|
338
|
+
const dir = dirname(origPath);
|
|
339
|
+
outPath = resolve(dir, `${base}_processed${ext}`);
|
|
340
|
+
}
|
|
187
341
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
342
|
+
const buf = readFileBytesOrThrow(origPath);
|
|
343
|
+
const doc = await DocumentObject.load(buf);
|
|
344
|
+
const engine = new RedlineEngine(doc, authorName);
|
|
192
345
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
346
|
+
let stats;
|
|
347
|
+
try {
|
|
348
|
+
stats = engine.process_batch(changes);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
if (e instanceof BatchValidationError) {
|
|
351
|
+
return {
|
|
352
|
+
content: [
|
|
353
|
+
{
|
|
354
|
+
type: "text",
|
|
355
|
+
text: `Batch rejected. Some edits failed validation:\n\n${(e as BatchValidationError).errors.join("\n\n")}`,
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
isError: true,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
throw e;
|
|
362
|
+
}
|
|
197
363
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
364
|
+
const outBuf = await doc.save();
|
|
365
|
+
// Using dynamic import of fs/promises or just sync write
|
|
366
|
+
const fs = await import("node:fs");
|
|
367
|
+
fs.writeFileSync(outPath, outBuf);
|
|
201
368
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
369
|
+
let res = `Batch complete. Saved to: ${outPath}\nActions: ${stats.actions_applied} applied, ${stats.actions_skipped} skipped.\nEdits: ${stats.edits_applied} applied, ${stats.edits_skipped} skipped.`;
|
|
370
|
+
if (stats.skipped_details?.length > 0) {
|
|
371
|
+
res += `\n\nSkipped Details:\n${stats.skipped_details.join("\n")}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
content: [{ type: "text", text: res }],
|
|
376
|
+
};
|
|
207
377
|
}
|
|
208
378
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// We implement the public facing accept_all wrapper from python
|
|
214
|
-
engine.accept_all_revisions();
|
|
215
|
-
|
|
216
|
-
const outBuf = await doc.save();
|
|
217
|
-
const fs = await import('node:fs');
|
|
218
|
-
fs.writeFileSync(outPath, outBuf);
|
|
379
|
+
if (name === "accept_all_changes") {
|
|
380
|
+
const docxPath = args?.docx_path as string;
|
|
381
|
+
let outPath = args?.output_path as string;
|
|
219
382
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
383
|
+
if (!outPath) {
|
|
384
|
+
const ext = extname(docxPath);
|
|
385
|
+
const base = basename(docxPath, ext);
|
|
386
|
+
const dir = dirname(docxPath);
|
|
387
|
+
outPath = resolve(dir, `${base}_clean${ext}`);
|
|
388
|
+
}
|
|
224
389
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
390
|
+
const buf = readFileBytesOrThrow(docxPath);
|
|
391
|
+
const doc = await DocumentObject.load(buf);
|
|
392
|
+
const engine = new RedlineEngine(doc);
|
|
228
393
|
|
|
229
|
-
|
|
230
|
-
|
|
394
|
+
// We implement the public facing accept_all wrapper from python
|
|
395
|
+
engine.accept_all_revisions();
|
|
231
396
|
|
|
232
|
-
|
|
233
|
-
|
|
397
|
+
const outBuf = await doc.save();
|
|
398
|
+
const fs = await import("node:fs");
|
|
399
|
+
fs.writeFileSync(outPath, outBuf);
|
|
234
400
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
401
|
+
return {
|
|
402
|
+
content: [
|
|
403
|
+
{
|
|
404
|
+
type: "text",
|
|
405
|
+
text: `Accepted all changes. Saved to: ${outPath}`,
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (name === "diff_docx_files") {
|
|
411
|
+
const origPath = args?.original_path as string;
|
|
412
|
+
const modPath = args?.modified_path as string;
|
|
413
|
+
const compareClean = (args?.compare_clean as boolean) ?? true;
|
|
414
|
+
const origBuf = readFileBytesOrThrow(origPath);
|
|
415
|
+
const modBuf = readFileBytesOrThrow(modPath);
|
|
416
|
+
|
|
417
|
+
// Pass compareClean flag into extraction
|
|
418
|
+
const origText = await extractTextFromBuffer(origBuf, compareClean);
|
|
419
|
+
const modText = await extractTextFromBuffer(modBuf, compareClean);
|
|
241
420
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
421
|
+
const diff = create_word_patch_diff(
|
|
422
|
+
origText,
|
|
423
|
+
modText,
|
|
424
|
+
basename(origPath),
|
|
425
|
+
basename(modPath),
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
content: [{ type: "text", text: diff || "No differences found." }],
|
|
430
|
+
};
|
|
251
431
|
}
|
|
252
432
|
|
|
253
|
-
|
|
254
|
-
|
|
433
|
+
if (name === "finalize_document") {
|
|
434
|
+
const filePath = args?.file_path as string;
|
|
435
|
+
let outPath = args?.output_path as string;
|
|
436
|
+
|
|
437
|
+
if (!outPath) {
|
|
438
|
+
const ext = extname(filePath);
|
|
439
|
+
const base = basename(filePath, ext);
|
|
440
|
+
const dir = dirname(filePath);
|
|
441
|
+
outPath = resolve(dir, `${base}_final${ext}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const buf = readFileBytesOrThrow(filePath);
|
|
445
|
+
const doc = await DocumentObject.load(buf);
|
|
255
446
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
447
|
+
const result = await finalize_document(doc, {
|
|
448
|
+
filename: basename(filePath),
|
|
449
|
+
sanitize_mode: (args?.sanitize_mode as any) || "full",
|
|
450
|
+
accept_all: args?.accept_all as boolean,
|
|
451
|
+
protection_mode: args?.protection_mode as any,
|
|
452
|
+
author: args?.author as string,
|
|
453
|
+
export_pdf: args?.export_pdf as boolean,
|
|
454
|
+
});
|
|
264
455
|
|
|
265
|
-
|
|
266
|
-
|
|
456
|
+
const fs = await import("node:fs");
|
|
457
|
+
fs.writeFileSync(outPath, result.outBuffer!);
|
|
267
458
|
|
|
459
|
+
return {
|
|
460
|
+
content: [
|
|
461
|
+
{
|
|
462
|
+
type: "text",
|
|
463
|
+
text: `Saved to: ${outPath}\n\n${result.reportText}`,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (name === "login_to_adeu_cloud") {
|
|
469
|
+
return await login_to_adeu_cloud();
|
|
470
|
+
}
|
|
471
|
+
if (name === "logout_of_adeu_cloud") {
|
|
472
|
+
return await logout_of_adeu_cloud();
|
|
473
|
+
}
|
|
474
|
+
if (name === "search_and_fetch_emails") {
|
|
475
|
+
return await search_and_fetch_emails(args || {});
|
|
476
|
+
}
|
|
477
|
+
if (name === "create_email_draft") {
|
|
478
|
+
return await create_email_draft(args || {});
|
|
479
|
+
}
|
|
480
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
481
|
+
} catch (error: any) {
|
|
268
482
|
return {
|
|
269
|
-
content: [
|
|
483
|
+
content: [
|
|
484
|
+
{
|
|
485
|
+
type: "text",
|
|
486
|
+
text: `Error executing tool ${name}: ${error.message}`,
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
isError: true,
|
|
270
490
|
};
|
|
271
491
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
} catch (error: any) {
|
|
276
|
-
return {
|
|
277
|
-
content: [{ type: 'text', text: `Error executing tool ${name}: ${error.message}` }],
|
|
278
|
-
isError: true,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
});
|
|
492
|
+
},
|
|
493
|
+
);
|
|
282
494
|
|
|
283
495
|
// --- Startup ---
|
|
284
496
|
async function main() {
|
|
285
497
|
const transport = new StdioServerTransport();
|
|
286
498
|
await server.connect(transport);
|
|
287
|
-
console.error(
|
|
499
|
+
console.error(
|
|
500
|
+
`Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio`,
|
|
501
|
+
);
|
|
288
502
|
}
|
|
289
503
|
|
|
290
|
-
main().catch(console.error);
|
|
504
|
+
main().catch(console.error);
|