关于 ios-sim-mcp
ios-sim-mcp 是用于交互式操控 iOS Simulator 应用的 MCP 服务,面向 iOS 开发与测试。它通过无障碍树和稳定标识定位控件,并提供可选的进程内检查与网络工具。
主要功能
- 01无障碍树快照与引用
- 02查找并操作界面元素
- 03检查 UIView 层级
- 04捕获并模拟应用网络流量
- 05在应用内执行 JavaScript
- 06流式读取系统日志
适用场景
- 01按标签或标识浏览模拟器界面
- 02结合视图层级排查点击问题
- 03检查或模拟应用 HTTP 请求
MCP 相关
向 MCP 客户端提供 iOS Simulator 控制和检查工具,包括界面操作、应用内检查、网络捕获与日志读取。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
ios-sim-mcp
一个 MCP 服务,能够像 Chrome 的 DevTools MCP 驱动浏览器一样驱动 iOS Simulator:提供带稳定元素引用的语义化无障碍树快照、低于 200 毫秒的操作速度,并且无需反复截图、目测坐标。
开发这个工具,是因为现有的 iOS Simulator 自动化工具要么速度很慢(Appium、基于截图的 Agent),要么需要预先编写脚本。它能让大模型以交互方式驱动模拟器——按标签或 ID 查找元素、通过引用点击、等待界面稳定,然后重复操作——速度大致与人类相当。
为什么要开发它
现有的大多数 iOS-sim MCP 都通过截取屏幕截图、让模型识别坐标,然后点击像素来工作。这种方式速度慢(图片字节会占用上下文)、成本高(消耗视觉 Token),而且脆弱(任何布局变化都会导致操作失效)。
iOS 应用已经提供了结构化的无障碍树——VoiceOver 使用的也是同一棵树。查询这棵树,就能获取每个交互元素的标签、角色、AX 标识符、位置和启用状态。你可以按语义查找目标,通过稳定的引用指向它,并直接操作。这就是 Chrome MCP 对 DOM 的处理方式,而此工具则是对 iOS Simulator 中 UIView 层级的处理方式。
工作原理
分为三个层级,其中两个目前已可用:
| 层级 | 状态 | 技术 | 提供的功能 |
|---|---|---|---|
| 1 — AX 树 + 操作 | ✅ 已发布 | idb(Facebook 的 CoreSimulator 桥接工具) | 快照、查找、点击、输入、滑动、滚动、启动、截图——全部约 100 毫秒 |
| 2a — Dylib 概念验证 | ✅ 已发布 | 通过 SIMCTL_CHILD_* 使用 DYLD_INSERT_LIBRARIES | 构造函数会在目标应用的 main() 之前运行;通过 os_log 记录生命周期 |
| 2b — 进程内 RPC | ✅ 已发布 | Unix 套接字 + JSON-Lines | 与运行中的应用往返通信,每次调用约 1 毫秒 |
| 2c — 视图内省 | ✅ 已发布 | 主线程 UIView 遍历 | view_tree、view_hit_test——类层级、位置、响应者链、VC 注释 |
| 2d — 网络拦截 | ✅ 已发布 | URLProtocol + URLSessionConfiguration swizzle | 在应用内捕获每个 HTTP 请求/响应(标头、正文、时间信息)——无需安装证书,也无需 mitmproxy |
| 2e — JS 求值 | ✅ 已发布 | JSContext + JSExport 桥接 | 使用 app / key_window() / defaults / pasteboard / bundle / process / 视图查找辅助工具执行 eval_js({code})——相当于 Chrome 的 Runtime.evaluate |
| 2f — 网络模拟 | ✅ 已发布 | 同一 URLProtocol,合成响应 | 按 URL 子字符串和可选方法注册预设响应;支持自定义状态码/标头/正文,以及用于模拟慢速网络的 delay_ms |
| 3 — 系统日志 | ✅ 已发布 | xcrun simctl spawn log stream | 将 os_log 流式传输到一个 5000 行环形缓冲区 |
idb 直接与 CoreSimulator 的私有框架通信——无需 WebDriverAgent,也无需通过 HTTP 跳转到模拟器进程——因此每次调用的延迟更接近本地子进程,而不是一次网络往返。
要求
- 安装 Xcode + iOS Simulator 的 macOS
idb(brew tap facebook/fb && brew install idb-companion && pipx install fb-idb)- Bun(
curl -fsSL https://bun.sh/install | bash) - 已启动的模拟器(
xcrun simctl boot <udid>或在 Xcode 中打开)
安装
最快的方式(npm + npx)
# Register with Claude Code, one line:
claude mcp add -s user ios-sim -- npx -y ios-sim-mcp
npx 会在首次运行时获取软件包。验证:
claude mcp list | grep ios-sim # ios-sim: ... ✓ Connected
第 2 层的 dylib 以预构建通用二进制文件的形式提供(arm64 + x86_64),开箱即用,同时支持 Apple Silicon 和 Intel Mac。
从源代码安装
git clone https://github.com/rmathew1973/ios-sim-mcp.git
cd ios-sim-mcp
bun install
./dylib/build.sh # builds the Layer 2 dylib (only needed if you'll use inject:true)
然后注册:
# Prefer the absolute bun path — clients with a minimal PATH fail on bare `bun`:
claude mcp add -s user ios-sim -- "$HOME/.bun/bin/bun" run "$(pwd)/src/server.ts"
在 Grok 中注册(CLI / 桌面版)
Grok MCP 的生成进程 PATH 通常只有 /usr/bin:/bin。直接使用 bun 会失败,并显示:
Failed to spawn MCP server 'ios-sim': No such file or directory (os error 2)
在 ~/.grok/config.toml 中使用 bun 二进制文件的绝对路径:
[mcp_servers.ios-sim]
command = "/Users/YOU/.bun/bin/bun"
args = ["run", "/Users/YOU/Projects/ios-sim-mcp/src/server.ts"]
enabled = true
startup_timeout_sec = 60
或者:
grok mcp add ios-sim -- "$HOME/.bun/bin/bun" run "$HOME/Projects/ios-sim-mcp/src/server.ts"
更改 MCP 配置后,重启 Grok 会话。验证:grok mcp list 应显示 ios-sim 已连接(而非失败)。
在 Claude Desktop 中注册
添加到 ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"ios-sim": {
"command": "npx",
"args": ["-y", "ios-sim-mcp"]
}
}
}
或者,如果从源代码安装,请指向本地路径:
{
"mcpServers": {
"ios-sim": {
"command": "/Users/YOU/.bun/bin/bun",
"args": ["run", "/absolute/path/to/ios-sim-mcp/src/server.ts"]
}
}
}
退出并重新打开 Claude Desktop。
在其他客户端中注册(通用 MCP 客户端)
这是一个标准的 stdio MCP 服务。运行 bun run src/server.ts 并传递 JSON-RPC。请参阅 test/smoke.ts,了解可正常工作的客户端示例。
工具
| 工具 | 功能 |
|---|---|
list_simulators | 列出模拟器及其 UDID、状态和 OS 版本 |
use_simulator | 选择特定的 UDID(如果只有一个模拟器已启动,则自动选择) |
snapshot | 捕获 AX 树,为元素分配引用 e1..eN,并返回精简的文本呈现。筛选条件:interactive(默认)、actionable、all |
find | 按 id(AXUniqueId)、label、labelContains、role、actionable 搜索快照 |
tap | 按 {ref}、{id} 或 {x,y} 点击。可选的 duration 参数可执行长按 |
type_text | 在当前聚焦的字段中输入文本。可选的 {ref} 或 {id} 参数会先点击再输入。当 dylib 已加载时,会自动通过粘贴路径输入,确保字节完全一致——不会触发 iOS 自动更正,也不会将首字母自动大写。via: "keystroke" 强制使用按键输入路径;via: "paste" 强制使用粘贴路径 |
paste_text | 通过 UIPasteboard 和第一响应者的 paste: 粘贴字节完全一致的文本。需要注入 dylib。适用于电子邮件地址、密码、OAuth 令牌等任何区分大小写的内容 |
view_tree | 在主线程上遍历运行中应用的 UIView 层级。报告类名、窗口坐标中的框架、alpha/hidden/interactive、文本内容,并标注视图控制器边界。内容严格多于 snapshot——还能看到自定义绘制的视图、临时叠加层和 SwiftUI 内部结构。可按 class_filter / ax_id_contains / text_contains 筛选。需要注入 dylib |
view_hit_test | “在 (x,y) 处,实际接收点击的是哪个视图?”返回最顶层视图,以及一路到 UIApplication 的完整响应者链。当 tap 的行为不符合预期时,这是合适的调试工具。需要注入 dylib |
network_start / network_stop / network_status | 安装/移除 URLProtocol 拦截器,并对 URLSessionConfiguration 进行 swizzle,以记录应用中所有基于 URLSession 的 HTTP 请求。选项:max_records、max_body_bytes、filter_url_substring。需要注入 dylib |
network_tail | 返回最近捕获的 N 条记录。默认每个请求显示一行(id、方法、状态、耗时、大小、URL)。full=true 会在行内包含标头和正文预览。通过 since_id 向前翻页 |
network_get_body | 获取指定记录 id 的完整请求正文或响应正文(最多 max_body_bytes,默认为 256KB)。文本正文会以 base64 和 UTF-8 解码形式返回 |
network_clear | 清空环形缓冲区及所有保留的正文 |
network_self_test | 在应用内部发起 HTTP 请求,以端到端验证捕获路径 |
network_stub / network_stubs / network_unstub / network_unstub_all | 按 URL 子字符串(以及可选的方法)注册预设 HTTP 响应。合成状态、标头和正文,不会转发请求;delay_ms 可模拟慢速网络。被 stub 的请求仍会在 network_tail 中显示,且带有 stubbed:true |
eval_js | 在注入应用内的持久化 JSContext 中运行任意 JavaScript。桥接的全局变量:app、key_window()、all_windows()、defaults、pasteboard、bundle、process、notif_center、first_responder()、find_view_by_ax_id、find_view_by_class、find_vc_by_class、post_notification、cls、log。状态会在多次调用间保留。需要注入 dylib |
eval_js_reset | 丢弃 JSContext,并在下次执行 eval 时重建桥接——清除已定义的变量和函数 |
key | 按名称(RETURN、ESC、DELETE、TAB、SPACE、F1–F12、方向键)或原始 HID 代码按下按键 |
button | 硬件按钮:HOME、LOCK、SIDE_BUTTON、SIRI、APPLE_PAY |
swipe | 在两个引用/id/点之间滑动。可选参数:duration 和 delta |
scroll | 按读者视角滚动:down 可显示下方内容。可选锚点元素 |
launch_app | 按 bundle id 启动应用。可选参数 foreground_if_running 会先终止正在运行的应用 |
terminate_app | 按 bundle id 终止应用 |
screenshot | 将 PNG 保存到磁盘或内联返回。请谨慎使用——AX 树速度更快且信息更丰富 |
await_quiescent | 阻塞等待 AX 树哈希保持一致,持续时间达到 stable_ms(默认 250),或直到 timeout_ms(默认 5000)超时。启动应用和导航后请使用此工具,而不是 sleep |
log_start / log_tail / log_stop / log_clear | 将 os_log 流式写入 5000 行环形缓冲区。可按 bundle_id 子字符串、level 或 NSPredicate 筛选 |
示例会话
> snapshot
# Settings screen=402x874 v=5 elements=15
e3 Button "Apple Account" #com.apple.settings.primaryAppleAccount @(201,213)
e5 Button "General" #com.apple.settings.general @(201,406)
e6 Button "Accessibility" #com.apple.settings.accessibility @(201,458)
e14 TextField "Search" ="Search" @(201,822)
...
> tap {id: "com.apple.settings.general"}
tapped e5/com.apple.settings.general
> await_quiescent {stable_ms: 250}
stable hash=8326196f25c8 samples=4 elapsed=1731ms
> snapshot
# Settings v=10 elements=13
e2 Button "Settings" #BackButton @(38,84)
e6 Button "About" #About @(201,418)
...
> type_text {ref: "e14", text: "wifi"}
typed 4 chars into e14
在 iPhone 17 Pro / iOS 26.5 / M 系列 Mac 上测得:
snapshot:127–190 mstap:100 msfind:<1 ms(针对缓存快照进行内存内查找)await_quiescent:400 ms–2.5 s(UI 实际稳定所需时间)
设计说明
- 快照有版本号并会缓存。 任何操作(
tap、swipe、type_text、launch_app、button、key)都会使缓存失效,以防旧引用在状态变更后仍然有效。find和引用解析会提示你重新创建快照,而不是基于旧数据执行操作。 - 引用在同一快照内稳定,但在不同快照间不稳定。
v=10中的e5与v=11中的e5不是同一个元素。AXUniqueIds(iOS 的accessibilityIdentifier)在不同快照间保持稳定,是跨 UI 变更定位控件的首选方式。 await_quiescent会忽略退化的 AX 树。launch_app后约 1 秒内,idb只会返回没有子元素的AXApplication根节点。简单的哈希稳定性检查会在空树上立即返回。实现会在检测到退化快照时重置稳定性计时器。scroll采用阅读者视角。down表示“查看下方内容”(手指向上滑动),与鼠标滚轮和 Page Down 的惯例一致,而非手指移动方向。默认的 0.4s 手势时长可防止 iOS 将松手位置识别为底层元素上的点击。
提示:为自己的应用添加无障碍标识符
当交互控件设置了 accessibilityIdentifier 时,此 MCP 的速度和可靠性最高:
Button("Sign In") { ... }
.accessibilityIdentifier("login_submit_button")
这样,tap({id: "login_submit_button"}) 就是 O(1),并且在标签变更、本地化和布局变化后仍然有效。
第 2 层动态库(可选,每次启动时选择启用)
launch_app({bundle_id, inject: true}) 会通过 SIMCTL_CHILD_DYLD_INSERT_LIBRARIES 将一个小型动态库加载到目标应用中。该动态库会在 /tmp/ios-sim-mcp-<bundle-id>.sock 打开一个 Unix 套接字,并提供 JSON-Lines RPC 服务。随后,你可以与正在运行的应用通信:
dylib_ping({ echo: "hi" }) // pong from com.apple.Preferences (RTT 1ms)
dylib_info({}) // {pid, bundle_id, process_name, bundle_path, methods, ...}
dylib_call({ method: "...", params }) // generic; future phases register more methods
连接后,每次 IPC 调用的耗时都不到 1 毫秒,与第 1 层启动子进程的开销相比低了几个数量级。连接采用惰性建立(首次调用 dylib_* 时建立),并按 bundle id 进行连接池管理;重新启动应用会自动使旧客户端失效。
即使只是随意操作,也建议使用 inject: true 启动应用
iOS 会自动将输入的首字母大写,将 " 和 ' 替换为智能引号,并对看起来像单词的内容进行自动更正。操作 UI 时,这些转换会在不提示的情况下破坏输入:
[email protected]会变成[email protected]→ 服务器拒绝该电子邮件地址 → 登录失败,且没有明显原因café会变成 iOS 无法渲染的内容 → 字段收到乱码a"b'c会在智能引号替换和自动更正后变成A
加载动态库后,type_text 会自动通过 UIPasteboard.general.string = text 和第一响应者的 paste: 进行输入,从而完全绕过键盘。传入的内容会原样到达,不会有任何字节变化。
构建一次即可:
./dylib/build.sh # outputs dylib/build/libios-sim-mcp.dylib
该动态库针对 iOS Simulator 构建(LC_BUILD_VERSION 平台 7,iOS 14+),只会加载到通过此工具并使用 inject: true 启动的应用中,不会修改磁盘上的应用,并且会在进程退出后不留任何痕迹。
何时使用 snapshot 与 view_tree
二者都用于检查正在运行的 UI,但读取的树不同:
snapshot(Layer 1,AX) | view_tree(Layer 2c,UIView) | |
|---|---|---|
| 来源 | iOS 辅助功能树 | 实时 UIView 层级 |
| 需要 dylib | 否 | 是(inject: true) |
| 延迟 | 约 150 ms(子进程) | 约 5 ms(进程内) |
能否看到 SwiftUI 上的 accessibilityIdentifier | 是 | 否(SwiftUI 不会传递此属性) |
能否看到 UIKit 上的 accessibilityIdentifier | 是(如果已设置) | 是(如果已设置) |
| 能否看到自定义绘制的视图 | 否(没有 AX 节点) | 是 |
能否看到 SwiftUI 内部结构(UIHostingController 等) | 已扁平化 | 完整层级 |
| 能否看到视图控制器边界 | 否 | 是 |
| 能否看到 alpha / isHidden / interactionEnabled | 部分 | 是 |
**经验法则:**对于“点击此控件”工作流(尤其是在 SwiftUI 应用中),使用 snapshot + find;对于排查“屏幕上实际显示了什么,以及为什么不起作用”,使用 view_tree。
网络捕获(Layer 2d)
launch_app({ bundle_id: "com.yourco.app", inject: true })
network_start({ max_body_bytes: 262144 })
// ... use the app normally ...
network_tail({ n: 20, full: true })
// → #34 POST 201 142ms ttfb=98ms reqB=312B resB=2.3KB application/json https://api.yourco.com/orders
// request_headers:
// Authorization: Bearer eyJhbG...
// Content-Type: application/json
// request_body:
// {"sku":"PRO_MONTHLY","quantity":1,"coupon":"WELCOME10"}
// response_body:
// {"order_id":"ord_8a3f2c","total":2700,...}
network_get_body({ id: 34, which: "response" }) // full body if it was truncated
范围:
- ✅ 捕获所有基于
URLSession的流量:原生URLSession、URLRequest、Alamofire、SwiftUIAsyncImage,以及任何使用URLSessionConfiguration.default或.ephemeral的内容 - ✅ 解密 HTTP/HTTPS 正文 — 我们在 TLS 上方的 URLSession 流水线内部运行
- ✅ 无需安装证书、mitmproxy 或代理配置
- ⚠️ 不会捕获:原始
CFNetwork/nw_connection_t(针对 BSD 套接字层编写的底层网络代码)、后台URLSession、绕过 URLSession 的 gRPC 库、WKWebView资源加载(独立进程) - ⚠️
HTTPBodyStream请求正文会被记录,但不会被捕获(这需要排空并重置流) - ⚠️ 在
network_start之前创建的会话不会被追溯补装;如果需要捕获应用启动流量,请重启应用,或使用inject:true重新启动应用
网络模拟(Layer 2f)
针对任意 URL 子字符串注册预设响应;dylib 会合成响应,而不是转发请求。无需触及后端,即可进行确定性的错误路径和慢速网络测试。
// Force a 401 for the next login attempt
network_stub({
url_substring: "/api/auth/login",
method: "POST",
status: 401,
headers: { "Content-Type": "application/json" },
body: '{"error":"invalid_credentials"}',
})
// Simulate slow API for offline-UX testing — 3 second delay
network_stub({
url_substring: "/api/products",
delay_ms: 3000,
status: 200,
body: '[{"sku":"FAKE","name":"Stub Product"}]',
})
// Now drive the app — login fails as if password was wrong; product list spinner shows
network_stubs() // list active stubs
network_unstub({id}) // remove one
network_unstub_all() // clear them all
模拟请求仍会以 stubbed:true 和匹配的 stub_id 出现在 network_tail 中,因此你可以验证应用是否按预期发送了请求,然后再接收合成响应。
脚本化运行中的应用(Layer 2e)
launch_app({ bundle_id: "com.yourco.app", inject: true })
eval_js({ code: "bundle.bundleIdentifier" })
// → "com.yourco.app"
eval_js({ code: "app.windows[0].rootViewController.title || 'untitled'" })
// → "Home"
// Toggle a feature flag stored in NSUserDefaults
eval_js({ code: "defaults.setBoolForKey(true, 'feature.dark_mode'); 'set'" })
// Inspect or mutate a live view
eval_js({ code: "var v = find_view_by_class('UITextField'); v && (v.text = '[email protected]')" })
// Define a helper, use it later — state persists across calls
eval_js({ code: "function visibleWindows(){return all_windows().filter(w => !w.isHidden && w.alpha > 0.01);}" })
eval_js({ code: "visibleWindows().length" })
// → 1
桥接全局变量(首次调用 eval_js 时自动全部安装):
| 全局变量 | 类型 | 用途 |
|---|---|---|
app | UIApplication | app.windows、app.applicationState |
key_window() | → UIWindow | 前景窗口 |
all_windows() | → [UIWindow] | 所有场景中的每个窗口 |
defaults | NSUserDefaults | stringForKey、setObjectForKey 等 |
pasteboard | UIPasteboard | string getter/setter |
bundle | NSBundle | bundleIdentifier、infoDictionary |
process | NSProcessInfo | processName、environment、processIdentifier |
notif_center | NSNotificationCenter | |
first_responder() | → UIResponder | 当前获得焦点的响应者 |
find_view_by_ax_id(id) | → UIView | null | 快速查找视图 |
find_view_by_class(name) | → UIView | null | 按类名查找第一个匹配项 |
find_vc_by_class(name) | → UIViewController | null | 遍历 rootVC → presented + children |
post_notification(name, userInfo?) | → void | 发送 NSNotification |
cls(name) | → Class | NSClassFromString 的快捷方式 |
log(msg) | → void | 写入 os_log(可通过 log_tail 查看) |
JSC 的 JSExport 已在 UIView、UIWindow、UIViewController、UILabel、UIButton、UITextField、UITextView、UIApplication、NSBundle、NSProcessInfo、NSUserDefaults、UIPasteboard 上实现,因此无需进一步桥接即可访问常见属性并调用方法。选择器中的冒号会转换为驼峰式函数名:setObject:forKey: → setObjectForKey(value, key)。
App 扩展、辅助进程、插件宿主
SIMCTL_CHILD_DYLD_INSERT_LIBRARIES 只会影响 simctl 直接启动的进程,不会影响 SpringBoard、Mediaserverd 或其他系统守护进程。在应用自己的子进程中(XPC 辅助进程、按需启动的 App 扩展:分享、操作、今日视图小组件),只要 dyld 遵循该环境变量,动态库就会加载。为避免混乱:
- 如果已加载进程的
[NSBundle mainBundle].bundleIdentifier为空或为 nil(XPC 辅助进程、某些插件宿主),动态库会静默跳过套接字绑定——不会暴露 MCP,也不会与主应用发生/tmp/ios-sim-mcp-*.sock路径冲突。 - 如果已加载进程是拥有自己 bundle id 的真正 App 扩展(例如
com.yourco.app.ShareExtension),动态库会在/tmp/ios-sim-mcp-<extension-bundle-id>.sock打开一个独立套接字。向dylib_*工具传入该 bundle id,即可独立驱动此扩展。 - 在正常流程中,你只需对主应用调用
launch_app。iOS 在分享或小组件交互期间启动的扩展在技术上可以访问,但通常不会作为目标——如需指定扩展,请在每次工具调用时显式设置bundle_id。
分发模式——为何选择运行时注入,而非 SwiftPM
我们采用方案 1:预构建的通用动态库,通过 DYLD_INSERT_LIBRARIES 在运行时注入。开发者无需对 Xcode 项目做任何改动——不需要 SPM 依赖、构建设置或 #if DEBUG 守卫。该动态库实际上并不属于他们的应用,因此不会影响 App Store 提交和生产构建。
我们考虑过以下替代方案:
| 方案 | 可实现的功能 | 代价 |
|---|---|---|
| 1. 运行时 DYLD 注入(当前方案) | 零集成。适用于任何模拟器构建。不影响 App Store。 | 仅限公开 API 和通过 JSExport 桥接的 ObjC。无法访问 internal Swift 类型。 |
| 2. SwiftPM 开发依赖 | 通过 @testable 导入访问应用的内部类型。通过编译时 #if DEBUG 强制限制。可在应用定义的接入点添加自定义钩子。 | 开发者需添加一个包、通过 #if DEBUG 限制其使用,并接受测试构建受到一定的编译时开销影响。 |
| 3. 两者兼用 | 为有此需求的开发者提供两种方案的优势。 | 需要维护的范围翻倍。 |
方案 1 覆盖了 90% 的使用场景(无需更改项目即可驱动任意 iOS 模拟器),因此我们优先推出了该方案。如果用户团队有特定需求,需要检查自己的 Swift 内部实现,方案 2 或 3 也可作为合理的后续补充。
安全性与生产环境注意事项
Layer 2 dylib 的设计目标是绝不导致宿主应用崩溃:
- 全局忽略
SIGPIPE,并为每个 fd 设置SO_NOSIGPIPE,因此 MCP 端的套接字关闭不会导致宿主进程退出。 - 每项已分派的任务(接受循环、逐连接服务、URLSession 委托回调、桩合成、视图遍历、JS 求值)都由
@try包裹;发生异常时会通过os_log记录日志,而不是继续传播。 - 接受循环遇到暂时性的
accept()失败时会以 250ms 的间隔重试,而不会退出。 - 方法处理程序在后台队列上运行;需要操作 UIKit 的处理程序会通过
dispatch_sync切换到主线程,并支持由调用方控制超时时间。 - 缓冲区大小有限:5,000 行日志、500 条网络记录(可配置)、256KB 的请求正文上限(可配置)。
- 所有共享状态均由
NSLock保护。
这是开发/测试工具,不适用于生产构建。 以下两项安全保障意味着它仅适用于工程模拟器:
- 没有 App Store 风险 —
DYLD_INSERT_LIBRARIES通过SIMCTL_CHILD_*环境变量在运行时注入。该 dylib 不会与应用二进制文件链接、嵌入或随应用一起发布。App Store 构建在物理上无法加载它。 - 每次启动均需显式启用 —
launch_app默认设置为inject: false。只有显式传入inject: true时才会加载 dylib,且仅限模拟器(Apple 的强化运行时和代码签名要求会阻止向真实设备或生产构建注入 DYLD)。
如果在未设置 inject: true 时意外调用仅适用于 dylib 的工具(view_tree、eval_js、network_* 等),你会收到明确指出修复方法的错误,而不会遇到挂起或崩溃。可在脚本中使用 dylib_health 检测功能是否可用,而不触发异常。
路线图
describe_point— 调试手势目标时,查询“(x,y) 下方是什么?”。- 视频录制 — 将
idb record-video封装为record_start/record_stop。 - 多模拟器并行 — 多台已启动的设备共用一个服务,以进行矩阵测试。
- JS 交互式 REPL 工具 — 支持多行缓冲和历史记录,便于临时探索。
许可证
MIT。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。