@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 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
- this.client = Client.init({
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 searchResult = await this.client.search(
8394
- Object.keys(criteria).length > 0 ? criteria : { all: true },
8395
- { uid: true }
8396
- );
8397
- const allUids = Array.isArray(searchResult) ? searchResult : [];
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
- const emails = [];
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) return 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
- try {
8871
- if (creds.provider === ProviderType.Gmail) {
8872
- const auth = new GmailAuth(GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET);
8873
- const newTokens = await auth.refreshAccessToken(creds.oauth.refresh_token);
8874
- creds.oauth = newTokens;
8875
- await this.store.save(creds);
8876
- } else if (creds.provider === ProviderType.Outlook) {
8877
- const outlookAuth = new OutlookAuth(OUTLOOK_CLIENT_ID);
8878
- const homeAccountId = creds.oauth.msal_home_account_id;
8879
- if (homeAccountId) {
8880
- const result = await outlookAuth.refreshTokenSilent(homeAccountId);
8881
- creds.oauth = {
8882
- access_token: result.accessToken,
8883
- refresh_token: creds.oauth.refresh_token,
8884
- expiry: result.expiresOn?.toISOString() ?? "",
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
- await this.store.save(creds);
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
- } catch (error48) {
8900
- console.error(`Token refresh failed for ${creds.provider}: ${error48.message}`);
8960
+ await this.store.save(creds);
8901
8961
  }
8902
8962
  }
8903
8963
  };