@adeu/mcp-server 1.12.0 → 1.13.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/index.js +202 -179
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +80 -67
- package/src/tools/email.test.ts +68 -12
- package/src/tools/email.ts +64 -55
package/dist/index.js
CHANGED
|
@@ -607,57 +607,59 @@ function removeNestedQuotes(text) {
|
|
|
607
607
|
function getUniqueFilepath(saveDir, filename) {
|
|
608
608
|
return join2(saveDir, filename);
|
|
609
609
|
}
|
|
610
|
-
async function
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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}`);
|
|
610
|
+
async function pollEmailTask(taskId, apiKey) {
|
|
611
|
+
const pollUrl = `${BACKEND_URL}/api/v1/emails/tasks/${taskId}`;
|
|
612
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
613
|
+
let res;
|
|
614
|
+
try {
|
|
615
|
+
res = await fetch(pollUrl, {
|
|
616
|
+
headers: {
|
|
617
|
+
Authorization: `Bearer ${apiKey}`,
|
|
618
|
+
Accept: "application/json"
|
|
619
|
+
},
|
|
620
|
+
signal: AbortSignal.timeout(15e3)
|
|
621
|
+
});
|
|
622
|
+
} catch (err) {
|
|
623
|
+
if (isTimeoutError(err)) {
|
|
624
|
+
throw new Error("Checking task status timed out.");
|
|
651
625
|
}
|
|
652
|
-
|
|
626
|
+
throw err;
|
|
653
627
|
}
|
|
628
|
+
if (res.status === 401) {
|
|
629
|
+
DesktopAuthManager.clearApiKey();
|
|
630
|
+
throw new Error(
|
|
631
|
+
"Authentication expired. Please call `login_to_adeu_cloud` to re-authenticate."
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
if (!res.ok) {
|
|
635
|
+
throw new Error(formatBackendError(res.status, await res.text()));
|
|
636
|
+
}
|
|
637
|
+
const taskData = await res.json();
|
|
638
|
+
const status = taskData.status;
|
|
639
|
+
if (status === "COMPLETED") {
|
|
640
|
+
return taskData;
|
|
641
|
+
}
|
|
642
|
+
if (status === "FAILED") {
|
|
643
|
+
const errorMsg = taskData.error || "Unknown internal error";
|
|
644
|
+
throw new Error(`Validation task failed on the server: ${errorMsg}`);
|
|
645
|
+
}
|
|
646
|
+
await new Promise((resolve3) => setTimeout(resolve3, 5e3));
|
|
647
|
+
}
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
async function search_and_fetch_emails(args2) {
|
|
651
|
+
const apiKey = await getCloudAuthToken();
|
|
652
|
+
const maxAttachmentSizeMb = typeof args2.max_attachment_size_mb === "number" && args2.max_attachment_size_mb > 0 ? args2.max_attachment_size_mb : 10;
|
|
653
|
+
let data;
|
|
654
|
+
if (args2.task_id) {
|
|
655
|
+
const completedData = await pollEmailTask(args2.task_id, apiKey);
|
|
654
656
|
if (!completedData) {
|
|
655
|
-
const msg = `Task ${
|
|
657
|
+
const msg = `Task ${args2.task_id} is still processing. Please call \`search_and_fetch_emails\` again with task_id=${args2.task_id}.`;
|
|
656
658
|
return {
|
|
657
659
|
content: [{ type: "text", text: msg }],
|
|
658
660
|
structuredContent: {
|
|
659
661
|
status: "pending",
|
|
660
|
-
task_id:
|
|
662
|
+
task_id: args2.task_id,
|
|
661
663
|
message: msg
|
|
662
664
|
}
|
|
663
665
|
};
|
|
@@ -666,7 +668,7 @@ async function search_and_fetch_emails(args) {
|
|
|
666
668
|
} else {
|
|
667
669
|
let realEmailId;
|
|
668
670
|
try {
|
|
669
|
-
realEmailId =
|
|
671
|
+
realEmailId = args2.email_id ? resolveEmailId(args2.email_id) : void 0;
|
|
670
672
|
} catch (err) {
|
|
671
673
|
if (err instanceof StaleShortIdError) {
|
|
672
674
|
return {
|
|
@@ -678,16 +680,16 @@ async function search_and_fetch_emails(args) {
|
|
|
678
680
|
}
|
|
679
681
|
const payload = {
|
|
680
682
|
email_id: realEmailId,
|
|
681
|
-
sender:
|
|
682
|
-
subject:
|
|
683
|
-
has_attachments:
|
|
684
|
-
attachment_name:
|
|
685
|
-
is_unread:
|
|
686
|
-
days_ago:
|
|
687
|
-
folder:
|
|
688
|
-
limit:
|
|
689
|
-
offset:
|
|
690
|
-
mailbox_address:
|
|
683
|
+
sender: args2.sender,
|
|
684
|
+
subject: args2.subject,
|
|
685
|
+
has_attachments: args2.has_attachments,
|
|
686
|
+
attachment_name: args2.attachment_name,
|
|
687
|
+
is_unread: args2.is_unread,
|
|
688
|
+
days_ago: args2.days_ago,
|
|
689
|
+
folder: args2.folder,
|
|
690
|
+
limit: args2.limit ?? 10,
|
|
691
|
+
offset: args2.offset ?? 0,
|
|
692
|
+
mailbox_address: args2.mailbox_address
|
|
691
693
|
};
|
|
692
694
|
Object.keys(payload).forEach(
|
|
693
695
|
(k) => payload[k] === void 0 && delete payload[k]
|
|
@@ -722,15 +724,19 @@ async function search_and_fetch_emails(args) {
|
|
|
722
724
|
data = await res.json();
|
|
723
725
|
if (res.status === 202 || data && (data.status === "pending" || data.task_id) && data.type === void 0) {
|
|
724
726
|
const newTaskId = data.task_id;
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
727
|
+
const completedData = await pollEmailTask(String(newTaskId), apiKey);
|
|
728
|
+
if (!completedData) {
|
|
729
|
+
const msg = `Task ${newTaskId} is still processing. Please call \`search_and_fetch_emails\` again immediately with task_id=${newTaskId} to monitor the progress.`;
|
|
730
|
+
return {
|
|
731
|
+
content: [{ type: "text", text: msg }],
|
|
732
|
+
structuredContent: {
|
|
733
|
+
status: "pending",
|
|
734
|
+
task_id: String(newTaskId),
|
|
735
|
+
message: msg
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
data = completedData;
|
|
734
740
|
}
|
|
735
741
|
}
|
|
736
742
|
const cache = loadIdCache();
|
|
@@ -763,8 +769,8 @@ async function search_and_fetch_emails(args) {
|
|
|
763
769
|
);
|
|
764
770
|
}
|
|
765
771
|
saveIdCache(cache);
|
|
766
|
-
const limit = typeof
|
|
767
|
-
const offset = typeof
|
|
772
|
+
const limit = typeof args2.limit === "number" ? args2.limit : 10;
|
|
773
|
+
const offset = typeof args2.offset === "number" ? args2.offset : 0;
|
|
768
774
|
const pageHint = previews.length >= limit ? `
|
|
769
775
|
*(If you need to see more results, call this tool again with offset=${offset + limit})*` : "";
|
|
770
776
|
lines.push(
|
|
@@ -779,11 +785,11 @@ async function search_and_fetch_emails(args) {
|
|
|
779
785
|
const full = data.full_email || {};
|
|
780
786
|
const shortTargetId = minifyEmailId(full.id || "unknown_id", cache);
|
|
781
787
|
saveIdCache(cache);
|
|
782
|
-
const autoEscalated = !
|
|
783
|
-
const baseDir =
|
|
788
|
+
const autoEscalated = !args2.email_id && (args2.sender !== void 0 || args2.subject !== void 0 || args2.has_attachments !== void 0 || args2.attachment_name !== void 0 || args2.is_unread !== void 0 || args2.days_ago !== void 0 || args2.folder !== void 0);
|
|
789
|
+
const baseDir = args2.working_directory && existsSync2(args2.working_directory) ? args2.working_directory : tmpdir();
|
|
784
790
|
const saveDir = join2(
|
|
785
791
|
baseDir,
|
|
786
|
-
|
|
792
|
+
args2.working_directory ? "adeu_attachments" : "adeu_downloads",
|
|
787
793
|
shortTargetId
|
|
788
794
|
);
|
|
789
795
|
mkdirSync2(saveDir, { recursive: true });
|
|
@@ -907,20 +913,20 @@ ${removeNestedQuotes(stripTags(histMsg.body_html || ""))}
|
|
|
907
913
|
content: [{ type: "text", text: "Unknown response format from backend." }]
|
|
908
914
|
};
|
|
909
915
|
}
|
|
910
|
-
async function create_email_draft(
|
|
916
|
+
async function create_email_draft(args2) {
|
|
911
917
|
const apiKey = await getCloudAuthToken();
|
|
912
|
-
if (!
|
|
918
|
+
if (!args2.reply_to_email_id && (!args2.subject || !args2.to_recipients)) {
|
|
913
919
|
throw new Error(
|
|
914
920
|
"You must provide either 'reply_to_email_id' OR both 'subject' and 'to_recipients'."
|
|
915
921
|
);
|
|
916
922
|
}
|
|
917
923
|
const formData = new FormData();
|
|
918
|
-
formData.append("body_markdown",
|
|
919
|
-
if (
|
|
924
|
+
formData.append("body_markdown", args2.body_markdown);
|
|
925
|
+
if (args2.reply_to_email_id) {
|
|
920
926
|
try {
|
|
921
927
|
formData.append(
|
|
922
928
|
"reply_to_email_id",
|
|
923
|
-
resolveEmailId(
|
|
929
|
+
resolveEmailId(args2.reply_to_email_id)
|
|
924
930
|
);
|
|
925
931
|
} catch (err) {
|
|
926
932
|
if (err instanceof StaleShortIdError) {
|
|
@@ -932,16 +938,16 @@ async function create_email_draft(args) {
|
|
|
932
938
|
throw err;
|
|
933
939
|
}
|
|
934
940
|
}
|
|
935
|
-
if (
|
|
936
|
-
if (
|
|
937
|
-
formData.append("mailbox_address",
|
|
941
|
+
if (args2.subject) formData.append("subject", args2.subject);
|
|
942
|
+
if (args2.mailbox_address) {
|
|
943
|
+
formData.append("mailbox_address", args2.mailbox_address);
|
|
938
944
|
}
|
|
939
|
-
if (
|
|
940
|
-
const recips = typeof
|
|
945
|
+
if (args2.to_recipients) {
|
|
946
|
+
const recips = typeof args2.to_recipients === "string" ? JSON.parse(args2.to_recipients) : args2.to_recipients;
|
|
941
947
|
formData.append("to_recipients", JSON.stringify(recips));
|
|
942
948
|
}
|
|
943
|
-
if (
|
|
944
|
-
const paths = typeof
|
|
949
|
+
if (args2.attachment_paths) {
|
|
950
|
+
const paths = typeof args2.attachment_paths === "string" ? JSON.parse(args2.attachment_paths) : args2.attachment_paths;
|
|
945
951
|
for (const p of paths) {
|
|
946
952
|
const buf = readFileSync2(p);
|
|
947
953
|
const filename = p.split(/[/\\]/).pop();
|
|
@@ -1053,7 +1059,18 @@ function readFileBytesOrThrow(filePath) {
|
|
|
1053
1059
|
} catch (err) {
|
|
1054
1060
|
if (err.code === "ENOENT") {
|
|
1055
1061
|
throw new Error(
|
|
1056
|
-
`File not found: ${filePath}.
|
|
1062
|
+
`File not found: ${filePath}.
|
|
1063
|
+
If you are running in a sandboxed/containerized environment (such as Claude Desktop or another containerized client), the host application or MCP server may not have direct access to your local workspace files.
|
|
1064
|
+
You can resolve this by installing and running the local 'adeu' CLI tool directly within your environment.
|
|
1065
|
+
Here is how the MCP tools map to their CLI equivalents:
|
|
1066
|
+
- read_docx -> adeu extract ${filePath}
|
|
1067
|
+
- process_document_batch -> adeu apply ${filePath}
|
|
1068
|
+
- diff_docx_files -> adeu diff ${filePath} <modified_path>
|
|
1069
|
+
- accept_all_changes -> adeu accept-all ${filePath}
|
|
1070
|
+
|
|
1071
|
+
To run the local tool, install it via:
|
|
1072
|
+
uv tool install adeu
|
|
1073
|
+
and run the mapped CLI command directly in your terminal.`
|
|
1057
1074
|
);
|
|
1058
1075
|
}
|
|
1059
1076
|
throw err;
|
|
@@ -1072,9 +1089,13 @@ var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use pa
|
|
|
1072
1089
|
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";
|
|
1073
1090
|
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.";
|
|
1074
1091
|
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 = "
|
|
1076
|
-
var packageVersion = "1.
|
|
1092
|
+
var gitSha = "cab9a8d";
|
|
1093
|
+
var packageVersion = "1.13.0";
|
|
1077
1094
|
var buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;
|
|
1095
|
+
var args = process.argv.slice(2);
|
|
1096
|
+
var scopeIdx = args.indexOf("--scope");
|
|
1097
|
+
var requestedScope = (scopeIdx !== -1 ? args[scopeIdx + 1] : "all").toLowerCase();
|
|
1098
|
+
var isDocxOnly = requestedScope === "docx";
|
|
1078
1099
|
var server = new McpServer({
|
|
1079
1100
|
name: "adeu-redlining-service",
|
|
1080
1101
|
version: packageVersion
|
|
@@ -1221,43 +1242,6 @@ registerAppTool(
|
|
|
1221
1242
|
}
|
|
1222
1243
|
}
|
|
1223
1244
|
);
|
|
1224
|
-
registerAppTool(
|
|
1225
|
-
server,
|
|
1226
|
-
"search_and_fetch_emails",
|
|
1227
|
-
{
|
|
1228
|
-
title: "Search & Fetch Emails",
|
|
1229
|
-
description: "Searches the user's live email inbox via the Adeu cloud backend.\n\nTWO MODES:\n1. Search mode (no `email_id`): returns up to `limit` lightweight previews. Use filters (`sender`, `subject`, `is_unread`, `days_ago`, `folder`, `has_attachments`, `attachment_name`) to narrow down.\n2. Fetch mode (with `email_id`): returns the full email body, thread history, and downloads attachments under `max_attachment_size_mb` to the local disk.\n\nAUTO-ESCALATION: If a search returns exactly one preview, the backend automatically fetches the full email in the same call. Plan around the response shape \u2014 check the `type` field (`previews` vs `full_email`) before assuming.\n\nEMAIL ID FORMATS (`email_id` parameter accepts any of):\n- `msg_<6 chars>` \u2014 short ID returned by previews on THIS machine. NOT portable across machines or sessions; the local cache holds the most recent 1000. If you reference one that's been evicted, the tool returns a StaleShortIdError telling you to re-search.\n- `adeu_<numeric>` \u2014 server-side reference for emails Adeu has previously processed. Portable across machines and sessions for the same authenticated user.\n- Raw provider ID (Gmail/Outlook native ID) \u2014 works if you have it, but you usually won't.\n\nFOLDER DEFAULT: omitting `folder` searches the Inbox only (matching what the user sees in their mail client). Use `folder='sent'` for sent items, `folder='all'` to include Deleted Items, Drafts, and other folders.\n\nATTACHMENTS: attachments larger than `max_attachment_size_mb` (default 10) are listed in the response but NOT downloaded \u2014 raise the cap if you need them. Always set `working_directory` when calling from a project so attachments land alongside the user's other files.",
|
|
1230
|
-
inputSchema: z.object({
|
|
1231
|
-
sender: z.string().optional(),
|
|
1232
|
-
subject: z.string().optional(),
|
|
1233
|
-
has_attachments: z.boolean().optional(),
|
|
1234
|
-
attachment_name: z.string().optional(),
|
|
1235
|
-
is_unread: z.boolean().optional(),
|
|
1236
|
-
days_ago: z.number().optional(),
|
|
1237
|
-
folder: z.enum(["inbox", "sent", "all"]).optional(),
|
|
1238
|
-
limit: z.number().default(10),
|
|
1239
|
-
offset: z.number().default(0),
|
|
1240
|
-
email_id: z.string().optional(),
|
|
1241
|
-
working_directory: z.string().optional(),
|
|
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."),
|
|
1244
|
-
max_attachment_size_mb: z.number().optional().describe(
|
|
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."
|
|
1246
|
-
)
|
|
1247
|
-
}),
|
|
1248
|
-
_meta: { ui: { resourceUri: EMAIL_UI_URI } }
|
|
1249
|
-
},
|
|
1250
|
-
async (args) => {
|
|
1251
|
-
try {
|
|
1252
|
-
return await search_and_fetch_emails(args);
|
|
1253
|
-
} catch (e) {
|
|
1254
|
-
return {
|
|
1255
|
-
isError: true,
|
|
1256
|
-
content: [{ type: "text", text: e.message }]
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
);
|
|
1261
1245
|
server.registerTool(
|
|
1262
1246
|
"process_document_batch",
|
|
1263
1247
|
{
|
|
@@ -1468,67 +1452,106 @@ ${result.reportText}`
|
|
|
1468
1452
|
}
|
|
1469
1453
|
}
|
|
1470
1454
|
);
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1455
|
+
if (!isDocxOnly) {
|
|
1456
|
+
registerAppTool(
|
|
1457
|
+
server,
|
|
1458
|
+
"search_and_fetch_emails",
|
|
1459
|
+
{
|
|
1460
|
+
title: "Search & Fetch Emails",
|
|
1461
|
+
description: "Searches the user's live email inbox via the Adeu cloud backend.\n\nTWO MODES:\n1. Search mode (no `email_id`): returns up to `limit` lightweight previews. Use filters (`sender`, `subject`, `is_unread`, `days_ago`, `folder`, `has_attachments`, `attachment_name`) to narrow down.\n2. Fetch mode (with `email_id`): returns the full email body, thread history, and downloads attachments under `max_attachment_size_mb` to the local disk.\n\nAUTO-ESCALATION: If a search returns exactly one preview, the backend automatically fetches the full email in the same call. Plan around the response shape \u2014 check the `type` field (`previews` vs `full_email`) before assuming.\n\nEMAIL ID FORMATS (`email_id` parameter accepts any of):\n- `msg_<6 chars>` \u2014 short ID returned by previews on THIS machine. NOT portable across machines or sessions; the local cache holds the most recent 1000. If you reference one that's been evicted, the tool returns a StaleShortIdError telling you to re-search.\n- `adeu_<numeric>` \u2014 server-side reference for emails Adeu has previously processed. Portable across machines and sessions for the same authenticated user.\n- Raw provider ID (Gmail/Outlook native ID) \u2014 works if you have it, but you usually won't.\n\nFOLDER DEFAULT: omitting `folder` searches the Inbox only (matching what the user sees in their mail client). Use `folder='sent'` for sent items, `folder='all'` to include Deleted Items, Drafts, and other folders.\n\nATTACHMENTS: attachments larger than `max_attachment_size_mb` (default 10) are listed in the response but NOT downloaded \u2014 raise the cap if you need them. Always set `working_directory` when calling from a project so attachments land alongside the user's other files.",
|
|
1462
|
+
inputSchema: z.object({
|
|
1463
|
+
sender: z.string().optional(),
|
|
1464
|
+
subject: z.string().optional(),
|
|
1465
|
+
has_attachments: z.boolean().optional(),
|
|
1466
|
+
attachment_name: z.string().optional(),
|
|
1467
|
+
is_unread: z.boolean().optional(),
|
|
1468
|
+
days_ago: z.number().optional(),
|
|
1469
|
+
folder: z.enum(["inbox", "sent", "all"]).optional(),
|
|
1470
|
+
limit: z.number().default(10),
|
|
1471
|
+
offset: z.number().default(0),
|
|
1472
|
+
email_id: z.string().optional(),
|
|
1473
|
+
working_directory: z.string().optional(),
|
|
1474
|
+
mailbox_address: z.string().optional().describe("Optional target mailbox email address to search within."),
|
|
1475
|
+
task_id: z.string().optional().describe("If resuming a pending check, provide the task ID here."),
|
|
1476
|
+
max_attachment_size_mb: z.number().optional().describe(
|
|
1477
|
+
"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."
|
|
1478
|
+
)
|
|
1479
|
+
}),
|
|
1480
|
+
_meta: { ui: { resourceUri: EMAIL_UI_URI } }
|
|
1481
|
+
},
|
|
1482
|
+
async (args2) => {
|
|
1483
|
+
try {
|
|
1484
|
+
return await search_and_fetch_emails(args2);
|
|
1485
|
+
} catch (e) {
|
|
1486
|
+
return {
|
|
1487
|
+
isError: true,
|
|
1488
|
+
content: [{ type: "text", text: e.message }]
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1481
1491
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
+
);
|
|
1493
|
+
server.registerTool(
|
|
1494
|
+
"login_to_adeu_cloud",
|
|
1495
|
+
{
|
|
1496
|
+
description: "Logs the user into Adeu Cloud. Opens a browser window for SSO authentication.\n\nIMPORTANT \u2014 login is user-level, not account-level:\n- An Adeu user can have multiple linked provider accounts (Microsoft, Google) and multiple mailboxes (personal + shared/delegated). One linked account is marked primary.\n- Signing in through ANY of the user's linked accounts authenticates the same Adeu user. Once logged in, the session can read from and draft in ALL of that user's linked accounts and ALL of their mailboxes \u2014 not just the one used to sign in.\n- The choice of which provider account to sign in through is purely an SSO mechanism; it does not select a 'current account' for the session.\n\nWhen the user asks which accounts or mailboxes are available, call `list_available_mailboxes` rather than naming a single account from the login response."
|
|
1497
|
+
},
|
|
1498
|
+
async () => {
|
|
1499
|
+
try {
|
|
1500
|
+
return await login_to_adeu_cloud();
|
|
1501
|
+
} catch (e) {
|
|
1502
|
+
return { isError: true, content: [{ type: "text", text: e.message }] };
|
|
1503
|
+
}
|
|
1492
1504
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
to_recipients: z.array(z.string()).optional(),
|
|
1504
|
-
attachment_paths: z.array(z.string()).optional(),
|
|
1505
|
-
mailbox_address: z.string().optional().describe(
|
|
1506
|
-
"Optional target mailbox email address to create the draft in."
|
|
1507
|
-
)
|
|
1505
|
+
);
|
|
1506
|
+
server.registerTool(
|
|
1507
|
+
"logout_of_adeu_cloud",
|
|
1508
|
+
{ description: "Logs out of the Adeu Cloud backend." },
|
|
1509
|
+
async () => {
|
|
1510
|
+
try {
|
|
1511
|
+
return await logout_of_adeu_cloud();
|
|
1512
|
+
} catch (e) {
|
|
1513
|
+
return { isError: true, content: [{ type: "text", text: e.message }] };
|
|
1514
|
+
}
|
|
1508
1515
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1516
|
+
);
|
|
1517
|
+
server.registerTool(
|
|
1518
|
+
"create_email_draft",
|
|
1519
|
+
{
|
|
1520
|
+
description: "Creates an email draft in the user's native draft box (Outlook Drafts or Gmail Drafts).\n\nTWO MODES:\n1. Reply mode: pass `reply_to_email_id` to create a threaded reply. The draft inherits subject, recipients, and threading headers from the original \u2014 do NOT pass `subject` or `to_recipients`.\n2. New email mode: omit `reply_to_email_id` and pass BOTH `subject` and `to_recipients`.\n\n`reply_to_email_id` accepts the same ID formats as search_and_fetch_emails (`msg_*` short IDs, `adeu_*` references, or raw provider IDs). Short IDs are validated against the local cache before the call; stale ones fail fast with a clear error telling you to re-search.\n\n`body_markdown` is converted server-side to styled HTML with inlined CSS for email-client compatibility. Write the body in plain Markdown \u2014 do not pre-render HTML.\n\n`attachment_paths` takes absolute file paths on the user's local disk and uploads them with the draft. Useful right after search_and_fetch_emails downloaded attachments \u2014 those local paths can be passed directly here.",
|
|
1521
|
+
inputSchema: {
|
|
1522
|
+
body_markdown: z.string(),
|
|
1523
|
+
reply_to_email_id: z.string().optional(),
|
|
1524
|
+
subject: z.string().optional(),
|
|
1525
|
+
to_recipients: z.array(z.string()).optional(),
|
|
1526
|
+
attachment_paths: z.array(z.string()).optional(),
|
|
1527
|
+
mailbox_address: z.string().optional().describe(
|
|
1528
|
+
"Optional target mailbox email address to create the draft in."
|
|
1529
|
+
)
|
|
1530
|
+
}
|
|
1531
|
+
},
|
|
1532
|
+
async (args2) => {
|
|
1533
|
+
try {
|
|
1534
|
+
return await create_email_draft(args2);
|
|
1535
|
+
} catch (e) {
|
|
1536
|
+
return { isError: true, content: [{ type: "text", text: e.message }] };
|
|
1537
|
+
}
|
|
1515
1538
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1539
|
+
);
|
|
1540
|
+
server.registerTool(
|
|
1541
|
+
"list_available_mailboxes",
|
|
1542
|
+
{
|
|
1543
|
+
description: "Lists all personal and shared/delegated mailboxes the authenticated Adeu user has access to, across ALL of their linked provider accounts. Returns each mailbox's `email_address`, `display_name`, auto-processing settings, and write-back preference.\n\nThis is the right tool to answer 'which accounts/mailboxes am I logged into?' \u2014 Adeu login is user-level, so a single MCP session can see every mailbox listed here regardless of which provider account was used for SSO.\n\nCall this FIRST when the user names a specific mailbox or shared inbox, to resolve the canonical `email_address`. Then pass that address as `mailbox_address` to `search_and_fetch_emails` or `create_email_draft` to scope the operation. Omitting `mailbox_address` on those tools targets the user's primary personal mailbox.",
|
|
1544
|
+
inputSchema: {}
|
|
1545
|
+
},
|
|
1546
|
+
async () => {
|
|
1547
|
+
try {
|
|
1548
|
+
return await list_available_mailboxes();
|
|
1549
|
+
} catch (e) {
|
|
1550
|
+
return { isError: true, content: [{ type: "text", text: e.message }] };
|
|
1551
|
+
}
|
|
1529
1552
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1532
1555
|
function formatBatchResult(stats, outPath, dry_run) {
|
|
1533
1556
|
let res = "";
|
|
1534
1557
|
if (dry_run) {
|
|
@@ -1582,8 +1605,8 @@ ${stats.skipped_details.join("\n")}`;
|
|
|
1582
1605
|
async function main() {
|
|
1583
1606
|
const transport = new StdioServerTransport();
|
|
1584
1607
|
await server.connect(transport);
|
|
1585
|
-
const gitSha2 = "
|
|
1586
|
-
const buildTs = "2026-06-
|
|
1608
|
+
const gitSha2 = "cab9a8d";
|
|
1609
|
+
const buildTs = "2026-06-23T13:51:25.626Z";
|
|
1587
1610
|
console.error(
|
|
1588
1611
|
`Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha2}@${buildTs}`
|
|
1589
1612
|
);
|