A comprehensive suite of governed MCP servers for Apple ecosystem automation with dry-run defaults, human-gated writes, audit trails, provenance tracking, and undo capabilities
Cortland demonstrates strong governance patterns and security-first design. Tool naming is consistent (verb_noun: calendars_list, events_window, mail_search). Descriptions are detailed and explain WHY constraints exist (e.g., events_window bounded to 62 days to avoid ~30s per-calendar cost). All 30 tools have descriptions (10-200 chars, well within baseline). Input schemas are present with typed parameters and descriptions. However, output schemas are not documented in the source, LLMs cannot see what fields to expect from responses. Error handling is implicit (governance framework enforces dry-run/approval gates) but not explicitly surfaced in tool descriptions. Tool composition is excellent: calendars_list→events_window→event_create chains naturally. Security is exceptional: no secrets in params, write-gating enforced by construction, audit trails built-in. Main gaps: missing output schema documentation, no tool annotations (readOnlyHint/destructiveHint), and STDIO transport limits remote accessibility.
List every calendar by name, with its writable flag when available.
Generate today's briefing from the context store: meetings with correlated threads, changes since yesterday, first-time senders. Deterministic; every claim cites pointers.
Run one incremental Mail capture sweep into the context store (headers only — bodies are never stored). Normally the daemon's job; useful for catching up on demand.
What changed since a point in time: new mail volume, senders, newly seen people, active subjects. Deterministic, from the local context store. Citations are Message-IDs — fetch content via cortland-mail if permitted.
Profile a person from the context store: interaction counts, first/last seen, recent message pointers (headers only).
Create a calendar event (local, provenance-stamped — visible and deletable on any device). Deliberately takes NO attendees: v1 cannot send invitations, so the outward path does not exist. Worst case is a stray event on your own calendar, not an email in someone else's inbox.
Output schemas not documented. LLMs cannot see what fields responses contain (e.g., does events_window return event_id, summary, start, end, location?). This forces agents to guess field names and risks extraction errors.
Tool annotations missing. No readOnlyHint, destructiveHint, or idempotentHint in tool definitions. Agents cannot distinguish safe reads from destructive writes without parsing descriptions. event_delete and reminder_delete should be marked destructive; mail_search should be marked read-only.
Duplicate tool: reminder_create appears twice (packages/reminders/src/tools.ts and examples/reminders/src/server.ts) with slightly different schemas. One has 'dueDate' (ISO 8601), the other has 'due' (ISO datetime). This ambiguity will confuse LLMs about which to call and what format to use.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 69 | 2026-07-28+ | v2 |
Delete an event by calendar + uid. Destructive; undo recipe captured first.
Events in ONE named calendar within a bounded start-date window (max 62 days). The calendar is required by design: Calendar queries cost ~30s per calendar, so this tool never scans all calendars — query them one at a time.
Create a draft in Mail's Drafts mailbox. The draft is the END of this tool's reach: sending is a human action in Mail.app. Account is required — the writing identity is never guessed.
List Mail accounts with their addresses and mailboxes.
Set read/flagged status on a message.
Move a message to another mailbox in the same account.
Read one message in full by Message-ID (body arrives fenced).
Header search (subject/sender/date) across a mailbox. Fast, index-backed. Full-text body search is not available in this tier.
Full-text search of message bodies via Spotlight (Tier 2 — requires Full Disk Access; refuses with instructions when absent rather than returning silently-empty results). Returns headers; fetch bodies with mail_read.
Send an outgoing message from a named Mail account. Write-gated: live mode plus per-action human approval. Account and to are required — identity and recipient are never guessed. Body is redacted from the audit log.
Naive conversation view: messages in inbox+sent sharing the normalized subject.
Append text to an existing note by id.
Create a note (default folder unless named). The note is a local, provenance-stamped artifact you can see and delete on any device.
Read a full note by id, HTML stripped to readable text; includes name, folder, and dates.
List every Notes folder with its note count.
Search notes by name, optionally within one folder. Returns a plain-text snippet of each body.
Send a short iMessage to the owner of this Mac — the one person this tool can reach. Use it to report something the human asked to be told about: a job finished, a watcher fired, a run needs attention. The recipient comes from local config and cannot be set by an argument, so this cannot message anyone else.
Set a reminder's completed state by id.
Create a reminder in the default list.
Create a reminder (default list unless named). The reminder is a local, provenance-stamped artifact you can see and delete on any device.
Delete a reminder by id. Destructive; undo recipe captured first.
List open reminders in the default list (name, due date, notes).
List every reminders list with its open-item count.
Search reminders by name/list/due window. Open items by default.
Error handling not surfaced in tool descriptions. Governance framework enforces dry-run and approval gates, but tool descriptions do not explain what happens on failure (e.g., 'Requires human approval in live mode' or 'Returns error if calendar is read-only'). Agents cannot plan recovery.
Parameter constraints not fully specified. mail_search accepts 'limit' (default 20, max 100) but description does not state the range. reminders_search has 'limit' (default 50, max 200), inconsistent across tools. Agents may pass invalid values; validation should be explicit in descriptions.