@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.
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +47 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-C1TH-p-c.js +1553 -0
- package/dist/config-C1TH-p-c.js.map +1 -0
- package/dist/index.d.ts +475 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/package.json +81 -0
- package/scripts/wda.sh +145 -0
package/dist/index.d.ts
ADDED
|
@@ -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":"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"}
|
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
|
+
}
|