@marlinjai/email-mcp 1.3.0 → 1.4.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.
@@ -264,7 +264,10 @@ function createFileCachePlugin(cacheFilePath) {
264
264
  }
265
265
  var OutlookAuth = class _OutlookAuth {
266
266
  static AUTHORITY = "https://login.microsoftonline.com/consumers";
267
- static SCOPES = ["Mail.ReadWrite", "Mail.Send", "offline_access"];
267
+ // MailboxSettings.ReadWrite is required for the messageRules API (block
268
+ // rules) — confirmed by Microsoft's own docs to be supported for personal
269
+ // Microsoft accounts (not just work/school), not just Mail.ReadWrite/Send.
270
+ static SCOPES = ["Mail.ReadWrite", "Mail.Send", "MailboxSettings.ReadWrite", "offline_access"];
268
271
  pca;
269
272
  cryptoProvider;
270
273
  constructor(clientId, cacheFilePath) {
@@ -901,6 +904,74 @@ var GmailAdapter = class {
901
904
  messageCount: label.messagesTotal ?? 0
902
905
  }));
903
906
  }
907
+ /**
908
+ * Trains Gmail's own spam filter — the same signal the "Report spam" UI
909
+ * button sends (add SPAM, drop INBOX). Not an abuse report to Google.
910
+ */
911
+ async reportSpam(emailId, _sourceFolder) {
912
+ const gmail = this.ensureConnected();
913
+ await gmail.users.messages.modify({
914
+ userId: "me",
915
+ id: emailId,
916
+ requestBody: { addLabelIds: ["SPAM"], removeLabelIds: ["INBOX"] }
917
+ });
918
+ }
919
+ async createBlockRule(rule) {
920
+ const gmail = this.ensureConnected();
921
+ const criteria = {};
922
+ switch (rule.matchType) {
923
+ case "senderDomain":
924
+ criteria.from = `@${rule.value}`;
925
+ break;
926
+ case "senderAddress":
927
+ criteria.from = rule.value;
928
+ break;
929
+ case "subjectContains":
930
+ criteria.subject = rule.value;
931
+ break;
932
+ case "headerContains":
933
+ criteria.query = rule.value;
934
+ break;
935
+ }
936
+ const action = rule.action === "delete" ? { addLabelIds: ["TRASH"] } : { addLabelIds: ["SPAM"], removeLabelIds: ["INBOX"] };
937
+ const res = await gmail.users.settings.filters.create({
938
+ userId: "me",
939
+ requestBody: { criteria, action }
940
+ });
941
+ return { id: res.data.id || "" };
942
+ }
943
+ async listBlockRules() {
944
+ const gmail = this.ensureConnected();
945
+ const res = await gmail.users.settings.filters.list({ userId: "me" });
946
+ return (res.data.filter || []).map((f) => {
947
+ const criteria = f.criteria || {};
948
+ const addLabelIds = f.action?.addLabelIds || [];
949
+ let matchType = "headerContains";
950
+ let value = criteria.query || "";
951
+ if (typeof criteria.from === "string" && criteria.from.startsWith("@")) {
952
+ matchType = "senderDomain";
953
+ value = criteria.from.slice(1);
954
+ } else if (criteria.from) {
955
+ matchType = "senderAddress";
956
+ value = criteria.from;
957
+ } else if (criteria.subject) {
958
+ matchType = "subjectContains";
959
+ value = criteria.subject;
960
+ }
961
+ return {
962
+ id: f.id || "",
963
+ matchType,
964
+ value,
965
+ action: addLabelIds.includes("TRASH") ? "delete" : "moveToJunk",
966
+ createdAt: ""
967
+ // Gmail's filter API does not expose a creation timestamp
968
+ };
969
+ });
970
+ }
971
+ async deleteBlockRule(ruleId) {
972
+ const gmail = this.ensureConnected();
973
+ await gmail.users.settings.filters.delete({ userId: "me", id: ruleId });
974
+ }
904
975
  buildRfc2822(params) {
905
976
  const lines = [];
906
977
  lines.push(`From: ${this.email}`);
@@ -1420,6 +1491,86 @@ var OutlookAdapter = class {
1420
1491
  }
1421
1492
  return result;
1422
1493
  }
