• v0.7.0 ab790e5c9f

    v0.7.0 — create_event, behind a per-calendar write allowlist
    All checks were successful
    CI / gate (push) Successful in 1m0s
    CI / image (push) Successful in 50s
    Stable

    joeyr released this 2026-08-09 15:27:21 -05:00 | 103 commits to main since this release

    First release that can change the calendar. Everything before this was read-only by
    construction; from here it is read-only by configuration, and the configuration
    defaults to read-only.

    Added

    • create_event — creates one non-recurring event on a calendar named in the new
      WRITABLE_CALENDARS allowlist. Accepts a finish time as either end or
      duration_minutes, handles all-day events, and echoes the whole created event back
      rather than an acknowledgement, so a timezone or all-day misunderstanding shows up in
      the transcript instead of on someone's phone a day later.

    • WRITABLE_CALENDARS, empty by default — and empty means the tool is not
      registered
      , not that it refuses. An offered tool is one the model will promise the
      user before it discovers it cannot deliver, so with this unset the served tool surface
      is identical to 0.6.0.

      Separate from CALENDARS rather than a flag on it: reading a shared household
      calendar to answer "when is everyone free" is not consent to write to it.

    • A duplicate guard. A create whose response the caller never saw — a timeout on the
      write — leaves the event created and the model convinced it was not, and the retry
      books the meeting twice. An event with the same title and start is refused, and the
      refusal carries the existing event so the user can be told what is already booked.
      allow_duplicate=true overrides it. Best-effort by design: if the check cannot read
      the calendar the create still proceeds, because turning iCloud throttling into an
      inability to add anything is a worse failure than the one being prevented.

    • A writable preflight check and writable_calendars on the boot line. A name in
      the allowlist matching no readable calendar registers a tool that refuses every call,
      which is invisible otherwise. Reported as a failure but never fatal — a typo in the
      write allowlist must not take the read path down with it.

    • mcp_gateway/calendar/compose.py, the inverse of expand.py and network-free for
      the same reason. Two things in it are the ones worth getting right: DTEND is
      exclusive, so a one-day all-day event ends on the following date — writing the same
      date twice yields a zero-length event most clients render as nothing at all. And timed
      events are written in UTC rather than with a TZID, because a TZID reference is
      only legal alongside a matching VTIMEZONE component; these are single non-recurring
      instances, so there is no future DST transition for a zone rule to get right.

    Changed

    • Calendar.read_only reports the truth instead of a hardcoded true. It is how
      the model knows which calendar to offer. Subscriptions are always read-only — a feed is
      an HTTPS GET against someone else's server, and naming one in WRITABLE_CALENDARS
      does not change that.

    • The server instructions name the writable calendars and, just as explicitly, the
      limits: nothing can be edited or deleted, and repeating events cannot be created.
      Stating the capability without the limits invites the model to offer the rest.

    Security

    • The credential was never the boundary and still is not. An app-specific password
      has granted full CalDAV read and write since the first release; what changed is that
      this code will now use it. WRITABLE_CALENDARS is the boundary, it is enforced before
      iCloud is contacted, and it is checked before the calendar name is even looked up — so
      a real calendar that is merely not writable does not come back as "no such calendar".

    • Resource URLs are never logged or returned. They carry the account DSID, and tool
      output reaches the model.

    Downloads