Skip to main content
Glama
Connected-Mate

iMessage Connector

iMessage Connector

Connect a coding agent to iMessage — under rules it cannot talk its way past.

An MCP server for macOS Messages. Your agent can read conversations, search them, send messages, react, and be commanded from your phone. Every one of those is governed by a policy whose layers combine by narrowing: nothing can widen what another layer closed.

you, from a café          your Mac at home
     │                          │
  "!imsgc status"  ──────────► agent (Claude Code / Codex)
     │                          │  reads the repo, runs the tests
  "✅ api: tests (2m 14s)" ◄────┘

Three things people actually want from this:

  • Tell me when it's done. A long build finishes at 23:40; you find out without watching.

  • Work from anywhere. Text the Mac at home; it does the work and answers.

  • A room with the agent in it. A group chat with your team where the agent posts and answers, and a human can reply in the same thread.

Before you install: what this cannot do

Some of it is possible and some of it is not, and the not is worth knowing before you start.

Sending works. tell application "Messages" to send … to chat id … is verified working on macOS 27 (build 26A5388g), including attachments, once Automation is granted. The first Apple Event raises an authorisation dialog, and until someone answers it every call that needs a reply blocks — activate still returns, so the app looks healthy while nothing else responds. That looks exactly like a hang, and it is the single most likely reason iMessage Connector appears broken on a fresh install. imsgc doctor names it.

Two fallbacks exist for when Automation is unavailable: a Shortcut you create once (text only), and driving the Messages window through Accessibility. iMessage Connector probes rather than assuming, and imsgc doctor reports which works on your machine.

These are not possible on any current macOS, and iMessage Connector says so rather than pretending:

  • Group create / rename / add or remove participants. The Messages scripting dictionary's entire vocabulary is send, login, logout. There is no command, on any version.

  • Pinning, and replying in-thread. Both need to point at one specific message. The accessibility tree of the Messages window exposes no individual messages — no static text, no scroll area, no rows — so any implementation would be a guess about pixel positions.

  • Reacting to an older message. ⌘T targets the newest incoming message and nothing else. Reacting to the newest is supported; anything before it is not. Reactions have never had a scripting API, which is why this needs Accessibility and stays behind ui_automation.

The projects that go further do it by injecting a helper into Messages.app to reach private IMCore APIs — BlueBubbles is the best-known. That buys edit, unsend, typing indicators and arbitrary reactions, at the price of disabling System Integrity Protection; and on macOS 26 library validation blocks the injection anyway (open issue). iMessage Connector deliberately stays on supported interfaces.

Reading is unaffected by all of this: it goes straight to the database and is complete.

Related MCP server: imessage-mcp

Install

Requires macOS and Node 22.5 or later. No native modules, no compiler.

npx imessage-connector --help     # or: npm i -g imessage-connector
imsgc init               # writes ~/.imessage-connector/config.toml
imsgc doctor             # checks permissions and backends, tells you what to fix

Claude Code

claude mcp add imessage-connector -- npx -y imessage-connector

Codex — in ~/.codex/config.toml:

[mcp_servers.imessage-connector]
command = "npx"
args = ["-y", "imessage-connector"]

Permissions. Reading needs Full Disk Access for the program that runs the server — for a terminal agent that is the terminal application (Terminal, iTerm2, Ghostty…), not node. Interface automation additionally needs Accessibility. imsgc doctor names the exact setting for whatever is missing.

The policy

One file, ~/.imessage-connector/config.toml, plus optional layers. imsgc init writes a commented starting point.

mode = "interactive"        # off | read | notify | interactive | full
ui_automation = false

[chats]
allow = ["Ship Room", "+33600000000"]

[content]
max_length = 2000
require_marker = true
marker = "🤖 "
deny_patterns = ["(?i)-----BEGIN [A-Z ]*PRIVATE KEY-----", "sk-[A-Za-z0-9_-]{20,}"]

[rate]
per_chat_per_minute = 3
global_per_hour = 60

[schedule]
timezone = "Europe/Paris"
quiet_hours = ["22:00-08:00"]

[approval]
mode = "first_time"

[remote]
commanders = ["+33600000000"]
required_prefixes = ["!imsg"]

Layers narrow; they never widen

Four sources combine: built-in defaults, ~/.imessage-connector/config.toml, a project's .imessage-connector/config.toml, [chat."…"] sections, and a sealed policy. They are merged with an operation that is commutative, associative and idempotent — the result is at least as restrictive as every input, on every field.

That is a stronger claim than "the last layer wins":

There is no ordering of layers to get wrong, because order cannot change the result.

A repository's config cannot out-rank yours. A per-chat section cannot re-open what another layer closed. A layer injected by an agent can only ever take capability away. Limits take their lowest value, allow-lists intersect, deny-lists union, and "this is required" stays required. It is checked by property tests over generated policies, not by inspection.

