v0.3.0
[0.3.0] - 2026-07-25
Unified Calendar, Contacts, and Mail MCP server. Default remains Calendar-only
(10 tools). Global read-only with optional domains off exposes 7 tools. The
complete surface is 23 tools when Contacts, Mail read, Mail mutation, and Mail
send are all enabled.
Added
- Unified Calendar, Contacts, and Mail server in the existing
icloud-mcp
binary. Calendar remains always enabled; Contacts, Mail read, Mail mutation,
and SMTP submission are independently composed from validated capability
gates. - Global local
icloud_capabilitiestool with version, global read-only state,
enabled domains, effective capability groups, exact sorted tool inventory,
and count. The default surface is 10 tools, global read-only is 7, and the
complete surface is 23. - Six opt-in Contacts tools over CardDAV:
list_address_books,
search_contacts,get_contact,create_contact,update_contact, and
delete_contact. Contacts uses lazy successful-cache discovery, opaque
address-book identifiers, bounded vCard 3.0/4.0 reads, vCard 3.0 writes,
strong ETags, conditional mutations, compatible server-side search
prefilters, combined bounded local filtering, and a bounded all-card path for
digit-normalized phone search. - Three opt-in Mail read tools over IMAP:
list_mailboxes,search_messages,
andget_message. Reads use fresh sessions, UIDVALIDITY identities,
descending bounded UID windows, EXAMINE/PEEK, curated headers, bounded plain
text, attachment metadata, and no raw MIME, HTML, or attachment payloads. - Three independently gated IMAP mutation tools:
set_message_flags,
move_message, andtrash_message. Mutations check UIDVALIDITY, fail closed
withprotocol_errorbefore STORE when CONDSTORE is advertised because
go-imap beta.8 cannot expose MODIFIED, use native MOVE or a UIDPLUS-only
fallback, and never use plain EXPUNGE. The unavailable CONDSTORE flag path
does not claimconcurrent_modification. - Independently gated
send_messageover authenticated SMTP submission with
mandatory STARTTLS, a boot-validated exact-address recipient allowlist,
all-RCPT-before-DATA policy, no automatic retry, and explicit
outcome_unknownreconciliation.to,cc, andbccare each optional,
with at least one aggregate recipient required. - Unified 12-variable configuration with strict parsing for all five booleans,
dedicated Mail identity/password support, Mail password fallback, and
boot-onlyfile://support for every identity/password input. - Per-domain Calendar/Contacts HTTP allowlists plus fixed IMAP and SMTP socket
destinations, TLS 1.2 minimum, separate credentials/transports/rates/
semaphores, Mail SASL redaction, and process-local opaque HMAC audit tokens for
every Calendar, Contacts, IMAP, and SMTP mutation. Production audit records
no longer contain raw Calendar paths or UIDs. - Direct protocol dependencies for vCard, IMAP, MIME, SMTP, and SASL, bringing
the documented direct dependency set to 10. Added CardDAV and Mail
compatibility documentation and expanded architecture, security, setup, and
testing guidance for the unified server. - Tools:
get_event,find_free_slots,validate_event,calendar_capabilities. search_eventsoptional filters: multi-calendar list, UID, status, all-day,
cancelled, busy-only, compact (omit notes), stable sort by start+UID.search_eventsreturnsrecurrenceId,isOverride, and REPORTetagon
each row; all-day times formatted asYYYY-MM-DD. Multi-calendar queries
always hit every calendar then cap fairly; non-auth failures surface as
partialFailure+ warnings.get_eventreturnsoverrides[]withrecurrenceIdfor exception targeting.create_eventoptional status, transparency, URL, timezone,client_uid/
idempotency_key(conflict if UID already exists; never silent overwrite).- Recurring timed creates write TZID + generated VTIMEZONE (timezone param or
ICLOUD_MCP_DEFAULT_TZfallback) so wall-clock RRULEs survive DST. update_event/delete_eventsupportscope=series|occurrence,
recurrence_id, and optionaletag(If-Match). Occurrence never deletes
the whole series.delete_eventsupportsdry_run(zero PUT/DELETE).ParseRecurrenceIDacceptsYYYY-MM-DDfor all-day occurrence ops.- Conditional DELETE with If-Match; structured
conflictfor HTTP 409. - Create PUT sends
If-None-Match: *(hand-rolled) so concurrent same-UID
creates cannot silently overwrite (412 maps toconflict). - Expanded structured error codes (
validation,authentication,
authorization,timeout,unavailable,partial_failure,
protocol_error,internal_error) with optionalretryable/
retry_after_seconds. - Pure free-slot interval merge (buffers, working hours, overnight windows,
generative tests). Local event validation shared by create/validate tools. - Native Go fuzz targets (paths, UIDs, RRULE, dates, redaction, hosts) and
MCP in-process E2E registration tests. - Docs under
docs/(architecture, security, CalDAV, CardDAV, Mail, testing).
CI: mod verify/tidy, fuzz smoke, pinned govulncheck, binary size budget,
InsecureSkipVerifyguard. CONTRIBUTING.md; integration runbook indocs/testing.md.- Redactor also masks password-only Base64 (Std/RawStd/URL/RawURL).
- Enforced a 1 MiB stdio frame cap, 64 KiB caller-reflecting error threshold,
256 KiB Calendar/MCP result caps, 1 MiB SMTP inbound-response budget,
XML/IMAP/MIME/iCalendar parser depth and item bounds, and pathological
recurrence work budgets. - Native fuzz targets in all five parser/security packages, per-package coverage
floors, a 78% aggregate coverage gate, and opt-in live Contacts and Mail
integration gates. Live tests require credentials and are not run in CI.
Fixed
- SMTP failures or parser panics after DATA starts now retain
outcome_unknown; cleanup failures cannot overwrite a definitive accepted
result. Successful IMAP COPY/MOVE with unusable COPYUID metadata likewise
retains reconciliation guidance. find_free_slotsnow fails closed on any incomplete calendar, widens its
query for event buffers, and never reports authoritative availability from a
truncated recurrence or partial search.- Calendar recurrence validation rejects unreachable clock and date selectors
before entering non-interruptible dependency loops; unsafe non-RFC or
sub-daily selector combinations fail closed. Search now has aggregate
event/work bounds and independent 4/2 read/write semaphores. Malformed remote
objects fail closed instead of disappearing from results. - iCalendar input is structurally preflighted before recursive decoding.
VTIMEZONE transitions use exact pre-transition wall times and correct local
annual rules; all-day EXDATE/UNTIL values preserve DATE form. - CardDAV REPORT/GET redirects remain inside the selected opaque address book.
Contacts paths reject encoded traversal/separators and queries, ETags are
strict and singular, and ambiguous-write reconciliation reaches MCP clients. - Every JSON-RPC output frame is redacted, including protocol errors emitted
before tool middleware. Overlapping redaction secrets are processed longest
first. - Credential files are regular-file-only and capped at 4 KiB. Accepted
identities are plain addresses, and credential control characters fail at
boot without leaking values. - The optional health listener no longer resolves arbitrary hostnames and now
has read, write, idle, and header limits. - Local rate limiter fails fast (
rate_limited) when the next token is more
than 2s away, instead of blocking toward the 25s tool timeout. - Calendar retry handling now rewinds read request bodies only. Automatic
retries are limited to reads; PUT, DELETE, and full-series delete are never
replayed, and every ambiguous dispatched mutation, including a redirect,
returnsoutcome_unknown. - Imported-UID fallback scan window widened from +/-5 years to +/-10 years.
gofmtalignment onEvent.ETagfield.- Occurrence update with only
startkeeps duration (DTEND = start + prior length). - Client-supplied
etag=*rejected (no last-writer-wins If-Match). - Caller-supplied calendar paths bound to the discovered home-set; REPORT hrefs
validated. - Expanded occurrences no longer carry the master RRULE; EXDATE re-delete is
deduped; UID rejects backslash and other controls. - Redactor iterates to a fixed point so secrets re-formed across a previous
[REDACTED]boundary are still masked; secrets with newlines are rejected
at registration (line-buffered writer cannot mask them). - Config validation and
file://load errors no longer embed email, password,
or filesystem paths (boot failures log before the Redactor is installed). - Discovery revalidates port empty-or-443 on production iCloud hostnames
(principal and home-set), matching the HTTP transport allowlist. - Calendar paths reject scheme-relative hosts (
//...), query/fragment, and
percent-encoded..; PUT/REPORT/DELETE keep the resolved URL on the shard. update_eventpreserves existing DTSTART/DTEND form (DATE / TZID / Z) instead
of forcing UTC Z; occurrence EXDATE/RECURRENCE-ID match the master form.- Imported-UID lookup (REPORT) always re-GETs before mutate so VTIMEZONE and
VERSION survive the round-trip. update_eventand seriesdelete_eventfail closed when no ETag is available
(no unconditional PUT/DELETE; passetagfromget_event).scope=occurrencerequires a recurring master (RRULE); override copy skips
ATTENDEE/ORGANIZER; duration defaults honor nominal DURATION and next-civil-day
all-day semantics across DST.update_eventrejects non-string optional fields (no silent clear).- Discovery failures are no longer cached forever (retry after transient errors).
- MCP success payloads and path-escaped password forms are redacted; free-slot
duration/buffers clamped to schema maxes;validate_eventaccepts
idempotency_key;list_calendarsnormalizes absolute hrefs to paths. - Binary version for
calendar_capabilitiesis passed via tool deps (no global).
Changed
- Read-only mode with optional domains disabled now exposes 7 tools (was 2),
including the global capability tool; write tools remain absent. - Default
ICLOUD_MCP_DEFAULT_TZremains UTC for compatibility (operators
should set the calendar owner timezone explicitly). - Docs: operator trust for
file://secrets, read-request body rewind plus the
2s local rate-limit cap, CONTRIBUTING; threat model notesdeletedTitle
on MCP success (never in audit logs). README anddocs/refreshed to match
current behavior (fair multi-calendar search, TZID+VTIMEZONE recurring
creates, structured recurrence/alarms). Removed internal audit and V2
migration notes from the tree. file://load failures report a stable reason code (not_found,
permission_denied, orunreadable) without the path.make releasepinsgolang:1.25by image digest;make lintfalls back to
pinnedgolangci-lintviago runwhen not on PATH.make buildembedsVERSIONvia the same ldflags as release targets
(make build VERSION=v0.3.0).- Release targets now reject unset/
devversions, createdist/from a clean
checkout, package binaries with license notices, and emit SHA-256 checksums.
make installnow builds for the host instead of installing linux/arm64. - Version reporting falls back to Go module build information for
go install ...@versionwhile preserving the release ldflags override. - CI now checks Darwin arm64 builds, release version injection, public-text
ASCII policy, forbidden attribution trailers, and unresolved direct network
destinations.
Security
- Account identity is never printed on invalid
ICLOUD_EMAILat boot. file://credential paths with a..segment or an empty path are rejected.- Redactor also masks Basic-auth base64 in RawStd, URL, and RawURL encodings.
- Stdio output applies a hard 256 KiB frame cap after redaction so a missed
domain result budget cannot inflate the JSON-RPC channel. - Build and CI require Go 1.25.12 so stdlib fixes for known
crypto/tls,
net/url, and related advisories are in the supported toolchain.
Full Changelog: v0.2.0...v0.3.0
Operator notes
- First deploy: set
ICLOUD_MCP_READ_ONLY=true(7 tools, Calendar reads only). - Mail send requires an exact recipient allowlist; avoid
*in production. - When IMAP advertises CONDSTORE,
set_message_flagsfails closed withprotocol_error(go-imap beta.8). - Use app-specific passwords only; never the main Apple Account password.
- Requires Go 1.25.12 or newer to build from source.