feat(contacts): page list_contacts with a book-bound cursor; get_contact by href (v0.3.0) #39
No reviewers
Labels
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jlxq0/carddav-mcp!39
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/t_6e196692-paging"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Card t_6e196692, AC2 and AC3 (Julian approved contact paging and get-by-href directly). Second of two sequential PRs; stacked on the v0.2.2 dependency patch.
Why
CardDAVhas no server-side paging.list_contactsfetched every card's body with oneaddressbook-queryand returned the firstlimit(max 100), so the Contacts book (366 cards) could never be enumerated past 100, and there was no way to fetch one card by its href.What
src/contact_page.rs): onePROPFIND Depth: 1lists hrefs andETags (no bodies) plus the book'sgetctag; the page is the firstlimithrefs in byte order after the cursor; only those bodies are fetched withaddressbook-multiget, in theversionrequested. The cursor is the last href wrapped with the address book and thectagof its page, encoded opaquely (c1.<hex(json)>). Properties: a card that exists throughout is returned exactly once; a cursor for another book is refused before any request; paths are compared percent-decoded so@and%40name the same book; members the server lists outside the book (or off this origin) are dropped, and a listing whose members all fall outside the named book is refused rather than answered as empty; a member that vanished between listing and fetch is counted inmissing;changed_since_cursorcarries the enumeration's firstctagand stays true once the book moved (a card added behind the cursor is not revisited).list_contactsgainscursorand returnsnext_cursor,complete,total,ctag,changed_since_cursor,missing.get_contact(contact_href, version): one card with itsETag, rendered as the server does by default or in the requested version; foreign hrefs are refused by the client's origin confinement, a same-origin resource that is not a vCard is refused by itsContent-Type, and the server's permissions decide the rest. A 403 is now reported as a refusal of that resource instead of a session expiry (only a 401 ends the session).src/fake_stalwart.rs(tests only): the fake server moves out ofcontact_write.rsand gains PROPFIND, multiget with a 404propstatfor a missing member, a movingctag, and an injectable out-of-book member.Out of scope here, per the card: sync-collection tokens, sharing, client UI.
Evidence
get_contactrenders 3.0/4.0 with the sameETag, refuses a foreign origin, reports not-found.completeinverted (1), sticky flag removed (3), raw-href body matching (1), undecoded path comparison (2), all-outside guard disabled (1).cargo audit,cargo deny check, gitleaks: clean.3910aba; round 2 SHOULD-FIX (a book href typed with@instead of%40produced an empty, complete page while the old path returned the cards), fixed inc416930and pinned inb3331e4; confirmation pass PASS.Live proof (AC4) follows Clark's rollout: >100 synthetic contacts created, enumerated and read back through a real MCP client, then deleted conditionally and verified absent; no real contact is written.
🤖 Generated with Claude Code
https://claude.ai/code/session_016ZfeS2nAr54h4afQ8ShfVE