- TypeScript 96.6%
- JavaScript 3.4%
|
|
||
|---|---|---|
| src | ||
| .env.example | ||
| .gitignore | ||
| .npmrc | ||
| biome.json | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
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 titlestart,end— ISO 8601 datetimescalendarUrl— calendar to create the event indescription?— notes / event bodylocation?— location or addressrecurrenceRule?— RFC 5545 recurrence (freq,interval,count,until,byday,bymonthday,bymonth)alarms?— array of alarms; each is{ action: "DISPLAY"|"EMAIL"|"AUDIO", trigger, ... }.triggeris 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 matchcategory?— exact match against vCard CATEGORIES valueslimit?— 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 FNorg?,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-contactto 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.