icloud-mcp 2.2.0 → 2.4.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/.claude/settings.local.json +10 -1
- package/README.md +32 -2
- package/index.js +300 -1
- package/lib/caldav.js +502 -0
- package/lib/carddav.js +401 -0
- package/lib/digest.js +46 -0
- package/lib/event-extractor.js +47 -0
- package/package.json +3 -1
|
@@ -16,7 +16,16 @@
|
|
|
16
16
|
"mcp__icloud-mail__archive_older_than",
|
|
17
17
|
"mcp__icloud-mail__bulk_move_by_sender",
|
|
18
18
|
"mcp__icloud-mail__get_move_status",
|
|
19
|
-
"mcp__icloud-mail__bulk_move_by_domain"
|
|
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"
|
|
20
29
|
]
|
|
21
30
|
},
|
|
22
31
|
"enableAllProjectMcpServers": true,
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# icloud-mcp
|
|
2
2
|
|
|
3
|
-
A Model Context Protocol (MCP) server that connects Claude to your iCloud Mail
|
|
3
|
+
A Model Context Protocol (MCP) server that connects Claude to your iCloud account — Mail, Contacts, and Calendar. Read, search, organize, send, and automate across the full iCloud suite.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -21,6 +21,8 @@ A Model Context Protocol (MCP) server that connects Claude to your iCloud Mail a
|
|
|
21
21
|
- 🔄 Dry run mode for bulk operations — preview before committing
|
|
22
22
|
- 🔐 Safe move — emails are fingerprinted and verified in the destination before removal from source
|
|
23
23
|
- 📝 Session logging — Claude tracks progress across long multi-step operations
|
|
24
|
+
- 👤 Contacts — list, search, create, update, and delete iCloud Contacts via CardDAV
|
|
25
|
+
- 📅 Calendar — list calendars, query events by date, create/update/delete events via CalDAV
|
|
24
26
|
|
|
25
27
|
## Prerequisites
|
|
26
28
|
|
|
@@ -161,7 +163,7 @@ You're all set. Try asking Claude:
|
|
|
161
163
|
- *"Show me the top senders in my iCloud inbox"*
|
|
162
164
|
- *"How many unread emails do I have?"*
|
|
163
165
|
|
|
164
|
-
## Available Tools (
|
|
166
|
+
## Available Tools (65)
|
|
165
167
|
|
|
166
168
|
### Read & Search
|
|
167
169
|
|
|
@@ -247,6 +249,29 @@ You're all set. Try asking Claude:
|
|
|
247
249
|
| `run_all_rules` | Run all saved rules in sequence; supports `dryRun` |
|
|
248
250
|
| `delete_rule` | Delete a saved rule by name |
|
|
249
251
|
|
|
252
|
+
### Contacts (CardDAV)
|
|
253
|
+
|
|
254
|
+
| Tool | Description |
|
|
255
|
+
|------|-------------|
|
|
256
|
+
| `list_contacts` | List contacts from iCloud Contacts; supports `limit`, `offset` for pagination |
|
|
257
|
+
| `search_contacts` | Search contacts by name, email, or phone number |
|
|
258
|
+
| `get_contact` | Get full details for a specific contact by ID |
|
|
259
|
+
| `create_contact` | Create a new contact; supports name, phones, emails, address, org, birthday, note |
|
|
260
|
+
| `update_contact` | Update an existing contact; only provided fields are changed |
|
|
261
|
+
| `delete_contact` | Permanently delete a contact |
|
|
262
|
+
|
|
263
|
+
### Calendar (CalDAV)
|
|
264
|
+
|
|
265
|
+
| Tool | Description |
|
|
266
|
+
|------|-------------|
|
|
267
|
+
| `list_calendars` | List all iCloud calendars with name, ID, and supported event types |
|
|
268
|
+
| `list_events` | List events in a calendar within a date range; supports `since`, `before`, `limit` |
|
|
269
|
+
| `get_event` | Get full details of a specific event by ID |
|
|
270
|
+
| `create_event` | Create a new event; supports title, start/end, timezone, all-day, description, location, recurrence |
|
|
271
|
+
| `update_event` | Update an existing event; only provided fields are changed |
|
|
272
|
+
| `delete_event` | Permanently delete a calendar event |
|
|
273
|
+
| `search_events` | Search for events by title across all calendars; supports date range |
|
|
274
|
+
|
|
250
275
|
### Session Log
|
|
251
276
|
|
|
252
277
|
| Tool | Description |
|
|
@@ -299,6 +324,11 @@ Once configured, you can ask Claude things like:
|
|
|
299
324
|
- *"Create a rule that moves all emails from spotify.com to bulk-mail/services"*
|
|
300
325
|
- *"Reply to the last email from John and cc Sarah"*
|
|
301
326
|
- *"Draft a follow-up email to the team about the Q1 report"*
|
|
327
|
+
- *"Find John Smith's phone number in my contacts"*
|
|
328
|
+
- *"Add a new contact: Jane Doe, jane@example.com, +1 555 123 4567"*
|
|
329
|
+
- *"What's on my calendar next week?"*
|
|
330
|
+
- *"Create an event: dentist appointment Monday at 10am Eastern"*
|
|
331
|
+
- *"Find all my calendar events about 'team meeting'"*
|
|
302
332
|
|
|
303
333
|
## Security
|
|
304
334
|
|
package/index.js
CHANGED
|
@@ -19,6 +19,10 @@ import {
|
|
|
19
19
|
} from './lib/imap.js';
|
|
20
20
|
import { logRead, logWrite, logClear } from './lib/session.js';
|
|
21
21
|
import { composeEmail, replyToEmail, forwardEmail, saveDraft } from './lib/smtp.js';
|
|
22
|
+
import { listContacts, searchContacts, getContact, createContact, updateContact, deleteContact } from './lib/carddav.js';
|
|
23
|
+
import { getDigestState, updateDigestState } from './lib/digest.js';
|
|
24
|
+
import { formatEmailForExtraction } from './lib/event-extractor.js';
|
|
25
|
+
import { listCalendars, listEvents, getEvent, createEvent, updateEvent, deleteEvent, searchEvents } from './lib/caldav.js';
|
|
22
26
|
|
|
23
27
|
const IMAP_USER = process.env.IMAP_USER;
|
|
24
28
|
const IMAP_PASSWORD = process.env.IMAP_PASSWORD;
|
|
@@ -36,7 +40,7 @@ if (!IMAP_USER || !IMAP_PASSWORD) {
|
|
|
36
40
|
|
|
37
41
|
async function main() {
|
|
38
42
|
const server = new Server(
|
|
39
|
-
{ name: 'icloud-mail', version: '2.
|
|
43
|
+
{ name: 'icloud-mail', version: '2.4.0' },
|
|
40
44
|
{ capabilities: { tools: {} } }
|
|
41
45
|
);
|
|
42
46
|
|
|
@@ -673,6 +677,227 @@ async function main() {
|
|
|
673
677
|
},
|
|
674
678
|
required: ['to', 'subject']
|
|
675
679
|
}
|
|
680
|
+
},
|
|
681
|
+
// ── Digest State ──
|
|
682
|
+
{
|
|
683
|
+
name: 'get_digest_state',
|
|
684
|
+
description: 'Get the current inbox digest state — last run timestamp, processed email UIDs (to skip on next run), pending actions, and per-sender skip counts for smart unsubscribe.',
|
|
685
|
+
inputSchema: { type: 'object', properties: {} }
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: 'update_digest_state',
|
|
689
|
+
description: 'Update the digest state after a run. Merges new processed UIDs into the existing list, updates lastRun, replaces pendingActions, and accumulates per-sender skip counts.',
|
|
690
|
+
inputSchema: {
|
|
691
|
+
type: 'object',
|
|
692
|
+
properties: {
|
|
693
|
+
lastRun: { type: 'string', description: 'ISO timestamp of this run' },
|
|
694
|
+
processedUids: { type: 'array', items: { type: 'number' }, description: 'Email UIDs processed in this run — merged with existing and capped at 5000' },
|
|
695
|
+
pendingActions: { type: 'array', description: 'Full replacement list of pending action items to track across runs (deadlines, waiting-for-reply, etc.). Each item: { type, subject, to/from, dueDate?, notes? }' },
|
|
696
|
+
skipCounts: { type: 'object', description: 'Map of sender address to skip count increment for this run, e.g. { "bestbuy@email.bestbuy.com": 3 }. Accumulated across runs for smart unsubscribe.' }
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
// ── CardDAV / Contacts ──
|
|
701
|
+
{
|
|
702
|
+
name: 'list_contacts',
|
|
703
|
+
description: 'List contacts from iCloud Contacts. Returns names, phones, emails, and other fields.',
|
|
704
|
+
inputSchema: {
|
|
705
|
+
type: 'object',
|
|
706
|
+
properties: {
|
|
707
|
+
limit: { type: 'number', description: 'Max contacts to return (default 50)' },
|
|
708
|
+
offset: { type: 'number', description: 'Skip this many contacts (default 0, for pagination)' }
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
name: 'search_contacts',
|
|
714
|
+
description: 'Search iCloud Contacts by name, email address, or phone number.',
|
|
715
|
+
inputSchema: {
|
|
716
|
+
type: 'object',
|
|
717
|
+
properties: {
|
|
718
|
+
query: { type: 'string', description: 'Text to search for (matched against name, email, and phone)' }
|
|
719
|
+
},
|
|
720
|
+
required: ['query']
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
name: 'get_contact',
|
|
725
|
+
description: 'Get full details for a specific contact by ID. Use list_contacts or search_contacts to find a contactId.',
|
|
726
|
+
inputSchema: {
|
|
727
|
+
type: 'object',
|
|
728
|
+
properties: {
|
|
729
|
+
contactId: { type: 'string', description: 'Contact ID (UUID from list_contacts or search_contacts)' }
|
|
730
|
+
},
|
|
731
|
+
required: ['contactId']
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: 'create_contact',
|
|
736
|
+
description: 'Create a new contact in iCloud Contacts.',
|
|
737
|
+
inputSchema: {
|
|
738
|
+
type: 'object',
|
|
739
|
+
properties: {
|
|
740
|
+
firstName: { type: 'string', description: 'First name' },
|
|
741
|
+
lastName: { type: 'string', description: 'Last name' },
|
|
742
|
+
fullName: { type: 'string', description: 'Full display name (overrides firstName + lastName for FN field)' },
|
|
743
|
+
org: { type: 'string', description: 'Organization / company name' },
|
|
744
|
+
phone: { type: 'string', description: 'Primary phone number (shorthand for phones array)' },
|
|
745
|
+
email: { type: 'string', description: 'Primary email address (shorthand for emails array)' },
|
|
746
|
+
phones: { type: 'array', description: 'Array of phone objects: [{ number, type }] where type is cell/home/work/etc.' },
|
|
747
|
+
emails: { type: 'array', description: 'Array of email objects: [{ email, type }] where type is home/work/etc.' },
|
|
748
|
+
addresses: { type: 'array', description: 'Array of address objects: [{ street, city, state, zip, country, type }]' },
|
|
749
|
+
birthday: { type: 'string', description: 'Birthday in YYYY-MM-DD format' },
|
|
750
|
+
note: { type: 'string', description: 'Notes / free text' },
|
|
751
|
+
url: { type: 'string', description: 'Website URL' }
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
name: 'update_contact',
|
|
757
|
+
description: 'Update an existing contact in iCloud Contacts. Only provided fields are changed; others are preserved.',
|
|
758
|
+
inputSchema: {
|
|
759
|
+
type: 'object',
|
|
760
|
+
properties: {
|
|
761
|
+
contactId: { type: 'string', description: 'Contact ID to update' },
|
|
762
|
+
firstName: { type: 'string' },
|
|
763
|
+
lastName: { type: 'string' },
|
|
764
|
+
fullName: { type: 'string' },
|
|
765
|
+
org: { type: 'string' },
|
|
766
|
+
phone: { type: 'string' },
|
|
767
|
+
email: { type: 'string' },
|
|
768
|
+
phones: { type: 'array' },
|
|
769
|
+
emails: { type: 'array' },
|
|
770
|
+
addresses: { type: 'array' },
|
|
771
|
+
birthday: { type: 'string' },
|
|
772
|
+
note: { type: 'string' },
|
|
773
|
+
url: { type: 'string' }
|
|
774
|
+
},
|
|
775
|
+
required: ['contactId']
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
name: 'delete_contact',
|
|
780
|
+
description: 'Delete a contact from iCloud Contacts permanently.',
|
|
781
|
+
inputSchema: {
|
|
782
|
+
type: 'object',
|
|
783
|
+
properties: {
|
|
784
|
+
contactId: { type: 'string', description: 'Contact ID to delete' }
|
|
785
|
+
},
|
|
786
|
+
required: ['contactId']
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
// ── CalDAV / Calendar ──
|
|
790
|
+
{
|
|
791
|
+
name: 'list_calendars',
|
|
792
|
+
description: 'List all calendars in iCloud Calendar (e.g. Personal, Work, LSAT PREP). Returns calendarId, name, and supported event types.',
|
|
793
|
+
inputSchema: { type: 'object', properties: {} }
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
name: 'list_events',
|
|
797
|
+
description: 'List events in a specific iCloud calendar within a date range. Use list_calendars first to get a calendarId.',
|
|
798
|
+
inputSchema: {
|
|
799
|
+
type: 'object',
|
|
800
|
+
properties: {
|
|
801
|
+
calendarId: { type: 'string', description: 'Calendar ID from list_calendars' },
|
|
802
|
+
since: { type: 'string', description: 'Start of range (YYYY-MM-DD, default: 30 days ago)' },
|
|
803
|
+
before: { type: 'string', description: 'End of range (YYYY-MM-DD, default: 30 days ahead)' },
|
|
804
|
+
limit: { type: 'number', description: 'Max events to return (default 50)' }
|
|
805
|
+
},
|
|
806
|
+
required: ['calendarId']
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
name: 'get_event',
|
|
811
|
+
description: 'Get full details of a specific calendar event by its ID.',
|
|
812
|
+
inputSchema: {
|
|
813
|
+
type: 'object',
|
|
814
|
+
properties: {
|
|
815
|
+
calendarId: { type: 'string', description: 'Calendar ID containing the event' },
|
|
816
|
+
eventId: { type: 'string', description: 'Event ID (UUID from list_events or search_events)' }
|
|
817
|
+
},
|
|
818
|
+
required: ['calendarId', 'eventId']
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
name: 'create_event',
|
|
823
|
+
description: 'Create a new event in an iCloud calendar. For all-day events use allDay:true and YYYY-MM-DD for start/end.',
|
|
824
|
+
inputSchema: {
|
|
825
|
+
type: 'object',
|
|
826
|
+
properties: {
|
|
827
|
+
calendarId: { type: 'string', description: 'Calendar ID to add the event to' },
|
|
828
|
+
summary: { type: 'string', description: 'Event title' },
|
|
829
|
+
start: { type: 'string', description: 'Start date/time — ISO 8601 (e.g. 2026-03-15T10:00:00) or YYYY-MM-DD for all-day' },
|
|
830
|
+
end: { type: 'string', description: 'End date/time — ISO 8601 or YYYY-MM-DD. Defaults to 1 hour after start.' },
|
|
831
|
+
timezone: { type: 'string', description: 'IANA timezone (e.g. America/New_York). Use "UTC" or omit for UTC.' },
|
|
832
|
+
allDay: { type: 'boolean', description: 'True for all-day event (uses DATE values, no time)' },
|
|
833
|
+
description: { type: 'string', description: 'Event description / notes' },
|
|
834
|
+
location: { type: 'string', description: 'Event location' },
|
|
835
|
+
recurrence: { type: 'string', description: 'iCal RRULE string (e.g. FREQ=WEEKLY;BYDAY=MO,WE,FR)' },
|
|
836
|
+
status: { type: 'string', description: 'Event status: CONFIRMED, TENTATIVE, or CANCELLED' },
|
|
837
|
+
reminder: { type: 'number', description: 'Alert this many minutes before the event (default 30, set to 0 to disable)' }
|
|
838
|
+
},
|
|
839
|
+
required: ['calendarId', 'summary', 'start']
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: 'update_event',
|
|
844
|
+
description: 'Update an existing calendar event. Only provided fields are changed; others are preserved.',
|
|
845
|
+
inputSchema: {
|
|
846
|
+
type: 'object',
|
|
847
|
+
properties: {
|
|
848
|
+
calendarId: { type: 'string', description: 'Calendar ID containing the event' },
|
|
849
|
+
eventId: { type: 'string', description: 'Event ID to update' },
|
|
850
|
+
summary: { type: 'string' },
|
|
851
|
+
start: { type: 'string' },
|
|
852
|
+
end: { type: 'string' },
|
|
853
|
+
timezone: { type: 'string' },
|
|
854
|
+
allDay: { type: 'boolean' },
|
|
855
|
+
description: { type: 'string' },
|
|
856
|
+
location: { type: 'string' },
|
|
857
|
+
recurrence: { type: 'string' },
|
|
858
|
+
status: { type: 'string' },
|
|
859
|
+
reminder: { type: 'number', description: 'Alert minutes before event (0 to disable)' }
|
|
860
|
+
},
|
|
861
|
+
required: ['calendarId', 'eventId']
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
name: 'delete_event',
|
|
866
|
+
description: 'Delete a calendar event permanently from iCloud Calendar.',
|
|
867
|
+
inputSchema: {
|
|
868
|
+
type: 'object',
|
|
869
|
+
properties: {
|
|
870
|
+
calendarId: { type: 'string', description: 'Calendar ID containing the event' },
|
|
871
|
+
eventId: { type: 'string', description: 'Event ID to delete' }
|
|
872
|
+
},
|
|
873
|
+
required: ['calendarId', 'eventId']
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: 'search_events',
|
|
878
|
+
description: 'Search for events by title/summary across all calendars within an optional date range.',
|
|
879
|
+
inputSchema: {
|
|
880
|
+
type: 'object',
|
|
881
|
+
properties: {
|
|
882
|
+
query: { type: 'string', description: 'Text to search for in event titles' },
|
|
883
|
+
since: { type: 'string', description: 'Start of search range (YYYY-MM-DD, default: 1 year ago)' },
|
|
884
|
+
before: { type: 'string', description: 'End of search range (YYYY-MM-DD, default: 1 year ahead)' }
|
|
885
|
+
},
|
|
886
|
+
required: ['query']
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
// ── Smart extraction ──
|
|
890
|
+
{
|
|
891
|
+
name: 'suggest_event_from_email',
|
|
892
|
+
description: 'Fetch an email and return its content formatted for calendar event extraction. After calling this tool, extract the event fields from the returned content (pay attention to _dateAnchor for resolving relative dates like "Tuesday"), present a summary to the user for confirmation, then call create_event. No API key required.',
|
|
893
|
+
inputSchema: {
|
|
894
|
+
type: 'object',
|
|
895
|
+
properties: {
|
|
896
|
+
uid: { type: 'number', description: 'Email UID to extract event from' },
|
|
897
|
+
mailbox: { type: 'string', description: 'Mailbox containing the email (default INBOX)' }
|
|
898
|
+
},
|
|
899
|
+
required: ['uid']
|
|
900
|
+
}
|
|
676
901
|
}
|
|
677
902
|
]
|
|
678
903
|
}));
|
|
@@ -781,6 +1006,16 @@ async function main() {
|
|
|
781
1006
|
result = logRead();
|
|
782
1007
|
} else if (name === 'log_clear') {
|
|
783
1008
|
result = logClear();
|
|
1009
|
+
// ── Digest state (synchronous, no timeout needed) ──
|
|
1010
|
+
} else if (name === 'get_digest_state') {
|
|
1011
|
+
result = getDigestState();
|
|
1012
|
+
} else if (name === 'update_digest_state') {
|
|
1013
|
+
result = updateDigestState({
|
|
1014
|
+
lastRun: args.lastRun,
|
|
1015
|
+
processedUids: args.processedUids,
|
|
1016
|
+
pendingActions: args.pendingActions,
|
|
1017
|
+
skipCounts: args.skipCounts
|
|
1018
|
+
});
|
|
784
1019
|
// ── Saved rules (synchronous CRUD; run_rule/run_all_rules use internal chunk timeouts) ──
|
|
785
1020
|
} else if (name === 'create_rule') {
|
|
786
1021
|
result = createRule(args.name, args.filters || {}, args.action, args.description || '');
|
|
@@ -815,6 +1050,70 @@ async function main() {
|
|
|
815
1050
|
result = await withTimeout('save_draft', TIMEOUT.FETCH, () =>
|
|
816
1051
|
saveDraft(args.to, args.subject, args.body, { html: args.html, cc: args.cc, bcc: args.bcc })
|
|
817
1052
|
);
|
|
1053
|
+
// ── CardDAV / Contacts (FETCH tier 30s) ──
|
|
1054
|
+
} else if (name === 'list_contacts') {
|
|
1055
|
+
result = await withTimeout('list_contacts', TIMEOUT.FETCH, () =>
|
|
1056
|
+
listContacts(args.limit || 50, args.offset || 0)
|
|
1057
|
+
);
|
|
1058
|
+
} else if (name === 'search_contacts') {
|
|
1059
|
+
result = await withTimeout('search_contacts', TIMEOUT.FETCH, () =>
|
|
1060
|
+
searchContacts(args.query)
|
|
1061
|
+
);
|
|
1062
|
+
} else if (name === 'get_contact') {
|
|
1063
|
+
result = await withTimeout('get_contact', TIMEOUT.FETCH, () =>
|
|
1064
|
+
getContact(args.contactId)
|
|
1065
|
+
);
|
|
1066
|
+
} else if (name === 'create_contact') {
|
|
1067
|
+
const { contactId: _ignore, ...fields } = args;
|
|
1068
|
+
result = await withTimeout('create_contact', TIMEOUT.FETCH, () =>
|
|
1069
|
+
createContact(fields)
|
|
1070
|
+
);
|
|
1071
|
+
} else if (name === 'update_contact') {
|
|
1072
|
+
const { contactId, ...fields } = args;
|
|
1073
|
+
result = await withTimeout('update_contact', TIMEOUT.FETCH, () =>
|
|
1074
|
+
updateContact(contactId, fields)
|
|
1075
|
+
);
|
|
1076
|
+
} else if (name === 'delete_contact') {
|
|
1077
|
+
result = await withTimeout('delete_contact', TIMEOUT.SINGLE, () =>
|
|
1078
|
+
deleteContact(args.contactId)
|
|
1079
|
+
);
|
|
1080
|
+
// ── CalDAV / Calendar (FETCH tier 30s) ──
|
|
1081
|
+
} else if (name === 'list_calendars') {
|
|
1082
|
+
result = await withTimeout('list_calendars', TIMEOUT.FETCH, () =>
|
|
1083
|
+
listCalendars()
|
|
1084
|
+
);
|
|
1085
|
+
} else if (name === 'list_events') {
|
|
1086
|
+
result = await withTimeout('list_events', TIMEOUT.FETCH, () =>
|
|
1087
|
+
listEvents(args.calendarId, args.since || null, args.before || null, args.limit || 50)
|
|
1088
|
+
);
|
|
1089
|
+
} else if (name === 'get_event') {
|
|
1090
|
+
result = await withTimeout('get_event', TIMEOUT.FETCH, () =>
|
|
1091
|
+
getEvent(args.calendarId, args.eventId)
|
|
1092
|
+
);
|
|
1093
|
+
} else if (name === 'create_event') {
|
|
1094
|
+
const { calendarId, ...fields } = args;
|
|
1095
|
+
result = await withTimeout('create_event', TIMEOUT.FETCH, () =>
|
|
1096
|
+
createEvent(calendarId, fields)
|
|
1097
|
+
);
|
|
1098
|
+
} else if (name === 'update_event') {
|
|
1099
|
+
const { calendarId, eventId, ...fields } = args;
|
|
1100
|
+
result = await withTimeout('update_event', TIMEOUT.FETCH, () =>
|
|
1101
|
+
updateEvent(calendarId, eventId, fields)
|
|
1102
|
+
);
|
|
1103
|
+
} else if (name === 'delete_event') {
|
|
1104
|
+
result = await withTimeout('delete_event', TIMEOUT.SINGLE, () =>
|
|
1105
|
+
deleteEvent(args.calendarId, args.eventId)
|
|
1106
|
+
);
|
|
1107
|
+
} else if (name === 'search_events') {
|
|
1108
|
+
result = await withTimeout('search_events', TIMEOUT.FETCH, () =>
|
|
1109
|
+
searchEvents(args.query, args.since || null, args.before || null)
|
|
1110
|
+
);
|
|
1111
|
+
// ── Smart extraction (SCAN tier 60s — LLM round-trip) ──
|
|
1112
|
+
} else if (name === 'suggest_event_from_email') {
|
|
1113
|
+
const email = await withTimeout('get_email_for_extraction', TIMEOUT.FETCH, () =>
|
|
1114
|
+
getEmailContent(args.uid, args.mailbox || 'INBOX', 10000, false)
|
|
1115
|
+
);
|
|
1116
|
+
result = formatEmailForExtraction(email);
|
|
818
1117
|
} else {
|
|
819
1118
|
throw new Error(`Unknown tool: ${name}`);
|
|
820
1119
|
}
|