使用说明
Check out my other MCP tools
jmap-dav-mcp
Generic Model Context Protocol (MCP) server for open email/PIM standards:
- Mail via JMAP (RFC 8620/8621)
- Calendar via CalDAV
- Contacts via CardDAV
Works with any compliant server. Fastmail works out of the box (this project began as fastmail-mcp), and the JMAP side also works with self-hosted servers like Stalwart, Cyrus, and Apache James. The CalDAV/CardDAV side works with iCloud, Nextcloud, Radicale, Baïkal, and friends.
Tool domains (mail / calendar / contacts) are registered based on which credentials you configure — a calendar-only setup exposes only calendar tools.
Requirements
- Bun 1.3+ (recommended) or Node.js 20+
- Credentials for a JMAP and/or CalDAV/CardDAV server
Configuration
Mail (JMAP)
| Variable | Description |
|---|---|
JMAP_BASE_URL |
Server base URL. The session is discovered at <base>/.well-known/jmap (RFC 8620). |
JMAP_SESSION_URL |
Alternative: point directly at the session resource (overrides JMAP_BASE_URL). |
JMAP_API_TOKEN |
Bearer token auth (recommended where supported). |
JMAP_USERNAME + JMAP_PASSWORD |
Basic auth alternative. |
Calendar + Contacts (CalDAV/CardDAV)
| Variable | Description |
|---|---|
CALDAV_URL |
CalDAV server URL (server root is fine — collections are discovered). |
CARDDAV_URL |
CardDAV server URL. |
DAV_USERNAME + DAV_PASSWORD |
Basic auth credentials (use an app password where supported). |
DAV_ORGANIZER_EMAIL |
Optional. ORGANIZER email used when generating events (defaults to DAV_USERNAME). |
Domain selection
| Variable | Description |
|---|---|
MCP_DOMAINS |
Optional comma list of mail, calendar, contacts. Overrides credential-based auto-detection. |
Fastmail
Legacy FASTMAIL_* variables are fully supported and imply the Fastmail server URLs, so an existing Fastmail setup needs no URL configuration:
FASTMAIL_USERNAME+FASTMAIL_APP_PASSWORD— mail (JMAP basic auth) + calendar + contactsFASTMAIL_API_TOKEN— mail via JMAP bearer token (optional alternative)FASTMAIL_BASE_URL,FASTMAIL_CALDAV_URL,FASTMAIL_CARDDAV_URL,FASTMAIL_DAV_USERNAME,FASTMAIL_ORGANIZER_EMAIL— optional overrides
Create an app password in Fastmail Settings → Privacy & Security → App passwords.
Install
bun install
Optional (bundle to dist/):
bun run build
Run
bun run start # all configured domains
Dev (auto-reload):
bun run dev
Via bunx:
bunx --bun github:adinschmidt/fastmail-mcp
Entry points
| Bin | Domains |
|---|---|
jmap-dav-mcp (also fastmail-mcp) |
everything configured |
jmap-mcp (src/bin/jmap-mcp.ts) |
mail only |
dav-mcp (src/bin/dav-mcp.ts) |
calendar + contacts only |
MCP Client Config Examples
Fastmail (mcpServers)
{
"mcpServers": {
"fastmail": {
"command": "bun",
"args": ["/absolute/path/to/jmap-dav-mcp/src/index.ts"],
"env": {
"FASTMAIL_USERNAME": "you@fastmail.com",
"FASTMAIL_APP_PASSWORD": "your-app-password"
}
}
}
}
Generic servers
{
"mcpServers": {
"mail-calendar-contacts": {
"command": "bun",
"args": ["/absolute/path/to/jmap-dav-mcp/src/index.ts"],
"env": {
"JMAP_BASE_URL": "https://mail.example.com",
"JMAP_API_TOKEN": "your-token",
"CALDAV_URL": "https://dav.example.com",
"CARDDAV_URL": "https://dav.example.com",
"DAV_USERNAME": "you@example.com",
"DAV_PASSWORD": "your-app-password"
}
}
}
}
Calendar/contacts only (e.g. iCloud, Nextcloud)
{
"mcpServers": {
"calendar": {
"command": "bun",
"args": ["/absolute/path/to/jmap-dav-mcp/src/bin/dav-mcp.ts"],
"env": {
"CALDAV_URL": "https://caldav.icloud.com",
"CARDDAV_URL": "https://contacts.icloud.com",
"DAV_USERNAME": "you@icloud.com",
"DAV_PASSWORD": "app-specific-password"
}
}
}
}
Tools
Mail (JMAP):
list_mailboxescreate_mailboxupdate_mailboxdelete_mailboxlist_emailsget_emailsearch_emailssend_emailmark_email_readmove_emaildelete_emailget_email_attachmentsdownload_attachment
Calendar (CalDAV):
list_calendarscreate_calendarupdate_calendardelete_calendarget_calendar_eventlist_calendar_eventscreate_calendar_eventupdate_calendar_eventdelete_calendar_event
Contacts (CardDAV):
list_contact_listslist_contactsget_contactcreate_contactsearch_contactsupdate_contactdelete_contact
Pagination and SDK
The MCP handlers call the functions in src/sdk.ts. Scripts can import the same SDK without starting a server. See SKILL.md for the complete API.
list_emails, search_emails, list_calendar_events, list_contacts, and search_contacts return an object instead of a bare array:
{"items": [], "position": 0, "total": 0, "nextPosition": null}
Start with position: 0. Pass nextPosition as the next request's position, keeping the filters unchanged, until it is null. Mail pages default to 20 records and allow at most 200. Calendar and contact pages default to 50 and allow at most 500. Limits and offsets must be integers.
Calendar and contact list pages omit raw iCal and vCard data by default. Set includeRaw: true or use get_calendar_event and get_contact to retrieve full records. Supply both calendar time range endpoints together. Individual records can still be large; the page limit bounds record count, not bytes.
JMAP pages use server-side offsets. DAV has no portable offset query, so each page fetches all matching calendar objects or address book cards, sorts by URL, then slices locally. Contact search pages count matches across all selected books. Use calendar time ranges and addressBookId to narrow requests. Offsets do not provide a snapshot: concurrent inserts and deletions can shift results. For a changing collection, deduplicate IDs or restart the scan.
Existing SDK methods such as listEmails and listContacts still return arrays. Use their Page counterparts to obtain continuation metadata. SDK array methods for events and contacts retain raw data by default. All these methods accept position and enforce the same page limits.
import { listEmailsPage } from './src/sdk.js';
let position = 0;
do {
const page = await listEmailsPage({ limit: 100, position });
const unread = page.items.filter(email => !email.keywords?.$seen);
console.log(unread.map(email => ({ id: email.id, subject: email.subject })));
if (page.nextPosition === null) break;
position = page.nextPosition;
} while (true);
Security Notes
- Prefer app passwords / API tokens over your account password.
delete_mailboxrefuses to delete protected system mailboxes (Inbox, Spam, Trash, Sent, Drafts, Archive).delete_calendarrefuses to delete the last remaining calendar.
Troubleshooting
403 Forbidden creating calendar events
In almost all cases this means you're trying to write to a read-only calendar (e.g. a subscribed/shared calendar).
- Run
list_calendars - Pick a calendar with
canWrite: true - Use that calendar's
idascalendarIdforcreate_calendar_event
JMAP session errors on non-Fastmail servers
The session is discovered at <JMAP_BASE_URL>/.well-known/jmap, following redirects with auth. If your server hosts the session elsewhere, set JMAP_SESSION_URL directly.
