calendar
Manage Apple Calendar events by searching, creating, opening, or listing appointments to organize your schedule directly from Claude AI.
Instructions
Search, create, and open calendar events in Apple Calendar app
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| operation | Yes | Operation to perform: 'search', 'open', 'list', or 'create' | |
| searchText | No | Text to search for in event titles, locations, and notes (required for search operation) | |
| eventId | No | ID of the event to open (required for open operation) | |
| limit | No | Number of events to retrieve (optional, default 10) | |
| fromDate | No | Start date for search range in ISO format (optional, default is today) | |
| toDate | No | End date for search range in ISO format (optional, default is 30 days from now for search, 7 days for list) | |
| title | No | Title of the event to create (required for create operation) | |
| startDate | No | Start date/time of the event in ISO format (required for create operation) | |
| endDate | No | End date/time of the event in ISO format (required for create operation) | |
| location | No | Location of the event (optional for create operation) | |
| notes | No | Additional notes for the event (optional for create operation) | |
| isAllDay | No | Whether the event is an all-day event (optional for create operation, default is false) | |
| calendarName | No | Name of the calendar to create the event in (optional for create operation, uses default calendar if not specified) |
Implementation Reference
- index.ts:794-890 (handler)The main handler for the 'calendar' tool in the MCP server. It validates arguments using isCalendarArgs, loads the calendar module lazily, and dispatches to specific operations: search, open, list, create by calling functions from utils/calendar.ts.case "calendar": { if (!isCalendarArgs(args)) { throw new Error("Invalid arguments for calendar tool"); } try { const calendarModule = await loadModule("calendar"); const { operation } = args; switch (operation) { case "search": { const { searchText, limit, fromDate, toDate } = args; const events = await calendarModule.searchEvents(searchText!, limit, fromDate, toDate); return { content: [{ type: "text", text: events.length > 0 ? `Found ${events.length} events matching "${searchText}":\n\n${events.map(event => `${event.title} (${new Date(event.startDate!).toLocaleString()} - ${new Date(event.endDate!).toLocaleString()})\n` + `Location: ${event.location || 'Not specified'}\n` + `Calendar: ${event.calendarName}\n` + `ID: ${event.id}\n` + `${event.notes ? `Notes: ${event.notes}\n` : ''}` ).join("\n\n")}` : `No events found matching "${searchText}".` }], isError: false }; } case "open": { const { eventId } = args; const result = await calendarModule.openEvent(eventId!); return { content: [{ type: "text", text: result.success ? result.message : `Error opening event: ${result.message}` }], isError: !result.success }; } case "list": { const { limit, fromDate, toDate } = args; const events = await calendarModule.getEvents(limit, fromDate, toDate); const startDateText = fromDate ? new Date(fromDate).toLocaleDateString() : 'today'; const endDateText = toDate ? new Date(toDate).toLocaleDateString() : 'next 7 days'; return { content: [{ type: "text", text: events.length > 0 ? `Found ${events.length} events from ${startDateText} to ${endDateText}:\n\n${events.map(event => `${event.title} (${new Date(event.startDate!).toLocaleString()} - ${new Date(event.endDate!).toLocaleString()})\n` + `Location: ${event.location || 'Not specified'}\n` + `Calendar: ${event.calendarName}\n` + `ID: ${event.id}` ).join("\n\n")}` : `No events found from ${startDateText} to ${endDateText}.` }], isError: false }; } case "create": { const { title, startDate, endDate, location, notes, isAllDay, calendarName } = args; const result = await calendarModule.createEvent(title!, startDate!, endDate!, location, notes, isAllDay, calendarName); return { content: [{ type: "text", text: result.success ? `${result.message} Event scheduled from ${new Date(startDate!).toLocaleString()} to ${new Date(endDate!).toLocaleString()}${result.eventId ? `\nEvent ID: ${result.eventId}` : ''}` : `Error creating event: ${result.message}` }], isError: !result.success }; } default: throw new Error(`Unknown calendar operation: ${operation}`); } } catch (error) { return { content: [{ type: "text", text: `Error in calendar tool: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } }
- tools.ts:195-257 (schema)The input schema definition for the 'calendar' tool, specifying parameters and validation for different operations.const CALENDAR_TOOL: Tool = { name: "calendar", description: "Search, create, and open calendar events in Apple Calendar app", inputSchema: { type: "object", properties: { operation: { type: "string", description: "Operation to perform: 'search', 'open', 'list', or 'create'", enum: ["search", "open", "list", "create"] }, searchText: { type: "string", description: "Text to search for in event titles, locations, and notes (required for search operation)" }, eventId: { type: "string", description: "ID of the event to open (required for open operation)" }, limit: { type: "number", description: "Number of events to retrieve (optional, default 10)" }, fromDate: { type: "string", description: "Start date for search range in ISO format (optional, default is today)" }, toDate: { type: "string", description: "End date for search range in ISO format (optional, default is 30 days from now for search, 7 days for list)" }, title: { type: "string", description: "Title of the event to create (required for create operation)" }, startDate: { type: "string", description: "Start date/time of the event in ISO format (required for create operation)" }, endDate: { type: "string", description: "End date/time of the event in ISO format (required for create operation)" }, location: { type: "string", description: "Location of the event (optional for create operation)" }, notes: { type: "string", description: "Additional notes for the event (optional for create operation)" }, isAllDay: { type: "boolean", description: "Whether the event is an all-day event (optional for create operation, default is false)" }, calendarName: { type: "string", description: "Name of the calendar to create the event in (optional for create operation, uses default calendar if not specified)" } }, required: ["operation"] } };
- tools.ts:308-310 (registration)Registration of the 'calendar' tool as part of the exported tools array used by the MCP server.const tools = [CONTACTS_TOOL, NOTES_TOOL, MESSAGES_TOOL, MAIL_TOOL, REMINDERS_TOOL, WEB_SEARCH_TOOL, CALENDAR_TOOL, MAPS_TOOL]; export default tools;
- utils/calendar.ts:60-213 (helper)Helper function searchEvents: Searches for calendar events matching text in titles, locations, notes within date range using Apple Calendar app via JXA.async function searchEvents( searchText: string, limit: number = 10, fromDate?: string, toDate?: string ): Promise<CalendarEvent[]> { try { if (!await checkCalendarAccess()) { return []; } console.error(`searchEvents - Processing calendars for search: "${searchText}"`); const events = await run((args: { searchText: string, limit: number, fromDate?: string, toDate?: string, maxEventsPerCalendar: number }) => { try { const Calendar = Application("Calendar"); // Set default date range if not provided (today to 30 days from now) const today = new Date(); const defaultStartDate = today; const defaultEndDate = new Date(); defaultEndDate.setDate(today.getDate() + 30); const startDate = args.fromDate ? new Date(args.fromDate) : defaultStartDate; const endDate = args.toDate ? new Date(args.toDate) : defaultEndDate; // Array to store matching events const matchingEvents: CalendarEvent[] = []; // Get all calendars at once const allCalendars = Calendar.calendars(); // Search in each calendar for (let i = 0; i < allCalendars.length && matchingEvents.length < args.limit; i++) { try { const calendar = allCalendars[i]; const calendarName = calendar.name(); // Get all events from this calendar const events = calendar.events.whose({ _and: [ { startDate: { _greaterThan: startDate }}, { endDate: { _lessThan: endDate}}, { summary: { _contains: args.searchText}} ] }); const convertedEvents = events(); // Limit the number of events to process const eventCount = Math.min(convertedEvents.length, args.maxEventsPerCalendar); // Filter events by date range and search text for (let j = 0; j < eventCount && matchingEvents.length < args.limit; j++) { const event = convertedEvents[j]; try { const eventStartDate = new Date(event.startDate()); const eventEndDate = new Date(event.endDate()); // Skip events outside our date range if (eventEndDate < startDate || eventStartDate > endDate) { continue; } // Get event details let title = ""; let location = ""; let notes = ""; try { title = event.summary(); } catch (e) { title = "Unknown Title"; } try { location = event.location() || ""; } catch (e) { location = ""; } try { notes = event.description() || ""; } catch (e) { notes = ""; } // Check if event matches search text if ( title.toLowerCase().includes(args.searchText.toLowerCase()) || location.toLowerCase().includes(args.searchText.toLowerCase()) || notes.toLowerCase().includes(args.searchText.toLowerCase()) ) { // Create event object const eventData: CalendarEvent = { id: "", title: title, location: location, notes: notes, startDate: null, endDate: null, calendarName: calendarName, isAllDay: false, url: null }; try { eventData.id = event.uid(); } catch (e) { eventData.id = `unknown-${Date.now()}-${Math.random()}`; } try { eventData.startDate = eventStartDate.toISOString(); } catch (e) { /* Keep as null */ } try { eventData.endDate = eventEndDate.toISOString(); } catch (e) { /* Keep as null */ } try { eventData.isAllDay = event.alldayEvent(); } catch (e) { /* Keep as false */ } try { eventData.url = event.url(); } catch (e) { /* Keep as null */ } matchingEvents.push(eventData); } } catch (e) { // Skip events we can't process console.log("searchEvents - Error processing events: ----0----", JSON.stringify(e)); continue; } } } catch (e) { // Skip calendars we can't access console.log("searchEvents - Error processing calendars: ----1----", JSON.stringify(e)); continue; } } return matchingEvents; } catch (e) { return []; // Return empty array on any error } }, { searchText, limit, fromDate, toDate, maxEventsPerCalendar: CONFIG.MAX_EVENTS_PER_CALENDAR }) as CalendarEvent[]; // If no events found, create dummy events if (events.length === 0) { console.error("searchEvents - No events found, creating dummy events"); return []; } return events; } catch (error) { console.error(`Error searching events: ${error instanceof Error ? error.message : String(error)}`); // Fall back to dummy events on error return []; } }
- utils/calendar.ts:221-292 (helper)Helper function openEvent: Opens a specific calendar event by ID in the Calendar app.async function openEvent(eventId: string): Promise<{ success: boolean; message: string }> { try { if (!await checkCalendarAccess()) { return { success: false, message: "Cannot access Calendar app. Please grant access in System Settings > Privacy & Security > Automation." }; } console.error(`openEvent - Attempting to open event with ID: ${eventId}`); const result = await run((args: { eventId: string, maxEventsPerCalendar: number }) => { try { const Calendar = Application("Calendar"); // Get all calendars at once const allCalendars = Calendar.calendars(); // Search in each calendar for (let i = 0; i < allCalendars.length; i++) { try { const calendar = allCalendars[i]; // Get the event from this calendar const events = calendar.events.whose({ uid: { _equals: args.eventId } }); const event = events[0] if(event.uid() === args.eventId) { Calendar.activate(); event.show(); return { success: true, message: `Successfully opened event: ${event.summary()}` }; } } catch (e) { // Skip calendars we can't access console.log("openEvent - Error processing calendars: ----2----", JSON.stringify(e)); continue; } } return { success: false, message: `No event found with ID: ${args.eventId}` }; } catch (e) { return { success: false, message: "Error opening event" }; } }, { eventId, maxEventsPerCalendar: CONFIG.MAX_EVENTS_PER_CALENDAR }) as { success: boolean; message: string }; return result; } catch (error) { return { success: false, message: `Error opening event: ${error instanceof Error ? error.message : String(error)}` }; } }
- utils/calendar.ts:301-441 (helper)Helper function getEvents (used for 'list' operation): Retrieves upcoming calendar events within date range.async function getEvents( limit: number = 10, fromDate?: string, toDate?: string ): Promise<CalendarEvent[]> { try { console.error("getEvents - Starting to fetch calendar events"); if (!await checkCalendarAccess()) { console.error("getEvents - Failed to access Calendar app"); return []; } console.error("getEvents - Calendar access check passed"); const events = await run((args: { limit: number, fromDate?: string, toDate?: string, maxEventsPerCalendar: number }) => { try { // Access the Calendar app directly const Calendar = Application("Calendar"); // Set default date range if not provided (today to 7 days from now) const today = new Date(); const defaultStartDate = today; const defaultEndDate = new Date(); defaultEndDate.setDate(today.getDate() + 7); const startDate = args.fromDate ? new Date(args.fromDate) : defaultStartDate; const endDate = args.toDate ? new Date(args.toDate) : defaultEndDate; const calendars = Calendar.calendars(); // Array to store events const events: CalendarEvent[] = []; // Get events from each calendar for (const calender of calendars) { if (events.length >= args.limit) break; try { // Get all events from this calendar const calendarEvents = calender.events.whose({ _and: [ { startDate: { _greaterThan: startDate }}, { endDate: { _lessThan: endDate}} ] }); const convertedEvents = calendarEvents(); // Limit the number of events to process const eventCount = Math.min(convertedEvents.length, args.maxEventsPerCalendar); // Process events for (let i = 0; i < eventCount && events.length < args.limit; i++) { const event = convertedEvents[i]; try { const eventStartDate = new Date(event.startDate()); const eventEndDate = new Date(event.endDate()); // Skip events outside our date range if (eventEndDate < startDate || eventStartDate > endDate) { continue; } // Create event object const eventData: CalendarEvent = { id: "", title: "Unknown Title", location: null, notes: null, startDate: null, endDate: null, calendarName: calender.name(), isAllDay: false, url: null }; try { eventData.id = event.uid(); } catch (e) { eventData.id = `unknown-${Date.now()}-${Math.random()}`; } try { eventData.title = event.summary(); } catch (e) { /* Keep default title */ } try { eventData.location = event.location(); } catch (e) { /* Keep as null */ } try { eventData.notes = event.description(); } catch (e) { /* Keep as null */ } try { eventData.startDate = eventStartDate.toISOString(); } catch (e) { /* Keep as null */ } try { eventData.endDate = eventEndDate.toISOString(); } catch (e) { /* Keep as null */ } try { eventData.isAllDay = event.alldayEvent(); } catch (e) { /* Keep as false */ } try { eventData.url = event.url(); } catch (e) { /* Keep as null */ } events.push(eventData); } catch (e) { // Skip events we can't process continue; } } } catch (e) { // Skip calendars we can't access console.log("getEvents - Error processing events: ----0----", JSON.stringify(e)); continue; } } return events; } catch (e) { console.log("getEvents - Error processing events: ----1----", JSON.stringify(e)); return []; // Return empty array on any error } }, { limit, fromDate, toDate, maxEventsPerCalendar: CONFIG.MAX_EVENTS_PER_CALENDAR }) as CalendarEvent[]; // If no events found, create dummy events if (events.length === 0) { console.error("getEvents - No events found, creating dummy events"); return []; } return events; } catch (error) { console.error(`Error getting events: ${error instanceof Error ? error.message : String(error)}`); return []; } }
- utils/calendar.ts:454-558 (helper)Helper function createEvent: Creates a new calendar event with specified details in the target calendar.async function createEvent( title: string, startDate: string, endDate: string, location?: string, notes?: string, isAllDay: boolean = false, calendarName?: string ): Promise<{ success: boolean; message: string; eventId?: string }> { try { if (!await checkCalendarAccess()) { return { success: false, message: "Cannot access Calendar app. Please grant access in System Settings > Privacy & Security > Automation." }; } console.error(`createEvent - Attempting to create event: "${title}"`); const result = await run((args: { title: string, startDate: string, endDate: string, location?: string, notes?: string, isAllDay: boolean, calendarName?: string }) => { try { const Calendar = Application("Calendar"); // Parse dates const startDateTime = new Date(args.startDate); const endDateTime = new Date(args.endDate); // Find the target calendar let targetCalendar; if (args.calendarName) { // Find the specified calendar const calendars = Calendar.calendars.whose({ name: { _equals: args.calendarName } }); if (calendars.length > 0) { targetCalendar = calendars[0]; } else { return { success: false, message: `Calendar "${args.calendarName}" not found.` }; } } else { // Use default calendar // Calendar.defaultCalendar() doesn't exist - get the first calendar instead const allCalendars = Calendar.calendars(); if (allCalendars.length === 0) { return { success: false, message: "No calendars found in Calendar app." }; } targetCalendar = allCalendars[0]; } // Create the new event const newEvent = Calendar.Event({ summary: args.title, startDate: startDateTime, endDate: endDateTime, location: args.location || "", description: args.notes || "", alldayEvent: args.isAllDay }); // Add the event to the calendar targetCalendar.events.push(newEvent); return { success: true, message: `Event "${args.title}" created successfully.`, eventId: newEvent.uid() }; } catch (e) { return { success: false, message: `Error creating event: ${e instanceof Error ? e.message : String(e)}` }; } }, { title, startDate, endDate, location, notes, isAllDay, calendarName }) as { success: boolean; message: string; eventId?: string }; return result; } catch (error) { return { success: false, message: `Error creating event: ${error instanceof Error ? error.message : String(error)}` }; }