@marlinjai/email-mcp 1.2.0 → 1.2.2
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 +136 -76
- package/dist/index.js.map +3 -3
- package/dist/setup/wizard.js +136 -76
- package/dist/setup/wizard.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7680,11 +7680,16 @@ var init_adapter2 = __esm({
|
|
|
7680
7680
|
}
|
|
7681
7681
|
this.accountId = credentials.id;
|
|
7682
7682
|
this.accessToken = credentials.oauth.access_token;
|
|
7683
|
-
|
|
7683
|
+
const client = Client.init({
|
|
7684
7684
|
authProvider: (done) => {
|
|
7685
7685
|
done(null, this.accessToken);
|
|
7686
7686
|
}
|
|
7687
7687
|
});
|
|
7688
|
+
const originalApi = client.api.bind(client);
|
|
7689
|
+
client.api = (path3) => {
|
|
7690
|
+
return originalApi(path3).header("Prefer", 'IdType="ImmutableId"');
|
|
7691
|
+
};
|
|
7692
|
+
this.client = client;
|
|
7688
7693
|
}
|
|
7689
7694
|
async disconnect() {
|
|
7690
7695
|
this.client = null;
|
|
@@ -8389,60 +8394,96 @@ var init_adapter3 = __esm({
|
|
|
8389
8394
|
throw formatImapError(error48, `Failed to open folder "${folder}"`);
|
|
8390
8395
|
}
|
|
8391
8396
|
try {
|
|
8397
|
+
const mailboxExists = this.client.mailbox?.exists ?? -1;
|
|
8398
|
+
if (mailboxExists === 0) {
|
|
8399
|
+
return [];
|
|
8400
|
+
}
|
|
8392
8401
|
const criteria = this.buildSearchCriteria(query);
|
|
8393
|
-
const
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8402
|
+
const hasCriteria = Object.keys(criteria).length > 0;
|
|
8403
|
+
let allUids;
|
|
8404
|
+
try {
|
|
8405
|
+
const searchResult = await this.client.search(
|
|
8406
|
+
hasCriteria ? criteria : { all: true },
|
|
8407
|
+
{ uid: true }
|
|
8408
|
+
);
|
|
8409
|
+
allUids = Array.isArray(searchResult) ? searchResult : [];
|
|
8410
|
+
} catch (searchError) {
|
|
8411
|
+
if (!hasCriteria) {
|
|
8412
|
+
allUids = await this.collectUidsViaFetch(query);
|
|
8413
|
+
} else {
|
|
8414
|
+
throw searchError;
|
|
8415
|
+
}
|
|
8416
|
+
}
|
|
8398
8417
|
const offset = query.offset || 0;
|
|
8399
8418
|
const slicedUids = query.limit ? allUids.slice(offset, offset + query.limit) : allUids.slice(offset);
|
|
8400
8419
|
if (slicedUids.length === 0) return [];
|
|
8401
|
-
|
|
8402
|
-
if (query.returnBody) {
|
|
8403
|
-
for await (const msg of this.client.fetch(slicedUids, { source: true, uid: true, flags: true })) {
|
|
8404
|
-
const parsed = await simpleParser(msg.source);
|
|
8405
|
-
parsed.flags = msg.flags;
|
|
8406
|
-
emails.push(mapParsedEmail(parsed, folder, this.accountId, msg.uid));
|
|
8407
|
-
}
|
|
8408
|
-
} else {
|
|
8409
|
-
for await (const msg of this.client.fetch(slicedUids, {
|
|
8410
|
-
envelope: true,
|
|
8411
|
-
uid: true,
|
|
8412
|
-
flags: true,
|
|
8413
|
-
bodyStructure: true
|
|
8414
|
-
})) {
|
|
8415
|
-
const env = msg.envelope;
|
|
8416
|
-
emails.push({
|
|
8417
|
-
id: String(msg.uid),
|
|
8418
|
-
accountId: this.accountId,
|
|
8419
|
-
threadId: env.messageId || void 0,
|
|
8420
|
-
folder,
|
|
8421
|
-
from: env.from?.[0] ? { name: env.from[0].name || void 0, email: env.from[0].address || "" } : { email: "" },
|
|
8422
|
-
to: (env.to || []).map((a) => ({ name: a.name || void 0, email: a.address || "" })),
|
|
8423
|
-
cc: env.cc?.length ? env.cc.map((a) => ({ name: a.name || void 0, email: a.address || "" })) : void 0,
|
|
8424
|
-
bcc: env.bcc?.length ? env.bcc.map((a) => ({ name: a.name || void 0, email: a.address || "" })) : void 0,
|
|
8425
|
-
subject: env.subject || "(no subject)",
|
|
8426
|
-
date: env.date ? new Date(env.date).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
8427
|
-
body: { text: void 0, html: void 0 },
|
|
8428
|
-
snippet: env.subject || "",
|
|
8429
|
-
attachments: [],
|
|
8430
|
-
flags: {
|
|
8431
|
-
read: msg.flags?.has("\\Seen") ?? false,
|
|
8432
|
-
starred: msg.flags?.has("\\Flagged") ?? false,
|
|
8433
|
-
flagged: msg.flags?.has("\\Flagged") ?? false,
|
|
8434
|
-
draft: msg.flags?.has("\\Draft") ?? false
|
|
8435
|
-
}
|
|
8436
|
-
});
|
|
8437
|
-
}
|
|
8438
|
-
}
|
|
8439
|
-
return emails;
|
|
8420
|
+
return await this.fetchEmails(slicedUids, folder, query.returnBody);
|
|
8440
8421
|
} catch (error48) {
|
|
8441
8422
|
throw formatImapError(error48, `Search failed in folder "${folder}"`);
|
|
8442
8423
|
} finally {
|
|
8443
8424
|
lock.release();
|
|
8444
8425
|
}
|
|
8445
8426
|
}
|
|
8427
|
+
/**
|
|
8428
|
+
* Fallback UID collection when UID SEARCH fails (e.g. iCloud "Invalid message number").
|
|
8429
|
+
* Uses FETCH 1:* with sequence numbers to collect UIDs directly, optionally filtering
|
|
8430
|
+
* by flag-based criteria (unread, starred).
|
|
8431
|
+
*/
|
|
8432
|
+
async collectUidsViaFetch(query) {
|
|
8433
|
+
if (!this.client) return [];
|
|
8434
|
+
const uids = [];
|
|
8435
|
+
for await (const msg of this.client.fetch("1:*", { uid: true, flags: true })) {
|
|
8436
|
+
if (query.unreadOnly && msg.flags?.has("\\Seen")) continue;
|
|
8437
|
+
if (query.starredOnly && !msg.flags?.has("\\Flagged")) continue;
|
|
8438
|
+
uids.push(msg.uid);
|
|
8439
|
+
}
|
|
8440
|
+
return uids;
|
|
8441
|
+
}
|
|
8442
|
+
/**
|
|
8443
|
+
* Fetches email data for a set of UIDs, either with full body or lightweight headers.
|
|
8444
|
+
*/
|
|
8445
|
+
async fetchEmails(uids, folder, returnBody) {
|
|
8446
|
+
if (!this.client) return [];
|
|
8447
|
+
const emails = [];
|
|
8448
|
+
if (returnBody) {
|
|
8449
|
+
for await (const msg of this.client.fetch(uids, { source: true, uid: true, flags: true })) {
|
|
8450
|
+
const parsed = await simpleParser(msg.source);
|
|
8451
|
+
parsed.flags = msg.flags;
|
|
8452
|
+
emails.push(mapParsedEmail(parsed, folder, this.accountId, msg.uid));
|
|
8453
|
+
}
|
|
8454
|
+
} else {
|
|
8455
|
+
for await (const msg of this.client.fetch(uids, {
|
|
8456
|
+
envelope: true,
|
|
8457
|
+
uid: true,
|
|
8458
|
+
flags: true,
|
|
8459
|
+
bodyStructure: true
|
|
8460
|
+
})) {
|
|
8461
|
+
const env = msg.envelope;
|
|
8462
|
+
emails.push({
|
|
8463
|
+
id: String(msg.uid),
|
|
8464
|
+
accountId: this.accountId,
|
|
8465
|
+
threadId: env.messageId || void 0,
|
|
8466
|
+
folder,
|
|
8467
|
+
from: env.from?.[0] ? { name: env.from[0].name || void 0, email: env.from[0].address || "" } : { email: "" },
|
|
8468
|
+
to: (env.to || []).map((a) => ({ name: a.name || void 0, email: a.address || "" })),
|
|
8469
|
+
cc: env.cc?.length ? env.cc.map((a) => ({ name: a.name || void 0, email: a.address || "" })) : void 0,
|
|
8470
|
+
bcc: env.bcc?.length ? env.bcc.map((a) => ({ name: a.name || void 0, email: a.address || "" })) : void 0,
|
|
8471
|
+
subject: env.subject || "(no subject)",
|
|
8472
|
+
date: env.date ? new Date(env.date).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
8473
|
+
body: { text: void 0, html: void 0 },
|
|
8474
|
+
snippet: env.subject || "",
|
|
8475
|
+
attachments: [],
|
|
8476
|
+
flags: {
|
|
8477
|
+
read: msg.flags?.has("\\Seen") ?? false,
|
|
8478
|
+
starred: msg.flags?.has("\\Flagged") ?? false,
|
|
8479
|
+
flagged: msg.flags?.has("\\Flagged") ?? false,
|
|
8480
|
+
draft: msg.flags?.has("\\Draft") ?? false
|
|
8481
|
+
}
|
|
8482
|
+
});
|
|
8483
|
+
}
|
|
8484
|
+
}
|
|
8485
|
+
return emails;
|
|
8486
|
+
}
|
|
8446
8487
|
async getEmail(id, folder) {
|
|
8447
8488
|
if (!this.client) throw new Error("Not connected");
|
|
8448
8489
|
const targetFolder = folder || "INBOX";
|
|
@@ -8814,7 +8855,21 @@ var init_account_manager = __esm({
|
|
|
8814
8855
|
}
|
|
8815
8856
|
async getProvider(accountId) {
|
|
8816
8857
|
const existing = this.providers.get(accountId);
|
|
8817
|
-
if (existing)
|
|
8858
|
+
if (existing) {
|
|
8859
|
+
const creds = this.credentials.get(accountId);
|
|
8860
|
+
if (creds?.oauth?.expiry) {
|
|
8861
|
+
const expiryDate = new Date(creds.oauth.expiry);
|
|
8862
|
+
const now = /* @__PURE__ */ new Date();
|
|
8863
|
+
if (!isNaN(expiryDate.getTime()) && expiryDate <= now) {
|
|
8864
|
+
await this.disconnectAccount(accountId);
|
|
8865
|
+
await this.connectAccount(accountId);
|
|
8866
|
+
const refreshed = this.providers.get(accountId);
|
|
8867
|
+
if (!refreshed) throw new Error(`Failed to reconnect account ${accountId} after token expiry`);
|
|
8868
|
+
return refreshed;
|
|
8869
|
+
}
|
|
8870
|
+
}
|
|
8871
|
+
return existing;
|
|
8872
|
+
}
|
|
8818
8873
|
await this.connectAccount(accountId);
|
|
8819
8874
|
const provider = this.providers.get(accountId);
|
|
8820
8875
|
if (!provider) throw new Error(`Failed to connect account ${accountId}`);
|
|
@@ -8866,38 +8921,43 @@ var init_account_manager = __esm({
|
|
|
8866
8921
|
if (!creds.oauth?.expiry) return;
|
|
8867
8922
|
const expiryDate = new Date(creds.oauth.expiry);
|
|
8868
8923
|
const fiveMinutesFromNow = new Date(Date.now() + 5 * 60 * 1e3);
|
|
8869
|
-
if (expiryDate > fiveMinutesFromNow) return;
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
msal_home_account_id: result.homeAccountId ?? homeAccountId
|
|
8886
|
-
};
|
|
8887
|
-
} else if (creds.oauth.refresh_token) {
|
|
8888
|
-
const result = await outlookAuth.refreshToken(creds.oauth.refresh_token);
|
|
8889
|
-
creds.oauth = {
|
|
8890
|
-
access_token: result.accessToken,
|
|
8891
|
-
refresh_token: creds.oauth.refresh_token,
|
|
8892
|
-
expiry: result.expiresOn?.toISOString() ?? ""
|
|
8893
|
-
};
|
|
8894
|
-
} else {
|
|
8895
|
-
throw new Error("No MSAL account ID or refresh token \u2014 re-authenticate via setup wizard");
|
|
8924
|
+
if (!isNaN(expiryDate.getTime()) && expiryDate > fiveMinutesFromNow) return;
|
|
8925
|
+
if (creds.provider === ProviderType.Gmail) {
|
|
8926
|
+
const auth = new GmailAuth(GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET);
|
|
8927
|
+
const newTokens = await auth.refreshAccessToken(creds.oauth.refresh_token);
|
|
8928
|
+
if (!newTokens.access_token) {
|
|
8929
|
+
throw new Error("Gmail token refresh returned empty access token \u2014 re-authenticate via setup wizard");
|
|
8930
|
+
}
|
|
8931
|
+
creds.oauth = newTokens;
|
|
8932
|
+
await this.store.save(creds);
|
|
8933
|
+
} else if (creds.provider === ProviderType.Outlook) {
|
|
8934
|
+
const outlookAuth = new OutlookAuth(OUTLOOK_CLIENT_ID);
|
|
8935
|
+
const homeAccountId = creds.oauth.msal_home_account_id;
|
|
8936
|
+
if (homeAccountId) {
|
|
8937
|
+
const result = await outlookAuth.refreshTokenSilent(homeAccountId);
|
|
8938
|
+
if (!result.accessToken) {
|
|
8939
|
+
throw new Error("Outlook token refresh returned empty access token \u2014 re-authenticate via setup wizard");
|
|
8896
8940
|
}
|
|
8897
|
-
|
|
8941
|
+
creds.oauth = {
|
|
8942
|
+
access_token: result.accessToken,
|
|
8943
|
+
refresh_token: creds.oauth.refresh_token,
|
|
8944
|
+
expiry: result.expiresOn?.toISOString() ?? "",
|
|
8945
|
+
msal_home_account_id: result.homeAccountId ?? homeAccountId
|
|
8946
|
+
};
|
|
8947
|
+
} else if (creds.oauth.refresh_token) {
|
|
8948
|
+
const result = await outlookAuth.refreshToken(creds.oauth.refresh_token);
|
|
8949
|
+
if (!result.accessToken) {
|
|
8950
|
+
throw new Error("Outlook token refresh returned empty access token \u2014 re-authenticate via setup wizard");
|
|
8951
|
+
}
|
|
8952
|
+
creds.oauth = {
|
|
8953
|
+
access_token: result.accessToken,
|
|
8954
|
+
refresh_token: creds.oauth.refresh_token,
|
|
8955
|
+
expiry: result.expiresOn?.toISOString() ?? ""
|
|
8956
|
+
};
|
|
8957
|
+
} else {
|
|
8958
|
+
throw new Error("No MSAL account ID or refresh token \u2014 re-authenticate via setup wizard");
|
|
8898
8959
|
}
|
|
8899
|
-
|
|
8900
|
-
console.error(`Token refresh failed for ${creds.provider}: ${error48.message}`);
|
|
8960
|
+
await this.store.save(creds);
|
|
8901
8961
|
}
|
|
8902
8962
|
}
|
|
8903
8963
|
};
|