icloud-mcp 2.5.0 → 2.7.0
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/LICENSE +21 -0
- package/README.md +235 -341
- package/index.js +30 -1138
- package/lib/caldav.js +628 -3
- package/lib/carddav.js +28 -2
- package/lib/data-paths.js +13 -0
- package/lib/digest.js +23 -8
- package/lib/imap.js +298 -85
- package/lib/reminders.js +316 -0
- package/lib/session.js +9 -7
- package/lib/smtp.js +53 -21
- package/lib/tools/calendar.js +333 -0
- package/lib/tools/contacts.js +135 -0
- package/lib/tools/mail.js +903 -0
- package/lib/tools/reminders.js +165 -0
- package/package.json +26 -7
- package/.claude/scheduled_tasks.lock +0 -1
- package/.claude/settings.local.json +0 -35
- package/.mcp.json.example +0 -12
- package/run-digest.js +0 -57
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// iCloud Reminders (JXA) tools.
|
|
2
|
+
import {
|
|
3
|
+
listReminderLists, listReminders, getReminder, createReminder, updateReminder, completeReminder, deleteReminder,
|
|
4
|
+
createReminderList, deleteReminderList, renameReminderList,
|
|
5
|
+
} from '../reminders.js';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const reminderTools = [
|
|
9
|
+
// ── Reminders (JXA — real iCloud Reminders, not legacy CalDAV) ──
|
|
10
|
+
{
|
|
11
|
+
name: 'list_reminder_lists',
|
|
12
|
+
description: 'List all Reminders lists in iCloud Reminders (e.g. "Reminders", "Work", "Shopping"). Returns name, id, and count per list.',
|
|
13
|
+
inputSchema: { type: 'object', properties: {} }
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'create_reminder_list',
|
|
17
|
+
description: 'Create a new Reminders list in iCloud Reminders. Fails if a list with that name already exists.',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
name: { type: 'string', description: 'Name of the new list' }
|
|
22
|
+
},
|
|
23
|
+
required: ['name']
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'rename_reminder_list',
|
|
28
|
+
description: 'Rename a Reminders list. The old name must match exactly one list, and the new name must not already be in use.',
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
oldName: { type: 'string', description: 'Exact current name of the list' },
|
|
33
|
+
newName: { type: 'string', description: 'New name for the list' },
|
|
34
|
+
dryRun: { type: 'boolean', description: 'If true, report exactly what would change and do not modify anything' },
|
|
35
|
+
},
|
|
36
|
+
required: ['oldName', 'newName']
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'delete_reminder_list',
|
|
41
|
+
description: 'Delete a Reminders list. The list must be empty first, and the name must match exactly one list.',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
name: { type: 'string', description: 'Exact name of the list to delete' },
|
|
46
|
+
dryRun: { type: 'boolean', description: 'If true, report exactly what would change and do not modify anything' },
|
|
47
|
+
},
|
|
48
|
+
required: ['name']
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'list_reminders',
|
|
53
|
+
description: 'List reminders from iCloud Reminders. Omit listName to fetch from all lists.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
listName: { type: 'string', description: 'List name from list_reminder_lists (omit for all lists)' },
|
|
58
|
+
includeCompleted: { type: 'boolean', description: 'Include completed reminders (default false)' },
|
|
59
|
+
limit: { type: 'number', description: 'Max reminders to return (default 50)' }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'get_reminder',
|
|
65
|
+
description: 'Get full details of a specific reminder by ID.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
listName: { type: 'string', description: 'List name containing the reminder' },
|
|
70
|
+
reminderId: { type: 'string', description: 'Reminder ID from list_reminders' }
|
|
71
|
+
},
|
|
72
|
+
required: ['listName', 'reminderId']
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'create_reminder',
|
|
77
|
+
description: 'Create a new reminder in iCloud Reminders.',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
listName: { type: 'string', description: 'List name to add the reminder to (required)' },
|
|
82
|
+
title: { type: 'string', description: 'Reminder title' },
|
|
83
|
+
notes: { type: 'string', description: 'Notes / description' },
|
|
84
|
+
due: { type: 'string', description: 'Due date/time — ISO 8601 (e.g. 2026-03-20T09:00:00)' },
|
|
85
|
+
priority: { type: 'string', description: 'Priority: high, medium, low, or none (default none)' }
|
|
86
|
+
},
|
|
87
|
+
required: ['listName', 'title']
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'update_reminder',
|
|
92
|
+
description: 'Update an existing reminder. Only provided fields are changed; others are preserved.',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
listName: { type: 'string', description: 'List name containing the reminder' },
|
|
97
|
+
reminderId: { type: 'string', description: 'Reminder ID to update' },
|
|
98
|
+
title: { type: 'string' },
|
|
99
|
+
notes: { type: 'string' },
|
|
100
|
+
due: { type: 'string' },
|
|
101
|
+
priority: { type: 'string', description: 'high, medium, low, or none' }
|
|
102
|
+
},
|
|
103
|
+
required: ['listName', 'reminderId']
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'complete_reminder',
|
|
108
|
+
description: 'Mark a reminder as completed in iCloud Reminders.',
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
listName: { type: 'string', description: 'List name containing the reminder' },
|
|
113
|
+
reminderId: { type: 'string', description: 'Reminder ID to mark as completed' }
|
|
114
|
+
},
|
|
115
|
+
required: ['listName', 'reminderId']
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'delete_reminder',
|
|
120
|
+
description: 'Delete a reminder from iCloud Reminders permanently.',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
listName: { type: 'string', description: 'List name containing the reminder' },
|
|
125
|
+
reminderId: { type: 'string', description: 'Reminder ID to delete' },
|
|
126
|
+
dryRun: { type: 'boolean', description: 'If true, report exactly what would change and do not modify anything' },
|
|
127
|
+
},
|
|
128
|
+
required: ['listName', 'reminderId']
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const _reminderTools_NAMES = new Set(reminderTools.map((tool) => tool.name));
|
|
134
|
+
|
|
135
|
+
export async function handleReminderTool(name, args, ctx) {
|
|
136
|
+
if (!_reminderTools_NAMES.has(name)) return undefined;
|
|
137
|
+
const { resolveCreds, resolveMailbox, accounts: ACCOUNTS } = ctx;
|
|
138
|
+
let result;
|
|
139
|
+
if (name === 'list_reminder_lists') {
|
|
140
|
+
result = listReminderLists();
|
|
141
|
+
} else if (name === 'create_reminder_list') {
|
|
142
|
+
result = createReminderList(args.name);
|
|
143
|
+
} else if (name === 'rename_reminder_list') {
|
|
144
|
+
result = renameReminderList(args.oldName, args.newName, args.dryRun || false);
|
|
145
|
+
} else if (name === 'delete_reminder_list') {
|
|
146
|
+
result = deleteReminderList(args.name, args.dryRun || false);
|
|
147
|
+
} else if (name === 'list_reminders') {
|
|
148
|
+
result = listReminders(args.listName || null, args.includeCompleted || false, args.limit || 50);
|
|
149
|
+
} else if (name === 'get_reminder') {
|
|
150
|
+
result = getReminder(args.listName, args.reminderId);
|
|
151
|
+
} else if (name === 'create_reminder') {
|
|
152
|
+
const { listName, ...fields } = args;
|
|
153
|
+
result = createReminder(listName, fields);
|
|
154
|
+
} else if (name === 'update_reminder') {
|
|
155
|
+
const { listName, reminderId, ...fields } = args;
|
|
156
|
+
result = updateReminder(listName, reminderId, fields);
|
|
157
|
+
} else if (name === 'complete_reminder') {
|
|
158
|
+
result = completeReminder(args.listName, args.reminderId);
|
|
159
|
+
} else if (name === 'delete_reminder') {
|
|
160
|
+
result = deleteReminder(args.listName, args.reminderId, args.dryRun || false);
|
|
161
|
+
// ── Smart extraction (SCAN tier 60s — LLM round-trip) ──
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "icloud-mcp",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.7.0",
|
|
4
|
+
"description": "Maintained Model Context Protocol (MCP) server for iCloud Mail, Contacts, Calendar, and Reminders",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"icloud-mcp": "
|
|
7
|
+
"icloud-mcp": "index.js"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node index.js",
|
|
12
|
-
"test": "node tests/test.js"
|
|
12
|
+
"test": "node --test tests/dry-run.test.js tests/send-guard.test.js tests/pr-claims.test.js",
|
|
13
|
+
"test:live": "ICLOUD_MCP_LIVE=1 node tests/live-destructive.test.js",
|
|
14
|
+
"test:send": "node tests/test.js"
|
|
13
15
|
},
|
|
14
16
|
"keywords": [
|
|
15
17
|
"mcp",
|
|
@@ -20,10 +22,27 @@
|
|
|
20
22
|
],
|
|
21
23
|
"author": "Adam Zaidi",
|
|
22
24
|
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/adamzaidi/icloud-mcp.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/adamzaidi/icloud-mcp#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/adamzaidi/icloud-mcp/issues"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"index.js",
|
|
38
|
+
"lib/",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
23
42
|
"dependencies": {
|
|
24
43
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
25
|
-
"fast-xml-parser": "^5.
|
|
26
|
-
"imapflow": "^1.
|
|
27
|
-
"nodemailer": "^8.0.
|
|
44
|
+
"fast-xml-parser": "^5.8.0",
|
|
45
|
+
"imapflow": "^1.3.3",
|
|
46
|
+
"nodemailer": "^8.0.9"
|
|
28
47
|
}
|
|
29
48
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"sessionId":"583b9a47-29dc-4265-b2f0-dc31dbbd06c3","pid":81825,"acquiredAt":1773517693304}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"mcp__icloud-mail__list_mailboxes",
|
|
5
|
-
"mcp__icloud-mail__get_top_senders",
|
|
6
|
-
"mcp__icloud-mail__get_unread_senders",
|
|
7
|
-
"mcp__icloud-mail__get_storage_report",
|
|
8
|
-
"mcp__icloud-mail__get_mailbox_summary",
|
|
9
|
-
"mcp__icloud-mail__create_mailbox",
|
|
10
|
-
"mcp__icloud-mail__bulk_move",
|
|
11
|
-
"mcp__icloud-mail__delete_mailbox",
|
|
12
|
-
"mcp__icloud-mail__get_inbox_summary",
|
|
13
|
-
"mcp__icloud-mail__abandon_move",
|
|
14
|
-
"mcp__icloud-mail__search_emails",
|
|
15
|
-
"mcp__icloud-mail__count_emails",
|
|
16
|
-
"mcp__icloud-mail__archive_older_than",
|
|
17
|
-
"mcp__icloud-mail__bulk_move_by_sender",
|
|
18
|
-
"mcp__icloud-mail__get_move_status",
|
|
19
|
-
"mcp__icloud-mail__bulk_move_by_domain",
|
|
20
|
-
"WebSearch",
|
|
21
|
-
"WebFetch(domain:www.mdcourts.gov)",
|
|
22
|
-
"WebFetch(domain:probonomd.org)",
|
|
23
|
-
"WebFetch(domain:www.courts.state.md.us)",
|
|
24
|
-
"WebFetch(domain:msa.maryland.gov)",
|
|
25
|
-
"WebFetch(domain:www.harford.edu)",
|
|
26
|
-
"WebFetch(domain:thedailyrecord.com)",
|
|
27
|
-
"WebFetch(domain:ballotpedia.org)",
|
|
28
|
-
"mcp__icloud-mail__compose_email"
|
|
29
|
-
]
|
|
30
|
-
},
|
|
31
|
-
"enableAllProjectMcpServers": true,
|
|
32
|
-
"enabledMcpjsonServers": [
|
|
33
|
-
"icloud-mail"
|
|
34
|
-
]
|
|
35
|
-
}
|
package/.mcp.json.example
DELETED
package/run-digest.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// ─── Inbox Intelligence — Digest Status Helper ────────────────────────────────
|
|
3
|
-
// Shows current digest state and what's pending. Not the runner itself —
|
|
4
|
-
// Claude Code is the runner. Use this to inspect state or reset it.
|
|
5
|
-
//
|
|
6
|
-
// Usage:
|
|
7
|
-
// node run-digest.js → show current state
|
|
8
|
-
// node run-digest.js --reset → clear all processed UIDs and state
|
|
9
|
-
|
|
10
|
-
import { getDigestState, updateDigestState } from './lib/digest.js';
|
|
11
|
-
|
|
12
|
-
const args = process.argv.slice(2);
|
|
13
|
-
|
|
14
|
-
if (args.includes('--reset')) {
|
|
15
|
-
updateDigestState({
|
|
16
|
-
lastRun: null,
|
|
17
|
-
processedUids: [],
|
|
18
|
-
pendingActions: [],
|
|
19
|
-
skipCounts: {}
|
|
20
|
-
});
|
|
21
|
-
// Force full reset by re-reading and overwriting
|
|
22
|
-
const { writeFileSync } = await import('fs');
|
|
23
|
-
const { homedir } = await import('os');
|
|
24
|
-
const { join } = await import('path');
|
|
25
|
-
writeFileSync(
|
|
26
|
-
join(homedir(), '.icloud-mcp-digest.json'),
|
|
27
|
-
JSON.stringify({ lastRun: null, processedUids: [], pendingActions: [], skipCounts: {} }, null, 2)
|
|
28
|
-
);
|
|
29
|
-
console.log('✓ Digest state reset.');
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const state = getDigestState();
|
|
34
|
-
|
|
35
|
-
console.log('\n── Digest State ──────────────────────────────────────────');
|
|
36
|
-
console.log(`Last run: ${state.lastRun ? new Date(state.lastRun).toLocaleString() : 'never'}`);
|
|
37
|
-
console.log(`Processed UIDs: ${state.processedUids.length}`);
|
|
38
|
-
console.log(`Pending actions: ${state.pendingActions.length}`);
|
|
39
|
-
|
|
40
|
-
if (state.pendingActions.length > 0) {
|
|
41
|
-
for (const a of state.pendingActions) {
|
|
42
|
-
console.log(` • [${a.type}] ${a.subject}${a.dueDate ? ' — due ' + a.dueDate : ''}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const candidates = Object.entries(state.skipCounts)
|
|
47
|
-
.filter(([, c]) => c >= 3)
|
|
48
|
-
.sort((a, b) => b[1] - a[1]);
|
|
49
|
-
|
|
50
|
-
if (candidates.length > 0) {
|
|
51
|
-
console.log(`\nUnsubscribe candidates (skipped 3+ times):`);
|
|
52
|
-
for (const [sender, count] of candidates) {
|
|
53
|
-
console.log(` • ${sender} (${count}×)`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
console.log('──────────────────────────────────────────────────────────\n');
|