内容
# Apple MCP 日志桥
iCloud 日志的本地 MCP/WebUI 桥。
该应用程序保持一个经过身份验证的 iCloud 日志浏览器会话,同步日志到 MongoDB,并通过 MCP 暴露日志操作。
## 命令
```bash
make start
make stop
make clean
```
- `make start` 启动 Docker Compose 而不重建,并保留卷/会话状态。
- `make stop` 停止容器并保留卷/会话状态。
- `make clean` 删除容器和卷。这会清除 MongoDB 和浏览器配置文件,因此需要重新登录 iCloud。
要将代码更改应用于应用程序容器,同时保留卷/会话:
```bash
docker compose up -d --build mcp-notes
```
## 登录流程
1. 在 `http://localhost:3000` 打开 WebUI。
2. 点击 `生成代码`。
3. 复制生成的代码。
4. 在嵌入式浏览器视图中登录 iCloud。
5. 点击 `开始`。
6. 启动后,WebUI/API/MCP 访问需要生成的代码。
浏览器配置文件保存在挂载到 `/data` 的 Docker 卷中,因此正常的 `make stop` / `make start` 应该保持 iCloud 会话。
## 当前架构
```text
WebUI / MCP
|
日志处理器
|
浏览器控制器
|
Playwright 身份验证的 iCloud 页面
|
iCloud 日志 iframe
|
window.NotesApp
|
NotesApp.dataManager.allNotes
|
note.getTopoText()
|
MongoDB
```
Playwright 仍然有用,但不是用于 OCR 或视觉抓取。它用于保持身份验证的 iCloud 页面打开,并在 iCloud 日志 iframe 中评估 JavaScript。
## iCloud 日志发现
我们最初看到日志内容通过类似画布的自定义编辑器表面渲染。OCR 被拒绝,因为它不可靠并且会丢失结构。重要的发现是,可见的编辑器只是表示层;真正的日志模型在运行的 iCloud 网页应用程序中可用。
发现的路径是:
1. 顶级 iCloud HTML 显示 `/notes` 启动了一个子应用程序 iframe。
2. 启动脚本将 `/notes` 解析为 `notes3`。
3. 它创建了一个 id 为 `early-child` 的 iframe。
4. 该 iframe 加载:
```text
https://www.icloud.com/applications/notes3/current/en-us/index.html?rootDomain=www...
```
5. Safari Apple Events 检查确认顶级页面有该 iframe。
6. 检查 `document.querySelector('iframe').contentWindow` 显示这些全局变量:
```text
CloudKit
NotesApp
```
7. 深入 `window.NotesApp` 显示:
```text
NotesApp.dataManager
NotesApp.mainViewModel
NotesApp.rootViewController
```
8. `NotesApp.dataManager.allNotes` 包含加载的日志模型。
9. `NotesApp.mainViewModel.selectedNote` 指向选定的日志。
10. 每个日志模型公开日志特定的字段和帮助程序:
```text
id
recordName
标题
摘要
TopoTextString
getTopoText()
创建时间
修改时间
zoneID
```
`note.getTopoText()` 使用 Apple 自己的应用程序代码加载/解码完整的日志正文。这避免了 OCR、画布解析和重新实现 Apple 的 TopoText 解码器。
## CloudKit 与 NotesApp
`CloudKit` 是 Apple 的通用 iCloud 数据库传输层。它与以下端点通信:
```text
ckdatabasews/.../database/1/com.apple.notes/production/private/records/query
ckdatabasews/.../database/1/com.apple.notes/production/private/records/lookup
ckdatabasews/.../database/1/com.apple.notes/production/private/changes/zone
```
`NotesApp` 是在 iframe 中加载的运行的 iCloud 日志网页应用程序。它封装了 CloudKit,拥有 UI/应用程序状态,管理文件夹和日志,并解码日志特定的内容。
对于此项目,`NotesApp` 是首选的第一个集成点,因为它已经公开了解码的日志模型:
```js
const notesWindow = document.querySelector('iframe').contentWindow;
const notes = notesWindow.NotesApp.dataManager.allNotes;
const body = String(await notes[0].getTopoText());
```
直接 CloudKit 访问在后期仍然有用,但需要处理原始记录字段、资产、压缩的 protobuf TopoText 和写入语义。
## 稳定的日志身份
iCloud 日志 URL 包含 CloudKit 身份,编码为 base64:
```text
/notes/note/<base64>
```
解码 URL 路径得到:
```text
Private::Notes::currentUser::<recordName>
```
从 Safari 观察到的示例:
```text
Private::Notes::currentUser::ADAC358D-E303-4639-A5C2-192AE0726967
```
同步存储此元数据为 `cloudKit`:
```json
{
"recordId": "Private::Notes::currentUser::<recordName>",
"database": "Private",
"zoneName": "Notes",
"ownerName": "currentUser",
"recordName": "<recordName>"
}
```
## 同步策略
当前的读取路径是:
1. 打开或重用身份验证的 iCloud 日志页面。
2. 查找日志 iframe。
3. 在 iframe 中评估 JavaScript。
4. 读取 `NotesApp.dataManager.allNotes`。
5. 过滤已删除/回收站日志。
6. 为每个日志等待 `note.getTopoText()`。
7. 在 MongoDB 中存储标题、正文、URL 身份和 CloudKit 元数据。
旧的 DOM/卡片刮擦器仅作为备用保留。
## 写入策略
Safari 运行时测试确认可以通过 `NotesApp` 直接驱动创建、更新和删除。
观察到的有效方法:
```js
const app = document.querySelector('iframe').contentWindow.NotesApp;
const dataManager = app.dataManager;
const Note = app.mainViewModel.selectedNote.constructor;
```
创建:
```js
const note = Note.createNoteWithTitleText(fullText, folder);
dataManager.userDidCreateNote(note);
await note.save(true);
```
更新:
```js
const replacement = Note.createInitialTopoTextString(nextText);
dataManager.topoTextManager.load(note.id, replacement);
note.userDidChangeTopoText();
await note.save(true);
```
删除:
```js
await note.deleteOrMoveToRecentlyDeletedAsNeeded();
```
删除路径将普通私有日志移动到最近删除的日志,匹配网页应用程序的行为。
探针使用了一个临时日志,并验证:
- 通过 `Note.createNoteWithTitleText` 创建
- 通过 `topoTextManager.load` 和 `userDidChangeTopoText` 更新
- 通过 `deleteOrMoveToRecentlyDeletedAsNeeded` 删除
- 持久的 CloudKit 身份为 `Private::Notes::currentUser::<recordName>`
MCP 写入现在使用此运行时路径。首先,UI 键盘备用仅作为追加/创建的备份。
## 不该做的事
- 不要对日志正文使用 OCR/Tesseract。
- 不要将画布像素视为真实来源。
- 不要通过标题识别日志;标题是可变的和非唯一的。
- 除非您故意要清除浏览器和数据库持久性,否则不要使用 `make clean`。
## 验证
```bash
npm run check
```
## MCP 端点
MCP 服务器在以下位置公开:
```text
POST /mcp
```
身份验证接受以下任意一项:
```text
Authorization: Bearer <generated-code>
X-Auth-Token: <generated-code>
?token=<generated-code>
apple_mcp_token cookie
```
服务器宣传这些 MCP 工具:
```text
listNotes
getNote
searchNotes
createNote
appendNote
deleteNote
```
对于 ChatGPT 测试,通过 HTTPS 公开应用程序,并将 MCP URL 配置为:
```text
https://your-domain.example/mcp?token=<generated-code>
```
在 URL 中使用令牌便于测试,因为当前服务器使用生成的静态代码,而不是 OAuth。对于持久的公共部署,更倾向于添加 OAuth 或反向代理,服务器端注入 bearer 令牌,以便代码不会存储在连接器 URL 或日志中。