Apple Messages MCP
Summary: Use this local MCP server to read and optionally send Apple Messages/iMessage/SMS from your Mac's chat.db, scoped and read-only by default.
Check status: readability, FDA missing, macOS hints, send/redact flags, snapshot size/age, scoped vs unscoped (
messages_status).List recent chats, with optional query filter and limit (
messages_list_chats).Read a thread by
chat_id,guid, or handle, with pagination (before), date filters, optional tapbacks, and attachment metadata (messages_get_thread).Search message text case-insensitively across chats or one chat, using SQL LIKE plus bounded Tahoe decode; reports
truncated/scanned(messages_search).Send a real text only if
ENABLE_SEND=1; supportsdry_runand requiresconfirm: true(messages_send).Run a separate watcher process to poll
chat.db/WAL and emit new-message JSON lines, optionally invoking a wake hook (MESSAGES_WAKE_HOOK).Respect scoping: without
MESSAGES_SCOPEorMESSAGES_ALLOW_UNSCOPED=1, list/search/thread/send/watch returnSCOPE.Does not dump
chat.dbto a server; reads stay on localhost.
Integrates with Apple's Messages.app on macOS, providing read access to the local chat database (iMessage + SMS) and optional send capability through AppleScript, gated behind Full Disk Access and Automation permissions.
Allows reading iMessage and SMS conversations from the local Messages.app chat.db on a Mac, including listing chats, retrieving threads, searching message content, and optionally sending replies via AppleScript. Includes a watcher process for polling new messages to wake an agent.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Apple Messages MCP search my iMessage history for dinner plans"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
apple-messages-mcp
Local MCP server that lets a Cursor or Grok Bot agent read your Messages.app database on the user's Mac (iMessage + SMS), and optionally send a reply through AppleScript.
There is no cloud inbox and no extra phone number. Reads stay on localhost. Read-only by default. Scoped shut by default. With no MESSAGES_SCOPE and no MESSAGES_ALLOW_UNSCOPED=1, list/search/thread/send/watch return SCOPE. A personal bot that wants the whole inbox sets one line: MESSAGES_ALLOW_UNSCOPED=1.
See DESIGN.md for schema notes, the threat model, Phase 2 watcher hook, and agent instructions.
Requirements
macOS with Messages.app (the live
chat.dbpath)Node.js 22 (engines:
>=22 <23)Full Disk Access for the app that launches this server (Cursor, or Terminal if you test from a shell)
Automation permission for Messages only if you turn send on
Linux CI uses a tiny fixture SQLite database. You do not need Messages.app to run npm test.
Related MCP server: iMessage MCP Server
Install (pinned 0.2.0)
This is meant to be cloned, built, and launched as node /ABS/PATH/dist/index.js — not npx'd unpinned from a random machine. Requires Node 22. bin is dist/index.js.
git clone https://github.com/dh-repo/apple-messages-mcp.git
cd apple-messages-mcp
# stay on a commit whose package.json version you intend to run (0.2.0+)
npm install
npm test
npm run build
node dist/index.jsThe server speaks MCP over stdio (leave this to Cursor; do not write logs to stdout). The example Cursor config stays node /ABS/PATH/TO/apple-messages-mcp/dist/index.js.
Dev: npx tsx src/index.ts. Same binary is a local CLI for the smoke checklist and the Phase 2 watcher:
npx tsx src/index.ts status
npx tsx src/index.ts list
npx tsx src/index.ts thread --chat-id 12 --limit 20
npx tsx src/index.ts search snacks
npx tsx src/index.ts watch --interval 3000Full Disk Access
Open System Settings → Privacy & Security → Full Disk Access.
Enable Cursor (and Terminal if you smoke-test here).
Quit and reopen that app. TCC is applied at launch.
If
messages_statusreturnsfda_likely_missing: trueorPERMISSION, this step is not done.
Without FDA, macOS returns EPERM for ~/Library/Messages/chat.db even to the file's owner.
Cursor / Claude Desktop config
Cursor Add MCP server / mcp.json (same shape):
{
"mcpServers": {
"apple-messages": {
"command": "node",
"args": ["/ABS/PATH/TO/apple-messages-mcp/dist/index.js"],
"env": {
"MESSAGES_DB_MODE": "copy",
"ENABLE_SEND": "0",
"REDACT_PREVIEWS": "0"
}
}
}
}A checked-in copy lives at examples/cursor-mcp.json. Point args at your clone.
Claude Desktop: paste the same mcpServers block into claude_desktop_config.json.
Dev without a build: npx tsx src/index.ts.
Environment
Default is scoped shut. Pick one:
"MESSAGES_SCOPE": "Family, 12""MESSAGES_ALLOW_UNSCOPED": "1"Variable | Default | Purpose |
| unset | Comma/semicolon tokens: display name, |
|
|
|
|
| Override for fixtures. |
|
| Copy + WAL replay. |
|
|
|
|
| Bodies become |
| unset | Watcher runs this executable with one JSON event on stdin. |
|
| Watcher includes a redacted preview. |
Tools
Tool | What it does |
| Can we read |
| Recent chats, or only the allowlist if one is set. |
| Messages for a |
| Two-phase substring search (plain |
| Registered only when |
IDs in responses are SQLite ROWIDs: chat_id, message_id, handle_id, attachment_id. Handles are phone numbers and emails, not Contacts names.
Copy this into the agent's MCP instructions: the block in DESIGN.md §8.
Watcher (Phase 2)
Not push, and not part of the MCP stdio stream. A second process polls chat.db (and optionally fs.watchs the WAL) for new ROWIDs — all chats, or only the allowlist if one is set — then writes JSON lines:
npx tsx src/index.ts watch --interval 3000
# {"type":"messages.ready","chat_id":…,"newest_message_id":…}
# {"type":"messages.new","chat_id":…,"count_new":1,"preview":null}Pipe that into your own wake script, or set MESSAGES_WAKE_HOOK to examples/wake-hook.sh. The host should start an agent turn and call messages_get_thread. Do not treat this as a Slack-style channel.
Smoke-test checklist (Mac)
npm testpasses (fixture-only; safe on any machine).Grant FDA, restart Cursor, reload the MCP server.
Call
messages_status.readable: truefda_likely_missing: falseunscoped: falseunless you setMESSAGES_ALLOW_UNSCOPED=1Optional: set
MESSAGES_SCOPEand confirmscope.active: trueandscope.chatsmatches
messages_list_chatswithout scope env returnsSCOPEandscope.candidates.messages_get_threadwith achat_idfrom a scoped or unscoped list returns recent lines. At least sometext_sourcevalues may beattributedBodyon current macOS.text_source: "guess"means the decoder fell back to printable bytes.messages_searchwith a word you know exists returns hits across chats, or empty +truncated: truewhen the Tahoe window missed.messages_sendis absent fromtools/listwhileENABLE_SENDis unset.Optional:
ENABLE_SEND=1, Automation allowed.dry_run: truereturns{ to, chat_id, guid, body }without sending. A real send still needsconfirm: true. Leave send off afterward.npx tsx src/index.ts status(CLI) matches the MCPmessages_statuspayload.npx tsx src/index.ts watch --interval 2000prints onemessages.readyline and, on a new message,messages.newwithout plaintext.
Inspector, if you want a GUI without Cursor:
npx @modelcontextprotocol/inspector npx tsx src/index.tsWhat this is not
Not Inkbox / Linq / a hosted agent number.
Not a push notification service. The Phase 2
watchprocess polls /fs.watchschat.dband wakes the host — see DESIGN.md.Not a way to dump
chat.dbto a server.
License
MIT. See LICENSE.
Available Tools
4 toolsmessages_get_threadGet threadA
Return messages for one chat, oldest-first within the page. No MESSAGES_SCOPE and MESSAGES_ALLOW_UNSCOPED is unset; list/search/thread/send/watch return SCOPE. Identify the chat with chat_id (chat.ROWID), guid (via handle field or chat_id), or a participant handle (phone/email). If omitted and the allowlist has exactly one chat, that chat is used. before paginates: a message_id or ISO-8601 timestamp. from_date / to_date filter on the converted Apple date. Attachment metadata is included; file bytes are not. Tapbacks are omitted unless include_reactions is true.
Inputs: limit, before, handle, chat_id, +3 more.
messages_list_chatsList chatsA
List recent conversations from the local Messages database (iMessage and SMS). No MESSAGES_SCOPE and MESSAGES_ALLOW_UNSCOPED is unset; list/search/thread/send/watch return SCOPE. Returns chat_id (chat.ROWID), guid, chat_identifier, handles (phone/email), and a last-message preview. Prefer guid to remember a thread across launches. Does not return attachment bytes.
Inputs: limit, query.
messages_searchSearch messagesA
Search message text: SQL LIKE on the plain text column, then a bounded decode-scan of empty-text / Tahoe rows. No MESSAGES_SCOPE and MESSAGES_ALLOW_UNSCOPED is unset; list/search/thread/send/watch return SCOPE. Returns truncated:true and scanned when the Tahoe window is exhausted. With no chat_id, searches across all readable chats (or the allowlist). Case-insensitive. Does not search attachment binaries. Does not write FTS into chat.db.
Inputs: query (required), limit, chat_id, to_date, +1 more.
messages_statusMessages statusA
Report whether chat.db is readable, whether Full Disk Access is likely missing, macOS version hints, send/redact flags, snapshot size/age, and whether the inbox is scoped shut. Call this first when anything fails. Does not return message bodies. unscoped is true only when MESSAGES_ALLOW_UNSCOPED=1.
Inputs: no named parameters.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 4 tools
Each tool has a distinct role: messages_status is diagnostic, messages_list_chats enumerates conversations, messages_get_thread reads a single thread, and messages_search finds text. Minor overlap exists since list_chats returns a last-message preview and get_thread/search both return message content, but the intended usage is clear enough to avoid misselection.
All tools share the messages_ prefix and mostly follow a verb_noun pattern (list_chats, get_thread, search). messages_status is a noun-only outlier, and messages_search lacks a noun object, but the prefix convention is consistent and readable.
Four tools is on the lean side but reasonable for a focused, read-oriented Messages server covering diagnostics, listing, reading, and searching. It is slightly thin given lifecycle operations appear to exist elsewhere.
The surface covers discovery, read, and search but exposes no send/redact operations, even though messages_status references send/redact flags and descriptions mention send/watch returning SCOPE. These references imply tools that are not present, leaving a notable gap for a messaging domain.
Maintenance
Related MCP Connectors
MCP connector for iMessage & Contacts via a local Mac agent + Vercel relay
Let AI agents securely read the text messages on your phone.
Explore your Messages SQLite database to browse tables and inspect schemas with ease. Run flexible…
Send and read iMessages from your own Mac and Apple ID — drafts, history and contacts.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables reading, searching, and sending iMessages directly from MCP-compatible clients by accessing the local macOS iMessage database, supporting conversations, attachments, and both individual and group chats.674 npm10MIT
- FlicenseAqualityDmaintenanceEnables reading, searching, and sending iMessages on macOS by accessing the local messages database and utilizing AppleScript. Users can list conversations, search message history, and send messages to individuals or group chats directly through the Model Context Protocol.6-
- AlicenseAqualityBmaintenanceEnables reading the local iMessage database and sending messages through Messages.app on macOS, with both local stdio and remote HTTP access.7MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to read the entire Apple Messages (iMessage/SMS) history on a Mac through a read-only, batched tool that supports listing chats, retrieving transcripts, polling recent messages, and searching message bodies via REST or streamable HTTP MCP.MIT