icloud-mcp 1.4.1 → 1.4.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/README.md +35 -7
- package/index.js +145 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,9 +43,39 @@ Then find the install location:
|
|
|
43
43
|
npm root -g
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
The path varies by setup:
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
| Setup | Typical path |
|
|
49
|
+
|-------|-------------|
|
|
50
|
+
| Mac with Homebrew Node | `/opt/homebrew/lib/node_modules` |
|
|
51
|
+
| Mac with system Node | `/usr/local/lib/node_modules` |
|
|
52
|
+
| nvm | `~/.nvm/versions/node/v20.x.x/lib/node_modules` |
|
|
53
|
+
|
|
54
|
+
### 3. Verify your setup
|
|
55
|
+
|
|
56
|
+
Before configuring Claude Desktop, run the doctor command to confirm everything is working:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
IMAP_USER="you@icloud.com" IMAP_PASSWORD="your-app-specific-password" node $(npm root -g)/icloud-mcp/index.js --doctor
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
You should see:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
icloud-mcp doctor
|
|
66
|
+
─────────────────────────────
|
|
67
|
+
✅ IMAP_USER is set
|
|
68
|
+
✅ IMAP_PASSWORD is set
|
|
69
|
+
✅ Connected to imap.mail.me.com:993
|
|
70
|
+
✅ Authenticated as you@icloud.com
|
|
71
|
+
✅ INBOX opened (12453 messages)
|
|
72
|
+
─────────────────────────────
|
|
73
|
+
All checks passed. Ready to use with Claude Desktop.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If any step fails, a plain-English explanation and suggested fix will be shown.
|
|
77
|
+
|
|
78
|
+
### 4. Configure Claude Desktop
|
|
49
79
|
|
|
50
80
|
Open your Claude Desktop config file:
|
|
51
81
|
|
|
@@ -53,7 +83,7 @@ Open your Claude Desktop config file:
|
|
|
53
83
|
open ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
|
54
84
|
```
|
|
55
85
|
|
|
56
|
-
Add the following under `mcpServers`, replacing the path with your npm root
|
|
86
|
+
Add the following under `mcpServers`, replacing the path with your npm root from step 2:
|
|
57
87
|
|
|
58
88
|
```json
|
|
59
89
|
{
|
|
@@ -70,9 +100,7 @@ Add the following under `mcpServers`, replacing the path with your npm root path
|
|
|
70
100
|
}
|
|
71
101
|
```
|
|
72
102
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
### 4. Add Custom Instructions (Recommended)
|
|
103
|
+
### 5. Add Custom Instructions (Recommended)
|
|
76
104
|
|
|
77
105
|
For large inbox operations, add the following to Claude Desktop's custom instructions to ensure Claude stays on track and checks in with you regularly. Go to **Claude Desktop → Settings → Custom Instructions** and add:
|
|
78
106
|
|
|
@@ -85,7 +113,7 @@ When using icloud-mail tools:
|
|
|
85
113
|
5. If you are ever unsure what you have done so far, call log_read before proceeding
|
|
86
114
|
```
|
|
87
115
|
|
|
88
|
-
###
|
|
116
|
+
### 6. Restart Claude Desktop
|
|
89
117
|
|
|
90
118
|
Fully quit Claude Desktop (Cmd+Q) and reopen it. You should now be able to manage your iCloud inbox through Claude.
|
|
91
119
|
|
package/index.js
CHANGED
|
@@ -15,8 +15,12 @@ const IMAP_USER = process.env.IMAP_USER;
|
|
|
15
15
|
const IMAP_PASSWORD = process.env.IMAP_PASSWORD;
|
|
16
16
|
|
|
17
17
|
if (!IMAP_USER || !IMAP_PASSWORD) {
|
|
18
|
-
process.
|
|
19
|
-
|
|
18
|
+
if (process.argv.includes('--doctor')) {
|
|
19
|
+
// Doctor will handle missing credentials with friendly output
|
|
20
|
+
} else {
|
|
21
|
+
process.stderr.write('Error: IMAP_USER and IMAP_PASSWORD environment variables are required\n');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
function createClient() {
|
|
@@ -977,7 +981,7 @@ function logClear() {
|
|
|
977
981
|
|
|
978
982
|
async function main() {
|
|
979
983
|
const server = new Server(
|
|
980
|
-
{ name: 'icloud-mail', version: '1.
|
|
984
|
+
{ name: 'icloud-mail', version: '1.6.0' },
|
|
981
985
|
{ capabilities: { tools: {} } }
|
|
982
986
|
);
|
|
983
987
|
|
|
@@ -1426,7 +1430,7 @@ async function main() {
|
|
|
1426
1430
|
}
|
|
1427
1431
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1428
1432
|
} catch (error) {
|
|
1429
|
-
return { content: [{ type: 'text', text: `Error: ${error
|
|
1433
|
+
return { content: [{ type: 'text', text: `Error: ${friendlyError(error)}` }], isError: true };
|
|
1430
1434
|
}
|
|
1431
1435
|
});
|
|
1432
1436
|
|
|
@@ -1435,6 +1439,132 @@ async function main() {
|
|
|
1435
1439
|
process.stderr.write('iCloud Mail MCP Server running\n');
|
|
1436
1440
|
}
|
|
1437
1441
|
|
|
1442
|
+
// ─── Friendly error messages ──────────────────────────────────────────────────
|
|
1443
|
+
|
|
1444
|
+
function friendlyError(err) {
|
|
1445
|
+
const msg = err.message ?? '';
|
|
1446
|
+
|
|
1447
|
+
if (msg.includes('AUTHENTICATIONFAILED') || msg.includes('Invalid credentials') || msg.includes('Authentication failed')) {
|
|
1448
|
+
return [
|
|
1449
|
+
'Authentication failed.',
|
|
1450
|
+
'→ Make sure IMAP_PASSWORD is an app-specific password, not your regular iCloud password.',
|
|
1451
|
+
'→ Generate one at: appleid.apple.com → Sign-In and Security → App-Specific Passwords',
|
|
1452
|
+
'→ Also check that IMAP_USER is your full iCloud email address (e.g. you@icloud.com)'
|
|
1453
|
+
].join('\n');
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND') || msg.includes('ENETUNREACH')) {
|
|
1457
|
+
return [
|
|
1458
|
+
'Could not reach imap.mail.me.com:993.',
|
|
1459
|
+
'→ Check your internet connection.',
|
|
1460
|
+
'→ If you are behind a firewall or VPN, port 993 may be blocked.'
|
|
1461
|
+
].join('\n');
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
if (msg.includes('ETIMEDOUT') || msg.includes('socket hang up')) {
|
|
1465
|
+
return [
|
|
1466
|
+
'Connection to iCloud timed out.',
|
|
1467
|
+
'→ Check your internet connection and try again.',
|
|
1468
|
+
'→ iCloud IMAP can be slow under load — this is usually transient.'
|
|
1469
|
+
].join('\n');
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
if (msg.includes('ECONNRESET')) {
|
|
1473
|
+
return [
|
|
1474
|
+
'iCloud closed the connection unexpectedly.',
|
|
1475
|
+
'→ This is usually transient. Try again in a few seconds.'
|
|
1476
|
+
].join('\n');
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
if (msg.includes('Mailbox does not exist') || msg.includes('does not exist') || msg.includes('NONEXISTENT')) {
|
|
1480
|
+
return [
|
|
1481
|
+
`Mailbox not found: ${msg}`,
|
|
1482
|
+
'→ Check the folder name is correct — iCloud folder names are case-sensitive.',
|
|
1483
|
+
'→ Use list_mailboxes to see all available folders.'
|
|
1484
|
+
].join('\n');
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// Fall through — return original message
|
|
1488
|
+
return msg;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// ─── Doctor command ───────────────────────────────────────────────────────────
|
|
1492
|
+
|
|
1493
|
+
async function runDoctor() {
|
|
1494
|
+
const divider = '─'.repeat(45);
|
|
1495
|
+
process.stdout.write(`\nicloud-mcp doctor\n${divider}\n`);
|
|
1496
|
+
|
|
1497
|
+
const checks = [
|
|
1498
|
+
{
|
|
1499
|
+
label: 'IMAP_USER is set',
|
|
1500
|
+
run: () => {
|
|
1501
|
+
if (!IMAP_USER) throw new Error('IMAP_USER environment variable is not set.\n→ Add it to your Claude Desktop config env block.');
|
|
1502
|
+
}
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
label: 'IMAP_PASSWORD is set',
|
|
1506
|
+
run: () => {
|
|
1507
|
+
if (!IMAP_PASSWORD) throw new Error('IMAP_PASSWORD environment variable is not set.\n→ Add it to your Claude Desktop config env block.');
|
|
1508
|
+
}
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
label: 'IMAP_USER looks like an email address',
|
|
1512
|
+
run: () => {
|
|
1513
|
+
if (!IMAP_USER?.includes('@')) throw new Error(`IMAP_USER "${IMAP_USER}" doesn't look like an email address.\n→ Use your full iCloud address, e.g. you@icloud.com`);
|
|
1514
|
+
}
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
label: `Connected to imap.mail.me.com:993`,
|
|
1518
|
+
run: async () => {
|
|
1519
|
+
const client = createClient();
|
|
1520
|
+
await client.connect();
|
|
1521
|
+
await client.logout();
|
|
1522
|
+
}
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
label: `Authenticated as ${IMAP_USER}`,
|
|
1526
|
+
run: async () => {
|
|
1527
|
+
// Auth is validated as part of connect — if we reach here it passed.
|
|
1528
|
+
// This check exists to give a clearer label in the output.
|
|
1529
|
+
}
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
label: 'INBOX opened',
|
|
1533
|
+
run: async () => {
|
|
1534
|
+
const client = createClient();
|
|
1535
|
+
await client.connect();
|
|
1536
|
+
const mb = await client.mailboxOpen('INBOX');
|
|
1537
|
+
await client.logout();
|
|
1538
|
+
return `${mb.exists.toLocaleString()} messages`;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
];
|
|
1542
|
+
|
|
1543
|
+
let allPassed = true;
|
|
1544
|
+
|
|
1545
|
+
for (const check of checks) {
|
|
1546
|
+
try {
|
|
1547
|
+
const detail = await check.run();
|
|
1548
|
+
const suffix = detail ? ` (${detail})` : '';
|
|
1549
|
+
process.stdout.write(`✅ ${check.label}${suffix}\n`);
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
process.stdout.write(`❌ ${check.label}\n ${friendlyError(err).replace(/\n/g, '\n ')}\n`);
|
|
1552
|
+
allPassed = false;
|
|
1553
|
+
break; // No point continuing after a failure
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
process.stdout.write(`${divider}\n`);
|
|
1558
|
+
if (allPassed) {
|
|
1559
|
+
process.stdout.write('All checks passed. Ready to use with Claude Desktop.\n\n');
|
|
1560
|
+
process.exit(0);
|
|
1561
|
+
} else {
|
|
1562
|
+
process.stdout.write('Setup is not complete. Fix the issue above and run --doctor again.\n\n');
|
|
1563
|
+
process.exit(1);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
|
|
1438
1568
|
process.on('uncaughtException', (err) => {
|
|
1439
1569
|
process.stderr.write(`Uncaught exception: ${err.message}\n${err.stack}\n`);
|
|
1440
1570
|
process.exit(1);
|
|
@@ -1445,7 +1575,14 @@ process.on('unhandledRejection', (reason) => {
|
|
|
1445
1575
|
process.exit(1);
|
|
1446
1576
|
});
|
|
1447
1577
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1578
|
+
if (process.argv.includes('--doctor')) {
|
|
1579
|
+
runDoctor().catch((err) => {
|
|
1580
|
+
process.stderr.write(`Doctor failed unexpectedly: ${err.message}\n`);
|
|
1581
|
+
process.exit(1);
|
|
1582
|
+
});
|
|
1583
|
+
} else {
|
|
1584
|
+
main().catch((err) => {
|
|
1585
|
+
process.stderr.write(`Fatal error: ${err.message}\n${err.stack}\n`);
|
|
1586
|
+
process.exit(1);
|
|
1587
|
+
});
|
|
1588
|
+
}
|