Fork with contacts and more event metadata
mcp
  • TypeScript 96.6%
  • JavaScript 3.4%
Find a file
2026-04-29 22:21:43 -07:00
src feat: CardDAV contact tools (#2) 2026-04-29 22:21:43 -07:00
.env.example feat: CardDAV contact tools (#2) 2026-04-29 22:21:43 -07:00
.gitignore Hard fork to dav-mcp: cleanup, modular setup, event metadata (#1) 2026-04-29 21:24:12 -07:00
.npmrc ops: Add npmrc to always point to npm for public packages 2025-06-19 14:32:39 +02:00
biome.json ci(github-actions): add build step to release workflow 2026-02-11 12:16:56 +01:00
CHANGELOG.md chore(release): 0.5.0 [skip ci] 2026-04-09 15:03:06 +00:00
CLAUDE.md feat: CardDAV contact tools (#2) 2026-04-29 22:21:43 -07:00
LICENSE Hard fork to dav-mcp: cleanup, modular setup, event metadata (#1) 2026-04-29 21:24:12 -07:00
package-lock.json feat: CardDAV contact tools (#2) 2026-04-29 22:21:43 -07:00
package.json feat: CardDAV contact tools (#2) 2026-04-29 22:21:43 -07:00
README.md feat: CardDAV contact tools (#2) 2026-04-29 22:21:43 -07:00
tsconfig.json refactor(typescript): update moduleResolution to bundler for modern 2026-02-11 12:22:28 +01:00
vitest.config.ts style(formatting): apply consistent code formatting 2026-02-11 12:20:23 +01:00

dav-mcp

A CalDAV / CardDAV Model Context Protocol server exposing calendar and contact operations as tools for AI assistants.

Hard fork of caldav-mcp for private deployment in Brad's homelab. Adds CardDAV contact tools, additional event metadata (description, location, alarms), and modular calendar-only / contacts-only / both deployment.

Setup

git clone https://git.brads.house/brad/dav-mcp.git
cd dav-mcp
npm install

Then point your MCP client at it:

- name: dav
  command: /absolute/path/to/dav-mcp/node_modules/.bin/tsx
  args:
  - /absolute/path/to/dav-mcp/src/index.ts
  env:
    CALDAV_BASE_URL: https://your-server.example.com/dav.php/
    CALDAV_USERNAME: your-username
    CALDAV_PASSWORD: your-password
    CARDDAV_ADDRESSBOOK_URL: https://your-server.example.com/dav.php/addressbooks/your-username/default/

No build step — tsx runs the TypeScript directly.

Deployment modes

The server registers tools based on which env vars are set, so the same install can be deployed differently per agent context:

Mode Required env vars
Calendar-only CALDAV_BASE_URL, CALDAV_USERNAME, CALDAV_PASSWORD
Contacts-only CARDDAV_ADDRESSBOOK_URL, CARDDAV_USERNAME, CARDDAV_PASSWORD (server base URL is derived from the address book URL if CARDDAV_BASE_URL is unset)
Both (e.g. Baikal) CALDAV_* plus CARDDAV_ADDRESSBOOK_URL (other CardDAV vars fall back to CalDAV equivalents)

You can also run two independent processes sharing this same install — e.g. one MCP entry named calendar with only CALDAV_* set, another named contacts with only CARDDAV_* set. Each process registers only the tools matching its env, so tools are namespaced cleanly (calendar:list-events vs contacts:search-contacts).

If neither module is configured, the server exits with an error at startup.

Calendar tools

list-calendars

Lists all calendars (name + URL).

list-events

Lists events between two timestamps in a given calendar.

Parameters: start, end (ISO 8601), calendarUrl. Returns: array of { uid, summary, start, end }.

create-event

Creates a calendar event.

Parameters:

  • summary — event title
  • start, end — ISO 8601 datetimes
  • calendarUrl — calendar to create the event in
  • description? — notes / event body
  • location? — location or address
  • recurrenceRule? — RFC 5545 recurrence (freq, interval, count, until, byday, bymonthday, bymonth)
  • alarms? — array of alarms; each is { action: "DISPLAY"|"EMAIL"|"AUDIO", trigger, ... }. trigger is an iCalendar duration (-PT15M = 15 min before) or absolute UTC datetime.

Returns: the created event's uid.

update-event

Updates an existing event. Only provided fields are changed.

Parameters: uid, calendarUrl, plus any of summary, start, end, description, location, recurrenceRule, alarms.

delete-event

Deletes an event by uid from calendarUrl.

Contact tools

The address book URL is configured once via CARDDAV_ADDRESSBOOK_URL — contact tools take no per-call book parameter.

search-contacts

Search by substring across name, email, phone, organization, role, notes, or address; optionally filter by category.

Parameters:

  • query? — case-insensitive substring match
  • category? — exact match against vCard CATEGORIES values
  • limit? — default 50, max 500

Returns: { count, limit, contacts: Contact[] } with full structured contact records (multi-value phones/emails/addresses preserved).

get-contact

Fetch a single contact by UID.

Returns: structured contact record.

create-contact

Create a new contact. The tool generates a UUID; returns it.

Parameters (flat schema, hybrid input):

  • name — required, vCard FN
  • org?, role?, phone?, email?, address?, notes?, categories?, birthday? (YYYY-MM-DD)

Phones default to TYPE=CELL. To preserve multi-value structure on later updates, prefer setting one value at create time and using update-contact for additions.

update-contact

Update fields by UID. Only fields you provide are changed; unmodeled vCard properties (PHOTO, X- extensions, additional phones/emails) are preserved*.

Parameters: uid plus any of the create-contact fields.

delete-contact

Delete a contact by UID.

Composition pattern: "save without checking duplicates first"

There's no upsert-contact tool by design — name-matching ambiguity is better resolved in conversation than codified in the server. The recommended pattern, from a CLAUDE.md instruction:

To save a contact without thinking about duplicates: call search-contacts(query=name) first.

  • 0 hits → create-contact
  • 1 hit → update-contact to merge any new fields
  • 2+ hits → ask the user which one to use

Development

npm run dev      # run with file watch and .env auto-load
npm run check    # lint/format check (Biome)
npm run check:fix
npm test

License

MIT — see LICENSE. Original work © 2025 Dominik Grusemann; fork additions © 2026 Brad Wenner.