Skip to main content

madrid

English | 中文

Send and receive iMessage from your agent. Rich text in, rich text out.

madrid reads your local Messages database (read-only, never writes to it) and sends through Messages.app. Your agent gets a clean HTTP API, an MCP server, or a Hermes gateway plugin — whichever fits.

pip install -e .
python3 -m madrid serve --port 8787 --watch

That's it. Your agent can now:

  • Receive iMessages as they arrive (sub-second, kqueue on the database)
  • Reply with formatting — bold, italic, links, tables, code blocks
  • Stream responses word-by-word like a person typing
  • See when someone reads a message
  • React to messages, edit them, unsend them

Why not openclaw/imsg?

imsg is a great Swift CLI. We built madrid because we needed things imsg doesn't do:

imsg madrid
Rich text on receive Plain text only Full decode (bold/italic/links/mentions/effects)
Rich text on send Requires SIP off + dylib injection Pasteboard channel, works out of the box
Markdown → iMessage No Tables and code blocks become inline images
Typewriter streaming No Yes, via the edit protocol
HTTP API No REST + SSE + OpenAPI spec
MCP server No Yes
Hermes integration No Native gateway plugin
Edit/retract events No Frame-by-frame
Read receipts No Live events

imsg is better if you want a native compiled binary, Linux read-only support, or its more polished CLI. Both tools are honest about what they can and can't do.

Heads up: csrutil enable --without debug

Some features (the debugger bridge for typewriter and advanced sends) need you to run this once in recovery mode. It's an official Apple developer exemption that lets a debugger attach to system processes.

You don't need it for anything else in madrid — rich text, HTTP API, MCP, Hermes, typewriter via UI fallback all work without it.

What stays on: filesystem protection, NVRAM, runtime integrity, kext signing. What's exempted: task_for_pid() on Apple binaries.

The risk: malware already on your machine could read process memory. But if malware is running, it can keylog anyway. Only on machines you trust. Revert with csrutil enable in recovery.


What it looks like

Your agent sends Markdown:

## Deploy complete

| Service | Status |
|---------|--------|
| API     | ✅     |
| Worker  | ✅     |

```python
print("hello")
```

The recipient sees in iMessage:

  • "Deploy complete" in bold
  • A formatted table as a clean card image
  • The code block as a syntax-highlighted card image

They reply, your agent gets:

{
  "type": "message",
  "text": "nice, ship it",
  "sender": "+14155551212",
  "chat": {"name": "Alex", "handles": ["+14155551212"]},
  "html": "nice, ship it"
}

Install

pip install madrid

That's it. Requires macOS and Python 3.10+.

Grant Full Disk Access to your terminal (System Settings → Privacy & Security) so madrid can read chat.db.

python3 -m madrid doctor     # verify everything works
python3 -m madrid serve --port 8787 --watch   # start the API

For developers

git clone https://github.com/XXY-CH/madrid.git
cd madrid
pip install -e .

# Run tests (skip real-db tests in CI/containers)
python3 tests/test_smoke.py

# Run the adapter tests
python3 integrations/hermes/test_adapter.py

# Lint
pip install ruff && ruff check madrid/

No build step needed. No dependencies to install. The ObjC helpers in madrid/native/ compile themselves with clang the first time they're used.

Project layout is in the Layout section below. PRs welcome.

Hermes setup

The adapter is already at ~/.hermes/hermes-agent/plugins/platforms/imessage/.

export MADRID_URL=http://127.0.0.1:8787
hermes gateway setup    # pick "iMessage (madrid)"
hermes gateway start

Now anyone who iMessages you gets your agent. It responds with rich text. You can set MADRID_ALLOWED_USERS=+14155551212 to restrict who can talk to it, or MADRID_TYPEWRITER=true to stream responses.

Sending channels

How What Notes
AppleScript Text + files Works everywhere
Pasteboard Rich text (bold/links/fonts) Needs Automation permission
Shortcuts Headless text + attachments Set up once in Shortcuts.app
Debugger bridge Everything Needs csrutil --without debug (see below)

Typewriter

python3 -m madrid typewriter --to "+14155551212" \
    --frames "Generating;;Generating answer;;Generating answer: 42"

The first frame sends normally. Each subsequent frame edits the message, so the recipient watches the text grow. Uses the bridge if available, falls back to UI scripting otherwise. Fifteen-minute window, roughly one edit per second.

Privacy

  • chat.db is read-only. madrid never writes to it.
  • Real sends need MADRID_ALLOW_REAL_SEND=1 plus an explicit --real flag.
  • serve binds 127.0.0.1 only.

Layout

madrid/           Python package (no dependencies)
  serve.py        HTTP + SSE server
  engine.py       Message lifecycle and routing
  rich.py         HTML ⇄ Markdown ⇄ Apple attribute keys
  mddoc.py        Markdown → rich text + PNG cards
  realbox.py      chat.db reader (read-only)
  watch.py        Event daemon (kqueue + polling fallback)
  stats.py        Message/chat/contact statistics
  contacts.py     Handle → contact name resolution
  fastwatch.py    kqueue file-change detection
  bridge.py       Debugger bridge (optional, needs SIP exemption)
  native/         ObjC helpers (compiled with clang on first use)
integrations/hermes/  Hermes gateway plugin
sdk/python/           Python client (stdlib)
sdk/go/               Go client
sdk/rust/             Rust client
sdk/typescript/       TypeScript client

License

Apache-2.0

Metadata

Release files for madrid 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for madrid 1.2.0
File Size Uploaded Provenance Actions
madrid-1.2.0.tar.gz 99.4 kB With provenance Details for madrid-1.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for madrid 1.2.0
File Interpreter ABI Platform Actions
madrid-1.2.0-py3-none-any.whl Python 3 none any Details for madrid-1.2.0-py3-none-any.whl

Total release size: 193.6 kB

Release files / madrid-1.2.0.tar.gz

Download URL madrid-1.2.0.tar.gz
Size 99.4 kB
Tags Source
SHA-256 checksum
How to use checksums
e32d6d243589696ea0e43fce6f1af95f6427238dbd81a54261b9a7b687b0dfac
BLAKE2b-256 checksum
How to use checksums
fee4f1dcafd5027e6baf60076e771bbec356d4396f115fd200e07ac3175060d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / madrid-1.2.0-py3-none-any.whl

Download URL madrid-1.2.0-py3-none-any.whl
Size 94.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
78ffa4275f3befa435f9eee3c7543216549c7fdd7a205cab1bd60cb5ed0b3fbe
BLAKE2b-256 checksum
How to use checksums
427a23d5c17c0d8613ace0fe162d866006616f26234a2b920112d603e2826612
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Security for madrid 1.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for madrid-1.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page