1494
+ /**
1495
+ * Trains Outlook's own spam filter — the same signal "Report Junk" sends
1496
+ * in the UI (move to the Junk Email well-known folder). Not an abuse
1497
+ * report to Microsoft's security team.
1498
+ */
1499
+ async reportSpam(emailId, _sourceFolder) {
1500
+ const client = this.ensureClient();
1501
+ await client.api(`/me/messages/${encodeURIComponent(emailId)}/move`).post({
1502
+ destinationId: "junkemail"
1503
+ });
1504
+ }
1505
+ /**
1506
+ * Requires the `MailboxSettings.ReadWrite` delegated scope, confirmed by
1507
+ * Microsoft's own docs to be supported for personal Microsoft accounts
1508
+ * (outlook.com/hotmail), not just work/school. Accounts authenticated
1509
+ * before this scope was added need to re-run the setup wizard to
1510
+ * re-consent — old tokens will 403 on this call until then.
1511
+ */
1512
+ async createBlockRule(rule) {
1513
+ const client = this.ensureClient();
1514
+ const conditions = {};
1515
+ switch (rule.matchType) {
1516
+ case "senderDomain":
1517
+ case "senderAddress":
1518
+ conditions.senderContains = [rule.value];
1519
+ break;
1520
+ case "subjectContains":
1521
+ conditions.subjectContains = [rule.value];
1522
+ break;
1523
+ case "headerContains":
1524
+ conditions.headerContains = [rule.value];
1525
+ break;
1526
+ }
1527
+ const actions = { stopProcessingRules: true };
1528
+ if (rule.action === "delete") {
1529
+ actions.delete = true;
1530
+ } else {
1531
+ actions.moveToFolder = await this.resolveFolder("junkemail");
1532
+ }
1533
+ const existing = await this.listBlockRules();
1534
+ const result = await client.api("/me/mailFolders/inbox/messageRules").post({
1535
+ displayName: `email-mcp: ${rule.matchType} "${rule.value}"`.slice(0, 255),
1536
+ sequence: existing.length + 1,
1537
+ isEnabled: true,
1538
+ conditions,
1539
+ actions
1540
+ });
1541
+ return { id: result.id };
1542
+ }
1543
+ async listBlockRules() {
1544
+ const client = this.ensureClient();
1545
+ const response = await client.api("/me/mailFolders/inbox/messageRules").get();
1546
+ return (response.value || []).map((r) => {
1547
+ const conditions = r.conditions || {};
1548
+ let matchType = "headerContains";
1549
+ let value = "";
1550
+ if (conditions.senderContains?.length) {
1551
+ value = conditions.senderContains[0];
1552
+ matchType = value.includes("@") ? "senderAddress" : "senderDomain";
1553
+ } else if (conditions.subjectContains?.length) {
1554
+ matchType = "subjectContains";
1555
+ value = conditions.subjectContains[0];
1556
+ } else if (conditions.headerContains?.length) {
1557
+ matchType = "headerContains";
1558
+ value = conditions.headerContains[0];
1559
+ }
1560
+ return {
1561
+ id: r.id,
1562
+ matchType,
1563
+ value,
1564
+ action: r.actions?.delete ? "delete" : "moveToJunk",
1565
+ createdAt: ""
1566
+ // Graph's messageRule resource has no creation timestamp field
1567
+ };
1568
+ });
1569
+ }
1570
+ async deleteBlockRule(ruleId) {
1571
+ const client = this.ensureClient();
1572
+ await client.api(`/me/mailFolders/inbox/messageRules/${encodeURIComponent(ruleId)}`).delete();
1573
+ }
1423
1574
  async getCategories() {
1424
1575
  const client = this.ensureClient();
1425
1576
  const response = await client.api("/me/outlook/masterCategories").get();
@@ -1969,6 +2120,30 @@ var ImapAdapter = class {
1969
2120
  lock.release();
1970
2121
  }
1971
2122
  }
2123
+ /**
2124
+ * Best-effort only: generic IMAP has no server-side ML classifier to
2125
+ * train, so this just moves the message to the account's Junk-typed
2126
+ * folder (via the same alias resolution `deleteEmail` uses for Trash).
2127
+ * No standing block-rule mechanism exists across IMAP servers (Sieve
2128
+ * exists on some but isn't universal), so `createBlockRule` is
2129
+ * intentionally left unimplemented for this provider.
2130
+ */
2131
+ async reportSpam(emailId, sourceFolder) {
2132
+ if (!this.client) throw new Error("Not connected");
2133
+ const folder = sourceFolder || "INBOX";
2134
+ const junkFolder = await this.resolveFolder("junk");
2135
+ let lock;
2136
+ try {
2137
+ lock = await this.client.getMailboxLock(folder);
2138
+ } catch (error) {
2139
+ throw formatImapError(error, `Failed to open folder "${folder}"`);
2140
+ }
2141
+ try {
2142
+ await this.client.messageMove(emailId, junkFolder, { uid: true });
2143
+ } finally {
2144
+ lock.release();
2145
+ }
2146
+ }
1972
2147
  async deleteEmail(emailId, permanent, sourceFolder) {
1973
2148
  if (!this.client) throw new Error("Not connected");
1974
2149
  const folder = sourceFolder ? await this.resolveFolder(sourceFolder) : "INBOX";