@mgcrea/mcp-ios-simulator 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -136,42 +136,51 @@ See [.mcp.json.example](.mcp.json.example).
136
136
 
137
137
  ## Tools
138
138
 
139
- Nineteen. Five are read-only; the other fourteen disappear with
139
+ Twenty-one. Six are read-only; the other fifteen disappear with
140
140
  `IOS_SIMULATOR_ALLOW_WRITES=0`.
141
141
 
142
- | Tool | Writes? | What it does |
143
- | ------------------------------- | ------------- | ------------------------------------------------------------------------ |
144
- | `ios_simulator_list` | | Every simulator, with state and whether its runtime is installed |
145
- | `ios_simulator_diagnostics` | | Both lanes, the resolved target, the geometry, and who owns the WDA port |
146
- | `ios_simulator_list_apps` | | Installed apps, with bundle id and host-path data container |
147
- | `ios_simulator_screenshot` | | The screen, in point space — **no runner needed** |
148
- | `ios_simulator_ui_tree` | | Addressable elements with precomputed tap points |
149
- | `ios_simulator_tap` | ✓ | Tap a point |
150
- | `ios_simulator_tap_element` | ✓ | Tap by identifier, label or predicate |
151
- | `ios_simulator_swipe` | ✓ | Drag between two points |
152
- | `ios_simulator_type` | ✓ | Type into the focused field, or a named one |
153
- | `ios_simulator_press_button` | ✓ | Home |
154
- | `ios_simulator_power` | ✓ | Boot or shut down; never implicit |
155
- | `ios_simulator_erase` | ✓ **confirm** | Wipe to factory — the only irreversible tool |
156
- | `ios_simulator_install` | ✓ | Install a simulator `.app` |
157
- | `ios_simulator_launch` | ✓ | Launch, with fixture arguments and captured output |
158
- | `ios_simulator_terminate` | ✓ | Kill a running app |
159
- | `ios_simulator_open_url` | ✓ | Deep links and universal links |
160
- | `ios_simulator_set_environment` | ✓ | Appearance, Dynamic Type, contrast, status bar, location, permissions |
161
- | `ios_simulator_push` | ✓ | A remote notification, with no APNs certificate |
162
- | `ios_simulator_restart_wda` | ✓ | Start or restart the runner, detached |
142
+ | Tool | Writes? | What it does |
143
+ | -------------------------------- | ------------- | ------------------------------------------------------------------------ |
144
+ | `ios_simulator_list` | | Every simulator, with state and whether its runtime is installed |
145
+ | `ios_simulator_diagnostics` | | Both lanes, the resolved target, the geometry, and who owns the WDA port |
146
+ | `ios_simulator_list_apps` | | Installed apps, with bundle id and host-path data container |
147
+ | `ios_simulator_screenshot` | | The screen, in point space — **no runner needed** |
148
+ | `ios_simulator_ui_tree` | | Addressable elements with precomputed tap points |
149
+ | `ios_simulator_wait_for_element` | | Poll until something appears, or goes away |
150
+ | `ios_simulator_tap` | ✓ | Tap a point |
151
+ | `ios_simulator_tap_element` | ✓ | Tap by identifier, label or predicate |
152
+ | `ios_simulator_swipe` | ✓ | Drag between two points |
153
+ | `ios_simulator_type` | ✓ | Type into the focused field, or a named one |
154
+ | `ios_simulator_press_button` | ✓ | Home |
155
+ | `ios_simulator_power` | ✓ | Boot or shut down; never implicit |
156
+ | `ios_simulator_erase` | ✓ **confirm** | Wipe to factory — the only irreversible tool |
157
+ | `ios_simulator_install` | ✓ | Install a simulator `.app` |
158
+ | `ios_simulator_launch` | ✓ | Launch, with fixture arguments and captured output |
159
+ | `ios_simulator_terminate` | ✓ | Kill a running app |
160
+ | `ios_simulator_open_url` | ✓ | Deep links and universal links |
161
+ | `ios_simulator_set_environment` | ✓ | Appearance, Dynamic Type, contrast, status bar, location, permissions |
162
+ | `ios_simulator_add_media` | ✓ | Seed the photo library — the way around the missing camera |
163
+ | `ios_simulator_push` | ✓ | A remote notification, with no APNs certificate |
164
+ | `ios_simulator_restart_wda` | ✓ | Start or restart the runner, detached |
163
165
 
164
166
  Deliberately absent: `create`, `clone`, `delete`, `rename`, `upgrade` and `pair`
165
167
  (fleet management, not driving, and `delete all` is a footgun with no upside);
166
168
  `get_app_container` (`list_apps` already returns the path); `uninstall`
167
- (`install` overwrites, and `erase` covers first-run properly); `addmedia`,
168
- `keychain`, `pbcopy`, `spawn`, `diagnose` and `recordVideo` (real capabilities
169
- that an agent would use approximately never, and every tool costs listing bytes
170
- on every connect).
169
+ (`install` overwrites, and `erase` covers first-run properly); `keychain`,
170
+ `pbcopy`, `spawn`, `diagnose` and `recordVideo` (real capabilities that an agent
171
+ would use approximately never, and every tool costs listing bytes on every
172
+ connect).
173
+
174
+ `addmedia` was on that list until 0.2.0, and it was the wrong call. A simulator
175
+ has no camera, so seeding the photo library is not a nice-to-have — it is the
176
+ only way an app whose first step is "choose a photo" can be driven here at all.
177
+ Anyone hitting that had to drop out to a shell, which is exactly what these
178
+ tools exist to avoid.
171
179
 
172
180
  ## Traps worth knowing
173
181
 
174
- All measured on Xcode 26.6 (17F113).
182
+ Measured on Xcode 26.6 (17F113) unless marked Xcode 27, which was checked on
183
+ Xcode 27.0 (27A266a) with an iOS 27.0 runtime.
175
184
 
176
185
  - **`simctl io … screenshot -` does not write to stdout.** The help text says it
177
186
  does. It creates a file literally named `-` in the current working directory
@@ -198,11 +207,32 @@ All measured on Xcode 26.6 (17F113).
198
207
  every later screenshot.
199
208
  - **`simctl launch` environment variables need a `SIMCTL_CHILD_` prefix** on the
200
209
  calling process, not a flag.
210
+ - **A label belongs to the control _and_ to every container around it.**
211
+ WebDriverAgent answers depth-first, so an unqualified label match lands on the
212
+ navigation bar as readily as on the button — and a tap on a container does
213
+ nothing while reporting success. `tap_element` narrows a label to the
214
+ interactive types first, and says `preferredControl` when it did.
215
+ - **`isVisible` is not always truthful.** A `PHPicker` presented over Safari
216
+ reports all nine of its asset cells `isVisible: "0"` while they are on screen
217
+ and tappable — a synthesised tap on one opens the preview. The default
218
+ `ui_tree` filter drops them, which is why the result carries a `filtered`
219
+ tally: a short list that has been filtered and a screen that is genuinely bare
220
+ are otherwise the same answer.
201
221
  - **`simctl install` wants a `.app` bundle directory** built for the simulator —
202
222
  an `.ipa` or a device build fails with "No such file or directory", which reads
203
223
  like a path typo.
204
224
  - **`simctl boot` gives you no window.** It runs headless, which is what an agent
205
- wants and confusing the first time; `power` opens Simulator.app by default.
225
+ wants and confusing the first time; `power` opens a window by default.
226
+ - **Xcode 27 ships no Simulator.app.** The simulator's window belongs to
227
+ DeviceHub (`com.apple.dt.Devices`), and `open -a Simulator` fails. `power`
228
+ tries Simulator.app first and falls back to `devices://device/<UDID>`; a
229
+ window that still will not open is a `warning` on a boot that worked, never
230
+ an error.
231
+ - **Xcode 27 moved the screen geometry out of `profile.plist`.** No device
232
+ type's profile carries `mainScreen*` any more, old types included; the same
233
+ numbers are under `capabilities.ScreenDimensionsCapability` in
234
+ `capabilities.plist`. Reading only the profile defaults the scale to 1 and
235
+ labels a 1206×2622 pixel capture as points, with nothing looking wrong.
206
236
  - **Two runtimes can share one identifier**, so the key of `list devices` is not
207
237
  a primary key.
208
238
 
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as createServer, j as BUILD_INFO, n as loadConfig, t as defaultConfigPath } from "./config-CVBCSB8L.js";
2
+ import { N as BUILD_INFO, a as createServer, n as loadConfig, t as defaultConfigPath } from "./config-D-Zn0z5X.js";
3
3
  import { ZodError } from "zod";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
5
5
  //#region src/cli.ts
@@ -23,8 +23,8 @@ const pkg = readPackageJson();
23
23
  const BUILD_INFO = {
24
24
  name: pkg.name,
25
25
  version: pkg.version,
26
- gitCommit: "3dcc1d9",
27
- gitCommitDate: "2026-09-05T14:29:39+02:00"
26
+ gitCommit: "98deba7",
27
+ gitCommitDate: "2026-09-26T14:53:12+02:00"
28
28
  };
29
29
  //#endregion
30
30
  //#region src/client/errors.ts
