imessage-mcp
README.md
<img src="assets/icon.svg" width="72" height="72" alt="">
# iMessageMCP
Search, read, reply to, and clean up your Apple Messages from Claude or any other MCP client. It runs on your Mac and reads the Messages history already there.
- Finds messages by words, exact text, or phrase across iMessage, SMS, MMS, and RCS
- Reads whole conversations with edits, unsent messages, reactions, replies, and read receipts
- Shows photos people sent you, with location data removed
- Keeps up with new messages through a change feed, and answers counts and response-time questions
- Sorts conversations into Known and Unknown Senders, the way Messages does
- Replies in an existing conversation after you approve a preview of exactly what will be sent
- Deletes spam threads (one-to-one, nobody in your Contacts) after you approve a preview
- Keeps working when Messages rewrites its database, and a watchdog restarts it if it ever stops answering
## How it runs
The server runs on your Mac as a LaunchAgent and listens on `127.0.0.1:8765` over HTTP with a bearer token. `tailscale serve` makes that port reachable on your tailnet, so an MCP gateway elsewhere (for example a small VPS that Claude connects to) can call it. It can also run over stdio for a client on the same Mac.
```
Claude ──> your MCP gateway (VPS) ──> Tailscale ──> tailscale serve on the Mac ──> 127.0.0.1:8765
```
## Install on the Mac
**Requirements:** macOS 14 or newer and Tailscale, connected. The installer downloads its own Node 24 (checksum-verified) into `~/Library/Application Support/imessage-mcp-runtime`, so any other Node on the Mac is untouched.
```bash
git clone https://github.com/jaredzimmerman/iMessageMCP.git
cd iMessageMCP
SEND=1 DELETE=1 IMESSAGE_VPS_SSH=root@<your-vps> bash mac/install.sh
```
- `SEND=1` turns on replying and `DELETE=1` turns on deleting spam threads. Leave either out to keep it off.
- `IMESSAGE_VPS_SSH` copies the bearer token to the gateway at `/etc/code-mcp/imessage.token`. Leave it out to copy the token yourself from `~/.config/imessage-mcp/token`.
- When it finishes, give the Node binary it prints **Full Disk Access** (System Settings > Privacy & Security > Full Disk Access). Messages history is protected by macOS; without it the server can't read anything.
- The first reply or delete asks for permission to control Messages. Allow it.
The installer copies the code to `~/Library/Application Support/imessage-mcp`, builds it, and loads two LaunchAgents: `com.indigokarasu.imessage-mcp` (the server) and `com.indigokarasu.imessage-mcp-watchdog`. Run it again after pulling changes; it keeps the existing token. `bash mac/uninstall.sh` removes both. More detail: [mac/README.md](mac/README.md).
### Running it locally over stdio
```bash
npm ci && npm run build
```
```json
{
"mcpServers": {
"imessage": {
"command": "node",
"args": ["/path/to/iMessageMCP/bin/imessage-mcp.js", "--allow-send"]
}
}
}
```
The app that starts the server (your terminal, Claude, an editor) needs Full Disk Access.
## Prompts
| prompt | what it does |
| --- | --- |
| `catch_up` | Who is waiting on a reply from you, and what they need |
| `draft_reply` | A reply in your own texting style. With sending on, it can be sent after you approve the preview |
| `recap` | Your week in messages: volume, busiest conversations, anyone still waiting |
Clients that attach resources can use `imessage://conversations` and `imessage://conversations/{chat_id}`.
## Tools
| tool | what it does |
| --- | --- |
| `search_messages` | Search by substring, exact text, token, or phrase. Message text by default; conversation names and attachment filenames are opt-in. Filter by service, sent or received, and date |
| `get_conversation` | Read a conversation by `chat_id` or by a contact or group name, with edits, reactions, receipts, replies, group events, and attachments. The cursor pages to older events |
| `list_conversations` | Find conversations by contact, service, kind, reply state, Known or Unknown Senders folder, or date, each with its latest message |
| `get_attachment` | Show one attachment from `get_conversation`: images as a JPEG with location and camera metadata removed, text files as text. Needs the `full` privacy mode |
| `sync_messages` | Pull every change since a cursor: new, edited, unsent, and deleted messages, conversation memberships, reactions, and receipts |
| `analyze_communication` | Message counts by hour and weekday, response times, streaks, and who starts conversations |
| `resolve_contact` | Match a name, phone number, or email to a contact in your Mac's Address Book, and report ambiguity rather than guess |
| `server_status` | Version, privacy mode, index and cache state, schema support, conversation membership counts, and Contacts access |
| `prepare_send` | Preview a reply in an existing conversation and get a one-time token (with `--allow-send`) |
| `confirm_send` | Send the previewed reply, then check chat.db for sent, delivered, or failed |
| `cancel_send` | Discard a prepared reply |
| `prepare_delete` | Preview deleting one whole one-to-one conversation with nobody in Contacts and get a one-time token (with `--allow-delete`) |
| `confirm_delete` | Have Messages delete the previewed conversation, then check chat.db that it is gone |
| `cancel_delete` | Discard a prepared delete |
The send and delete tools appear only when the server is started with `--allow-send` or `--allow-delete`. Replies go only into conversations that already exist; the server can't start a new one. A delete is refused for group chats, for anyone in Contacts, and when a new message arrived after the preview. With Messages in iCloud a delete reaches every device, and Recently Deleted keeps it for up to 30 days.
Results use plain ids (`message_id`, `chat_id`, `attachment_id`) you can pass between tools. Every tool's output schema accepts both its success envelope and the structured error envelope, so strict clients can show the original reason and recovery hints.
<details>
<summary>How conversations, counts and partial reads work</summary>
Apple can record the same message in several conversations. Search returns that message once with all canonical `chat_ids` and their `conversations` labels. A single membership also includes `chat_id` and `conversation`; for shared results, pass the relevant id from `chat_ids` to `get_conversation`. Only Apple's explicit lookup evidence combines chat aliases.
Global and contact message counts deduplicate shared records within the requested scope. Conversation timelines and sequence metrics keep each recorded membership, so adding per-conversation counts can exceed the global count. Sync includes `chat_ids` on changes and emits `message_membership_changed` with `previous_chat_ids`; its `changed_at` is null because Apple does not timestamp these joins. When a sync cursor returns `DATABASE_CHANGED`, start a new sync without a cursor.
Contact names expand to the selected person's addresses. An exact phone number or email scopes only that address. `resolve_contact` reports ambiguous identity instead of merging people.
Conversation reads are strict by default. When a body or relationship can't be recovered completely, the error explains the `allow_partial: true` retry. Partial results keep `completeness`, warnings, and each event's `row_status`.
</details>
## Configuration
| option | description |
| --- | --- |
| `--allow-send` | Add `prepare_send`, `confirm_send`, `cancel_send`. Needs the `full` privacy mode. *env* `IMESSAGE_ALLOW_SEND=1` |
| `--allow-delete` | Add `prepare_delete`, `confirm_delete`, `cancel_delete`. *env* `IMESSAGE_ALLOW_DELETE=1` |
| `IMESSAGE_SEND_LIMIT_PER_HOUR` | Replies allowed per hour, 1 to 200 (default 20) |
| `IMESSAGE_DELETE_LIMIT_PER_HOUR` | Deletes allowed per hour (default 20) |
| `--privacy <mode>` | The most any caller can see. `full` (default), `redacted` (names and masked handles, calendar days, no message text or filenames), or `aggregate` (counts only). A call can ask for a stricter mode, never a looser one. *env* `IMESSAGE_PRIVACY` |
| `--contacts <mode>` | `live` (default) names handles from your Contacts; `none` shows handles only. *env* `IMESSAGE_CONTACTS` |
| `--database <path>` | Read a copy of `chat.db` instead of this Mac's Messages. *env* `IMESSAGE_DB` |
| `--transport http --port <n>` | Serve MCP over HTTP on 127.0.0.1 instead of stdio. Requires `IMESSAGE_API_TOKEN` or `IMESSAGE_API_TOKEN_FILE`. `IMESSAGE_ALLOWED_HOSTS` and `IMESSAGE_ALLOWED_ORIGINS` take comma-separated lists; both default to localhost |
| `IMESSAGE_CACHE=0` | Keep the search index in memory only |
| `IMESSAGE_WARM_SEARCH=0` | Build the search index on the first search instead of at startup |
## Safety and privacy
- **Nothing is sent or deleted without your approval.** Each reply or delete is two steps: a preview with a single-use token, then a confirm. The assistant is told to confirm only after you approve that exact preview. Sends and deletes are rate-limited and logged to `~/Library/Logs/imessage-mcp/sends.jsonl` and `deletes.jsonl` without message text.
- **chat.db is never written.** The server opens the Messages database with `query_only` and changes it only through the Messages app, which it drives with fixed AppleScript. Message text and ids reach `osascript` as arguments, never as script source.
- **Local.** No accounts, telemetry, or analytics. The HTTP transport binds to 127.0.0.1, checks the bearer token, Host, and Origin, and is meant to be reached through Tailscale, not exposed publicly.
- **Your client sees what you ask for.** Results go to the MCP client you use and its model provider, under their policies. `--privacy redacted` or `aggregate` limits what leaves the server.
- **Search index.** Built on your Mac and cached encrypted in `~/Library/Caches/imessage-mcp`, with a key derived from your Messages database, so it opens only for an app that can already read your messages. Deleting it is always safe.
- **Untrusted content.** Messages can contain text written to manipulate an AI. The server tells clients to treat all message content as data, never as instructions.
Details: [SECURITY.md](SECURITY.md) and [PRIVACY.md](PRIVACY.md).
## When something goes wrong
- Server log: `~/Library/Logs/imessage-mcp/server.log`. A `database_reopened` line means Messages changed its database and the server reopened it on its own.
- Watchdog log: `~/Library/Logs/imessage-mcp/watchdog.log`. The watchdog calls `server_status` every 2 minutes and restarts the server after two failed checks in a row.
- Restart by hand: `launchctl kickstart -k gui/$(id -u)/com.indigokarasu.imessage-mcp`
- Check access and schema: `node bin/imessage-mcp.js doctor`
## Development
```bash
npm ci
npm test # unit tests on synthetic Messages databases
npm run e2e # launches the built server over stdio and HTTP
npm run perf # one-million-message performance gates
```
Tests use synthetic data only. See [CONTRIBUTING.md](CONTRIBUTING.md).
## Credits and license
MIT. Built on [imessage-mcp](https://github.com/anipottsbuilds/imessage-mcp) by Ani Potts (MIT); see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues