邮件日历联系人MCP服务

一个基于JMAP、CalDAV和CardDAV标准的MCP服务器,用于通过邮件、日历和联系人工具实现统一的消息协作管理,兼容Fastmail、iCloud、Nextcloud等服务。

  • by adinschmidt
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 adinschmidt/fastmail-mcp

使用说明

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 + contacts
  • FASTMAIL_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_mailboxes
  • create_mailbox
  • update_mailbox
  • delete_mailbox
  • list_emails
  • get_email
  • search_emails
  • send_email
  • mark_email_read
  • move_email
  • delete_email
  • get_email_attachments
  • download_attachment

Calendar (CalDAV):

  • list_calendars
  • create_calendar
  • update_calendar
  • delete_calendar
  • get_calendar_event
  • list_calendar_events
  • create_calendar_event
  • update_calendar_event
  • delete_calendar_event

Contacts (CardDAV):

  • list_contact_lists
  • list_contacts
  • get_contact
  • create_contact
  • search_contacts
  • update_contact
  • delete_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_mailbox refuses to delete protected system mailboxes (Inbox, Spam, Trash, Sent, Drafts, Archive).
  • delete_calendar refuses 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).

  1. Run list_calendars
  2. Pick a calendar with canWrite: true
  3. Use that calendar's id as calendarId for create_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.