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.dbis read-only. madrid never writes to it.- Real sends need
MADRID_ALLOW_REAL_SEND=1plus an explicit--realflag. servebinds127.0.0.1only.
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.