@@ -32,13 +32,30 @@ const BUILD_INFO = {
32
32
  const TIMEOUT_REMEDY = "Raise IOS_SIMULATOR_TIMEOUT_MS if this is a slow operation (a boot or a large `install` genuinely takes tens of seconds), or check the simulator is still booted.";
33
33
  const TOOLCHAIN_REMEDY = "Install Xcode and run `xcode-select --install`, or point IOS_SIMULATOR_XCRUN_PATH at a different xcrun.";
34
34
  /**
35
- * How to get the runner up. Much shorter than the device server's equivalent,
36
- * and that is the headline difference between the two: a simulator runner needs
37
- * no Apple team, no signing, no trust prompt and no tunnel.
35
+ * How to get the runner up, from a shell.
36
+ *
37
+ * Much shorter than the device server's equivalent, and that is the headline
38
+ * difference between the two: a simulator runner needs no Apple team, no
39
+ * signing, no trust prompt and no tunnel.
40
+ *
41
+ * This is the *fallback*. It stays because it is the only route when writes are
42
+ * off, and because it is what a person setting the server up for the first time
43
+ * needs — but a remedy that sends a caller holding ios_simulator_restart_wda out
44
+ * to a terminal is a remedy that wastes the tool it already has.
38
45
  */
39
- const START_RUNNER_REMEDY = "Build and start the runner with `scripts/wda.sh setup` then `scripts/wda.sh run` (or `npx -p @mgcrea/mcp-ios-simulator ios-simulator-wda run` from an npm install) and leave it open — the HTTP server is the XCTest process, so it stops when that command does. It needs no Apple Developer team: a simulator build is not signed.";
46
+ const START_RUNNER_SCRIPT_REMEDY = "Build and start the runner with `scripts/wda.sh setup` then `scripts/wda.sh run` (or `npx -p @mgcrea/mcp-ios-simulator ios-simulator-wda run` from an npm install) and leave it open — the HTTP server is the XCTest process, so it stops when that command does. It needs no Apple Developer team: a simulator build is not signed.";
47
+ /** The same thing, done with this server's own tool. */
48
+ const START_RUNNER_TOOL_REMEDY = "Start it with ios_simulator_restart_wda, which spawns the runner detached so it outlives this conversation. Give it about fifteen seconds, then check ios_simulator_diagnostics for `wda.reachable`.";
49
+ /**
50
+ * How to get the runner up, in whichever way the caller can actually reach.
51
+ *
52
+ * `ios_simulator_restart_wda` is registered only when writes are on, so naming
53
+ * it unconditionally would be the same mistake this fixes, pointed the other
54
+ * way: a remedy naming a tool that is not in the list.
55
+ */
56
+ const startRunnerRemedy = (allowWrites) => allowWrites ? START_RUNNER_TOOL_REMEDY : START_RUNNER_SCRIPT_REMEDY;
40
57
  /** What to do when WebDriverAgent does not answer at all. */
41
- const WDA_UNAVAILABLE_REMEDY = `${START_RUNNER_REMEDY} Everything except ios_simulator_ui_tree and the input tools works without it — screenshots go through simctl. If something else already forwards the port, set IOS_SIMULATOR_WDA_URL.`;
58
+ const wdaUnavailableRemedy = (allowWrites) => `${startRunnerRemedy(allowWrites)} Everything except ios_simulator_ui_tree and the input tools works without it — screenshots go through simctl. If something else already forwards the port, set IOS_SIMULATOR_WDA_URL.`;
42
59
  /** No simulator matched, or several did and none was named. */
43
60
  var SimulatorNotFoundError = class extends IosError {
44
61
  name = "SimulatorNotFoundError";
@@ -104,21 +121,38 @@ const EMPTY_SCREENSHOT_REMEDY = "The simulator produced an empty capture. Check
104
121
  * 150-line dump and aborts on an unavailable device. Not used.
105
122
  */
106
123
  const profilePlistPath = (bundlePath) => join(bundlePath, "Contents", "Resources", "profile.plist");
107
- const readScreenProfile = async (deviceType, opts) => {
108
- if (!deviceType.bundlePath) return {};
109
- const { stdout } = await opts.exec(opts.plutilPath, [
110
- "-convert",
111
- "json",
112
- "-o",
113
- "-",
114
- profilePlistPath(deviceType.bundlePath)
115
- ], opts.timeoutMs);
124
+ /**
125
+ * Where Xcode 27 moved the screen geometry: its `profile.plist` no longer carries
126
+ * any `mainScreen*` key — for every device type, old ones included — and the
127
+ * same three numbers live under `capabilities.ScreenDimensionsCapability`.
128
+ */
129
+ const capabilitiesPlistPath = (bundlePath) => join(bundlePath, "Contents", "Resources", "capabilities.plist");
130
+ const readPlistJson = async (path, opts) => {
116
131
  try {
132
+ const { stdout } = await opts.exec(opts.plutilPath, [
133
+ "-convert",
134
+ "json",
135
+ "-o",
136
+ "-",
137
+ path
138
+ ], opts.timeoutMs);
117
139
  return JSON.parse(stdout);
118
140
  } catch {
119
- return {};
141
+ return;
120
142
  }
121
143
  };
144
+ const readScreenProfile = async (deviceType, opts) => {
145
+ if (!deviceType.bundlePath) return {};
146
+ const profile = await readPlistJson(profilePlistPath(deviceType.bundlePath), opts) ?? {};
147
+ if (profile.mainScreenScale !== void 0) return profile;
148
+ const dims = (await readPlistJson(capabilitiesPlistPath(deviceType.bundlePath), opts))?.capabilities?.ScreenDimensionsCapability;
149
+ if (!dims) return profile;
150
+ return {
151
+ mainScreenWidth: dims["main-screen-width"],
152
+ mainScreenHeight: dims["main-screen-height"],
153
+ mainScreenScale: dims["main-screen-scale"]
154
+ };
155
+ };
122
156
  /**
123
157
  * Width and height straight out of a PNG's IHDR.
124
158
  *
@@ -362,9 +396,22 @@ var Simctl = class {
362
396
  assertNotBulkTarget(udid);
363
397
  await this.run(["erase", udid]);
364
398
  }
365
- /** Attach the Simulator UI to whatever is booted. A `boot` alone is headless. */
366
- async openApp(openPath) {
367
- await this.exec(openPath, ["-a", "Simulator"], this.opts.timeoutMs);
399
+ /**
400
+ * Put a window on a booted simulator. A `boot` alone is headless.
401
+ *
402
+ * Xcode 27 ships no Simulator.app — the window belongs to DeviceHub, which
403
+ * answers `devices://device/<UDID>` — while Xcode 26 and earlier only have
404
+ * Simulator.app. Try the old app first, since on a Mac with both the one the
405
+ * user already has open is the one they expect; fall back to DeviceHub.
406
+ */
407
+ async openApp(openPath, udid) {
408
+ try {
409
+ await this.exec(openPath, ["-a", "Simulator"], this.opts.timeoutMs);
410
+ return "Simulator";
411
+ } catch {
412
+ await this.exec(openPath, [`devices://device/${udid}`], this.opts.timeoutMs);
413
+ return "DeviceHub";
414
+ }
368
415
  }
369
416
  async listApps(udid) {
370
417
  assertNotBulkTarget(udid);
@@ -380,6 +427,23 @@ var Simctl = class {
380
427
  ]);
381
428
  }
382
429
  /**
430
+ * Put photos and videos in the simulator's library.
431
+ *
432
+ * `addmedia` is variadic and takes the paths as plain argv, so unlike `push`
433
+ * this needs no temp file. It is the one route around the simulator's missing
434
+ * camera: an app that reads from the photo library can be exercised end to
435
+ * end, while one that insists on a live capture still cannot.
436
+ */
437
+ async addMedia(udid, paths) {
438
+ assertNotBulkTarget(udid);
439
+ for (const path of paths) assertNoShellMetachars("path", path);
440
+ await this.run([
441
+ "addmedia",
442
+ udid,
443
+ ...paths
444
+ ]);
445
+ }
446
+ /**
383
447
  * Launch, optionally replacing a running copy.
384
448
  *
385
449
  * Environment variables do not go on the command line: simctl reads them from
@@ -553,13 +617,20 @@ var Simctl = class {
553
617
  };
554
618
  //#endregion
555
619
  //#region src/client/simulator.ts
556
- /** What to say when WebDriverAgent fails, on a simulator. */
557
- const SIMULATOR_WDA_REMEDIES = {
558
- unavailable: WDA_UNAVAILABLE_REMEDY,
559
- notAuthorized: `WebDriverAgent is running but is refusing to drive the UI. On a simulator there is no Enable UI Automation toggle to blame, so this is nearly always a stale runner. ${START_RUNNER_REMEDY}`,
560
- noForegroundApp: "WebDriverAgent can see no foreground application on a session it has just created. Launch the app with ios_simulator_launch, or restart the runner with ios_simulator_restart_wda.",
620
+ /**
621
+ * What to say when WebDriverAgent fails, on a simulator.
622
+ *
623
+ * A function of the write gate rather than a constant: three of these four
624
+ * remedies name ios_simulator_restart_wda, and that tool is not registered when
625
+ * writes are off. Sending a caller to a tool it cannot see is the same failure
626
+ * as sending it to a shell it did not need.
627
+ */
628
+ const simulatorWdaRemedies = (allowWrites) => ({
629
+ unavailable: wdaUnavailableRemedy(allowWrites),
630
+ notAuthorized: "WebDriverAgent is running but is refusing to drive the UI. On a simulator there is no Enable UI Automation toggle to blame, so this is nearly always a stale runner. " + startRunnerRemedy(allowWrites),
631
+ noForegroundApp: `WebDriverAgent can see no foreground application on a session it has just created. Launch the app with ios_simulator_launch, or restart the runner. ${startRunnerRemedy(allowWrites)}`,
561
632
  noSuchElement: "Call ios_simulator_ui_tree to see what is actually on screen — the element may not have appeared yet."
562
- };
633
+ });
563
634
  var SimulatorClient = class {
564
635
  simctl;
565
636
  exec;
@@ -708,7 +779,7 @@ var SimulatorClient = class {
708
779
  const client = new WdaClient({
709
780
  baseUrl: async () => url,
710
781
  timeoutMs: this.opts.wdaTimeoutMs,
711
- remedies: SIMULATOR_WDA_REMEDIES,
782
+ remedies: simulatorWdaRemedies(this.opts.allowWrites !== false),
712
783
  ...this.opts.fetch ? { fetch: this.opts.fetch } : {},
713
784
  ...this.opts.logger ? { logger: this.opts.logger } : {}
714
785
  });
@@ -943,6 +1014,7 @@ const diagnose = async (client, ctx, deviceHint) => {
943
1014
  problems.push("No simulator is booted.");
944
1015
  nextSteps.push("Boot one with ios_simulator_power {\"state\":\"booted\"} — this server never boots implicitly.");
945
1016
  }
1017
+ const nothingBooted = simulators.length > 0 && booted.length === 0;
946
1018
  let target;
947
1019
  try {
948
1020
  target = await client.resolveTarget(deviceHint);
@@ -971,7 +1043,7 @@ const diagnose = async (client, ctx, deviceHint) => {
971
1043
  } catch (err) {
972
1044
  wda.error = err instanceof Error ? err.message : String(err);
973
1045
  problems.push("WebDriverAgent is not answering, so the UI tree and the input tools will fail.");
974
- nextSteps.push(`${START_RUNNER_REMEDY} Everything else — including screenshots — works without it.`);
1046
+ nextSteps.push((nothingBooted ? "Then, once it is booted: " : "") + startRunnerRemedy(ctx.allowWrites) + " Everything else — including screenshots — works without it.");
975
1047
  }
976
1048
  if (wda.reachable) {
977
1049
  const owner = await portOwner(client, client.wdaPort);
@@ -1157,6 +1229,27 @@ const registerEnvironmentTools = (server, client) => {
1157
1229
  }
1158
1230
  };
1159
1231
  }));
1232
+ server.registerTool("ios_simulator_add_media", {
1233
+ title: "iOS Simulator: Add Media",
1234
+ description: "Put photos or videos into the simulator's photo library. A simulator has no camera, and this is how you get around that: seed the library first, then drive the app's picker as normal. Without it, every flow that starts with an image is untestable here. Files are copied in, so the originals are untouched and the simulator keeps them until it is erased. Pair it with `permission` on ios_simulator_set_environment to skip the access prompt entirely.",
1235
+ inputSchema: z.object({
1236
+ device: deviceArg,
1237
+ paths: z.array(z.string()).min(1).describe("Absolute paths to the images or videos, e.g. [\"/Users/me/fixtures/monstera.jpg\"]. Formats are the ones Photos itself accepts — JPEG, PNG, HEIC, MOV, MP4.")
1238
+ }),
1239
+ annotations: {
1240
+ readOnlyHint: false,
1241
+ destructiveHint: false,
1242
+ idempotentHint: false
1243
+ }
1244
+ }, async ({ device, paths }) => wrap(async () => {
1245
+ for (const path of paths) if (!path.startsWith("/")) throw new IosError$1(`Every path must be absolute, got "${path}".`, { remedy: "Pass the full path to each file." });
1246
+ const target = client.requireBooted(await client.resolveTarget(device));
1247
+ await client.simctl.addMedia(target.id, paths);
1248
+ return {
1249
+ added: paths,
1250
+ udid: target.id
1251
+ };
1252
+ }));
1160
1253
  server.registerTool("ios_simulator_push", {
1161
1254
  title: "iOS Simulator: Push",
1162
1255
  description: "Deliver a push notification, with no APNs certificate and no server. The payload is a normal remote-notification body and must contain an `aps` key. Only remote pushes are simulated — not VoIP, complications or file-provider. The app must be installed; it does not have to be running.",
@@ -1190,16 +1283,17 @@ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1190
1283
  const registerListTool = (server, client) => {
1191
1284
  server.registerTool("ios_simulator_list", {
1192
1285
  title: "iOS Simulator: List",
1193
- description: "List the simulators on this machine, with the UDID every other tool takes. Returns all of them by default — booted and shut down, available and not — because \"why can't I use that one\" has to be answerable from this list: a simulator whose runtime is missing shows `available: false` with the reason.",
1286
+ description: "List the simulators on this machine, with the UDID every other tool takes. Shows the usable ones: a typical machine carries thirty-odd simulators and a good fraction of them are orphans whose runtime is no longer installed, which cannot be booted, driven or screenshotted at all. The result still says how many were left out and how to delete them, and `include_unavailable` brings them back with `available: false` and the reason, so \"why can't I use that one\" is still answerable here.",
1194
1287
  inputSchema: z.object({
1195
1288
  booted_only: z.boolean().default(false).describe("Only simulators that are currently running."),
1289
+ include_unavailable: z.boolean().default(false).describe("Also list simulators whose runtime is not installed. Off by default because they are not usable for anything; turn it on to see why a specific one is missing."),
1196
1290
  name_contains: z.string().optional().describe("Case-insensitive substring filter on the name, e.g. \"iPhone 17\".")
1197
1291
  }),
1198
1292
  annotations: { readOnlyHint: true }
1199
- }, async ({ booted_only, name_contains }) => wrap(async () => {
1293
+ }, async ({ booted_only, include_unavailable, name_contains }) => wrap(async () => {
1200
1294
  const all = await client.listSimulators({ fresh: true });
1201
1295
  const needle = name_contains?.toLowerCase();
1202
- const matched = all.filter((sim) => (!booted_only || sim.state === "Booted") && (needle === void 0 || sim.name.toLowerCase().includes(needle)));
1296
+ const matched = all.filter((sim) => (include_unavailable || sim.available) && (!booted_only || sim.state === "Booted") && (needle === void 0 || sim.name.toLowerCase().includes(needle)));
1203
1297
  const unavailable = all.filter((sim) => !sim.available).length;
1204
1298
  return {
1205
1299
  simulators: matched.map(toListRow),
@@ -1207,7 +1301,7 @@ const registerListTool = (server, client) => {
1207
1301
  total: all.length,
1208
1302
  ...unavailable > 0 ? {
1209
1303
  unavailable,
1210
- note: `${unavailable} simulators have no installed runtime and cannot be used. \`xcrun simctl delete unavailable\` clears them.`
1304
+ note: `${unavailable} simulators have no installed runtime and cannot be used` + (include_unavailable ? "" : ", and are not listed above") + ". `xcrun simctl delete unavailable` clears them, or pass `include_unavailable: true` to see them here."
1211
1305
  } : {}
1212
1306
  };
1213
1307
  }));
@@ -1219,7 +1313,7 @@ const registerLifecycleTools = (server, client) => {
1219
1313
  inputSchema: z.object({
1220
1314
  device: deviceArg,
1221
1315
  state: z.enum(["booted", "shutdown"]).describe("The state to put it in. `booted` also waits for it to actually get there."),
1222
- open_window: z.boolean().default(true).describe("Bring up the Simulator app so the screen is visible. A `boot` on its own is headless, which is fine for an agent and confusing for a person watching."),
1316
+ open_window: z.boolean().default(true).describe("Bring up a window on the device — Simulator.app, or DeviceHub on Xcode 27 — so the screen is visible. A `boot` on its own is headless, which is fine for an agent and confusing for a person watching."),
1223
1317
  wait_ms: z.number().int().min(0).max(3e5).default(6e4).describe("How long to wait for a boot to complete before reporting what it saw.")
1224
1318
  }),
1225
1319
  annotations: {
@@ -1245,7 +1339,10 @@ const registerLifecycleTools = (server, client) => {
1245
1339
  }
1246
1340
  const already = target.state === "Booted";
1247
1341
  if (!already) await client.simctl.boot(target.id);
1248
- if (open_window) await client.simctl.openApp(client.openPath);
1342
+ let windowWarning;
1343
+ if (open_window) await client.simctl.openApp(client.openPath, target.id).catch((err) => {
1344
+ windowWarning = `Booted, but no window could be opened: ${err instanceof Error ? err.message : String(err)}`;
1345
+ });
1249
1346
  const deadline = Date.now() + wait_ms;
1250
1347
  let observed = target.state;
1251
1348
  while (Date.now() < deadline) {
@@ -1258,7 +1355,7 @@ const registerLifecycleTools = (server, client) => {
1258
1355
  name: target.name,
1259
1356
  state: observed,
1260
1357
  ...already ? { alreadyThere: true } : {},
1261
- ...observed === "Booted" ? {} : { warning: `Still ${observed} after ${wait_ms}ms. Raise wait_ms, or check Xcode.` }
1358
+ ...observed === "Booted" ? windowWarning ? { warning: windowWarning } : {} : { warning: `Still ${observed} after ${wait_ms}ms. Raise wait_ms, or check Xcode.` }
1262
1359
  };
1263
1360
  }));
1264
1361
  server.registerTool("ios_simulator_erase", {
@@ -1338,7 +1435,7 @@ const registerRunnerTools = (server, client, outputDir, spawnRunner = defaultSpa
1338
1435
  },
1339
1436
  logPath
1340
1437
  });
1341
- if (pid === void 0) throw new IosError$1("Could not start the runner — the process had no pid.", { remedy: START_RUNNER_REMEDY });
1438
+ if (pid === void 0) throw new IosError$1("Could not start the runner — the process had no pid.", { remedy: START_RUNNER_SCRIPT_REMEDY });
1342
1439
  return {
1343
1440
  started: true,
1344
1441
  pid,
@@ -1379,6 +1476,7 @@ const registerTools = (server, client, ctx, spawnRunner) => {
1379
1476
  capHint: "raise IOS_SIMULATOR_MAX_TREE_BYTES",
1380
1477
  buttons: ["home"],
1381
1478
  emptyScreenshotRemedy: EMPTY_SCREENSHOT_REMEDY,
1479
+ alertHint: "If this is a permission prompt, it can be answered before it ever appears: ios_simulator_set_environment {\"permission\":{\"action\":\"grant\",\"service\":\"photos\",\"bundle_id\":\"…\"}}. `reset` puts the prompt back for testing the other branch.",
1382
1480
  includeDisplayInfo: false
1383
1481
  };
1384
1482
  registerDiagnosticsTools(server, client, ctx);
@@ -1416,6 +1514,7 @@ const createServer = (opts) => {
1416
1514
  execTimeoutMs: config.execTimeoutMs,
1417
1515
  wdaTimeoutMs: config.wdaTimeoutMs,
1418
1516
  wdaPort: config.wdaPort,
1517
+ allowWrites: config.allowWrites,
1419
1518
  ...config.wdaUrl ? { wdaUrl: config.wdaUrl } : {},
1420
1519
  ...config.simulatorId ? { defaultSimulatorId: config.simulatorId } : {},
1421
1520
  ...opts.exec ? { exec: opts.exec } : {},
@@ -1548,6 +1647,6 @@ const loadConfig = (env = process.env, configPath = defaultConfigPath(env)) => {
1548
1647
  });
1549
1648
  };
1550
1649
  //#endregion
1551
- export { WritesDisabledError as A, SimulatorNotFoundError as C, WDA_UNAVAILABLE_REMEDY as D, ToolchainError as E, WdaError as O, SimulatorNotBootedError as S, TOOLCHAIN_REMEDY as T, CommandTimeoutError as _, createServer as a, RuntimeUnavailableError as b, assertNotBulkTarget as c, summarizeDevices as d, toListRow as f, CommandError as g, toDisplayInfo as h, SERVER_VERSION as i, BUILD_INFO as j, WdaUnavailableError as k, parseSimctlError as l, readScreenProfile as m, loadConfig as n, SimulatorClient as o, pngDimensions as p, SERVER_NAME as r, Simctl as s, defaultConfigPath as t, summarizeApps as u, EMPTY_SCREENSHOT_REMEDY as v, TIMEOUT_REMEDY as w, START_RUNNER_REMEDY as x, IosError$1 as y };
1650
+ export { WritesDisabledError as A, SimulatorNotBootedError as C, ToolchainError as D, TOOLCHAIN_REMEDY as E, wdaUnavailableRemedy as M, BUILD_INFO as N, WdaError as O, START_RUNNER_TOOL_REMEDY as S, TIMEOUT_REMEDY as T, CommandTimeoutError as _, createServer as a, RuntimeUnavailableError as b, assertNotBulkTarget as c, summarizeDevices as d, toListRow as f, CommandError as g, toDisplayInfo as h, SERVER_VERSION as i, startRunnerRemedy as j, WdaUnavailableError as k, parseSimctlError as l, readScreenProfile as m, loadConfig as n, SimulatorClient as o, pngDimensions as p, SERVER_NAME as r, Simctl as s, defaultConfigPath as t, summarizeApps as u, EMPTY_SCREENSHOT_REMEDY as v, SimulatorNotFoundError as w, START_RUNNER_SCRIPT_REMEDY as x, IosError$1 as y };
1552
1651
 
1553
- //# sourceMappingURL=config-CVBCSB8L.js.map
1652
+ //# sourceMappingURL=config-D-Zn0z5X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-D-Zn0z5X.js","names":["IosError","IosError","IosError","IosError","IosError"],"sources":["../src/build-info.ts","../src/client/errors.ts","../src/client/display.ts","../src/client/shape.ts","../src/client/simctl.ts","../src/client/simulator.ts","../src/tools/util.ts","../src/tools/apps.ts","../src/tools/diagnostics.ts","../src/tools/environment.ts","../src/tools/lifecycle.ts","../src/tools/runner.ts","../src/tools/index.ts","../src/server.ts","../src/config.ts"],"sourcesContent":["// Build-time / runtime identity for the running server. `name`/`version` are\n// read from package.json at startup (always accurate); `gitCommit` /\n// `gitCommitDate` are injected by tsdown's `define` substitution at build time\n// and fall back to \"unknown\" when running from source (e.g. vitest).\n\nimport { readFileSync } from \"node:fs\";\n\n// oxlint-disable no-underscore-dangle -- bundler-injected build-time constants.\ndeclare const __GIT_COMMIT__: string;\ndeclare const __GIT_COMMIT_DATE__: string;\n\ntype PackageJson = { name: string; version: string };\n\nconst readPackageJson = (): PackageJson => {\n try {\n const pkgUrl = new URL(\"../package.json\", import.meta.url);\n return JSON.parse(readFileSync(pkgUrl, \"utf8\")) as PackageJson;\n } catch {\n return { name: \"@mgcrea/mcp-ios-device\", version: \"0.0.0\" };\n }\n};\n\nconst pkg = readPackageJson();\n\nexport type BuildInfo = {\n name: string;\n version: string;\n gitCommit: string;\n gitCommitDate: string;\n};\n\nexport const BUILD_INFO: BuildInfo = {\n name: pkg.name,\n version: pkg.version,\n gitCommit: typeof __GIT_COMMIT__ === \"string\" ? __GIT_COMMIT__ : \"unknown\",\n gitCommitDate: typeof __GIT_COMMIT_DATE__ === \"string\" ? __GIT_COMMIT_DATE__ : \"unknown\",\n};\n","import { IosError } from \"@mgcrea/mcp-ios-core\";\n\nexport {\n CommandError,\n CommandTimeoutError,\n IosError,\n ToolchainError,\n WdaError,\n WdaUnavailableError,\n} from \"@mgcrea/mcp-ios-core\";\n\n/** Raising the budget, or noticing the simulator died mid-call. */\nexport const TIMEOUT_REMEDY =\n \"Raise IOS_SIMULATOR_TIMEOUT_MS if this is a slow operation (a boot or a large `install` \" +\n \"genuinely takes tens of seconds), or check the simulator is still booted.\";\n\nexport const TOOLCHAIN_REMEDY =\n \"Install Xcode and run `xcode-select --install`, or point IOS_SIMULATOR_XCRUN_PATH at a \" +\n \"different xcrun.\";\n\n/**\n * How to get the runner up, from a shell.\n *\n * Much shorter than the device server's equivalent, and that is the headline\n * difference between the two: a simulator runner needs no Apple team, no\n * signing, no trust prompt and no tunnel.\n *\n * This is the *fallback*. It stays because it is the only route when writes are\n * off, and because it is what a person setting the server up for the first time\n * needs — but a remedy that sends a caller holding ios_simulator_restart_wda out\n * to a terminal is a remedy that wastes the tool it already has.\n */\nexport const START_RUNNER_SCRIPT_REMEDY =\n \"Build and start the runner with `scripts/wda.sh setup` then `scripts/wda.sh run` (or \" +\n \"`npx -p @mgcrea/mcp-ios-simulator ios-simulator-wda run` from an npm install) and leave it \" +\n \"open — the HTTP server is the XCTest process, so it stops when that command does. It needs no \" +\n \"Apple Developer team: a simulator build is not signed.\";\n\n/** The same thing, done with this server's own tool. */\nexport const START_RUNNER_TOOL_REMEDY =\n \"Start it with ios_simulator_restart_wda, which spawns the runner detached so it outlives this \" +\n \"conversation. Give it about fifteen seconds, then check ios_simulator_diagnostics for \" +\n \"`wda.reachable`.\";\n\n/**\n * How to get the runner up, in whichever way the caller can actually reach.\n *\n * `ios_simulator_restart_wda` is registered only when writes are on, so naming\n * it unconditionally would be the same mistake this fixes, pointed the other\n * way: a remedy naming a tool that is not in the list.\n */\nexport const startRunnerRemedy = (allowWrites: boolean): string =>\n allowWrites ? START_RUNNER_TOOL_REMEDY : START_RUNNER_SCRIPT_REMEDY;\n\n/** What to do when WebDriverAgent does not answer at all. */\nexport const wdaUnavailableRemedy = (allowWrites: boolean): string =>\n `${startRunnerRemedy(allowWrites)} Everything except ios_simulator_ui_tree and the input ` +\n \"tools works without it — screenshots go through simctl. If something else already forwards \" +\n \"the port, set IOS_SIMULATOR_WDA_URL.\";\n\n/** No simulator matched, or several did and none was named. */\nexport class SimulatorNotFoundError extends IosError {\n override readonly name = \"SimulatorNotFoundError\";\n\n constructor(message: string, remedy: string) {\n super(message, { remedy });\n }\n}\n\n/**\n * The simulator exists but is shut down.\n *\n * Its own error class because the fix is a single specific call and nothing\n * else: almost every simctl subcommand fails this way, `listapps` included, so\n * \"it is shut down\" would otherwise be buried in a CoreSimulator envelope that\n * reads like a lookup bug.\n */\nexport class SimulatorNotBootedError extends IosError {\n override readonly name = \"SimulatorNotBootedError\";\n\n constructor(name: string, udid: string) {\n super(`${name} is shut down.`, {\n remedy:\n `Boot it with ios_simulator_power {\"simulator\":\"${udid}\",\"state\":\"booted\"} — this server ` +\n \"never boots a simulator implicitly, because a cold boot takes tens of seconds and puts a \" +\n \"window on the user's screen.\",\n });\n }\n}\n\n/**\n * The simulator's runtime is not installed, so CoreSimulator cannot service it.\n *\n * Worth its own class because the failure it prevents is not an error message\n * at all: `simctl io` against one of these aborts with an uncaught\n * NSInternalInconsistencyException and a thirty-line stack trace.\n */\nexport class RuntimeUnavailableError extends IosError {\n override readonly name = \"RuntimeUnavailableError\";\n\n constructor(name: string, reason: string | undefined) {\n super(`${name} is unavailable${reason ? `: ${reason}` : \".\"}`, {\n remedy:\n \"The iOS runtime this simulator was created against is not installed. Install it in \" +\n \"Xcode > Settings > Components, or clear the orphans with `xcrun simctl delete unavailable`.\",\n });\n }\n}\n\n/** Thrown when a write path is reached while IOS_SIMULATOR_ALLOW_WRITES is off. */\nexport class WritesDisabledError extends IosError {\n override readonly name = \"WritesDisabledError\";\n\n constructor(what: string) {\n super(\n `${what} changes the simulator, but writes are disabled. ` +\n \"Unset IOS_SIMULATOR_ALLOW_WRITES (it defaults to on) to enable them again.\",\n );\n }\n}\n\n/** A shut-down simulator captures nothing, and that is the usual cause here. */\nexport const EMPTY_SCREENSHOT_REMEDY =\n \"The simulator produced an empty capture. Check it is actually booted with \" +\n \"ios_simulator_diagnostics, and that its runtime is installed.\";\n","import { join } from \"node:path\";\n\nimport type { DisplayInfo, ExecImpl } from \"@mgcrea/mcp-ios-core\";\n\nimport { IosError } from \"#/client/errors\";\nimport type { RawDeviceType } from \"#/client/simctl\";\n\n/**\n * Screen geometry for a simulator, which has no `devicectl device info displays`.\n *\n * Three sources were measured, and the split between them is the interesting\n * part:\n *\n * - **`profile.plist`, per device type.** `mainScreenWidth`, `mainScreenHeight`\n * and `mainScreenScale`, agreeing exactly with both `io enumerate` and the\n * captured PNG. A pure file read: no simctl call, nothing to boot, and no\n * crash surface on an unavailable simulator. This is where `pointScale`\n * comes from.\n * - **The captured PNG's own header.** This is where *pixels* come from\n * whenever there is a capture, because `simctl io … screenConfig geometry`\n * can resize a booted simulator's screen at runtime and the profile would\n * then be silently wrong — putting every tap coordinate in the wrong space\n * with nothing looking wrong.\n * - **`io enumerate`**, which also reports it, but is a text parse of a\n * 150-line dump and aborts on an unavailable device. Not used.\n */\nexport const profilePlistPath = (bundlePath: string): string =>\n join(bundlePath, \"Contents\", \"Resources\", \"profile.plist\");\n\nexport type ScreenProfile = {\n mainScreenWidth?: number;\n mainScreenHeight?: number;\n mainScreenScale?: number;\n};\n\n/**\n * Where Xcode 27 moved the screen geometry: its `profile.plist` no longer carries\n * any `mainScreen*` key — for every device type, old ones included — and the\n * same three numbers live under `capabilities.ScreenDimensionsCapability`.\n */\nexport const capabilitiesPlistPath = (bundlePath: string): string =>\n join(bundlePath, \"Contents\", \"Resources\", \"capabilities.plist\");\n\ntype CapabilitiesPlist = {\n capabilities?: {\n ScreenDimensionsCapability?: {\n \"main-screen-width\"?: number;\n \"main-screen-height\"?: number;\n \"main-screen-scale\"?: number;\n };\n };\n};\n\ntype PlistOpts = { plutilPath: string; exec: ExecImpl; timeoutMs: number };\n\nconst readPlistJson = async <T>(path: string, opts: PlistOpts): Promise<T | undefined> => {\n try {\n const { stdout } = await opts.exec(\n opts.plutilPath,\n [\"-convert\", \"json\", \"-o\", \"-\", path],\n opts.timeoutMs,\n );\n return JSON.parse(stdout) as T;\n } catch {\n return undefined;\n }\n};\n\nexport const readScreenProfile = async (\n deviceType: RawDeviceType,\n opts: PlistOpts,\n): Promise<ScreenProfile> => {\n if (!deviceType.bundlePath) return {};\n const profile =\n (await readPlistJson<ScreenProfile>(profilePlistPath(deviceType.bundlePath), opts)) ?? {};\n if (profile.mainScreenScale !== undefined) return profile;\n\n const dims = (\n await readPlistJson<CapabilitiesPlist>(capabilitiesPlistPath(deviceType.bundlePath), opts)\n )?.capabilities?.ScreenDimensionsCapability;\n if (!dims) return profile;\n return {\n mainScreenWidth: dims[\"main-screen-width\"],\n mainScreenHeight: dims[\"main-screen-height\"],\n mainScreenScale: dims[\"main-screen-scale\"],\n };\n};\n\n/**\n * Width and height straight out of a PNG's IHDR.\n *\n * Cheaper than shelling to `sips` for the one number that must not be stale,\n * and the bytes are already in hand: the signature is fixed, `IHDR` is always\n * the first chunk, and the two dimensions are big-endian at offsets 16 and 20.\n */\nexport const pngDimensions = (png: Buffer): { width: number; height: number } | undefined => {\n const SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n if (png.length < 24 || !png.subarray(0, 8).equals(SIGNATURE)) return undefined;\n if (png.subarray(12, 16).toString(\"ascii\") !== \"IHDR\") return undefined;\n return { width: png.readUInt32BE(16), height: png.readUInt32BE(20) };\n};\n\n/**\n * Assemble a `DisplayInfo`.\n *\n * `orientation` is derived by comparing the capture's own aspect against the\n * profile's portrait dimensions, and is `\"unknown\"` when there is no capture to\n * compare — never guessed as `\"portrait\"`, because the whole point of the field\n * is telling a caller whether the screen they are about to read is rotated.\n *\n * `backlightState` is left undefined: a simulator has no backlight, and\n * `io enumerate`'s `Power state` is about the framebuffer port rather than the\n * screen, so mapping it would be a lie.\n */\nexport const toDisplayInfo = (\n profile: ScreenProfile,\n capture?: { width: number; height: number },\n): DisplayInfo => {\n const scale = profile.mainScreenScale ?? 1;\n const pixelWidth = capture?.width ?? profile.mainScreenWidth;\n const pixelHeight = capture?.height ?? profile.mainScreenHeight;\n\n if (!pixelWidth || !pixelHeight) {\n throw new IosError(\"Could not determine the simulator's screen geometry.\", {\n remedy:\n \"The device type's profile.plist could not be read and no screenshot was available. \" +\n \"Run ios_simulator_diagnostics — an unavailable runtime is the usual cause.\",\n });\n }\n\n const landscape =\n profile.mainScreenWidth !== undefined && profile.mainScreenHeight !== undefined\n ? pixelWidth > pixelHeight && profile.mainScreenWidth < profile.mainScreenHeight\n : pixelWidth > pixelHeight;\n\n return {\n pixelWidth,\n pixelHeight,\n pointWidth: Math.round(pixelWidth / scale),\n pointHeight: Math.round(pixelHeight / scale),\n pointScale: scale,\n orientation: capture ? (landscape ? \"landscape\" : \"portrait\") : \"unknown\",\n };\n};\n","// The context-window layer.\n//\n// `simctl listapps` spends ~38 KB on the 31 apps a stock simulator carries, 25\n// of which are Apple's own, and most of every entry is group-container UUIDs no\n// caller acts on. `list devices` is smaller but still reports five keys per\n// simulator that only matter to CoreSimulator.\n//\n// So: lists return the few fields the other tools consume, and nothing else.\n\nimport type { RawDeviceType, RawRuntime, RawSim, RawSimApp } from \"#/client/simctl\";\n\nexport type SimulatorSummary = {\n /**\n * The UDID. Called `id` because that is the name the shared `ScreenHost`\n * contract uses — a physical device has two identifiers and a simulator has\n * one, so there is nothing here to disambiguate. Rendered back as `udid` by\n * `toListRow`, which is the word anyone reading the output will know it by.\n */\n id: string;\n name: string;\n /** `Booted` or `Shutdown`, verbatim from CoreSimulator. */\n state: string;\n runtime: string;\n deviceType: string | undefined;\n deviceTypeIdentifier: string | undefined;\n /**\n * False when the runtime is missing. Kept rather than filtered: \"why can't I\n * use that one\" has to be answerable from the list, and an unavailable\n * simulator is the one input that makes `simctl io` abort rather than fail.\n */\n available: boolean;\n unavailableReason?: string;\n lastBootedAt?: string;\n};\n\n/**\n * Flatten the runtime-keyed map `list devices` returns.\n *\n * The key is a runtime *identifier* and identifiers are not unique — a machine\n * carrying both iOS 26.4 and 26.4.1 has two entries under\n * `com.apple.CoreSimulator.SimRuntime.iOS-26-4`. So the key is never used as a\n * primary key: it is joined against `list runtimes` for a display name, 0..n\n * matches tolerated, and carried through verbatim besides.\n */\nexport const summarizeDevices = (\n devices: Record<string, RawSim[]>,\n runtimes: RawRuntime[],\n deviceTypes: RawDeviceType[],\n): SimulatorSummary[] => {\n const runtimeName = new Map(\n runtimes.map((r) => [r.identifier, r.name ?? r.version ?? r.identifier]),\n );\n const typeName = new Map(deviceTypes.map((t) => [t.identifier, t.name]));\n\n return Object.entries(devices).flatMap(([runtimeId, sims]) =>\n (sims ?? []).map((sim) => ({\n id: sim.udid,\n name: sim.name,\n state: sim.state,\n runtime: runtimeName.get(runtimeId) ?? runtimeId.split(\".\").pop() ?? runtimeId,\n deviceType: sim.deviceTypeIdentifier ? typeName.get(sim.deviceTypeIdentifier) : undefined,\n deviceTypeIdentifier: sim.deviceTypeIdentifier,\n available: sim.isAvailable !== false,\n ...(sim.availabilityError ? { unavailableReason: sim.availabilityError } : {}),\n ...(sim.lastBootedAt ? { lastBootedAt: sim.lastBootedAt } : {}),\n })),\n );\n};\n\nexport type SimAppSummary = {\n bundleId: string;\n name: string | undefined;\n version: string | undefined;\n build: string | undefined;\n type: string | undefined;\n /**\n * Host filesystem paths, and the reason there is no `pull_container` tool: a\n * simulator's containers are already on this machine, so reading an app's\n * SwiftData store is an ordinary file read rather than a copy off a device.\n */\n bundlePath: string | undefined;\n dataContainer: string | undefined;\n};\n\n/** `file:///Users/…/Foo.app/` → `/Users/…/Foo.app`, which is what a person can use. */\nconst toPath = (url: string | undefined): string | undefined => {\n if (!url) return undefined;\n if (!url.startsWith(\"file://\")) return url;\n try {\n return decodeURIComponent(new URL(url).pathname).replace(/\\/$/, \"\");\n } catch {\n return url;\n }\n};\n\nexport const summarizeApps = (\n apps: Record<string, RawSimApp>,\n opts: { includeAll?: boolean } = {},\n): SimAppSummary[] =>\n Object.values(apps)\n .filter((app) => opts.includeAll === true || app.ApplicationType === \"User\")\n .map((app) => ({\n bundleId: app.CFBundleIdentifier ?? \"\",\n name: app.CFBundleDisplayName ?? app.CFBundleName,\n version: app.CFBundleShortVersionString,\n build: app.CFBundleVersion,\n type: app.ApplicationType,\n bundlePath: toPath(app.Bundle ?? app.Path),\n dataContainer: toPath(app.DataContainer),\n }))\n .filter((app) => app.bundleId !== \"\")\n .toSorted((a, b) => a.bundleId.localeCompare(b.bundleId));\n\n/** The public shape of one row of `ios_simulator_list`. */\nexport const toListRow = (sim: SimulatorSummary): Record<string, unknown> => ({\n udid: sim.id,\n name: sim.name,\n state: sim.state,\n runtime: sim.runtime,\n ...(sim.deviceType ? { deviceType: sim.deviceType } : {}),\n ...(sim.available ? {} : { available: false }),\n ...(sim.unavailableReason ? { unavailableReason: sim.unavailableReason } : {}),\n});\n","import { mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport {\n assertNoShellMetachars,\n createExec,\n type ExecImpl,\n type Logger,\n} from \"@mgcrea/mcp-ios-core\";\n\nimport { IosError, TIMEOUT_REMEDY, TOOLCHAIN_REMEDY } from \"#/client/errors\";\n\n/**\n * `xcrun simctl` — Apple's own simulator tool, and the half of this server that\n * needs nothing installed and nothing signed.\n *\n * It covers strictly more here than `devicectl` does on a physical device: app\n * lifecycle, but also the screen itself (`io screenshot`), the appearance, the\n * status bar, permissions, location and push. Only the accessibility tree and\n * synthetic touches need WebDriverAgent.\n *\n * Three output shapes, and knowing which is which explains the whole class:\n *\n * - `list` takes `-j` and writes clean JSON to **stdout**. It is the only\n * subcommand that does; `listapps --json` does not exist.\n * - `listapps` writes an old-style NeXTSTEP plist. `plutil` converts it, but\n * `ExecImpl` has no stdin, so it goes through a temp file and two processes.\n * - everything else answers in prose, including `(null)` and `unknown`, which\n * are passed through rather than normalised — see the readers below.\n */\nexport type SimctlOptions = {\n xcrunPath: string;\n plutilPath: string;\n timeoutMs: number;\n exec?: ExecImpl | undefined;\n logger?: Logger | undefined;\n};\n\nexport type RawSim = {\n udid: string;\n name: string;\n state: string;\n isAvailable?: boolean;\n availabilityError?: string;\n deviceTypeIdentifier?: string;\n lastBootedAt?: string;\n};\n\nexport type RawRuntime = {\n identifier: string;\n name?: string;\n version?: string;\n isAvailable?: boolean;\n};\n\nexport type RawDeviceType = {\n identifier: string;\n name?: string;\n bundlePath?: string;\n modelIdentifier?: string;\n};\n\n/** One app as `listapps` reports it, once plutil has turned it into JSON. */\nexport type RawSimApp = {\n CFBundleIdentifier?: string;\n CFBundleDisplayName?: string;\n CFBundleName?: string;\n CFBundleShortVersionString?: string;\n CFBundleVersion?: string;\n ApplicationType?: string;\n Bundle?: string;\n Path?: string;\n DataContainer?: string;\n};\n\n/**\n * The two exit codes simctl actually uses, and what they mean.\n *\n * `148` is a device it could not even parse as an identifier. `149` is\n * everything else, wrapped in an envelope whose first line is machine-readable\n * and whose second line is the only part worth showing a person.\n */\nconst INVALID_DEVICE = 148;\n\n/**\n * Pull the human sentence out of a CoreSimulator error envelope.\n *\n * An error was encountered processing the command (domain=…, code=405):\n * Unable to lookup in current state: Shutdown\n *\n * Without this the message a caller sees is the first line, which names a\n * numeric code and says nothing about what to do.\n */\nexport const parseSimctlError = (\n stderr: string,\n): { domain?: string; code?: number; message: string } => {\n const envelope = /\\(domain=([^,]+),\\s*code=(\\d+)\\)\\s*:?\\s*\\n?([\\s\\S]*)/.exec(stderr);\n if (envelope) {\n return {\n domain: envelope[1]?.trim(),\n code: Number(envelope[2]),\n message: (envelope[3] ?? \"\").trim().split(\"\\n\")[0]?.trim() ?? stderr.trim(),\n };\n }\n return { message: stderr.trim().split(\"\\n\")[0]?.trim() ?? stderr.trim() };\n};\n\n/**\n * simctl reports its interesting failures in prose, and each has a different\n * fix. Every string here describes something only a simulator has.\n */\nconst remedyFor = (stderr: string): string | undefined => {\n const { message } = parseSimctlError(stderr);\n const text = `${stderr} ${message}`.toLowerCase();\n if (text.includes(\"invalid device\")) {\n return \"That is not a UDID or a name simctl knows. Run ios_simulator_list to see what exists.\";\n }\n if (text.includes(\"unable to lookup in current state: shutdown\")) {\n return 'The simulator is shut down. Boot it with ios_simulator_power {\"state\":\"booted\"}.';\n }\n if (text.includes(\"current state: booted\")) {\n return \"The simulator is already booted, which for most operations means there is nothing to do.\";\n }\n if (text.includes(\"no such file or directory\")) {\n return (\n \"Check the path exists and is a `.app` **bundle directory** built for the simulator — an \" +\n \"`.ipa` or a device build will not install, and reports exactly this.\"\n );\n }\n if (text.includes(\"failed to initialize io ports\")) {\n return (\n \"The simulator's runtime is not installed, so CoreSimulator cannot service it. Install the \" +\n \"runtime, or clear the orphans with `xcrun simctl delete unavailable`.\"\n );\n }\n return undefined;\n};\n\n/**\n * The one argument that must never reach simctl.\n *\n * `erase`, `delete` and `shutdown` all accept the literal string `all`, and\n * `simctl erase all` wipes every simulator on the machine. A device hint that\n * happens to be the word \"all\" is the single worst thing this server could\n * forward, so it is refused here as well as in the resolver — the resolver is\n * where it should be caught, and this is where it cannot be missed.\n */\nexport const assertNotBulkTarget = (udid: string): void => {\n if (udid === \"all\" || udid === \"unavailable\" || udid === \"booted\") {\n throw new IosError(`Refusing to pass \"${udid}\" to simctl as a device.`, {\n remedy:\n \"simctl reads these as *every* matching simulator, so `erase all` would wipe the machine. \" +\n \"Resolve to one concrete UDID with ios_simulator_list first.\",\n });\n }\n};\n\nexport class Simctl {\n private readonly opts: SimctlOptions;\n private readonly exec: ExecImpl;\n /** Device types change only when Xcode is updated; re-reading is pure cost. */\n private deviceTypes: RawDeviceType[] | undefined;\n\n constructor(opts: SimctlOptions) {\n this.opts = opts;\n this.exec =\n opts.exec ?? createExec({ timeout: TIMEOUT_REMEDY, toolchain: TOOLCHAIN_REMEDY, remedyFor });\n }\n\n /** Raw stdout, for the subcommands that answer in prose. */\n private async run(args: string[]): Promise<string> {\n this.opts.logger?.debug?.(\"simctl\", ...args);\n const { stdout } = await this.exec(\n this.opts.xcrunPath,\n [\"simctl\", ...args],\n this.opts.timeoutMs,\n );\n return stdout;\n }\n\n /**\n * `list` and only `list`. `-e` stops `/` being escaped as `\\/`, which matters\n * only for fixture readability but costs nothing.\n */\n private async runJson<T>(args: string[]): Promise<T> {\n const stdout = await this.run([...args, \"-j\", \"-e\"]);\n try {\n return JSON.parse(stdout) as T;\n } catch {\n throw new IosError(`simctl ${args.join(\" \")} did not return JSON.`, {\n details: stdout.slice(0, 400),\n });\n }\n }\n\n /**\n * A NeXTSTEP plist, via `plutil` and a temp file.\n *\n * The obvious `simctl listapps … | plutil -convert json -o - -` is not\n * expressible: `ExecImpl` takes argv and no stdin, deliberately, because that\n * is what keeps every caller value data rather than shell syntax. So simctl's\n * stdout is written out and handed to plutil by path. A fresh directory per\n * call rather than a shared one, so two concurrent tools cannot read each\n * other's answer with nothing looking wrong.\n */\n private async runPlist<T>(args: string[]): Promise<T> {\n const stdout = await this.run(args);\n const dir = await mkdtemp(join(tmpdir(), \"simctl-\"));\n const path = join(dir, \"out.plist\");\n try {\n await writeFile(path, stdout);\n const { stdout: json } = await this.exec(\n this.opts.plutilPath,\n [\"-convert\", \"json\", \"-o\", \"-\", path],\n this.opts.timeoutMs,\n );\n return JSON.parse(json) as T;\n } catch (err) {\n if (err instanceof IosError) throw err;\n throw new IosError(`Could not read simctl ${args[0]} output as a property list.`, {\n details: stdout.slice(0, 400),\n });\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n\n // ------------------------------------------------------------------ list --\n\n async listDevices(): Promise<Record<string, RawSim[]>> {\n const result = await this.runJson<{ devices?: Record<string, RawSim[]> }>([\"list\", \"devices\"]);\n return result.devices ?? {};\n }\n\n async listRuntimes(): Promise<RawRuntime[]> {\n return (await this.runJson<{ runtimes?: RawRuntime[] }>([\"list\", \"runtimes\"])).runtimes ?? [];\n }\n\n async listDeviceTypes(): Promise<RawDeviceType[]> {\n this.deviceTypes ??=\n (await this.runJson<{ devicetypes?: RawDeviceType[] }>([\"list\", \"devicetypes\"]))\n .devicetypes ?? [];\n return this.deviceTypes;\n }\n\n // ------------------------------------------------------------- lifecycle --\n\n async boot(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"boot\", udid]);\n }\n\n async shutdown(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"shutdown\", udid]);\n }\n\n async erase(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"erase\", udid]);\n }\n\n /**\n * Put a window on a booted simulator. A `boot` alone is headless.\n *\n * Xcode 27 ships no Simulator.app — the window belongs to DeviceHub, which\n * answers `devices://device/<UDID>` — while Xcode 26 and earlier only have\n * Simulator.app. Try the old app first, since on a Mac with both the one the\n * user already has open is the one they expect; fall back to DeviceHub.\n */\n async openApp(openPath: string, udid: string): Promise<\"Simulator\" | \"DeviceHub\"> {\n try {\n await this.exec(openPath, [\"-a\", \"Simulator\"], this.opts.timeoutMs);\n return \"Simulator\";\n } catch {\n await this.exec(openPath, [`devices://device/${udid}`], this.opts.timeoutMs);\n return \"DeviceHub\";\n }\n }\n\n // ------------------------------------------------------------------ apps --\n\n async listApps(udid: string): Promise<Record<string, RawSimApp>> {\n assertNotBulkTarget(udid);\n return this.runPlist<Record<string, RawSimApp>>([\"listapps\", udid]);\n }\n\n async install(udid: string, appPath: string): Promise<void> {\n assertNotBulkTarget(udid);\n assertNoShellMetachars(\"path\", appPath);\n await this.run([\"install\", udid, appPath]);\n }\n\n /**\n * Put photos and videos in the simulator's library.\n *\n * `addmedia` is variadic and takes the paths as plain argv, so unlike `push`\n * this needs no temp file. It is the one route around the simulator's missing\n * camera: an app that reads from the photo library can be exercised end to\n * end, while one that insists on a live capture still cannot.\n */\n async addMedia(udid: string, paths: string[]): Promise<void> {\n assertNotBulkTarget(udid);\n for (const path of paths) assertNoShellMetachars(\"path\", path);\n await this.run([\"addmedia\", udid, ...paths]);\n }\n\n /**\n * Launch, optionally replacing a running copy.\n *\n * Environment variables do not go on the command line: simctl reads them from\n * its **own** environment under a `SIMCTL_CHILD_` prefix. `ExecImpl` pins a\n * minimal environment on purpose, so a caller wanting them has to say so, and\n * this is where that would be threaded through.\n */\n async launch(\n udid: string,\n bundleId: string,\n opts: { args?: string[]; terminateExisting?: boolean; stdout?: string; stderr?: string } = {},\n ): Promise<string> {\n assertNotBulkTarget(udid);\n const flags = [\n ...(opts.terminateExisting ? [\"--terminate-running-process\"] : []),\n ...(opts.stdout ? [`--stdout=${opts.stdout}`] : []),\n ...(opts.stderr ? [`--stderr=${opts.stderr}`] : []),\n ];\n return this.run([\"launch\", ...flags, udid, bundleId, ...(opts.args ?? [])]);\n }\n\n async terminate(udid: string, bundleId: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"terminate\", udid, bundleId]);\n }\n\n async openUrl(udid: string, url: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"openurl\", udid, url]);\n }\n\n // ---------------------------------------------------------------- screen --\n\n /**\n * A PNG of the screen, base64, with no WebDriverAgent involved.\n *\n * The documented `-` for stdout **does not work**: measured on Xcode 26.6, it\n * writes a file literally named `-` into the current working directory and\n * leaves stdout empty, exit code 0. So this passes a real path, like the\n * device lane's `--json-output` does, and for the same reason uses a fresh\n * directory per call.\n *\n * A successful capture also writes a `Note: No display specified…` line to\n * stderr, so non-empty stderr is not a failure here.\n */\n async screenshotPng(udid: string): Promise<string> {\n assertNotBulkTarget(udid);\n const dir = await mkdtemp(join(tmpdir(), \"simshot-\"));\n const path = join(dir, \"shot.png\");\n try {\n await this.run([\"io\", udid, \"screenshot\", \"--type=png\", path]);\n return (await readFile(path)).toString(\"base64\");\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n\n // ----------------------------------------------------------- environment --\n\n /**\n * Read a `simctl ui` option.\n *\n * `unknown` and `unsupported` are passed through rather than normalised: they\n * mean genuinely different things — the runtime is too old, versus it failed\n * to read — and collapsing them throws away a distinction a caller can act on.\n */\n async uiGet(udid: string, option: string): Promise<string> {\n assertNotBulkTarget(udid);\n return (await this.run([\"ui\", udid, option])).trim();\n }\n\n async uiSet(udid: string, option: string, value: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"ui\", udid, option, value]);\n }\n\n async statusBarOverride(udid: string, flags: string[]): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"status_bar\", udid, \"override\", ...flags]);\n }\n\n async statusBarClear(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"status_bar\", udid, \"clear\"]);\n }\n\n async statusBarList(udid: string): Promise<string> {\n assertNotBulkTarget(udid);\n return (await this.run([\"status_bar\", udid, \"list\"])).trim();\n }\n\n async locationSet(udid: string, latitude: number, longitude: number): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"location\", udid, \"set\", `${latitude},${longitude}`]);\n }\n\n async locationClear(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"location\", udid, \"clear\"]);\n }\n\n /** `grant` and `revoke` need a bundle id; `reset` does not. */\n async privacy(udid: string, action: string, service: string, bundleId?: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"privacy\", udid, action, service, ...(bundleId ? [bundleId] : [])]);\n }\n\n /**\n * A simulated remote push. The payload goes through a temp file rather than\n * stdin, for the same argv-only reason as the plist lane.\n */\n async push(udid: string, bundleId: string, payload: unknown): Promise<void> {\n assertNotBulkTarget(udid);\n const dir = await mkdtemp(join(tmpdir(), \"simpush-\"));\n const path = join(dir, \"payload.json\");\n try {\n await writeFile(path, JSON.stringify(payload));\n await this.run([\"push\", udid, bundleId, path]);\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n}\n\nexport { INVALID_DEVICE };\n","import {\n createExec,\n WdaClient,\n type DisplayInfo,\n type ExecImpl,\n type Logger,\n type ScreenHost,\n} from \"@mgcrea/mcp-ios-core\";\n\nimport { pngDimensions, readScreenProfile, toDisplayInfo } from \"#/client/display\";\nimport {\n RuntimeUnavailableError,\n SimulatorNotBootedError,\n SimulatorNotFoundError,\n startRunnerRemedy,\n TIMEOUT_REMEDY,\n TOOLCHAIN_REMEDY,\n wdaUnavailableRemedy,\n} from \"#/client/errors\";\nimport { summarizeDevices, type SimulatorSummary } from \"#/client/shape\";\nimport { Simctl } from \"#/client/simctl\";\n\n/**\n * What to say when WebDriverAgent fails, on a simulator.\n *\n * A function of the write gate rather than a constant: three of these four\n * remedies name ios_simulator_restart_wda, and that tool is not registered when\n * writes are off. Sending a caller to a tool it cannot see is the same failure\n * as sending it to a shell it did not need.\n */\nconst simulatorWdaRemedies = (allowWrites: boolean) => ({\n unavailable: wdaUnavailableRemedy(allowWrites),\n notAuthorized:\n \"WebDriverAgent is running but is refusing to drive the UI. On a simulator there is no \" +\n \"Enable UI Automation toggle to blame, so this is nearly always a stale runner. \" +\n startRunnerRemedy(allowWrites),\n noForegroundApp:\n \"WebDriverAgent can see no foreground application on a session it has just created. Launch \" +\n `the app with ios_simulator_launch, or restart the runner. ${startRunnerRemedy(allowWrites)}`,\n noSuchElement:\n \"Call ios_simulator_ui_tree to see what is actually on screen — the element may not have \" +\n \"appeared yet.\",\n});\n\nexport type SimulatorClientOptions = {\n xcrunPath: string;\n sipsPath: string;\n plutilPath: string;\n openPath: string;\n execTimeoutMs: number;\n wdaTimeoutMs: number;\n wdaPort: number;\n wdaUrl?: string | undefined;\n /** Only so the WebDriverAgent remedies can name a tool this server registers. */\n allowWrites?: boolean | undefined;\n defaultSimulatorId?: string | undefined;\n exec?: ExecImpl | undefined;\n fetch?: typeof fetch | undefined;\n logger?: Logger | undefined;\n};\n\nexport class SimulatorClient implements ScreenHost<SimulatorSummary> {\n readonly simctl: Simctl;\n readonly exec: ExecImpl;\n readonly sipsPath: string;\n readonly plutilPath: string;\n readonly openPath: string;\n readonly execTimeoutMs: number;\n private readonly opts: SimulatorClientOptions;\n private readonly wdaClients = new Map<string, WdaClient>();\n /** `simctl list` is ~80ms and most tools want it twice; 2s collapses that. */\n private cache: { at: number; simulators: SimulatorSummary[] } | undefined;\n private profiles = new Map<string, Awaited<ReturnType<typeof readScreenProfile>>>();\n\n constructor(opts: SimulatorClientOptions) {\n this.opts = opts;\n this.exec = opts.exec ?? createExec({ timeout: TIMEOUT_REMEDY, toolchain: TOOLCHAIN_REMEDY });\n this.sipsPath = opts.sipsPath;\n this.plutilPath = opts.plutilPath;\n this.openPath = opts.openPath;\n this.execTimeoutMs = opts.execTimeoutMs;\n this.simctl = new Simctl({\n xcrunPath: opts.xcrunPath,\n plutilPath: opts.plutilPath,\n timeoutMs: opts.execTimeoutMs,\n ...(opts.exec ? { exec: opts.exec } : {}),\n ...(opts.logger ? { logger: opts.logger } : {}),\n });\n }\n\n async listSimulators(opts: { fresh?: boolean } = {}): Promise<SimulatorSummary[]> {\n const cached = this.cache;\n if (!opts.fresh && cached && Date.now() - cached.at < 2000) return cached.simulators;\n const [devices, runtimes, deviceTypes] = await Promise.all([\n this.simctl.listDevices(),\n this.simctl.listRuntimes(),\n this.simctl.listDeviceTypes(),\n ]);\n const simulators = summarizeDevices(devices, runtimes, deviceTypes);\n this.cache = { at: Date.now(), simulators };\n return simulators;\n }\n\n /**\n * Resolve a hint to exactly one simulator, or refuse.\n *\n * The device server's rule — never guess between two — transfers, but\n * \"connected\" has to become the right predicate. A typical machine carries\n * dozens of simulators, most of them shut down and some of them unavailable,\n * so the population is mostly non-candidates.\n *\n * Three things happen here that cannot happen further down:\n *\n * - `all` and `unavailable` are refused outright. `simctl erase all` wipes\n * every simulator on the machine, and a hint is a string from a model.\n * - `booted` is resolved *here* rather than passed through. simctl's own\n * documentation says that with several booted it \"will choose one of\n * them\", and a coin flip that drives the wrong screen most of the time is\n * exactly what the refuse-to-guess rule exists to prevent.\n * - availability is checked before the target is returned, because `simctl\n * io` against an unavailable simulator aborts with an uncaught\n * NSInternalInconsistencyException rather than failing.\n *\n * State is deliberately *not* checked: `ios_simulator_list` and\n * `ios_simulator_diagnostics` are useful against a shut-down simulator, so\n * requiring booted is each tool's decision, via `requireBooted`.\n */\n async resolveTarget(hint?: string): Promise<SimulatorSummary> {\n const wanted = hint ?? this.opts.defaultSimulatorId;\n const simulators = await this.listSimulators();\n\n if (wanted === \"all\" || wanted === \"unavailable\") {\n throw new SimulatorNotFoundError(\n `\"${wanted}\" is not a simulator, it is how simctl says *every* simulator.`,\n \"Name one by UDID or by name. `simctl erase all` would wipe the machine, so this server \" +\n \"refuses to forward it.\",\n );\n }\n\n const usable = simulators.filter((sim) => sim.available);\n if (wanted !== undefined && wanted !== \"booted\") {\n const byUdid = simulators.filter((sim) => sim.id.toLowerCase() === wanted.toLowerCase());\n // A name is ambiguous far more often than a UDID: the same device name\n // exists once per runtime, and the orphans of an uninstalled runtime keep\n // theirs. Prefer the ones that can actually be used, so \"iPhone 17 Pro\"\n // resolves to the real one rather than erroring against a dead 26.3 twin.\n const named = simulators.filter((sim) => sim.name === wanted);\n const byName = named.some((sim) => sim.available)\n ? named.filter((sim) => sim.available)\n : named;\n const matches = byUdid.length > 0 ? byUdid : byName;\n if (matches.length === 0) {\n throw new SimulatorNotFoundError(\n `No simulator matches \"${wanted}\".`,\n simulators.length === 0\n ? \"simctl knows about no simulators at all. Install a runtime in Xcode > Settings > Components.\"\n : `Run ios_simulator_list to see them. ${usable.length} of ${simulators.length} are available.`,\n );\n }\n if (matches.length > 1) {\n throw new SimulatorNotFoundError(\n `${matches.length} simulators are named \"${wanted}\".`,\n `Pass a UDID instead: ${matches.map((sim) => `${sim.name} = ${sim.id}`).join(\", \")}.`,\n );\n }\n return this.assertAvailable(matches[0] as SimulatorSummary);\n }\n\n const booted = usable.filter((sim) => sim.state === \"Booted\");\n if (booted.length === 1) return this.assertAvailable(booted[0] as SimulatorSummary);\n if (booted.length === 0) {\n const candidates = usable\n .slice(0, 6)\n .map((sim) => `${sim.name} = ${sim.id}`)\n .join(\", \");\n throw new SimulatorNotFoundError(\n \"No simulator is booted.\",\n usable.length === 0\n ? \"No simulator on this machine has an installed runtime. Install one in Xcode > Settings > Components.\"\n : `Boot one with ios_simulator_power, e.g. ${candidates}.`,\n );\n }\n throw new SimulatorNotFoundError(\n `${booted.length} simulators are booted, so there is no obvious default.`,\n `Pass \\`simulator\\` (or set IOS_SIMULATOR_ID) to one of: ${booted\n .map((sim) => `${sim.name} = ${sim.id}`)\n .join(\", \")}.`,\n );\n }\n\n private assertAvailable(sim: SimulatorSummary): SimulatorSummary {\n if (!sim.available) throw new RuntimeUnavailableError(sim.name, sim.unavailableReason);\n return sim;\n }\n\n /** Every tool that needs a running simulator calls this; the resolver does not. */\n requireBooted(sim: SimulatorSummary): SimulatorSummary {\n if (sim.state !== \"Booted\") throw new SimulatorNotBootedError(sim.name, sim.id);\n return sim;\n }\n\n /**\n * Geometry. Cached per device *type* rather than per simulator, since that is\n * what the profile describes and it changes only when Xcode does.\n */\n async display(target: SimulatorSummary, capturePng?: string): Promise<DisplayInfo> {\n const id = target.deviceTypeIdentifier;\n let profile = id ? this.profiles.get(id) : undefined;\n if (!profile) {\n const deviceType = (await this.simctl.listDeviceTypes()).find((t) => t.identifier === id);\n profile = deviceType\n ? await readScreenProfile(deviceType, {\n plutilPath: this.plutilPath,\n exec: this.exec,\n timeoutMs: this.execTimeoutMs,\n })\n : {};\n if (id) this.profiles.set(id, profile);\n }\n const capture = capturePng ? pngDimensions(Buffer.from(capturePng, \"base64\")) : undefined;\n return toDisplayInfo(profile, capture);\n }\n\n /** The capture lane that needs no runner — the whole reason this server is cheap to start. */\n async screenshotPng(target: SimulatorSummary): Promise<string> {\n this.requireBooted(target);\n return this.simctl.screenshotPng(target.id);\n }\n\n /**\n * Where WebDriverAgent is.\n *\n * Unlike the device lane there is no tunnel address to derive: a simulator\n * shares the host's network stack, so the runner's port *is* a host loopback\n * port. That is also the trap — several booted simulators contend for the\n * same one, and WebDriverAgent scans 8100-8199 when `USE_PORT` is unset, so a\n * second runner comes up healthy on 8101 and a server pointed at 8100 drives\n * the first simulator while reporting success. `ios_simulator_diagnostics`\n * reports which simulator actually owns the port.\n */\n wdaUrl(): string {\n return this.opts.wdaUrl ?? `http://127.0.0.1:${this.opts.wdaPort}`;\n }\n\n get wdaPort(): number {\n return this.opts.wdaPort;\n }\n\n wda(_target: SimulatorSummary): WdaClient {\n const url = this.wdaUrl();\n const existing = this.wdaClients.get(url);\n if (existing) return existing;\n const client = new WdaClient({\n baseUrl: async () => url,\n timeoutMs: this.opts.wdaTimeoutMs,\n remedies: simulatorWdaRemedies(this.opts.allowWrites !== false),\n ...(this.opts.fetch ? { fetch: this.opts.fetch } : {}),\n ...(this.opts.logger ? { logger: this.opts.logger } : {}),\n });\n this.wdaClients.set(url, client);\n return client;\n }\n}\n","import { createArgs, type ScreenNaming } from \"@mgcrea/mcp-ios-core\";\n\nexport {\n compact,\n fail,\n ok,\n okImage,\n okText,\n toFailure,\n wrap,\n wrapResult,\n} from \"@mgcrea/mcp-ios-core\";\nexport type { ImageContent, TextContent, ToolResult } from \"@mgcrea/mcp-ios-core\";\n\n/**\n * How the shared tools name themselves and each other in this server.\n *\n * `copy.target` is written out rather than derived: a simulator is picked by\n * UDID, by name, or by being the only booted one, and none of those is what the\n * device server's version of this sentence says. `names.listTargets` is\n * overridden for the same reason — `ios_simulator_list_devices` would be a\n * mouthful for a tool that lists simulators.\n */\nexport const SIMULATOR_NAMING: ScreenNaming = {\n prefix: \"ios_simulator\",\n title: \"iOS Simulator\",\n noun: \"simulator\",\n envPrefix: \"IOS_SIMULATOR\",\n names: { listTargets: \"ios_simulator_list\" },\n copy: {\n target:\n \"Which simulator: its UDID or its name as shown by ios_simulator_list. Omit it when exactly \" +\n \"one is booted — that is the normal case, and IOS_SIMULATOR_ID pins it when it is not. \" +\n \"Unlike simctl's own `booted`, this never picks arbitrarily between two.\",\n },\n};\n\nconst args = createArgs(SIMULATOR_NAMING);\n\nexport const { bundleIdArg, confirmArg, detailArg, screenshotArg, settleArg, xArg, yArg } = args;\n\n/**\n * The schema field is `device` in every tool here, shared and local alike.\n *\n * It looks odd next to a server called \"simulator\" and it is still the right\n * name: `simctl`'s own interface calls a simulator a device — `simctl list\n * devices`, and `<device>` in the usage line of every subcommand — so this is\n * Apple's word for the thing, and the `ios_simulator_` prefix already says\n * which kind. Mixing `simulator` here with the shared tools' `device` would be\n * worse than either alone.\n */\nexport const deviceArg = args.targetArg;\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { IosError } from \"#/client/errors\";\nimport { summarizeApps } from \"#/client/shape\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport { bundleIdArg, deviceArg, wrap } from \"#/tools/util\";\n\nexport const registerListAppsTool = (server: McpServer, client: SimulatorClient): void => {\n server.registerTool(\n \"ios_simulator_list_apps\",\n {\n title: \"iOS Simulator: List Apps\",\n description:\n \"List the apps installed on a simulator, with the bundle id every other tool takes. \" +\n \"Defaults to your own apps: a stock simulator carries about thirty, and twenty-five of \" +\n \"them are Apple's. Each entry also carries `dataContainer` — an ordinary path on this \" +\n \"Mac, so an app's database or logs can be read directly with no copy step.\",\n inputSchema: z.object({\n device: deviceArg,\n include_all: z\n .boolean()\n .default(false)\n .describe(\"Include Apple's built-in apps as well. Off by default; the list is long.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ device, include_all }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n const apps = await client.simctl.listApps(target.id);\n return { apps: summarizeApps(apps, { includeAll: include_all }) };\n }),\n );\n};\n\nexport const registerAppTools = (\n server: McpServer,\n client: SimulatorClient,\n launchArgs: string[],\n outputDir: string,\n): void => {\n server.registerTool(\n \"ios_simulator_install\",\n {\n title: \"iOS Simulator: Install\",\n description:\n \"Install a build. The path must be a `.app` **bundle directory** built for the simulator \" +\n '— not an `.ipa`, and not a device build, both of which fail with a bare \"No such file or ' +\n 'directory\" that reads like a typo. Installing over an existing copy replaces it and ' +\n \"keeps its data.\",\n inputSchema: z.object({\n device: deviceArg,\n path: z\n .string()\n .describe(\n \"Absolute path to the .app bundle, e.g. \" +\n \"~/Library/Developer/Xcode/DerivedData/…/Build/Products/Debug-iphonesimulator/Foo.app\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({ device, path }) =>\n wrap(async () => {\n if (!path.startsWith(\"/\")) {\n throw new IosError(`path must be absolute, got \"${path}\".`, {\n remedy: \"Pass the full path to the .app bundle.\",\n });\n }\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.install(target.id, path);\n return { installed: path, udid: target.id };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_launch\",\n {\n title: \"iOS Simulator: Launch\",\n description:\n \"Launch an installed app. Pass `arguments` to put it into a fixture or demo mode — that \" +\n \"is what IOS_SIMULATOR_LAUNCH_ARGS sets as the default for every launch that does not \" +\n \"override it. Standard output and error are captured to files under the output directory, \" +\n \"so an app that dies on launch leaves something readable behind.\",\n inputSchema: z.object({\n device: deviceArg,\n bundle_id: bundleIdArg,\n arguments: z\n .array(z.string())\n .optional()\n .describe(\n 'Launch arguments, e.g. [\"-DemoMode\", \"YES\"]. Overrides IOS_SIMULATOR_LAUNCH_ARGS ' +\n \"rather than adding to it.\",\n ),\n terminate_first: z\n .boolean()\n .default(true)\n .describe(\n \"Replace a running copy rather than attaching to it. On by default so a launch means \" +\n \"a fresh process and a predictable first screen.\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, bundle_id, arguments: args, terminate_first }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n const effective = args ?? launchArgs;\n const stdout = `${outputDir}/${bundle_id}.out.log`;\n const stderr = `${outputDir}/${bundle_id}.err.log`;\n const output = await client.simctl.launch(target.id, bundle_id, {\n args: effective,\n terminateExisting: terminate_first,\n stdout,\n stderr,\n });\n const pid = /:\\s*(\\d+)\\s*$/.exec(output.trim())?.[1];\n return {\n launched: bundle_id,\n ...(pid ? { pid: Number(pid) } : {}),\n ...(effective.length > 0 ? { arguments: effective } : {}),\n ...(args === undefined && launchArgs.length > 0\n ? { argumentsFrom: \"IOS_SIMULATOR_LAUNCH_ARGS\" }\n : {}),\n logs: { stdout, stderr },\n };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_terminate\",\n {\n title: \"iOS Simulator: Terminate\",\n description:\n \"Kill a running app. The app is not uninstalled and its data is untouched — this is how \" +\n \"you get back to a cold start without erasing anything.\",\n inputSchema: z.object({ device: deviceArg, bundle_id: bundleIdArg }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({ device, bundle_id }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.terminate(target.id, bundle_id);\n return { terminated: bundle_id };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_open_url\",\n {\n title: \"iOS Simulator: Open URL\",\n description:\n \"Open a URL on the simulator, which is how you exercise a deep link or a universal link \" +\n \"without finding a way to tap one. An `https://` link opens in Safari unless the app \" +\n \"claims it; a custom scheme goes straight to whichever app registered it.\",\n inputSchema: z.object({\n device: deviceArg,\n url: z.url().describe('e.g. \"myapp://garden/42\" or \"https://example.com/garden/42\".'),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, url }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.openUrl(target.id, url);\n return { opened: url };\n }),\n );\n};\n","import type { DisplayInfo } from \"@mgcrea/mcp-ios-core\";\nimport type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { startRunnerRemedy } from \"#/client/errors\";\nimport { toListRow, type SimulatorSummary } from \"#/client/shape\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport type { ToolContext } from \"#/tools/index\";\nimport { deviceArg, wrap } from \"#/tools/util\";\n\nexport type Diagnosis = {\n ok: boolean;\n writes: \"enabled\" | \"disabled\";\n /**\n * The booted ones only, plus the target. A typical machine carries thirty-odd\n * simulators and listing them all here spent more of the context window than\n * the whole rest of the report — and none of them is what a diagnosis is\n * about. `counts` carries the shape of the rest; ios_simulator_list is there\n * for the full picture.\n */\n simulators: Record<string, unknown>[];\n counts: { total: number; booted: number; unavailable: number };\n target?: { udid: string; name: string; state: string; runtime: string };\n display?: DisplayInfo;\n wda: {\n url: string;\n port: number;\n reachable: boolean;\n /** Which simulator actually owns the port — the failure unique to this server. */\n boundTo?: string;\n state?: unknown;\n error?: string;\n };\n problems: string[];\n nextSteps: string[];\n};\n\n/**\n * Which simulator owns the WebDriverAgent port.\n *\n * This check has no device-server equivalent and it is the most valuable one\n * here. A simulator shares the host's network stack, so a runner's port is a\n * *host* port, and WebDriverAgent scans 8100-8199 when `USE_PORT` is unset. Two\n * booted simulators therefore give you a second runner answering healthily on\n * 8101 while a server pointed at 8100 drives the first one and reports success\n * on every call.\n *\n * Every process inside a simulator carries `SIMULATOR_UDID` in its environment,\n * and `ps -Eww` prints it, so the port's real owner is knowable rather than\n * assumed.\n */\nconst portOwner = async (client: SimulatorClient, port: number): Promise<string | undefined> => {\n try {\n const { stdout: pids } = await client.exec(\n \"/usr/sbin/lsof\",\n [\"-nP\", `-iTCP:${port}`, \"-sTCP:LISTEN\", \"-t\"],\n 5000,\n );\n const pid = pids.trim().split(\"\\n\")[0];\n if (!pid) return undefined;\n const { stdout: env } = await client.exec(\"/bin/ps\", [\"-Eww\", \"-p\", pid], 5000);\n return /SIMULATOR_UDID=([0-9A-Fa-f-]{36})/.exec(env)?.[1];\n } catch {\n // lsof is not always installed and needs no permissions we can assume.\n // Not knowing the owner is a missing field, not a failure.\n return undefined;\n }\n};\n\n/**\n * The whole point of this tool is that **it never throws**. Every other tool\n * fails when nothing is booted, the runtime is missing or the runner is not up,\n * and each of those looks much like the others from the outside. This one\n * collects all of it as data and names which half is missing.\n */\nexport const diagnose = async (\n client: SimulatorClient,\n ctx: ToolContext,\n deviceHint?: string,\n): Promise<Diagnosis> => {\n const problems: string[] = [];\n const nextSteps: string[] = [];\n const writes = ctx.allowWrites ? \"enabled\" : \"disabled\";\n\n let simulators: SimulatorSummary[] = [];\n try {\n simulators = await client.listSimulators({ fresh: true });\n } catch (err) {\n problems.push(`Could not list simulators: ${err instanceof Error ? err.message : String(err)}`);\n nextSteps.push(\n \"Install Xcode and its command line tools, then run `xcrun simctl list devices`.\",\n );\n return {\n ok: false,\n writes,\n simulators: [],\n counts: { total: 0, booted: 0, unavailable: 0 },\n wda: { url: client.wdaUrl(), port: client.wdaPort, reachable: false },\n problems,\n nextSteps,\n };\n }\n\n const booted = simulators.filter((sim) => sim.state === \"Booted\");\n const unavailable = simulators.filter((sim) => !sim.available);\n const counts = {\n total: simulators.length,\n booted: booted.length,\n unavailable: unavailable.length,\n };\n\n if (simulators.length === 0) {\n problems.push(\"simctl knows about no simulators at all.\");\n nextSteps.push(\"Install a runtime: Xcode > Settings > Components.\");\n } else if (booted.length === 0) {\n problems.push(\"No simulator is booted.\");\n nextSteps.push(\n 'Boot one with ios_simulator_power {\"state\":\"booted\"} — this server never boots implicitly.',\n );\n }\n const nothingBooted = simulators.length > 0 && booted.length === 0;\n\n let target: SimulatorSummary | undefined;\n try {\n target = await client.resolveTarget(deviceHint);\n } catch (err) {\n if (booted.length > 0) {\n problems.push(err instanceof Error ? err.message : String(err));\n const remedy = (err as { remedy?: string }).remedy;\n if (remedy) nextSteps.push(remedy);\n }\n }\n\n let display: DisplayInfo | undefined;\n if (target) {\n try {\n display = await client.display(target);\n } catch {\n // Geometry comes from the device type's profile.plist, so failing here\n // means the profile could not be read — worth a missing field, not a\n // failed report.\n }\n }\n\n // WebDriverAgent. `reachable` and usable coincide on a simulator: there is no\n // Enable UI Automation toggle, so the device server's separate `authorized`\n // probe would be pure latency. What replaces it is `boundTo`.\n const url = client.wdaUrl();\n const wda: Diagnosis[\"wda\"] = { url, port: client.wdaPort, reachable: false };\n try {\n wda.state = await client.wda(target ?? ({ id: \"\", name: \"\" } as SimulatorSummary)).status();\n wda.reachable = true;\n } catch (err) {\n wda.error = err instanceof Error ? err.message : String(err);\n problems.push(\"WebDriverAgent is not answering, so the UI tree and the input tools will fail.\");\n // Ordered, not merely listed. The runner tool calls `requireBooted`, so on a\n // machine with nothing booted the two steps above and below are one\n // sequence and running them the other way round cannot work — which the\n // previous pair of independent bullets did not say anywhere.\n nextSteps.push(\n (nothingBooted ? \"Then, once it is booted: \" : \"\") +\n startRunnerRemedy(ctx.allowWrites) +\n \" Everything else — including screenshots — works without it.\",\n );\n }\n\n if (wda.reachable) {\n const owner = await portOwner(client, client.wdaPort);\n if (owner) wda.boundTo = owner;\n if (owner && target && owner.toLowerCase() !== target.id.toLowerCase()) {\n const other = simulators.find((sim) => sim.id.toLowerCase() === owner.toLowerCase());\n problems.push(\n `Port ${client.wdaPort} is owned by ${other?.name ?? owner}, not ${target.name}. ` +\n \"Every tap and every UI tree read would go to the wrong screen.\",\n );\n nextSteps.push(\n \"WebDriverAgent scans 8100-8199 when USE_PORT is unset, so a second simulator's runner \" +\n \"takes the next free port and nothing looks wrong. Give this one its own port with \" +\n \"IOS_SIMULATOR_WDA_PORT and start its runner with a matching USE_PORT.\",\n );\n }\n }\n\n if (unavailable.length > 0) {\n nextSteps.push(\n `${unavailable.length} simulators have no installed runtime and cannot be used at all. ` +\n \"`xcrun simctl delete unavailable` clears them.\",\n );\n }\n\n return {\n ok: problems.length === 0,\n writes,\n simulators: [...booted, ...(target && target.state !== \"Booted\" ? [target] : [])].map(\n toListRow,\n ),\n counts,\n ...(target\n ? {\n target: {\n udid: target.id,\n name: target.name,\n state: target.state,\n runtime: target.runtime,\n },\n }\n : {}),\n ...(display ? { display } : {}),\n wda,\n problems,\n nextSteps,\n };\n};\n\nexport const registerDiagnosticsTools = (\n server: McpServer,\n client: SimulatorClient,\n ctx: ToolContext,\n): void => {\n server.registerTool(\n \"ios_simulator_diagnostics\",\n {\n title: \"iOS Simulator: Diagnostics\",\n description:\n \"Report both lanes separately and never fail: which simulators exist and which are \" +\n \"usable, which one this server would drive, its screen geometry, and whether \" +\n \"WebDriverAgent is answering — including *which* simulator owns its port, which is the \" +\n \"one way to catch a second runner quietly taking your taps. Start here when anything \" +\n \"else misbehaves.\",\n inputSchema: z.object({ device: deviceArg }),\n annotations: { readOnlyHint: true },\n },\n async ({ device }) => wrap(async () => diagnose(client, ctx, device)),\n );\n\n server.registerResource(\n \"diagnostics\",\n \"ios-simulator://diagnostics\",\n {\n title: \"iOS Simulator diagnostics\",\n description: \"The same report as ios_simulator_diagnostics, as a resource.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(await diagnose(client, ctx)),\n },\n ],\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { IosError } from \"#/client/errors\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport { bundleIdArg, deviceArg, wrap } from \"#/tools/util\";\n\n/** simctl's own list, verbatim — a value it does not accept is not worth guessing at. */\nconst PRIVACY_SERVICES = [\n \"all\",\n \"calendar\",\n \"contacts-limited\",\n \"contacts\",\n \"location\",\n \"location-always\",\n \"photos-add\",\n \"photos\",\n \"media-library\",\n \"microphone\",\n \"motion\",\n \"reminders\",\n \"siri\",\n] as const;\n\n/** APNs refuses anything larger, and it is worth saying so before the round trip. */\nconst MAX_PUSH_BYTES = 4096;\n\nexport const registerEnvironmentTools = (server: McpServer, client: SimulatorClient): void => {\n server.registerTool(\n \"ios_simulator_set_environment\",\n {\n title: \"iOS Simulator: Set Environment\",\n description:\n \"Stage the simulator before a screenshot or a test: dark mode, Dynamic Type size, \" +\n \"increased contrast, a frozen status bar, a simulated location, and app permissions. \" +\n \"None of this is possible on a physical device, and it is most of the reason to prefer a \" +\n \"simulator for UI work. Every field is optional; those given are applied in order and the \" +\n \"result reports the state afterwards. A status bar override survives a reboot, so clear \" +\n \"it when you are done or it quietly pollutes later screenshots.\",\n inputSchema: z.object({\n device: deviceArg,\n appearance: z.enum([\"light\", \"dark\"]).optional().describe(\"Interface style.\"),\n content_size: z\n .enum([\n \"extra-small\",\n \"small\",\n \"medium\",\n \"large\",\n \"extra-large\",\n \"extra-extra-large\",\n \"extra-extra-extra-large\",\n \"accessibility-medium\",\n \"accessibility-large\",\n \"accessibility-extra-large\",\n \"accessibility-extra-extra-large\",\n \"accessibility-extra-extra-extra-large\",\n ])\n .optional()\n .describe(\"Dynamic Type size. The accessibility- sizes are where layouts break.\"),\n increase_contrast: z.boolean().optional().describe(\"Increase Contrast accessibility mode.\"),\n status_bar: z\n .object({\n time: z\n .string()\n .optional()\n .describe('Fixed clock, e.g. \"9:41\" — what App Store screenshots use.'),\n wifi_bars: z.number().int().min(0).max(3).optional(),\n cellular_bars: z.number().int().min(0).max(4).optional(),\n operator_name: z.string().optional().describe('Carrier name; \"\" hides it.'),\n battery_level: z.number().int().min(0).max(100).optional(),\n battery_state: z.enum([\"charging\", \"charged\", \"discharging\"]).optional(),\n })\n .optional()\n .describe(\"Freeze the status bar. Overrides persist until cleared.\"),\n clear_status_bar: z\n .boolean()\n .default(false)\n .describe(\"Drop every status bar override, restoring the real one.\"),\n location: z\n .object({ latitude: z.number(), longitude: z.number() })\n .optional()\n .describe(\"Teleport the device, for location-gated screens.\"),\n clear_location: z.boolean().default(false).describe(\"Stop simulating a location.\"),\n permission: z\n .object({\n action: z\n .enum([\"grant\", \"revoke\", \"reset\"])\n .describe(\n \"`grant` and `revoke` answer the prompt without showing it; `reset` makes it \" +\n \"appear again on next use.\",\n ),\n service: z.enum(PRIVACY_SERVICES),\n bundle_id: bundleIdArg\n .optional()\n .describe(\"Required for grant and revoke; optional for reset.\"),\n })\n .optional()\n .describe(\n \"Set an app permission directly. This is how you reach the denied branch of a \" +\n \"permission check, which is otherwise a prompt nobody can tap.\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({\n device,\n appearance,\n content_size,\n increase_contrast,\n status_bar,\n clear_status_bar,\n location,\n clear_location,\n permission,\n }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n const applied: string[] = [];\n\n if (appearance) {\n await client.simctl.uiSet(target.id, \"appearance\", appearance);\n applied.push(`appearance=${appearance}`);\n }\n if (content_size) {\n await client.simctl.uiSet(target.id, \"content_size\", content_size);\n applied.push(`content_size=${content_size}`);\n }\n if (increase_contrast !== undefined) {\n await client.simctl.uiSet(\n target.id,\n \"increase_contrast\",\n increase_contrast ? \"enabled\" : \"disabled\",\n );\n applied.push(`increase_contrast=${increase_contrast}`);\n }\n if (clear_status_bar) {\n await client.simctl.statusBarClear(target.id);\n applied.push(\"status_bar cleared\");\n }\n if (status_bar) {\n const flags = [\n ...(status_bar.time ? [\"--time\", status_bar.time] : []),\n ...(status_bar.wifi_bars !== undefined\n ? [\"--wifiMode\", \"active\", \"--wifiBars\", String(status_bar.wifi_bars)]\n : []),\n ...(status_bar.cellular_bars !== undefined\n ? [\"--cellularMode\", \"active\", \"--cellularBars\", String(status_bar.cellular_bars)]\n : []),\n ...(status_bar.operator_name !== undefined\n ? [\"--operatorName\", status_bar.operator_name]\n : []),\n ...(status_bar.battery_level !== undefined\n ? [\"--batteryLevel\", String(status_bar.battery_level)]\n : []),\n ...(status_bar.battery_state ? [\"--batteryState\", status_bar.battery_state] : []),\n ];\n if (flags.length === 0) {\n throw new IosError(\"`status_bar` was given with no fields to override.\", {\n remedy: \"Set at least one field, or use `clear_status_bar` to drop the overrides.\",\n });\n }\n await client.simctl.statusBarOverride(target.id, flags);\n applied.push(\"status_bar overridden\");\n }\n if (clear_location) {\n await client.simctl.locationClear(target.id);\n applied.push(\"location cleared\");\n }\n if (location) {\n await client.simctl.locationSet(target.id, location.latitude, location.longitude);\n applied.push(`location=${location.latitude},${location.longitude}`);\n }\n if (permission) {\n if (permission.action !== \"reset\" && !permission.bundle_id) {\n throw new IosError(`\\`${permission.action}\\` needs a bundle_id.`, {\n remedy: \"Pass the app's bundle id, or use `reset`, which does not need one.\",\n });\n }\n await client.simctl.privacy(\n target.id,\n permission.action,\n permission.service,\n permission.bundle_id,\n );\n applied.push(`permission ${permission.action} ${permission.service}`);\n }\n\n if (applied.length === 0) {\n throw new IosError(\"Nothing to do — no field was given.\", {\n remedy:\n \"Set at least one of appearance, content_size, status_bar, location, permission.\",\n });\n }\n\n // Read back rather than echo the request: `simctl ui` answers `unknown`\n // or `unsupported` on a runtime too old for a setting, and reporting the\n // value we asked for would hide that.\n return {\n applied,\n state: {\n appearance: await client.simctl.uiGet(target.id, \"appearance\"),\n contentSize: await client.simctl.uiGet(target.id, \"content_size\"),\n statusBar: await client.simctl.statusBarList(target.id),\n },\n };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_add_media\",\n {\n title: \"iOS Simulator: Add Media\",\n description:\n \"Put photos or videos into the simulator's photo library. A simulator has no camera, and \" +\n \"this is how you get around that: seed the library first, then drive the app's picker as \" +\n \"normal. Without it, every flow that starts with an image is untestable here. Files are \" +\n \"copied in, so the originals are untouched and the simulator keeps them until it is \" +\n \"erased. Pair it with `permission` on ios_simulator_set_environment to skip the access \" +\n \"prompt entirely.\",\n inputSchema: z.object({\n device: deviceArg,\n paths: z\n .array(z.string())\n .min(1)\n .describe(\n \"Absolute paths to the images or videos, e.g. \" +\n '[\"/Users/me/fixtures/monstera.jpg\"]. Formats are the ones Photos itself accepts — ' +\n \"JPEG, PNG, HEIC, MOV, MP4.\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, paths }) =>\n wrap(async () => {\n // Checked here rather than left to simctl, which answers a relative path\n // with a bare \"No such file or directory\" that reads like a typo in the\n // filename rather than a statement about the working directory.\n for (const path of paths) {\n if (!path.startsWith(\"/\")) {\n throw new IosError(`Every path must be absolute, got \"${path}\".`, {\n remedy: \"Pass the full path to each file.\",\n });\n }\n }\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.addMedia(target.id, paths);\n return { added: paths, udid: target.id };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_push\",\n {\n title: \"iOS Simulator: Push\",\n description:\n \"Deliver a push notification, with no APNs certificate and no server. The payload is a \" +\n \"normal remote-notification body and must contain an `aps` key. Only remote pushes are \" +\n \"simulated — not VoIP, complications or file-provider. The app must be installed; it does \" +\n \"not have to be running.\",\n inputSchema: z.object({\n device: deviceArg,\n bundle_id: bundleIdArg,\n payload: z\n .record(z.string(), z.unknown())\n .describe(\n 'The APNs payload, e.g. {\"aps\":{\"alert\":{\"title\":\"Watering due\",\"body\":\"Monstera\"},' +\n '\"sound\":\"default\",\"badge\":1}}. Max 4096 bytes.',\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, bundle_id, payload }) =>\n wrap(async () => {\n if (!(\"aps\" in payload)) {\n throw new IosError(\"A push payload must have a top-level `aps` key.\", {\n remedy: 'Wrap the notification, e.g. {\"aps\":{\"alert\":\"Hello\"}}.',\n });\n }\n const bytes = Buffer.byteLength(JSON.stringify(payload));\n if (bytes > MAX_PUSH_BYTES) {\n throw new IosError(`The payload is ${bytes} bytes; APNs allows ${MAX_PUSH_BYTES}.`, {\n remedy: \"Shorten it — this is refused here rather than by simctl, but the cap is real.\",\n });\n }\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.push(target.id, bundle_id, payload);\n return { pushed: bundle_id, bytes };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { toListRow } from \"#/client/shape\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport { confirmArg, deviceArg, wrap } from \"#/tools/util\";\n\n/** Booting is slow and visible; this is how long `power` waits to see it land. */\nconst BOOT_POLL_MS = 500;\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nexport const registerListTool = (server: McpServer, client: SimulatorClient): void => {\n server.registerTool(\n \"ios_simulator_list\",\n {\n title: \"iOS Simulator: List\",\n description:\n \"List the simulators on this machine, with the UDID every other tool takes. Shows the \" +\n \"usable ones: a typical machine carries thirty-odd simulators and a good fraction of them \" +\n \"are orphans whose runtime is no longer installed, which cannot be booted, driven or \" +\n \"screenshotted at all. The result still says how many were left out and how to delete \" +\n \"them, and `include_unavailable` brings them back with `available: false` and the reason, \" +\n 'so \"why can\\'t I use that one\" is still answerable here.',\n inputSchema: z.object({\n booted_only: z\n .boolean()\n .default(false)\n .describe(\"Only simulators that are currently running.\"),\n include_unavailable: z\n .boolean()\n .default(false)\n .describe(\n \"Also list simulators whose runtime is not installed. Off by default because they \" +\n \"are not usable for anything; turn it on to see why a specific one is missing.\",\n ),\n name_contains: z\n .string()\n .optional()\n .describe('Case-insensitive substring filter on the name, e.g. \"iPhone 17\".'),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ booted_only, include_unavailable, name_contains }) =>\n wrap(async () => {\n const all = await client.listSimulators({ fresh: true });\n const needle = name_contains?.toLowerCase();\n const matched = all.filter(\n (sim) =>\n (include_unavailable || sim.available) &&\n (!booted_only || sim.state === \"Booted\") &&\n (needle === undefined || sim.name.toLowerCase().includes(needle)),\n );\n const unavailable = all.filter((sim) => !sim.available).length;\n return {\n simulators: matched.map(toListRow),\n booted: all.filter((sim) => sim.state === \"Booted\").length,\n total: all.length,\n ...(unavailable > 0\n ? {\n unavailable,\n note:\n `${unavailable} simulators have no installed runtime and cannot be used` +\n (include_unavailable ? \"\" : \", and are not listed above\") +\n \". `xcrun simctl delete unavailable` clears them, or pass \" +\n \"`include_unavailable: true` to see them here.\",\n }\n : {}),\n };\n }),\n );\n};\n\nexport const registerLifecycleTools = (server: McpServer, client: SimulatorClient): void => {\n server.registerTool(\n \"ios_simulator_power\",\n {\n title: \"iOS Simulator: Power\",\n description:\n \"Boot or shut down a simulator. Booting is the one thing this server will not do for you \" +\n \"implicitly — a cold boot takes tens of seconds and puts a window on the user's screen, \" +\n \"and it changes which simulator an unqualified call resolves to afterwards. Both \" +\n \"directions are idempotent: booting a booted simulator succeeds.\",\n inputSchema: z.object({\n device: deviceArg,\n state: z\n .enum([\"booted\", \"shutdown\"])\n .describe(\"The state to put it in. `booted` also waits for it to actually get there.\"),\n open_window: z\n .boolean()\n .default(true)\n .describe(\n \"Bring up a window on the device — Simulator.app, or DeviceHub on Xcode 27 — so the \" +\n \"screen is visible. A `boot` on its own is headless, \" +\n \"which is fine for an agent and confusing for a person watching.\",\n ),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .max(300_000)\n .default(60_000)\n .describe(\"How long to wait for a boot to complete before reporting what it saw.\"),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({ device, state, open_window, wait_ms }) =>\n wrap(async () => {\n const target = await client.resolveTarget(device);\n if (state === \"shutdown\") {\n if (target.state !== \"Booted\") {\n return { udid: target.id, name: target.name, state: \"Shutdown\", alreadyThere: true };\n }\n await client.simctl.shutdown(target.id);\n return { udid: target.id, name: target.name, state: \"Shutdown\" };\n }\n\n const already = target.state === \"Booted\";\n if (!already) await client.simctl.boot(target.id);\n // A window that will not open is a warning, not a failed boot: the boot\n // has already been accepted, and reporting an error over a simulator\n // that is coming up sends the caller to retry something that worked.\n let windowWarning: string | undefined;\n if (open_window) {\n await client.simctl.openApp(client.openPath, target.id).catch((err: unknown) => {\n windowWarning = `Booted, but no window could be opened: ${\n err instanceof Error ? err.message : String(err)\n }`;\n });\n }\n\n // Poll rather than trust the exit code: `simctl boot` returns as soon as\n // CoreSimulator has accepted the request, well before the device is\n // usable, and every tool after this one would fail on a half-booted one.\n const deadline = Date.now() + wait_ms;\n let observed = target.state;\n while (Date.now() < deadline) {\n const now = (await client.listSimulators({ fresh: true })).find(\n (sim) => sim.id === target.id,\n );\n observed = now?.state ?? observed;\n if (observed === \"Booted\") break;\n await sleep(BOOT_POLL_MS);\n }\n return {\n udid: target.id,\n name: target.name,\n state: observed,\n ...(already ? { alreadyThere: true } : {}),\n ...(observed === \"Booted\"\n ? windowWarning\n ? { warning: windowWarning }\n : {}\n : { warning: `Still ${observed} after ${wait_ms}ms. Raise wait_ms, or check Xcode.` }),\n };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_erase\",\n {\n title: \"iOS Simulator: Erase\",\n description:\n \"Wipe a simulator back to a factory state: installed apps, their data, the keychain and \" +\n \"every granted permission. The only irreversible tool here, and the reason it is the only \" +\n \"one behind `confirm`. Use it to test a genuine first launch. A booted simulator is shut \" +\n \"down first and booted again afterwards, because simctl refuses to erase a running one.\",\n inputSchema: z.object({\n device: deviceArg,\n confirm: confirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },\n },\n async ({ device }) =>\n wrap(async () => {\n const target = await client.resolveTarget(device);\n const wasBooted = target.state === \"Booted\";\n if (wasBooted) await client.simctl.shutdown(target.id);\n await client.simctl.erase(target.id);\n if (wasBooted) await client.simctl.boot(target.id);\n return {\n udid: target.id,\n name: target.name,\n erased: true,\n rebooted: wasBooted,\n };\n }),\n );\n};\n","import { spawn } from \"node:child_process\";\nimport { mkdir, open } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { IosError, START_RUNNER_SCRIPT_REMEDY } from \"#/client/errors\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport { deviceArg, wrap } from \"#/tools/util\";\n\n/** Injected in tests so no xcodebuild is ever started by the suite. */\nexport type SpawnRunner = (\n command: string,\n args: string[],\n opts: { env: Record<string, string>; logPath: string },\n) => Promise<number | undefined>;\n\nconst defaultSpawnRunner: SpawnRunner = async (command, args, { env, logPath }) => {\n await mkdir(dirname(logPath), { recursive: true });\n const log = await open(logPath, \"a\");\n const child = spawn(command, args, {\n // Detached, because the XCTest session *is* the HTTP server: it has to\n // outlive this MCP server, or the runner dies with the conversation.\n detached: true,\n stdio: [\"ignore\", log.fd, log.fd],\n env: { ...process.env, ...env },\n });\n child.unref();\n await log.close();\n return child.pid;\n};\n\nconst scriptPath = (): string => {\n // Beside the built module in dist/, or beside src/ in a checkout.\n const here = dirname(fileURLToPath(import.meta.url));\n for (const candidate of [\n join(here, \"..\", \"scripts\", \"wda.sh\"),\n join(here, \"..\", \"..\", \"scripts\", \"wda.sh\"),\n ]) {\n return candidate;\n }\n throw new IosError(\"Could not find `scripts/wda.sh` beside this module.\");\n};\n\nexport const registerRunnerTools = (\n server: McpServer,\n client: SimulatorClient,\n outputDir: string,\n spawnRunner: SpawnRunner = defaultSpawnRunner,\n): void => {\n server.registerTool(\n \"ios_simulator_restart_wda\",\n {\n title: \"iOS Simulator: Restart WebDriverAgent\",\n description:\n \"Start the WebDriverAgent runner, or restart it after it has died. Only the UI tree and \" +\n \"the input tools need it — screenshots and everything simctl does keep working without \" +\n \"it. The runner is started detached so it outlives this conversation, and its output goes \" +\n \"to a log file rather than to you. It does not wait: give it fifteen seconds or so, then \" +\n \"check ios_simulator_diagnostics.\",\n inputSchema: z.object({\n device: deviceArg,\n port: z\n .number()\n .int()\n .min(1)\n .max(65535)\n .optional()\n .describe(\n \"Port to bind, passed to the runner as USE_PORT. Defaults to IOS_SIMULATOR_WDA_PORT. \" +\n \"Two simulators need two ports: they share this Mac's loopback, and a runner with \" +\n \"no USE_PORT scans 8100-8199 and silently takes the next free one.\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, port }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n const usePort = port ?? client.wdaPort;\n const logPath = join(outputDir, \"wda.log\");\n const pid = await spawnRunner(\"/bin/bash\", [scriptPath(), \"run\"], {\n env: {\n IOS_SIMULATOR_ID: target.id,\n IOS_SIMULATOR_WDA_PORT: String(usePort),\n USE_PORT: String(usePort),\n },\n logPath,\n });\n if (pid === undefined) {\n throw new IosError(\"Could not start the runner — the process had no pid.\", {\n remedy: START_RUNNER_SCRIPT_REMEDY,\n });\n }\n return {\n started: true,\n pid,\n port: usePort,\n udid: target.id,\n logPath,\n note: \"Give it ~15s, then check ios_simulator_diagnostics for `wda.reachable`.\",\n };\n }),\n );\n};\n","import { registerInputTools, registerScreenTools } from \"@mgcrea/mcp-ios-core\";\nimport type { McpServer } from \"@modelcontextprotocol/server\";\n\nimport { EMPTY_SCREENSHOT_REMEDY } from \"#/client/errors\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport type { Config } from \"#/config\";\nimport { registerAppTools, registerListAppsTool } from \"#/tools/apps\";\nimport { registerDiagnosticsTools } from \"#/tools/diagnostics\";\nimport { registerEnvironmentTools } from \"#/tools/environment\";\nimport { registerLifecycleTools, registerListTool } from \"#/tools/lifecycle\";\nimport { registerRunnerTools, type SpawnRunner } from \"#/tools/runner\";\nimport { SIMULATOR_NAMING } from \"#/tools/util\";\n\nexport type ToolContext = {\n config: Config;\n /** On by default here — see IOS_SIMULATOR_ALLOW_WRITES. */\n allowWrites: boolean;\n};\n\n/**\n * All capability decisions in one place, so \"why can't I call that\" is answered\n * by one file.\n *\n * The split is observe versus drive, exactly as in the device server. What\n * differs is the **default**: writes are on. The convention that keeps the rest\n * of the fleet read-only exists because a mutating tool acts on someone's real\n * account or someone's real phone, and a simulator is neither — it holds no\n * person's data and `ios_simulator_erase` puts it back to factory in seconds.\n * Turning writes off with IOS_SIMULATOR_ALLOW_WRITES=0 restores the device\n * server's posture, and then the driving tools are *absent* rather than\n * refused, because a refusal still lets a model try, retry and reason about a\n * way around it.\n *\n * The observe half is unusually capable here: `screenshot` goes through simctl,\n * so seeing the screen needs no WebDriverAgent and no setup of any kind. Only\n * `ui_tree` does, and that is a runtime fact reported by\n * ios_simulator_diagnostics rather than a registration decision — the runner\n * can start and stop while this server is connected.\n */\nexport const registerTools = (\n server: McpServer,\n client: SimulatorClient,\n ctx: ToolContext,\n spawnRunner?: SpawnRunner,\n): void => {\n const shared = {\n host: client,\n naming: SIMULATOR_NAMING,\n maxTreeBytes: ctx.config.maxTreeBytes,\n capHint: \"raise IOS_SIMULATOR_MAX_TREE_BYTES\",\n // `home` is the gesture and works; a simulator has no volume rocker worth\n // pressing, and offering one that does nothing is worse than offering none.\n buttons: [\"home\"] as [string, ...string[]],\n emptyScreenshotRemedy: EMPTY_SCREENSHOT_REMEDY,\n // An alert is usually a permission prompt, and a simulator is the one place\n // a permission prompt never had to appear. The moment one is on screen is\n // the moment that is worth saying so.\n alertHint:\n \"If this is a permission prompt, it can be answered before it ever appears: \" +\n 'ios_simulator_set_environment {\"permission\":{\"action\":\"grant\",\"service\":\"photos\",' +\n '\"bundle_id\":\"…\"}}. `reset` puts the prompt back for testing the other branch.',\n // Geometry here is a local plist read reported on every screenshot, not a\n // slow round trip worth its own tool as it is on a device.\n includeDisplayInfo: false,\n };\n\n registerDiagnosticsTools(server, client, ctx);\n registerListTool(server, client);\n registerListAppsTool(server, client);\n registerScreenTools(server, shared);\n\n if (!ctx.allowWrites) return;\n\n registerInputTools(server, shared);\n registerLifecycleTools(server, client);\n registerAppTools(server, client, ctx.config.launchArgs, ctx.config.outputDir);\n registerEnvironmentTools(server, client);\n registerRunnerTools(server, client, ctx.config.outputDir, spawnRunner);\n};\n","import type { ExecImpl, Logger } from \"@mgcrea/mcp-ios-core\";\nimport { McpServer } from \"@modelcontextprotocol/server\";\n\nimport { BUILD_INFO } from \"#/build-info\";\nimport { SimulatorClient } from \"#/client/simulator\";\nimport type { Config } from \"#/config\";\nimport { registerTools } from \"#/tools/index\";\nimport type { SpawnRunner } from \"#/tools/runner\";\n\nexport const SERVER_NAME = BUILD_INFO.name;\nexport const SERVER_VERSION = BUILD_INFO.version;\n\nexport type CreateServerOptions = {\n config: Config;\n /** Override the process boundary (tests): `xcrun`, `plutil` and `sips` never run. */\n exec?: ExecImpl;\n /** Override HTTP to WebDriverAgent (tests). */\n fetch?: typeof fetch;\n /** Override starting the runner (tests): no process is spawned. */\n spawnRunner?: SpawnRunner;\n logger?: Logger;\n};\n\nexport type CreatedServer = {\n server: McpServer;\n client: SimulatorClient;\n};\n\n/**\n * A pure factory. The injectable seams — `exec`, `fetch` and `spawnRunner` —\n * are the whole reason the test suite can drive real tools through the real SDK\n * with no simulator, no Xcode, no WebDriverAgent and no process left running.\n * Nothing below `config.ts` reads `process.env`.\n */\nexport const createServer = (opts: CreateServerOptions): CreatedServer => {\n const { config } = opts;\n const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });\n\n const client = new SimulatorClient({\n xcrunPath: config.xcrunPath,\n sipsPath: config.sipsPath,\n plutilPath: config.plutilPath,\n openPath: config.openPath,\n execTimeoutMs: config.execTimeoutMs,\n wdaTimeoutMs: config.wdaTimeoutMs,\n wdaPort: config.wdaPort,\n allowWrites: config.allowWrites,\n ...(config.wdaUrl ? { wdaUrl: config.wdaUrl } : {}),\n ...(config.simulatorId ? { defaultSimulatorId: config.simulatorId } : {}),\n ...(opts.exec ? { exec: opts.exec } : {}),\n ...(opts.fetch ? { fetch: opts.fetch } : {}),\n ...(opts.logger ? { logger: opts.logger } : {}),\n });\n\n registerTools(server, client, { config, allowWrites: config.allowWrites }, opts.spawnRunner);\n\n return { server, client };\n};\n","import { readFileSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { z } from \"zod\";\n\n/**\n * There are no credentials here, and unlike the device server there is barely\n * any setup either. Two lanes reach a simulator and both are local:\n *\n * * `xcrun simctl` — Apple's own tool. It covers app lifecycle, the device's\n * whole staged environment, and the screen itself: `simctl io screenshot`\n * needs no runner at all.\n * * WebDriverAgent — an HTTP server inside the simulator, on the *host's*\n * loopback since a simulator shares the host network stack. Only the\n * accessibility tree and synthetic touches go through it.\n */\nconst ConfigSchema = z\n .object({\n /**\n * UDID, name, or the literal `booted`. Left unset, a single booted\n * simulator is used and two or more is an error that names them, rather\n * than the coin flip simctl's own `booted` performs.\n */\n simulatorId: z.string().min(1).optional(),\n wdaUrl: z.url().optional(),\n /**\n * Must match the runner's `USE_PORT`. One port per simulator: they all bind\n * the same host loopback, so two runners on 8100 is a silent wrong-target\n * bug rather than a collision.\n */\n wdaPort: z.number().int().min(1).max(65535).default(8100),\n wdaTimeoutMs: z.number().int().min(1000).max(600_000).default(30_000),\n /** A cold boot or a large `install` genuinely takes tens of seconds. */\n execTimeoutMs: z.number().int().min(1000).max(1_800_000).default(120_000),\n /**\n * **On by default**, which is the one place this server deliberately breaks\n * the fleet convention. What makes the device server read-only is that a\n * phone belongs to a real person; a simulator is disposable and holds\n * nobody's data. `ios_simulator_erase` still requires an explicit `confirm`\n * because it is the only irreversible tool here.\n */\n allowWrites: z.boolean().default(true),\n /** Applied by `launch` when the call passes no `arguments` of its own. */\n launchArgs: z.array(z.string()).default([]),\n /** Saved screenshots, launch logs and the runner log. */\n outputDir: z.string().min(1).default(join(tmpdir(), \"mcp-ios-simulator\")),\n maxTreeBytes: z.number().int().min(1000).max(500_000).default(24_000),\n xcrunPath: z.string().min(1).default(\"/usr/bin/xcrun\"),\n sipsPath: z.string().min(1).default(\"/usr/bin/sips\"),\n /** The `listapps` plist lane, and the device-type profile read. */\n plutilPath: z.string().min(1).default(\"/usr/bin/plutil\"),\n /** `simctl boot` is headless; this is what attaches a window. */\n openPath: z.string().min(1).default(\"/usr/bin/open\"),\n })\n .strict()\n .superRefine((_cfg, _ctx) => {\n // Deliberately NOT an error when there is no simulator and no Xcode. An MCP\n // server that exits on startup shows up in the client as a bare\n // \"MCP error -32000: Connection closed\" with stderr swallowed, so the one\n // message that would have explained the problem never reaches anyone.\n // ios_simulator_diagnostics reports all of it as data instead.\n });\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nconst trimmed = (value: string | undefined): string | undefined => {\n const t = value?.trim();\n return t ? t : undefined;\n};\n\nconst parseBool = (value: string | undefined): boolean | undefined => {\n const t = trimmed(value);\n if (t === undefined) return undefined;\n return [\"1\", \"true\", \"yes\", \"on\"].includes(t.toLowerCase());\n};\n\nconst parseIntOpt = (value: string | undefined): number | undefined => {\n const t = trimmed(value);\n if (t === undefined) return undefined;\n const n = Number(t);\n return Number.isInteger(n) ? n : undefined;\n};\n\n/** Whitespace-separated, so `IOS_SIMULATOR_LAUNCH_ARGS=\"-DemoMode -NoCloud\"` works. */\nconst parseArgs = (value: string | undefined): string[] | undefined => {\n const t = trimmed(value);\n return t === undefined ? undefined : t.split(/\\s+/);\n};\n\nexport const defaultConfigPath = (env: NodeJS.ProcessEnv = process.env): string =>\n trimmed(env.IOS_SIMULATOR_CONFIG) ??\n join(homedir(), \".config\", \"ios-simulator-mcp\", \"config.json\");\n\n/**\n * `.strict()` on purpose: a typo'd `wdaPort` spelled `wda_port` must be an\n * error. Silently ignoring an unknown key looks exactly like \"that setting had\n * no effect\", which is the worst possible way to learn where your config came\n * from.\n */\nconst readConfigFile = (path: string): Partial<Config> => {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return {};\n }\n return ConfigSchema.partial().strict().parse(JSON.parse(raw)) as Partial<Config>;\n};\n\n/**\n * Environment first, config file second, **per field** — not whole-source.\n * A one-off `IOS_SIMULATOR_ALLOW_WRITES=0` has to beat a file that says `true`,\n * while a file that sets `simulatorId` keeps working when the environment says\n * nothing about it. Merging field by field is the only rule that gives both.\n */\nexport const loadConfig = (\n env: NodeJS.ProcessEnv = process.env,\n configPath: string = defaultConfigPath(env),\n): Config => {\n const file = readConfigFile(configPath);\n const pick = <K extends keyof Config>(\n fromEnv: Config[K] | undefined,\n key: K,\n ): Config[K] | undefined => fromEnv ?? file[key];\n\n return ConfigSchema.parse({\n simulatorId: pick(trimmed(env.IOS_SIMULATOR_ID), \"simulatorId\"),\n wdaUrl: pick(trimmed(env.IOS_SIMULATOR_WDA_URL), \"wdaUrl\"),\n wdaPort: pick(parseIntOpt(env.IOS_SIMULATOR_WDA_PORT), \"wdaPort\"),\n wdaTimeoutMs: pick(parseIntOpt(env.IOS_SIMULATOR_WDA_TIMEOUT_MS), \"wdaTimeoutMs\"),\n execTimeoutMs: pick(parseIntOpt(env.IOS_SIMULATOR_TIMEOUT_MS), \"execTimeoutMs\"),\n allowWrites: pick(parseBool(env.IOS_SIMULATOR_ALLOW_WRITES), \"allowWrites\"),\n launchArgs: pick(parseArgs(env.IOS_SIMULATOR_LAUNCH_ARGS), \"launchArgs\"),\n outputDir: pick(trimmed(env.IOS_SIMULATOR_OUTPUT_DIR), \"outputDir\"),\n maxTreeBytes: pick(parseIntOpt(env.IOS_SIMULATOR_MAX_TREE_BYTES), \"maxTreeBytes\"),\n xcrunPath: pick(trimmed(env.IOS_SIMULATOR_XCRUN_PATH), \"xcrunPath\"),\n sipsPath: pick(trimmed(env.IOS_SIMULATOR_SIPS_PATH), \"sipsPath\"),\n plutilPath: pick(trimmed(env.IOS_SIMULATOR_PLUTIL_PATH), \"plutilPath\"),\n openPath: pick(trimmed(env.IOS_SIMULATOR_OPEN_PATH), \"openPath\"),\n });\n};\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -114,10 +114,26 @@ declare class Simctl {
114
114
  boot(udid: string): Promise<void>;
115
115
  shutdown(udid: string): Promise<void>;
116
116
  erase(udid: string): Promise<void>;
117
- /** Attach the Simulator UI to whatever is booted. A `boot` alone is headless. */
118
- openApp(openPath: string): Promise<void>;
117
+ /**
118
+ * Put a window on a booted simulator. A `boot` alone is headless.
119
+ *
120
+ * Xcode 27 ships no Simulator.app — the window belongs to DeviceHub, which
121
+ * answers `devices://device/<UDID>` — while Xcode 26 and earlier only have
122
+ * Simulator.app. Try the old app first, since on a Mac with both the one the
123
+ * user already has open is the one they expect; fall back to DeviceHub.
124
+ */
125
+ openApp(openPath: string, udid: string): Promise<"Simulator" | "DeviceHub">;
119
126
  listApps(udid: string): Promise<Record<string, RawSimApp>>;
120
127
  install(udid: string, appPath: string): Promise<void>;
128
+ /**
129
+ * Put photos and videos in the simulator's library.
130
+ *
131
+ * `addmedia` is variadic and takes the paths as plain argv, so unlike `push`
132
+ * this needs no temp file. It is the one route around the simulator's missing
133
+ * camera: an app that reads from the photo library can be exercised end to
134
+ * end, while one that insists on a live capture still cannot.
135
+ */
136
+ addMedia(udid: string, paths: string[]): Promise<void>;
121
137
  /**
122
138
  * Launch, optionally replacing a running copy.
123
139
  *
@@ -234,6 +250,8 @@ type SimulatorClientOptions = {
234
250
  wdaTimeoutMs: number;
235
251
  wdaPort: number;
236
252
  wdaUrl?: string | undefined;
253
+ /** Only so the WebDriverAgent remedies can name a tool this server registers. */
254
+ allowWrites?: boolean | undefined;
237
255
  defaultSimulatorId?: string | undefined;
238
256
  exec?: ExecImpl | undefined;
239
257
  fetch?: typeof fetch | undefined;
@@ -381,11 +399,12 @@ type ScreenProfile = {
381
399
  mainScreenHeight?: number;
382
400
  mainScreenScale?: number;
383
401
  };
384
- declare const readScreenProfile: (deviceType: RawDeviceType, opts: {
402
+ type PlistOpts = {
385
403
  plutilPath: string;
386
404
  exec: ExecImpl;
387
405
  timeoutMs: number;
388
- }) => Promise<ScreenProfile>;
406
+ };
407
+ declare const readScreenProfile: (deviceType: RawDeviceType, opts: PlistOpts) => Promise<ScreenProfile>;
389
408
  /**
390
409
  * Width and height straight out of a PNG's IHDR.
391
410
  *
@@ -419,13 +438,30 @@ declare const toDisplayInfo: (profile: ScreenProfile, capture?: {
419
438
  declare const TIMEOUT_REMEDY: string;
420
439
  declare const TOOLCHAIN_REMEDY: string;
421
440
  /**
422
- * How to get the runner up. Much shorter than the device server's equivalent,
423
- * and that is the headline difference between the two: a simulator runner needs
424
- * no Apple team, no signing, no trust prompt and no tunnel.
441
+ * How to get the runner up, from a shell.
442
+ *
443
+ * Much shorter than the device server's equivalent, and that is the headline
444
+ * difference between the two: a simulator runner needs no Apple team, no
445
+ * signing, no trust prompt and no tunnel.
446
+ *
447
+ * This is the *fallback*. It stays because it is the only route when writes are
448
+ * off, and because it is what a person setting the server up for the first time
449
+ * needs — but a remedy that sends a caller holding ios_simulator_restart_wda out
450
+ * to a terminal is a remedy that wastes the tool it already has.
451
+ */
452
+ declare const START_RUNNER_SCRIPT_REMEDY: string;
453
+ /** The same thing, done with this server's own tool. */
454
+ declare const START_RUNNER_TOOL_REMEDY: string;
455
+ /**
456
+ * How to get the runner up, in whichever way the caller can actually reach.
457
+ *
458
+ * `ios_simulator_restart_wda` is registered only when writes are on, so naming
459
+ * it unconditionally would be the same mistake this fixes, pointed the other
460
+ * way: a remedy naming a tool that is not in the list.
425
461
  */
426
- declare const START_RUNNER_REMEDY: string;
462
+ declare const startRunnerRemedy: (allowWrites: boolean) => string;
427
463
  /** What to do when WebDriverAgent does not answer at all. */
428
- declare const WDA_UNAVAILABLE_REMEDY: string;
464
+ declare const wdaUnavailableRemedy: (allowWrites: boolean) => string;
429
465
  /** No simulator matched, or several did and none was named. */
430
466
  declare class SimulatorNotFoundError extends IosError$1 {
431
467
  readonly name = "SimulatorNotFoundError";
@@ -471,5 +507,5 @@ type BuildInfo = {
471
507
  };
472
508
  declare const BUILD_INFO: BuildInfo;
473
509
  //#endregion
474
- export { BUILD_INFO, CommandError, CommandTimeoutError, type Config, type CreateServerOptions, type CreatedServer, EMPTY_SCREENSHOT_REMEDY, IosError, type RawDeviceType, type RawRuntime, type RawSim, type RawSimApp, RuntimeUnavailableError, SERVER_NAME, SERVER_VERSION, START_RUNNER_REMEDY, type SimAppSummary, Simctl, SimulatorClient, SimulatorNotBootedError, SimulatorNotFoundError, type SimulatorSummary, TIMEOUT_REMEDY, TOOLCHAIN_REMEDY, ToolchainError, WDA_UNAVAILABLE_REMEDY, WdaError, WdaUnavailableError, WritesDisabledError, assertNotBulkTarget, createServer, defaultConfigPath, loadConfig, parseSimctlError, pngDimensions, readScreenProfile, summarizeApps, summarizeDevices, toDisplayInfo, toListRow };
510
+ export { BUILD_INFO, CommandError, CommandTimeoutError, type Config, type CreateServerOptions, type CreatedServer, EMPTY_SCREENSHOT_REMEDY, IosError, type RawDeviceType, type RawRuntime, type RawSim, type RawSimApp, RuntimeUnavailableError, SERVER_NAME, SERVER_VERSION, START_RUNNER_SCRIPT_REMEDY, START_RUNNER_TOOL_REMEDY, type SimAppSummary, Simctl, SimulatorClient, SimulatorNotBootedError, SimulatorNotFoundError, type SimulatorSummary, TIMEOUT_REMEDY, TOOLCHAIN_REMEDY, ToolchainError, WdaError, WdaUnavailableError, WritesDisabledError, assertNotBulkTarget, createServer, defaultConfigPath, loadConfig, parseSimctlError, pngDimensions, readScreenProfile, startRunnerRemedy, summarizeApps, summarizeDevices, toDisplayInfo, toListRow, wdaUnavailableRemedy };
475
511
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/client/simctl.ts","../src/client/shape.ts","../src/client/simulator.ts","../src/config.ts","../src/tools/runner.ts","../src/server.ts","../src/client/display.ts","../src/client/errors.ts","../src/build-info.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/client/simctl.ts","../src/client/shape.ts","../src/client/simulator.ts","../src/config.ts","../src/tools/runner.ts","../src/server.ts","../src/client/display.ts","../src/client/errors.ts","../src/build-info.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as WritesDisabledError, C as SimulatorNotFoundError, D as WDA_UNAVAILABLE_REMEDY, E as ToolchainError, O as WdaError, S as SimulatorNotBootedError, T as TOOLCHAIN_REMEDY, _ as CommandTimeoutError, a as createServer, b as RuntimeUnavailableError, c as assertNotBulkTarget, d as summarizeDevices, f as toListRow, g as CommandError, h as toDisplayInfo, i as SERVER_VERSION, j as BUILD_INFO, k as WdaUnavailableError, l as parseSimctlError, m as readScreenProfile, n as loadConfig, o as SimulatorClient, p as pngDimensions, r as SERVER_NAME, s as Simctl, t as defaultConfigPath, u as summarizeApps, v as EMPTY_SCREENSHOT_REMEDY, w as TIMEOUT_REMEDY, x as START_RUNNER_REMEDY, y as IosError } from "./config-CVBCSB8L.js";
2
- export { BUILD_INFO, CommandError, CommandTimeoutError, EMPTY_SCREENSHOT_REMEDY, IosError, RuntimeUnavailableError, SERVER_NAME, SERVER_VERSION, START_RUNNER_REMEDY, Simctl, SimulatorClient, SimulatorNotBootedError, SimulatorNotFoundError, TIMEOUT_REMEDY, TOOLCHAIN_REMEDY, ToolchainError, WDA_UNAVAILABLE_REMEDY, WdaError, WdaUnavailableError, WritesDisabledError, assertNotBulkTarget, createServer, defaultConfigPath, loadConfig, parseSimctlError, pngDimensions, readScreenProfile, summarizeApps, summarizeDevices, toDisplayInfo, toListRow };
1
+ import { A as WritesDisabledError, C as SimulatorNotBootedError, D as ToolchainError, E as TOOLCHAIN_REMEDY, M as wdaUnavailableRemedy, N as BUILD_INFO, O as WdaError, S as START_RUNNER_TOOL_REMEDY, T as TIMEOUT_REMEDY, _ as CommandTimeoutError, a as createServer, b as RuntimeUnavailableError, c as assertNotBulkTarget, d as summarizeDevices, f as toListRow, g as CommandError, h as toDisplayInfo, i as SERVER_VERSION, j as startRunnerRemedy, k as WdaUnavailableError, l as parseSimctlError, m as readScreenProfile, n as loadConfig, o as SimulatorClient, p as pngDimensions, r as SERVER_NAME, s as Simctl, t as defaultConfigPath, u as summarizeApps, v as EMPTY_SCREENSHOT_REMEDY, w as SimulatorNotFoundError, x as START_RUNNER_SCRIPT_REMEDY, y as IosError } from "./config-D-Zn0z5X.js";
2
+ export { BUILD_INFO, CommandError, CommandTimeoutError, EMPTY_SCREENSHOT_REMEDY, IosError, RuntimeUnavailableError, SERVER_NAME, SERVER_VERSION, START_RUNNER_SCRIPT_REMEDY, START_RUNNER_TOOL_REMEDY, Simctl, SimulatorClient, SimulatorNotBootedError, SimulatorNotFoundError, TIMEOUT_REMEDY, TOOLCHAIN_REMEDY, ToolchainError, WdaError, WdaUnavailableError, WritesDisabledError, assertNotBulkTarget, createServer, defaultConfigPath, loadConfig, parseSimctlError, pngDimensions, readScreenProfile, startRunnerRemedy, summarizeApps, summarizeDevices, toDisplayInfo, toListRow, wdaUnavailableRemedy };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/mcp-ios-simulator",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Model Context Protocol server for driving iOS Simulators: screenshots, UI tree, taps, app lifecycle and device staging",
5
5
  "keywords": [
6
6
  "ios",
@@ -60,7 +60,7 @@
60
60
  "prepublishOnly": "pnpm build"
61
61
  },
62
62
  "dependencies": {
63
- "@mgcrea/mcp-ios-core": "^0.1.0",
63
+ "@mgcrea/mcp-ios-core": "^0.2.0",
64
64
  "@modelcontextprotocol/server": "^2.0.0",
65
65
  "zod": "^4.4.3"
66
66
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-CVBCSB8L.js","names":["IosError","IosError","IosError","IosError","IosError"],"sources":["../src/build-info.ts","../src/client/errors.ts","../src/client/display.ts","../src/client/shape.ts","../src/client/simctl.ts","../src/client/simulator.ts","../src/tools/util.ts","../src/tools/apps.ts","../src/tools/diagnostics.ts","../src/tools/environment.ts","../src/tools/lifecycle.ts","../src/tools/runner.ts","../src/tools/index.ts","../src/server.ts","../src/config.ts"],"sourcesContent":["// Build-time / runtime identity for the running server. `name`/`version` are\n// read from package.json at startup (always accurate); `gitCommit` /\n// `gitCommitDate` are injected by tsdown's `define` substitution at build time\n// and fall back to \"unknown\" when running from source (e.g. vitest).\n\nimport { readFileSync } from \"node:fs\";\n\n// oxlint-disable no-underscore-dangle -- bundler-injected build-time constants.\ndeclare const __GIT_COMMIT__: string;\ndeclare const __GIT_COMMIT_DATE__: string;\n\ntype PackageJson = { name: string; version: string };\n\nconst readPackageJson = (): PackageJson => {\n try {\n const pkgUrl = new URL(\"../package.json\", import.meta.url);\n return JSON.parse(readFileSync(pkgUrl, \"utf8\")) as PackageJson;\n } catch {\n return { name: \"@mgcrea/mcp-ios-device\", version: \"0.0.0\" };\n }\n};\n\nconst pkg = readPackageJson();\n\nexport type BuildInfo = {\n name: string;\n version: string;\n gitCommit: string;\n gitCommitDate: string;\n};\n\nexport const BUILD_INFO: BuildInfo = {\n name: pkg.name,\n version: pkg.version,\n gitCommit: typeof __GIT_COMMIT__ === \"string\" ? __GIT_COMMIT__ : \"unknown\",\n gitCommitDate: typeof __GIT_COMMIT_DATE__ === \"string\" ? __GIT_COMMIT_DATE__ : \"unknown\",\n};\n","import { IosError } from \"@mgcrea/mcp-ios-core\";\n\nexport {\n CommandError,\n CommandTimeoutError,\n IosError,\n ToolchainError,\n WdaError,\n WdaUnavailableError,\n} from \"@mgcrea/mcp-ios-core\";\n\n/** Raising the budget, or noticing the simulator died mid-call. */\nexport const TIMEOUT_REMEDY =\n \"Raise IOS_SIMULATOR_TIMEOUT_MS if this is a slow operation (a boot or a large `install` \" +\n \"genuinely takes tens of seconds), or check the simulator is still booted.\";\n\nexport const TOOLCHAIN_REMEDY =\n \"Install Xcode and run `xcode-select --install`, or point IOS_SIMULATOR_XCRUN_PATH at a \" +\n \"different xcrun.\";\n\n/**\n * How to get the runner up. Much shorter than the device server's equivalent,\n * and that is the headline difference between the two: a simulator runner needs\n * no Apple team, no signing, no trust prompt and no tunnel.\n */\nexport const START_RUNNER_REMEDY =\n \"Build and start the runner with `scripts/wda.sh setup` then `scripts/wda.sh run` (or \" +\n \"`npx -p @mgcrea/mcp-ios-simulator ios-simulator-wda run` from an npm install) and leave it \" +\n \"open — the HTTP server is the XCTest process, so it stops when that command does. It needs no \" +\n \"Apple Developer team: a simulator build is not signed.\";\n\n/** What to do when WebDriverAgent does not answer at all. */\nexport const WDA_UNAVAILABLE_REMEDY =\n `${START_RUNNER_REMEDY} Everything except ios_simulator_ui_tree and the input tools works ` +\n \"without it — screenshots go through simctl. If something else already forwards the port, set \" +\n \"IOS_SIMULATOR_WDA_URL.\";\n\n/** No simulator matched, or several did and none was named. */\nexport class SimulatorNotFoundError extends IosError {\n override readonly name = \"SimulatorNotFoundError\";\n\n constructor(message: string, remedy: string) {\n super(message, { remedy });\n }\n}\n\n/**\n * The simulator exists but is shut down.\n *\n * Its own error class because the fix is a single specific call and nothing\n * else: almost every simctl subcommand fails this way, `listapps` included, so\n * \"it is shut down\" would otherwise be buried in a CoreSimulator envelope that\n * reads like a lookup bug.\n */\nexport class SimulatorNotBootedError extends IosError {\n override readonly name = \"SimulatorNotBootedError\";\n\n constructor(name: string, udid: string) {\n super(`${name} is shut down.`, {\n remedy:\n `Boot it with ios_simulator_power {\"simulator\":\"${udid}\",\"state\":\"booted\"} — this server ` +\n \"never boots a simulator implicitly, because a cold boot takes tens of seconds and puts a \" +\n \"window on the user's screen.\",\n });\n }\n}\n\n/**\n * The simulator's runtime is not installed, so CoreSimulator cannot service it.\n *\n * Worth its own class because the failure it prevents is not an error message\n * at all: `simctl io` against one of these aborts with an uncaught\n * NSInternalInconsistencyException and a thirty-line stack trace.\n */\nexport class RuntimeUnavailableError extends IosError {\n override readonly name = \"RuntimeUnavailableError\";\n\n constructor(name: string, reason: string | undefined) {\n super(`${name} is unavailable${reason ? `: ${reason}` : \".\"}`, {\n remedy:\n \"The iOS runtime this simulator was created against is not installed. Install it in \" +\n \"Xcode > Settings > Components, or clear the orphans with `xcrun simctl delete unavailable`.\",\n });\n }\n}\n\n/** Thrown when a write path is reached while IOS_SIMULATOR_ALLOW_WRITES is off. */\nexport class WritesDisabledError extends IosError {\n override readonly name = \"WritesDisabledError\";\n\n constructor(what: string) {\n super(\n `${what} changes the simulator, but writes are disabled. ` +\n \"Unset IOS_SIMULATOR_ALLOW_WRITES (it defaults to on) to enable them again.\",\n );\n }\n}\n\n/** A shut-down simulator captures nothing, and that is the usual cause here. */\nexport const EMPTY_SCREENSHOT_REMEDY =\n \"The simulator produced an empty capture. Check it is actually booted with \" +\n \"ios_simulator_diagnostics, and that its runtime is installed.\";\n","import { join } from \"node:path\";\n\nimport type { DisplayInfo, ExecImpl } from \"@mgcrea/mcp-ios-core\";\n\nimport { IosError } from \"#/client/errors\";\nimport type { RawDeviceType } from \"#/client/simctl\";\n\n/**\n * Screen geometry for a simulator, which has no `devicectl device info displays`.\n *\n * Three sources were measured, and the split between them is the interesting\n * part:\n *\n * - **`profile.plist`, per device type.** `mainScreenWidth`, `mainScreenHeight`\n * and `mainScreenScale`, agreeing exactly with both `io enumerate` and the\n * captured PNG. A pure file read: no simctl call, nothing to boot, and no\n * crash surface on an unavailable simulator. This is where `pointScale`\n * comes from.\n * - **The captured PNG's own header.** This is where *pixels* come from\n * whenever there is a capture, because `simctl io … screenConfig geometry`\n * can resize a booted simulator's screen at runtime and the profile would\n * then be silently wrong — putting every tap coordinate in the wrong space\n * with nothing looking wrong.\n * - **`io enumerate`**, which also reports it, but is a text parse of a\n * 150-line dump and aborts on an unavailable device. Not used.\n */\nexport const profilePlistPath = (bundlePath: string): string =>\n join(bundlePath, \"Contents\", \"Resources\", \"profile.plist\");\n\nexport type ScreenProfile = {\n mainScreenWidth?: number;\n mainScreenHeight?: number;\n mainScreenScale?: number;\n};\n\nexport const readScreenProfile = async (\n deviceType: RawDeviceType,\n opts: { plutilPath: string; exec: ExecImpl; timeoutMs: number },\n): Promise<ScreenProfile> => {\n if (!deviceType.bundlePath) return {};\n const { stdout } = await opts.exec(\n opts.plutilPath,\n [\"-convert\", \"json\", \"-o\", \"-\", profilePlistPath(deviceType.bundlePath)],\n opts.timeoutMs,\n );\n try {\n return JSON.parse(stdout) as ScreenProfile;\n } catch {\n return {};\n }\n};\n\n/**\n * Width and height straight out of a PNG's IHDR.\n *\n * Cheaper than shelling to `sips` for the one number that must not be stale,\n * and the bytes are already in hand: the signature is fixed, `IHDR` is always\n * the first chunk, and the two dimensions are big-endian at offsets 16 and 20.\n */\nexport const pngDimensions = (png: Buffer): { width: number; height: number } | undefined => {\n const SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n if (png.length < 24 || !png.subarray(0, 8).equals(SIGNATURE)) return undefined;\n if (png.subarray(12, 16).toString(\"ascii\") !== \"IHDR\") return undefined;\n return { width: png.readUInt32BE(16), height: png.readUInt32BE(20) };\n};\n\n/**\n * Assemble a `DisplayInfo`.\n *\n * `orientation` is derived by comparing the capture's own aspect against the\n * profile's portrait dimensions, and is `\"unknown\"` when there is no capture to\n * compare — never guessed as `\"portrait\"`, because the whole point of the field\n * is telling a caller whether the screen they are about to read is rotated.\n *\n * `backlightState` is left undefined: a simulator has no backlight, and\n * `io enumerate`'s `Power state` is about the framebuffer port rather than the\n * screen, so mapping it would be a lie.\n */\nexport const toDisplayInfo = (\n profile: ScreenProfile,\n capture?: { width: number; height: number },\n): DisplayInfo => {\n const scale = profile.mainScreenScale ?? 1;\n const pixelWidth = capture?.width ?? profile.mainScreenWidth;\n const pixelHeight = capture?.height ?? profile.mainScreenHeight;\n\n if (!pixelWidth || !pixelHeight) {\n throw new IosError(\"Could not determine the simulator's screen geometry.\", {\n remedy:\n \"The device type's profile.plist could not be read and no screenshot was available. \" +\n \"Run ios_simulator_diagnostics — an unavailable runtime is the usual cause.\",\n });\n }\n\n const landscape =\n profile.mainScreenWidth !== undefined && profile.mainScreenHeight !== undefined\n ? pixelWidth > pixelHeight && profile.mainScreenWidth < profile.mainScreenHeight\n : pixelWidth > pixelHeight;\n\n return {\n pixelWidth,\n pixelHeight,\n pointWidth: Math.round(pixelWidth / scale),\n pointHeight: Math.round(pixelHeight / scale),\n pointScale: scale,\n orientation: capture ? (landscape ? \"landscape\" : \"portrait\") : \"unknown\",\n };\n};\n","// The context-window layer.\n//\n// `simctl listapps` spends ~38 KB on the 31 apps a stock simulator carries, 25\n// of which are Apple's own, and most of every entry is group-container UUIDs no\n// caller acts on. `list devices` is smaller but still reports five keys per\n// simulator that only matter to CoreSimulator.\n//\n// So: lists return the few fields the other tools consume, and nothing else.\n\nimport type { RawDeviceType, RawRuntime, RawSim, RawSimApp } from \"#/client/simctl\";\n\nexport type SimulatorSummary = {\n /**\n * The UDID. Called `id` because that is the name the shared `ScreenHost`\n * contract uses — a physical device has two identifiers and a simulator has\n * one, so there is nothing here to disambiguate. Rendered back as `udid` by\n * `toListRow`, which is the word anyone reading the output will know it by.\n */\n id: string;\n name: string;\n /** `Booted` or `Shutdown`, verbatim from CoreSimulator. */\n state: string;\n runtime: string;\n deviceType: string | undefined;\n deviceTypeIdentifier: string | undefined;\n /**\n * False when the runtime is missing. Kept rather than filtered: \"why can't I\n * use that one\" has to be answerable from the list, and an unavailable\n * simulator is the one input that makes `simctl io` abort rather than fail.\n */\n available: boolean;\n unavailableReason?: string;\n lastBootedAt?: string;\n};\n\n/**\n * Flatten the runtime-keyed map `list devices` returns.\n *\n * The key is a runtime *identifier* and identifiers are not unique — a machine\n * carrying both iOS 26.4 and 26.4.1 has two entries under\n * `com.apple.CoreSimulator.SimRuntime.iOS-26-4`. So the key is never used as a\n * primary key: it is joined against `list runtimes` for a display name, 0..n\n * matches tolerated, and carried through verbatim besides.\n */\nexport const summarizeDevices = (\n devices: Record<string, RawSim[]>,\n runtimes: RawRuntime[],\n deviceTypes: RawDeviceType[],\n): SimulatorSummary[] => {\n const runtimeName = new Map(\n runtimes.map((r) => [r.identifier, r.name ?? r.version ?? r.identifier]),\n );\n const typeName = new Map(deviceTypes.map((t) => [t.identifier, t.name]));\n\n return Object.entries(devices).flatMap(([runtimeId, sims]) =>\n (sims ?? []).map((sim) => ({\n id: sim.udid,\n name: sim.name,\n state: sim.state,\n runtime: runtimeName.get(runtimeId) ?? runtimeId.split(\".\").pop() ?? runtimeId,\n deviceType: sim.deviceTypeIdentifier ? typeName.get(sim.deviceTypeIdentifier) : undefined,\n deviceTypeIdentifier: sim.deviceTypeIdentifier,\n available: sim.isAvailable !== false,\n ...(sim.availabilityError ? { unavailableReason: sim.availabilityError } : {}),\n ...(sim.lastBootedAt ? { lastBootedAt: sim.lastBootedAt } : {}),\n })),\n );\n};\n\nexport type SimAppSummary = {\n bundleId: string;\n name: string | undefined;\n version: string | undefined;\n build: string | undefined;\n type: string | undefined;\n /**\n * Host filesystem paths, and the reason there is no `pull_container` tool: a\n * simulator's containers are already on this machine, so reading an app's\n * SwiftData store is an ordinary file read rather than a copy off a device.\n */\n bundlePath: string | undefined;\n dataContainer: string | undefined;\n};\n\n/** `file:///Users/…/Foo.app/` → `/Users/…/Foo.app`, which is what a person can use. */\nconst toPath = (url: string | undefined): string | undefined => {\n if (!url) return undefined;\n if (!url.startsWith(\"file://\")) return url;\n try {\n return decodeURIComponent(new URL(url).pathname).replace(/\\/$/, \"\");\n } catch {\n return url;\n }\n};\n\nexport const summarizeApps = (\n apps: Record<string, RawSimApp>,\n opts: { includeAll?: boolean } = {},\n): SimAppSummary[] =>\n Object.values(apps)\n .filter((app) => opts.includeAll === true || app.ApplicationType === \"User\")\n .map((app) => ({\n bundleId: app.CFBundleIdentifier ?? \"\",\n name: app.CFBundleDisplayName ?? app.CFBundleName,\n version: app.CFBundleShortVersionString,\n build: app.CFBundleVersion,\n type: app.ApplicationType,\n bundlePath: toPath(app.Bundle ?? app.Path),\n dataContainer: toPath(app.DataContainer),\n }))\n .filter((app) => app.bundleId !== \"\")\n .toSorted((a, b) => a.bundleId.localeCompare(b.bundleId));\n\n/** The public shape of one row of `ios_simulator_list`. */\nexport const toListRow = (sim: SimulatorSummary): Record<string, unknown> => ({\n udid: sim.id,\n name: sim.name,\n state: sim.state,\n runtime: sim.runtime,\n ...(sim.deviceType ? { deviceType: sim.deviceType } : {}),\n ...(sim.available ? {} : { available: false }),\n ...(sim.unavailableReason ? { unavailableReason: sim.unavailableReason } : {}),\n});\n","import { mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport {\n assertNoShellMetachars,\n createExec,\n type ExecImpl,\n type Logger,\n} from \"@mgcrea/mcp-ios-core\";\n\nimport { IosError, TIMEOUT_REMEDY, TOOLCHAIN_REMEDY } from \"#/client/errors\";\n\n/**\n * `xcrun simctl` — Apple's own simulator tool, and the half of this server that\n * needs nothing installed and nothing signed.\n *\n * It covers strictly more here than `devicectl` does on a physical device: app\n * lifecycle, but also the screen itself (`io screenshot`), the appearance, the\n * status bar, permissions, location and push. Only the accessibility tree and\n * synthetic touches need WebDriverAgent.\n *\n * Three output shapes, and knowing which is which explains the whole class:\n *\n * - `list` takes `-j` and writes clean JSON to **stdout**. It is the only\n * subcommand that does; `listapps --json` does not exist.\n * - `listapps` writes an old-style NeXTSTEP plist. `plutil` converts it, but\n * `ExecImpl` has no stdin, so it goes through a temp file and two processes.\n * - everything else answers in prose, including `(null)` and `unknown`, which\n * are passed through rather than normalised — see the readers below.\n */\nexport type SimctlOptions = {\n xcrunPath: string;\n plutilPath: string;\n timeoutMs: number;\n exec?: ExecImpl | undefined;\n logger?: Logger | undefined;\n};\n\nexport type RawSim = {\n udid: string;\n name: string;\n state: string;\n isAvailable?: boolean;\n availabilityError?: string;\n deviceTypeIdentifier?: string;\n lastBootedAt?: string;\n};\n\nexport type RawRuntime = {\n identifier: string;\n name?: string;\n version?: string;\n isAvailable?: boolean;\n};\n\nexport type RawDeviceType = {\n identifier: string;\n name?: string;\n bundlePath?: string;\n modelIdentifier?: string;\n};\n\n/** One app as `listapps` reports it, once plutil has turned it into JSON. */\nexport type RawSimApp = {\n CFBundleIdentifier?: string;\n CFBundleDisplayName?: string;\n CFBundleName?: string;\n CFBundleShortVersionString?: string;\n CFBundleVersion?: string;\n ApplicationType?: string;\n Bundle?: string;\n Path?: string;\n DataContainer?: string;\n};\n\n/**\n * The two exit codes simctl actually uses, and what they mean.\n *\n * `148` is a device it could not even parse as an identifier. `149` is\n * everything else, wrapped in an envelope whose first line is machine-readable\n * and whose second line is the only part worth showing a person.\n */\nconst INVALID_DEVICE = 148;\n\n/**\n * Pull the human sentence out of a CoreSimulator error envelope.\n *\n * An error was encountered processing the command (domain=…, code=405):\n * Unable to lookup in current state: Shutdown\n *\n * Without this the message a caller sees is the first line, which names a\n * numeric code and says nothing about what to do.\n */\nexport const parseSimctlError = (\n stderr: string,\n): { domain?: string; code?: number; message: string } => {\n const envelope = /\\(domain=([^,]+),\\s*code=(\\d+)\\)\\s*:?\\s*\\n?([\\s\\S]*)/.exec(stderr);\n if (envelope) {\n return {\n domain: envelope[1]?.trim(),\n code: Number(envelope[2]),\n message: (envelope[3] ?? \"\").trim().split(\"\\n\")[0]?.trim() ?? stderr.trim(),\n };\n }\n return { message: stderr.trim().split(\"\\n\")[0]?.trim() ?? stderr.trim() };\n};\n\n/**\n * simctl reports its interesting failures in prose, and each has a different\n * fix. Every string here describes something only a simulator has.\n */\nconst remedyFor = (stderr: string): string | undefined => {\n const { message } = parseSimctlError(stderr);\n const text = `${stderr} ${message}`.toLowerCase();\n if (text.includes(\"invalid device\")) {\n return \"That is not a UDID or a name simctl knows. Run ios_simulator_list to see what exists.\";\n }\n if (text.includes(\"unable to lookup in current state: shutdown\")) {\n return 'The simulator is shut down. Boot it with ios_simulator_power {\"state\":\"booted\"}.';\n }\n if (text.includes(\"current state: booted\")) {\n return \"The simulator is already booted, which for most operations means there is nothing to do.\";\n }\n if (text.includes(\"no such file or directory\")) {\n return (\n \"Check the path exists and is a `.app` **bundle directory** built for the simulator — an \" +\n \"`.ipa` or a device build will not install, and reports exactly this.\"\n );\n }\n if (text.includes(\"failed to initialize io ports\")) {\n return (\n \"The simulator's runtime is not installed, so CoreSimulator cannot service it. Install the \" +\n \"runtime, or clear the orphans with `xcrun simctl delete unavailable`.\"\n );\n }\n return undefined;\n};\n\n/**\n * The one argument that must never reach simctl.\n *\n * `erase`, `delete` and `shutdown` all accept the literal string `all`, and\n * `simctl erase all` wipes every simulator on the machine. A device hint that\n * happens to be the word \"all\" is the single worst thing this server could\n * forward, so it is refused here as well as in the resolver — the resolver is\n * where it should be caught, and this is where it cannot be missed.\n */\nexport const assertNotBulkTarget = (udid: string): void => {\n if (udid === \"all\" || udid === \"unavailable\" || udid === \"booted\") {\n throw new IosError(`Refusing to pass \"${udid}\" to simctl as a device.`, {\n remedy:\n \"simctl reads these as *every* matching simulator, so `erase all` would wipe the machine. \" +\n \"Resolve to one concrete UDID with ios_simulator_list first.\",\n });\n }\n};\n\nexport class Simctl {\n private readonly opts: SimctlOptions;\n private readonly exec: ExecImpl;\n /** Device types change only when Xcode is updated; re-reading is pure cost. */\n private deviceTypes: RawDeviceType[] | undefined;\n\n constructor(opts: SimctlOptions) {\n this.opts = opts;\n this.exec =\n opts.exec ?? createExec({ timeout: TIMEOUT_REMEDY, toolchain: TOOLCHAIN_REMEDY, remedyFor });\n }\n\n /** Raw stdout, for the subcommands that answer in prose. */\n private async run(args: string[]): Promise<string> {\n this.opts.logger?.debug?.(\"simctl\", ...args);\n const { stdout } = await this.exec(\n this.opts.xcrunPath,\n [\"simctl\", ...args],\n this.opts.timeoutMs,\n );\n return stdout;\n }\n\n /**\n * `list` and only `list`. `-e` stops `/` being escaped as `\\/`, which matters\n * only for fixture readability but costs nothing.\n */\n private async runJson<T>(args: string[]): Promise<T> {\n const stdout = await this.run([...args, \"-j\", \"-e\"]);\n try {\n return JSON.parse(stdout) as T;\n } catch {\n throw new IosError(`simctl ${args.join(\" \")} did not return JSON.`, {\n details: stdout.slice(0, 400),\n });\n }\n }\n\n /**\n * A NeXTSTEP plist, via `plutil` and a temp file.\n *\n * The obvious `simctl listapps … | plutil -convert json -o - -` is not\n * expressible: `ExecImpl` takes argv and no stdin, deliberately, because that\n * is what keeps every caller value data rather than shell syntax. So simctl's\n * stdout is written out and handed to plutil by path. A fresh directory per\n * call rather than a shared one, so two concurrent tools cannot read each\n * other's answer with nothing looking wrong.\n */\n private async runPlist<T>(args: string[]): Promise<T> {\n const stdout = await this.run(args);\n const dir = await mkdtemp(join(tmpdir(), \"simctl-\"));\n const path = join(dir, \"out.plist\");\n try {\n await writeFile(path, stdout);\n const { stdout: json } = await this.exec(\n this.opts.plutilPath,\n [\"-convert\", \"json\", \"-o\", \"-\", path],\n this.opts.timeoutMs,\n );\n return JSON.parse(json) as T;\n } catch (err) {\n if (err instanceof IosError) throw err;\n throw new IosError(`Could not read simctl ${args[0]} output as a property list.`, {\n details: stdout.slice(0, 400),\n });\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n\n // ------------------------------------------------------------------ list --\n\n async listDevices(): Promise<Record<string, RawSim[]>> {\n const result = await this.runJson<{ devices?: Record<string, RawSim[]> }>([\"list\", \"devices\"]);\n return result.devices ?? {};\n }\n\n async listRuntimes(): Promise<RawRuntime[]> {\n return (await this.runJson<{ runtimes?: RawRuntime[] }>([\"list\", \"runtimes\"])).runtimes ?? [];\n }\n\n async listDeviceTypes(): Promise<RawDeviceType[]> {\n this.deviceTypes ??=\n (await this.runJson<{ devicetypes?: RawDeviceType[] }>([\"list\", \"devicetypes\"]))\n .devicetypes ?? [];\n return this.deviceTypes;\n }\n\n // ------------------------------------------------------------- lifecycle --\n\n async boot(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"boot\", udid]);\n }\n\n async shutdown(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"shutdown\", udid]);\n }\n\n async erase(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"erase\", udid]);\n }\n\n /** Attach the Simulator UI to whatever is booted. A `boot` alone is headless. */\n async openApp(openPath: string): Promise<void> {\n await this.exec(openPath, [\"-a\", \"Simulator\"], this.opts.timeoutMs);\n }\n\n // ------------------------------------------------------------------ apps --\n\n async listApps(udid: string): Promise<Record<string, RawSimApp>> {\n assertNotBulkTarget(udid);\n return this.runPlist<Record<string, RawSimApp>>([\"listapps\", udid]);\n }\n\n async install(udid: string, appPath: string): Promise<void> {\n assertNotBulkTarget(udid);\n assertNoShellMetachars(\"path\", appPath);\n await this.run([\"install\", udid, appPath]);\n }\n\n /**\n * Launch, optionally replacing a running copy.\n *\n * Environment variables do not go on the command line: simctl reads them from\n * its **own** environment under a `SIMCTL_CHILD_` prefix. `ExecImpl` pins a\n * minimal environment on purpose, so a caller wanting them has to say so, and\n * this is where that would be threaded through.\n */\n async launch(\n udid: string,\n bundleId: string,\n opts: { args?: string[]; terminateExisting?: boolean; stdout?: string; stderr?: string } = {},\n ): Promise<string> {\n assertNotBulkTarget(udid);\n const flags = [\n ...(opts.terminateExisting ? [\"--terminate-running-process\"] : []),\n ...(opts.stdout ? [`--stdout=${opts.stdout}`] : []),\n ...(opts.stderr ? [`--stderr=${opts.stderr}`] : []),\n ];\n return this.run([\"launch\", ...flags, udid, bundleId, ...(opts.args ?? [])]);\n }\n\n async terminate(udid: string, bundleId: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"terminate\", udid, bundleId]);\n }\n\n async openUrl(udid: string, url: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"openurl\", udid, url]);\n }\n\n // ---------------------------------------------------------------- screen --\n\n /**\n * A PNG of the screen, base64, with no WebDriverAgent involved.\n *\n * The documented `-` for stdout **does not work**: measured on Xcode 26.6, it\n * writes a file literally named `-` into the current working directory and\n * leaves stdout empty, exit code 0. So this passes a real path, like the\n * device lane's `--json-output` does, and for the same reason uses a fresh\n * directory per call.\n *\n * A successful capture also writes a `Note: No display specified…` line to\n * stderr, so non-empty stderr is not a failure here.\n */\n async screenshotPng(udid: string): Promise<string> {\n assertNotBulkTarget(udid);\n const dir = await mkdtemp(join(tmpdir(), \"simshot-\"));\n const path = join(dir, \"shot.png\");\n try {\n await this.run([\"io\", udid, \"screenshot\", \"--type=png\", path]);\n return (await readFile(path)).toString(\"base64\");\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n\n // ----------------------------------------------------------- environment --\n\n /**\n * Read a `simctl ui` option.\n *\n * `unknown` and `unsupported` are passed through rather than normalised: they\n * mean genuinely different things — the runtime is too old, versus it failed\n * to read — and collapsing them throws away a distinction a caller can act on.\n */\n async uiGet(udid: string, option: string): Promise<string> {\n assertNotBulkTarget(udid);\n return (await this.run([\"ui\", udid, option])).trim();\n }\n\n async uiSet(udid: string, option: string, value: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"ui\", udid, option, value]);\n }\n\n async statusBarOverride(udid: string, flags: string[]): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"status_bar\", udid, \"override\", ...flags]);\n }\n\n async statusBarClear(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"status_bar\", udid, \"clear\"]);\n }\n\n async statusBarList(udid: string): Promise<string> {\n assertNotBulkTarget(udid);\n return (await this.run([\"status_bar\", udid, \"list\"])).trim();\n }\n\n async locationSet(udid: string, latitude: number, longitude: number): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"location\", udid, \"set\", `${latitude},${longitude}`]);\n }\n\n async locationClear(udid: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"location\", udid, \"clear\"]);\n }\n\n /** `grant` and `revoke` need a bundle id; `reset` does not. */\n async privacy(udid: string, action: string, service: string, bundleId?: string): Promise<void> {\n assertNotBulkTarget(udid);\n await this.run([\"privacy\", udid, action, service, ...(bundleId ? [bundleId] : [])]);\n }\n\n /**\n * A simulated remote push. The payload goes through a temp file rather than\n * stdin, for the same argv-only reason as the plist lane.\n */\n async push(udid: string, bundleId: string, payload: unknown): Promise<void> {\n assertNotBulkTarget(udid);\n const dir = await mkdtemp(join(tmpdir(), \"simpush-\"));\n const path = join(dir, \"payload.json\");\n try {\n await writeFile(path, JSON.stringify(payload));\n await this.run([\"push\", udid, bundleId, path]);\n } finally {\n await rm(dir, { recursive: true, force: true });\n }\n }\n}\n\nexport { INVALID_DEVICE };\n","import {\n createExec,\n WdaClient,\n type DisplayInfo,\n type ExecImpl,\n type Logger,\n type ScreenHost,\n} from \"@mgcrea/mcp-ios-core\";\n\nimport { pngDimensions, readScreenProfile, toDisplayInfo } from \"#/client/display\";\nimport {\n RuntimeUnavailableError,\n SimulatorNotBootedError,\n SimulatorNotFoundError,\n START_RUNNER_REMEDY,\n TIMEOUT_REMEDY,\n TOOLCHAIN_REMEDY,\n WDA_UNAVAILABLE_REMEDY,\n} from \"#/client/errors\";\nimport { summarizeDevices, type SimulatorSummary } from \"#/client/shape\";\nimport { Simctl } from \"#/client/simctl\";\n\n/** What to say when WebDriverAgent fails, on a simulator. */\nconst SIMULATOR_WDA_REMEDIES = {\n unavailable: WDA_UNAVAILABLE_REMEDY,\n notAuthorized:\n \"WebDriverAgent is running but is refusing to drive the UI. On a simulator there is no \" +\n `Enable UI Automation toggle to blame, so this is nearly always a stale runner. ${START_RUNNER_REMEDY}`,\n noForegroundApp:\n \"WebDriverAgent can see no foreground application on a session it has just created. Launch \" +\n \"the app with ios_simulator_launch, or restart the runner with ios_simulator_restart_wda.\",\n noSuchElement:\n \"Call ios_simulator_ui_tree to see what is actually on screen — the element may not have \" +\n \"appeared yet.\",\n};\n\nexport type SimulatorClientOptions = {\n xcrunPath: string;\n sipsPath: string;\n plutilPath: string;\n openPath: string;\n execTimeoutMs: number;\n wdaTimeoutMs: number;\n wdaPort: number;\n wdaUrl?: string | undefined;\n defaultSimulatorId?: string | undefined;\n exec?: ExecImpl | undefined;\n fetch?: typeof fetch | undefined;\n logger?: Logger | undefined;\n};\n\nexport class SimulatorClient implements ScreenHost<SimulatorSummary> {\n readonly simctl: Simctl;\n readonly exec: ExecImpl;\n readonly sipsPath: string;\n readonly plutilPath: string;\n readonly openPath: string;\n readonly execTimeoutMs: number;\n private readonly opts: SimulatorClientOptions;\n private readonly wdaClients = new Map<string, WdaClient>();\n /** `simctl list` is ~80ms and most tools want it twice; 2s collapses that. */\n private cache: { at: number; simulators: SimulatorSummary[] } | undefined;\n private profiles = new Map<string, Awaited<ReturnType<typeof readScreenProfile>>>();\n\n constructor(opts: SimulatorClientOptions) {\n this.opts = opts;\n this.exec = opts.exec ?? createExec({ timeout: TIMEOUT_REMEDY, toolchain: TOOLCHAIN_REMEDY });\n this.sipsPath = opts.sipsPath;\n this.plutilPath = opts.plutilPath;\n this.openPath = opts.openPath;\n this.execTimeoutMs = opts.execTimeoutMs;\n this.simctl = new Simctl({\n xcrunPath: opts.xcrunPath,\n plutilPath: opts.plutilPath,\n timeoutMs: opts.execTimeoutMs,\n ...(opts.exec ? { exec: opts.exec } : {}),\n ...(opts.logger ? { logger: opts.logger } : {}),\n });\n }\n\n async listSimulators(opts: { fresh?: boolean } = {}): Promise<SimulatorSummary[]> {\n const cached = this.cache;\n if (!opts.fresh && cached && Date.now() - cached.at < 2000) return cached.simulators;\n const [devices, runtimes, deviceTypes] = await Promise.all([\n this.simctl.listDevices(),\n this.simctl.listRuntimes(),\n this.simctl.listDeviceTypes(),\n ]);\n const simulators = summarizeDevices(devices, runtimes, deviceTypes);\n this.cache = { at: Date.now(), simulators };\n return simulators;\n }\n\n /**\n * Resolve a hint to exactly one simulator, or refuse.\n *\n * The device server's rule — never guess between two — transfers, but\n * \"connected\" has to become the right predicate. A typical machine carries\n * dozens of simulators, most of them shut down and some of them unavailable,\n * so the population is mostly non-candidates.\n *\n * Three things happen here that cannot happen further down:\n *\n * - `all` and `unavailable` are refused outright. `simctl erase all` wipes\n * every simulator on the machine, and a hint is a string from a model.\n * - `booted` is resolved *here* rather than passed through. simctl's own\n * documentation says that with several booted it \"will choose one of\n * them\", and a coin flip that drives the wrong screen most of the time is\n * exactly what the refuse-to-guess rule exists to prevent.\n * - availability is checked before the target is returned, because `simctl\n * io` against an unavailable simulator aborts with an uncaught\n * NSInternalInconsistencyException rather than failing.\n *\n * State is deliberately *not* checked: `ios_simulator_list` and\n * `ios_simulator_diagnostics` are useful against a shut-down simulator, so\n * requiring booted is each tool's decision, via `requireBooted`.\n */\n async resolveTarget(hint?: string): Promise<SimulatorSummary> {\n const wanted = hint ?? this.opts.defaultSimulatorId;\n const simulators = await this.listSimulators();\n\n if (wanted === \"all\" || wanted === \"unavailable\") {\n throw new SimulatorNotFoundError(\n `\"${wanted}\" is not a simulator, it is how simctl says *every* simulator.`,\n \"Name one by UDID or by name. `simctl erase all` would wipe the machine, so this server \" +\n \"refuses to forward it.\",\n );\n }\n\n const usable = simulators.filter((sim) => sim.available);\n if (wanted !== undefined && wanted !== \"booted\") {\n const byUdid = simulators.filter((sim) => sim.id.toLowerCase() === wanted.toLowerCase());\n // A name is ambiguous far more often than a UDID: the same device name\n // exists once per runtime, and the orphans of an uninstalled runtime keep\n // theirs. Prefer the ones that can actually be used, so \"iPhone 17 Pro\"\n // resolves to the real one rather than erroring against a dead 26.3 twin.\n const named = simulators.filter((sim) => sim.name === wanted);\n const byName = named.some((sim) => sim.available)\n ? named.filter((sim) => sim.available)\n : named;\n const matches = byUdid.length > 0 ? byUdid : byName;\n if (matches.length === 0) {\n throw new SimulatorNotFoundError(\n `No simulator matches \"${wanted}\".`,\n simulators.length === 0\n ? \"simctl knows about no simulators at all. Install a runtime in Xcode > Settings > Components.\"\n : `Run ios_simulator_list to see them. ${usable.length} of ${simulators.length} are available.`,\n );\n }\n if (matches.length > 1) {\n throw new SimulatorNotFoundError(\n `${matches.length} simulators are named \"${wanted}\".`,\n `Pass a UDID instead: ${matches.map((sim) => `${sim.name} = ${sim.id}`).join(\", \")}.`,\n );\n }\n return this.assertAvailable(matches[0] as SimulatorSummary);\n }\n\n const booted = usable.filter((sim) => sim.state === \"Booted\");\n if (booted.length === 1) return this.assertAvailable(booted[0] as SimulatorSummary);\n if (booted.length === 0) {\n const candidates = usable\n .slice(0, 6)\n .map((sim) => `${sim.name} = ${sim.id}`)\n .join(\", \");\n throw new SimulatorNotFoundError(\n \"No simulator is booted.\",\n usable.length === 0\n ? \"No simulator on this machine has an installed runtime. Install one in Xcode > Settings > Components.\"\n : `Boot one with ios_simulator_power, e.g. ${candidates}.`,\n );\n }\n throw new SimulatorNotFoundError(\n `${booted.length} simulators are booted, so there is no obvious default.`,\n `Pass \\`simulator\\` (or set IOS_SIMULATOR_ID) to one of: ${booted\n .map((sim) => `${sim.name} = ${sim.id}`)\n .join(\", \")}.`,\n );\n }\n\n private assertAvailable(sim: SimulatorSummary): SimulatorSummary {\n if (!sim.available) throw new RuntimeUnavailableError(sim.name, sim.unavailableReason);\n return sim;\n }\n\n /** Every tool that needs a running simulator calls this; the resolver does not. */\n requireBooted(sim: SimulatorSummary): SimulatorSummary {\n if (sim.state !== \"Booted\") throw new SimulatorNotBootedError(sim.name, sim.id);\n return sim;\n }\n\n /**\n * Geometry. Cached per device *type* rather than per simulator, since that is\n * what the profile describes and it changes only when Xcode does.\n */\n async display(target: SimulatorSummary, capturePng?: string): Promise<DisplayInfo> {\n const id = target.deviceTypeIdentifier;\n let profile = id ? this.profiles.get(id) : undefined;\n if (!profile) {\n const deviceType = (await this.simctl.listDeviceTypes()).find((t) => t.identifier === id);\n profile = deviceType\n ? await readScreenProfile(deviceType, {\n plutilPath: this.plutilPath,\n exec: this.exec,\n timeoutMs: this.execTimeoutMs,\n })\n : {};\n if (id) this.profiles.set(id, profile);\n }\n const capture = capturePng ? pngDimensions(Buffer.from(capturePng, \"base64\")) : undefined;\n return toDisplayInfo(profile, capture);\n }\n\n /** The capture lane that needs no runner — the whole reason this server is cheap to start. */\n async screenshotPng(target: SimulatorSummary): Promise<string> {\n this.requireBooted(target);\n return this.simctl.screenshotPng(target.id);\n }\n\n /**\n * Where WebDriverAgent is.\n *\n * Unlike the device lane there is no tunnel address to derive: a simulator\n * shares the host's network stack, so the runner's port *is* a host loopback\n * port. That is also the trap — several booted simulators contend for the\n * same one, and WebDriverAgent scans 8100-8199 when `USE_PORT` is unset, so a\n * second runner comes up healthy on 8101 and a server pointed at 8100 drives\n * the first simulator while reporting success. `ios_simulator_diagnostics`\n * reports which simulator actually owns the port.\n */\n wdaUrl(): string {\n return this.opts.wdaUrl ?? `http://127.0.0.1:${this.opts.wdaPort}`;\n }\n\n get wdaPort(): number {\n return this.opts.wdaPort;\n }\n\n wda(_target: SimulatorSummary): WdaClient {\n const url = this.wdaUrl();\n const existing = this.wdaClients.get(url);\n if (existing) return existing;\n const client = new WdaClient({\n baseUrl: async () => url,\n timeoutMs: this.opts.wdaTimeoutMs,\n remedies: SIMULATOR_WDA_REMEDIES,\n ...(this.opts.fetch ? { fetch: this.opts.fetch } : {}),\n ...(this.opts.logger ? { logger: this.opts.logger } : {}),\n });\n this.wdaClients.set(url, client);\n return client;\n }\n}\n","import { createArgs, type ScreenNaming } from \"@mgcrea/mcp-ios-core\";\n\nexport {\n compact,\n fail,\n ok,\n okImage,\n okText,\n toFailure,\n wrap,\n wrapResult,\n} from \"@mgcrea/mcp-ios-core\";\nexport type { ImageContent, TextContent, ToolResult } from \"@mgcrea/mcp-ios-core\";\n\n/**\n * How the shared tools name themselves and each other in this server.\n *\n * `copy.target` is written out rather than derived: a simulator is picked by\n * UDID, by name, or by being the only booted one, and none of those is what the\n * device server's version of this sentence says. `names.listTargets` is\n * overridden for the same reason — `ios_simulator_list_devices` would be a\n * mouthful for a tool that lists simulators.\n */\nexport const SIMULATOR_NAMING: ScreenNaming = {\n prefix: \"ios_simulator\",\n title: \"iOS Simulator\",\n noun: \"simulator\",\n envPrefix: \"IOS_SIMULATOR\",\n names: { listTargets: \"ios_simulator_list\" },\n copy: {\n target:\n \"Which simulator: its UDID or its name as shown by ios_simulator_list. Omit it when exactly \" +\n \"one is booted — that is the normal case, and IOS_SIMULATOR_ID pins it when it is not. \" +\n \"Unlike simctl's own `booted`, this never picks arbitrarily between two.\",\n },\n};\n\nconst args = createArgs(SIMULATOR_NAMING);\n\nexport const { bundleIdArg, confirmArg, detailArg, screenshotArg, settleArg, xArg, yArg } = args;\n\n/**\n * The schema field is `device` in every tool here, shared and local alike.\n *\n * It looks odd next to a server called \"simulator\" and it is still the right\n * name: `simctl`'s own interface calls a simulator a device — `simctl list\n * devices`, and `<device>` in the usage line of every subcommand — so this is\n * Apple's word for the thing, and the `ios_simulator_` prefix already says\n * which kind. Mixing `simulator` here with the shared tools' `device` would be\n * worse than either alone.\n */\nexport const deviceArg = args.targetArg;\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { IosError } from \"#/client/errors\";\nimport { summarizeApps } from \"#/client/shape\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport { bundleIdArg, deviceArg, wrap } from \"#/tools/util\";\n\nexport const registerListAppsTool = (server: McpServer, client: SimulatorClient): void => {\n server.registerTool(\n \"ios_simulator_list_apps\",\n {\n title: \"iOS Simulator: List Apps\",\n description:\n \"List the apps installed on a simulator, with the bundle id every other tool takes. \" +\n \"Defaults to your own apps: a stock simulator carries about thirty, and twenty-five of \" +\n \"them are Apple's. Each entry also carries `dataContainer` — an ordinary path on this \" +\n \"Mac, so an app's database or logs can be read directly with no copy step.\",\n inputSchema: z.object({\n device: deviceArg,\n include_all: z\n .boolean()\n .default(false)\n .describe(\"Include Apple's built-in apps as well. Off by default; the list is long.\"),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ device, include_all }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n const apps = await client.simctl.listApps(target.id);\n return { apps: summarizeApps(apps, { includeAll: include_all }) };\n }),\n );\n};\n\nexport const registerAppTools = (\n server: McpServer,\n client: SimulatorClient,\n launchArgs: string[],\n outputDir: string,\n): void => {\n server.registerTool(\n \"ios_simulator_install\",\n {\n title: \"iOS Simulator: Install\",\n description:\n \"Install a build. The path must be a `.app` **bundle directory** built for the simulator \" +\n '— not an `.ipa`, and not a device build, both of which fail with a bare \"No such file or ' +\n 'directory\" that reads like a typo. Installing over an existing copy replaces it and ' +\n \"keeps its data.\",\n inputSchema: z.object({\n device: deviceArg,\n path: z\n .string()\n .describe(\n \"Absolute path to the .app bundle, e.g. \" +\n \"~/Library/Developer/Xcode/DerivedData/…/Build/Products/Debug-iphonesimulator/Foo.app\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({ device, path }) =>\n wrap(async () => {\n if (!path.startsWith(\"/\")) {\n throw new IosError(`path must be absolute, got \"${path}\".`, {\n remedy: \"Pass the full path to the .app bundle.\",\n });\n }\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.install(target.id, path);\n return { installed: path, udid: target.id };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_launch\",\n {\n title: \"iOS Simulator: Launch\",\n description:\n \"Launch an installed app. Pass `arguments` to put it into a fixture or demo mode — that \" +\n \"is what IOS_SIMULATOR_LAUNCH_ARGS sets as the default for every launch that does not \" +\n \"override it. Standard output and error are captured to files under the output directory, \" +\n \"so an app that dies on launch leaves something readable behind.\",\n inputSchema: z.object({\n device: deviceArg,\n bundle_id: bundleIdArg,\n arguments: z\n .array(z.string())\n .optional()\n .describe(\n 'Launch arguments, e.g. [\"-DemoMode\", \"YES\"]. Overrides IOS_SIMULATOR_LAUNCH_ARGS ' +\n \"rather than adding to it.\",\n ),\n terminate_first: z\n .boolean()\n .default(true)\n .describe(\n \"Replace a running copy rather than attaching to it. On by default so a launch means \" +\n \"a fresh process and a predictable first screen.\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, bundle_id, arguments: args, terminate_first }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n const effective = args ?? launchArgs;\n const stdout = `${outputDir}/${bundle_id}.out.log`;\n const stderr = `${outputDir}/${bundle_id}.err.log`;\n const output = await client.simctl.launch(target.id, bundle_id, {\n args: effective,\n terminateExisting: terminate_first,\n stdout,\n stderr,\n });\n const pid = /:\\s*(\\d+)\\s*$/.exec(output.trim())?.[1];\n return {\n launched: bundle_id,\n ...(pid ? { pid: Number(pid) } : {}),\n ...(effective.length > 0 ? { arguments: effective } : {}),\n ...(args === undefined && launchArgs.length > 0\n ? { argumentsFrom: \"IOS_SIMULATOR_LAUNCH_ARGS\" }\n : {}),\n logs: { stdout, stderr },\n };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_terminate\",\n {\n title: \"iOS Simulator: Terminate\",\n description:\n \"Kill a running app. The app is not uninstalled and its data is untouched — this is how \" +\n \"you get back to a cold start without erasing anything.\",\n inputSchema: z.object({ device: deviceArg, bundle_id: bundleIdArg }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({ device, bundle_id }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.terminate(target.id, bundle_id);\n return { terminated: bundle_id };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_open_url\",\n {\n title: \"iOS Simulator: Open URL\",\n description:\n \"Open a URL on the simulator, which is how you exercise a deep link or a universal link \" +\n \"without finding a way to tap one. An `https://` link opens in Safari unless the app \" +\n \"claims it; a custom scheme goes straight to whichever app registered it.\",\n inputSchema: z.object({\n device: deviceArg,\n url: z.url().describe('e.g. \"myapp://garden/42\" or \"https://example.com/garden/42\".'),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, url }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.openUrl(target.id, url);\n return { opened: url };\n }),\n );\n};\n","import type { DisplayInfo } from \"@mgcrea/mcp-ios-core\";\nimport type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { START_RUNNER_REMEDY } from \"#/client/errors\";\nimport { toListRow, type SimulatorSummary } from \"#/client/shape\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport type { ToolContext } from \"#/tools/index\";\nimport { deviceArg, wrap } from \"#/tools/util\";\n\nexport type Diagnosis = {\n ok: boolean;\n writes: \"enabled\" | \"disabled\";\n /**\n * The booted ones only, plus the target. A typical machine carries thirty-odd\n * simulators and listing them all here spent more of the context window than\n * the whole rest of the report — and none of them is what a diagnosis is\n * about. `counts` carries the shape of the rest; ios_simulator_list is there\n * for the full picture.\n */\n simulators: Record<string, unknown>[];\n counts: { total: number; booted: number; unavailable: number };\n target?: { udid: string; name: string; state: string; runtime: string };\n display?: DisplayInfo;\n wda: {\n url: string;\n port: number;\n reachable: boolean;\n /** Which simulator actually owns the port — the failure unique to this server. */\n boundTo?: string;\n state?: unknown;\n error?: string;\n };\n problems: string[];\n nextSteps: string[];\n};\n\n/**\n * Which simulator owns the WebDriverAgent port.\n *\n * This check has no device-server equivalent and it is the most valuable one\n * here. A simulator shares the host's network stack, so a runner's port is a\n * *host* port, and WebDriverAgent scans 8100-8199 when `USE_PORT` is unset. Two\n * booted simulators therefore give you a second runner answering healthily on\n * 8101 while a server pointed at 8100 drives the first one and reports success\n * on every call.\n *\n * Every process inside a simulator carries `SIMULATOR_UDID` in its environment,\n * and `ps -Eww` prints it, so the port's real owner is knowable rather than\n * assumed.\n */\nconst portOwner = async (client: SimulatorClient, port: number): Promise<string | undefined> => {\n try {\n const { stdout: pids } = await client.exec(\n \"/usr/sbin/lsof\",\n [\"-nP\", `-iTCP:${port}`, \"-sTCP:LISTEN\", \"-t\"],\n 5000,\n );\n const pid = pids.trim().split(\"\\n\")[0];\n if (!pid) return undefined;\n const { stdout: env } = await client.exec(\"/bin/ps\", [\"-Eww\", \"-p\", pid], 5000);\n return /SIMULATOR_UDID=([0-9A-Fa-f-]{36})/.exec(env)?.[1];\n } catch {\n // lsof is not always installed and needs no permissions we can assume.\n // Not knowing the owner is a missing field, not a failure.\n return undefined;\n }\n};\n\n/**\n * The whole point of this tool is that **it never throws**. Every other tool\n * fails when nothing is booted, the runtime is missing or the runner is not up,\n * and each of those looks much like the others from the outside. This one\n * collects all of it as data and names which half is missing.\n */\nexport const diagnose = async (\n client: SimulatorClient,\n ctx: ToolContext,\n deviceHint?: string,\n): Promise<Diagnosis> => {\n const problems: string[] = [];\n const nextSteps: string[] = [];\n const writes = ctx.allowWrites ? \"enabled\" : \"disabled\";\n\n let simulators: SimulatorSummary[] = [];\n try {\n simulators = await client.listSimulators({ fresh: true });\n } catch (err) {\n problems.push(`Could not list simulators: ${err instanceof Error ? err.message : String(err)}`);\n nextSteps.push(\n \"Install Xcode and its command line tools, then run `xcrun simctl list devices`.\",\n );\n return {\n ok: false,\n writes,\n simulators: [],\n counts: { total: 0, booted: 0, unavailable: 0 },\n wda: { url: client.wdaUrl(), port: client.wdaPort, reachable: false },\n problems,\n nextSteps,\n };\n }\n\n const booted = simulators.filter((sim) => sim.state === \"Booted\");\n const unavailable = simulators.filter((sim) => !sim.available);\n const counts = {\n total: simulators.length,\n booted: booted.length,\n unavailable: unavailable.length,\n };\n\n if (simulators.length === 0) {\n problems.push(\"simctl knows about no simulators at all.\");\n nextSteps.push(\"Install a runtime: Xcode > Settings > Components.\");\n } else if (booted.length === 0) {\n problems.push(\"No simulator is booted.\");\n nextSteps.push(\n 'Boot one with ios_simulator_power {\"state\":\"booted\"} — this server never boots implicitly.',\n );\n }\n\n let target: SimulatorSummary | undefined;\n try {\n target = await client.resolveTarget(deviceHint);\n } catch (err) {\n if (booted.length > 0) {\n problems.push(err instanceof Error ? err.message : String(err));\n const remedy = (err as { remedy?: string }).remedy;\n if (remedy) nextSteps.push(remedy);\n }\n }\n\n let display: DisplayInfo | undefined;\n if (target) {\n try {\n display = await client.display(target);\n } catch {\n // Geometry comes from the device type's profile.plist, so failing here\n // means the profile could not be read — worth a missing field, not a\n // failed report.\n }\n }\n\n // WebDriverAgent. `reachable` and usable coincide on a simulator: there is no\n // Enable UI Automation toggle, so the device server's separate `authorized`\n // probe would be pure latency. What replaces it is `boundTo`.\n const url = client.wdaUrl();\n const wda: Diagnosis[\"wda\"] = { url, port: client.wdaPort, reachable: false };\n try {\n wda.state = await client.wda(target ?? ({ id: \"\", name: \"\" } as SimulatorSummary)).status();\n wda.reachable = true;\n } catch (err) {\n wda.error = err instanceof Error ? err.message : String(err);\n problems.push(\"WebDriverAgent is not answering, so the UI tree and the input tools will fail.\");\n nextSteps.push(\n `${START_RUNNER_REMEDY} Everything else — including screenshots — works without it.`,\n );\n }\n\n if (wda.reachable) {\n const owner = await portOwner(client, client.wdaPort);\n if (owner) wda.boundTo = owner;\n if (owner && target && owner.toLowerCase() !== target.id.toLowerCase()) {\n const other = simulators.find((sim) => sim.id.toLowerCase() === owner.toLowerCase());\n problems.push(\n `Port ${client.wdaPort} is owned by ${other?.name ?? owner}, not ${target.name}. ` +\n \"Every tap and every UI tree read would go to the wrong screen.\",\n );\n nextSteps.push(\n \"WebDriverAgent scans 8100-8199 when USE_PORT is unset, so a second simulator's runner \" +\n \"takes the next free port and nothing looks wrong. Give this one its own port with \" +\n \"IOS_SIMULATOR_WDA_PORT and start its runner with a matching USE_PORT.\",\n );\n }\n }\n\n if (unavailable.length > 0) {\n nextSteps.push(\n `${unavailable.length} simulators have no installed runtime and cannot be used at all. ` +\n \"`xcrun simctl delete unavailable` clears them.\",\n );\n }\n\n return {\n ok: problems.length === 0,\n writes,\n simulators: [...booted, ...(target && target.state !== \"Booted\" ? [target] : [])].map(\n toListRow,\n ),\n counts,\n ...(target\n ? {\n target: {\n udid: target.id,\n name: target.name,\n state: target.state,\n runtime: target.runtime,\n },\n }\n : {}),\n ...(display ? { display } : {}),\n wda,\n problems,\n nextSteps,\n };\n};\n\nexport const registerDiagnosticsTools = (\n server: McpServer,\n client: SimulatorClient,\n ctx: ToolContext,\n): void => {\n server.registerTool(\n \"ios_simulator_diagnostics\",\n {\n title: \"iOS Simulator: Diagnostics\",\n description:\n \"Report both lanes separately and never fail: which simulators exist and which are \" +\n \"usable, which one this server would drive, its screen geometry, and whether \" +\n \"WebDriverAgent is answering — including *which* simulator owns its port, which is the \" +\n \"one way to catch a second runner quietly taking your taps. Start here when anything \" +\n \"else misbehaves.\",\n inputSchema: z.object({ device: deviceArg }),\n annotations: { readOnlyHint: true },\n },\n async ({ device }) => wrap(async () => diagnose(client, ctx, device)),\n );\n\n server.registerResource(\n \"diagnostics\",\n \"ios-simulator://diagnostics\",\n {\n title: \"iOS Simulator diagnostics\",\n description: \"The same report as ios_simulator_diagnostics, as a resource.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [\n {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(await diagnose(client, ctx)),\n },\n ],\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { IosError } from \"#/client/errors\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport { bundleIdArg, deviceArg, wrap } from \"#/tools/util\";\n\n/** simctl's own list, verbatim — a value it does not accept is not worth guessing at. */\nconst PRIVACY_SERVICES = [\n \"all\",\n \"calendar\",\n \"contacts-limited\",\n \"contacts\",\n \"location\",\n \"location-always\",\n \"photos-add\",\n \"photos\",\n \"media-library\",\n \"microphone\",\n \"motion\",\n \"reminders\",\n \"siri\",\n] as const;\n\n/** APNs refuses anything larger, and it is worth saying so before the round trip. */\nconst MAX_PUSH_BYTES = 4096;\n\nexport const registerEnvironmentTools = (server: McpServer, client: SimulatorClient): void => {\n server.registerTool(\n \"ios_simulator_set_environment\",\n {\n title: \"iOS Simulator: Set Environment\",\n description:\n \"Stage the simulator before a screenshot or a test: dark mode, Dynamic Type size, \" +\n \"increased contrast, a frozen status bar, a simulated location, and app permissions. \" +\n \"None of this is possible on a physical device, and it is most of the reason to prefer a \" +\n \"simulator for UI work. Every field is optional; those given are applied in order and the \" +\n \"result reports the state afterwards. A status bar override survives a reboot, so clear \" +\n \"it when you are done or it quietly pollutes later screenshots.\",\n inputSchema: z.object({\n device: deviceArg,\n appearance: z.enum([\"light\", \"dark\"]).optional().describe(\"Interface style.\"),\n content_size: z\n .enum([\n \"extra-small\",\n \"small\",\n \"medium\",\n \"large\",\n \"extra-large\",\n \"extra-extra-large\",\n \"extra-extra-extra-large\",\n \"accessibility-medium\",\n \"accessibility-large\",\n \"accessibility-extra-large\",\n \"accessibility-extra-extra-large\",\n \"accessibility-extra-extra-extra-large\",\n ])\n .optional()\n .describe(\"Dynamic Type size. The accessibility- sizes are where layouts break.\"),\n increase_contrast: z.boolean().optional().describe(\"Increase Contrast accessibility mode.\"),\n status_bar: z\n .object({\n time: z\n .string()\n .optional()\n .describe('Fixed clock, e.g. \"9:41\" — what App Store screenshots use.'),\n wifi_bars: z.number().int().min(0).max(3).optional(),\n cellular_bars: z.number().int().min(0).max(4).optional(),\n operator_name: z.string().optional().describe('Carrier name; \"\" hides it.'),\n battery_level: z.number().int().min(0).max(100).optional(),\n battery_state: z.enum([\"charging\", \"charged\", \"discharging\"]).optional(),\n })\n .optional()\n .describe(\"Freeze the status bar. Overrides persist until cleared.\"),\n clear_status_bar: z\n .boolean()\n .default(false)\n .describe(\"Drop every status bar override, restoring the real one.\"),\n location: z\n .object({ latitude: z.number(), longitude: z.number() })\n .optional()\n .describe(\"Teleport the device, for location-gated screens.\"),\n clear_location: z.boolean().default(false).describe(\"Stop simulating a location.\"),\n permission: z\n .object({\n action: z\n .enum([\"grant\", \"revoke\", \"reset\"])\n .describe(\n \"`grant` and `revoke` answer the prompt without showing it; `reset` makes it \" +\n \"appear again on next use.\",\n ),\n service: z.enum(PRIVACY_SERVICES),\n bundle_id: bundleIdArg\n .optional()\n .describe(\"Required for grant and revoke; optional for reset.\"),\n })\n .optional()\n .describe(\n \"Set an app permission directly. This is how you reach the denied branch of a \" +\n \"permission check, which is otherwise a prompt nobody can tap.\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({\n device,\n appearance,\n content_size,\n increase_contrast,\n status_bar,\n clear_status_bar,\n location,\n clear_location,\n permission,\n }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n const applied: string[] = [];\n\n if (appearance) {\n await client.simctl.uiSet(target.id, \"appearance\", appearance);\n applied.push(`appearance=${appearance}`);\n }\n if (content_size) {\n await client.simctl.uiSet(target.id, \"content_size\", content_size);\n applied.push(`content_size=${content_size}`);\n }\n if (increase_contrast !== undefined) {\n await client.simctl.uiSet(\n target.id,\n \"increase_contrast\",\n increase_contrast ? \"enabled\" : \"disabled\",\n );\n applied.push(`increase_contrast=${increase_contrast}`);\n }\n if (clear_status_bar) {\n await client.simctl.statusBarClear(target.id);\n applied.push(\"status_bar cleared\");\n }\n if (status_bar) {\n const flags = [\n ...(status_bar.time ? [\"--time\", status_bar.time] : []),\n ...(status_bar.wifi_bars !== undefined\n ? [\"--wifiMode\", \"active\", \"--wifiBars\", String(status_bar.wifi_bars)]\n : []),\n ...(status_bar.cellular_bars !== undefined\n ? [\"--cellularMode\", \"active\", \"--cellularBars\", String(status_bar.cellular_bars)]\n : []),\n ...(status_bar.operator_name !== undefined\n ? [\"--operatorName\", status_bar.operator_name]\n : []),\n ...(status_bar.battery_level !== undefined\n ? [\"--batteryLevel\", String(status_bar.battery_level)]\n : []),\n ...(status_bar.battery_state ? [\"--batteryState\", status_bar.battery_state] : []),\n ];\n if (flags.length === 0) {\n throw new IosError(\"`status_bar` was given with no fields to override.\", {\n remedy: \"Set at least one field, or use `clear_status_bar` to drop the overrides.\",\n });\n }\n await client.simctl.statusBarOverride(target.id, flags);\n applied.push(\"status_bar overridden\");\n }\n if (clear_location) {\n await client.simctl.locationClear(target.id);\n applied.push(\"location cleared\");\n }\n if (location) {\n await client.simctl.locationSet(target.id, location.latitude, location.longitude);\n applied.push(`location=${location.latitude},${location.longitude}`);\n }\n if (permission) {\n if (permission.action !== \"reset\" && !permission.bundle_id) {\n throw new IosError(`\\`${permission.action}\\` needs a bundle_id.`, {\n remedy: \"Pass the app's bundle id, or use `reset`, which does not need one.\",\n });\n }\n await client.simctl.privacy(\n target.id,\n permission.action,\n permission.service,\n permission.bundle_id,\n );\n applied.push(`permission ${permission.action} ${permission.service}`);\n }\n\n if (applied.length === 0) {\n throw new IosError(\"Nothing to do — no field was given.\", {\n remedy:\n \"Set at least one of appearance, content_size, status_bar, location, permission.\",\n });\n }\n\n // Read back rather than echo the request: `simctl ui` answers `unknown`\n // or `unsupported` on a runtime too old for a setting, and reporting the\n // value we asked for would hide that.\n return {\n applied,\n state: {\n appearance: await client.simctl.uiGet(target.id, \"appearance\"),\n contentSize: await client.simctl.uiGet(target.id, \"content_size\"),\n statusBar: await client.simctl.statusBarList(target.id),\n },\n };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_push\",\n {\n title: \"iOS Simulator: Push\",\n description:\n \"Deliver a push notification, with no APNs certificate and no server. The payload is a \" +\n \"normal remote-notification body and must contain an `aps` key. Only remote pushes are \" +\n \"simulated — not VoIP, complications or file-provider. The app must be installed; it does \" +\n \"not have to be running.\",\n inputSchema: z.object({\n device: deviceArg,\n bundle_id: bundleIdArg,\n payload: z\n .record(z.string(), z.unknown())\n .describe(\n 'The APNs payload, e.g. {\"aps\":{\"alert\":{\"title\":\"Watering due\",\"body\":\"Monstera\"},' +\n '\"sound\":\"default\",\"badge\":1}}. Max 4096 bytes.',\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, bundle_id, payload }) =>\n wrap(async () => {\n if (!(\"aps\" in payload)) {\n throw new IosError(\"A push payload must have a top-level `aps` key.\", {\n remedy: 'Wrap the notification, e.g. {\"aps\":{\"alert\":\"Hello\"}}.',\n });\n }\n const bytes = Buffer.byteLength(JSON.stringify(payload));\n if (bytes > MAX_PUSH_BYTES) {\n throw new IosError(`The payload is ${bytes} bytes; APNs allows ${MAX_PUSH_BYTES}.`, {\n remedy: \"Shorten it — this is refused here rather than by simctl, but the cap is real.\",\n });\n }\n const target = client.requireBooted(await client.resolveTarget(device));\n await client.simctl.push(target.id, bundle_id, payload);\n return { pushed: bundle_id, bytes };\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { toListRow } from \"#/client/shape\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport { confirmArg, deviceArg, wrap } from \"#/tools/util\";\n\n/** Booting is slow and visible; this is how long `power` waits to see it land. */\nconst BOOT_POLL_MS = 500;\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nexport const registerListTool = (server: McpServer, client: SimulatorClient): void => {\n server.registerTool(\n \"ios_simulator_list\",\n {\n title: \"iOS Simulator: List\",\n description:\n \"List the simulators on this machine, with the UDID every other tool takes. Returns all \" +\n \"of them by default — booted and shut down, available and not — because \\\"why can't I use \" +\n 'that one\" has to be answerable from this list: a simulator whose runtime is missing shows ' +\n \"`available: false` with the reason.\",\n inputSchema: z.object({\n booted_only: z\n .boolean()\n .default(false)\n .describe(\"Only simulators that are currently running.\"),\n name_contains: z\n .string()\n .optional()\n .describe('Case-insensitive substring filter on the name, e.g. \"iPhone 17\".'),\n }),\n annotations: { readOnlyHint: true },\n },\n async ({ booted_only, name_contains }) =>\n wrap(async () => {\n const all = await client.listSimulators({ fresh: true });\n const needle = name_contains?.toLowerCase();\n const matched = all.filter(\n (sim) =>\n (!booted_only || sim.state === \"Booted\") &&\n (needle === undefined || sim.name.toLowerCase().includes(needle)),\n );\n const unavailable = all.filter((sim) => !sim.available).length;\n return {\n simulators: matched.map(toListRow),\n booted: all.filter((sim) => sim.state === \"Booted\").length,\n total: all.length,\n ...(unavailable > 0\n ? {\n unavailable,\n note:\n `${unavailable} simulators have no installed runtime and cannot be used. ` +\n \"`xcrun simctl delete unavailable` clears them.\",\n }\n : {}),\n };\n }),\n );\n};\n\nexport const registerLifecycleTools = (server: McpServer, client: SimulatorClient): void => {\n server.registerTool(\n \"ios_simulator_power\",\n {\n title: \"iOS Simulator: Power\",\n description:\n \"Boot or shut down a simulator. Booting is the one thing this server will not do for you \" +\n \"implicitly — a cold boot takes tens of seconds and puts a window on the user's screen, \" +\n \"and it changes which simulator an unqualified call resolves to afterwards. Both \" +\n \"directions are idempotent: booting a booted simulator succeeds.\",\n inputSchema: z.object({\n device: deviceArg,\n state: z\n .enum([\"booted\", \"shutdown\"])\n .describe(\"The state to put it in. `booted` also waits for it to actually get there.\"),\n open_window: z\n .boolean()\n .default(true)\n .describe(\n \"Bring up the Simulator app so the screen is visible. A `boot` on its own is headless, \" +\n \"which is fine for an agent and confusing for a person watching.\",\n ),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .max(300_000)\n .default(60_000)\n .describe(\"How long to wait for a boot to complete before reporting what it saw.\"),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },\n },\n async ({ device, state, open_window, wait_ms }) =>\n wrap(async () => {\n const target = await client.resolveTarget(device);\n if (state === \"shutdown\") {\n if (target.state !== \"Booted\") {\n return { udid: target.id, name: target.name, state: \"Shutdown\", alreadyThere: true };\n }\n await client.simctl.shutdown(target.id);\n return { udid: target.id, name: target.name, state: \"Shutdown\" };\n }\n\n const already = target.state === \"Booted\";\n if (!already) await client.simctl.boot(target.id);\n if (open_window) await client.simctl.openApp(client.openPath);\n\n // Poll rather than trust the exit code: `simctl boot` returns as soon as\n // CoreSimulator has accepted the request, well before the device is\n // usable, and every tool after this one would fail on a half-booted one.\n const deadline = Date.now() + wait_ms;\n let observed = target.state;\n while (Date.now() < deadline) {\n const now = (await client.listSimulators({ fresh: true })).find(\n (sim) => sim.id === target.id,\n );\n observed = now?.state ?? observed;\n if (observed === \"Booted\") break;\n await sleep(BOOT_POLL_MS);\n }\n return {\n udid: target.id,\n name: target.name,\n state: observed,\n ...(already ? { alreadyThere: true } : {}),\n ...(observed === \"Booted\"\n ? {}\n : { warning: `Still ${observed} after ${wait_ms}ms. Raise wait_ms, or check Xcode.` }),\n };\n }),\n );\n\n server.registerTool(\n \"ios_simulator_erase\",\n {\n title: \"iOS Simulator: Erase\",\n description:\n \"Wipe a simulator back to a factory state: installed apps, their data, the keychain and \" +\n \"every granted permission. The only irreversible tool here, and the reason it is the only \" +\n \"one behind `confirm`. Use it to test a genuine first launch. A booted simulator is shut \" +\n \"down first and booted again afterwards, because simctl refuses to erase a running one.\",\n inputSchema: z.object({\n device: deviceArg,\n confirm: confirmArg,\n }),\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },\n },\n async ({ device }) =>\n wrap(async () => {\n const target = await client.resolveTarget(device);\n const wasBooted = target.state === \"Booted\";\n if (wasBooted) await client.simctl.shutdown(target.id);\n await client.simctl.erase(target.id);\n if (wasBooted) await client.simctl.boot(target.id);\n return {\n udid: target.id,\n name: target.name,\n erased: true,\n rebooted: wasBooted,\n };\n }),\n );\n};\n","import { spawn } from \"node:child_process\";\nimport { mkdir, open } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { McpServer } from \"@modelcontextprotocol/server\";\nimport { z } from \"zod\";\n\nimport { IosError, START_RUNNER_REMEDY } from \"#/client/errors\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport { deviceArg, wrap } from \"#/tools/util\";\n\n/** Injected in tests so no xcodebuild is ever started by the suite. */\nexport type SpawnRunner = (\n command: string,\n args: string[],\n opts: { env: Record<string, string>; logPath: string },\n) => Promise<number | undefined>;\n\nconst defaultSpawnRunner: SpawnRunner = async (command, args, { env, logPath }) => {\n await mkdir(dirname(logPath), { recursive: true });\n const log = await open(logPath, \"a\");\n const child = spawn(command, args, {\n // Detached, because the XCTest session *is* the HTTP server: it has to\n // outlive this MCP server, or the runner dies with the conversation.\n detached: true,\n stdio: [\"ignore\", log.fd, log.fd],\n env: { ...process.env, ...env },\n });\n child.unref();\n await log.close();\n return child.pid;\n};\n\nconst scriptPath = (): string => {\n // Beside the built module in dist/, or beside src/ in a checkout.\n const here = dirname(fileURLToPath(import.meta.url));\n for (const candidate of [\n join(here, \"..\", \"scripts\", \"wda.sh\"),\n join(here, \"..\", \"..\", \"scripts\", \"wda.sh\"),\n ]) {\n return candidate;\n }\n throw new IosError(\"Could not find `scripts/wda.sh` beside this module.\");\n};\n\nexport const registerRunnerTools = (\n server: McpServer,\n client: SimulatorClient,\n outputDir: string,\n spawnRunner: SpawnRunner = defaultSpawnRunner,\n): void => {\n server.registerTool(\n \"ios_simulator_restart_wda\",\n {\n title: \"iOS Simulator: Restart WebDriverAgent\",\n description:\n \"Start the WebDriverAgent runner, or restart it after it has died. Only the UI tree and \" +\n \"the input tools need it — screenshots and everything simctl does keep working without \" +\n \"it. The runner is started detached so it outlives this conversation, and its output goes \" +\n \"to a log file rather than to you. It does not wait: give it fifteen seconds or so, then \" +\n \"check ios_simulator_diagnostics.\",\n inputSchema: z.object({\n device: deviceArg,\n port: z\n .number()\n .int()\n .min(1)\n .max(65535)\n .optional()\n .describe(\n \"Port to bind, passed to the runner as USE_PORT. Defaults to IOS_SIMULATOR_WDA_PORT. \" +\n \"Two simulators need two ports: they share this Mac's loopback, and a runner with \" +\n \"no USE_PORT scans 8100-8199 and silently takes the next free one.\",\n ),\n }),\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ device, port }) =>\n wrap(async () => {\n const target = client.requireBooted(await client.resolveTarget(device));\n const usePort = port ?? client.wdaPort;\n const logPath = join(outputDir, \"wda.log\");\n const pid = await spawnRunner(\"/bin/bash\", [scriptPath(), \"run\"], {\n env: {\n IOS_SIMULATOR_ID: target.id,\n IOS_SIMULATOR_WDA_PORT: String(usePort),\n USE_PORT: String(usePort),\n },\n logPath,\n });\n if (pid === undefined) {\n throw new IosError(\"Could not start the runner — the process had no pid.\", {\n remedy: START_RUNNER_REMEDY,\n });\n }\n return {\n started: true,\n pid,\n port: usePort,\n udid: target.id,\n logPath,\n note: \"Give it ~15s, then check ios_simulator_diagnostics for `wda.reachable`.\",\n };\n }),\n );\n};\n","import { registerInputTools, registerScreenTools } from \"@mgcrea/mcp-ios-core\";\nimport type { McpServer } from \"@modelcontextprotocol/server\";\n\nimport { EMPTY_SCREENSHOT_REMEDY } from \"#/client/errors\";\nimport type { SimulatorClient } from \"#/client/simulator\";\nimport type { Config } from \"#/config\";\nimport { registerAppTools, registerListAppsTool } from \"#/tools/apps\";\nimport { registerDiagnosticsTools } from \"#/tools/diagnostics\";\nimport { registerEnvironmentTools } from \"#/tools/environment\";\nimport { registerLifecycleTools, registerListTool } from \"#/tools/lifecycle\";\nimport { registerRunnerTools, type SpawnRunner } from \"#/tools/runner\";\nimport { SIMULATOR_NAMING } from \"#/tools/util\";\n\nexport type ToolContext = {\n config: Config;\n /** On by default here — see IOS_SIMULATOR_ALLOW_WRITES. */\n allowWrites: boolean;\n};\n\n/**\n * All capability decisions in one place, so \"why can't I call that\" is answered\n * by one file.\n *\n * The split is observe versus drive, exactly as in the device server. What\n * differs is the **default**: writes are on. The convention that keeps the rest\n * of the fleet read-only exists because a mutating tool acts on someone's real\n * account or someone's real phone, and a simulator is neither — it holds no\n * person's data and `ios_simulator_erase` puts it back to factory in seconds.\n * Turning writes off with IOS_SIMULATOR_ALLOW_WRITES=0 restores the device\n * server's posture, and then the driving tools are *absent* rather than\n * refused, because a refusal still lets a model try, retry and reason about a\n * way around it.\n *\n * The observe half is unusually capable here: `screenshot` goes through simctl,\n * so seeing the screen needs no WebDriverAgent and no setup of any kind. Only\n * `ui_tree` does, and that is a runtime fact reported by\n * ios_simulator_diagnostics rather than a registration decision — the runner\n * can start and stop while this server is connected.\n */\nexport const registerTools = (\n server: McpServer,\n client: SimulatorClient,\n ctx: ToolContext,\n spawnRunner?: SpawnRunner,\n): void => {\n const shared = {\n host: client,\n naming: SIMULATOR_NAMING,\n maxTreeBytes: ctx.config.maxTreeBytes,\n capHint: \"raise IOS_SIMULATOR_MAX_TREE_BYTES\",\n // `home` is the gesture and works; a simulator has no volume rocker worth\n // pressing, and offering one that does nothing is worse than offering none.\n buttons: [\"home\"] as [string, ...string[]],\n emptyScreenshotRemedy: EMPTY_SCREENSHOT_REMEDY,\n // Geometry here is a local plist read reported on every screenshot, not a\n // slow round trip worth its own tool as it is on a device.\n includeDisplayInfo: false,\n };\n\n registerDiagnosticsTools(server, client, ctx);\n registerListTool(server, client);\n registerListAppsTool(server, client);\n registerScreenTools(server, shared);\n\n if (!ctx.allowWrites) return;\n\n registerInputTools(server, shared);\n registerLifecycleTools(server, client);\n registerAppTools(server, client, ctx.config.launchArgs, ctx.config.outputDir);\n registerEnvironmentTools(server, client);\n registerRunnerTools(server, client, ctx.config.outputDir, spawnRunner);\n};\n","import type { ExecImpl, Logger } from \"@mgcrea/mcp-ios-core\";\nimport { McpServer } from \"@modelcontextprotocol/server\";\n\nimport { BUILD_INFO } from \"#/build-info\";\nimport { SimulatorClient } from \"#/client/simulator\";\nimport type { Config } from \"#/config\";\nimport { registerTools } from \"#/tools/index\";\nimport type { SpawnRunner } from \"#/tools/runner\";\n\nexport const SERVER_NAME = BUILD_INFO.name;\nexport const SERVER_VERSION = BUILD_INFO.version;\n\nexport type CreateServerOptions = {\n config: Config;\n /** Override the process boundary (tests): `xcrun`, `plutil` and `sips` never run. */\n exec?: ExecImpl;\n /** Override HTTP to WebDriverAgent (tests). */\n fetch?: typeof fetch;\n /** Override starting the runner (tests): no process is spawned. */\n spawnRunner?: SpawnRunner;\n logger?: Logger;\n};\n\nexport type CreatedServer = {\n server: McpServer;\n client: SimulatorClient;\n};\n\n/**\n * A pure factory. The injectable seams — `exec`, `fetch` and `spawnRunner` —\n * are the whole reason the test suite can drive real tools through the real SDK\n * with no simulator, no Xcode, no WebDriverAgent and no process left running.\n * Nothing below `config.ts` reads `process.env`.\n */\nexport const createServer = (opts: CreateServerOptions): CreatedServer => {\n const { config } = opts;\n const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });\n\n const client = new SimulatorClient({\n xcrunPath: config.xcrunPath,\n sipsPath: config.sipsPath,\n plutilPath: config.plutilPath,\n openPath: config.openPath,\n execTimeoutMs: config.execTimeoutMs,\n wdaTimeoutMs: config.wdaTimeoutMs,\n wdaPort: config.wdaPort,\n ...(config.wdaUrl ? { wdaUrl: config.wdaUrl } : {}),\n ...(config.simulatorId ? { defaultSimulatorId: config.simulatorId } : {}),\n ...(opts.exec ? { exec: opts.exec } : {}),\n ...(opts.fetch ? { fetch: opts.fetch } : {}),\n ...(opts.logger ? { logger: opts.logger } : {}),\n });\n\n registerTools(server, client, { config, allowWrites: config.allowWrites }, opts.spawnRunner);\n\n return { server, client };\n};\n","import { readFileSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { z } from \"zod\";\n\n/**\n * There are no credentials here, and unlike the device server there is barely\n * any setup either. Two lanes reach a simulator and both are local:\n *\n * * `xcrun simctl` — Apple's own tool. It covers app lifecycle, the device's\n * whole staged environment, and the screen itself: `simctl io screenshot`\n * needs no runner at all.\n * * WebDriverAgent — an HTTP server inside the simulator, on the *host's*\n * loopback since a simulator shares the host network stack. Only the\n * accessibility tree and synthetic touches go through it.\n */\nconst ConfigSchema = z\n .object({\n /**\n * UDID, name, or the literal `booted`. Left unset, a single booted\n * simulator is used and two or more is an error that names them, rather\n * than the coin flip simctl's own `booted` performs.\n */\n simulatorId: z.string().min(1).optional(),\n wdaUrl: z.url().optional(),\n /**\n * Must match the runner's `USE_PORT`. One port per simulator: they all bind\n * the same host loopback, so two runners on 8100 is a silent wrong-target\n * bug rather than a collision.\n */\n wdaPort: z.number().int().min(1).max(65535).default(8100),\n wdaTimeoutMs: z.number().int().min(1000).max(600_000).default(30_000),\n /** A cold boot or a large `install` genuinely takes tens of seconds. */\n execTimeoutMs: z.number().int().min(1000).max(1_800_000).default(120_000),\n /**\n * **On by default**, which is the one place this server deliberately breaks\n * the fleet convention. What makes the device server read-only is that a\n * phone belongs to a real person; a simulator is disposable and holds\n * nobody's data. `ios_simulator_erase` still requires an explicit `confirm`\n * because it is the only irreversible tool here.\n */\n allowWrites: z.boolean().default(true),\n /** Applied by `launch` when the call passes no `arguments` of its own. */\n launchArgs: z.array(z.string()).default([]),\n /** Saved screenshots, launch logs and the runner log. */\n outputDir: z.string().min(1).default(join(tmpdir(), \"mcp-ios-simulator\")),\n maxTreeBytes: z.number().int().min(1000).max(500_000).default(24_000),\n xcrunPath: z.string().min(1).default(\"/usr/bin/xcrun\"),\n sipsPath: z.string().min(1).default(\"/usr/bin/sips\"),\n /** The `listapps` plist lane, and the device-type profile read. */\n plutilPath: z.string().min(1).default(\"/usr/bin/plutil\"),\n /** `simctl boot` is headless; this is what attaches a window. */\n openPath: z.string().min(1).default(\"/usr/bin/open\"),\n })\n .strict()\n .superRefine((_cfg, _ctx) => {\n // Deliberately NOT an error when there is no simulator and no Xcode. An MCP\n // server that exits on startup shows up in the client as a bare\n // \"MCP error -32000: Connection closed\" with stderr swallowed, so the one\n // message that would have explained the problem never reaches anyone.\n // ios_simulator_diagnostics reports all of it as data instead.\n });\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nconst trimmed = (value: string | undefined): string | undefined => {\n const t = value?.trim();\n return t ? t : undefined;\n};\n\nconst parseBool = (value: string | undefined): boolean | undefined => {\n const t = trimmed(value);\n if (t === undefined) return undefined;\n return [\"1\", \"true\", \"yes\", \"on\"].includes(t.toLowerCase());\n};\n\nconst parseIntOpt = (value: string | undefined): number | undefined => {\n const t = trimmed(value);\n if (t === undefined) return undefined;\n const n = Number(t);\n return Number.isInteger(n) ? n : undefined;\n};\n\n/** Whitespace-separated, so `IOS_SIMULATOR_LAUNCH_ARGS=\"-DemoMode -NoCloud\"` works. */\nconst parseArgs = (value: string | undefined): string[] | undefined => {\n const t = trimmed(value);\n return t === undefined ? undefined : t.split(/\\s+/);\n};\n\nexport const defaultConfigPath = (env: NodeJS.ProcessEnv = process.env): string =>\n trimmed(env.IOS_SIMULATOR_CONFIG) ??\n join(homedir(), \".config\", \"ios-simulator-mcp\", \"config.json\");\n\n/**\n * `.strict()` on purpose: a typo'd `wdaPort` spelled `wda_port` must be an\n * error. Silently ignoring an unknown key looks exactly like \"that setting had\n * no effect\", which is the worst possible way to learn where your config came\n * from.\n */\nconst readConfigFile = (path: string): Partial<Config> => {\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return {};\n }\n return ConfigSchema.partial().strict().parse(JSON.parse(raw)) as Partial<Config>;\n};\n\n/**\n * Environment first, config file second, **per field** — not whole-source.\n * A one-off `IOS_SIMULATOR_ALLOW_WRITES=0` has to beat a file that says `true`,\n * while a file that sets `simulatorId` keeps working when the environment says\n * nothing about it. Merging field by field is the only rule that gives both.\n */\nexport const loadConfig = (\n env: NodeJS.ProcessEnv = process.env,\n configPath: string = defaultConfigPath(env),\n): Config => {\n const file = readConfigFile(configPath);\n const pick = <K extends keyof Config>(\n fromEnv: Config[K] | undefined,\n key: K,\n ): Config[K] | undefined => fromEnv ?? file[key];\n\n return ConfigSchema.parse({\n simulatorId: pick(trimmed(env.IOS_SIMULATOR_ID), \"simulatorId\"),\n wdaUrl: pick(trimmed(env.IOS_SIMULATOR_WDA_URL), \"wdaUrl\"),\n wdaPort: pick(parseIntOpt(env.IOS_SIMULATOR_WDA_PORT), \"wdaPort\"),\n wdaTimeoutMs: pick(parseIntOpt(env.IOS_SIMULATOR_WDA_TIMEOUT_MS), \"wdaTimeoutMs\"),\n execTimeoutMs: pick(parseIntOpt(env.IOS_SIMULATOR_TIMEOUT_MS), \"execTimeoutMs\"),\n allowWrites: pick(parseBool(env.IOS_SIMULATOR_ALLOW_WRITES), \"allowWrites\"),\n launchArgs: pick(parseArgs(env.IOS_SIMULATOR_LAUNCH_ARGS), \"launchArgs\"),\n outputDir: pick(trimmed(env.IOS_SIMULATOR_OUTPUT_DIR), \"outputDir\"),\n maxTreeBytes: pick(parseIntOpt(env.IOS_SIMULATOR_MAX_TREE_BYTES), \"maxTreeBytes\"),\n xcrunPath: pick(trimmed(env.IOS_SIMULATOR_XCRUN_PATH), \"xcrunPath\"),\n sipsPath: pick(trimmed(env.IOS_SIMULATOR_SIPS_PATH), \"sipsPath\"),\n plutilPath: pick(trimmed(env.IOS_SIMULATOR_PLUTIL_PATH), \"plutilPath\"),\n openPath: pick(trimmed(env.IOS_SIMULATOR_OPEN_PATH), \"openPath\"),\n });\n};\n"],"mappings":"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"}