@ebowwa/mcp-ios-devices 1.0.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,446 @@
1
+ /**
2
+ * Simulator Tools
3
+ * Control iOS Simulator via simctl
4
+ */
5
+ import { z } from 'zod';
6
+ import { registerTool } from '../registry.js';
7
+ import { SimCtl } from '@ebowwa/ios-devices';
8
+ const simCtl = new SimCtl();
9
+ // ============================================================================
10
+ // Device Management
11
+ // ============================================================================
12
+ registerTool({
13
+ name: 'sim_list',
14
+ description: 'List simulators, device types, or runtimes',
15
+ inputSchema: z.object({
16
+ devices: z.boolean().optional().describe('List devices'),
17
+ deviceTypes: z.boolean().optional().describe('List device types'),
18
+ runtimes: z.boolean().optional().describe('List runtimes'),
19
+ available: z.boolean().optional().describe('Only available'),
20
+ search: z.string().optional().describe('Search term'),
21
+ }),
22
+ handler: async (options) => {
23
+ return simCtl.list(options);
24
+ },
25
+ });
26
+ registerTool({
27
+ name: 'sim_create',
28
+ description: 'Create a new simulator',
29
+ inputSchema: z.object({
30
+ name: z.string().describe('Simulator name'),
31
+ deviceTypeId: z.string().describe('Device type (e.g., com.apple.CoreSimulator.SimDeviceType.iPhone-16)'),
32
+ runtimeId: z.string().describe('Runtime (e.g., com.apple.CoreSimulator.SimRuntime.iOS-18-1)'),
33
+ }),
34
+ handler: async ({ name, deviceTypeId, runtimeId }) => {
35
+ return simCtl.create(name, deviceTypeId, runtimeId);
36
+ },
37
+ });
38
+ registerTool({
39
+ name: 'sim_clone',
40
+ description: 'Clone an existing simulator',
41
+ inputSchema: z.object({
42
+ udid: z.string().describe('Source simulator UDID'),
43
+ name: z.string().describe('Name for cloned simulator'),
44
+ }),
45
+ handler: async ({ udid, name }) => {
46
+ return simCtl.clone(udid, name);
47
+ },
48
+ });
49
+ registerTool({
50
+ name: 'sim_delete',
51
+ description: 'Delete one or more simulators',
52
+ inputSchema: z.object({
53
+ udids: z.union([z.string(), z.array(z.string())]).describe('Simulator UDID(s) to delete'),
54
+ }),
55
+ handler: async ({ udids }) => {
56
+ return simCtl.delete(udids);
57
+ },
58
+ });
59
+ registerTool({
60
+ name: 'sim_delete_unavailable',
61
+ description: 'Delete all unavailable simulators',
62
+ inputSchema: z.object({}),
63
+ handler: async () => {
64
+ return simCtl.deleteUnavailable();
65
+ },
66
+ });
67
+ registerTool({
68
+ name: 'sim_erase',
69
+ description: 'Erase a simulator (reset to factory state)',
70
+ inputSchema: z.object({
71
+ udid: z.string().describe('Simulator UDID'),
72
+ }),
73
+ handler: async ({ udid }) => {
74
+ return simCtl.erase(udid);
75
+ },
76
+ });
77
+ registerTool({
78
+ name: 'sim_rename',
79
+ description: 'Rename a simulator',
80
+ inputSchema: z.object({
81
+ udid: z.string().describe('Simulator UDID'),
82
+ name: z.string().describe('New name'),
83
+ }),
84
+ handler: async ({ udid, name }) => {
85
+ return simCtl.rename(udid, name);
86
+ },
87
+ });
88
+ // ============================================================================
89
+ // Boot/Shutdown
90
+ // ============================================================================
91
+ registerTool({
92
+ name: 'sim_boot',
93
+ description: 'Boot a simulator',
94
+ inputSchema: z.object({
95
+ udid: z.string().describe('Simulator UDID (or "booted" for any booted)'),
96
+ }),
97
+ handler: async ({ udid }) => {
98
+ return simCtl.boot(udid);
99
+ },
100
+ });
101
+ registerTool({
102
+ name: 'sim_shutdown',
103
+ description: 'Shutdown a simulator',
104
+ inputSchema: z.object({
105
+ udid: z.string().describe('Simulator UDID'),
106
+ }),
107
+ handler: async ({ udid }) => {
108
+ return simCtl.shutdown(udid);
109
+ },
110
+ });
111
+ registerTool({
112
+ name: 'sim_shutdown_all',
113
+ description: 'Shutdown all simulators',
114
+ inputSchema: z.object({}),
115
+ handler: async () => {
116
+ return simCtl.shutdownAll();
117
+ },
118
+ });
119
+ // ============================================================================
120
+ // App Management
121
+ // ============================================================================
122
+ registerTool({
123
+ name: 'sim_install',
124
+ description: 'Install an app on a simulator',
125
+ inputSchema: z.object({
126
+ udid: z.string().describe('Simulator UDID'),
127
+ appPath: z.string().describe('Path to .app bundle'),
128
+ }),
129
+ handler: async ({ udid, appPath }) => {
130
+ return simCtl.install(udid, appPath);
131
+ },
132
+ });
133
+ registerTool({
134
+ name: 'sim_uninstall',
135
+ description: 'Uninstall an app from a simulator',
136
+ inputSchema: z.object({
137
+ udid: z.string().describe('Simulator UDID'),
138
+ bundleId: z.string().describe('App bundle identifier'),
139
+ }),
140
+ handler: async ({ udid, bundleId }) => {
141
+ return simCtl.uninstall(udid, bundleId);
142
+ },
143
+ });
144
+ registerTool({
145
+ name: 'sim_get_app_container',
146
+ description: 'Get the path to an app container',
147
+ inputSchema: z.object({
148
+ udid: z.string().describe('Simulator UDID'),
149
+ bundleId: z.string().describe('App bundle identifier'),
150
+ containerType: z.string().optional().describe('Container type (app, data, groups, etc.)'),
151
+ }),
152
+ handler: async ({ udid, bundleId, containerType }) => {
153
+ return simCtl.getAppContainer(udid, bundleId, containerType);
154
+ },
155
+ });
156
+ // ============================================================================
157
+ // App Execution
158
+ // ============================================================================
159
+ registerTool({
160
+ name: 'sim_launch',
161
+ description: 'Launch an app on a simulator',
162
+ inputSchema: z.object({
163
+ udid: z.string().describe('Simulator UDID'),
164
+ bundleId: z.string().describe('App bundle identifier'),
165
+ waitForDebugger: z.boolean().optional().describe('Wait for debugger to attach'),
166
+ console: z.boolean().optional().describe('Show console output'),
167
+ env: z.record(z.string()).optional().describe('Environment variables'),
168
+ args: z.array(z.string()).optional().describe('Arguments'),
169
+ }),
170
+ handler: async ({ udid, bundleId, waitForDebugger, console, env, args }) => {
171
+ return simCtl.launch(udid, bundleId, { waitForDebugger, console, env, args });
172
+ },
173
+ });
174
+ registerTool({
175
+ name: 'sim_terminate',
176
+ description: 'Terminate an app on a simulator',
177
+ inputSchema: z.object({
178
+ udid: z.string().describe('Simulator UDID'),
179
+ bundleId: z.string().describe('App bundle identifier'),
180
+ }),
181
+ handler: async ({ udid, bundleId }) => {
182
+ return simCtl.terminate(udid, bundleId);
183
+ },
184
+ });
185
+ registerTool({
186
+ name: 'sim_spawn',
187
+ description: 'Spawn a process on a simulator',
188
+ inputSchema: z.object({
189
+ udid: z.string().describe('Simulator UDID'),
190
+ executable: z.string().describe('Executable path'),
191
+ args: z.array(z.string()).optional().describe('Arguments'),
192
+ }),
193
+ handler: async ({ udid, executable, args }) => {
194
+ return simCtl.spawn(udid, executable, args);
195
+ },
196
+ });
197
+ // ============================================================================
198
+ // IO Operations
199
+ // ============================================================================
200
+ registerTool({
201
+ name: 'sim_screenshot',
202
+ description: 'Take a screenshot of a simulator',
203
+ inputSchema: z.object({
204
+ udid: z.string().describe('Simulator UDID'),
205
+ outputPath: z.string().describe('Output file path'),
206
+ type: z.enum(['png', 'jpeg', 'tiff']).optional().default('png'),
207
+ }),
208
+ handler: async ({ udid, outputPath, type }) => {
209
+ return simCtl.screenshot(udid, outputPath, { type });
210
+ },
211
+ });
212
+ registerTool({
213
+ name: 'sim_record_video',
214
+ description: 'Record video of a simulator',
215
+ inputSchema: z.object({
216
+ udid: z.string().describe('Simulator UDID'),
217
+ outputPath: z.string().describe('Output file path (.mp4)'),
218
+ codec: z.enum(['h264', 'hevc']).optional(),
219
+ }),
220
+ handler: async ({ udid, outputPath, codec }) => {
221
+ return simCtl.recordVideo(udid, outputPath, { codec });
222
+ },
223
+ });
224
+ // ============================================================================
225
+ // Location
226
+ // ============================================================================
227
+ registerTool({
228
+ name: 'sim_set_location',
229
+ description: 'Set simulated location on a simulator',
230
+ inputSchema: z.object({
231
+ udid: z.string().describe('Simulator UDID'),
232
+ latitude: z.number().describe('Latitude'),
233
+ longitude: z.number().describe('Longitude'),
234
+ }),
235
+ handler: async ({ udid, latitude, longitude }) => {
236
+ return simCtl.setLocation(udid, latitude, longitude);
237
+ },
238
+ });
239
+ registerTool({
240
+ name: 'sim_reset_location',
241
+ description: 'Reset simulated location',
242
+ inputSchema: z.object({
243
+ udid: z.string().describe('Simulator UDID'),
244
+ }),
245
+ handler: async ({ udid }) => {
246
+ return simCtl.resetLocation(udid);
247
+ },
248
+ });
249
+ // ============================================================================
250
+ // Status Bar
251
+ // ============================================================================
252
+ registerTool({
253
+ name: 'sim_status_bar_override',
254
+ description: 'Override status bar appearance',
255
+ inputSchema: z.object({
256
+ udid: z.string().describe('Simulator UDID'),
257
+ time: z.string().optional().describe('Time (e.g., "9:41")'),
258
+ dataNetwork: z.string().optional().describe('Data network type'),
259
+ wifiMode: z.enum(['active', 'searching', 'failed']).optional(),
260
+ cellularMode: z.enum(['active', 'searching', 'failed']).optional(),
261
+ batteryLevel: z.number().optional().describe('Battery level (0-100)'),
262
+ batteryState: z.enum(['charging', 'charged', 'discharging']).optional(),
263
+ }),
264
+ handler: async ({ udid, ...overrides }) => {
265
+ return simCtl.statusBar(udid, overrides);
266
+ },
267
+ });
268
+ registerTool({
269
+ name: 'sim_status_bar_clear',
270
+ description: 'Clear status bar overrides',
271
+ inputSchema: z.object({
272
+ udid: z.string().describe('Simulator UDID'),
273
+ }),
274
+ handler: async ({ udid }) => {
275
+ return simCtl.clearStatusBar(udid);
276
+ },
277
+ });
278
+ // ============================================================================
279
+ // URL & Notifications
280
+ // ============================================================================
281
+ registerTool({
282
+ name: 'sim_open_url',
283
+ description: 'Open a URL on a simulator',
284
+ inputSchema: z.object({
285
+ udid: z.string().describe('Simulator UDID'),
286
+ url: z.string().describe('URL to open'),
287
+ }),
288
+ handler: async ({ udid, url }) => {
289
+ return simCtl.openURL(udid, url);
290
+ },
291
+ });
292
+ registerTool({
293
+ name: 'sim_push_notification',
294
+ description: 'Send a simulated push notification',
295
+ inputSchema: z.object({
296
+ udid: z.string().describe('Simulator UDID'),
297
+ bundleId: z.string().describe('App bundle identifier'),
298
+ payload: z.record(z.any()).describe('Push notification payload'),
299
+ }),
300
+ handler: async ({ udid, bundleId, payload }) => {
301
+ return simCtl.push(udid, bundleId, payload);
302
+ },
303
+ });
304
+ // ============================================================================
305
+ // Privacy
306
+ // ============================================================================
307
+ registerTool({
308
+ name: 'sim_grant_privacy',
309
+ description: 'Grant privacy permission to an app',
310
+ inputSchema: z.object({
311
+ udid: z.string().describe('Simulator UDID'),
312
+ bundleId: z.string().describe('App bundle identifier'),
313
+ service: z.string().describe('Service name (e.g., camera, microphone, photos)'),
314
+ }),
315
+ handler: async ({ udid, bundleId, service }) => {
316
+ return simCtl.grantPrivacy(udid, bundleId, service);
317
+ },
318
+ });
319
+ registerTool({
320
+ name: 'sim_revoke_privacy',
321
+ description: 'Revoke privacy permission from an app',
322
+ inputSchema: z.object({
323
+ udid: z.string().describe('Simulator UDID'),
324
+ bundleId: z.string().describe('App bundle identifier'),
325
+ service: z.string().describe('Service name'),
326
+ }),
327
+ handler: async ({ udid, bundleId, service }) => {
328
+ return simCtl.revokePrivacy(udid, bundleId, service);
329
+ },
330
+ });
331
+ // ============================================================================
332
+ // Keychain
333
+ // ============================================================================
334
+ registerTool({
335
+ name: 'sim_keychain_add',
336
+ description: 'Add item to simulator keychain',
337
+ inputSchema: z.object({
338
+ udid: z.string().describe('Simulator UDID'),
339
+ service: z.string().describe('Service name'),
340
+ account: z.string().describe('Account name'),
341
+ password: z.string().describe('Password'),
342
+ }),
343
+ handler: async ({ udid, service, account, password }) => {
344
+ return simCtl.keychainAdd(udid, service, account, password);
345
+ },
346
+ });
347
+ registerTool({
348
+ name: 'sim_keychain_reset',
349
+ description: 'Reset simulator keychain',
350
+ inputSchema: z.object({
351
+ udid: z.string().describe('Simulator UDID'),
352
+ }),
353
+ handler: async ({ udid }) => {
354
+ return simCtl.keychainReset(udid);
355
+ },
356
+ });
357
+ // ============================================================================
358
+ // Environment & Diagnostics
359
+ // ============================================================================
360
+ registerTool({
361
+ name: 'sim_getenv',
362
+ description: 'Get environment variable from simulator',
363
+ inputSchema: z.object({
364
+ udid: z.string().describe('Simulator UDID'),
365
+ variable: z.string().optional().describe('Variable name (omit for all)'),
366
+ }),
367
+ handler: async ({ udid, variable }) => {
368
+ return simCtl.getenv(udid, variable);
369
+ },
370
+ });
371
+ registerTool({
372
+ name: 'sim_diagnose',
373
+ description: 'Collect simulator diagnostics',
374
+ inputSchema: z.object({
375
+ outputPath: z.string().optional().describe('Output path'),
376
+ }),
377
+ handler: async ({ outputPath }) => {
378
+ return simCtl.diagnose(outputPath);
379
+ },
380
+ });
381
+ // ============================================================================
382
+ // Media & iCloud
383
+ // ============================================================================
384
+ registerTool({
385
+ name: 'sim_add_media',
386
+ description: 'Add media (photos/videos) to simulator',
387
+ inputSchema: z.object({
388
+ udid: z.string().describe('Simulator UDID'),
389
+ mediaPaths: z.union([z.string(), z.array(z.string())]).describe('Path(s) to media files'),
390
+ }),
391
+ handler: async ({ udid, mediaPaths }) => {
392
+ return simCtl.addMedia(udid, mediaPaths);
393
+ },
394
+ });
395
+ registerTool({
396
+ name: 'sim_icloud_sync',
397
+ description: 'Trigger iCloud sync on simulator',
398
+ inputSchema: z.object({
399
+ udid: z.string().describe('Simulator UDID'),
400
+ }),
401
+ handler: async ({ udid }) => {
402
+ return simCtl.icloudSync(udid);
403
+ },
404
+ });
405
+ // ============================================================================
406
+ // Pasteboard
407
+ // ============================================================================
408
+ registerTool({
409
+ name: 'sim_pbpaste',
410
+ description: 'Get pasteboard contents from simulator',
411
+ inputSchema: z.object({
412
+ udid: z.string().describe('Simulator UDID'),
413
+ }),
414
+ handler: async ({ udid }) => {
415
+ return simCtl.pbpaste(udid);
416
+ },
417
+ });
418
+ // ============================================================================
419
+ // UI
420
+ // ============================================================================
421
+ registerTool({
422
+ name: 'sim_set_appearance',
423
+ description: 'Set simulator appearance (light/dark mode)',
424
+ inputSchema: z.object({
425
+ udid: z.string().describe('Simulator UDID'),
426
+ appearance: z.enum(['light', 'dark']),
427
+ }),
428
+ handler: async ({ udid, appearance }) => {
429
+ return simCtl.setUI(udid, { appearance });
430
+ },
431
+ });
432
+ // ============================================================================
433
+ // Runtime
434
+ // ============================================================================
435
+ registerTool({
436
+ name: 'sim_upgrade',
437
+ description: 'Upgrade simulator to a new runtime',
438
+ inputSchema: z.object({
439
+ udid: z.string().describe('Simulator UDID'),
440
+ runtimeId: z.string().describe('Target runtime ID'),
441
+ }),
442
+ handler: async ({ udid, runtimeId }) => {
443
+ return simCtl.upgrade(udid, runtimeId);
444
+ },
445
+ });
446
+ //# sourceMappingURL=simulator.js.map
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@ebowwa/mcp-ios-devices",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for iOS device control - devicectl, libimobiledevice, simctl",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "mcp-ios-devices": "dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "start": "node dist/index.js",
15
+ "clean": "rm -rf dist",
16
+ "prepublishOnly": "bun run build"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "ios",
21
+ "device",
22
+ "devicectl",
23
+ "simctl",
24
+ "libimobiledevice",
25
+ "iphone",
26
+ "ipad",
27
+ "simulator",
28
+ "xcode"
29
+ ],
30
+ "author": "ebowwa",
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "@anthropic-ai/sdk": "^0.39.0",
34
+ "@ebowwa/ios-devices": "^1.0.0",
35
+ "@modelcontextprotocol/sdk": "^1.0.0",
36
+ "zod": "^3.22.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.0.0",
40
+ "typescript": "^5.6.0"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/ebowwa/codespaces",
45
+ "directory": "packages/mcp/mcp-ios-devices"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }
package/src/index.ts ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @ebowwa/mcp-ios-devices
5
+ * MCP server for iOS device control
6
+ *
7
+ * Provides 80+ tools for:
8
+ * - Physical device management (devicectl, libimobiledevice)
9
+ * - Simulator control (simctl)
10
+ * - App installation and management
11
+ * - Process management
12
+ * - Logging and diagnostics
13
+ * - File system access
14
+ * - Screenshots and location spoofing
15
+ * - Backup and restore
16
+ */
17
+
18
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
19
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
20
+ import {
21
+ CallToolRequestSchema,
22
+ ListToolsRequestSchema,
23
+ ErrorCode,
24
+ McpError,
25
+ } from '@modelcontextprotocol/sdk/types.js';
26
+ import { z } from 'zod';
27
+
28
+ // Import registry
29
+ import { tools } from './registry.js';
30
+
31
+ // Import all tool modules (side effects - they register themselves)
32
+ import './tools/device.js';
33
+ import './tools/process.js';
34
+ import './tools/app.js';
35
+ import './tools/log.js';
36
+ import './tools/file.js';
37
+ import './tools/simulator.js';
38
+ import './tools/backup.js';
39
+ import './tools/location.js';
40
+ import './tools/screenshot.js';
41
+ import './tools/diagnostics.js';
42
+ import './tools/profile.js';
43
+
44
+ // Create MCP server
45
+ const server = new Server(
46
+ {
47
+ name: '@ebowwa/mcp-ios-devices',
48
+ version: '1.0.0',
49
+ },
50
+ {
51
+ capabilities: {
52
+ tools: {},
53
+ },
54
+ }
55
+ );
56
+
57
+ // Handle list tools request
58
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
59
+ return {
60
+ tools: tools.map((tool) => ({
61
+ name: tool.name,
62
+ description: tool.description,
63
+ inputSchema: tool.inputSchema
64
+ ? zodToJsonSchema(tool.inputSchema)
65
+ : { type: 'object', properties: {} },
66
+ })),
67
+ };
68
+ });
69
+
70
+ // Handle tool execution
71
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
72
+ const { name, arguments: args } = request.params;
73
+
74
+ const tool = tools.find((t) => t.name === name);
75
+ if (!tool) {
76
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
77
+ }
78
+
79
+ try {
80
+ // Validate input
81
+ const validatedArgs = tool.inputSchema?.parse(args) ?? args;
82
+
83
+ // Execute tool
84
+ const result = await tool.handler(validatedArgs);
85
+
86
+ return {
87
+ content: [
88
+ {
89
+ type: 'text',
90
+ text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
91
+ },
92
+ ],
93
+ };
94
+ } catch (error) {
95
+ if (error instanceof z.ZodError) {
96
+ const messages = error.errors.map((e) => e.message).join(', ');
97
+ throw new McpError(
98
+ ErrorCode.InvalidParams,
99
+ `Invalid parameters: ${messages}`
100
+ );
101
+ }
102
+
103
+ const errorMessage = error instanceof Error ? error.message : String(error);
104
+ throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${errorMessage}`);
105
+ }
106
+ });
107
+
108
+ /**
109
+ * Convert Zod schema to JSON Schema for MCP
110
+ */
111
+ function zodToJsonSchema(schema: z.ZodType<any>): any {
112
+ if (schema instanceof z.ZodObject) {
113
+ const properties: Record<string, any> = {};
114
+ const required: string[] = [];
115
+
116
+ for (const [key, value] of Object.entries(schema.shape)) {
117
+ properties[key] = zodToJsonSchema(value as z.ZodType<any>);
118
+ if (!(value instanceof z.ZodOptional)) {
119
+ required.push(key);
120
+ }
121
+ }
122
+
123
+ return {
124
+ type: 'object',
125
+ properties,
126
+ required: required.length > 0 ? required : undefined,
127
+ };
128
+ }
129
+
130
+ if (schema instanceof z.ZodString) {
131
+ return { type: 'string', description: schema.description };
132
+ }
133
+
134
+ if (schema instanceof z.ZodNumber) {
135
+ return { type: 'number', description: schema.description };
136
+ }
137
+
138
+ if (schema instanceof z.ZodBoolean) {
139
+ return { type: 'boolean', description: schema.description };
140
+ }
141
+
142
+ if (schema instanceof z.ZodArray) {
143
+ return {
144
+ type: 'array',
145
+ items: zodToJsonSchema(schema.element),
146
+ description: schema.description,
147
+ };
148
+ }
149
+
150
+ if (schema instanceof z.ZodEnum) {
151
+ return {
152
+ type: 'string',
153
+ enum: schema.options,
154
+ description: schema.description,
155
+ };
156
+ }
157
+
158
+ if (schema instanceof z.ZodOptional) {
159
+ return zodToJsonSchema(schema.unwrap());
160
+ }
161
+
162
+ if (schema instanceof z.ZodDefault) {
163
+ return zodToJsonSchema(schema._def.innerType);
164
+ }
165
+
166
+ if (schema instanceof z.ZodLiteral) {
167
+ return { type: 'string', enum: [schema.value] };
168
+ }
169
+
170
+ if (schema instanceof z.ZodUnion) {
171
+ return {
172
+ oneOf: schema.options.map(zodToJsonSchema),
173
+ };
174
+ }
175
+
176
+ return { type: 'object' };
177
+ }
178
+
179
+ // Start server
180
+ async function main() {
181
+ const transport = new StdioServerTransport();
182
+ await server.connect(transport);
183
+ console.error('iOS Devices MCP server running');
184
+ }
185
+
186
+ main().catch((error) => {
187
+ console.error('Server error:', error);
188
+ process.exit(1);
189
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Tool registry for MCP server
3
+ * Separated to avoid circular dependencies
4
+ */
5
+
6
+ import { z } from 'zod';
7
+
8
+ export interface ToolDefinition {
9
+ name: string;
10
+ description: string;
11
+ inputSchema: z.ZodType<any>;
12
+ handler: (params: any) => Promise<any>;
13
+ }
14
+
15
+ // Tool registry
16
+ export const tools: ToolDefinition[] = [];
17
+
18
+ /**
19
+ * Register a tool
20
+ */
21
+ export function registerTool(options: ToolDefinition): void {
22
+ tools.push(options);
23
+ }