iMessage bridge for Claude Code. Reads ~/Library/Messages/chat.db directly for history and new-message polling; sends via AppleScript to Messages.app. macOS only. Built-in access control: inbound messages are gated by an allowlist (default: self-chat only), outbound sends are scoped to the same allowlist. The /imessage:access skill manages allowlists and policy. Requires Full Disk Access and Automation TCC grants — both prompted by macOS on first use. Ships full source — server.ts runs locally via bun, started by the .mcp.json command.
6.6 KiB
iMessage — Access & Delivery
This channel reads your Messages database (~/Library/Messages/chat.db) directly. Every text to this Mac — from any contact, in any chat — reaches the gate. Access control selects which conversations the assistant should see.
Texting yourself always works. Self-chat bypasses the gate with no setup: the server learns your own addresses at boot and lets them through unconditionally. For other senders, the default policy is allowlist: nothing passes until you add the handle with /imessage:access allow <address>.
All state lives in ~/.claude/channels/imessage/access.json. The /imessage:access skill commands edit this file; the server re-reads it on every inbound message, so changes take effect without a restart. Set IMESSAGE_ACCESS_MODE=static to pin config to what was on disk at boot.
At a glance
| Default policy | allowlist |
| Self-chat | Bypasses the gate; no config needed |
| Sender ID | Handle address: +15551234567 or someone@icloud.com |
| Group key | Chat GUID: iMessage;+;chat… |
| Mention quirk | Regex only; iMessage has no structured @mentions |
| Config file | ~/.claude/channels/imessage/access.json |
Self-chat
Open Messages on any device signed into your Apple ID, start a conversation with yourself, and text. It reaches the assistant.
The server identifies your addresses at boot by reading message.account and chat.last_addressed_handle from chat.db. Messages from those addresses skip the gate entirely. To distinguish your input from its own replies — both appear in chat.db as from-me — it maintains a 15-second window of recently sent text and matches against it.
DM policies
dmPolicy controls how texts from senders other than you, not on the allowlist, are handled.
| Policy | Behavior |
|---|---|
allowlist (default) |
Drop silently. Safe default for a personal account. |
pairing |
Reply with a pairing code, drop the message. Every contact who texts this Mac will receive one; only use this if very few people have the number. |
disabled |
Drop everything except self-chat, which always bypasses. |
/imessage:access policy pairing
Handle addresses
iMessage identifies senders by handle addresses: either a phone number in +country format or the Apple ID email. The form matches what appears at the top of the conversation in Messages.app.
| Contact shown as | Handle address |
|---|---|
| Phone number | +15551234567 (keep the +, no spaces or dashes) |
someone@icloud.com |
If the exact form is unclear, check the chat_messages tool output or (under pairing policy) the pending entry in access.json.
/imessage:access allow +15551234567
/imessage:access allow friend@icloud.com
/imessage:access remove +15551234567
Groups
Groups are off by default. Opt each one in individually, keyed on the chat GUID.
Chat GUIDs look like iMessage;+;chat123456789012345678. They're not exposed in Messages.app; get them from the chat_id field in chat_messages tool output or from the server's stderr log when it drops a group message.
/imessage:access group add "iMessage;+;chat123456789012345678"
Quote the GUID; the semicolons are shell metacharacters.
iMessage has no structured @mentions. The @Name highlight in group chats is presentational styling — nothing in chat.db marks it as a mention. With the default requireMention: true, the only trigger is a mentionPatterns regex match. Set at least one pattern before opting a group in, or no message will ever match.
/imessage:access set mentionPatterns '["^claude\\b", "@assistant"]'
Pass --no-mention to process every message in the group, or --allow addr1,addr2 to restrict which members can trigger it.
/imessage:access group add "iMessage;+;chat123456789012345678" --no-mention
/imessage:access group add "iMessage;+;chat123456789012345678" --allow +15551234567,friend@icloud.com
/imessage:access group rm "iMessage;+;chat123456789012345678"
Delivery
AppleScript can send messages but cannot tapback, edit, or thread-reply; those require private API. Delivery config is correspondingly limited. Set with /imessage:access set <key> <value>.
textChunkLimit sets the split threshold. iMessage has no length cap; chunking is for readability. Defaults to 10000.
chunkMode chooses the split strategy: length cuts exactly at the limit; newline prefers paragraph boundaries.
There is no ackReaction or replyToMode on this channel.
Skill reference
| Command | Effect |
|---|---|
/imessage:access |
Print current state: policy, allowlist, pending pairings, enabled groups. |
/imessage:access pair a4f91c |
Approve a pending code (relevant only under pairing policy). |
/imessage:access deny a4f91c |
Discard a pending code. |
/imessage:access allow +15551234567 |
Add a handle. The primary entry point under the default allowlist policy. |
/imessage:access remove +15551234567 |
Remove from the allowlist. |
/imessage:access policy pairing |
Set dmPolicy. Values: pairing, allowlist, disabled. |
/imessage:access group add "iMessage;+;chat…" |
Enable a group. Quote the GUID. Flags: --no-mention, --allow a,b. |
/imessage:access group rm "iMessage;+;chat…" |
Disable a group. |
/imessage:access set textChunkLimit 5000 |
Set a config key: textChunkLimit, chunkMode, mentionPatterns. |
Config file
~/.claude/channels/imessage/access.json. Absent file is equivalent to allowlist policy with empty lists: only self-chat passes.
{
// Handling for texts from senders not in allowFrom.
// Defaults to allowlist since this reads your personal chat.db.
// Self-chat bypasses regardless.
"dmPolicy": "allowlist",
// Handle addresses allowed to reach the assistant.
"allowFrom": ["+15551234567", "friend@icloud.com"],
// Group chats the assistant participates in. Empty object = DM-only.
"groups": {
"iMessage;+;chat123456789012345678": {
// true: respond only on mentionPatterns match.
// iMessage has no structured @mentions; regex is the only trigger.
"requireMention": true,
// Restrict triggers to these senders. Empty = any member (subject to requireMention).
"allowFrom": []
}
},
// Case-insensitive regexes that count as a mention.
// Required for groups with requireMention, since there are no structured mentions.
"mentionPatterns": ["^claude\\b", "@assistant"],
// Split threshold. No length cap; this is about readability.
"textChunkLimit": 10000,
// length = cut at limit. newline = prefer paragraph boundaries.
"chunkMode": "newline"
}