Releases: ni-c/carddav-mcp
Releases · ni-c/carddav-mcp
Release list
v0.1.0
Added
First release. An MCP server for CardDAV: 17 tools over contacts, groups and
photos, on any server that speaks the standard.
- 17 tools, 10 read and 7 write.
CARDDAV_READ_ONLY=trueleaves the write
tools unregistered rather than failing them, so they do not appear in
tools/listat all. - A listing is cheap and says so.
list_contactsretrieves only the
summary properties, which is the difference between a few kilobytes and
several megabytes once inline photos are involved — and every entry it
returns is markedpartial, because a card retrieved that way is missing
everything not asked for. The write path never accepts one: it issues its own
GETfor the card it is about to replace, every time. - Writes are read-modify-write over the parsed card, never a document
rebuilt from the fields this server models. AnX-property some phone wrote
in 2014, a photo nobody mentioned and every parameter survive an edit because
they are never touched. Guarded withIf-Matchfrom the same read; never
If-Match: *; a weak ETag is refused rather than dropped. - Both group conventions. vCard 4's
KIND:groupwithMEMBER, and Apple's
X-ADDRESSBOOKSERVER-KINDon top of vCard 3 — which is what Apple Contacts,
Nextcloud and most of the installed base actually store. Both are read; the
one written follows whatever the address book already uses, because a group
in the other convention is invisible in the client the person is looking at. - A search is one request, not one per field. RFC 6352 gives
<C:filter>a
testattribute defaulting toanyof, so several properties are matched in
a singleaddressbook-query. Whatever comes back is checked again locally,
because a server that filters only partially is indistinguishable from one
that matched properly, and the result says which path it took. - Birthdays keep the year they have, and no more.
BDAY:--0415means "15
April, year unknown", which is what a phone writes when the year was never
entered and a large share of real birthdays look like. It is reported as
{month, day}rather than mapped to nothing or given an invented year. CARDDAV_ADDRESSBOOKSfences the server to named address books, enforced
where an id is decoded rather than at the edge of each tool, so no tool can
forget it — including a group membership change, which is the one place a
fenced-off path could otherwise be named. A listing reports how many
collections it withheld instead of quietly being shorter.- Ids are opaque and carry no origin. The host is rebuilt from
CARDDAV_URLon every decode, so a forged id cannot point this server at
another server. Every join of a collection URL and a card name is checked on
the resolved path: checking the name for a literal/is not the same
check, because the URL parser normalises%2E%2Eand treats a backslash as a
separator. - Contacts are treated as text a stranger wrote, because an address book
rarely is written only by its owner — cards arrive by import from a phone, by
sync from a directory, and from anyone with write access to a shared book.
Names, organisations and notes are fenced with a per-call nonce and marked
line by line; invisible and directional characters are removed; markdown
image syntax is defused. Two of the injection shapes reported are specific to
an address book, and the second is the reason contacts are worth attacking at
all: a card asserting that somebody's number or account has changed does not
need the model to run a tool, only to be believed. - Nothing this server says quotes card content. Not the approval dialogs,
not the error messages. Every value an error repeats is escaped, collapsed to
one line and cut first, because an error reaches the model in the server's
own voice, outside any fence. - Photos are reported, not delivered.
get_contactsays a photo is there,
its type and its size;get_contact_photoreturns the bytes as an image when
asked for by name. A photo stored as a URI is reported and never fetched —
that address was chosen by whoever wrote the card. - It never fetches an address somebody else chose. No tool takes a URL.
Links returned by the server are pinned to the configured origin and refused
if they carry credentials or a scheme this server does not speak — checked
again at the point the credentials would leave the process. CARDDAV_ALLOW_PLAINTEXT. A plainhttp://URL to a host that is not
loopback refuses to start instead of printing a warning that a stdio
deployment never shows. The switch lifts the refusal and is read strictly,
likeCARDDAV_INSECURE_TLS.- Bearer or Basic authentication, RFC 6764 discovery from a server root or a
collection URL, RFC 6578sync-collectionaslist_changes, and a raw vCard
export that keeps the properties this server does not model — because an
export that dropped them would be a backup that silently loses data.