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.1.1

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.1.1
File Size Uploaded Provenance Actions
madrid-1.1.1.tar.gz 99.4 kB With provenance Details for madrid-1.1.1.tar.gz

Built distribution (wheel)

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

Total release size: 193.6 kB

Release files / madrid-1.1.1.tar.gz

Download URL madrid-1.1.1.tar.gz
Size 99.4 kB
Tags Source
SHA-256 checksum
How to use checksums
f98b3677caf71c4b338c0403e817194e3ece9965a36ce809d5beaa1fc3c37672
BLAKE2b-256 checksum
How to use checksums
16984d6bb69d72a5494db7c9ead0c269b03e81358b4fbacf711702027c7845c5
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.1.1-py3-none-any.whl

Download URL madrid-1.1.1-py3-none-any.whl
Size 94.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0e8025b869ecf6af66549e72bddb9625af7b0e4e25cbf5922fd2a1415891c864
BLAKE2b-256 checksum
How to use checksums
e6efb07287748e3223b04784d122b16fa502a024c0d7c3359a4bbf3057c612c6
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.1.1

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.1.1.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