# In the repo, checked in: this project may only ever notify, never read.
mode = "notify"

# Per-conversation, anywhere:
[chat."Famille*"]
mode = "off"

Sealing

Every config file is writable by the agent — it runs as the same user. Narrowing alone protects against a confused agent, not a determined one: it could rewrite config.toml and restart.

~/.imessage-connector/policy.sealed.toml is pinned by a SHA-256 in a separate lock file. Any change to it, a missing lock, or a missing sealed file all mean lockdown — every capability refused, including reads — until a human re-seals:

imsgc seal      # refuses unless run from a real terminal

The MCP server exposes no tool that writes configuration, and seal will not run without a TTY. That combination is what an agent cannot get around.

Also: imsgc pause refuses everything immediately, and ~/.imessage-connector/PAUSE does the same if you would rather touch a file.

Tools

tool

capability

notes

imsg_chats

chat.list

conversations, newest first

imsg_read

chat.read

reactions folded in; removed ones are not shown

imsg_search

chat.search

reports how much it examined and whether it stopped early

imsg_handles

handle.list

imsg_attachment

attachment.read

inside the attachments directory only, size-capped

imsg_send

message.send

draft-then-confirm when approval is on

imsg_send_file

message.send_attachment

needs the AppleScript backend

imsg_react

tapback.add

newest incoming message only; needs ui_automation

imsg_bind / imsg_unbind

bind.create / bind.remove

addressing, not permission

imsg_status

bind.list

what is permitted, and which layer decided

imsg_wait_reply

remote.receive

blocks until someone answers, or the timeout

imsg_notify

message.send

completion notice, same policy as any message

A tool cannot be registered without declaring its capability and how to derive the target conversation from its arguments, and the server builds its tool list from that registry. There is no path from a client request to an effect on the world that skips the policy check.

Text me when the job is done

Bind a conversation once, then add the hook:

// ~/.claude/settings.json
{ "hooks": { "Stop": [ { "matcher": "", "hooks": [
  { "type": "command", "command": "imsgc notify --outcome done --task \"$CLAUDE_LAST_USER_MESSAGE\"" }
] } ] } }

imsgc notify goes through the same policy as any other message — including quiet hours, which is the point: a job that finishes at 03:00 is exactly when you do not want a text.

Being commanded from your phone

Four gates, all of which must pass before an inbound message counts as an instruction: the conversation is bound for control, the sender is on the commander list, the message carries every required prefix, and in a group it mentions the agent. Then the verb must be on the allow-list.

A message that fails any gate is still readable by the agent — nothing can stop a model reading text put in front of it — it simply is not an instruction. Each rejection names the gate it failed, so a command that did nothing can be explained.

Approving from your phone

When the policy asks for approval, a send returns a draft with a token instead of going out. The token is pinned to the exact text and the exact conversation, so an agent cannot get "deploying to staging" approved and then send "deploying to production".

The agent can also ask in the conversation itself and wait for a 👍 — a reaction is attached to one specific message, so a bare "yes" cannot be misread as answering a different pending question. 👎 rejects immediately instead of waiting out the deadline.

What iMessage Connector records

Every decision goes to ~/.imessage-connector/audit.jsonl, one line each, chained to the previous entry's hash. Deleting the one message the agent should not have sent breaks every hash after it, and imsgc audit says exactly where.

Message bodies are never written to the log. A record of everything the agent ever sent would be a second plaintext copy of your private conversations. Length and a short hash are enough to answer "was this the message I saw".

How it reads messages

On modern macOS, about 97 % of rows in chat.db have message.text set to NULL. The real content lives in attributedBody, a NeXTSTEP streamtyped archive. iMessage Connector ships a decoder for it, validated against 2242 real messages: zero decode failures, and an exact match on every row where Apple also back-filled text.

Reads open the database read-only, falling back to a snapshot copy — including the -wal, without which the newest messages are missing — when SQLite refuses the live file. Optional columns are probed at startup rather than pinned to a macOS version.

Documentation

Licence

MIT.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    A local MCP server that enables reading iMessage conversations and sending new messages through Claude Desktop. It provides secure, read-only access to your Mac's iMessage database and AppleScript-based message sending capabilities.
    Last updated
    6
    MIT
  • A
    license
    -
    quality
    D
    maintenance
    Enables reading and sending iMessages on macOS through MCP, with tools for managing chats, messages, and attachments via AI agents.
    Last updated
    MIT

View all related MCP servers

Related MCP Connectors

  • Remote MCP server for The Colony — a social network for AI agents (posts, DMs, search, marketplace).

  • Let ChatGPT, Claude & Cursor use your Mac: email, calendar, iMessage, Teams, files. Local, free.

  • Push notifications for AI agents - send instant iPhone notifications from any MCP client.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Connected-Mate/imessage-connector'

If you have feedback or need assistance with the MCP directory API, please join our Discord server