苹果桥
Swift 6 MCP(模型上下文协议)服务器,为AI助手提供对macOS应用程序和服务的访问。
特性
Apple Bridge通过MCP工具公开了7个macOS域:
| 域 | 工具 | 访问方法 |
|---|---|---|
| 日历 | calendar_list, calendar_create, calendar_update, calendar_delete EventKit | |
| 提醒事项 | reminders_list, reminders_create, reminders_update, reminders_complete, reminders_delete EventKit | |
| 联系人 | contacts_search, contacts_me | AppleScript |
| 备注 | notes_search, notes_list, notes_create | AppleScript |
| 消息 | messages_read, messages_send, messages_unread | AppleScript(部分) |
| 邮件 | mail_search, mail_unread, mail_send | AppleScript |
| 地图 | maps_search, maps_nearby, maps_directions, maps_open | 地图工具包 |
需求
- macOS 13.0(文图拉)或更高版本
- Swift 6.0+
- Xcode 16.0+(用于开发)
安装
git clone https://github.com/boutquin/apple-bridge.git
cd apple-bridge
swift build -c release可执行文件将位于 .build/release/apple-bridge.
权限
Apple Bridge需要各种macOS权限,具体取决于您使用的域:
| 权限 | 需要 | 授予地点 |
|---|---|---|
| 日历 | 日历域 | 系统设置→ 隐私和安全→ 日历 |
| 提醒事项 | 提醒域 | 系统设置→ 隐私和安全→ 提醒事项 |
| 自动化(联系人) | 联系人域 | 系统设置→ 隐私和安全→ 自动化 |
| 自动化(备注) | Notes域 | 系统设置→ 隐私和安全→ 自动化 |
| 自动化(消息) | 消息域(聊天、发送) | 系统设置→ 隐私和安全→ 自动化 |
| 全磁盘访问 | 邮件域(已读、未读) | 系统设置→ 隐私和安全→ 全磁盘访问 |
| 自动化(邮件) | 邮件域 | 系统设置→ 隐私和安全→ 自动化 |
| 定位服务 | 地图域 | 系统设置→ 隐私和安全→ 定位服务 |
注: 已读/未读邮件需要完全磁盘访问权限,因为Messages.app脚本字典不会公开单个邮件。所有其他AppleScript域(联系人、笔记、消息聊天/发送、邮件)只需要自动化权限。
使用Claude Desktop
添加到您的 claude_desktop_config.json:
{
"mcpServers": {
"apple-bridge": {
"command": "/path/to/apple-bridge"
}
}
}发展
建筑
swift build # Debug build
swift build -c release # Release build测试
Apple Bridge有几个测试类别:
# Run all unit tests (no permissions required)
swift test
# Run specific test suites
swift test --filter CoreTests
swift test --filter AdapterTests
swift test --filter MCPServerTests
swift test --filter E2ETests系统测试
系统测试验证真正的macOS集成,并需要适当的权限。他们是 默认情况下禁用 允许CI在没有macOS权限的情况下通过。
在本地运行系统测试
# Enable and run system tests
export APPLE_BRIDGE_SYSTEM_TESTS=1
swift test --filter SystemTests
# Or use the convenience script
./scripts/run-system-tests.sh
# With coverage reporting
./scripts/run-system-tests.sh --coverage系统测试的先决条件
在运行系统测试之前,请确保:
- 日历访问 授予终端/IDE
- 自动化接入 授予终端/IDE(用于联系人、笔记、消息、邮件)
- 全磁盘访问 授予终端/IDE(仅用于消息读取/未读取测试)
- 苹果邮件 已配置至少一个帐户
- 苹果地图 可用的
手动质量保证测试(FDA降解)
一些测试验证了权限被拒绝时的优雅降级。这些需要特殊设置:
# 1. REMOVE Full Disk Access from the apple-bridge binary
# 2. Set both environment variables
export APPLE_BRIDGE_SYSTEM_TESTS=1
export APPLE_BRIDGE_MANUAL_QA=1
# 3. Run FDA tests
swift test --filter FDA
# 4. Re-grant Full Disk Access after testing项目结构
apple-bridge/
├── Sources/
│ ├── apple-bridge/ # Main executable
│ ├── Core/ # Domain models, service protocols, errors
│ ├── Adapters/ # macOS framework adapters (see Architecture)
│ └── MCPServer/ # MCP protocol implementation and handlers
├── Tests/
│ ├── CoreTests/ # Unit tests for Core
│ ├── AdapterTests/ # Unit tests for Adapters
│ ├── MCPServerTests/ # Unit tests for MCP handlers
│ ├── E2ETests/ # End-to-end protocol tests
│ ├── SystemTests/ # Real macOS integration tests
│ └── TestUtilities/ # Shared test helpers
└── scripts/
└── run-system-tests.sh # System test runner公共应用接口模块
这 TestUtilities 模块提供共享测试基础设施:
- 工厂功能:
makeTestEvent(),makeTestReminder()等等。 - 模拟服务:
MockCalendarService,MockNotesService等等。 - ProcessRunner:用于E2E和系统测试的共享可执行运行程序
建筑
三层体系结构
Apple Bridge采用简洁的三层架构,将关注点分开,实现全面测试:
┌─────────────────────────────────────────────────────────────────┐
│ MCP Handlers │
│ (MCPServer/Handlers/) │
│ Receives MCP tool calls, validates arguments, returns JSON │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Service Layer │
│ (Core/Services/) │
│ Domain protocols (CalendarService, NotesService, etc.) │
│ Uses domain models (CalendarEvent, Note, Message, etc.) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Adapter Layer │
│ (Adapters/) │
│ Adapter protocols + Real implementations │
│ Uses DTOs (CalendarEventData, NoteData, MessageData, etc.) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ macOS Frameworks │
│ EventKit, AppleScript, MapKit │
└─────────────────────────────────────────────────────────────────┘适配器模式
每个域都使用 适配器模式 将服务层与特定的macOS框架实现解耦。这提供了:
- 可测试性:可以在没有macOS权限的情况下注入模拟适配器进行单元测试
- 灵活性:可以交换替代实现(例如,地图的MapKit与AppleScript)
- 干净的边界:DTO提供跨参与者边界的可发送安全数据传输
- 实现隐藏:服务层不知道数据是来自SQLite、EventKit还是AppleScript
按域划分的适配器协议
| 域 | 适配器协议 | DTO | 实现 |
|---|---|---|---|
| 日历 | CalendarAdapterProtocol | CalendarEventData, CalendarData | EventKitAdapter |
| 提醒事项 | CalendarAdapterProtocol | ReminderData, ReminderListData | EventKitAdapter |
| 联系人 | ContactsAdapterProtocol | ContactData | AppleScriptContactsAdapter |
| 备注 | NotesAdapterProtocol | NoteData, NoteFolderData | AppleScriptNotesAdapter |
| 消息 | MessagesAdapterProtocol | MessageData, ChatData | AppleScriptMessagesAdapter |
| 邮件 | MailAdapterProtocol | EmailData, MailboxData | AppleScriptMailAdapter |
| 地图 | MapsAdapterProtocol | LocationData | MapKitAdapter |
按域读/写技术
| 域 | 读 | 写 | 适配器 |
|---|---|---|---|
| 日历 | EventKit | EventKit | EventKitAdapter |
| 提醒 | EventKit | EventKit | EventKitAdapter |
| 联系人 | AppleScript | 只读 | AppleScriptContactsAdapter |
| 注释 | AppleScript | AppleScript | AppleScriptNotesAdapter |
| 消息 | AppleScript(仅聊天) | AppleScript | AppleScriptMessagesAdapter |
| 邮件 | AppleScript | AppleScript | AppleScriptMailAdapter |
| 地图 | MapKit | MapKit | MapKitAdapter |
DTO设计原则
数据传输对象(DTO)遵循以下原则:
- 可发送的:所有DTO都是
Sendable安全演员边界穿越 - 可编码:DTO是
Codable在需要时进行序列化 - 可等同的:DTO是
Equatable用于测试断言 - 实施无关:没有特定于框架的前缀(例如。,
CalendarEventData不EKEventData) - 轻量级:只有基本字段,没有框架依赖关系
DTO示例:
public struct CalendarEventData: Sendable, Equatable {
public let id: String
public let title: String
public let startDate: Date
public let endDate: Date
public let calendarId: String
public let location: String?
public let notes: String?
}服务到适配器流
服务委托给适配器,并在域模型和DTO之间转换:
// Service implementation
public struct NotesSQLiteService: NotesService {
private let adapter: any NotesAdapterProtocol
public func get(id: String) async throws -> Note {
let noteData = try await adapter.fetchNote(id: id)
return toNote(noteData) // Convert DTO to domain model
}
private func toNote(_ data: NoteData) -> Note {
Note(id: data.id, title: data.title, body: data.body, ...)
}
}用于测试的模拟适配器
每个域都有一个相应的模拟适配器用于单元测试:
| 域 | 模拟适配器 | 位置 |
|---|---|---|
| 日历/提醒 | MockEventKitAdapter | Tests/AdapterTests/Mocks/ |
| 联系人 | MockContactsAdapter | Tests/AdapterTests/Mocks/ |
| 注意事项 | MockNotesAdapter | Tests/AdapterTests/Mocks/ |
| 留言 | MockMessagesAdapter | Tests/AdapterTests/Mocks/ |
| 邮件 | MockMailAdapter | Tests/AdapterTests/Mocks/ |
| 地图 | MockMapsAdapter | Tests/AdapterTests/Mocks/ |
模拟适配器是以下角色:
- 为返回值存储存根数据
- 跟踪方法需要验证
- 允许错误注入以测试错误路径
示例用法:
func testSearchNotes() async throws {
let mockAdapter = MockNotesAdapter()
await mockAdapter.setStubNotes([
NoteData(id: "1", title: "Meeting Notes", body: "...", modifiedAt: "...")
])
let service = NotesSQLiteService(adapter: mockAdapter)
let results = try await service.search(query: "Meeting", limit: 10, includeBody: true)
XCTAssertEqual(results.items.count, 1)
XCTAssertEqual(results.items[0].title, "Meeting Notes")
}适配器文件位置
Sources/Adapters/
├── EventKitAdapter/
│ ├── CalendarAdapterProtocol.swift # Protocol + Calendar/Reminder DTOs
│ ├── EventKitAdapter.swift # EventKit implementation
│ ├── EventKitCalendarService.swift # CalendarService using adapter
│ └── EventKitRemindersService.swift # RemindersService using adapter
├── ContactsAdapter/
│ ├── ContactsAdapterProtocol.swift # Protocol + ContactData DTO
│ ├── ContactsAdapter.swift # Contacts framework implementation (requires signed binary)
│ ├── AppleScriptContactsAdapter.swift # AppleScript implementation (default)
│ └── ContactsFrameworkService.swift # ContactsService using adapter
├── NotesAdapter/
│ ├── NotesAdapterProtocol.swift # Protocol + NoteData DTO
│ ├── SQLiteNotesAdapter.swift # SQLite implementation (requires Full Disk Access)
│ └── AppleScriptNotesAdapter.swift # AppleScript implementation (default)
├── MessagesAdapter/
│ ├── MessagesAdapterProtocol.swift # Protocol + MessageData/ChatData DTOs
│ ├── HybridMessagesAdapter.swift # SQLite (read) + AppleScript (send) (requires FDA)
│ └── AppleScriptMessagesAdapter.swift # AppleScript implementation (default, partial)
├── MailAdapter/
│ ├── MailAdapterProtocol.swift # Protocol + EmailData DTO
│ └── AppleScriptMailAdapter.swift # AppleScript implementation
├── MapsAdapter/
│ ├── MapsAdapterProtocol.swift # Protocol + LocationData DTO
│ ├── MapKitAdapter.swift # MapKit implementation
│ └── MapsKitService.swift # MapsService using MapKitAdapter
├── SQLiteAdapter/
│ ├── SQLiteConnection.swift # Low-level SQLite wrapper
│ ├── SchemaValidation.swift # Database schema validation
│ ├── NotesSQLiteService.swift # NotesService using NotesAdapter
│ └── MessagesSQLiteService.swift # MessagesService using MessagesAdapter
└── AppleScriptAdapter/
├── AppleScriptRunner.swift # Actor for script execution
└── MailAppleScriptService.swift # MailService using MailAdapterSwift 6并发
Apple Bridge完全符合Swift 6,并具有严格的并发检查:
- 所有服务协议
Sendable - 所有适配器协议
Sendable - 所有DTO都是
Sendable - 适配器在需要线程安全的地方使用actors
- 无数据竞争或Sendable违规
错误处理
错误遵循一致的模式:
ToolErrorMCP级别错误(无效参数、未知工具)ValidationError用于域验证(未找到,缺少必填字段)PermissionError访问问题(日历被拒绝,需要全磁盘访问)AppleScriptErrorAppleScript执行失败SQLiteError数据库错误
所有错误都包括适用的补救说明。
MCP协议
Apple Bridge执行MCP规范2024-11-05:
- 基于stdio的JSON-RPC 2.0
- 带有JSON模式验证的工具定义
- 列表操作的基于光标的分页
故障排除
权限被拒绝错误
日历/提醒的“权限被拒绝”:
- 打开系统设置→ 隐私和安全
- 查找相关部分(日历或提醒)
- 为您的终端应用程序或Claude Desktop启用访问权限
联系人/笔记/消息/邮件的“AppleScript错误”:
- 打开系统设置→ 隐私和安全→ 自动化
- 允许访问相关应用程序(Contacts.app、Notes.app、Messages.app、Mail.app)
- 您可能需要运行一次命令来触发权限提示
已读/未读邮件需要“全磁盘访问”: Messages.app脚本字典不公开单个消息,因此读取消息历史记录需要通过全磁盘访问直接访问数据库:
- 打开系统设置→ 隐私和安全→ 全磁盘访问
- 添加
apple-bridge可执行文件(需要正确签名的二进制文件) - 授予访问权限后重新启动应用程序
注: 临时签名的二进制文件无法可靠地保存全磁盘访问TCC条目。消息读取/未读取功能需要签名的二进制文件(Apple Developer证书)。
常见问题
服务器没有响应:
- 确保您使用的是正确的路径
apple-bridge可执行 - 检查可执行文件是否具有执行权限:
chmod +x apple-bridge - 验证没有其他进程正在使用stdin/stdout
工具返回空结果:
- 验证相关macOS应用程序是否有数据(例如,日历有事件)
- 检查是否在系统设置中授予了权限
邮件操作缓慢或超时:
- Mail使用索引反向迭代来避免一次加载所有邮件
- 非常大的收件箱(超过10万条消息)可能仍需要几秒钟的搜索时间
- 确保Mail.app未处于无响应状态
测试失败,出现权限错误:
- 单元测试不需要权限,应该始终通过
- 系统测试需要
APPLE_BRIDGE_SYSTEM_TESTS=1以及适当的权限 - 有关设置说明,请参阅上面的“系统测试”部分
调试模式
要查看详细的日志记录,请在stderr可见的情况下运行:
# View logs while running
./apple-bridge 2>apple-bridge.log &
tail -f apple-bridge.log日志仅转到stderr;stdout保留用于MCP协议消息。
CI状态

许可证
MIT许可证-有关详细信息,请参阅许可证文件。
