@adeu/mcp-server 1.7.5 → 1.9.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/dist/assets/adeu.svg +3 -0
- package/dist/assets/logo.png +0 -0
- package/dist/assets/marked.min.js +69 -0
- package/dist/index.js +523 -395
- package/dist/index.js.map +1 -1
- package/dist/templates/email_ui.html +667 -0
- package/dist/templates/markdown_ui.html +745 -0
- package/package.json +4 -2
- package/src/assets/adeu.svg +3 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/marked.min.js +69 -0
- package/src/index.ts +510 -407
- package/src/mcp.cloud.test.ts +13 -0
- package/src/response-builders.ts +111 -50
- package/src/templates/email_ui.html +667 -0
- package/src/templates/markdown_ui.html +745 -0
- package/src/tools/email.ts +61 -2
- package/tsup.config.ts +35 -11
package/dist/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import {
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
7
|
+
import { basename as basename2, resolve as resolve2, extname, dirname, join as join3 } from "path";
|
|
8
|
+
import { z } from "zod";
|
|
6
9
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import
|
|
10
|
+
registerAppTool,
|
|
11
|
+
registerAppResource,
|
|
12
|
+
RESOURCE_MIME_TYPE
|
|
13
|
+
} from "@modelcontextprotocol/ext-apps/server";
|
|
14
|
+
import fs from "fs";
|
|
12
15
|
import {
|
|
13
16
|
identifyEngine,
|
|
14
17
|
extractTextFromBuffer,
|
|
@@ -20,7 +23,7 @@ import {
|
|
|
20
23
|
} from "@adeu/core";
|
|
21
24
|
|
|
22
25
|
// src/response-builders.ts
|
|
23
|
-
import { resolve } from "path";
|
|
26
|
+
import { resolve, basename } from "path";
|
|
24
27
|
import {
|
|
25
28
|
paginate,
|
|
26
29
|
split_structural_appendix,
|
|
@@ -66,7 +69,8 @@ Document has ${nodes.length} headings, all at deeper levels. Call read_docx with
|
|
|
66
69
|
if (verbose) {
|
|
67
70
|
const meta_parts = [`p${node.page}`, node.style];
|
|
68
71
|
if (node.has_table) meta_parts.push("has table");
|
|
69
|
-
if (node.footnote_ids && node.footnote_ids.length > 0)
|
|
72
|
+
if (node.footnote_ids && node.footnote_ids.length > 0)
|
|
73
|
+
meta_parts.push("fn:" + node.footnote_ids.join(","));
|
|
70
74
|
lines.push(`${prefix} ${node.text} (${meta_parts.join(", ")})`);
|
|
71
75
|
} else {
|
|
72
76
|
lines.push(`${prefix} ${node.text} (p${node.page})`);
|
|
@@ -79,18 +83,30 @@ function build_paginated_response(text, page, file_path) {
|
|
|
79
83
|
const has_appendix = Boolean(appendix.trim());
|
|
80
84
|
const result = paginate(body, "");
|
|
81
85
|
if (page < 1 || page > result.total_pages) {
|
|
82
|
-
throw new Error(
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Page ${page} out of range (doc has ${result.total_pages} pages).`
|
|
88
|
+
);
|
|
83
89
|
}
|
|
84
90
|
const selected = result.pages[page - 1];
|
|
85
91
|
const banner = _build_page_banner(selected.page, selected.total_pages);
|
|
86
|
-
const footer = _build_page_footer(
|
|
92
|
+
const footer = _build_page_footer(
|
|
93
|
+
selected.page,
|
|
94
|
+
selected.total_pages,
|
|
95
|
+
selected.has_next
|
|
96
|
+
);
|
|
87
97
|
const appendix_pointer = _build_appendix_pointer(has_appendix);
|
|
88
98
|
const ui_markdown = banner + selected.page_content + footer + appendix_pointer;
|
|
89
99
|
const llm_content = `> **File Path:** \`${resolve(file_path)}\`
|
|
90
100
|
|
|
91
101
|
${ui_markdown}`;
|
|
92
102
|
return {
|
|
93
|
-
content: [{ type: "text", text: llm_content }]
|
|
103
|
+
content: [{ type: "text", text: llm_content }],
|
|
104
|
+
// Include structuredContent for the UI to render the markdown
|
|
105
|
+
structuredContent: {
|
|
106
|
+
markdown: ui_markdown,
|
|
107
|
+
file_path: resolve(file_path),
|
|
108
|
+
title: basename(file_path)
|
|
109
|
+
}
|
|
94
110
|
};
|
|
95
111
|
}
|
|
96
112
|
function build_outline_response(doc, projected_text, file_path, outline_max_level = 2, outline_verbose = false) {
|
|
@@ -102,8 +118,14 @@ function build_outline_response(doc, projected_text, file_path, outline_max_leve
|
|
|
102
118
|
pagination_result.body_pages,
|
|
103
119
|
pagination_result.body_page_offsets
|
|
104
120
|
);
|
|
105
|
-
const rendered = render_outline_tree(
|
|
106
|
-
|
|
121
|
+
const rendered = render_outline_tree(
|
|
122
|
+
nodes,
|
|
123
|
+
outline_max_level,
|
|
124
|
+
outline_verbose
|
|
125
|
+
);
|
|
126
|
+
const visible_count = nodes.filter(
|
|
127
|
+
(n) => n.level <= outline_max_level
|
|
128
|
+
).length;
|
|
107
129
|
const deeper_count = nodes.length - visible_count;
|
|
108
130
|
const deeper_hint = deeper_count > 0 ? ` (${deeper_count} more at deeper levels, raise outline_max_level to see)` : "";
|
|
109
131
|
const header = `> **Outline view** \u2014 showing ${visible_count} of ${nodes.length} headings (L1-L${outline_max_level}${deeper_hint}) across ${pagination_result.total_pages} page(s). Call \`read_docx\` with \`mode='full'\` and \`page=N\` to read a section.
|
|
@@ -116,7 +138,12 @@ function build_outline_response(doc, projected_text, file_path, outline_max_leve
|
|
|
116
138
|
|
|
117
139
|
${ui_markdown}`;
|
|
118
140
|
return {
|
|
119
|
-
content: [{ type: "text", text: llm_content }]
|
|
141
|
+
content: [{ type: "text", text: llm_content }],
|
|
142
|
+
structuredContent: {
|
|
143
|
+
markdown: ui_markdown,
|
|
144
|
+
file_path: resolve(file_path),
|
|
145
|
+
title: `Outline: ${basename(file_path)}`
|
|
146
|
+
}
|
|
120
147
|
};
|
|
121
148
|
}
|
|
122
149
|
function build_appendix_response(text, page, file_path) {
|
|
@@ -127,12 +154,19 @@ function build_appendix_response(text, page, file_path) {
|
|
|
127
154
|
|
|
128
155
|
${ui_markdown2}`;
|
|
129
156
|
return {
|
|
130
|
-
content: [{ type: "text", text: llm_content2 }]
|
|
157
|
+
content: [{ type: "text", text: llm_content2 }],
|
|
158
|
+
structuredContent: {
|
|
159
|
+
markdown: ui_markdown2,
|
|
160
|
+
file_path: resolve(file_path),
|
|
161
|
+
title: `Appendix: ${basename(file_path)}`
|
|
162
|
+
}
|
|
131
163
|
};
|
|
132
164
|
}
|
|
133
165
|
const result = paginate(appendix, "");
|
|
134
166
|
if (page < 1 || page > result.total_pages) {
|
|
135
|
-
throw new Error(
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Appendix page ${page} out of range (appendix has ${result.total_pages} pages).`
|
|
169
|
+
);
|
|
136
170
|
}
|
|
137
171
|
const selected = result.pages[page - 1];
|
|
138
172
|
let banner = "";
|
|
@@ -156,7 +190,12 @@ ${ui_markdown2}`;
|
|
|
156
190
|
|
|
157
191
|
${ui_markdown}`;
|
|
158
192
|
return {
|
|
159
|
-
content: [{ type: "text", text: llm_content }]
|
|
193
|
+
content: [{ type: "text", text: llm_content }],
|
|
194
|
+
structuredContent: {
|
|
195
|
+
markdown: ui_markdown,
|
|
196
|
+
file_path: resolve(file_path),
|
|
197
|
+
title: `Appendix: ${basename(file_path)}`
|
|
198
|
+
}
|
|
160
199
|
};
|
|
161
200
|
}
|
|
162
201
|
|
|
@@ -177,6 +216,8 @@ import {
|
|
|
177
216
|
// src/shared.ts
|
|
178
217
|
var FRONTEND_URL = process.env.ADEU_FRONTEND_URL || "https://app.adeu.ai";
|
|
179
218
|
var BACKEND_URL = process.env.ADEU_BACKEND_URL || "https://app.adeu.ai";
|
|
219
|
+
var MARKDOWN_UI_URI = "ui://adeu/markdown-ui";
|
|
220
|
+
var EMAIL_UI_URI = "ui://adeu/email-ui";
|
|
180
221
|
|
|
181
222
|
// src/desktop-auth.ts
|
|
182
223
|
var ADEU_DIR = join(homedir(), ".adeu");
|
|
@@ -408,7 +449,8 @@ async function search_and_fetch_emails(args) {
|
|
|
408
449
|
days_ago: args.days_ago,
|
|
409
450
|
folder: args.folder,
|
|
410
451
|
limit: args.limit ?? 10,
|
|
411
|
-
offset: args.offset ?? 0
|
|
452
|
+
offset: args.offset ?? 0,
|
|
453
|
+
mailbox_address: args.mailbox_address
|
|
412
454
|
};
|
|
413
455
|
Object.keys(payload).forEach(
|
|
414
456
|
(k) => payload[k] === void 0 && delete payload[k]
|
|
@@ -462,7 +504,10 @@ async function search_and_fetch_emails(args) {
|
|
|
462
504
|
lines.push(
|
|
463
505
|
"\u26A0\uFE0F **ACTION REQUIRED**: To read the full body of an email and download its attachments, call this tool again and provide the exact `email_id`."
|
|
464
506
|
);
|
|
465
|
-
return {
|
|
507
|
+
return {
|
|
508
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
509
|
+
structuredContent: data
|
|
510
|
+
};
|
|
466
511
|
}
|
|
467
512
|
if (data.type === "full_email") {
|
|
468
513
|
const full = data.full_email || {};
|
|
@@ -535,7 +580,10 @@ ${removeNestedQuotes(stripTags(histMsg.body_html || ""))}
|
|
|
535
580
|
);
|
|
536
581
|
}
|
|
537
582
|
}
|
|
538
|
-
return {
|
|
583
|
+
return {
|
|
584
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
585
|
+
structuredContent: data
|
|
586
|
+
};
|
|
539
587
|
}
|
|
540
588
|
return {
|
|
541
589
|
isError: true,
|
|
@@ -558,6 +606,9 @@ async function create_email_draft(args) {
|
|
|
558
606
|
);
|
|
559
607
|
}
|
|
560
608
|
if (args.subject) formData.append("subject", args.subject);
|
|
609
|
+
if (args.mailbox_address) {
|
|
610
|
+
formData.append("mailbox_address", args.mailbox_address);
|
|
611
|
+
}
|
|
561
612
|
if (args.to_recipients) {
|
|
562
613
|
const recips = typeof args.to_recipients === "string" ? JSON.parse(args.to_recipients) : args.to_recipients;
|
|
563
614
|
formData.append("to_recipients", JSON.stringify(recips));
|
|
@@ -593,6 +644,52 @@ async function create_email_draft(args) {
|
|
|
593
644
|
]
|
|
594
645
|
};
|
|
595
646
|
}
|
|
647
|
+
async function list_available_mailboxes() {
|
|
648
|
+
const apiKey = await getCloudAuthToken();
|
|
649
|
+
const res = await fetch(`${BACKEND_URL}/api/v1/users/me/shared-mailboxes`, {
|
|
650
|
+
method: "GET",
|
|
651
|
+
headers: {
|
|
652
|
+
Authorization: `Bearer ${apiKey}`,
|
|
653
|
+
Accept: "application/json"
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
if (res.status === 401) {
|
|
657
|
+
DesktopAuthManager.clearApiKey();
|
|
658
|
+
throw new Error(
|
|
659
|
+
"Authentication expired. Please call `login_to_adeu_cloud` to re-authenticate."
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
if (!res.ok) {
|
|
663
|
+
throw new Error(`Failed to list available mailboxes: ${await res.text()}`);
|
|
664
|
+
}
|
|
665
|
+
const mailboxes = await res.json();
|
|
666
|
+
if (!mailboxes.length) {
|
|
667
|
+
return {
|
|
668
|
+
content: [
|
|
669
|
+
{
|
|
670
|
+
type: "text",
|
|
671
|
+
text: "No configured mailboxes found for your profile."
|
|
672
|
+
}
|
|
673
|
+
]
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
const lines = [
|
|
677
|
+
"### Connected Mailboxes",
|
|
678
|
+
"Below is the list of connected mailboxes you have access to. Use the `email_address` as the `mailbox_address` parameter in other tools to query or draft from a specific mailbox:",
|
|
679
|
+
""
|
|
680
|
+
];
|
|
681
|
+
for (const box of mailboxes) {
|
|
682
|
+
lines.push(
|
|
683
|
+
`- **${box.display_name || "Personal Mailbox"}**
|
|
684
|
+
- **Email Address**: \`${box.email_address}\`
|
|
685
|
+
- **Auto-Processing**: ${box.auto_process_enabled ? "Enabled" : "Disabled"}
|
|
686
|
+
- **Write-Back Mode**: \`${box.write_back_preference}\``
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
691
|
+
};
|
|
692
|
+
}
|
|
596
693
|
|
|
597
694
|
// src/index.ts
|
|
598
695
|
function readFileBytesOrThrow(filePath) {
|
|
@@ -605,413 +702,444 @@ function readFileBytesOrThrow(filePath) {
|
|
|
605
702
|
throw err;
|
|
606
703
|
}
|
|
607
704
|
}
|
|
705
|
+
var DIST_DIR = import.meta.dirname;
|
|
706
|
+
function getAssetContent(folder, filename, fallbackMessage) {
|
|
707
|
+
const filePath = join3(DIST_DIR, folder, filename);
|
|
708
|
+
if (existsSync3(filePath)) {
|
|
709
|
+
return readFileSync3(filePath, "utf-8");
|
|
710
|
+
}
|
|
711
|
+
return fallbackMessage;
|
|
712
|
+
}
|
|
608
713
|
var READ_DOCX_COMMON_DESC = "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";
|
|
609
714
|
var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use page=N to navigate.\n- 'outline': heading map only \u2014 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.";
|
|
610
715
|
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";
|
|
611
716
|
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.";
|
|
612
717
|
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.";
|
|
613
|
-
var server = new
|
|
718
|
+
var server = new McpServer({
|
|
719
|
+
name: "adeu-redlining-service",
|
|
720
|
+
version: "1.0.0"
|
|
721
|
+
});
|
|
722
|
+
var UI_CSP = {
|
|
723
|
+
connectDomains: ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
|
|
724
|
+
resourceDomains: [
|
|
725
|
+
"https://fonts.googleapis.com",
|
|
726
|
+
"https://fonts.gstatic.com"
|
|
727
|
+
]
|
|
728
|
+
};
|
|
729
|
+
registerAppResource(
|
|
730
|
+
server,
|
|
731
|
+
MARKDOWN_UI_URI,
|
|
732
|
+
MARKDOWN_UI_URI,
|
|
733
|
+
{ mimeType: RESOURCE_MIME_TYPE, description: "Adeu Markdown Viewer UI" },
|
|
734
|
+
async () => {
|
|
735
|
+
let html = getAssetContent(
|
|
736
|
+
"templates",
|
|
737
|
+
"markdown_ui.html",
|
|
738
|
+
"<html><body>UI Template Not Found</body></html>"
|
|
739
|
+
);
|
|
740
|
+
const markedJs = getAssetContent(
|
|
741
|
+
"assets",
|
|
742
|
+
"marked.min.js",
|
|
743
|
+
"window.__MARKED_ERROR = 'marked.min.js not found';"
|
|
744
|
+
);
|
|
745
|
+
const svg = getAssetContent("assets", "adeu.svg", "");
|
|
746
|
+
html = html.replace("[[marked_js_code | safe]]", markedJs).replace("[[ adeu_svg_code ]]", svg);
|
|
747
|
+
return {
|
|
748
|
+
contents: [
|
|
749
|
+
{
|
|
750
|
+
uri: MARKDOWN_UI_URI,
|
|
751
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
752
|
+
text: html,
|
|
753
|
+
_meta: { ui: { csp: UI_CSP } }
|
|
754
|
+
}
|
|
755
|
+
]
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
registerAppResource(
|
|
760
|
+
server,
|
|
761
|
+
EMAIL_UI_URI,
|
|
762
|
+
EMAIL_UI_URI,
|
|
763
|
+
{ mimeType: RESOURCE_MIME_TYPE, description: "Adeu Email Viewer UI" },
|
|
764
|
+
async () => {
|
|
765
|
+
let html = getAssetContent(
|
|
766
|
+
"templates",
|
|
767
|
+
"email_ui.html",
|
|
768
|
+
"<html><body>UI Template Not Found</body></html>"
|
|
769
|
+
);
|
|
770
|
+
const svg = getAssetContent("assets", "adeu.svg", "");
|
|
771
|
+
html = html.replace("[[ adeu_svg_code ]]", svg);
|
|
772
|
+
return {
|
|
773
|
+
contents: [
|
|
774
|
+
{
|
|
775
|
+
uri: EMAIL_UI_URI,
|
|
776
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
777
|
+
text: html,
|
|
778
|
+
_meta: { ui: { csp: UI_CSP } }
|
|
779
|
+
}
|
|
780
|
+
]
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
);
|
|
784
|
+
registerAppTool(
|
|
785
|
+
server,
|
|
786
|
+
"read_docx",
|
|
614
787
|
{
|
|
615
|
-
|
|
616
|
-
|
|
788
|
+
title: "Read DOCX",
|
|
789
|
+
description: READ_DOCX_COMMON_DESC + READ_DOCX_TAIL,
|
|
790
|
+
inputSchema: z.object({
|
|
791
|
+
file_path: z.string().describe("Absolute path to the DOCX file."),
|
|
792
|
+
clean_view: z.boolean().default(false).describe(
|
|
793
|
+
"If False (default), returns the 'Raw' text with inline CriticMarkup. If True, returns 'Accepted' text."
|
|
794
|
+
),
|
|
795
|
+
mode: z.enum(["full", "outline", "appendix"]).default("full").describe(
|
|
796
|
+
"'full' returns body content. 'outline' returns a structural heading map. 'appendix' returns defined terms."
|
|
797
|
+
),
|
|
798
|
+
page: z.number().default(1).describe("Page number (1-indexed) for mode='full'. Defaults to 1."),
|
|
799
|
+
outline_max_level: z.number().default(2).describe("For mode='outline' only: cap on heading depth."),
|
|
800
|
+
outline_verbose: z.boolean().default(false).describe("For mode='outline' only: includes metadata.")
|
|
801
|
+
}),
|
|
802
|
+
_meta: { ui: { resourceUri: MARKDOWN_UI_URI } }
|
|
617
803
|
},
|
|
618
|
-
{
|
|
619
|
-
|
|
620
|
-
|
|
804
|
+
async ({
|
|
805
|
+
file_path,
|
|
806
|
+
clean_view,
|
|
807
|
+
mode,
|
|
808
|
+
page,
|
|
809
|
+
outline_max_level,
|
|
810
|
+
outline_verbose
|
|
811
|
+
}) => {
|
|
812
|
+
try {
|
|
813
|
+
const buf = readFileBytesOrThrow(file_path);
|
|
814
|
+
const text = await extractTextFromBuffer(buf, clean_view);
|
|
815
|
+
if (mode === "outline") {
|
|
816
|
+
const doc = await DocumentObject2.load(buf);
|
|
817
|
+
return build_outline_response(
|
|
818
|
+
doc,
|
|
819
|
+
text,
|
|
820
|
+
file_path,
|
|
821
|
+
outline_max_level,
|
|
822
|
+
outline_verbose
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
if (mode === "appendix") {
|
|
826
|
+
return build_appendix_response(text, page, file_path);
|
|
827
|
+
}
|
|
828
|
+
return build_paginated_response(text, page, file_path);
|
|
829
|
+
} catch (e) {
|
|
830
|
+
return {
|
|
831
|
+
isError: true,
|
|
832
|
+
content: [
|
|
833
|
+
{
|
|
834
|
+
type: "text",
|
|
835
|
+
text: `Error executing tool read_docx: ${e.message}`
|
|
836
|
+
}
|
|
837
|
+
]
|
|
838
|
+
};
|
|
621
839
|
}
|
|
622
840
|
}
|
|
623
841
|
);
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
default: 2
|
|
657
|
-
},
|
|
658
|
-
outline_verbose: {
|
|
659
|
-
type: "boolean",
|
|
660
|
-
description: "For mode='outline' only: includes metadata.",
|
|
661
|
-
default: false
|
|
662
|
-
}
|
|
663
|
-
},
|
|
664
|
-
required: ["file_path"]
|
|
665
|
-
}
|
|
666
|
-
},
|
|
667
|
-
{
|
|
668
|
-
name: "process_document_batch",
|
|
669
|
-
description: PROCESS_BATCH_COMMON_DESC + PROCESS_BATCH_OPERATIONS_DESC,
|
|
670
|
-
inputSchema: {
|
|
671
|
-
type: "object",
|
|
672
|
-
properties: {
|
|
673
|
-
original_docx_path: {
|
|
674
|
-
type: "string",
|
|
675
|
-
description: "Absolute path to the source file."
|
|
676
|
-
},
|
|
677
|
-
author_name: {
|
|
678
|
-
type: "string",
|
|
679
|
-
description: "Name to appear in Track Changes (e.g., 'Reviewer AI')."
|
|
680
|
-
},
|
|
681
|
-
changes: {
|
|
682
|
-
type: "array",
|
|
683
|
-
description: "List of changes to apply. Each change must specify 'type'.",
|
|
684
|
-
items: { type: "object" }
|
|
685
|
-
},
|
|
686
|
-
output_path: {
|
|
687
|
-
type: "string",
|
|
688
|
-
description: "Optional output path."
|
|
689
|
-
}
|
|
690
|
-
},
|
|
691
|
-
required: ["original_docx_path", "author_name", "changes"]
|
|
692
|
-
}
|
|
693
|
-
},
|
|
694
|
-
{
|
|
695
|
-
name: "accept_all_changes",
|
|
696
|
-
description: "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.",
|
|
697
|
-
inputSchema: {
|
|
698
|
-
type: "object",
|
|
699
|
-
properties: {
|
|
700
|
-
docx_path: {
|
|
701
|
-
type: "string",
|
|
702
|
-
description: "Absolute path to the DOCX file."
|
|
703
|
-
},
|
|
704
|
-
output_path: {
|
|
705
|
-
type: "string",
|
|
706
|
-
description: "Optional output path."
|
|
707
|
-
}
|
|
708
|
-
},
|
|
709
|
-
required: ["docx_path"]
|
|
710
|
-
}
|
|
711
|
-
},
|
|
712
|
-
{
|
|
713
|
-
name: "diff_docx_files",
|
|
714
|
-
description: DIFF_DOCX_DESC,
|
|
715
|
-
inputSchema: {
|
|
716
|
-
type: "object",
|
|
717
|
-
properties: {
|
|
718
|
-
original_path: {
|
|
719
|
-
type: "string",
|
|
720
|
-
description: "Absolute path to the baseline DOCX file."
|
|
721
|
-
},
|
|
722
|
-
modified_path: {
|
|
723
|
-
type: "string",
|
|
724
|
-
description: "Absolute path to the modified DOCX file."
|
|
725
|
-
},
|
|
726
|
-
compare_clean: {
|
|
727
|
-
type: "boolean",
|
|
728
|
-
description: "If True, compares 'Accepted' state. If False, compares raw text.",
|
|
729
|
-
default: true
|
|
730
|
-
}
|
|
731
|
-
},
|
|
732
|
-
required: ["original_path", "modified_path"]
|
|
733
|
-
}
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
name: "finalize_document",
|
|
737
|
-
description: "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.",
|
|
738
|
-
inputSchema: {
|
|
739
|
-
type: "object",
|
|
740
|
-
properties: {
|
|
741
|
-
file_path: {
|
|
742
|
-
type: "string",
|
|
743
|
-
description: "Absolute path to the DOCX file."
|
|
744
|
-
},
|
|
745
|
-
output_path: {
|
|
746
|
-
type: "string",
|
|
747
|
-
description: "Optional output path."
|
|
748
|
-
},
|
|
749
|
-
sanitize_mode: {
|
|
750
|
-
type: "string",
|
|
751
|
-
enum: ["full", "keep-markup"],
|
|
752
|
-
description: "full removes all markup, keep-markup redacts metadata but keeps comments/redlines."
|
|
753
|
-
},
|
|
754
|
-
accept_all: {
|
|
755
|
-
type: "boolean",
|
|
756
|
-
description: "If true, auto-accepts all unresolved track changes before finalizing."
|
|
757
|
-
},
|
|
758
|
-
protection_mode: {
|
|
759
|
-
type: "string",
|
|
760
|
-
enum: ["read_only", "encrypt"],
|
|
761
|
-
description: "Native OOXML document locking. encrypt falls back to read_only in this environment."
|
|
762
|
-
},
|
|
763
|
-
password: {
|
|
764
|
-
type: "string",
|
|
765
|
-
description: "Ignored in this environment."
|
|
766
|
-
},
|
|
767
|
-
author: {
|
|
768
|
-
type: "string",
|
|
769
|
-
description: "Replace all remaining markup authorship with this name."
|
|
770
|
-
},
|
|
771
|
-
export_pdf: {
|
|
772
|
-
type: "boolean",
|
|
773
|
-
description: "Ignored in this environment."
|
|
774
|
-
}
|
|
775
|
-
},
|
|
776
|
-
required: ["file_path"]
|
|
777
|
-
}
|
|
778
|
-
},
|
|
779
|
-
{
|
|
780
|
-
name: "login_to_adeu_cloud",
|
|
781
|
-
description: "Logs the user into the Adeu Cloud backend. Securely opens a browser window for authentication.",
|
|
782
|
-
inputSchema: { type: "object", properties: {} }
|
|
783
|
-
},
|
|
784
|
-
{
|
|
785
|
-
name: "logout_of_adeu_cloud",
|
|
786
|
-
description: "Logs out of the Adeu Cloud backend by clearing the local API key.",
|
|
787
|
-
inputSchema: { type: "object", properties: {} }
|
|
788
|
-
},
|
|
789
|
-
{
|
|
790
|
-
name: "search_and_fetch_emails",
|
|
791
|
-
description: "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.",
|
|
792
|
-
inputSchema: {
|
|
793
|
-
type: "object",
|
|
794
|
-
properties: {
|
|
795
|
-
sender: { type: "string" },
|
|
796
|
-
subject: { type: "string" },
|
|
797
|
-
has_attachments: { type: "boolean" },
|
|
798
|
-
attachment_name: { type: "string" },
|
|
799
|
-
is_unread: { type: "boolean" },
|
|
800
|
-
days_ago: { type: "number" },
|
|
801
|
-
folder: { type: "string", enum: ["inbox", "sent", "all"] },
|
|
802
|
-
limit: { type: "number", default: 10 },
|
|
803
|
-
offset: { type: "number", default: 0 },
|
|
804
|
-
email_id: { type: "string" },
|
|
805
|
-
working_directory: { type: "string" }
|
|
842
|
+
registerAppTool(
|
|
843
|
+
server,
|
|
844
|
+
"search_and_fetch_emails",
|
|
845
|
+
{
|
|
846
|
+
title: "Search & Fetch Emails",
|
|
847
|
+
description: "Searches the user's live email inbox. Returns previews. Call again with `email_id` to fetch the full body.",
|
|
848
|
+
inputSchema: z.object({
|
|
849
|
+
sender: z.string().optional(),
|
|
850
|
+
subject: z.string().optional(),
|
|
851
|
+
has_attachments: z.boolean().optional(),
|
|
852
|
+
attachment_name: z.string().optional(),
|
|
853
|
+
is_unread: z.boolean().optional(),
|
|
854
|
+
days_ago: z.number().optional(),
|
|
855
|
+
folder: z.enum(["inbox", "sent", "all"]).optional(),
|
|
856
|
+
limit: z.number().default(10),
|
|
857
|
+
offset: z.number().default(0),
|
|
858
|
+
email_id: z.string().optional(),
|
|
859
|
+
working_directory: z.string().optional(),
|
|
860
|
+
mailbox_address: z.string().optional().describe("Optional target mailbox email address to search within.")
|
|
861
|
+
}),
|
|
862
|
+
_meta: { ui: { resourceUri: EMAIL_UI_URI } }
|
|
863
|
+
},
|
|
864
|
+
async (args) => {
|
|
865
|
+
try {
|
|
866
|
+
return await search_and_fetch_emails(args);
|
|
867
|
+
} catch (e) {
|
|
868
|
+
return {
|
|
869
|
+
isError: true,
|
|
870
|
+
content: [
|
|
871
|
+
{
|
|
872
|
+
type: "text",
|
|
873
|
+
text: `Error executing tool search_and_fetch_emails: ${e.message}`
|
|
806
874
|
}
|
|
807
|
-
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
]
|
|
825
|
-
};
|
|
826
|
-
});
|
|
827
|
-
server.setRequestHandler(
|
|
828
|
-
CallToolRequestSchema,
|
|
829
|
-
async (request) => {
|
|
830
|
-
const { name, arguments: args } = request.params;
|
|
875
|
+
]
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
);
|
|
880
|
+
server.registerTool(
|
|
881
|
+
"process_document_batch",
|
|
882
|
+
{
|
|
883
|
+
description: PROCESS_BATCH_COMMON_DESC + PROCESS_BATCH_OPERATIONS_DESC,
|
|
884
|
+
inputSchema: {
|
|
885
|
+
original_docx_path: z.string().describe("Absolute path to the source file."),
|
|
886
|
+
author_name: z.string().describe("Name to appear in Track Changes (e.g., 'Reviewer AI')."),
|
|
887
|
+
changes: z.array(z.any()).describe("List of changes to apply. Each change must specify 'type'."),
|
|
888
|
+
output_path: z.string().optional().describe("Optional output path.")
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
async ({ original_docx_path, author_name, changes, output_path }) => {
|
|
831
892
|
try {
|
|
832
|
-
if (
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
outline_verbose
|
|
849
|
-
);
|
|
850
|
-
}
|
|
851
|
-
if (mode === "appendix") {
|
|
852
|
-
return build_appendix_response(text, page, filePath);
|
|
853
|
-
}
|
|
854
|
-
return build_paginated_response(text, page, filePath);
|
|
893
|
+
if (!author_name || !author_name.trim())
|
|
894
|
+
return {
|
|
895
|
+
content: [
|
|
896
|
+
{ type: "text", text: "Error: author_name cannot be empty." }
|
|
897
|
+
]
|
|
898
|
+
};
|
|
899
|
+
if (!changes || changes.length === 0)
|
|
900
|
+
return {
|
|
901
|
+
content: [{ type: "text", text: "Error: No changes provided." }]
|
|
902
|
+
};
|
|
903
|
+
let outPath = output_path;
|
|
904
|
+
if (!outPath) {
|
|
905
|
+
const ext = extname(original_docx_path);
|
|
906
|
+
const base = basename2(original_docx_path, ext);
|
|
907
|
+
const dir = dirname(original_docx_path);
|
|
908
|
+
outPath = resolve2(dir, `${base}_processed${ext}`);
|
|
855
909
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
910
|
+
const buf = readFileBytesOrThrow(original_docx_path);
|
|
911
|
+
const doc = await DocumentObject2.load(buf);
|
|
912
|
+
const engine = new RedlineEngine(doc, author_name);
|
|
913
|
+
let stats;
|
|
914
|
+
try {
|
|
915
|
+
stats = engine.process_batch(changes);
|
|
916
|
+
} catch (e) {
|
|
917
|
+
if (e instanceof BatchValidationError) {
|
|
862
918
|
return {
|
|
919
|
+
isError: true,
|
|
863
920
|
content: [
|
|
864
|
-
{
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
if (!changes || changes.length === 0) {
|
|
869
|
-
return {
|
|
870
|
-
content: [{ type: "text", text: "Error: No changes provided." }]
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
if (!outPath) {
|
|
874
|
-
const ext = extname(origPath);
|
|
875
|
-
const base = basename2(origPath, ext);
|
|
876
|
-
const dir = dirname(origPath);
|
|
877
|
-
outPath = resolve2(dir, `${base}_processed${ext}`);
|
|
878
|
-
}
|
|
879
|
-
const buf = readFileBytesOrThrow(origPath);
|
|
880
|
-
const doc = await DocumentObject2.load(buf);
|
|
881
|
-
const engine = new RedlineEngine(doc, authorName);
|
|
882
|
-
let stats;
|
|
883
|
-
try {
|
|
884
|
-
stats = engine.process_batch(changes);
|
|
885
|
-
} catch (e) {
|
|
886
|
-
if (e instanceof BatchValidationError) {
|
|
887
|
-
return {
|
|
888
|
-
content: [
|
|
889
|
-
{
|
|
890
|
-
type: "text",
|
|
891
|
-
text: `Batch rejected. Some edits failed validation:
|
|
921
|
+
{
|
|
922
|
+
type: "text",
|
|
923
|
+
text: `Batch rejected. Some edits failed validation:
|
|
892
924
|
|
|
893
925
|
${e.errors.join("\n\n")}`
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
throw e;
|
|
926
|
+
}
|
|
927
|
+
]
|
|
928
|
+
};
|
|
900
929
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
930
|
+
throw e;
|
|
931
|
+
}
|
|
932
|
+
const outBuf = await doc.save();
|
|
933
|
+
fs.writeFileSync(outPath, outBuf);
|
|
934
|
+
let res = `Batch complete. Saved to: ${outPath}
|
|
905
935
|
Actions: ${stats.actions_applied} applied, ${stats.actions_skipped} skipped.
|
|
906
936
|
Edits: ${stats.edits_applied} applied, ${stats.edits_skipped} skipped.`;
|
|
907
|
-
|
|
908
|
-
|
|
937
|
+
if (stats.skipped_details?.length > 0) {
|
|
938
|
+
res += `
|
|
909
939
|
|
|
910
940
|
Skipped Details:
|
|
911
941
|
${stats.skipped_details.join("\n")}`;
|
|
912
|
-
}
|
|
913
|
-
return {
|
|
914
|
-
content: [{ type: "text", text: res }]
|
|
915
|
-
};
|
|
916
942
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const origPath = args?.original_path;
|
|
944
|
-
const modPath = args?.modified_path;
|
|
945
|
-
const compareClean = args?.compare_clean ?? true;
|
|
946
|
-
const origBuf = readFileBytesOrThrow(origPath);
|
|
947
|
-
const modBuf = readFileBytesOrThrow(modPath);
|
|
948
|
-
const origText = await extractTextFromBuffer(origBuf, compareClean);
|
|
949
|
-
const modText = await extractTextFromBuffer(modBuf, compareClean);
|
|
950
|
-
const diff = create_word_patch_diff(
|
|
951
|
-
origText,
|
|
952
|
-
modText,
|
|
953
|
-
basename2(origPath),
|
|
954
|
-
basename2(modPath)
|
|
955
|
-
);
|
|
956
|
-
return {
|
|
957
|
-
content: [{ type: "text", text: diff || "No differences found." }]
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
if (name === "finalize_document") {
|
|
961
|
-
const filePath = args?.file_path;
|
|
962
|
-
let outPath = args?.output_path;
|
|
963
|
-
if (!outPath) {
|
|
964
|
-
const ext = extname(filePath);
|
|
965
|
-
const base = basename2(filePath, ext);
|
|
966
|
-
const dir = dirname(filePath);
|
|
967
|
-
outPath = resolve2(dir, `${base}_final${ext}`);
|
|
968
|
-
}
|
|
969
|
-
const buf = readFileBytesOrThrow(filePath);
|
|
970
|
-
const doc = await DocumentObject2.load(buf);
|
|
971
|
-
const result = await finalize_document(doc, {
|
|
972
|
-
filename: basename2(filePath),
|
|
973
|
-
sanitize_mode: args?.sanitize_mode || "full",
|
|
974
|
-
accept_all: args?.accept_all,
|
|
975
|
-
protection_mode: args?.protection_mode,
|
|
976
|
-
author: args?.author,
|
|
977
|
-
export_pdf: args?.export_pdf
|
|
978
|
-
});
|
|
979
|
-
const fs = await import("fs");
|
|
980
|
-
fs.writeFileSync(outPath, result.outBuffer);
|
|
981
|
-
return {
|
|
982
|
-
content: [
|
|
983
|
-
{
|
|
984
|
-
type: "text",
|
|
985
|
-
text: `Saved to: ${outPath}
|
|
986
|
-
|
|
987
|
-
${result.reportText}`
|
|
988
|
-
}
|
|
989
|
-
]
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
if (name === "login_to_adeu_cloud") {
|
|
993
|
-
return await login_to_adeu_cloud();
|
|
994
|
-
}
|
|
995
|
-
if (name === "logout_of_adeu_cloud") {
|
|
996
|
-
return await logout_of_adeu_cloud();
|
|
997
|
-
}
|
|
998
|
-
if (name === "search_and_fetch_emails") {
|
|
999
|
-
return await search_and_fetch_emails(args || {});
|
|
943
|
+
return { content: [{ type: "text", text: res }] };
|
|
944
|
+
} catch (e) {
|
|
945
|
+
return {
|
|
946
|
+
isError: true,
|
|
947
|
+
content: [{ type: "text", text: `Error: ${e.message}` }]
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
);
|
|
952
|
+
server.registerTool(
|
|
953
|
+
"accept_all_changes",
|
|
954
|
+
{
|
|
955
|
+
description: "Accepts all tracked changes and removes all comments in a single operation.",
|
|
956
|
+
inputSchema: {
|
|
957
|
+
docx_path: z.string().describe("Absolute path to the DOCX file."),
|
|
958
|
+
output_path: z.string().optional().describe("Optional output path.")
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
async ({ docx_path, output_path }) => {
|
|
962
|
+
try {
|
|
963
|
+
let outPath = output_path;
|
|
964
|
+
if (!outPath) {
|
|
965
|
+
const ext = extname(docx_path);
|
|
966
|
+
const base = basename2(docx_path, ext);
|
|
967
|
+
const dir = dirname(docx_path);
|
|
968
|
+
outPath = resolve2(dir, `${base}_clean${ext}`);
|
|
1000
969
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
970
|
+
const buf = readFileBytesOrThrow(docx_path);
|
|
971
|
+
const doc = await DocumentObject2.load(buf);
|
|
972
|
+
const engine = new RedlineEngine(doc);
|
|
973
|
+
engine.accept_all_revisions();
|
|
974
|
+
const outBuf = await doc.save();
|
|
975
|
+
fs.writeFileSync(outPath, outBuf);
|
|
976
|
+
return {
|
|
977
|
+
content: [
|
|
978
|
+
{ type: "text", text: `Accepted all changes. Saved to: ${outPath}` }
|
|
979
|
+
]
|
|
980
|
+
};
|
|
981
|
+
} catch (e) {
|
|
982
|
+
return {
|
|
983
|
+
isError: true,
|
|
984
|
+
content: [{ type: "text", text: `Error: ${e.message}` }]
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
);
|
|
989
|
+
server.registerTool(
|
|
990
|
+
"diff_docx_files",
|
|
991
|
+
{
|
|
992
|
+
description: DIFF_DOCX_DESC,
|
|
993
|
+
inputSchema: {
|
|
994
|
+
original_path: z.string().describe("Absolute path to the baseline DOCX file."),
|
|
995
|
+
modified_path: z.string().describe("Absolute path to the modified DOCX file."),
|
|
996
|
+
compare_clean: z.boolean().default(true).describe(
|
|
997
|
+
"If True, compares 'Accepted' state. If False, compares raw text."
|
|
998
|
+
)
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
async ({ original_path, modified_path, compare_clean }) => {
|
|
1002
|
+
try {
|
|
1003
|
+
const origBuf = readFileBytesOrThrow(original_path);
|
|
1004
|
+
const modBuf = readFileBytesOrThrow(modified_path);
|
|
1005
|
+
const origText = await extractTextFromBuffer(origBuf, compare_clean);
|
|
1006
|
+
const modText = await extractTextFromBuffer(modBuf, compare_clean);
|
|
1007
|
+
const diff = create_word_patch_diff(
|
|
1008
|
+
origText,
|
|
1009
|
+
modText,
|
|
1010
|
+
basename2(original_path),
|
|
1011
|
+
basename2(modified_path)
|
|
1012
|
+
);
|
|
1013
|
+
return {
|
|
1014
|
+
content: [{ type: "text", text: diff || "No differences found." }]
|
|
1015
|
+
};
|
|
1016
|
+
} catch (e) {
|
|
1017
|
+
return {
|
|
1018
|
+
isError: true,
|
|
1019
|
+
content: [{ type: "text", text: `Error: ${e.message}` }]
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
1024
|
+
server.registerTool(
|
|
1025
|
+
"finalize_document",
|
|
1026
|
+
{
|
|
1027
|
+
description: "Prepares a document for external distribution or e-signature.",
|
|
1028
|
+
inputSchema: {
|
|
1029
|
+
file_path: z.string().describe("Absolute path to the DOCX file."),
|
|
1030
|
+
output_path: z.string().optional().describe("Optional output path."),
|
|
1031
|
+
sanitize_mode: z.enum(["full", "keep-markup"]).optional().describe("full removes all markup, keep-markup redacts metadata."),
|
|
1032
|
+
accept_all: z.boolean().optional().describe(
|
|
1033
|
+
"If true, auto-accepts all unresolved track changes before finalizing."
|
|
1034
|
+
),
|
|
1035
|
+
protection_mode: z.enum(["read_only", "encrypt"]).optional().describe("Native OOXML document locking."),
|
|
1036
|
+
password: z.string().optional().describe("Ignored in this environment."),
|
|
1037
|
+
author: z.string().optional().describe("Replace all remaining markup authorship with this name."),
|
|
1038
|
+
export_pdf: z.boolean().optional().describe("Ignored in this environment.")
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
async ({
|
|
1042
|
+
file_path,
|
|
1043
|
+
output_path,
|
|
1044
|
+
sanitize_mode,
|
|
1045
|
+
accept_all,
|
|
1046
|
+
protection_mode,
|
|
1047
|
+
author,
|
|
1048
|
+
export_pdf
|
|
1049
|
+
}) => {
|
|
1050
|
+
try {
|
|
1051
|
+
let outPath = output_path;
|
|
1052
|
+
if (!outPath) {
|
|
1053
|
+
const ext = extname(file_path);
|
|
1054
|
+
const base = basename2(file_path, ext);
|
|
1055
|
+
const dir = dirname(file_path);
|
|
1056
|
+
outPath = resolve2(dir, `${base}_final${ext}`);
|
|
1003
1057
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1058
|
+
const buf = readFileBytesOrThrow(file_path);
|
|
1059
|
+
const doc = await DocumentObject2.load(buf);
|
|
1060
|
+
const result = await finalize_document(doc, {
|
|
1061
|
+
filename: basename2(file_path),
|
|
1062
|
+
sanitize_mode: sanitize_mode || "full",
|
|
1063
|
+
accept_all,
|
|
1064
|
+
protection_mode,
|
|
1065
|
+
author,
|
|
1066
|
+
export_pdf
|
|
1067
|
+
});
|
|
1068
|
+
fs.writeFileSync(outPath, result.outBuffer);
|
|
1006
1069
|
return {
|
|
1007
1070
|
content: [
|
|
1008
1071
|
{
|
|
1009
1072
|
type: "text",
|
|
1010
|
-
text: `
|
|
1073
|
+
text: `Saved to: ${outPath}
|
|
1074
|
+
|
|
1075
|
+
${result.reportText}`
|
|
1011
1076
|
}
|
|
1012
|
-
]
|
|
1013
|
-
isError: true
|
|
1077
|
+
]
|
|
1014
1078
|
};
|
|
1079
|
+
} catch (e) {
|
|
1080
|
+
return {
|
|
1081
|
+
isError: true,
|
|
1082
|
+
content: [{ type: "text", text: `Error: ${e.message}` }]
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
);
|
|
1087
|
+
server.registerTool(
|
|
1088
|
+
"login_to_adeu_cloud",
|
|
1089
|
+
{ description: "Logs the user into the Adeu Cloud backend." },
|
|
1090
|
+
async () => {
|
|
1091
|
+
try {
|
|
1092
|
+
return await login_to_adeu_cloud();
|
|
1093
|
+
} catch (e) {
|
|
1094
|
+
return { isError: true, content: [{ type: "text", text: e.message }] };
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
);
|
|
1098
|
+
server.registerTool(
|
|
1099
|
+
"logout_of_adeu_cloud",
|
|
1100
|
+
{ description: "Logs out of the Adeu Cloud backend." },
|
|
1101
|
+
async () => {
|
|
1102
|
+
try {
|
|
1103
|
+
return await logout_of_adeu_cloud();
|
|
1104
|
+
} catch (e) {
|
|
1105
|
+
return { isError: true, content: [{ type: "text", text: e.message }] };
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
);
|
|
1109
|
+
server.registerTool(
|
|
1110
|
+
"create_email_draft",
|
|
1111
|
+
{
|
|
1112
|
+
description: "Creates an email draft in the user's native draft box.",
|
|
1113
|
+
inputSchema: {
|
|
1114
|
+
body_markdown: z.string(),
|
|
1115
|
+
reply_to_email_id: z.string().optional(),
|
|
1116
|
+
subject: z.string().optional(),
|
|
1117
|
+
to_recipients: z.array(z.string()).optional(),
|
|
1118
|
+
attachment_paths: z.array(z.string()).optional(),
|
|
1119
|
+
mailbox_address: z.string().optional().describe(
|
|
1120
|
+
"Optional target mailbox email address to create the draft in."
|
|
1121
|
+
)
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
async (args) => {
|
|
1125
|
+
try {
|
|
1126
|
+
return await create_email_draft(args);
|
|
1127
|
+
} catch (e) {
|
|
1128
|
+
return { isError: true, content: [{ type: "text", text: e.message }] };
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
);
|
|
1132
|
+
server.registerTool(
|
|
1133
|
+
"list_available_mailboxes",
|
|
1134
|
+
{
|
|
1135
|
+
description: "Lists all personal and shared delegated mailboxes configured for the authenticated profile. Use this to discover valid email addresses to scope search and draft operations.",
|
|
1136
|
+
inputSchema: {}
|
|
1137
|
+
},
|
|
1138
|
+
async () => {
|
|
1139
|
+
try {
|
|
1140
|
+
return await list_available_mailboxes();
|
|
1141
|
+
} catch (e) {
|
|
1142
|
+
return { isError: true, content: [{ type: "text", text: e.message }] };
|
|
1015
1143
|
}
|
|
1016
1144
|
}
|
|
1017
1145
|
);
|