@agenticmail/mcp 0.5.50 → 0.5.51
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 +374 -279
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
6
7
|
|
|
7
8
|
// src/pending-followup.ts
|
|
8
9
|
var STEP_DELAYS_MS = [
|
|
@@ -1064,22 +1065,22 @@ function mcpBuildSecuritySection(security, attachments) {
|
|
|
1064
1065
|
--- Security ---
|
|
1065
1066
|
${lines.join("\n")}`;
|
|
1066
1067
|
}
|
|
1067
|
-
async function handleToolCall(name,
|
|
1068
|
+
async function handleToolCall(name, args2) {
|
|
1068
1069
|
recordToolCall(name);
|
|
1069
1070
|
const useMaster = MASTER_KEY_TOOLS.has(name);
|
|
1070
1071
|
switch (name) {
|
|
1071
1072
|
case "send_email": {
|
|
1072
1073
|
const sendBody = {
|
|
1073
|
-
to:
|
|
1074
|
-
subject:
|
|
1075
|
-
text:
|
|
1076
|
-
html:
|
|
1077
|
-
cc:
|
|
1078
|
-
inReplyTo:
|
|
1079
|
-
references:
|
|
1074
|
+
to: args2.to,
|
|
1075
|
+
subject: args2.subject,
|
|
1076
|
+
text: args2.text ?? "",
|
|
1077
|
+
html: args2.html,
|
|
1078
|
+
cc: args2.cc,
|
|
1079
|
+
inReplyTo: args2.inReplyTo,
|
|
1080
|
+
references: args2.references
|
|
1080
1081
|
};
|
|
1081
|
-
if (Array.isArray(
|
|
1082
|
-
sendBody.attachments =
|
|
1082
|
+
if (Array.isArray(args2.attachments) && args2.attachments.length > 0) {
|
|
1083
|
+
sendBody.attachments = args2.attachments.map((a) => ({
|
|
1083
1084
|
filename: a.filename,
|
|
1084
1085
|
content: a.content,
|
|
1085
1086
|
contentType: a.contentType,
|
|
@@ -1088,7 +1089,7 @@ async function handleToolCall(name, args) {
|
|
|
1088
1089
|
}
|
|
1089
1090
|
const result = await apiRequest("POST", "/mail/send", sendBody);
|
|
1090
1091
|
if (result?.blocked && result?.pendingId) {
|
|
1091
|
-
scheduleFollowUp(result.pendingId, String(
|
|
1092
|
+
scheduleFollowUp(result.pendingId, String(args2.to), String(args2.subject || "(no subject)"), makePendingCheck(result.pendingId));
|
|
1092
1093
|
return `Email NOT sent \u2014 blocked by outbound guard.
|
|
1093
1094
|
${result.summary}
|
|
1094
1095
|
|
|
@@ -1112,8 +1113,8 @@ ${result.outboundWarnings.map((w) => ` [${w.severity?.toUpperCase()}] ${w.descr
|
|
|
1112
1113
|
return response;
|
|
1113
1114
|
}
|
|
1114
1115
|
case "list_inbox": {
|
|
1115
|
-
const limit = Math.min(Math.max(Number(
|
|
1116
|
-
const offset = Math.max(Number(
|
|
1116
|
+
const limit = Math.min(Math.max(Number(args2.limit) || 20, 1), 100);
|
|
1117
|
+
const offset = Math.max(Number(args2.offset) || 0, 0);
|
|
1117
1118
|
const result = await apiRequest("GET", `/mail/inbox?limit=${limit}&offset=${offset}`);
|
|
1118
1119
|
if (!result?.messages?.length) {
|
|
1119
1120
|
return "Inbox is empty.";
|
|
@@ -1125,7 +1126,7 @@ ${result.outboundWarnings.map((w) => ` [${w.severity?.toUpperCase()}] ${w.descr
|
|
|
1125
1126
|
${lines.join("\n")}`;
|
|
1126
1127
|
}
|
|
1127
1128
|
case "read_email": {
|
|
1128
|
-
const uid = Number(
|
|
1129
|
+
const uid = Number(args2.uid);
|
|
1129
1130
|
if (!uid || uid < 1 || !Number.isInteger(uid)) {
|
|
1130
1131
|
throw new Error("uid must be a positive integer");
|
|
1131
1132
|
}
|
|
@@ -1153,7 +1154,7 @@ ${lines.join("\n")}`;
|
|
|
1153
1154
|
return lines.filter((line) => line !== null).join("\n");
|
|
1154
1155
|
}
|
|
1155
1156
|
case "delete_email": {
|
|
1156
|
-
const uid = Number(
|
|
1157
|
+
const uid = Number(args2.uid);
|
|
1157
1158
|
if (!uid || uid < 1 || !Number.isInteger(uid)) {
|
|
1158
1159
|
throw new Error("uid must be a positive integer");
|
|
1159
1160
|
}
|
|
@@ -1161,7 +1162,7 @@ ${lines.join("\n")}`;
|
|
|
1161
1162
|
return `Email UID ${uid} deleted successfully.`;
|
|
1162
1163
|
}
|
|
1163
1164
|
case "search_emails": {
|
|
1164
|
-
const { from, to, subject, text, since, before, seen, searchRelay } =
|
|
1165
|
+
const { from, to, subject, text, since, before, seen, searchRelay } = args2;
|
|
1165
1166
|
const result = await apiRequest("POST", "/mail/search", { from, to, subject, text, since, before, seen, searchRelay });
|
|
1166
1167
|
const lines = [];
|
|
1167
1168
|
if (result?.uids?.length) {
|
|
@@ -1183,7 +1184,7 @@ Connected account (${result.relayResults[0].account}): ${result.relayResults.len
|
|
|
1183
1184
|
return lines.join("\n");
|
|
1184
1185
|
}
|
|
1185
1186
|
case "import_relay_email": {
|
|
1186
|
-
const uid = Number(
|
|
1187
|
+
const uid = Number(args2.uid);
|
|
1187
1188
|
if (!uid || uid < 1 || !Number.isInteger(uid)) {
|
|
1188
1189
|
throw new Error("uid must be a positive integer (relay UID from search results)");
|
|
1189
1190
|
}
|
|
@@ -1191,7 +1192,7 @@ Connected account (${result.relayResults[0].account}): ${result.relayResults.len
|
|
|
1191
1192
|
return result?.ok ? "Email imported to local inbox. You can now use list_inbox to find it and reply_email to continue the thread." : `Import failed: ${result?.error || "unknown error"}`;
|
|
1192
1193
|
}
|
|
1193
1194
|
case "reply_email": {
|
|
1194
|
-
const uid = Number(
|
|
1195
|
+
const uid = Number(args2.uid);
|
|
1195
1196
|
if (!uid || uid < 1) throw new Error("uid must be a positive integer");
|
|
1196
1197
|
const original = await apiRequest("GET", `/mail/messages/${uid}`);
|
|
1197
1198
|
if (!original) throw new Error("Original email not found");
|
|
@@ -1202,12 +1203,12 @@ Connected account (${result.relayResults[0].account}): ${result.relayResults.len
|
|
|
1202
1203
|
const refs = Array.isArray(original.references) ? [...original.references] : [];
|
|
1203
1204
|
if (original.messageId) refs.push(original.messageId);
|
|
1204
1205
|
const quotedBody = (original.text || "").split("\n").map((l) => `> ${l}`).join("\n");
|
|
1205
|
-
const fullText = `${
|
|
1206
|
+
const fullText = `${args2.text}
|
|
1206
1207
|
|
|
1207
1208
|
On ${original.date}, ${replyTo} wrote:
|
|
1208
1209
|
${quotedBody}`;
|
|
1209
1210
|
let to = replyTo;
|
|
1210
|
-
if (
|
|
1211
|
+
if (args2.replyAll) {
|
|
1211
1212
|
const allTo = [...original.to || [], ...original.cc || []].map((a) => a.address).filter(Boolean);
|
|
1212
1213
|
to = [replyTo, ...allTo].filter((v, i, a) => v && a.indexOf(v) === i).join(", ");
|
|
1213
1214
|
}
|
|
@@ -1215,7 +1216,7 @@ ${quotedBody}`;
|
|
|
1215
1216
|
to,
|
|
1216
1217
|
subject,
|
|
1217
1218
|
text: fullText,
|
|
1218
|
-
html:
|
|
1219
|
+
html: args2.html,
|
|
1219
1220
|
inReplyTo: original.messageId,
|
|
1220
1221
|
references: refs
|
|
1221
1222
|
};
|
|
@@ -1244,21 +1245,21 @@ ${sendResult.outboundWarnings.map((w) => ` [${w.severity?.toUpperCase()}] ${w.d
|
|
|
1244
1245
|
return response;
|
|
1245
1246
|
}
|
|
1246
1247
|
case "forward_email": {
|
|
1247
|
-
const uid = Number(
|
|
1248
|
+
const uid = Number(args2.uid);
|
|
1248
1249
|
if (!uid || uid < 1) throw new Error("uid must be a positive integer");
|
|
1249
1250
|
const orig = await apiRequest("GET", `/mail/messages/${uid}`);
|
|
1250
1251
|
if (!orig) throw new Error("Email not found");
|
|
1251
1252
|
const fwdSubject = (orig.subject ?? "").startsWith("Fwd:") ? orig.subject : `Fwd: ${orig.subject}`;
|
|
1252
1253
|
const origFrom = orig.from?.[0]?.address ?? "unknown";
|
|
1253
1254
|
const origTo = (orig.to || []).map((a) => a.address).join(", ");
|
|
1254
|
-
const fwdBody = `${
|
|
1255
|
+
const fwdBody = `${args2.text ? args2.text + "\n\n" : ""}---------- Forwarded message ----------
|
|
1255
1256
|
From: ${origFrom}
|
|
1256
1257
|
To: ${origTo}
|
|
1257
1258
|
Date: ${orig.date}
|
|
1258
1259
|
Subject: ${orig.subject}
|
|
1259
1260
|
|
|
1260
1261
|
${orig.text || ""}`;
|
|
1261
|
-
const fwdSendBody = { to:
|
|
1262
|
+
const fwdSendBody = { to: args2.to, subject: fwdSubject, text: fwdBody };
|
|
1262
1263
|
if (Array.isArray(orig.attachments) && orig.attachments.length > 0) {
|
|
1263
1264
|
fwdSendBody.attachments = orig.attachments.map((a) => ({
|
|
1264
1265
|
filename: a.filename,
|
|
@@ -1269,7 +1270,7 @@ ${orig.text || ""}`;
|
|
|
1269
1270
|
}
|
|
1270
1271
|
const fwdResult = await apiRequest("POST", "/mail/send", fwdSendBody);
|
|
1271
1272
|
if (fwdResult?.blocked && fwdResult?.pendingId) {
|
|
1272
|
-
scheduleFollowUp(fwdResult.pendingId, String(
|
|
1273
|
+
scheduleFollowUp(fwdResult.pendingId, String(args2.to), fwdSubject, makePendingCheck(fwdResult.pendingId));
|
|
1273
1274
|
return `Forward NOT sent \u2014 blocked by outbound guard.
|
|
1274
1275
|
${fwdResult.summary}
|
|
1275
1276
|
|
|
@@ -1282,7 +1283,7 @@ You MUST now:
|
|
|
1282
1283
|
3. If this forward is urgent or has a deadline, tell your owner about the time sensitivity.
|
|
1283
1284
|
4. Periodically check with manage_pending_emails(action='list') and follow up with your owner if still pending.`;
|
|
1284
1285
|
}
|
|
1285
|
-
let response = `Forwarded to ${
|
|
1286
|
+
let response = `Forwarded to ${args2.to}. Message ID: ${fwdResult?.messageId ?? "unknown"}`;
|
|
1286
1287
|
if (fwdResult?.outboundWarnings?.length) {
|
|
1287
1288
|
response += `
|
|
1288
1289
|
|
|
@@ -1292,19 +1293,19 @@ ${fwdResult.outboundWarnings.map((w) => ` [${w.severity?.toUpperCase()}] ${w.de
|
|
|
1292
1293
|
return response;
|
|
1293
1294
|
}
|
|
1294
1295
|
case "move_email": {
|
|
1295
|
-
const uid = Number(
|
|
1296
|
+
const uid = Number(args2.uid);
|
|
1296
1297
|
if (!uid || uid < 1) throw new Error("uid must be a positive integer");
|
|
1297
|
-
await apiRequest("POST", `/mail/messages/${uid}/move`, { from:
|
|
1298
|
-
return `Moved message UID ${uid} to ${
|
|
1298
|
+
await apiRequest("POST", `/mail/messages/${uid}/move`, { from: args2.from || "INBOX", to: args2.to });
|
|
1299
|
+
return `Moved message UID ${uid} to ${args2.to}`;
|
|
1299
1300
|
}
|
|
1300
1301
|
case "mark_unread": {
|
|
1301
|
-
const uid = Number(
|
|
1302
|
+
const uid = Number(args2.uid);
|
|
1302
1303
|
if (!uid || uid < 1) throw new Error("uid must be a positive integer");
|
|
1303
1304
|
await apiRequest("POST", `/mail/messages/${uid}/unseen`);
|
|
1304
1305
|
return `Marked message UID ${uid} as unread`;
|
|
1305
1306
|
}
|
|
1306
1307
|
case "mark_read": {
|
|
1307
|
-
const uid = Number(
|
|
1308
|
+
const uid = Number(args2.uid);
|
|
1308
1309
|
if (!uid || uid < 1) throw new Error("uid must be a positive integer");
|
|
1309
1310
|
await apiRequest("POST", `/mail/messages/${uid}/seen`);
|
|
1310
1311
|
return `Marked message UID ${uid} as read`;
|
|
@@ -1315,74 +1316,74 @@ ${fwdResult.outboundWarnings.map((w) => ` [${w.severity?.toUpperCase()}] ${w.de
|
|
|
1315
1316
|
return result.folders.map((f) => `${f.path}${f.specialUse ? ` (${f.specialUse})` : ""}`).join("\n");
|
|
1316
1317
|
}
|
|
1317
1318
|
case "list_folder": {
|
|
1318
|
-
const folder = encodeURIComponent(String(
|
|
1319
|
-
const limit = Math.min(Math.max(Number(
|
|
1320
|
-
const offset = Math.max(Number(
|
|
1319
|
+
const folder = encodeURIComponent(String(args2.folder));
|
|
1320
|
+
const limit = Math.min(Math.max(Number(args2.limit) || 20, 1), 100);
|
|
1321
|
+
const offset = Math.max(Number(args2.offset) || 0, 0);
|
|
1321
1322
|
const result = await apiRequest("GET", `/mail/folders/${folder}?limit=${limit}&offset=${offset}`);
|
|
1322
|
-
if (!result?.messages?.length) return `Folder "${
|
|
1323
|
+
if (!result?.messages?.length) return `Folder "${args2.folder}" is empty.`;
|
|
1323
1324
|
const lines = result.messages.map((m, i) => `${i + 1}. [UID:${m.uid}] From: ${m.from?.[0]?.address ?? "unknown"} | Subject: ${m.subject} | Date: ${m.date}`);
|
|
1324
|
-
return `${
|
|
1325
|
+
return `${args2.folder} (${result.total} total):
|
|
1325
1326
|
${lines.join("\n")}`;
|
|
1326
1327
|
}
|
|
1327
1328
|
case "batch_delete": {
|
|
1328
|
-
const uids =
|
|
1329
|
+
const uids = args2.uids;
|
|
1329
1330
|
if (!Array.isArray(uids) || uids.length === 0) throw new Error("uids array required");
|
|
1330
|
-
await apiRequest("POST", "/mail/batch/delete", { uids, folder:
|
|
1331
|
+
await apiRequest("POST", "/mail/batch/delete", { uids, folder: args2.folder });
|
|
1331
1332
|
return `Deleted ${uids.length} messages.`;
|
|
1332
1333
|
}
|
|
1333
1334
|
case "batch_mark_read": {
|
|
1334
|
-
const uids =
|
|
1335
|
+
const uids = args2.uids;
|
|
1335
1336
|
if (!Array.isArray(uids) || uids.length === 0) throw new Error("uids array required");
|
|
1336
|
-
await apiRequest("POST", "/mail/batch/seen", { uids, folder:
|
|
1337
|
+
await apiRequest("POST", "/mail/batch/seen", { uids, folder: args2.folder });
|
|
1337
1338
|
return `Marked ${uids.length} messages as read.`;
|
|
1338
1339
|
}
|
|
1339
1340
|
case "manage_contacts": {
|
|
1340
|
-
if (
|
|
1341
|
+
if (args2.action === "list") {
|
|
1341
1342
|
const r = await apiRequest("GET", "/contacts");
|
|
1342
1343
|
if (!r?.contacts?.length) return "No contacts.";
|
|
1343
1344
|
return r.contacts.map((c) => `${c.name || "(no name)"} <${c.email}>`).join("\n");
|
|
1344
1345
|
}
|
|
1345
|
-
if (
|
|
1346
|
-
if (!
|
|
1347
|
-
await apiRequest("POST", "/contacts", { email:
|
|
1348
|
-
return `Contact added: ${
|
|
1346
|
+
if (args2.action === "add") {
|
|
1347
|
+
if (!args2.email) throw new Error("email is required");
|
|
1348
|
+
await apiRequest("POST", "/contacts", { email: args2.email, name: args2.name });
|
|
1349
|
+
return `Contact added: ${args2.name || ""} <${args2.email}>`;
|
|
1349
1350
|
}
|
|
1350
|
-
if (
|
|
1351
|
-
if (!
|
|
1352
|
-
await apiRequest("DELETE", `/contacts/${
|
|
1351
|
+
if (args2.action === "delete") {
|
|
1352
|
+
if (!args2.id) throw new Error("id is required");
|
|
1353
|
+
await apiRequest("DELETE", `/contacts/${args2.id}`);
|
|
1353
1354
|
return "Contact deleted.";
|
|
1354
1355
|
}
|
|
1355
1356
|
throw new Error("Invalid action");
|
|
1356
1357
|
}
|
|
1357
1358
|
case "manage_drafts": {
|
|
1358
|
-
if (
|
|
1359
|
+
if (args2.action === "list") {
|
|
1359
1360
|
const r = await apiRequest("GET", "/drafts");
|
|
1360
1361
|
if (!r?.drafts?.length) return "No drafts.";
|
|
1361
1362
|
return r.drafts.map((d) => `[${d.id}] To: ${d.to_addr || "?"} | Subject: ${d.subject || "?"}`).join("\n");
|
|
1362
1363
|
}
|
|
1363
|
-
if (
|
|
1364
|
-
const r = await apiRequest("POST", "/drafts", { to:
|
|
1364
|
+
if (args2.action === "create") {
|
|
1365
|
+
const r = await apiRequest("POST", "/drafts", { to: args2.to, subject: args2.subject, text: args2.text });
|
|
1365
1366
|
return `Draft created: ${r?.id}`;
|
|
1366
1367
|
}
|
|
1367
|
-
if (
|
|
1368
|
-
if (!
|
|
1369
|
-
await apiRequest("PUT", `/drafts/${
|
|
1370
|
-
return `Draft ${
|
|
1368
|
+
if (args2.action === "update") {
|
|
1369
|
+
if (!args2.id) throw new Error("id is required");
|
|
1370
|
+
await apiRequest("PUT", `/drafts/${args2.id}`, { to: args2.to, subject: args2.subject, text: args2.text });
|
|
1371
|
+
return `Draft ${args2.id} updated.`;
|
|
1371
1372
|
}
|
|
1372
|
-
if (
|
|
1373
|
-
if (!
|
|
1374
|
-
const r = await apiRequest("POST", `/drafts/${
|
|
1373
|
+
if (args2.action === "send") {
|
|
1374
|
+
if (!args2.id) throw new Error("id is required");
|
|
1375
|
+
const r = await apiRequest("POST", `/drafts/${args2.id}/send`);
|
|
1375
1376
|
return `Draft sent. Message ID: ${r?.messageId ?? "unknown"}`;
|
|
1376
1377
|
}
|
|
1377
|
-
if (
|
|
1378
|
-
if (!
|
|
1379
|
-
await apiRequest("DELETE", `/drafts/${
|
|
1378
|
+
if (args2.action === "delete") {
|
|
1379
|
+
if (!args2.id) throw new Error("id is required");
|
|
1380
|
+
await apiRequest("DELETE", `/drafts/${args2.id}`);
|
|
1380
1381
|
return "Draft deleted.";
|
|
1381
1382
|
}
|
|
1382
1383
|
throw new Error("Invalid action");
|
|
1383
1384
|
}
|
|
1384
1385
|
case "manage_scheduled": {
|
|
1385
|
-
const action =
|
|
1386
|
+
const action = args2.action || "create";
|
|
1386
1387
|
if (action === "list") {
|
|
1387
1388
|
const r2 = await apiRequest("GET", "/scheduled");
|
|
1388
1389
|
if (!r2?.scheduled?.length) return "No scheduled emails.";
|
|
@@ -1391,61 +1392,61 @@ ${lines.join("\n")}`;
|
|
|
1391
1392
|
).join("\n");
|
|
1392
1393
|
}
|
|
1393
1394
|
if (action === "cancel") {
|
|
1394
|
-
if (!
|
|
1395
|
-
await apiRequest("DELETE", `/scheduled/${
|
|
1395
|
+
if (!args2.id) throw new Error("id is required");
|
|
1396
|
+
await apiRequest("DELETE", `/scheduled/${args2.id}`);
|
|
1396
1397
|
return "Scheduled email cancelled.";
|
|
1397
1398
|
}
|
|
1398
1399
|
const r = await apiRequest("POST", "/scheduled", {
|
|
1399
|
-
to:
|
|
1400
|
-
subject:
|
|
1401
|
-
text:
|
|
1402
|
-
sendAt:
|
|
1400
|
+
to: args2.to,
|
|
1401
|
+
subject: args2.subject,
|
|
1402
|
+
text: args2.text,
|
|
1403
|
+
sendAt: args2.sendAt
|
|
1403
1404
|
});
|
|
1404
1405
|
return `Email scheduled for ${r?.sendAt}. ID: ${r?.id}`;
|
|
1405
1406
|
}
|
|
1406
1407
|
case "create_folder": {
|
|
1407
|
-
if (!
|
|
1408
|
-
await apiRequest("POST", "/mail/folders", { name:
|
|
1409
|
-
return `Folder "${
|
|
1408
|
+
if (!args2.name) throw new Error("name is required");
|
|
1409
|
+
await apiRequest("POST", "/mail/folders", { name: args2.name });
|
|
1410
|
+
return `Folder "${args2.name}" created successfully.`;
|
|
1410
1411
|
}
|
|
1411
1412
|
case "manage_tags": {
|
|
1412
|
-
const action =
|
|
1413
|
+
const action = args2.action;
|
|
1413
1414
|
if (action === "list") {
|
|
1414
1415
|
const r = await apiRequest("GET", "/tags");
|
|
1415
1416
|
if (!r?.tags?.length) return "No tags.";
|
|
1416
1417
|
return r.tags.map((t) => `[${t.id.slice(0, 8)}] ${t.name} (${t.color})`).join("\n");
|
|
1417
1418
|
}
|
|
1418
1419
|
if (action === "create") {
|
|
1419
|
-
if (!
|
|
1420
|
-
const r = await apiRequest("POST", "/tags", { name:
|
|
1421
|
-
return `Tag "${
|
|
1420
|
+
if (!args2.name) throw new Error("name is required");
|
|
1421
|
+
const r = await apiRequest("POST", "/tags", { name: args2.name, color: args2.color });
|
|
1422
|
+
return `Tag "${args2.name}" created (${r?.color}). ID: ${r?.id}`;
|
|
1422
1423
|
}
|
|
1423
1424
|
if (action === "delete") {
|
|
1424
|
-
if (!
|
|
1425
|
-
await apiRequest("DELETE", `/tags/${
|
|
1425
|
+
if (!args2.id) throw new Error("id is required");
|
|
1426
|
+
await apiRequest("DELETE", `/tags/${args2.id}`);
|
|
1426
1427
|
return "Tag deleted.";
|
|
1427
1428
|
}
|
|
1428
1429
|
if (action === "tag_message") {
|
|
1429
|
-
if (!
|
|
1430
|
-
await apiRequest("POST", `/tags/${
|
|
1431
|
-
return `Tagged message UID ${
|
|
1430
|
+
if (!args2.id || !args2.uid) throw new Error("id and uid are required");
|
|
1431
|
+
await apiRequest("POST", `/tags/${args2.id}/messages`, { uid: args2.uid, folder: args2.folder });
|
|
1432
|
+
return `Tagged message UID ${args2.uid} with tag ${args2.id}`;
|
|
1432
1433
|
}
|
|
1433
1434
|
if (action === "untag_message") {
|
|
1434
|
-
if (!
|
|
1435
|
-
const folder =
|
|
1436
|
-
await apiRequest("DELETE", `/tags/${
|
|
1437
|
-
return `Removed tag from message UID ${
|
|
1435
|
+
if (!args2.id || !args2.uid) throw new Error("id and uid are required");
|
|
1436
|
+
const folder = args2.folder || "INBOX";
|
|
1437
|
+
await apiRequest("DELETE", `/tags/${args2.id}/messages/${args2.uid}?folder=${encodeURIComponent(folder)}`);
|
|
1438
|
+
return `Removed tag from message UID ${args2.uid} in ${folder}`;
|
|
1438
1439
|
}
|
|
1439
1440
|
if (action === "get_messages") {
|
|
1440
|
-
if (!
|
|
1441
|
-
const r = await apiRequest("GET", `/tags/${
|
|
1441
|
+
if (!args2.id) throw new Error("id is required");
|
|
1442
|
+
const r = await apiRequest("GET", `/tags/${args2.id}/messages`);
|
|
1442
1443
|
if (!r?.messages?.length) return `No messages with this tag.`;
|
|
1443
1444
|
return `Tag "${r.tag.name}" \u2014 ${r.messages.length} messages:
|
|
1444
1445
|
${r.messages.map((m) => ` UID ${m.uid} (${m.folder})`).join("\n")}`;
|
|
1445
1446
|
}
|
|
1446
1447
|
if (action === "get_message_tags") {
|
|
1447
|
-
if (!
|
|
1448
|
-
const r = await apiRequest("GET", `/messages/${
|
|
1448
|
+
if (!args2.uid) throw new Error("uid is required");
|
|
1449
|
+
const r = await apiRequest("GET", `/messages/${args2.uid}/tags`);
|
|
1449
1450
|
if (!r?.tags?.length) return "No tags on this message.";
|
|
1450
1451
|
return r.tags.map((t) => `[${t.id.slice(0, 8)}] ${t.name} (${t.color})`).join("\n");
|
|
1451
1452
|
}
|
|
@@ -1453,9 +1454,9 @@ ${r.messages.map((m) => ` UID ${m.uid} (${m.folder})`).join("\n")}`;
|
|
|
1453
1454
|
}
|
|
1454
1455
|
case "create_account": {
|
|
1455
1456
|
const result = await apiRequest("POST", "/accounts", {
|
|
1456
|
-
name:
|
|
1457
|
-
domain:
|
|
1458
|
-
role:
|
|
1457
|
+
name: args2.name,
|
|
1458
|
+
domain: args2.domain,
|
|
1459
|
+
role: args2.role
|
|
1459
1460
|
}, useMaster);
|
|
1460
1461
|
if (!result) throw new Error("No response from account creation");
|
|
1461
1462
|
return [
|
|
@@ -1469,16 +1470,16 @@ ${r.messages.map((m) => ` UID ${m.uid} (${m.folder})`).join("\n")}`;
|
|
|
1469
1470
|
}
|
|
1470
1471
|
case "setup_email_relay": {
|
|
1471
1472
|
const result = await apiRequest("POST", "/gateway/relay", {
|
|
1472
|
-
provider:
|
|
1473
|
-
email:
|
|
1474
|
-
password:
|
|
1475
|
-
smtpHost:
|
|
1476
|
-
smtpPort:
|
|
1477
|
-
imapHost:
|
|
1478
|
-
imapPort:
|
|
1479
|
-
agentName:
|
|
1480
|
-
agentRole:
|
|
1481
|
-
skipDefaultAgent:
|
|
1473
|
+
provider: args2.provider,
|
|
1474
|
+
email: args2.email,
|
|
1475
|
+
password: args2.password,
|
|
1476
|
+
smtpHost: args2.smtpHost,
|
|
1477
|
+
smtpPort: args2.smtpPort,
|
|
1478
|
+
imapHost: args2.imapHost,
|
|
1479
|
+
imapPort: args2.imapPort,
|
|
1480
|
+
agentName: args2.agentName,
|
|
1481
|
+
agentRole: args2.agentRole,
|
|
1482
|
+
skipDefaultAgent: args2.skipDefaultAgent
|
|
1482
1483
|
}, useMaster);
|
|
1483
1484
|
if (!result) throw new Error("No response from relay setup");
|
|
1484
1485
|
const lines = [
|
|
@@ -1498,11 +1499,11 @@ ${r.messages.map((m) => ` UID ${m.uid} (${m.folder})`).join("\n")}`;
|
|
|
1498
1499
|
}
|
|
1499
1500
|
case "setup_email_domain": {
|
|
1500
1501
|
const result = await apiRequest("POST", "/gateway/domain", {
|
|
1501
|
-
cloudflareToken:
|
|
1502
|
-
cloudflareAccountId:
|
|
1503
|
-
domain:
|
|
1504
|
-
purchase:
|
|
1505
|
-
gmailRelay:
|
|
1502
|
+
cloudflareToken: args2.cloudflareToken,
|
|
1503
|
+
cloudflareAccountId: args2.cloudflareAccountId,
|
|
1504
|
+
domain: args2.domain,
|
|
1505
|
+
purchase: args2.purchase,
|
|
1506
|
+
gmailRelay: args2.gmailRelay
|
|
1506
1507
|
}, useMaster);
|
|
1507
1508
|
if (!result) throw new Error("No response from domain setup");
|
|
1508
1509
|
const lines = [
|
|
@@ -1542,8 +1543,8 @@ ${r.messages.map((m) => ` UID ${m.uid} (${m.folder})`).join("\n")}`;
|
|
|
1542
1543
|
}
|
|
1543
1544
|
case "setup_gmail_alias": {
|
|
1544
1545
|
const result = await apiRequest("POST", "/gateway/domain/alias-setup", {
|
|
1545
|
-
agentEmail:
|
|
1546
|
-
agentDisplayName:
|
|
1546
|
+
agentEmail: args2.agentEmail,
|
|
1547
|
+
agentDisplayName: args2.agentDisplayName
|
|
1547
1548
|
}, useMaster);
|
|
1548
1549
|
if (!result?.instructions) throw new Error("No response from alias setup");
|
|
1549
1550
|
const lines = [
|
|
@@ -1580,8 +1581,8 @@ ${r.messages.map((m) => ` UID ${m.uid} (${m.folder})`).join("\n")}`;
|
|
|
1580
1581
|
}
|
|
1581
1582
|
case "purchase_domain": {
|
|
1582
1583
|
const result = await apiRequest("POST", "/gateway/domain/purchase", {
|
|
1583
|
-
keywords:
|
|
1584
|
-
tld:
|
|
1584
|
+
keywords: args2.keywords,
|
|
1585
|
+
tld: args2.tld
|
|
1585
1586
|
}, useMaster);
|
|
1586
1587
|
if (!result?.domains?.length) return "No domains found.";
|
|
1587
1588
|
const lines = result.domains.map(
|
|
@@ -1603,7 +1604,7 @@ ${lines.join("\n")}`;
|
|
|
1603
1604
|
return lines.join("\n");
|
|
1604
1605
|
}
|
|
1605
1606
|
case "send_test_email": {
|
|
1606
|
-
const result = await apiRequest("POST", "/gateway/test", { to:
|
|
1607
|
+
const result = await apiRequest("POST", "/gateway/test", { to: args2.to }, useMaster);
|
|
1607
1608
|
return `Test email sent! Message ID: ${result?.messageId ?? "unknown"}`;
|
|
1608
1609
|
}
|
|
1609
1610
|
case "list_agents": {
|
|
@@ -1615,7 +1616,7 @@ ${lines.join("\n")}`;
|
|
|
1615
1616
|
${lines.join("\n")}`;
|
|
1616
1617
|
}
|
|
1617
1618
|
case "message_agent": {
|
|
1618
|
-
const agentName = String(
|
|
1619
|
+
const agentName = String(args2.agent ?? "").toLowerCase().trim();
|
|
1619
1620
|
if (!agentName) throw new Error("agent name is required");
|
|
1620
1621
|
try {
|
|
1621
1622
|
await apiRequest("GET", `/accounts/directory/${encodeURIComponent(agentName)}`);
|
|
@@ -1623,9 +1624,9 @@ ${lines.join("\n")}`;
|
|
|
1623
1624
|
throw new Error(`Agent "${agentName}" not found. Use list_agents to see available agents.`);
|
|
1624
1625
|
}
|
|
1625
1626
|
const to = `${agentName}@localhost`;
|
|
1626
|
-
const priority = String(
|
|
1627
|
-
const subject = priority === "urgent" ? `[URGENT] ${
|
|
1628
|
-
const result = await apiRequest("POST", "/mail/send", { to, subject, text:
|
|
1627
|
+
const priority = String(args2.priority ?? "normal");
|
|
1628
|
+
const subject = priority === "urgent" ? `[URGENT] ${args2.subject}` : priority === "high" ? `[HIGH] ${args2.subject}` : String(args2.subject);
|
|
1629
|
+
const result = await apiRequest("POST", "/mail/send", { to, subject, text: args2.text });
|
|
1629
1630
|
return `Message sent to ${to}. Message ID: ${result?.messageId ?? "unknown"}`;
|
|
1630
1631
|
}
|
|
1631
1632
|
case "check_messages": {
|
|
@@ -1650,13 +1651,13 @@ ${lines.join("\n")}`;
|
|
|
1650
1651
|
${details.join("\n")}${more}`;
|
|
1651
1652
|
}
|
|
1652
1653
|
case "delete_agent": {
|
|
1653
|
-
const agentName = String(
|
|
1654
|
+
const agentName = String(args2.name ?? "").trim();
|
|
1654
1655
|
if (!agentName) throw new Error("name is required");
|
|
1655
1656
|
const agents = await apiRequest("GET", "/accounts", void 0, true);
|
|
1656
1657
|
const fullAgent = agents?.agents?.find((a) => a.name === agentName);
|
|
1657
1658
|
if (!fullAgent) throw new Error(`Agent "${agentName}" not found`);
|
|
1658
1659
|
const qs = new URLSearchParams({ archive: "true", deletedBy: "mcp-tool" });
|
|
1659
|
-
if (
|
|
1660
|
+
if (args2.reason) qs.set("reason", String(args2.reason));
|
|
1660
1661
|
const report = await apiRequest("DELETE", `/accounts/${fullAgent.id}?${qs.toString()}`, void 0, true);
|
|
1661
1662
|
const lines = [
|
|
1662
1663
|
`Agent "${agentName}" deleted successfully.`,
|
|
@@ -1670,8 +1671,8 @@ ${details.join("\n")}${more}`;
|
|
|
1670
1671
|
return lines.join("\n");
|
|
1671
1672
|
}
|
|
1672
1673
|
case "deletion_reports": {
|
|
1673
|
-
if (
|
|
1674
|
-
const report = await apiRequest("GET", `/accounts/deletions/${encodeURIComponent(String(
|
|
1674
|
+
if (args2.id) {
|
|
1675
|
+
const report = await apiRequest("GET", `/accounts/deletions/${encodeURIComponent(String(args2.id))}`, void 0, true);
|
|
1675
1676
|
if (!report) throw new Error("Deletion report not found");
|
|
1676
1677
|
const lines2 = [
|
|
1677
1678
|
`Deletion Report: ${report.id}`,
|
|
@@ -1699,52 +1700,52 @@ ${details.join("\n")}${more}`;
|
|
|
1699
1700
|
${lines.join("\n")}`;
|
|
1700
1701
|
}
|
|
1701
1702
|
case "manage_signatures": {
|
|
1702
|
-
if (
|
|
1703
|
+
if (args2.action === "list") {
|
|
1703
1704
|
const r = await apiRequest("GET", "/signatures");
|
|
1704
1705
|
if (!r?.signatures?.length) return "No signatures.";
|
|
1705
1706
|
return r.signatures.map((s) => `[${s.id}] ${s.name}${s.isDefault ? " (default)" : ""}: ${s.text}`).join("\n");
|
|
1706
1707
|
}
|
|
1707
|
-
if (
|
|
1708
|
-
if (!
|
|
1709
|
-
const r = await apiRequest("POST", "/signatures", { name:
|
|
1710
|
-
return `Signature "${
|
|
1708
|
+
if (args2.action === "create") {
|
|
1709
|
+
if (!args2.name || !args2.text) throw new Error("name and text are required");
|
|
1710
|
+
const r = await apiRequest("POST", "/signatures", { name: args2.name, text: args2.text, isDefault: args2.isDefault });
|
|
1711
|
+
return `Signature "${args2.name}" created. ID: ${r?.id}`;
|
|
1711
1712
|
}
|
|
1712
|
-
if (
|
|
1713
|
-
if (!
|
|
1714
|
-
await apiRequest("DELETE", `/signatures/${
|
|
1713
|
+
if (args2.action === "delete") {
|
|
1714
|
+
if (!args2.id) throw new Error("id is required");
|
|
1715
|
+
await apiRequest("DELETE", `/signatures/${args2.id}`);
|
|
1715
1716
|
return "Signature deleted.";
|
|
1716
1717
|
}
|
|
1717
1718
|
throw new Error("Invalid action. Use: list, create, or delete");
|
|
1718
1719
|
}
|
|
1719
1720
|
case "manage_templates": {
|
|
1720
|
-
if (
|
|
1721
|
+
if (args2.action === "list") {
|
|
1721
1722
|
const r = await apiRequest("GET", "/templates");
|
|
1722
1723
|
if (!r?.templates?.length) return "No templates.";
|
|
1723
1724
|
return r.templates.map((t) => `[${t.id}] ${t.name}: ${t.subject}`).join("\n");
|
|
1724
1725
|
}
|
|
1725
|
-
if (
|
|
1726
|
-
if (!
|
|
1727
|
-
const r = await apiRequest("POST", "/templates", { name:
|
|
1728
|
-
return `Template "${
|
|
1726
|
+
if (args2.action === "create") {
|
|
1727
|
+
if (!args2.name || !args2.subject || !args2.text) throw new Error("name, subject, and text are required");
|
|
1728
|
+
const r = await apiRequest("POST", "/templates", { name: args2.name, subject: args2.subject, text: args2.text });
|
|
1729
|
+
return `Template "${args2.name}" created. ID: ${r?.id}`;
|
|
1729
1730
|
}
|
|
1730
|
-
if (
|
|
1731
|
-
if (!
|
|
1732
|
-
await apiRequest("DELETE", `/templates/${
|
|
1731
|
+
if (args2.action === "delete") {
|
|
1732
|
+
if (!args2.id) throw new Error("id is required");
|
|
1733
|
+
await apiRequest("DELETE", `/templates/${args2.id}`);
|
|
1733
1734
|
return "Template deleted.";
|
|
1734
1735
|
}
|
|
1735
1736
|
throw new Error("Invalid action. Use: list, create, or delete");
|
|
1736
1737
|
}
|
|
1737
1738
|
case "batch_mark_unread": {
|
|
1738
|
-
const uids =
|
|
1739
|
+
const uids = args2.uids;
|
|
1739
1740
|
if (!Array.isArray(uids) || uids.length === 0) throw new Error("uids array required");
|
|
1740
|
-
await apiRequest("POST", "/mail/batch/unseen", { uids, folder:
|
|
1741
|
+
await apiRequest("POST", "/mail/batch/unseen", { uids, folder: args2.folder });
|
|
1741
1742
|
return `Marked ${uids.length} messages as unread.`;
|
|
1742
1743
|
}
|
|
1743
1744
|
case "batch_move": {
|
|
1744
|
-
const uids =
|
|
1745
|
+
const uids = args2.uids;
|
|
1745
1746
|
if (!Array.isArray(uids) || uids.length === 0) throw new Error("uids array required");
|
|
1746
|
-
await apiRequest("POST", "/mail/batch/move", { uids, from:
|
|
1747
|
-
return `Moved ${uids.length} messages to ${
|
|
1747
|
+
await apiRequest("POST", "/mail/batch/move", { uids, from: args2.from || "INBOX", to: args2.to });
|
|
1748
|
+
return `Moved ${uids.length} messages to ${args2.to}.`;
|
|
1748
1749
|
}
|
|
1749
1750
|
case "whoami": {
|
|
1750
1751
|
const result = await apiRequest("GET", "/accounts/me");
|
|
@@ -1759,8 +1760,8 @@ ${lines.join("\n")}`;
|
|
|
1759
1760
|
].filter(Boolean).join("\n");
|
|
1760
1761
|
}
|
|
1761
1762
|
case "update_metadata": {
|
|
1762
|
-
if (!
|
|
1763
|
-
const result = await apiRequest("PATCH", "/accounts/me", { metadata:
|
|
1763
|
+
if (!args2.metadata || typeof args2.metadata !== "object") throw new Error("metadata object is required");
|
|
1764
|
+
const result = await apiRequest("PATCH", "/accounts/me", { metadata: args2.metadata });
|
|
1764
1765
|
return `Metadata updated successfully. Agent: ${result?.name ?? "unknown"}`;
|
|
1765
1766
|
}
|
|
1766
1767
|
case "check_health": {
|
|
@@ -1769,7 +1770,7 @@ ${lines.join("\n")}`;
|
|
|
1769
1770
|
return `\u{1F380} AgenticMail server: ${result.status ?? "ok"}${result.stalwart ? `, Stalwart: ${result.stalwart}` : ""}`;
|
|
1770
1771
|
}
|
|
1771
1772
|
case "wait_for_email": {
|
|
1772
|
-
const timeoutSec = Math.min(Math.max(Number(
|
|
1773
|
+
const timeoutSec = Math.min(Math.max(Number(args2.timeout) || 120, 5), 300);
|
|
1773
1774
|
const controller = new AbortController();
|
|
1774
1775
|
const timer = setTimeout(() => controller.abort(), timeoutSec * 1e3);
|
|
1775
1776
|
try {
|
|
@@ -1884,9 +1885,9 @@ ${lines.join("\n")}`;
|
|
|
1884
1885
|
}
|
|
1885
1886
|
}
|
|
1886
1887
|
case "batch_read": {
|
|
1887
|
-
const uids =
|
|
1888
|
+
const uids = args2.uids;
|
|
1888
1889
|
if (!Array.isArray(uids) || uids.length === 0) throw new Error("uids array required");
|
|
1889
|
-
const result = await apiRequest("POST", "/mail/batch/read", { uids, folder:
|
|
1890
|
+
const result = await apiRequest("POST", "/mail/batch/read", { uids, folder: args2.folder });
|
|
1890
1891
|
if (!result?.messages?.length) return "No messages found for the given UIDs.";
|
|
1891
1892
|
const lines = result.messages.map((m) => {
|
|
1892
1893
|
const from = m.from?.map((a) => a.address).join(", ") ?? "unknown";
|
|
@@ -1899,10 +1900,10 @@ ${lines.join("\n\n---\n\n")}`;
|
|
|
1899
1900
|
}
|
|
1900
1901
|
case "inbox_digest": {
|
|
1901
1902
|
const qs = new URLSearchParams();
|
|
1902
|
-
if (
|
|
1903
|
-
if (
|
|
1904
|
-
if (
|
|
1905
|
-
if (
|
|
1903
|
+
if (args2.limit) qs.set("limit", String(args2.limit));
|
|
1904
|
+
if (args2.offset) qs.set("offset", String(args2.offset));
|
|
1905
|
+
if (args2.folder) qs.set("folder", String(args2.folder));
|
|
1906
|
+
if (args2.previewLength) qs.set("previewLength", String(args2.previewLength));
|
|
1906
1907
|
const query = qs.toString();
|
|
1907
1908
|
const result = await apiRequest("GET", `/mail/digest${query ? "?" + query : ""}`);
|
|
1908
1909
|
if (!result?.messages?.length) return "Inbox is empty.";
|
|
@@ -1916,16 +1917,16 @@ ${lines.join("\n\n---\n\n")}`;
|
|
|
1916
1917
|
${lines.join("\n")}`;
|
|
1917
1918
|
}
|
|
1918
1919
|
case "template_send": {
|
|
1919
|
-
const result = await apiRequest("POST", `/templates/${
|
|
1920
|
-
to:
|
|
1921
|
-
variables:
|
|
1922
|
-
cc:
|
|
1923
|
-
bcc:
|
|
1920
|
+
const result = await apiRequest("POST", `/templates/${args2.id}/send`, {
|
|
1921
|
+
to: args2.to,
|
|
1922
|
+
variables: args2.variables,
|
|
1923
|
+
cc: args2.cc,
|
|
1924
|
+
bcc: args2.bcc
|
|
1924
1925
|
});
|
|
1925
1926
|
return `Template email sent. Message ID: ${result?.messageId ?? "unknown"}`;
|
|
1926
1927
|
}
|
|
1927
1928
|
case "manage_rules": {
|
|
1928
|
-
if (
|
|
1929
|
+
if (args2.action === "list") {
|
|
1929
1930
|
const r = await apiRequest("GET", "/rules");
|
|
1930
1931
|
if (!r?.rules?.length) return "No email rules configured.";
|
|
1931
1932
|
return r.rules.map(
|
|
@@ -1934,25 +1935,25 @@ ${lines.join("\n")}`;
|
|
|
1934
1935
|
Actions: ${JSON.stringify(rule.actions)}`
|
|
1935
1936
|
).join("\n");
|
|
1936
1937
|
}
|
|
1937
|
-
if (
|
|
1938
|
+
if (args2.action === "create") {
|
|
1938
1939
|
const r = await apiRequest("POST", "/rules", {
|
|
1939
|
-
name:
|
|
1940
|
-
priority:
|
|
1941
|
-
conditions:
|
|
1942
|
-
actions:
|
|
1940
|
+
name: args2.name,
|
|
1941
|
+
priority: args2.priority,
|
|
1942
|
+
conditions: args2.conditions,
|
|
1943
|
+
actions: args2.actions
|
|
1943
1944
|
});
|
|
1944
1945
|
return `Rule "${r?.name}" created. ID: ${r?.id}`;
|
|
1945
1946
|
}
|
|
1946
|
-
if (
|
|
1947
|
-
if (!
|
|
1948
|
-
await apiRequest("DELETE", `/rules/${
|
|
1947
|
+
if (args2.action === "delete") {
|
|
1948
|
+
if (!args2.id) throw new Error("id is required");
|
|
1949
|
+
await apiRequest("DELETE", `/rules/${args2.id}`);
|
|
1949
1950
|
return "Rule deleted.";
|
|
1950
1951
|
}
|
|
1951
1952
|
throw new Error("Invalid action. Use: list, create, or delete");
|
|
1952
1953
|
}
|
|
1953
1954
|
case "cleanup_agents": {
|
|
1954
|
-
if (
|
|
1955
|
-
const qs =
|
|
1955
|
+
if (args2.action === "list_inactive") {
|
|
1956
|
+
const qs = args2.hours ? `?hours=${args2.hours}` : "";
|
|
1956
1957
|
const r = await apiRequest("GET", `/accounts/inactive${qs}`, void 0, true);
|
|
1957
1958
|
if (!r?.agents?.length) return "No inactive agents found. All agents are either active or persistent.";
|
|
1958
1959
|
return `${r.count} inactive agent(s):
|
|
@@ -1960,8 +1961,8 @@ ${r.agents.map(
|
|
|
1960
1961
|
(a) => ` ${a.name} (${a.email}) \u2014 last active: ${a.last_activity_at || "never"}, persistent: ${a.persistent}`
|
|
1961
1962
|
).join("\n")}`;
|
|
1962
1963
|
}
|
|
1963
|
-
if (
|
|
1964
|
-
const r = await apiRequest("POST", "/accounts/cleanup", { hours:
|
|
1964
|
+
if (args2.action === "cleanup") {
|
|
1965
|
+
const r = await apiRequest("POST", "/accounts/cleanup", { hours: args2.hours, dryRun: args2.dryRun }, true);
|
|
1965
1966
|
if (r?.dryRun) {
|
|
1966
1967
|
if (!r.count) return "No inactive agents to clean up. All agents are either active or persistent.";
|
|
1967
1968
|
return `Would delete ${r.count} agent(s): ${r.wouldDelete.map((a) => a.name).join(", ")}`;
|
|
@@ -1969,39 +1970,39 @@ ${r.agents.map(
|
|
|
1969
1970
|
if (!r?.count) return "No inactive agents to clean up. All agents are either active or persistent.";
|
|
1970
1971
|
return `Deleted ${r.count} agent(s): ${r.deleted.join(", ")}`;
|
|
1971
1972
|
}
|
|
1972
|
-
if (
|
|
1973
|
-
if (!
|
|
1974
|
-
await apiRequest("PATCH", `/accounts/${
|
|
1975
|
-
return `Agent ${
|
|
1973
|
+
if (args2.action === "set_persistent") {
|
|
1974
|
+
if (!args2.agentId) throw new Error("agentId is required");
|
|
1975
|
+
await apiRequest("PATCH", `/accounts/${args2.agentId}/persistent`, { persistent: args2.persistent !== false }, true);
|
|
1976
|
+
return `Agent ${args2.agentId} persistent flag set to ${args2.persistent !== false}`;
|
|
1976
1977
|
}
|
|
1977
1978
|
throw new Error("Invalid action. Use: list_inactive, cleanup, or set_persistent");
|
|
1978
1979
|
}
|
|
1979
1980
|
case "check_tasks": {
|
|
1980
|
-
let endpoint =
|
|
1981
|
-
if (
|
|
1982
|
-
endpoint += `?assignee=${encodeURIComponent(
|
|
1981
|
+
let endpoint = args2.direction === "outgoing" ? "/tasks/assigned" : "/tasks/pending";
|
|
1982
|
+
if (args2.direction !== "outgoing" && args2.assignee) {
|
|
1983
|
+
endpoint += `?assignee=${encodeURIComponent(args2.assignee)}`;
|
|
1983
1984
|
}
|
|
1984
1985
|
const r = await apiRequest("GET", endpoint);
|
|
1985
|
-
if (!r?.tasks?.length) return
|
|
1986
|
+
if (!r?.tasks?.length) return args2.direction === "outgoing" ? "No tasks assigned by you." : "No pending tasks.";
|
|
1986
1987
|
return `${r.count} tasks:
|
|
1987
1988
|
${r.tasks.map(
|
|
1988
1989
|
(t) => ` [${t.id.slice(0, 8)}] ${t.taskType} \u2014 status: ${t.status}, payload: ${JSON.stringify(t.payload).slice(0, 100)}`
|
|
1989
1990
|
).join("\n")}`;
|
|
1990
1991
|
}
|
|
1991
1992
|
case "claim_task": {
|
|
1992
|
-
const r = await apiRequest("POST", `/tasks/${
|
|
1993
|
+
const r = await apiRequest("POST", `/tasks/${args2.id}/claim`);
|
|
1993
1994
|
return `Task ${r?.id} claimed. Payload: ${JSON.stringify(r?.payload)}`;
|
|
1994
1995
|
}
|
|
1995
1996
|
case "submit_result": {
|
|
1996
|
-
await apiRequest("POST", `/tasks/${
|
|
1997
|
-
return `Result submitted for task ${
|
|
1997
|
+
await apiRequest("POST", `/tasks/${args2.id}/result`, { result: args2.result });
|
|
1998
|
+
return `Result submitted for task ${args2.id}.`;
|
|
1998
1999
|
}
|
|
1999
2000
|
case "call_agent": {
|
|
2000
|
-
const timeoutSec = Math.min(Math.max(Number(
|
|
2001
|
+
const timeoutSec = Math.min(Math.max(Number(args2.timeout) || 180, 5), 300);
|
|
2001
2002
|
const created = await apiRequest("POST", "/tasks/assign", {
|
|
2002
|
-
assignee:
|
|
2003
|
+
assignee: args2.target,
|
|
2003
2004
|
taskType: "rpc",
|
|
2004
|
-
payload: { task:
|
|
2005
|
+
payload: { task: args2.task, ...args2.payload || {} }
|
|
2005
2006
|
});
|
|
2006
2007
|
if (!created?.id) throw new Error("Failed to create task");
|
|
2007
2008
|
const taskId = created.id;
|
|
@@ -2018,11 +2019,11 @@ ${r.tasks.map(
|
|
|
2018
2019
|
return `RPC timed out. Task ID: ${taskId} \u2014 check later with check_tasks.`;
|
|
2019
2020
|
}
|
|
2020
2021
|
case "manage_spam": {
|
|
2021
|
-
const action =
|
|
2022
|
+
const action = args2.action;
|
|
2022
2023
|
if (action === "list") {
|
|
2023
2024
|
const qs = new URLSearchParams();
|
|
2024
|
-
if (
|
|
2025
|
-
if (
|
|
2025
|
+
if (args2.limit) qs.set("limit", String(args2.limit));
|
|
2026
|
+
if (args2.offset) qs.set("offset", String(args2.offset));
|
|
2026
2027
|
const query = qs.toString();
|
|
2027
2028
|
const result = await apiRequest("GET", `/mail/spam${query ? "?" + query : ""}`);
|
|
2028
2029
|
if (!result?.messages?.length) return "Spam folder is empty.";
|
|
@@ -2031,21 +2032,21 @@ ${r.tasks.map(
|
|
|
2031
2032
|
${lines.join("\n")}`;
|
|
2032
2033
|
}
|
|
2033
2034
|
if (action === "report") {
|
|
2034
|
-
const uid = Number(
|
|
2035
|
+
const uid = Number(args2.uid);
|
|
2035
2036
|
if (!uid || uid < 1) throw new Error("uid is required");
|
|
2036
|
-
await apiRequest("POST", `/mail/messages/${uid}/spam`, { folder:
|
|
2037
|
+
await apiRequest("POST", `/mail/messages/${uid}/spam`, { folder: args2.folder || "INBOX" });
|
|
2037
2038
|
return `Message UID ${uid} moved to Spam.`;
|
|
2038
2039
|
}
|
|
2039
2040
|
if (action === "not_spam") {
|
|
2040
|
-
const uid = Number(
|
|
2041
|
+
const uid = Number(args2.uid);
|
|
2041
2042
|
if (!uid || uid < 1) throw new Error("uid is required");
|
|
2042
2043
|
await apiRequest("POST", `/mail/messages/${uid}/not-spam`);
|
|
2043
2044
|
return `Message UID ${uid} moved from Spam to INBOX.`;
|
|
2044
2045
|
}
|
|
2045
2046
|
if (action === "score") {
|
|
2046
|
-
const uid = Number(
|
|
2047
|
+
const uid = Number(args2.uid);
|
|
2047
2048
|
if (!uid || uid < 1) throw new Error("uid is required");
|
|
2048
|
-
const folder =
|
|
2049
|
+
const folder = args2.folder || "INBOX";
|
|
2049
2050
|
const result = await apiRequest("GET", `/mail/messages/${uid}/spam-score?folder=${encodeURIComponent(folder)}`);
|
|
2050
2051
|
const lines = [
|
|
2051
2052
|
`Spam Score: ${result.score}/100 (${result.isSpam ? "SPAM" : result.isWarning ? "WARNING" : "CLEAN"})`,
|
|
@@ -2062,7 +2063,7 @@ ${lines.join("\n")}`;
|
|
|
2062
2063
|
throw new Error("Invalid action. Use: list, report, not_spam, or score");
|
|
2063
2064
|
}
|
|
2064
2065
|
case "manage_pending_emails": {
|
|
2065
|
-
const action = String(
|
|
2066
|
+
const action = String(args2.action);
|
|
2066
2067
|
if (action === "list") {
|
|
2067
2068
|
const result = await apiRequest("GET", "/mail/pending");
|
|
2068
2069
|
if (result?.pending) {
|
|
@@ -2076,10 +2077,10 @@ ${lines.join("\n")}`;
|
|
|
2076
2077
|
${lines.join("\n")}`);
|
|
2077
2078
|
}
|
|
2078
2079
|
if (action === "get") {
|
|
2079
|
-
if (!
|
|
2080
|
-
const result = await apiRequest("GET", `/mail/pending/${encodeURIComponent(String(
|
|
2080
|
+
if (!args2.id) throw new Error("id is required");
|
|
2081
|
+
const result = await apiRequest("GET", `/mail/pending/${encodeURIComponent(String(args2.id))}`);
|
|
2081
2082
|
if (!result) throw new Error("Pending email not found");
|
|
2082
|
-
if (result.status !== "pending") cancelFollowUp(String(
|
|
2083
|
+
if (result.status !== "pending") cancelFollowUp(String(args2.id));
|
|
2083
2084
|
return withReminders(`Pending Email: ${result.id}
|
|
2084
2085
|
To: ${result.mailOptions?.to}
|
|
2085
2086
|
Subject: ${result.mailOptions?.subject}
|
|
@@ -2096,106 +2097,106 @@ ${result.summary}`);
|
|
|
2096
2097
|
// --- SMS / Google Voice Tools ---
|
|
2097
2098
|
case "sms_setup": {
|
|
2098
2099
|
const result = await apiRequest("POST", "/sms/setup", {
|
|
2099
|
-
phoneNumber:
|
|
2100
|
-
forwardingEmail:
|
|
2100
|
+
phoneNumber: args2.phoneNumber,
|
|
2101
|
+
forwardingEmail: args2.forwardingEmail,
|
|
2101
2102
|
provider: "google_voice"
|
|
2102
2103
|
});
|
|
2103
2104
|
return JSON.stringify(result, null, 2);
|
|
2104
2105
|
}
|
|
2105
2106
|
case "sms_send": {
|
|
2106
2107
|
const result = await apiRequest("POST", "/sms/send", {
|
|
2107
|
-
to:
|
|
2108
|
-
body:
|
|
2108
|
+
to: args2.to,
|
|
2109
|
+
body: args2.body
|
|
2109
2110
|
});
|
|
2110
2111
|
return JSON.stringify(result, null, 2);
|
|
2111
2112
|
}
|
|
2112
2113
|
case "sms_messages": {
|
|
2113
2114
|
const query = new URLSearchParams();
|
|
2114
|
-
if (
|
|
2115
|
-
if (
|
|
2116
|
-
if (
|
|
2115
|
+
if (args2.direction) query.set("direction", String(args2.direction));
|
|
2116
|
+
if (args2.limit) query.set("limit", String(args2.limit));
|
|
2117
|
+
if (args2.offset) query.set("offset", String(args2.offset));
|
|
2117
2118
|
const result = await apiRequest("GET", `/sms/messages?${query.toString()}`);
|
|
2118
2119
|
return JSON.stringify(result, null, 2);
|
|
2119
2120
|
}
|
|
2120
2121
|
case "sms_check_code": {
|
|
2121
|
-
const query =
|
|
2122
|
+
const query = args2.minutes ? `?minutes=${args2.minutes}` : "";
|
|
2122
2123
|
const result = await apiRequest("GET", `/sms/verification-code${query}`);
|
|
2123
2124
|
return JSON.stringify(result, null, 2);
|
|
2124
2125
|
}
|
|
2125
2126
|
case "sms_parse_email": {
|
|
2126
2127
|
const result = await apiRequest("POST", "/sms/parse-email", {
|
|
2127
|
-
emailBody:
|
|
2128
|
-
emailFrom:
|
|
2128
|
+
emailBody: args2.emailBody,
|
|
2129
|
+
emailFrom: args2.emailFrom
|
|
2129
2130
|
});
|
|
2130
2131
|
return JSON.stringify(result, null, 2);
|
|
2131
2132
|
}
|
|
2132
2133
|
case "storage": {
|
|
2133
|
-
const act =
|
|
2134
|
-
const tbl =
|
|
2134
|
+
const act = args2.action;
|
|
2135
|
+
const tbl = args2.table ? encodeURIComponent(args2.table) : "";
|
|
2135
2136
|
let result;
|
|
2136
2137
|
switch (act) {
|
|
2137
2138
|
// DDL
|
|
2138
2139
|
case "create_table":
|
|
2139
|
-
result = await apiRequest("POST", "/storage/tables", { name:
|
|
2140
|
+
result = await apiRequest("POST", "/storage/tables", { name: args2.table, columns: args2.columns, indexes: args2.indexes, shared: args2.shared, description: args2.description, timestamps: args2.timestamps });
|
|
2140
2141
|
break;
|
|
2141
2142
|
case "list_tables":
|
|
2142
|
-
result = await apiRequest("GET", `/storage/tables?includeShared=${
|
|
2143
|
+
result = await apiRequest("GET", `/storage/tables?includeShared=${args2.includeShared !== false}&includeArchived=${args2.includeArchived === true}`);
|
|
2143
2144
|
break;
|
|
2144
2145
|
case "describe_table":
|
|
2145
2146
|
result = await apiRequest("GET", `/storage/tables/${tbl}/describe`);
|
|
2146
2147
|
break;
|
|
2147
2148
|
case "add_column":
|
|
2148
|
-
result = await apiRequest("POST", `/storage/tables/${tbl}/columns`, { column:
|
|
2149
|
+
result = await apiRequest("POST", `/storage/tables/${tbl}/columns`, { column: args2.column });
|
|
2149
2150
|
break;
|
|
2150
2151
|
case "drop_column":
|
|
2151
|
-
result = await apiRequest("DELETE", `/storage/tables/${tbl}/columns/${encodeURIComponent(
|
|
2152
|
+
result = await apiRequest("DELETE", `/storage/tables/${tbl}/columns/${encodeURIComponent(args2.columnName)}`);
|
|
2152
2153
|
break;
|
|
2153
2154
|
case "rename_table":
|
|
2154
|
-
result = await apiRequest("POST", `/storage/tables/${tbl}/rename`, { newName:
|
|
2155
|
+
result = await apiRequest("POST", `/storage/tables/${tbl}/rename`, { newName: args2.newName });
|
|
2155
2156
|
break;
|
|
2156
2157
|
case "rename_column":
|
|
2157
|
-
result = await apiRequest("POST", `/storage/tables/${tbl}/rename-column`, { oldName:
|
|
2158
|
+
result = await apiRequest("POST", `/storage/tables/${tbl}/rename-column`, { oldName: args2.oldName, newName: args2.newName });
|
|
2158
2159
|
break;
|
|
2159
2160
|
case "drop_table":
|
|
2160
2161
|
result = await apiRequest("DELETE", `/storage/tables/${tbl}`);
|
|
2161
2162
|
break;
|
|
2162
2163
|
case "clone_table":
|
|
2163
|
-
result = await apiRequest("POST", `/storage/tables/${tbl}/clone`, { newName:
|
|
2164
|
+
result = await apiRequest("POST", `/storage/tables/${tbl}/clone`, { newName: args2.newName, includeData: args2.includeData });
|
|
2164
2165
|
break;
|
|
2165
2166
|
case "truncate":
|
|
2166
2167
|
result = await apiRequest("POST", `/storage/tables/${tbl}/truncate`);
|
|
2167
2168
|
break;
|
|
2168
2169
|
// Indexes
|
|
2169
2170
|
case "create_index":
|
|
2170
|
-
result = await apiRequest("POST", `/storage/tables/${tbl}/indexes`, { columns:
|
|
2171
|
+
result = await apiRequest("POST", `/storage/tables/${tbl}/indexes`, { columns: args2.indexColumns || args2.columns, unique: args2.indexUnique, name: args2.indexName, where: args2.indexWhere });
|
|
2171
2172
|
break;
|
|
2172
2173
|
case "list_indexes":
|
|
2173
2174
|
result = await apiRequest("GET", `/storage/tables/${tbl}/indexes`);
|
|
2174
2175
|
break;
|
|
2175
2176
|
case "drop_index":
|
|
2176
|
-
result = await apiRequest("DELETE", `/storage/tables/${tbl}/indexes/${encodeURIComponent(
|
|
2177
|
+
result = await apiRequest("DELETE", `/storage/tables/${tbl}/indexes/${encodeURIComponent(args2.indexName)}`);
|
|
2177
2178
|
break;
|
|
2178
2179
|
case "reindex":
|
|
2179
2180
|
result = await apiRequest("POST", `/storage/tables/${tbl}/reindex`);
|
|
2180
2181
|
break;
|
|
2181
2182
|
// DML
|
|
2182
2183
|
case "insert":
|
|
2183
|
-
result = await apiRequest("POST", "/storage/insert", { table:
|
|
2184
|
+
result = await apiRequest("POST", "/storage/insert", { table: args2.table, rows: args2.rows });
|
|
2184
2185
|
break;
|
|
2185
2186
|
case "upsert":
|
|
2186
|
-
result = await apiRequest("POST", "/storage/upsert", { table:
|
|
2187
|
+
result = await apiRequest("POST", "/storage/upsert", { table: args2.table, rows: args2.rows, conflictColumn: args2.conflictColumn });
|
|
2187
2188
|
break;
|
|
2188
2189
|
case "query":
|
|
2189
|
-
result = await apiRequest("POST", "/storage/query", { table:
|
|
2190
|
+
result = await apiRequest("POST", "/storage/query", { table: args2.table, where: args2.where, orderBy: args2.orderBy, limit: args2.limit, offset: args2.offset, columns: args2.selectColumns, distinct: args2.distinct, groupBy: args2.groupBy, having: args2.having });
|
|
2190
2191
|
break;
|
|
2191
2192
|
case "aggregate":
|
|
2192
|
-
result = await apiRequest("POST", "/storage/aggregate", { table:
|
|
2193
|
+
result = await apiRequest("POST", "/storage/aggregate", { table: args2.table, where: args2.where, operations: args2.operations, groupBy: args2.groupBy });
|
|
2193
2194
|
break;
|
|
2194
2195
|
case "update":
|
|
2195
|
-
result = await apiRequest("POST", "/storage/update", { table:
|
|
2196
|
+
result = await apiRequest("POST", "/storage/update", { table: args2.table, where: args2.where, set: args2.set });
|
|
2196
2197
|
break;
|
|
2197
2198
|
case "delete_rows":
|
|
2198
|
-
result = await apiRequest("POST", "/storage/delete-rows", { table:
|
|
2199
|
+
result = await apiRequest("POST", "/storage/delete-rows", { table: args2.table, where: args2.where });
|
|
2199
2200
|
break;
|
|
2200
2201
|
// Archive
|
|
2201
2202
|
case "archive_table":
|
|
@@ -2206,17 +2207,17 @@ ${result.summary}`);
|
|
|
2206
2207
|
break;
|
|
2207
2208
|
// Import/Export
|
|
2208
2209
|
case "export":
|
|
2209
|
-
result = await apiRequest("POST", `/storage/tables/${tbl}/export`, { format:
|
|
2210
|
+
result = await apiRequest("POST", `/storage/tables/${tbl}/export`, { format: args2.format, where: args2.where, limit: args2.limit });
|
|
2210
2211
|
break;
|
|
2211
2212
|
case "import":
|
|
2212
|
-
result = await apiRequest("POST", `/storage/tables/${tbl}/import`, { rows:
|
|
2213
|
+
result = await apiRequest("POST", `/storage/tables/${tbl}/import`, { rows: args2.rows, onConflict: args2.onConflict, conflictColumn: args2.conflictColumn });
|
|
2213
2214
|
break;
|
|
2214
2215
|
// Raw SQL
|
|
2215
2216
|
case "sql":
|
|
2216
|
-
result = await apiRequest("POST", "/storage/sql", { sql:
|
|
2217
|
+
result = await apiRequest("POST", "/storage/sql", { sql: args2.sql, params: args2.params });
|
|
2217
2218
|
break;
|
|
2218
2219
|
case "explain":
|
|
2219
|
-
result = await apiRequest("POST", "/storage/explain", { sql:
|
|
2220
|
+
result = await apiRequest("POST", "/storage/explain", { sql: args2.sql, params: args2.params });
|
|
2220
2221
|
break;
|
|
2221
2222
|
// Maintenance
|
|
2222
2223
|
case "stats":
|
|
@@ -2255,8 +2256,8 @@ ${result.summary}`);
|
|
|
2255
2256
|
}
|
|
2256
2257
|
case "sms_record": {
|
|
2257
2258
|
const result = await apiRequest("POST", "/sms/record", {
|
|
2258
|
-
from:
|
|
2259
|
-
body:
|
|
2259
|
+
from: args2.from,
|
|
2260
|
+
body: args2.body
|
|
2260
2261
|
});
|
|
2261
2262
|
return JSON.stringify(result, null, 2);
|
|
2262
2263
|
}
|
|
@@ -2322,64 +2323,158 @@ ${lines.join("\n")}`;
|
|
|
2322
2323
|
|
|
2323
2324
|
// src/index.ts
|
|
2324
2325
|
import { setTelemetryVersion } from "@agenticmail/core";
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
tool
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2326
|
+
import { createServer } from "http";
|
|
2327
|
+
import { randomUUID } from "crypto";
|
|
2328
|
+
setTelemetryVersion("0.5.50");
|
|
2329
|
+
function createMcpServer() {
|
|
2330
|
+
const server = new McpServer({
|
|
2331
|
+
name: "\u{1F380} AgenticMail",
|
|
2332
|
+
version: "0.2.27",
|
|
2333
|
+
description: "\u{1F380} AgenticMail \u2014 Email infrastructure for AI agents. By Ope Olatunji (https://github.com/agenticmail/agenticmail)"
|
|
2334
|
+
});
|
|
2335
|
+
for (const tool of toolDefinitions) {
|
|
2336
|
+
server.tool(
|
|
2337
|
+
tool.name,
|
|
2338
|
+
tool.description,
|
|
2339
|
+
tool.inputSchema,
|
|
2340
|
+
async ({ arguments: args2 }) => {
|
|
2341
|
+
try {
|
|
2342
|
+
const result = await handleToolCall(tool.name, args2);
|
|
2343
|
+
return { content: [{ type: "text", text: result }] };
|
|
2344
|
+
} catch (err) {
|
|
2345
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2346
|
+
return {
|
|
2347
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
2348
|
+
isError: true
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2346
2351
|
}
|
|
2347
|
-
|
|
2348
|
-
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
for (const resource of resourceDefinitions) {
|
|
2355
|
+
server.resource(
|
|
2356
|
+
resource.name,
|
|
2357
|
+
resource.uri,
|
|
2358
|
+
{ description: resource.description, mimeType: resource.mimeType },
|
|
2359
|
+
async () => {
|
|
2360
|
+
try {
|
|
2361
|
+
const content = await handleResourceRead(resource.uri);
|
|
2362
|
+
return {
|
|
2363
|
+
contents: [{ uri: resource.uri, text: content, mimeType: resource.mimeType }]
|
|
2364
|
+
};
|
|
2365
|
+
} catch (err) {
|
|
2366
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2367
|
+
return {
|
|
2368
|
+
contents: [{ uri: resource.uri, text: `Error: ${message}`, mimeType: "text/plain" }]
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
return server;
|
|
2349
2375
|
}
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2376
|
+
var args = process.argv.slice(2);
|
|
2377
|
+
var httpFlag = args.includes("--http");
|
|
2378
|
+
var portArg = args.find((a) => a.startsWith("--port="));
|
|
2379
|
+
var httpPort = portArg ? parseInt(portArg.split("=")[1], 10) : parseInt(process.env.MCP_PORT || "", 10) || 8014;
|
|
2380
|
+
if (httpFlag || process.env.MCP_HTTP === "1") {
|
|
2381
|
+
const server = createMcpServer();
|
|
2382
|
+
const transports = /* @__PURE__ */ new Map();
|
|
2383
|
+
const httpServer = createServer(async (req, res) => {
|
|
2384
|
+
const url = new URL(req.url ?? "/", `http://localhost:${httpPort}`);
|
|
2385
|
+
const path = url.pathname;
|
|
2386
|
+
if (path === "/health" && req.method === "GET") {
|
|
2387
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2388
|
+
res.end(JSON.stringify({ status: "ok", transport: "streamable-http", sessions: transports.size }));
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
if (path !== "/mcp") {
|
|
2392
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
2393
|
+
res.end(JSON.stringify({ error: "Not found. MCP endpoint is POST /mcp" }));
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
if (req.method === "DELETE") {
|
|
2397
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
2398
|
+
if (sessionId && transports.has(sessionId)) {
|
|
2399
|
+
const transport = transports.get(sessionId);
|
|
2400
|
+
await transport.handleRequest(req, res);
|
|
2401
|
+
transports.delete(sessionId);
|
|
2402
|
+
} else {
|
|
2403
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2404
|
+
res.end(JSON.stringify({ error: "Invalid or missing session ID" }));
|
|
2405
|
+
}
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
if (req.method === "GET") {
|
|
2409
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
2410
|
+
if (sessionId && transports.has(sessionId)) {
|
|
2411
|
+
const transport = transports.get(sessionId);
|
|
2412
|
+
await transport.handleRequest(req, res);
|
|
2413
|
+
} else {
|
|
2414
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2415
|
+
res.end(JSON.stringify({ error: "Invalid or missing session ID for SSE stream. Send a POST /mcp with initialize first." }));
|
|
2416
|
+
}
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
if (req.method === "POST") {
|
|
2420
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
2421
|
+
if (sessionId && transports.has(sessionId)) {
|
|
2422
|
+
const transport2 = transports.get(sessionId);
|
|
2423
|
+
await transport2.handleRequest(req, res);
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
const transport = new StreamableHTTPServerTransport({
|
|
2427
|
+
sessionIdGenerator: () => randomUUID(),
|
|
2428
|
+
onsessioninitialized: (sid) => {
|
|
2429
|
+
transports.set(sid, transport);
|
|
2430
|
+
}
|
|
2431
|
+
});
|
|
2432
|
+
transport.onclose = () => {
|
|
2433
|
+
const sid = transport.sessionId;
|
|
2434
|
+
if (sid) transports.delete(sid);
|
|
2435
|
+
};
|
|
2436
|
+
const sessionServer = createMcpServer();
|
|
2437
|
+
await sessionServer.connect(transport);
|
|
2438
|
+
await transport.handleRequest(req, res);
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
res.writeHead(405, { "Allow": "GET, POST, DELETE", "Content-Type": "application/json" });
|
|
2442
|
+
res.end(JSON.stringify({ error: "Method not allowed. Use POST /mcp for JSON-RPC, GET /mcp for SSE stream." }));
|
|
2443
|
+
});
|
|
2444
|
+
httpServer.listen(httpPort, () => {
|
|
2445
|
+
console.log(`\u{1F380} AgenticMail MCP Server (Streamable HTTP)`);
|
|
2446
|
+
console.log(` Endpoint: http://localhost:${httpPort}/mcp`);
|
|
2447
|
+
console.log(` Health: http://localhost:${httpPort}/health`);
|
|
2448
|
+
console.log(` Transport: Streamable HTTP (SSE + JSON responses)`);
|
|
2449
|
+
});
|
|
2450
|
+
async function shutdown() {
|
|
2451
|
+
for (const transport of transports.values()) {
|
|
2356
2452
|
try {
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
contents: [{ uri: resource.uri, text: content, mimeType: resource.mimeType }]
|
|
2360
|
-
};
|
|
2361
|
-
} catch (err) {
|
|
2362
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2363
|
-
return {
|
|
2364
|
-
contents: [{ uri: resource.uri, text: `Error: ${message}`, mimeType: "text/plain" }]
|
|
2365
|
-
};
|
|
2453
|
+
await transport.close();
|
|
2454
|
+
} catch {
|
|
2366
2455
|
}
|
|
2367
2456
|
}
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
}
|
|
2377
|
-
async function shutdown() {
|
|
2457
|
+
httpServer.close();
|
|
2458
|
+
process.exit(0);
|
|
2459
|
+
}
|
|
2460
|
+
process.on("SIGTERM", () => shutdown());
|
|
2461
|
+
process.on("SIGINT", () => shutdown());
|
|
2462
|
+
} else {
|
|
2463
|
+
const server = createMcpServer();
|
|
2464
|
+
const transport = new StdioServerTransport();
|
|
2378
2465
|
try {
|
|
2379
|
-
await server.
|
|
2380
|
-
} catch {
|
|
2466
|
+
await server.connect(transport);
|
|
2467
|
+
} catch (err) {
|
|
2468
|
+
console.error("[agenticmail-mcp] Failed to start:", err);
|
|
2469
|
+
process.exit(1);
|
|
2470
|
+
}
|
|
2471
|
+
async function shutdown() {
|
|
2472
|
+
try {
|
|
2473
|
+
await server.close();
|
|
2474
|
+
} catch {
|
|
2475
|
+
}
|
|
2476
|
+
process.exit(0);
|
|
2381
2477
|
}
|
|
2382
|
-
process.
|
|
2478
|
+
process.on("SIGTERM", () => shutdown());
|
|
2479
|
+
process.on("SIGINT", () => shutdown());
|
|
2383
2480
|
}
|
|
2384
|
-
process.on("SIGTERM", () => shutdown());
|
|
2385
|
-
process.on("SIGINT", () => shutdown());
|