@marlinjai/email-mcp 1.2.7 → 1.3.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/README.md +3 -2
- package/dist/index.js +203 -30
- package/dist/index.js.map +2 -2
- package/dist/setup/wizard.js +143 -30
- package/dist/setup/wizard.js.map +2 -2
- package/package.json +1 -1
package/dist/setup/wizard.js
CHANGED
|
@@ -363,6 +363,7 @@ var FolderType = {
|
|
|
363
363
|
};
|
|
364
364
|
|
|
365
365
|
// src/providers/gmail/adapter.ts
|
|
366
|
+
import { Readable } from "node:stream";
|
|
366
367
|
import { google } from "googleapis";
|
|
367
368
|
import { OAuth2Client as OAuth2Client2 } from "google-auth-library";
|
|
368
369
|
|
|
@@ -567,20 +568,32 @@ var GmailAdapter = class {
|
|
|
567
568
|
async search(query) {
|
|
568
569
|
const gmail = this.ensureConnected();
|
|
569
570
|
const q = buildGmailQuery(query);
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
571
|
+
const limit = query.limit || 20;
|
|
572
|
+
const offset = query.offset || 0;
|
|
573
|
+
const needed = offset + limit;
|
|
574
|
+
const ids = [];
|
|
575
|
+
let pageToken;
|
|
576
|
+
do {
|
|
577
|
+
const params = {
|
|
578
|
+
userId: "me",
|
|
579
|
+
maxResults: Math.min(500, needed - ids.length)
|
|
580
|
+
};
|
|
581
|
+
if (q) params.q = q;
|
|
582
|
+
if (query.folder) params.labelIds = [query.folder];
|
|
583
|
+
if (pageToken) params.pageToken = pageToken;
|
|
584
|
+
const res = await gmail.users.messages.list(params);
|
|
585
|
+
for (const msg of res.data.messages || []) {
|
|
586
|
+
if (msg.id) ids.push(msg.id);
|
|
587
|
+
}
|
|
588
|
+
pageToken = res.data.nextPageToken || void 0;
|
|
589
|
+
} while (pageToken && ids.length < needed);
|
|
590
|
+
const windowIds = ids.slice(offset, offset + limit);
|
|
591
|
+
if (windowIds.length === 0) return [];
|
|
579
592
|
const emails = [];
|
|
580
|
-
for (const
|
|
593
|
+
for (const id of windowIds) {
|
|
581
594
|
const full = await gmail.users.messages.get({
|
|
582
595
|
userId: "me",
|
|
583
|
-
id
|
|
596
|
+
id,
|
|
584
597
|
format: "full"
|
|
585
598
|
});
|
|
586
599
|
emails.push(mapGmailMessage(full.data, this.accountId));
|
|
@@ -828,6 +841,40 @@ var GmailAdapter = class {
|
|
|
828
841
|
}
|
|
829
842
|
return result;
|
|
830
843
|
}
|
|
844
|
+
async getRawMessage(emailId, _sourceFolder) {
|
|
845
|
+
const gmail = this.ensureConnected();
|
|
846
|
+
const res = await gmail.users.messages.get({
|
|
847
|
+
userId: "me",
|
|
848
|
+
id: emailId,
|
|
849
|
+
format: "raw"
|
|
850
|
+
});
|
|
851
|
+
return Buffer.from(res.data.raw || "", "base64url");
|
|
852
|
+
}
|
|
853
|
+
async appendRawMessage(raw, targetFolder, flags) {
|
|
854
|
+
const gmail = this.ensureConnected();
|
|
855
|
+
const labelIds = [];
|
|
856
|
+
labelIds.push(targetFolder ? await this.resolveLabelId(targetFolder) : "INBOX");
|
|
857
|
+
if (flags?.read === false) labelIds.push("UNREAD");
|
|
858
|
+
if (flags?.starred === true) labelIds.push("STARRED");
|
|
859
|
+
const res = await gmail.users.messages.insert({
|
|
860
|
+
userId: "me",
|
|
861
|
+
internalDateSource: "dateHeader",
|
|
862
|
+
requestBody: { labelIds },
|
|
863
|
+
media: { mimeType: "message/rfc822", body: Readable.from(raw) }
|
|
864
|
+
});
|
|
865
|
+
return { id: res.data.id || "" };
|
|
866
|
+
}
|
|
867
|
+
/** Resolve a Gmail label name or id to a label id (accepts either). */
|
|
868
|
+
async resolveLabelId(nameOrId) {
|
|
869
|
+
const gmail = this.ensureConnected();
|
|
870
|
+
const res = await gmail.users.labels.list({ userId: "me" });
|
|
871
|
+
const labels = res.data.labels || [];
|
|
872
|
+
const byId = labels.find((l) => l.id === nameOrId);
|
|
873
|
+
if (byId?.id) return byId.id;
|
|
874
|
+
const byName = labels.find((l) => (l.name || "").toLowerCase() === nameOrId.toLowerCase());
|
|
875
|
+
if (byName?.id) return byName.id;
|
|
876
|
+
return nameOrId;
|
|
877
|
+
}
|
|
831
878
|
async addLabels(emailId, labels) {
|
|
832
879
|
const gmail = this.ensureConnected();
|
|
833
880
|
await gmail.users.messages.modify({
|
|
@@ -1209,6 +1256,41 @@ var OutlookAdapter = class {
|
|
|
1209
1256
|
destinationId
|
|
1210
1257
|
});
|
|
1211
1258
|
}
|
|
1259
|
+
async getRawMessage(emailId, _sourceFolder) {
|
|
1260
|
+
this.ensureClient();
|
|
1261
|
+
const res = await fetch(
|
|
1262
|
+
`https://graph.microsoft.com/v1.0/me/messages/${encodeURIComponent(emailId)}/$value`,
|
|
1263
|
+
{ headers: { Authorization: `Bearer ${this.accessToken}` } }
|
|
1264
|
+
);
|
|
1265
|
+
if (!res.ok) {
|
|
1266
|
+
throw new Error(`Graph $value fetch failed for ${emailId}: HTTP ${res.status}`);
|
|
1267
|
+
}
|
|
1268
|
+
return Buffer.from(await res.arrayBuffer());
|
|
1269
|
+
}
|
|
1270
|
+
async appendRawMessage(raw, targetFolder, flags) {
|
|
1271
|
+
this.ensureClient();
|
|
1272
|
+
const endpoint = targetFolder ? `https://graph.microsoft.com/v1.0/me/mailFolders/${encodeURIComponent(await this.resolveFolder(targetFolder))}/messages` : "https://graph.microsoft.com/v1.0/me/messages";
|
|
1273
|
+
const res = await fetch(endpoint, {
|
|
1274
|
+
method: "POST",
|
|
1275
|
+
headers: {
|
|
1276
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
1277
|
+
"Content-Type": "text/plain"
|
|
1278
|
+
},
|
|
1279
|
+
body: raw.toString("base64")
|
|
1280
|
+
});
|
|
1281
|
+
if (!res.ok) {
|
|
1282
|
+
const detail = await res.text().catch(() => "");
|
|
1283
|
+
throw new Error(`Graph MIME import failed: HTTP ${res.status} ${detail.slice(0, 200)}`);
|
|
1284
|
+
}
|
|
1285
|
+
const data = await res.json().catch(() => ({}));
|
|
1286
|
+
if (flags && data.id && (flags.read !== void 0 || flags.starred !== void 0)) {
|
|
1287
|
+
try {
|
|
1288
|
+
await this.markEmail(data.id, flags);
|
|
1289
|
+
} catch {
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return { id: data.id || "" };
|
|
1293
|
+
}
|
|
1212
1294
|
async deleteEmail(emailId, permanent, _sourceFolder) {
|
|
1213
1295
|
const client = this.ensureClient();
|
|
1214
1296
|
if (permanent) {
|
|
@@ -1623,14 +1705,7 @@ var ImapAdapter = class {
|
|
|
1623
1705
|
}
|
|
1624
1706
|
try {
|
|
1625
1707
|
const mailboxExists = this.client.mailbox?.exists ?? 0;
|
|
1626
|
-
|
|
1627
|
-
try {
|
|
1628
|
-
const status = await this.client.status(folder, { messages: true });
|
|
1629
|
-
statusCount = status.messages ?? 0;
|
|
1630
|
-
} catch {
|
|
1631
|
-
}
|
|
1632
|
-
const effectiveCount = Math.max(mailboxExists, statusCount);
|
|
1633
|
-
if (effectiveCount === 0) {
|
|
1708
|
+
if (mailboxExists === 0) {
|
|
1634
1709
|
return [];
|
|
1635
1710
|
}
|
|
1636
1711
|
const criteria = this.buildSearchCriteria(query);
|
|
@@ -1643,7 +1718,11 @@ var ImapAdapter = class {
|
|
|
1643
1718
|
);
|
|
1644
1719
|
allUids = Array.isArray(searchResult) ? searchResult : [];
|
|
1645
1720
|
} catch {
|
|
1646
|
-
|
|
1721
|
+
try {
|
|
1722
|
+
allUids = await this.collectUidsViaFetch(query);
|
|
1723
|
+
} catch {
|
|
1724
|
+
return [];
|
|
1725
|
+
}
|
|
1647
1726
|
}
|
|
1648
1727
|
const offset = query.offset || 0;
|
|
1649
1728
|
const slicedUids = query.limit ? allUids.slice(offset, offset + query.limit) : allUids.slice(offset);
|
|
@@ -1705,19 +1784,27 @@ var ImapAdapter = class {
|
|
|
1705
1784
|
async fetchEmails(uids, folder, returnBody) {
|
|
1706
1785
|
if (!this.client) return [];
|
|
1707
1786
|
const emails = [];
|
|
1708
|
-
|
|
1709
|
-
|
|
1787
|
+
const fetchOpts = returnBody ? { source: true, uid: true, flags: true } : { envelope: true, uid: true, flags: true, bodyStructure: true };
|
|
1788
|
+
const uidRange = uids.join(",");
|
|
1789
|
+
let messages;
|
|
1790
|
+
try {
|
|
1791
|
+
messages = await this.client.fetchAll(uidRange, fetchOpts, { uid: true });
|
|
1792
|
+
} catch {
|
|
1793
|
+
messages = [];
|
|
1794
|
+
for (const uid of uids) {
|
|
1795
|
+
try {
|
|
1796
|
+
const batch = await this.client.fetchAll(String(uid), fetchOpts, { uid: true });
|
|
1797
|
+
messages.push(...batch);
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
for (const msg of messages) {
|
|
1803
|
+
if (returnBody) {
|
|
1710
1804
|
const parsed = await simpleParser(msg.source);
|
|
1711
1805
|
parsed.flags = msg.flags;
|
|
1712
1806
|
emails.push(mapParsedEmail(parsed, folder, this.accountId, msg.uid));
|
|
1713
|
-
}
|
|
1714
|
-
} else {
|
|
1715
|
-
for await (const msg of this.client.fetch(uids, {
|
|
1716
|
-
envelope: true,
|
|
1717
|
-
uid: true,
|
|
1718
|
-
flags: true,
|
|
1719
|
-
bodyStructure: true
|
|
1720
|
-
})) {
|
|
1807
|
+
} else {
|
|
1721
1808
|
const env = msg.envelope;
|
|
1722
1809
|
emails.push({
|
|
1723
1810
|
id: String(msg.uid),
|
|
@@ -1926,6 +2013,32 @@ var ImapAdapter = class {
|
|
|
1926
2013
|
lock.release();
|
|
1927
2014
|
}
|
|
1928
2015
|
}
|
|
2016
|
+
async getRawMessage(emailId, sourceFolder) {
|
|
2017
|
+
if (!this.client) throw new Error("Not connected");
|
|
2018
|
+
const folder = sourceFolder ? await this.resolveFolder(sourceFolder) : "INBOX";
|
|
2019
|
+
let lock;
|
|
2020
|
+
try {
|
|
2021
|
+
lock = await this.client.getMailboxLock(folder);
|
|
2022
|
+
} catch (error) {
|
|
2023
|
+
throw formatImapError(error, `Failed to open folder "${folder}"`);
|
|
2024
|
+
}
|
|
2025
|
+
try {
|
|
2026
|
+
const msg = await this.client.fetchOne(String(emailId), { source: true, uid: true }, { uid: true });
|
|
2027
|
+
if (!msg || !msg.source) throw new Error(`Email ${emailId} not found in "${folder}"`);
|
|
2028
|
+
return msg.source;
|
|
2029
|
+
} finally {
|
|
2030
|
+
lock.release();
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
async appendRawMessage(raw, targetFolder, flags) {
|
|
2034
|
+
if (!this.client) throw new Error("Not connected");
|
|
2035
|
+
const folder = targetFolder ? await this.resolveFolder(targetFolder) : "INBOX";
|
|
2036
|
+
const imapFlags = [];
|
|
2037
|
+
if (flags?.read) imapFlags.push("\\Seen");
|
|
2038
|
+
if (flags?.starred) imapFlags.push("\\Flagged");
|
|
2039
|
+
const result = await this.client.append(folder, raw, imapFlags);
|
|
2040
|
+
return { id: String(result?.uid ?? "") };
|
|
2041
|
+
}
|
|
1929
2042
|
async batchDelete(emailIds, permanent, sourceFolder) {
|
|
1930
2043
|
if (!this.client) throw new Error("Not connected");
|
|
1931
2044
|
const result = { succeeded: [], failed: [] };
|