Skip to content

Add secure macOS iMessage plugin - #71

Open
maximedegreve wants to merge 4 commits into
mainfrom
maximedegreve-add-imessage-plugin
Open

Add secure macOS iMessage plugin#71
maximedegreve wants to merge 4 commits into
mainfrom
maximedegreve-add-imessage-plugin

Conversation

@maximedegreve

@maximedegrevemaximedegreve commented Aug 12, 2026

Copy link
Copy Markdown

Summary

  • add a local-only macOS iMessage MCP server with read, search, and text-reply tools
  • register the plugin in the built-in marketplace and add setup/access skills
  • add focused coverage for authorization, modern self-chat identity modeling, spoofable service handling, typed message decoding, AppleScript safety, MCP behavior, and secure state writes

Security and permissions

  • opens ~/Library/Messages/chat.db with SQLite read-only and query-only modes
  • sends through Messages.app with a fixed AppleScript; message text and chat IDs are passed as argv rather than interpolated into source
  • identifies self from authenticated iMessage accounts plus local destination identities confirmed on both incoming and outgoing iMessages; ordinary recipients, one-sided values, short codes, and SMS/RCS data cannot become self
  • denies all other direct senders by default and requires explicit direct allowlists
  • requires an exact participant snapshot for each allowed group and fails closed when membership changes
  • disables SMS/MMS/RCS by default because sender IDs can be spoofed; enabling requires an explicit risk acknowledgement
  • stores access policy atomically with 0600 file and 0700 directory permissions under a Copilot-specific Application Support path
  • treats message content as untrusted input, does not poll in the background, does not expose an external server, and supports outbound text only

Users must grant Full Disk Access to the application running Copilot CLI for reads and Automation permission to control Messages.app for sends.

Upstream attribution

The security design and typedstream parser are derived from Anthropic's Apache-2.0 iMessage plugin at anthropics/claude-plugins-official commit c54b5608d9be1910de9a5b91c2d15bf6673b9c35. The plugin includes the upstream Apache License 2.0 copyright notice and a modification notice, uses Copilot-specific paths/configuration, and removes upstream outbound branding.

Validation

  • PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s plugins/imessage/tests -v (16 tests)
  • privacy-safe live validation against the local Messages database: exactly one direct iMessage self-chat authorized; no groups, SMS/RCS, or ordinary direct chats authorized
  • MCP stdio initialize, tools/list, imessage_status, and imessage_chats smoke tests
  • Python AST and JSON manifest parsing
  • copilot --plugin-dir ./plugins/imessage plugin list
  • secure configuration CLI permissions and SMS/RCS acknowledgement checks
  • git diff --check

Caveats

  • requires macOS, Messages.app, and Python 3.10+
  • message search covers the plain message.text column; newer binary attributedBody content is decoded for history on a best-effort basis but is not searchable
  • incoming messages do not proactively initiate Copilot sessions; access occurs only through explicit MCP tool calls

maximedegreveand others added 2 commits August 12, 2026 15:07
Add a macOS-only local MCP server with deny-by-default access controls, read-only Messages history, safe AppleScript sends, marketplace registration, documentation, and focused tests.\n\nAdapt the security model from Anthropic's Apache-2.0 iMessage plugin with attribution and Copilot-specific state and branding.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI balanced review requested due to automatic review settings August 12, 2026 14:08

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a local macOS iMessage MCP plugin with access controls, configuration workflows, documentation, and tests.

Changes:

  • Implements local message reading, search, and replies.
  • Adds secure policy configuration and Copilot skills.
  • Registers, documents, licenses, and tests the plugin.
Show a summary per file
FileDescription
README.mdDocuments MCP support and licensing.
.github/plugin/marketplace.jsonRegisters the iMessage plugin.
plugins/imessage/.gitignoreExcludes Python cache files.
plugins/imessage/.mcp.jsonConfigures the MCP server.
plugins/imessage/LICENSEAdds Apache 2.0 license.
plugins/imessage/NOTICERecords upstream attribution.
plugins/imessage/README.mdDocuments installation and security.
plugins/imessage/plugin.jsonDefines plugin metadata.
plugins/imessage/server/__init__.pyInitializes the server package.
plugins/imessage/server/imessage.pyImplements storage, authorization, queries, and sending.
plugins/imessage/server/imessage_mcp.pyImplements MCP and configuration CLI.
plugins/imessage/skills/imessage-configure/SKILL.mdGuides secure configuration.
plugins/imessage/skills/imessage-messaging/SKILL.mdGuides safe messaging workflows.
plugins/imessage/tests/test_imessage.pyTests security and MCP behavior.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (1)

plugins/imessage/server/imessage.py:503

  • Search has the same service-boundary gap: authorized chat IDs scope the query, but matching rows are never filtered by their resolved m.service/handle service. A spoofable SMS/RCS row joined to an otherwise authorized chat can therefore appear while SMS/RCS is disabled; pass the effective service policy into this query and filter before ordering/limiting.
 WHERE cmj.chat_id IN ({placeholders})
AND instr(lower(COALESCE(m.text, '')), lower(?)) > 0
  • Files reviewed: 14/14 changed files
  • Comments generated: 6
  • Review effort level: Balanced

Comment on lines +716 to +719
yield remaining[:split]
remaining = remaining[split:]
if remaining.startswith("\n") or remaining.startswith(" "):
remaining = remaining[1:]
Comment on lines +203 to +208
if method == "initialize":
requested = request.get("params", {}).get(
"protocolVersion", "2025-06-18"
)
result = {
"protocolVersion": requested,
Comment on lines +338 to +340
def _policy_update(store: ConfigStore, **changes: Any) -> AccessPolicy:
policy = replace(store.load(), **changes)
store.save(policy)
Comment on lines +638 to +642
return [
chat.public_dict()
for chat in self.database.list_chats(scan_limit=None)
if self.chat_allowed(chat, policy)
][:limit]
Comment on lines +321 to +331
try:
request = json.loads(raw_line)
if not isinstance(request, dict):
raise ValueError("JSON-RPC message must be an object.")
response = server.dispatch(request)
except (json.JSONDecodeError, ValueError) as error:
response = {
"jsonrpc": "2.0",
"id": None,
"error": {"code": -32700, "message": str(error)},
}
JOIN chat_message_join AS cmj ON cmj.message_id = m.ROWID
JOIN chat AS c ON c.ROWID = cmj.chat_id
LEFT JOIN handle AS h ON h.ROWID = m.handle_id
WHERE cmj.chat_id = ?
maximedegreveand others added 2 commits August 12, 2026 15:27
Report privacy-safe authorized and self-chat counts from the status tool, explain the fail-closed empty state, and cover live owner-alias reloads without an MCP restart.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Recognize local destination identities only when they occur on both incoming and outgoing authenticated iMessages for a trusted account. This supports modern Messages self-chats while excluding recipients, one-sided values, short codes, and SMS/RCS data.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@maximedegreve