@mgcrea/mcp-ios-simulator 0.1.0

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.
@@ -0,0 +1,475 @@
1
+ import { McpServer } from "@modelcontextprotocol/server";
2
+ import { CommandError, CommandTimeoutError, DisplayInfo, ExecImpl, IosError, IosError as IosError$1, Logger, ScreenHost, ToolchainError, WdaClient, WdaError, WdaUnavailableError } from "@mgcrea/mcp-ios-core";
3
+ import { z } from "zod";
4
+ //#region src/client/simctl.d.ts
5
+ /**
6
+ * `xcrun simctl` — Apple's own simulator tool, and the half of this server that
7
+ * needs nothing installed and nothing signed.
8
+ *
9
+ * It covers strictly more here than `devicectl` does on a physical device: app
10
+ * lifecycle, but also the screen itself (`io screenshot`), the appearance, the
11
+ * status bar, permissions, location and push. Only the accessibility tree and
12
+ * synthetic touches need WebDriverAgent.
13
+ *
14
+ * Three output shapes, and knowing which is which explains the whole class:
15
+ *
16
+ * - `list` takes `-j` and writes clean JSON to **stdout**. It is the only
17
+ * subcommand that does; `listapps --json` does not exist.
18
+ * - `listapps` writes an old-style NeXTSTEP plist. `plutil` converts it, but
19
+ * `ExecImpl` has no stdin, so it goes through a temp file and two processes.
20
+ * - everything else answers in prose, including `(null)` and `unknown`, which
21
+ * are passed through rather than normalised — see the readers below.
22
+ */
23
+ type SimctlOptions = {
24
+ xcrunPath: string;
25
+ plutilPath: string;
26
+ timeoutMs: number;
27
+ exec?: ExecImpl | undefined;
28
+ logger?: Logger | undefined;
29
+ };
30
+ type RawSim = {
31
+ udid: string;
32
+ name: string;
33
+ state: string;
34
+ isAvailable?: boolean;
35
+ availabilityError?: string;
36
+ deviceTypeIdentifier?: string;
37
+ lastBootedAt?: string;
38
+ };
39
+ type RawRuntime = {
40
+ identifier: string;
41
+ name?: string;
42
+ version?: string;
43
+ isAvailable?: boolean;
44
+ };
45
+ type RawDeviceType = {
46
+ identifier: string;
47
+ name?: string;
48
+ bundlePath?: string;
49
+ modelIdentifier?: string;
50
+ };
51
+ /** One app as `listapps` reports it, once plutil has turned it into JSON. */
52
+ type RawSimApp = {
53
+ CFBundleIdentifier?: string;
54
+ CFBundleDisplayName?: string;
55
+ CFBundleName?: string;
56
+ CFBundleShortVersionString?: string;
57
+ CFBundleVersion?: string;
58
+ ApplicationType?: string;
59
+ Bundle?: string;
60
+ Path?: string;
61
+ DataContainer?: string;
62
+ };
63
+ /**
64
+ * Pull the human sentence out of a CoreSimulator error envelope.
65
+ *
66
+ * An error was encountered processing the command (domain=…, code=405):
67
+ * Unable to lookup in current state: Shutdown
68
+ *
69
+ * Without this the message a caller sees is the first line, which names a
70
+ * numeric code and says nothing about what to do.
71
+ */
72
+ declare const parseSimctlError: (stderr: string) => {
73
+ domain?: string;
74
+ code?: number;
75
+ message: string;
76
+ };
77
+ /**
78
+ * The one argument that must never reach simctl.
79
+ *
80
+ * `erase`, `delete` and `shutdown` all accept the literal string `all`, and
81
+ * `simctl erase all` wipes every simulator on the machine. A device hint that
82
+ * happens to be the word "all" is the single worst thing this server could
83
+ * forward, so it is refused here as well as in the resolver — the resolver is
84
+ * where it should be caught, and this is where it cannot be missed.
85
+ */
86
+ declare const assertNotBulkTarget: (udid: string) => void;
87
+ declare class Simctl {
88
+ private readonly opts;
89
+ private readonly exec;
90
+ /** Device types change only when Xcode is updated; re-reading is pure cost. */
91
+ private deviceTypes;
92
+ constructor(opts: SimctlOptions);
93
+ /** Raw stdout, for the subcommands that answer in prose. */
94
+ private run;
95
+ /**
96
+ * `list` and only `list`. `-e` stops `/` being escaped as `\/`, which matters
97
+ * only for fixture readability but costs nothing.
98
+ */
99
+ private runJson;
100
+ /**
101
+ * A NeXTSTEP plist, via `plutil` and a temp file.
102
+ *
103
+ * The obvious `simctl listapps … | plutil -convert json -o - -` is not
104
+ * expressible: `ExecImpl` takes argv and no stdin, deliberately, because that
105
+ * is what keeps every caller value data rather than shell syntax. So simctl's
106
+ * stdout is written out and handed to plutil by path. A fresh directory per
107
+ * call rather than a shared one, so two concurrent tools cannot read each
108
+ * other's answer with nothing looking wrong.
109
+ */
110
+ private runPlist;
111
+ listDevices(): Promise<Record<string, RawSim[]>>;
112
+ listRuntimes(): Promise<RawRuntime[]>;
113
+ listDeviceTypes(): Promise<RawDeviceType[]>;
114
+ boot(udid: string): Promise<void>;
115
+ shutdown(udid: string): Promise<void>;
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>;
119
+ listApps(udid: string): Promise<Record<string, RawSimApp>>;
120
+ install(udid: string, appPath: string): Promise<void>;
121
+ /**
122
+ * Launch, optionally replacing a running copy.
123
+ *
124
+ * Environment variables do not go on the command line: simctl reads them from
125
+ * its **own** environment under a `SIMCTL_CHILD_` prefix. `ExecImpl` pins a
126
+ * minimal environment on purpose, so a caller wanting them has to say so, and
127
+ * this is where that would be threaded through.
128
+ */
129
+ launch(udid: string, bundleId: string, opts?: {
130
+ args?: string[];
131
+ terminateExisting?: boolean;
132
+ stdout?: string;
133
+ stderr?: string;
134
+ }): Promise<string>;
135
+ terminate(udid: string, bundleId: string): Promise<void>;
136
+ openUrl(udid: string, url: string): Promise<void>;
137
+ /**
138
+ * A PNG of the screen, base64, with no WebDriverAgent involved.
139
+ *
140
+ * The documented `-` for stdout **does not work**: measured on Xcode 26.6, it
141
+ * writes a file literally named `-` into the current working directory and
142
+ * leaves stdout empty, exit code 0. So this passes a real path, like the
143
+ * device lane's `--json-output` does, and for the same reason uses a fresh
144
+ * directory per call.
145
+ *
146
+ * A successful capture also writes a `Note: No display specified…` line to
147
+ * stderr, so non-empty stderr is not a failure here.
148
+ */
149
+ screenshotPng(udid: string): Promise<string>;
150
+ /**
151
+ * Read a `simctl ui` option.
152
+ *
153
+ * `unknown` and `unsupported` are passed through rather than normalised: they
154
+ * mean genuinely different things — the runtime is too old, versus it failed
155
+ * to read — and collapsing them throws away a distinction a caller can act on.
156
+ */
157
+ uiGet(udid: string, option: string): Promise<string>;
158
+ uiSet(udid: string, option: string, value: string): Promise<void>;
159
+ statusBarOverride(udid: string, flags: string[]): Promise<void>;
160
+ statusBarClear(udid: string): Promise<void>;
161
+ statusBarList(udid: string): Promise<string>;
162
+ locationSet(udid: string, latitude: number, longitude: number): Promise<void>;
163
+ locationClear(udid: string): Promise<void>;
164
+ /** `grant` and `revoke` need a bundle id; `reset` does not. */
165
+ privacy(udid: string, action: string, service: string, bundleId?: string): Promise<void>;
166
+ /**
167
+ * A simulated remote push. The payload goes through a temp file rather than
168
+ * stdin, for the same argv-only reason as the plist lane.
169
+ */
170
+ push(udid: string, bundleId: string, payload: unknown): Promise<void>;
171
+ }
172
+ //#endregion
173
+ //#region src/client/shape.d.ts
174
+ type SimulatorSummary = {
175
+ /**
176
+ * The UDID. Called `id` because that is the name the shared `ScreenHost`
177
+ * contract uses — a physical device has two identifiers and a simulator has
178
+ * one, so there is nothing here to disambiguate. Rendered back as `udid` by
179
+ * `toListRow`, which is the word anyone reading the output will know it by.
180
+ */
181
+ id: string;
182
+ name: string;
183
+ /** `Booted` or `Shutdown`, verbatim from CoreSimulator. */
184
+ state: string;
185
+ runtime: string;
186
+ deviceType: string | undefined;
187
+ deviceTypeIdentifier: string | undefined;
188
+ /**
189
+ * False when the runtime is missing. Kept rather than filtered: "why can't I
190
+ * use that one" has to be answerable from the list, and an unavailable
191
+ * simulator is the one input that makes `simctl io` abort rather than fail.
192
+ */
193
+ available: boolean;
194
+ unavailableReason?: string;
195
+ lastBootedAt?: string;
196
+ };
197
+ /**
198
+ * Flatten the runtime-keyed map `list devices` returns.
199
+ *
200
+ * The key is a runtime *identifier* and identifiers are not unique — a machine
201
+ * carrying both iOS 26.4 and 26.4.1 has two entries under
202
+ * `com.apple.CoreSimulator.SimRuntime.iOS-26-4`. So the key is never used as a
203
+ * primary key: it is joined against `list runtimes` for a display name, 0..n
204
+ * matches tolerated, and carried through verbatim besides.
205
+ */
206
+ declare const summarizeDevices: (devices: Record<string, RawSim[]>, runtimes: RawRuntime[], deviceTypes: RawDeviceType[]) => SimulatorSummary[];
207
+ type SimAppSummary = {
208
+ bundleId: string;
209
+ name: string | undefined;
210
+ version: string | undefined;
211
+ build: string | undefined;
212
+ type: string | undefined;
213
+ /**
214
+ * Host filesystem paths, and the reason there is no `pull_container` tool: a
215
+ * simulator's containers are already on this machine, so reading an app's
216
+ * SwiftData store is an ordinary file read rather than a copy off a device.
217
+ */
218
+ bundlePath: string | undefined;
219
+ dataContainer: string | undefined;
220
+ };
221
+ declare const summarizeApps: (apps: Record<string, RawSimApp>, opts?: {
222
+ includeAll?: boolean;
223
+ }) => SimAppSummary[];
224
+ /** The public shape of one row of `ios_simulator_list`. */
225
+ declare const toListRow: (sim: SimulatorSummary) => Record<string, unknown>;
226
+ //#endregion
227
+ //#region src/client/simulator.d.ts
228
+ type SimulatorClientOptions = {
229
+ xcrunPath: string;
230
+ sipsPath: string;
231
+ plutilPath: string;
232
+ openPath: string;
233
+ execTimeoutMs: number;
234
+ wdaTimeoutMs: number;
235
+ wdaPort: number;
236
+ wdaUrl?: string | undefined;
237
+ defaultSimulatorId?: string | undefined;
238
+ exec?: ExecImpl | undefined;
239
+ fetch?: typeof fetch | undefined;
240
+ logger?: Logger | undefined;
241
+ };
242
+ declare class SimulatorClient implements ScreenHost<SimulatorSummary> {
243
+ readonly simctl: Simctl;
244
+ readonly exec: ExecImpl;
245
+ readonly sipsPath: string;
246
+ readonly plutilPath: string;
247
+ readonly openPath: string;
248
+ readonly execTimeoutMs: number;
249
+ private readonly opts;
250
+ private readonly wdaClients;
251
+ /** `simctl list` is ~80ms and most tools want it twice; 2s collapses that. */
252
+ private cache;
253
+ private profiles;
254
+ constructor(opts: SimulatorClientOptions);
255
+ listSimulators(opts?: {
256
+ fresh?: boolean;
257
+ }): Promise<SimulatorSummary[]>;
258
+ /**
259
+ * Resolve a hint to exactly one simulator, or refuse.
260
+ *
261
+ * The device server's rule — never guess between two — transfers, but
262
+ * "connected" has to become the right predicate. A typical machine carries
263
+ * dozens of simulators, most of them shut down and some of them unavailable,
264
+ * so the population is mostly non-candidates.
265
+ *
266
+ * Three things happen here that cannot happen further down:
267
+ *
268
+ * - `all` and `unavailable` are refused outright. `simctl erase all` wipes
269
+ * every simulator on the machine, and a hint is a string from a model.
270
+ * - `booted` is resolved *here* rather than passed through. simctl's own
271
+ * documentation says that with several booted it "will choose one of
272
+ * them", and a coin flip that drives the wrong screen most of the time is
273
+ * exactly what the refuse-to-guess rule exists to prevent.
274
+ * - availability is checked before the target is returned, because `simctl
275
+ * io` against an unavailable simulator aborts with an uncaught
276
+ * NSInternalInconsistencyException rather than failing.
277
+ *
278
+ * State is deliberately *not* checked: `ios_simulator_list` and
279
+ * `ios_simulator_diagnostics` are useful against a shut-down simulator, so
280
+ * requiring booted is each tool's decision, via `requireBooted`.
281
+ */
282
+ resolveTarget(hint?: string): Promise<SimulatorSummary>;
283
+ private assertAvailable;
284
+ /** Every tool that needs a running simulator calls this; the resolver does not. */
285
+ requireBooted(sim: SimulatorSummary): SimulatorSummary;
286
+ /**
287
+ * Geometry. Cached per device *type* rather than per simulator, since that is
288
+ * what the profile describes and it changes only when Xcode does.
289
+ */
290
+ display(target: SimulatorSummary, capturePng?: string): Promise<DisplayInfo>;
291
+ /** The capture lane that needs no runner — the whole reason this server is cheap to start. */
292
+ screenshotPng(target: SimulatorSummary): Promise<string>;
293
+ /**
294
+ * Where WebDriverAgent is.
295
+ *
296
+ * Unlike the device lane there is no tunnel address to derive: a simulator
297
+ * shares the host's network stack, so the runner's port *is* a host loopback
298
+ * port. That is also the trap — several booted simulators contend for the
299
+ * same one, and WebDriverAgent scans 8100-8199 when `USE_PORT` is unset, so a
300
+ * second runner comes up healthy on 8101 and a server pointed at 8100 drives
301
+ * the first simulator while reporting success. `ios_simulator_diagnostics`
302
+ * reports which simulator actually owns the port.
303
+ */
304
+ wdaUrl(): string;
305
+ get wdaPort(): number;
306
+ wda(_target: SimulatorSummary): WdaClient;
307
+ }
308
+ //#endregion
309
+ //#region src/config.d.ts
310
+ /**
311
+ * There are no credentials here, and unlike the device server there is barely
312
+ * any setup either. Two lanes reach a simulator and both are local:
313
+ *
314
+ * * `xcrun simctl` — Apple's own tool. It covers app lifecycle, the device's
315
+ * whole staged environment, and the screen itself: `simctl io screenshot`
316
+ * needs no runner at all.
317
+ * * WebDriverAgent — an HTTP server inside the simulator, on the *host's*
318
+ * loopback since a simulator shares the host network stack. Only the
319
+ * accessibility tree and synthetic touches go through it.
320
+ */
321
+ declare const ConfigSchema: z.ZodObject<{
322
+ simulatorId: z.ZodOptional<z.ZodString>;
323
+ wdaUrl: z.ZodOptional<z.ZodURL>;
324
+ wdaPort: z.ZodDefault<z.ZodNumber>;
325
+ wdaTimeoutMs: z.ZodDefault<z.ZodNumber>;
326
+ execTimeoutMs: z.ZodDefault<z.ZodNumber>;
327
+ allowWrites: z.ZodDefault<z.ZodBoolean>;
328
+ launchArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
329
+ outputDir: z.ZodDefault<z.ZodString>;
330
+ maxTreeBytes: z.ZodDefault<z.ZodNumber>;
331
+ xcrunPath: z.ZodDefault<z.ZodString>;
332
+ sipsPath: z.ZodDefault<z.ZodString>;
333
+ plutilPath: z.ZodDefault<z.ZodString>;
334
+ openPath: z.ZodDefault<z.ZodString>;
335
+ }, z.core.$strict>;
336
+ type Config = z.infer<typeof ConfigSchema>;
337
+ declare const defaultConfigPath: (env?: NodeJS.ProcessEnv) => string;
338
+ /**
339
+ * Environment first, config file second, **per field** — not whole-source.
340
+ * A one-off `IOS_SIMULATOR_ALLOW_WRITES=0` has to beat a file that says `true`,
341
+ * while a file that sets `simulatorId` keeps working when the environment says
342
+ * nothing about it. Merging field by field is the only rule that gives both.
343
+ */
344
+ declare const loadConfig: (env?: NodeJS.ProcessEnv, configPath?: string) => Config;
345
+ //#endregion
346
+ //#region src/tools/runner.d.ts
347
+ /** Injected in tests so no xcodebuild is ever started by the suite. */
348
+ type SpawnRunner = (command: string, args: string[], opts: {
349
+ env: Record<string, string>;
350
+ logPath: string;
351
+ }) => Promise<number | undefined>;
352
+ //#endregion
353
+ //#region src/server.d.ts
354
+ declare const SERVER_NAME: string;
355
+ declare const SERVER_VERSION: string;
356
+ type CreateServerOptions = {
357
+ config: Config;
358
+ /** Override the process boundary (tests): `xcrun`, `plutil` and `sips` never run. */
359
+ exec?: ExecImpl;
360
+ /** Override HTTP to WebDriverAgent (tests). */
361
+ fetch?: typeof fetch;
362
+ /** Override starting the runner (tests): no process is spawned. */
363
+ spawnRunner?: SpawnRunner;
364
+ logger?: Logger;
365
+ };
366
+ type CreatedServer = {
367
+ server: McpServer;
368
+ client: SimulatorClient;
369
+ };
370
+ /**
371
+ * A pure factory. The injectable seams — `exec`, `fetch` and `spawnRunner` —
372
+ * are the whole reason the test suite can drive real tools through the real SDK
373
+ * with no simulator, no Xcode, no WebDriverAgent and no process left running.
374
+ * Nothing below `config.ts` reads `process.env`.
375
+ */
376
+ declare const createServer: (opts: CreateServerOptions) => CreatedServer;
377
+ //#endregion
378
+ //#region src/client/display.d.ts
379
+ type ScreenProfile = {
380
+ mainScreenWidth?: number;
381
+ mainScreenHeight?: number;
382
+ mainScreenScale?: number;
383
+ };
384
+ declare const readScreenProfile: (deviceType: RawDeviceType, opts: {
385
+ plutilPath: string;
386
+ exec: ExecImpl;
387
+ timeoutMs: number;
388
+ }) => Promise<ScreenProfile>;
389
+ /**
390
+ * Width and height straight out of a PNG's IHDR.
391
+ *
392
+ * Cheaper than shelling to `sips` for the one number that must not be stale,
393
+ * and the bytes are already in hand: the signature is fixed, `IHDR` is always
394
+ * the first chunk, and the two dimensions are big-endian at offsets 16 and 20.
395
+ */
396
+ declare const pngDimensions: (png: Buffer) => {
397
+ width: number;
398
+ height: number;
399
+ } | undefined;
400
+ /**
401
+ * Assemble a `DisplayInfo`.
402
+ *
403
+ * `orientation` is derived by comparing the capture's own aspect against the
404
+ * profile's portrait dimensions, and is `"unknown"` when there is no capture to
405
+ * compare — never guessed as `"portrait"`, because the whole point of the field
406
+ * is telling a caller whether the screen they are about to read is rotated.
407
+ *
408
+ * `backlightState` is left undefined: a simulator has no backlight, and
409
+ * `io enumerate`'s `Power state` is about the framebuffer port rather than the
410
+ * screen, so mapping it would be a lie.
411
+ */
412
+ declare const toDisplayInfo: (profile: ScreenProfile, capture?: {
413
+ width: number;
414
+ height: number;
415
+ }) => DisplayInfo;
416
+ //#endregion
417
+ //#region src/client/errors.d.ts
418
+ /** Raising the budget, or noticing the simulator died mid-call. */
419
+ declare const TIMEOUT_REMEDY: string;
420
+ declare const TOOLCHAIN_REMEDY: string;
421
+ /**
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.
425
+ */
426
+ declare const START_RUNNER_REMEDY: string;
427
+ /** What to do when WebDriverAgent does not answer at all. */
428
+ declare const WDA_UNAVAILABLE_REMEDY: string;
429
+ /** No simulator matched, or several did and none was named. */
430
+ declare class SimulatorNotFoundError extends IosError$1 {
431
+ readonly name = "SimulatorNotFoundError";
432
+ constructor(message: string, remedy: string);
433
+ }
434
+ /**
435
+ * The simulator exists but is shut down.
436
+ *
437
+ * Its own error class because the fix is a single specific call and nothing
438
+ * else: almost every simctl subcommand fails this way, `listapps` included, so
439
+ * "it is shut down" would otherwise be buried in a CoreSimulator envelope that
440
+ * reads like a lookup bug.
441
+ */
442
+ declare class SimulatorNotBootedError extends IosError$1 {
443
+ readonly name = "SimulatorNotBootedError";
444
+ constructor(name: string, udid: string);
445
+ }
446
+ /**
447
+ * The simulator's runtime is not installed, so CoreSimulator cannot service it.
448
+ *
449
+ * Worth its own class because the failure it prevents is not an error message
450
+ * at all: `simctl io` against one of these aborts with an uncaught
451
+ * NSInternalInconsistencyException and a thirty-line stack trace.
452
+ */
453
+ declare class RuntimeUnavailableError extends IosError$1 {
454
+ readonly name = "RuntimeUnavailableError";
455
+ constructor(name: string, reason: string | undefined);
456
+ }
457
+ /** Thrown when a write path is reached while IOS_SIMULATOR_ALLOW_WRITES is off. */
458
+ declare class WritesDisabledError extends IosError$1 {
459
+ readonly name = "WritesDisabledError";
460
+ constructor(what: string);
461
+ }
462
+ /** A shut-down simulator captures nothing, and that is the usual cause here. */
463
+ declare const EMPTY_SCREENSHOT_REMEDY: string;
464
+ //#endregion
465
+ //#region src/build-info.d.ts
466
+ type BuildInfo = {
467
+ name: string;
468
+ version: string;
469
+ gitCommit: string;
470
+ gitCommitDate: string;
471
+ };
472
+ declare const BUILD_INFO: BuildInfo;
473
+ //#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 };
475
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;;;KA+BY;EACV;EACA;EACA;EACA,OAAO;EACP,SAAS;;KAGC;EACV;EACA;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;;;KAIU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;cAqBW,mBAAgB;EAExB;EAAiB;EAAe;;;;;;;;;;;cAoDxB,sBAAmB;cAUnB;mBACM;mBACA;;UAET;EAER,YAAY,MAAM;;UAOJ;;;;;UAcA;;;;;;;;;;;UAqBA;EAwBR,eAAe,QAAQ,eAAe;EAKtC,gBAAgB,QAAQ;EAIxB,mBAAmB,QAAQ;EAS3B,KAAK,eAAe;EAKpB,SAAS,eAAe;EAKxB,MAAM,eAAe;;EAMrB,QAAQ,mBAAmB;EAM3B,SAAS,eAAe,QAAQ,eAAe;EAK/C,QAAQ,cAAc,kBAAkB;;;;;;;;;EAcxC,OACJ,cACA,kBACA;IAAQ;IAAiB;IAA6B;IAAiB;MACtE;EAUG,UAAU,cAAc,mBAAmB;EAK3C,QAAQ,cAAc,cAAc;;;;;;;;;;;;;EAmBpC,cAAc,eAAe;;;;;;;;EAqB7B,MAAM,cAAc,iBAAiB;EAKrC,MAAM,cAAc,gBAAgB,gBAAgB;EAKpD,kBAAkB,cAAc,kBAAkB;EAKlD,eAAe,eAAe;EAK9B,cAAc,eAAe;EAK7B,YAAY,cAAc,kBAAkB,oBAAoB;EAKhE,cAAc,eAAe;;EAM7B,QAAQ,cAAc,gBAAgB,iBAAiB,oBAAoB;;;;;EAS3E,KAAK,cAAc,kBAAkB,mBAAmB;;;;KC9XpD;;;;;;;EAOV;EACA;;EAEA;EACA;EACA;EACA;;;;;;EAMA;EACA;EACA;;;;;;;;;;;cAYW,mBAAgB,SAClB,eAAe,WAAS,UACvB,cAAY,aACT,oBACZ;KAqBS;EACV;EACA;EACA;EACA;EACA;;;;;;EAMA;EACA;;cAcW,gBAAa,MAClB,eAAe,YAAU;EACvB;MACP;;cAgBU,YAAS,KAAS,qBAAmB;;;KC9EtC;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,eAAe;EACf,SAAS;;cAGE,2BAA2B,WAAW;WACxC,QAAQ;WACR,MAAM;WACN;WACA;WACA;WACA;mBACQ;mBACA;;UAET;UACA;EAER,YAAY,MAAM;EAgBZ,eAAe;IAAQ;MAAyB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;EAqCxD,cAAc,gBAAgB,QAAQ;UA+DpC;;EAMR,cAAc,KAAK,mBAAmB;;;;;EAShC,QAAQ,QAAQ,kBAAkB,sBAAsB,QAAQ;;EAmBhE,cAAc,QAAQ,mBAAmB;;;;;;;;;;;;EAgB/C;MAII;EAIJ,IAAI,SAAS,mBAAmB;;;;;;;;;;;;;;;cC7N5B,cAAY,EAAA;;;;;;;;;;;;;;GA6Cd,EAAA,KAAA;KAEQ,SAAS,EAAE,aAAa;cA0BvB,oBAAiB,MAAS,OAAO;;;;;;;cA0BjC,aAAU,MAChB,OAAO,YAAU,wBAErB;;;;KC1GS,eACV,iBACA,gBACA;EAAQ,KAAK;EAAwB;MAClC;;;cCRQ;cACA;KAED;EACV,QAAQ;;EAER,OAAO;;EAEP,eAAe;;EAEf,cAAc;EACd,SAAS;;KAGC;EACV,QAAQ;EACR,QAAQ;;;;;;;;cASG,eAAY,MAAU,wBAAsB;;;KCL7C;EACV;EACA;EACA;;cAGW,oBAAiB,YAChB,eAAa;EACjB;EAAoB,MAAM;EAAU;MAC3C,QAAQ;;;;;;;;cAqBE,gBAAa,KAAS;EAAW;EAAe;;;;;;;;;;;;;;cAmBhD,gBAAa,SACf,eAAa;EACV;EAAe;MAC1B;;;;cCrEU;cAIA;;;;;;cASA;;cAOA;;cAMA,+BAA+B;WACxB;EAElB,YAAY,iBAAiB;;;;;;;;;;cAalB,gCAAgC;WACzB;EAElB,YAAY,cAAc;;;;;;;;;cAiBf,gCAAgC;WACzB;EAElB,YAAY,cAAc;;;cAUf,4BAA4B;WACrB;EAElB,YAAY;;;cASD;;;KC3ED;EACV;EACA;EACA;EACA;;cAGW,YAAY"}
package/dist/index.js ADDED
@@ -0,0 +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-C1TH-p-c.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 };
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@mgcrea/mcp-ios-simulator",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol server for driving iOS Simulators: screenshots, UI tree, taps, app lifecycle and device staging",
5
+ "keywords": [
6
+ "ios",
7
+ "mcp",
8
+ "model-context-protocol",
9
+ "simctl",
10
+ "simulator",
11
+ "webdriveragent"
12
+ ],
13
+ "homepage": "https://github.com/mgcrea/mcp-ios-simulator#readme",
14
+ "license": "MIT",
15
+ "author": "Olivier Louvignes <olivier@mgcrea.io>",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/mgcrea/mcp-ios-simulator.git"
19
+ },
20
+ "bin": {
21
+ "ios-simulator-mcp": "dist/cli.js",
22
+ "ios-simulator-wda": "scripts/wda.sh"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "scripts/wda.sh"
27
+ ],
28
+ "os": [
29
+ "darwin"
30
+ ],
31
+ "type": "module",
32
+ "main": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "imports": {
35
+ "#/*": "./src/*.ts",
36
+ "#test/*": "./test/*.ts"
37
+ },
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "import": "./dist/index.js"
42
+ }
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "tsdown",
49
+ "dev": "tsdown --watch",
50
+ "start": "node dist/cli.js",
51
+ "lint": "oxlint",
52
+ "lint:fix": "oxlint --fix",
53
+ "format": "oxfmt --write .",
54
+ "format:check": "oxfmt --check .",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "typecheck": "tsc --noEmit",
58
+ "release": "pnpm dlx release-it",
59
+ "prepublishOnly": "pnpm build"
60
+ },
61
+ "dependencies": {
62
+ "@mgcrea/mcp-ios-core": "^0.1.0",
63
+ "@modelcontextprotocol/server": "^2.0.0",
64
+ "zod": "^4.4.3"
65
+ },
66
+ "devDependencies": {
67
+ "@modelcontextprotocol/client": "^2.0.0",
68
+ "@types/node": "^26.2.0",
69
+ "oxfmt": "^0.62.0",
70
+ "oxlint": "^1.77.0",
71
+ "tsdown": "^0.22.14",
72
+ "tsx": "^4.23.11",
73
+ "typescript": "^7.0.2",
74
+ "vite": "^8.2.1",
75
+ "vitest": "^4.1.10"
76
+ },
77
+ "engines": {
78
+ "node": ">=22"
79
+ },
80
+ "packageManager": "pnpm@11.13.1"
81
+ }