mcpField
I
ios-sim-mcp

ios-sim-mcp

本地服务
github.com/rmathew1973开发工具013 周前更新

关于 ios-sim-mcp

ios-sim-mcp 是用于交互式操控 iOS Simulator 应用的 MCP 服务,面向 iOS 开发与测试。它通过无障碍树和稳定标识定位控件,并提供可选的进程内检查与网络工具。

主要功能

  1. 01无障碍树快照与引用
  2. 02查找并操作界面元素
  3. 03检查 UIView 层级
  4. 04捕获并模拟应用网络流量
  5. 05在应用内执行 JavaScript
  6. 06流式读取系统日志

适用场景

  1. 01按标签或标识浏览模拟器界面
  2. 02结合视图层级排查点击问题
  3. 03检查或模拟应用 HTTP 请求

MCP 相关

向 MCP 客户端提供 iOS Simulator 控制和检查工具,包括界面操作、应用内检查、网络捕获与日志读取。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"rmathew1973-ios-sim": {
"command": "npx",
"args": [
"-y",
"ios-sim-mcp"
]
}
}
}
启动命令npx -y ios-sim-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmios-sim-mcp0.4.2

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

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 ms
  • tap:100 ms
  • find:<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、SwiftUI AsyncImage,以及任何使用 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 时自动全部安装):

全局变量类型用途
appUIApplicationapp.windows、app.applicationState
key_window()→ UIWindow前景窗口
all_windows()→ [UIWindow]所有场景中的每个窗口
defaultsNSUserDefaultsstringForKey、setObjectForKey 等
pasteboardUIPasteboardstring getter/setter
bundleNSBundlebundleIdentifier、infoDictionary
processNSProcessInfoprocessName、environment、processIdentifier
notif_centerNSNotificationCenter
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)→ ClassNSClassFromString 的快捷方式
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 保护。

这是开发/测试工具,不适用于生产构建。 以下两项安全保障意味着它仅适用于工程模拟器:

  1. 没有 App Store 风险 — DYLD_INSERT_LIBRARIES 通过 SIMCTL_CHILD_* 环境变量在运行时注入。该 dylib 不会与应用二进制文件链接、嵌入或随应用一起发布。App Store 构建在物理上无法加载它。
  2. 每次启动均需显式启用 — 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。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。