icloud-mcp 1.8.0 → 1.8.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 +47 -11
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -89,6 +89,7 @@ async function reconnect(client, mailbox) {
|
|
|
89
89
|
|
|
90
90
|
const CHUNK_SIZE = 500;
|
|
91
91
|
const CHUNK_SIZE_RETRY = 100;
|
|
92
|
+
const ATTACHMENT_SCAN_LIMIT = 500; // max UIDs to scan client-side for hasAttachment filter
|
|
92
93
|
|
|
93
94
|
function readManifest() {
|
|
94
95
|
if (!existsSync(MANIFEST_FILE)) return { current: null, history: [] };
|
|
@@ -1302,7 +1303,14 @@ async function searchEmails(query, mailbox = 'INBOX', limit = 10, filters = {})
|
|
|
1302
1303
|
? { ...textQuery, ...extraQuery }
|
|
1303
1304
|
: textQuery;
|
|
1304
1305
|
|
|
1305
|
-
|
|
1306
|
+
let uids = (await client.search(finalQuery, { uid: true })) ?? [];
|
|
1307
|
+
if (filters.hasAttachment) {
|
|
1308
|
+
if (uids.length > ATTACHMENT_SCAN_LIMIT) {
|
|
1309
|
+
await client.logout();
|
|
1310
|
+
return { total: null, showing: 0, emails: [], error: `hasAttachment requires narrower filters first — ${uids.length} candidates exceeds scan limit of ${ATTACHMENT_SCAN_LIMIT}. Add from/since/before/subject filters to reduce the set.` };
|
|
1311
|
+
}
|
|
1312
|
+
uids = await filterUidsByAttachment(client, uids);
|
|
1313
|
+
}
|
|
1306
1314
|
const emails = [];
|
|
1307
1315
|
const recentUids = uids.slice(-limit).reverse();
|
|
1308
1316
|
for (const uid of recentUids) {
|
|
@@ -1344,16 +1352,23 @@ function buildQuery(filters) {
|
|
|
1344
1352
|
if (filters.flagged === false) query.unflagged = true;
|
|
1345
1353
|
if (filters.larger) query.larger = filters.larger * 1024;
|
|
1346
1354
|
if (filters.smaller) query.smaller = filters.smaller * 1024;
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
{ header: { 'Content-Disposition': 'attachment' } },
|
|
1350
|
-
{ header: { 'Content-Type': 'multipart/mixed' } }
|
|
1351
|
-
];
|
|
1352
|
-
}
|
|
1355
|
+
// hasAttachment is handled as a client-side post-filter (see filterUidsByAttachment)
|
|
1356
|
+
// iCloud does not support SEARCH HEADER or reliable size-based attachment detection
|
|
1353
1357
|
if (Object.keys(query).length === 0) query.all = true;
|
|
1354
1358
|
return query;
|
|
1355
1359
|
}
|
|
1356
1360
|
|
|
1361
|
+
async function filterUidsByAttachment(client, uids) {
|
|
1362
|
+
if (uids.length === 0) return [];
|
|
1363
|
+
const result = [];
|
|
1364
|
+
for await (const msg of client.fetch(uids, { bodyStructure: true }, { uid: true })) {
|
|
1365
|
+
if (msg.bodyStructure && findAttachments(msg.bodyStructure).length > 0) {
|
|
1366
|
+
result.push(msg.uid);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return result;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1357
1372
|
async function ensureMailbox(name) {
|
|
1358
1373
|
const client = createRateLimitedClient();
|
|
1359
1374
|
await client.connect();
|
|
@@ -1367,6 +1382,13 @@ async function bulkMove(filters, targetMailbox, sourceMailbox = 'INBOX', dryRun
|
|
|
1367
1382
|
await client.mailboxOpen(sourceMailbox);
|
|
1368
1383
|
const query = buildQuery(filters);
|
|
1369
1384
|
let uids = (await client.search(query, { uid: true })) ?? [];
|
|
1385
|
+
if (filters.hasAttachment) {
|
|
1386
|
+
if (uids.length > ATTACHMENT_SCAN_LIMIT) {
|
|
1387
|
+
await client.logout();
|
|
1388
|
+
return { error: `hasAttachment requires narrower filters first — ${uids.length} candidates exceeds scan limit of ${ATTACHMENT_SCAN_LIMIT}.` };
|
|
1389
|
+
}
|
|
1390
|
+
uids = await filterUidsByAttachment(client, uids);
|
|
1391
|
+
}
|
|
1370
1392
|
await client.logout();
|
|
1371
1393
|
|
|
1372
1394
|
if (limit !== null) uids = uids.slice(0, limit);
|
|
@@ -1391,7 +1413,14 @@ async function bulkDelete(filters, sourceMailbox = 'INBOX', dryRun = false) {
|
|
|
1391
1413
|
await client.connect();
|
|
1392
1414
|
await client.mailboxOpen(sourceMailbox);
|
|
1393
1415
|
const query = buildQuery(filters);
|
|
1394
|
-
|
|
1416
|
+
let uids = (await client.search(query, { uid: true })) ?? [];
|
|
1417
|
+
if (filters.hasAttachment) {
|
|
1418
|
+
if (uids.length > ATTACHMENT_SCAN_LIMIT) {
|
|
1419
|
+
await client.logout();
|
|
1420
|
+
return { error: `hasAttachment requires narrower filters first — ${uids.length} candidates exceeds scan limit of ${ATTACHMENT_SCAN_LIMIT}.` };
|
|
1421
|
+
}
|
|
1422
|
+
uids = await filterUidsByAttachment(client, uids);
|
|
1423
|
+
}
|
|
1395
1424
|
|
|
1396
1425
|
if (dryRun) {
|
|
1397
1426
|
await client.logout();
|
|
@@ -1428,7 +1457,14 @@ async function countEmails(filters, mailbox = 'INBOX') {
|
|
|
1428
1457
|
await client.connect();
|
|
1429
1458
|
await client.mailboxOpen(mailbox);
|
|
1430
1459
|
const query = buildQuery(filters);
|
|
1431
|
-
|
|
1460
|
+
let uids = (await client.search(query, { uid: true })) ?? [];
|
|
1461
|
+
if (filters.hasAttachment) {
|
|
1462
|
+
if (uids.length > ATTACHMENT_SCAN_LIMIT) {
|
|
1463
|
+
await client.logout();
|
|
1464
|
+
return { count: null, mailbox, filters, error: `hasAttachment requires narrower filters first — ${uids.length} candidates exceeds scan limit of ${ATTACHMENT_SCAN_LIMIT}. Add from/since/before/subject filters to reduce the set.` };
|
|
1465
|
+
}
|
|
1466
|
+
uids = await filterUidsByAttachment(client, uids);
|
|
1467
|
+
}
|
|
1432
1468
|
await client.logout();
|
|
1433
1469
|
return { count: uids.length, mailbox, filters };
|
|
1434
1470
|
}
|
|
@@ -1461,7 +1497,7 @@ function logClear() {
|
|
|
1461
1497
|
|
|
1462
1498
|
async function main() {
|
|
1463
1499
|
const server = new Server(
|
|
1464
|
-
{ name: 'icloud-mail', version: '1.8.
|
|
1500
|
+
{ name: 'icloud-mail', version: '1.8.1' },
|
|
1465
1501
|
{ capabilities: { tools: {} } }
|
|
1466
1502
|
);
|
|
1467
1503
|
|
|
@@ -1475,7 +1511,7 @@ async function main() {
|
|
|
1475
1511
|
flagged: { type: 'boolean', description: 'True for flagged only, false for unflagged only' },
|
|
1476
1512
|
larger: { type: 'number', description: 'Only emails larger than this size in KB' },
|
|
1477
1513
|
smaller: { type: 'number', description: 'Only emails smaller than this size in KB' },
|
|
1478
|
-
hasAttachment: { type: 'boolean', description: 'Only emails with attachments' }
|
|
1514
|
+
hasAttachment: { type: 'boolean', description: 'Only emails with attachments (client-side BODYSTRUCTURE scan — must be combined with other filters that narrow results to under 500 emails first)' }
|
|
1479
1515
|
};
|
|
1480
1516
|
|
|
1481
1517
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|