feat(mcp): add get_event_raw, and surface the master's recurrence properties #18

Merged
jlxq0 merged 1 commit from feat-event-raw into main 2026-08-26 09:12:03 +00:00
Owner

Refs #16, step 2 of the build order. Nothing else in that sequence can be validated without it.

What was unreachable

list_events REPORTs with <c:expand>, and RFC 4791 §9.6.5 requires a server to strip RRULE, EXDATE and RDATE when expanding. Every tool we shipped read through that path, so the master's recurrence properties were not obtainable at all — not lost by our parser, never sent.

get_event_raw returns the object exactly as stored, its ETag, and every VEVENT in it. It is the same GET update_event already performs before patching, so it inherits the existing href resolution, the same-origin check and the 16 MiB response cap rather than adding a new fetch path.

Event additions

  • is_override — true for components carrying a RECURRENCE-ID.
  • exdates / rdates — verbatim, as {value, tzid, is_date}.

There is no master_href. Every component of a series lives in one calendar object, so it would always equal href, and a second field that can never differ from another is a second source that can only agree — it costs a field and buys nothing. href is the master href for every instance and the doc comment says so.

Why exdates is a list of structs rather than strings

EXDATE and RDATE may each repeat, each may carry a comma-separated list, and TZID / VALUE=DATE belong to the property rather than to any one value. Flattening to bare strings loses the parameters, and an exclusion built from a value without its TZID matches nothing — the silent failure #16 is about. Entries are flattened with their parameters copied down, and a date-shaped value is treated as a date even when VALUE=DATE is absent.

Verified by mutation

mutation died
comma-separated values not split recurrence_dates_are_flattened_with_their_parameters, alone
TZID not carried down to each value that test and get_event_raw_returns_the_master_with_its_recurrence_properties
bare date not recognised without VALUE=DATE a_bare_date_value_is_recognised_without_the_parameter, alone
is_override hardcoded false get_event_raw_returns_the_master_with_its_recurrence_properties, alone

The first attempt at that table was wrong and the correction is in AGENTS.md. Two of the four mutations reported "no test died" for reasons that had nothing to do with the tests: a scripted str.replace whose pattern no longer matched after cargo fmt reflowed the loop across four lines, and cargo test --lib on a crate with no library target, which exits 101 and prints no test result: line at all while my grep reported nothing found. A mutation that never applied looks exactly like a suite that caught nothing. Re-run with the pattern asserted and --bins, every mutation kills the test it should.

Gates

cargo fmt --check, cargo clippy -D warnings, cargo test --all-features (110 + 9), cargo audit, cargo deny check bans licenses sources — green on rustc 1.98.0, through the shared build slot.

Next

delete_occurrence and update_occurrence for series with no ATTENDEE, refusing when attendees are present, per the decision recorded on #16. The EXDATE behaviour gets measured against Clark's throwaway account before anything depends on an exclusion landing.

Refs #16, step 2 of the build order. Nothing else in that sequence can be validated without it. ## What was unreachable `list_events` REPORTs with `<c:expand>`, and RFC 4791 §9.6.5 requires a server to strip `RRULE`, `EXDATE` and `RDATE` when expanding. Every tool we shipped read through that path, so the master's recurrence properties were not obtainable at all — not lost by our parser, never sent. `get_event_raw` returns the object exactly as stored, its `ETag`, and every `VEVENT` in it. It is the same `GET` `update_event` already performs before patching, so it inherits the existing href resolution, the same-origin check and the 16 MiB response cap rather than adding a new fetch path. ## `Event` additions - **`is_override`** — true for components carrying a `RECURRENCE-ID`. - **`exdates` / `rdates`** — verbatim, as `{value, tzid, is_date}`. **There is no `master_href`.** Every component of a series lives in one calendar object, so it would always equal `href`, and a second field that can never differ from another is a second source that can only agree — it costs a field and buys nothing. `href` is the master href for every instance and the doc comment says so. ## Why `exdates` is a list of structs rather than strings `EXDATE` and `RDATE` may each repeat, each may carry a comma-separated list, and `TZID` / `VALUE=DATE` belong to the **property** rather than to any one value. Flattening to bare strings loses the parameters, and an exclusion built from a value without its `TZID` matches nothing — the silent failure #16 is about. Entries are flattened with their parameters copied down, and a date-shaped value is treated as a date even when `VALUE=DATE` is absent. ## Verified by mutation | mutation | died | |---|---| | comma-separated values not split | `recurrence_dates_are_flattened_with_their_parameters`, alone | | `TZID` not carried down to each value | that test and `get_event_raw_returns_the_master_with_its_recurrence_properties` | | bare date not recognised without `VALUE=DATE` | `a_bare_date_value_is_recognised_without_the_parameter`, alone | | `is_override` hardcoded false | `get_event_raw_returns_the_master_with_its_recurrence_properties`, alone | **The first attempt at that table was wrong and the correction is in `AGENTS.md`.** Two of the four mutations reported "no test died" for reasons that had nothing to do with the tests: a scripted `str.replace` whose pattern no longer matched after `cargo fmt` reflowed the loop across four lines, and `cargo test --lib` on a crate with no library target, which exits 101 and prints no `test result:` line at all while my grep reported nothing found. A mutation that never applied looks exactly like a suite that caught nothing. Re-run with the pattern asserted and `--bins`, every mutation kills the test it should. ## Gates `cargo fmt --check`, `cargo clippy -D warnings`, `cargo test --all-features` (110 + 9), `cargo audit`, `cargo deny check bans licenses sources` — green on `rustc 1.98.0`, through the shared build slot. ## Next `delete_occurrence` and `update_occurrence` for series with no `ATTENDEE`, refusing when attendees are present, per the decision recorded on #16. The `EXDATE` behaviour gets measured against Clark's throwaway account before anything depends on an exclusion landing.
feat(mcp): add get_event_raw, and surface the master's recurrence properties
All checks were successful
CI / cargo (pull_request) Successful in 42s
CI / docker (pull_request) Successful in 53s
8518512c3d
list_events REPORTs with <c:expand>, and RFC 4791 §9.6.5 requires a server to
strip RRULE, EXDATE and RDATE when expanding, so the master's recurrence
properties were unreachable through every tool we shipped. Nothing about a
series could be validated without them.

get_event_raw returns the calendar object exactly as stored, its ETag, and
every VEVENT it contains. It is the same GET update_event already performs, so
it inherits the existing href resolution, same-origin check and 16 MiB response
cap rather than adding a new fetch path.

Event also gains:

- is_override, true for components carrying a RECURRENCE-ID. There is no
  separate master_href: every component of a series lives in one calendar
  object, so it would always equal href, and a second field that can never
  differ is a second source that can only agree.
- exdates and rdates, verbatim. Both properties may repeat and each may carry
  a comma-separated list, and TZID and VALUE=DATE belong to the property rather
  than to any one value, so entries are flattened with their parameters copied
  down. A date-shaped value is treated as a date even without VALUE=DATE.

Refs #16
jlxq0 merged commit c45db5d2f8 into main 2026-08-26 09:12:03 +00:00
jlxq0 deleted branch feat-event-raw 2026-08-26 09:12:04 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/caldav-mcp!18
No description provided.