icloud-mcp 1.5.0 → 1.5.1

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.
Files changed (3) hide show
  1. package/index.js +65 -36
  2. package/package.json +1 -1
  3. package/test.js +1 -1
package/index.js CHANGED
@@ -42,8 +42,35 @@ function createClient() {
42
42
 
43
43
  // ─── Managed client helpers ───────────────────────────────────────────────────
44
44
 
45
- async function openClient(mailbox) {
45
+ // Rate limit: space out connection initiations within a single server process
46
+ // to avoid triggering iCloud's connection throttle under concurrent tool calls.
47
+ // Wraps connect() on every client returned by createClient() so the gate
48
+ // applies regardless of whether tools use openClient() or createClient() directly.
49
+ // Uses a serialized gate — concurrent callers queue up; each waits 200ms after
50
+ // the previous before initiating its connection. Connections run concurrently
51
+ // after passing the gate.
52
+ let _lastConnectTime = 0;
53
+ let _connectGate = Promise.resolve();
54
+ const MIN_CONNECT_INTERVAL = 200; // ms between connection initiations
55
+
56
+ function createRateLimitedClient() {
46
57
  const client = createClient();
58
+ const originalConnect = client.connect.bind(client);
59
+ client.connect = async () => {
60
+ await new Promise(resolve => {
61
+ _connectGate = _connectGate.then(async () => {
62
+ const wait = MIN_CONNECT_INTERVAL - (Date.now() - _lastConnectTime);
63
+ if (wait > 0) await new Promise(r => setTimeout(r, wait));
64
+ _lastConnectTime = Date.now();
65
+ }).then(resolve, resolve);
66
+ });
67
+ return originalConnect();
68
+ };
69
+ return client;
70
+ }
71
+
72
+ async function openClient(mailbox) {
73
+ const client = createRateLimitedClient();
47
74
  await client.connect();
48
75
  if (mailbox) await client.mailboxOpen(mailbox);
49
76
  return client;
@@ -583,7 +610,7 @@ async function safeMoveEmails(uids, sourceMailbox, targetMailbox) {
583
610
  // ─── Email Functions ──────────────────────────────────────────────────────────
584
611
 
585
612
  async function fetchEmails(mailbox = 'INBOX', limit = 10, onlyUnread = false, page = 1) {
586
- const client = createClient();
613
+ const client = createRateLimitedClient();
587
614
  await client.connect();
588
615
  const mb = await client.mailboxOpen(mailbox);
589
616
  const total = mb.exists;
@@ -637,7 +664,7 @@ async function fetchEmails(mailbox = 'INBOX', limit = 10, onlyUnread = false, pa
637
664
  }
638
665
 
639
666
  async function getInboxSummary(mailbox = 'INBOX') {
640
- const client = createClient();
667
+ const client = createRateLimitedClient();
641
668
  await client.connect();
642
669
  const status = await client.status(mailbox, { messages: true, unseen: true, recent: true });
643
670
  await client.logout();
@@ -645,7 +672,7 @@ async function getInboxSummary(mailbox = 'INBOX') {
645
672
  }
646
673
 
647
674
  async function getTopSenders(mailbox = 'INBOX', sampleSize = 500, maxResults = 20) {
648
- const client = createClient();
675
+ const client = createRateLimitedClient();
649
676
  await client.connect();
650
677
  const mb = await client.mailboxOpen(mailbox);
651
678
  const total = mb.exists;
@@ -674,7 +701,7 @@ async function getTopSenders(mailbox = 'INBOX', sampleSize = 500, maxResults = 2
674
701
  }
675
702
 
676
703
  async function getUnreadSenders(mailbox = 'INBOX', sampleSize = 500, maxResults = 20) {
677
- const client = createClient();
704
+ const client = createRateLimitedClient();
678
705
  await client.connect();
679
706
  await client.mailboxOpen(mailbox);
680
707
  const uids = (await client.search({ seen: false }, { uid: true })) ?? [];
@@ -696,7 +723,7 @@ async function getUnreadSenders(mailbox = 'INBOX', sampleSize = 500, maxResults
696
723
  }
697
724
 
698
725
  async function getEmailsBySender(sender, mailbox = 'INBOX', limit = 10) {
699
- const client = createClient();
726
+ const client = createRateLimitedClient();
700
727
  await client.connect();
701
728
  await client.mailboxOpen(mailbox);
702
729
  const uids = (await client.search({ from: sender }, { uid: true })) ?? [];
@@ -721,7 +748,7 @@ async function getEmailsBySender(sender, mailbox = 'INBOX', limit = 10) {
721
748
  }
722
749
 
723
750
  async function bulkDeleteBySender(sender, mailbox = 'INBOX') {
724
- const client = createClient();
751
+ const client = createRateLimitedClient();
725
752
  await client.connect();
726
753
  await client.mailboxOpen(mailbox);
727
754
  const uids = (await client.search({ from: sender }, { uid: true })) ?? [];
@@ -736,12 +763,13 @@ async function bulkDeleteBySender(sender, mailbox = 'INBOX') {
736
763
  return { deleted, sender };
737
764
  }
738
765
 
739
- async function bulkMoveBySender(sender, targetMailbox, sourceMailbox = 'INBOX') {
740
- const client = createClient();
766
+ async function bulkMoveBySender(sender, targetMailbox, sourceMailbox = 'INBOX', dryRun = false) {
767
+ const client = createRateLimitedClient();
741
768
  await client.connect();
742
769
  await client.mailboxOpen(sourceMailbox);
743
770
  const uids = (await client.search({ from: sender }, { uid: true })) ?? [];
744
771
  await client.logout();
772
+ if (dryRun) return { dryRun: true, wouldMove: uids.length, sender, sourceMailbox, targetMailbox };
745
773
  if (uids.length === 0) return { moved: 0 };
746
774
  await ensureMailbox(targetMailbox);
747
775
  const result = await safeMoveEmails(uids, sourceMailbox, targetMailbox);
@@ -749,7 +777,7 @@ async function bulkMoveBySender(sender, targetMailbox, sourceMailbox = 'INBOX')
749
777
  }
750
778
 
751
779
  async function bulkDeleteBySubject(subject, mailbox = 'INBOX') {
752
- const client = createClient();
780
+ const client = createRateLimitedClient();
753
781
  await client.connect();
754
782
  await client.mailboxOpen(mailbox);
755
783
  const uids = (await client.search({ subject }, { uid: true })) ?? [];
@@ -765,7 +793,7 @@ async function bulkDeleteBySubject(subject, mailbox = 'INBOX') {
765
793
  }
766
794
 
767
795
  async function deleteOlderThan(days, mailbox = 'INBOX') {
768
- const client = createClient();
796
+ const client = createRateLimitedClient();
769
797
  await client.connect();
770
798
  await client.mailboxOpen(mailbox);
771
799
  const date = new Date();
@@ -783,7 +811,7 @@ async function deleteOlderThan(days, mailbox = 'INBOX') {
783
811
  }
784
812
 
785
813
  async function getEmailsByDateRange(startDate, endDate, mailbox = 'INBOX', limit = 10) {
786
- const client = createClient();
814
+ const client = createRateLimitedClient();
787
815
  await client.connect();
788
816
  await client.mailboxOpen(mailbox);
789
817
  const uids = (await client.search({ since: new Date(startDate), before: new Date(endDate) }, { uid: true })) ?? [];
@@ -808,7 +836,7 @@ async function getEmailsByDateRange(startDate, endDate, mailbox = 'INBOX', limit
808
836
  }
809
837
 
810
838
  async function bulkMarkRead(mailbox = 'INBOX', sender = null) {
811
- const client = createClient();
839
+ const client = createRateLimitedClient();
812
840
  await client.connect();
813
841
  await client.mailboxOpen(mailbox);
814
842
  const query = sender ? { from: sender, seen: false } : { seen: false };
@@ -820,7 +848,7 @@ async function bulkMarkRead(mailbox = 'INBOX', sender = null) {
820
848
  }
821
849
 
822
850
  async function bulkMarkUnread(mailbox = 'INBOX', sender = null) {
823
- const client = createClient();
851
+ const client = createRateLimitedClient();
824
852
  await client.connect();
825
853
  await client.mailboxOpen(mailbox);
826
854
  const query = sender ? { from: sender, seen: true } : { seen: true };
@@ -832,7 +860,7 @@ async function bulkMarkUnread(mailbox = 'INBOX', sender = null) {
832
860
  }
833
861
 
834
862
  async function bulkFlag(filters, flagged, mailbox = 'INBOX') {
835
- const client = createClient();
863
+ const client = createRateLimitedClient();
836
864
  await client.connect();
837
865
  await client.mailboxOpen(mailbox);
838
866
  const query = buildQuery(filters);
@@ -848,7 +876,7 @@ async function bulkFlag(filters, flagged, mailbox = 'INBOX') {
848
876
  }
849
877
 
850
878
  async function emptyTrash() {
851
- const client = createClient();
879
+ const client = createRateLimitedClient();
852
880
  await client.connect();
853
881
  await client.mailboxOpen('Deleted Messages');
854
882
  const uids = (await client.search({ all: true }, { uid: true })) ?? [];
@@ -864,7 +892,7 @@ async function emptyTrash() {
864
892
  }
865
893
 
866
894
  async function createMailbox(name) {
867
- const client = createClient();
895
+ const client = createRateLimitedClient();
868
896
  await client.connect();
869
897
  await client.mailboxCreate(name);
870
898
  await client.logout();
@@ -872,7 +900,7 @@ async function createMailbox(name) {
872
900
  }
873
901
 
874
902
  async function renameMailbox(oldName, newName) {
875
- const client = createClient();
903
+ const client = createRateLimitedClient();
876
904
  await client.connect();
877
905
  try {
878
906
  await Promise.race([
@@ -888,7 +916,7 @@ async function renameMailbox(oldName, newName) {
888
916
  }
889
917
 
890
918
  async function deleteMailbox(name) {
891
- const client = createClient();
919
+ const client = createRateLimitedClient();
892
920
  await client.connect();
893
921
  try {
894
922
  await Promise.race([
@@ -904,7 +932,7 @@ async function deleteMailbox(name) {
904
932
  }
905
933
 
906
934
  async function getMailboxSummary(mailbox) {
907
- const client = createClient();
935
+ const client = createRateLimitedClient();
908
936
  await client.connect();
909
937
  const status = await client.status(mailbox, { messages: true, unseen: true, recent: true });
910
938
  await client.logout();
@@ -912,7 +940,7 @@ async function getMailboxSummary(mailbox) {
912
940
  }
913
941
 
914
942
  async function getEmailContent(uid, mailbox = 'INBOX') {
915
- const client = createClient();
943
+ const client = createRateLimitedClient();
916
944
  await client.connect();
917
945
  await client.mailboxOpen(mailbox);
918
946
  const meta = await client.fetchOne(uid, { envelope: true, flags: true }, { uid: true });
@@ -942,7 +970,7 @@ async function getEmailContent(uid, mailbox = 'INBOX') {
942
970
  }
943
971
 
944
972
  async function flagEmail(uid, flagged, mailbox = 'INBOX') {
945
- const client = createClient();
973
+ const client = createRateLimitedClient();
946
974
  await client.connect();
947
975
  await client.mailboxOpen(mailbox);
948
976
  if (flagged) {
@@ -955,7 +983,7 @@ async function flagEmail(uid, flagged, mailbox = 'INBOX') {
955
983
  }
956
984
 
957
985
  async function markAsRead(uid, seen, mailbox = 'INBOX') {
958
- const client = createClient();
986
+ const client = createRateLimitedClient();
959
987
  await client.connect();
960
988
  await client.mailboxOpen(mailbox);
961
989
  if (seen) {
@@ -968,7 +996,7 @@ async function markAsRead(uid, seen, mailbox = 'INBOX') {
968
996
  }
969
997
 
970
998
  async function deleteEmail(uid, mailbox = 'INBOX') {
971
- const client = createClient();
999
+ const client = createRateLimitedClient();
972
1000
  await client.connect();
973
1001
  await client.mailboxOpen(mailbox);
974
1002
  await client.messageDelete(uid, { uid: true });
@@ -977,7 +1005,7 @@ async function deleteEmail(uid, mailbox = 'INBOX') {
977
1005
  }
978
1006
 
979
1007
  async function listMailboxes() {
980
- const client = createClient();
1008
+ const client = createRateLimitedClient();
981
1009
  await client.connect();
982
1010
  const tree = await client.listTree();
983
1011
  const mailboxes = [];
@@ -993,7 +1021,7 @@ async function listMailboxes() {
993
1021
  }
994
1022
 
995
1023
  async function searchEmails(query, mailbox = 'INBOX', limit = 10, filters = {}) {
996
- const client = createClient();
1024
+ const client = createRateLimitedClient();
997
1025
  await client.connect();
998
1026
  await client.mailboxOpen(mailbox);
999
1027
 
@@ -1024,7 +1052,7 @@ async function searchEmails(query, mailbox = 'INBOX', limit = 10, filters = {})
1024
1052
  }
1025
1053
 
1026
1054
  async function moveEmail(uid, targetMailbox, sourceMailbox = 'INBOX') {
1027
- const client = createClient();
1055
+ const client = createRateLimitedClient();
1028
1056
  await client.connect();
1029
1057
  await client.mailboxOpen(sourceMailbox);
1030
1058
  await client.messageMove(uid, targetMailbox, { uid: true });
@@ -1051,14 +1079,14 @@ function buildQuery(filters) {
1051
1079
  }
1052
1080
 
1053
1081
  async function ensureMailbox(name) {
1054
- const client = createClient();
1082
+ const client = createRateLimitedClient();
1055
1083
  await client.connect();
1056
1084
  try { await client.mailboxCreate(name); } catch { /* already exists */ }
1057
1085
  await client.logout();
1058
1086
  }
1059
1087
 
1060
1088
  async function bulkMove(filters, targetMailbox, sourceMailbox = 'INBOX', dryRun = false, limit = null) {
1061
- const client = createClient();
1089
+ const client = createRateLimitedClient();
1062
1090
  await client.connect();
1063
1091
  await client.mailboxOpen(sourceMailbox);
1064
1092
  const query = buildQuery(filters);
@@ -1083,7 +1111,7 @@ async function bulkMove(filters, targetMailbox, sourceMailbox = 'INBOX', dryRun
1083
1111
  // hanging forever.
1084
1112
 
1085
1113
  async function bulkDelete(filters, sourceMailbox = 'INBOX', dryRun = false) {
1086
- const client = createClient();
1114
+ const client = createRateLimitedClient();
1087
1115
  await client.connect();
1088
1116
  await client.mailboxOpen(sourceMailbox);
1089
1117
  const query = buildQuery(filters);
@@ -1120,7 +1148,7 @@ async function bulkDelete(filters, sourceMailbox = 'INBOX', dryRun = false) {
1120
1148
  }
1121
1149
 
1122
1150
  async function countEmails(filters, mailbox = 'INBOX') {
1123
- const client = createClient();
1151
+ const client = createRateLimitedClient();
1124
1152
  await client.connect();
1125
1153
  await client.mailboxOpen(mailbox);
1126
1154
  const query = buildQuery(filters);
@@ -1157,7 +1185,7 @@ function logClear() {
1157
1185
 
1158
1186
  async function main() {
1159
1187
  const server = new Server(
1160
- { name: 'icloud-mail', version: '1.5.0' },
1188
+ { name: 'icloud-mail', version: '1.5.1' },
1161
1189
  { capabilities: { tools: {} } }
1162
1190
  );
1163
1191
 
@@ -1337,7 +1365,8 @@ async function main() {
1337
1365
  properties: {
1338
1366
  sender: { type: 'string', description: 'Sender email address' },
1339
1367
  targetMailbox: { type: 'string', description: 'Destination folder' },
1340
- sourceMailbox: { type: 'string', description: 'Source mailbox (default INBOX)' }
1368
+ sourceMailbox: { type: 'string', description: 'Source mailbox (default INBOX)' },
1369
+ dryRun: { type: 'boolean', description: 'Preview only — return count without moving' }
1341
1370
  },
1342
1371
  required: ['sender', 'targetMailbox']
1343
1372
  }
@@ -1585,7 +1614,7 @@ async function main() {
1585
1614
  const { targetMailbox, sourceMailbox, dryRun, limit, ...filters } = args;
1586
1615
  result = await bulkMove(filters, targetMailbox, sourceMailbox || 'INBOX', dryRun || false, limit ?? null);
1587
1616
  } else if (name === 'bulk_move_by_sender') {
1588
- result = await bulkMoveBySender(args.sender, args.targetMailbox, args.sourceMailbox || 'INBOX');
1617
+ result = await bulkMoveBySender(args.sender, args.targetMailbox, args.sourceMailbox || 'INBOX', args.dryRun || false);
1589
1618
  } else if (name === 'bulk_delete') {
1590
1619
  // IMPROVEMENT 3: bulk_delete now has per-chunk timeouts internally
1591
1620
  const { sourceMailbox, dryRun, ...filters } = args;
@@ -1710,7 +1739,7 @@ async function runDoctor() {
1710
1739
  {
1711
1740
  label: `Connected to imap.mail.me.com:993`,
1712
1741
  run: async () => {
1713
- const client = createClient();
1742
+ const client = createRateLimitedClient();
1714
1743
  await client.connect();
1715
1744
  await client.logout();
1716
1745
  }
@@ -1725,7 +1754,7 @@ async function runDoctor() {
1725
1754
  {
1726
1755
  label: 'INBOX opened',
1727
1756
  run: async () => {
1728
- const client = createClient();
1757
+ const client = createRateLimitedClient();
1729
1758
  await client.connect();
1730
1759
  const mb = await client.mailboxOpen('INBOX');
1731
1760
  await client.logout();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icloud-mcp",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "A Model Context Protocol (MCP) server for iCloud Mail",
5
5
  "main": "index.js",
6
6
  "bin": {
package/test.js CHANGED
@@ -137,7 +137,7 @@ function assert(condition, message) {
137
137
  if (!condition) throw new Error(message);
138
138
  }
139
139
 
140
- console.log('\n🧪 iCloud MCP Server Tests v1.5.0\n');
140
+ console.log('\n🧪 iCloud MCP Server Tests v1.5.1\n');
141
141
  if (VERBOSE) console.log('🔊 Verbose mode: showing all stderr output\n');
142
142
 
143
143
  // ─── Pre-flight cleanup ───────────────────────────────────────────────────────