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.
- package/index.js +65 -36
- package/package.json +1 -1
- 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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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 =
|
|
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 =
|
|
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
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.
|
|
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 ───────────────────────────────────────────────────────
|