intentkit-calendar
v1.0.1
Published
CalDAV calendar adapter for IntentKit via tsdav
Maintainers
Readme
intentkit-calendar
CalDAV calendar adapter for IntentKit — read, create, update, and delete calendar events via the provider system using tsdav.
This adapter lets AI agents (like Claude via Dispatch) manage calendars through MCP.
Install
npm install intentkit-calendarQuick Start
import { defineFunction, IntentRegistry, createContext, serve, z } from 'intentkit';
import { createCalendarProvider, type CalendarClient } from 'intentkit-calendar';
// Register your functions
const registry = new IntentRegistry().register(listCalendars, listEvents, createEvent);
// Create context (no database needed for calendar-only projects)
const context = await createContext({ events: true });
// Boot MCP server with calendar provider
await serve({
name: 'my-calendar-agent',
registry,
context,
providers: [
createCalendarProvider({
serverUrl: 'https://caldav.icloud.com',
username: '[email protected]',
password: process.env.ICLOUD_APP_PASSWORD!,
}),
],
});Configuration
| Option | Default | Description |
|--------|---------|-------------|
| serverUrl | — | CalDAV server URL |
| username | — | CalDAV username |
| password | — | Password or app-specific password |
| authMethod | 'basic' | Authentication method ('basic' or 'oauth') |
| defaultCalendar | — | Display name or URL of default calendar |
| name | 'calendar' | Provider name in ctx.providers |
Common Provider Configs
iCloud Calendar (requires App-Specific Password):
createCalendarProvider({
serverUrl: 'https://caldav.icloud.com',
username: '[email protected]',
password: process.env.ICLOUD_APP_PASSWORD!,
})The iCloud CalDAV URL is the same for all users. Discovery is handled automatically by tsdav via the /.well-known/caldav endpoint.
Google Calendar (via CalDAV, requires App Password):
createCalendarProvider({
serverUrl: 'https://apidata.googleusercontent.com/caldav/v2/',
username: '[email protected]',
password: process.env.GOOGLE_APP_PASSWORD!,
})Note: Google's CalDAV support requires an app-specific password. The primary calendar URL is typically https://apidata.googleusercontent.com/caldav/v2/{email}/events/.
Nextcloud:
createCalendarProvider({
serverUrl: 'https://cloud.example.com/remote.php/dav',
username: 'your-username',
password: process.env.NEXTCLOUD_PASSWORD!,
})Radicale:
createCalendarProvider({
serverUrl: 'https://radicale.example.com',
username: 'your-username',
password: process.env.RADICALE_PASSWORD!,
})Baikal:
createCalendarProvider({
serverUrl: 'https://baikal.example.com/dav.php',
username: 'your-username',
password: process.env.BAIKAL_PASSWORD!,
})Using in Functions
Access the calendar client via ctx.providers.calendar:
import { defineFunction, z } from 'intentkit';
import type { CalendarClient } from 'intentkit-calendar';
export const todaysEvents = defineFunction({
name: 'todays_events',
intent: 'List all events happening today',
permissions: ['calendar:read'],
requires: ['calendar'], // Validates provider exists at startup
input: z.object({}),
output: z.object({
events: z.array(z.object({
summary: z.string(),
start: z.string(),
end: z.string(),
})),
}),
execute: async (_input, ctx) => {
const cal = ctx.providers.calendar as CalendarClient;
const now = new Date();
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).toISOString();
const events = await cal.listEvents({ start: startOfDay, end: endOfDay });
return {
events: events.map(e => ({ summary: e.summary, start: e.start, end: e.end })),
};
},
});Example Functions
The package includes 5 ready-to-use functions in functions/events.ts:
| Function | Intent | Permission |
|----------|--------|------------|
| list_calendars | List all available calendars | calendar:read |
| list_events | List events in a date range | calendar:read |
| create_event | Create a new calendar event | calendar:write |
| update_event | Update an existing event | calendar:write |
| delete_event | Delete an event by UID | calendar:write |
Import and register them:
import { listCalendars, listEvents, createEvent, updateEvent, deleteEvent } from 'intentkit-calendar/functions';
const registry = new IntentRegistry()
.register(listCalendars, listEvents, createEvent, updateEvent, deleteEvent);CalendarClient API
The full client interface for custom function implementations:
interface CalendarClient {
// Calendars
listCalendars(): Promise<Calendar[]>;
// Events
listEvents(options: ListEventsOptions): Promise<CalendarEvent[]>;
getEvent(uid: string, calendar?: string): Promise<CalendarEvent | null>;
createEvent(input: CreateEventInput): Promise<CalendarEvent>;
updateEvent(input: UpdateEventInput): Promise<CalendarEvent>;
deleteEvent(uid: string, calendar?: string): Promise<void>;
// Connection
ping(): Promise<boolean>;
}iCalendar Parsing
The adapter uses regex-based parsing to extract VEVENT properties from raw iCalendar (RFC 5545) text. This handles common cases well:
- Standard properties: SUMMARY, DESCRIPTION, LOCATION, DTSTART, DTEND, UID, STATUS, ORGANIZER, ATTENDEE, RRULE, CREATED, LAST-MODIFIED
- Date formats: date-time (
20260321T100000Z), date-only (20260321) for all-day events - Line unfolding (RFC 5545 section 3.1)
- Text escaping/unescaping (RFC 5545 section 3.3.11)
Not fully supported (inherent to regex-based parsing):
- Multi-valued properties with complex parameters
- VALARM (reminders)
- VTIMEZONE definitions
- Recurrence expansion (RRULE is stored as-is, not expanded)
For most calendar use cases via AI agents, the regex parser covers what's needed. If you need full RFC 5545 compliance, consider wrapping a dedicated iCal library in a custom client.
Claude Desktop Config
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"calendar": {
"command": "node",
"args": ["path/to/your/serve.js"],
"env": {
"ICLOUD_APP_PASSWORD": "your-app-password"
}
}
}
}Architecture
Claude (Dispatch / Desktop)
| MCP tool call
IntentKit (serve + permissions + hooks)
| ctx.providers.calendar
intentkit-calendar (CalendarClientImpl)
|
tsdav
(CalDAV)
|
CalDAV Server (iCloud, Google, Nextcloud, etc.)The CalDAV client authenticates via login() at server startup, which discovers the principal URL and calendar home. Subsequent operations use the discovered URLs. Since CalDAV is HTTP-based, there's no persistent connection to manage — the provider's destroy is undefined.
License
MIT
