car-integrations
Account-bound integrations for Common Agent Runtime — Calendar, Contacts, Mail, Messages, Notes, Reminders, Photos, Bookmarks, Files, Keychain.
What it does
The logical capabilities are the same on every OS; the backends are not. v1 defines the release contract — stable return shapes that carry explicit available + backend fields so downstream apps branch cleanly while backends light up incrementally.
| OS | Calendar | Contacts | Messages | Other Apple account surfaces | |
|---|---|---|---|---|---|
| macOS | EventKit (EKEventStore) |
Contacts.framework | Mail.app automation | Messages.app automation | Notes/Reminders/Photos app automation, Safari bookmarks, iCloud Drive paths, Keychain |
| Windows | MS Graph + Outlook MAPI | Windows.Contacts + Graph | MS Graph + MAPI | Not modeled | Not modeled |
| Linux | Evolution DS + CalDAV | Evolution DS + CardDAV | Evolution DS + IMAP | Not modeled | Not modeled |
Dependencies (and honest gaps)
Full operation requires:
car-secrets— where credentials and tokens livecar-accounts— to know which account a call is bound tocar-permissions— to preflight OS consent before the side effect
The macOS Calendar, Contacts, Mail account discovery, and Messages account discovery paths are implemented. Backends still return available:false with an explicit reason when macOS denies TCC access or an OS service is not configured.
Mail read surface
| Capability | macOS (Mail.app / JXA) | Microsoft Graph |
|---|---|---|
list_accounts |
Mail.accounts() |
the signed-in M365 account |
list_inbox |
per-account unread/total | /me/mailFolders/inbox |
list_mailboxes |
recursive mailboxes() walk |
/me/mailFolders |
list_messages |
bulk array property gets on one mailbox | /me/mailFolders/{id}/messages |
message_body |
content() of one message |
/me/messages/{id}?$select=body |
send |
OutgoingMessage |
/me/sendMail |
list_mailboxes / list_messages / message_body closed Parslee-ai/car-releases#84. Before them the read path could only reach INBOX and carried no message id, so a caller whose server-side rules file mail out of INBOX got an empty result that reads as "nothing there" rather than "cannot see there" — and had to shell out to osascript, which splits the macOS Automation TCC identity between CAR-spawned sends and caller-spawned reads.
Two properties worth knowing before extending them:
- Message ids are composite on macOS. A Mail.app numeric message id is only addressable inside one mailbox of one account, so the wire id is
mailapp:<b64url account>:<b64url mailbox path>:<numeric>(Graph ids are justmsgraph:<id>).encode_message_id/decode_message_idare the only supported readers. - The message walk uses bulk array property gets, not a per-message loop. Reading
subject/sender/dateReceived/readStatus/idoff the mailbox'smessagesspecifier is five Apple Events for a mailbox of any size; reading them per message is one Apple Event per message per field, which is what puts a full mailbox scan near the 15s JXA timeout. Per-message reads (recipients, bodies) happen only for the rows that survive thelimit.
Messages chat listing falls back to ~/Library/Messages/chat.db when Messages.app automation cannot enumerate chats. macOS protects that database behind Full Disk Access, which must be granted to the host app by the user; there is no entitlement that can silently grant it.
Where it fits
Surfaced via the WebSocket calendar.*, contacts.*, mail.*, messages.*, notes.*, reminders.*, photos.*, bookmarks.*, files.*, and keychain.* methods. See docs/websocket-protocol.md.