Skip to main content
Glama
nighomni123

imessage-bridge

by nighomni123
README.md
# iMessage Bridge for DeepSeek Harness

A **dependency-free MCP stdio server** that bridges agent harnesses to iMessage via BlueBubbles, enabling human-in-the-loop conversations through your Android device. It speaks plain MCP stdio (newline-delimited JSON-RPC), so it mounts under any MCP client — the examples below use DeepSeek Harness (DSH), where it runs in production daily.

## Architecture

```
DSH Agent (Mac) ──MCP stdio──▶ imessage-bridge ──REST API──▶ BlueBubbles Server (Mac)
                                                                      │
                                                                      ▼
                                                              iMessage Network
                                                                      │
                                                                      ▼
                                              Android (BlueMessage/BlueBubbles app)
```

**Flow:**
1. DSH agent calls `imessage_send` → bridge sends iMessage via BlueBubbles
2. Message appears in your Android iMessage app (BlueMessage/BlueBubbles client)
3. You reply on the **same chat** on Android
4. Bridge's `imessage_receive` polls for new messages and returns your replies
5. DSH agent continues with your response

## What it looks like in action

![A real exchange over iMessage relayed by the bridge: the agent checks in mid-run, a one-line reply unblocks it, the work continues](demo.jpg)

This bridge is the human-in-the-loop channel my agent fleet reports through —
research, code and status updates arrive on the phone, and a one-line reply
unblocks the run. Orchestration in the background, not chat-by-chat
micromanagement.

## Prerequisites

1. **BlueBubbles server** running on your Mac (the same Mac running DSH)
   - Download: https://bluebubbles.app
   - Configure a server password in Settings → Users/API
2. **Android iMessage client** paired to your BlueBubbles server
   - BlueBubbles app, BlueMessage, or similar
3. **Node.js 18+** (for global `fetch` support)

## Setup

### 1. Configure the bridge

Copy `.env.example` to `.env` and fill in your values:

```bash
cp .env.example .env
```

Edit `.env`:

```env
BLUEBUBBLES_URL=http://127.0.0.1:1234
BLUEBUBBLES_API_KEY=your-bluebubbles-server-password
BLUEBUBBLES_SELF_HANDLE=+15551234567  # or you@icloud.com
```

- **BLUEBUBBLES_API_KEY**: Your BlueBubbles server password (from Settings → Users/API)
- **BLUEBUBBLES_SELF_HANDLE**: Your own phone number or email (the one you'll message yourself from)

### 2. Test the bridge standalone

```bash
node index.mjs
```

You should see: `imessage-bridge ready (BASE=http://127.0.0.1:1234, defaultChat=iMessage;+15551234567)`

The bridge is now listening on stdin for MCP JSON-RPC messages. Press Ctrl+C to stop.

### 3. Register with DeepSeek Harness

Add this entry to your DSH web profile's `cordis.patch.yml` (typically `~/.dsh/profiles/web/cordis.patch.yml`):

```yaml
- id: imessage-bridge
  name: '@deepseek-ai/dsh-mcp-client'
  config:
    transport: stdio
    serverName: imessage
    command: /usr/local/bin/node
    args:
      - /path/to/imessage-bridge/index.mjs
    env: {}
    cwd: /path/to/imessage-bridge
    toolCallTimeoutMs: 300000
    failOnStartupError: false
```

**Important:** The bridge reads secrets from its `.env` file (not from DSH config), so `env: {}` is safe.

Restart the DSH web GUI to load the new MCP server.

## Usage

The bridge exposes four tools to the DSH agent:

### `imessage_send`

Send an iMessage to a recipient (defaults to yourself for self-chat).

**Parameters:**
- `message` (required): Text to send
- `recipient` (optional): Phone or email. Omit to use `BLUEBUBBLES_SELF_HANDLE` (message yourself)

**Example:**
```json
{
  "message": "Need your input on this design decision...",
  "recipient": "+15551234567"
}
```

Returns the sent message GUID and confirmation.

### `imessage_receive`

Read new human replies since the last activity. In a self-chat, the agent's own sent messages are filtered out, so only **your replies** come back.

**Parameters:**
- `waitMs` (optional): Max milliseconds to block waiting for a reply (default 0 = return immediately)
- `since` (optional): Epoch ms to read from. Omit to use last activity time
- `chatGuid` (optional): Chat GUID. Omit to use the default (self) chat
- `limit` (optional): Max messages to scan (default 50)

**Example (blocking wait for reply):**
```json
{
  "waitMs": 180000
}
```

Returns an array of messages: `{ guid, date, text, handle, chatGuid }`

**Tip:** Call `imessage_receive` with `waitMs` right after `imessage_send` to wait for the human's reply. The DSH tool call timeout is set to 5 minutes (300000ms), so you can wait up to ~280 seconds.

### `imessage_find_chat`

Look up BlueBubbles chat GUIDs for a participant. Use this once to confirm the self-chat GUID if the default `iMessage;<handle>` form is wrong.

**Parameters:**
- `participant` (required): Phone or email to search for

**Example:**
```json
{
  "participant": "+15551234567"
}
```

Returns matching chats with their GUIDs.

### `imessage_ping`

Health check: verifies the BlueBubbles server is reachable and the API key works.

**Parameters:** None

**Example:**
```json
{}
```

Returns `{ ok: true, server, chatCount, defaultChat }` on success.

## How It Works

### Self-Chat Message Filtering

When you message yourself, both the agent's sent messages and your replies have `isFromMe: true` (since they're all from your Apple ID). The bridge tracks sent message GUIDs in memory and filters them out in `imessage_receive`, so only your **replies** (new GUIDs not in the sent set) are returned.

### Polling with Blocking

`imessage_receive` polls BlueBubbles every 3 seconds when `waitMs > 0`. It returns as soon as it finds new messages or when the timeout expires. This lets the agent wait for your reply without busy-looping.

### Zero Dependencies

The bridge implements the MCP stdio protocol (newline-delimited JSON-RPC 2.0) by hand — no SDK, no build step, no `node_modules`. This keeps it lightweight and avoids dependency conflicts with DSH.

## Troubleshooting

### Bridge won't start in DSH

Check the DSH logs. Common issues:
- **Node path wrong**: Update `command` in `cordis.patch.yml` to your actual Node path (`which node`)
- **Bridge path wrong**: Verify the absolute path to `index.mjs`
- **Permission denied**: Ensure `index.mjs` is executable (`chmod +x index.mjs`)

### `imessage_ping` fails

- **401 Unauthorized**: Wrong `BLUEBUBBLES_API_KEY` in `.env`
- **Connection refused**: BlueBubbles server not running, or wrong `BLUEBUBBLES_URL`
- **404 Not Found**: BlueBubbles API version mismatch (this bridge targets BlueBubbles v1 API)

### `imessage_receive` returns empty

- **No default chat**: Set `BLUEBUBBLES_SELF_HANDLE` in `.env`, or pass `chatGuid` explicitly
- **Messages filtered out**: The bridge only returns messages with GUIDs not in its sent set. If you're testing by sending messages from the same Apple ID, they may be filtered. Use a different chat or restart the bridge.

### Agent times out waiting for reply

The DSH tool call timeout is 5 minutes (300000ms). If you need longer, increase `toolCallTimeoutMs` in `cordis.patch.yml`. Alternatively, have the agent call `imessage_receive` with `waitMs: 0` in a loop (less efficient but avoids timeout).

## Security Notes

- **Secrets in `.env`**: The bridge reads `BLUEBUBBLES_API_KEY` from `.env` (not DSH config). Keep `.env` out of git (it's in `.gitignore`).
- **Local-only**: The bridge assumes BlueBubbles runs on `127.0.0.1`. If you expose BlueBubbles to the network, use HTTPS and strong credentials.
- **Apple ToS**: Relay setups (BlueBubbles, AirMessage) operate in a gray area of Apple's Terms of Service. Use at your own risk.

## Development

The bridge is a single file (`index.mjs`) with no build step. To modify:

1. Edit `index.mjs`
2. Restart the DSH web GUI (or HMR if enabled)

To test the MCP protocol manually:

```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | node index.mjs
```

## License

MIT