-
v0.1.6
Stablereleased this
2026-08-29 00:20:02 +00:00 | 8 commits to main since this releaseAdds
upcoming_birthdays: contacts whose birthday falls within the next N days,
across one or all address books, in one call.BDAYwas parsed nowhere in the service before this, so the question could only
be answered by paginglist_contactsfour times against 366 cards over a limit
of 100 and parsing vCards client-side, which is 5.39 MB of vCard for a
one-line question.ContactSummaryalso gains abirthdayfield.Accepts every ISO form vCard 3.0 and 4.0 permit and returns nothing on anything
else, because a card with a free-textBDAYis a card and must not fail a query
over the whole book.unparseable_birthdaysin the response is the only signal
that an answer is incomplete.Accepted in the field, both arms
days=30 birthdays=3 contacts_scanned=374 unparseable=0 ref=2026-08-29 days=14 birthdays=0 contacts_scanned=374 unparseable=0 ref=2026-08-29Expected 3 and 0, predicted before the code existed from a raw
addressbook-queryREPORT rather than from the tool: 45 birthdays across 374
cards, three inside 30 days at offsets 15, 22 and 30.Confirmed independently by the pod's own audit log rather than only by the
caller:method=upcoming_birthdays outcome=ok result_count=3 latency_ms=431and
result_count=0 latency_ms=292. 431 ms to scan 374 cards is the measure of
what the tool replaces.The 14-day arm is a control only because the 30-day arm answers differently
over the same cards on the same day. Alone it returns an empty list from a
correct implementation, from a query matching nothing, and from one erroring
into an empty vector alike.What the address book cannot pin
45 birthdays, none today, none on a leap day, none unparseable. Fixtures pin the
date rules and each dies under its own mutation: the leap-day choice (29
February occurs on 1 March in a common year, so a birthday is never reported as
past while still ahead), the inclusive window edge, the wrap into next year, and
the birth-year floor.That last one is from cross-engine review, which found that a
reference_date
before the birth year treated the month-day as already recurring:1984-03-17
asked from1983-03-16returned 1 day instead of 367.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.1.5
Stablereleased this
2026-08-28 01:55:05 +00:00 | 14 commits to main since this releaseStops the initialize rate limiter livelocking the fleet. Every new MCP session
was refused for hours on 2026-08-28 and the server recorded nothing about it.- A refused request still spent a token:
check()charged the per-bearer bucket
and then tested the per-subject bucket, so retries consumed every refill
without reaching the two tokens one connection needs. It now charges exactly
one bucket. - Burst 8 to 32.
MAX_SESSIONS, the actual flood defence, is untouched at 256. - The refill is no longer anchored to
SESSION_KEEP_ALIVE: one token per minute
rather than one per thirty, because the old anchor tied recovery to session
lifetime and made one connection cost an hour of fleet silence. - Adds
carddav_mcp_initialize_rejected_total{bucket}and awarnline. Nothing
recorded a rejection before, which is why the outage had to be reconstructed
from what succeeded and from the silence after it.
Acceptance, and what it does not show
Pod
carddav-mcp-7bf7b8ff4d-nw6kl,sha256:85da0deb…, up 02:07:22Z from a
zero baseline. Three connections from the three agent sessions that hold a
carddav mount, inside four minutes:refused MCP initialize 0 create new session 6 client initialized 3 2:1, no orphans authenticated requests 23 distinct token_hash 1 xff_entries / hops 23 of 23 at 2 / 2 / resolvedThis window would have passed on v0.1.4 as well. Six charges against the old
burst of eight, and a pod replacement resets the buckets either way, so it
evidences the rollout rather than the fix. The charge-order path never executed:
zero rejections means the changed code did not run.The fix is evidenced by mutation, not in the field. Restoring the old order
turnsa_refused_initialize_spends_nothingred withleft: Err(Bearer), right: Err(Subject)— the rejected request having spent its own token. Reverting the
burst or the replenish period each kills only its own test.The field case cannot be constructed with three mounts: distinguishing the
images needs five connections in one window, or a drained bucket observed
recovering.v0.1.4's trusted-proxy-hop fix survives the upgrade unchanged, which is the
regression nobody thinks to check.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- A refused request still spent a token:
-
v0.1.4
Stablereleased this
2026-08-27 04:06:18 +00:00 | 27 commits to main since this releaseCorrects one sentence in this tag's annotation. The tag itself is not re-cut:
an image already points at the ref, and moving it would briefly make it
ambiguous which image the tag names.The annotation says the bypass residual costs "a stolen bearer plus LAN
access". It costs a stolen bearer plus code running inside the cluster.Measured 2026-08-27: from a machine on the house LAN,
203.24.209.5and its v6
address time out after 8 s on ports 80 and 443, while
https://carddav.kampong.social/answers 401 from that same machine as a
control. From a pod inside the cluster, both gateway addresses answer 401.
MetalLBis the reason: thefonduepool holding203.24.209.5/32is a
BGPAdvertisementpeered acrosssgp,laxandzrh, and the L2 pool is a
different address onhome-lan, so no BGP session carries the route to the
wifi.Everything else in the annotation stands. The corrected wording, the manifest
parentRefwhere the fact can actually be broken, and the reason a hop count
of 2 would be worse than 1 if it ever were, are insrc/config.rsand
AGENTS.mdas of PR 16.Verified on this release: pod
carddav-mcp-5cf76bb494-7dg5grunning
sha256:dd915b10117a66eb0d6fad3fcae1b6f99ac6fb1d0d77c405e2d03542cc580858,
reportingxff_entries=2 trusted_proxy_hops=2 client_ip_resolved=trueon 7 of
7 authenticated requests.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
carddav-mcp v0.1.2
Stablereleased this
2026-08-19 07:35:37 +00:00 | 47 commits to main since this releaseSecurity and open-source readiness release.
Highlights:
- bounds public MCP request bodies and identity-keyed in-memory state;
- coalesces and rate-limits unknown-key JWKS refreshes;
- restores OAuth token anti-caching headers and requires HTTPS for public dependencies;
- adds Claude Desktop and Cowork callbacks, self-hosting docs, Compose/Kubernetes examples, and contributor/security policies;
- ships a scanned Linux/AMD64 non-root image with SLSA provenance and SPDX SBOM attestations.
Container:
forge.oddie.app/jlxq0/carddav-mcp:v0.1.2@sha256:5ea11aa76eaf4a97a18a4185d96bfb1e51ff8b9e7270a4942968d58022a236aeAttached CycloneDX SBOM SHA-256:
56e4f72e8ebe6a28b6d938aff78de35c375803a7a79f4036319185c612c05b63The v0.1.1 source tag was superseded before container publication because its release workflow used a Buildx-only flag with standalone buildctl.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads