imessageMCP
README.md
# imessageMCP
**把 iMessage 的收发做成一个 MCP 工具,让别的 AI 应用里的角色能发真短信。**
第一个使用场景是 Float 小手机:那里的角色本来只能在虚拟界面里聊天,接上这个之后,
它可以在需要的时候把消息真的发到手机上,也能查收回信。
它是个**独立的小服务**,和 Uranus 本体互不干涉 —— 不改 Uranus 一行代码、不进它的
npm workspaces、不碰它的 `data/`。唯一的交集是可以顺手读 Uranus 已经配好的
Photon 凭据(可选,见下面的 A 方案)。
---
## 一分钟跑起来
双击 **`启动.bat`**。第一次运行它会自己装依赖、把 `.env.example` 复制成 `.env`
并打开记事本让你填。
或者手动:
```bash
npm install
cp .env.example .env
npm start
```
`.env` 里只有一件事必须填 —— **用哪条 iMessage 线路**,二选一:
```ini
# A) 用 Uranus 里已经配好的项目,密钥自动去读,不用手抄
# (只在和 Uranus 装在同一台机器上时能用)
URANUS_PROJECT_REF=p-xxxxxxxx
# B) 自己填 Photon 凭据,完全不依赖 Uranus
PHOTON_PROJECT_ID=...
PHOTON_PROJECT_SECRET=...
PHOTON_LINE_PHONE=+1xxxxxxxxxx
```
不确定 A 方案该填哪个 id 就**直接启动**,它会把本机可选的项目连同
「这条线在 Uranus 里绑没绑角色」一起列出来。
跑起来之后会打印:
```
MCP 地址:http://127.0.0.1:8790/mcp
```
## ⚠️ 线路不能和 Uranus 撞
Uranus 会自动连上**所有「凭据齐全且绑了角色」的项目**。如果这个 MCP 用的线路
在 Uranus 里也绑着角色,同一个号码上就有两个 AI 抢着回消息 —— 对方会收到两份
互不相干的回复,而两边的日志看着都一切正常,非常难查。
**最稳的做法:去 Uranus 的「iMessage」分区新建一个项目、领一条新号码,
不给它绑任何角色**,然后把这个项目的 id 填进 `.env`。
启动时会自检一遍并在撞车时警告。真想先用现有线路试试也行 —— 只要**别同时开着
Uranus**,就不会真的打架。
---
## 接进 Float 小手机
在小手机的 **MCP 服务器**设置里加一个,地址填 `http://127.0.0.1:8790/mcp`,
**打开「直连模式」**,然后点「发现工具」,应该出现四个动作。
> 「直连模式」不能省:小手机的服务端代理有一道 SSRF 防线,本机和内网地址一律
> 拦掉(它自己代码里写明了这点)。所以只能让浏览器直接连过来 —— 这个服务的
> CORS 就是为这个配的。
### 地址填什么,取决于你用哪台设备打开小手机
直连模式下,去连 MCP 的是**你正在用的那个浏览器**,不是小手机的服务器。
这一点决定了地址怎么填:
| 你在哪儿打开小手机 | MCP 地址填 | 还要做什么 |
|---|---|---|
| 就在跑服务的这台电脑上(`localhost:3001`) | `http://127.0.0.1:8790/mcp` | 什么都不用 |
| 手机 / 平板,通过局域网连电脑 | `http://电脑的局域网IP:8790/mcp` | `.env` 里设 `HOST=0.0.0.0`,**并且一定要设 `MCP_TOKEN`** |
填成 `127.0.0.1` 却用手机打开,手机会去连**它自己**的 8790 端口,当然连不上 ——
「发现工具」转半天失败,多半就是这个原因。
设了 `MCP_TOKEN` 之后,在小手机那个 MCP 服务器的**请求头**里加一条:
```
Authorization: Bearer 你设的那个值
```
## 部署到别的地方
### 搬到另一台电脑,或者发给别人
这个文件夹本身就是完整的,**但发出去之前必须先删三样**:
```
imessageMCP/
├── .env ← 你的 Photon 密钥,绝对不能带出去
├── data/ ← 你收到的所有短信和号码
└── node_modules/ ← 几百 MB,对方自己 npm install 就有
```
目录里的 `.gitignore` 已经挡住了这三样,所以**用 git 发是安全的**
(`git init` 之后 `git add .` 不会碰到它们)。直接压缩文件夹发的话,
自己记得手动删一遍。
对方拿到之后:装 Node 20+,双击 `启动.bat`,在 `.env` 里填**他自己的**
Photon 凭据(B 方案,A 方案依赖 Uranus 的数据目录,换台机器就没有了)。
### 放到 VPS / 公网上
可以,但有两个坎,建议先想清楚值不值:
1. **浏览器的混合内容拦截** —— 如果小手机那边是 `https://` 打开的,浏览器会
直接拒绝去连一个 `http://` 的 MCP,连报错都很含糊。这种情况下 MCP 必须也
套上 HTTPS(套个 Caddy / Nginx 反代,证书交给它)。
小手机跑在本地 `http://localhost:3001` 时没这个问题。
2. **公网上必须设 `MCP_TOKEN`** —— 不然任何知道地址的人都能用你的号码发短信、
读你收到的消息。这个服务默认只听 `127.0.0.1` 就是为了别让人手滑。
`.env` 里这么配:
```ini
HOST=0.0.0.0
PORT=8790
MCP_TOKEN=一串足够长的随机字符串
ALLOW_ORIGIN=https://你打开小手机的那个域名
```
想让它常驻,Linux 上用 systemd 或者 pm2 都行,入口是 `node src/index.js`。
---
## 四个工具
| 工具 | 干什么 |
|---|---|
| `send_imessage` | 发一条文字。收件人填手机号(`+8613800138000` 这种带区号的写法)或之前拿到的会话 ID |
| `get_new_messages` | 查收还没看过的新消息。**取过就算看过了**,下次只给更新的 |
| `get_messages` | 翻记录,不影响未读状态。可以只看某个号码 |
| `get_status` | 线路通不通、自己的号码是多少、积压了几条没查收 |
## 有一件事它做不到:自动提醒
**MCP 是「客户端主动调工具」的协议,服务端没有办法反过来叫醒角色。**
所以对方发来消息时,角色不会自己知道 —— 消息会先存在这边的收件箱里,等角色
下次调 `get_new_messages` 才看得到。实际用起来就是:
- 你在小手机里和角色说话时,它可以顺手查一下「有没有人给我发短信」
- 但对方半夜发了一条,角色不会半夜自己醒过来回你
想要真正的「自动回消息」,那是 Uranus 本体在做的事 —— 它自己守着线路,收到就回。
## 数据在哪
- `data/inbox.jsonl` —— 收到的消息,只追加
- `data/cursor.json` —— 读到哪了
都在这个目录下,不碰 Uranus 的 `data/`。
## 目前不做的
富媒体(图片、语音条、视频)、iMessage 原生玩法(tapback、气球特效、撤回、
已读回执)、群聊管理 —— SDK 全都支持,Uranus 那边也都实现了,等第一版跑顺了
再往上加。
对方发来图片或语音时,角色会收到一条 `[对方发来图片,这个版本还读不了内容]`
的占位,至少知道发生了什么事。
## 文件结构
```
imessageMCP/
├── src/
│ ├── index.js 入口:读配置 → 连线路 → 起 HTTP
│ ├── config.js 凭据从哪来 + 线路冲突自检
│ ├── bridge.js Spectrum 连接、收消息循环、发消息
│ ├── inbox.js 收件箱落盘 + 已读游标
│ ├── server.js Express + CORS + MCP 传输层
│ └── tools.js 四个工具的定义和实现
├── scripts/
│ └── test-inbox.mjs 收件箱自检(不联网、不碰真数据)
├── .env.example
└── 启动.bat
```
```bash
npm test # 等价于 node scripts/test-inbox.mjs
```
收发逻辑(哪些消息该丢、正文怎么摊平、会话怎么拿)跟着 Uranus 的
`server/src/imessage.js` 走,那边是实机跑出来的,每个分支都有踩坑记录。
## 一句提醒
这东西是配合 Uranus 写的,跟着它的规矩:**不许商用**,自己玩、自己学没问题。
要公开分发之前先看一眼 Uranus 的 LICENSE。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues