preflight-ios-mcp 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.
package/dist/index.js ADDED
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import * as logger from './helpers/logger.js';
5
+ // Tool imports
6
+ import { screenshotParams, handleScreenshot } from './tools/screenshot.js';
7
+ import { listDevicesParams, handleListDevices, bootParams, handleBoot, shutdownParams, handleShutdown, eraseParams, handleErase, openUrlParams, handleOpenUrl, openSimulatorParams, handleOpenSimulator, getBootedSimIdParams, handleGetBootedSimId, } from './tools/device.js';
8
+ import { listAppsParams, handleListApps, appInfoParams, handleAppInfo, launchAppParams, handleLaunchApp, terminateAppParams, handleTerminateApp, installAppParams, handleInstallApp, uninstallAppParams, handleUninstallApp, } from './tools/app.js';
9
+ import { tapParams, handleTap, swipeParams, handleSwipe, longPressParams, handleLongPress, describePointParams, handleDescribePoint, typeTextParams, handleTypeText, pressKeyParams, handlePressKey, } from './tools/interaction.js';
10
+ import { setLocationParams, handleSetLocation, sendPushParams, handleSendPush, setClipboardParams, handleSetClipboard, getClipboardParams, handleGetClipboard, addMediaParams, handleAddMedia, grantPermissionParams, handleGrantPermission, } from './tools/system.js';
11
+ import { setAppearanceParams, handleSetAppearance, overrideStatusBarParams, handleOverrideStatusBar, recordVideoParams, handleRecordVideo, stopRecordingParams, handleStopRecording, navigateBackParams, handleNavigateBack, } from './tools/ui.js';
12
+ import { getLogsParams, handleGetLogs, streamLogsParams, handleStreamLogs, getAppContainerParams, handleGetAppContainer, listAppFilesParams, handleListAppFiles, readAppFileParams, handleReadAppFile, getCrashLogsParams, handleGetCrashLogs, diagnoseParams, handleDiagnose, accessibilityAuditParams, handleAccessibilityAudit, getScreenInfoParams, handleGetScreenInfo, } from './tools/debug.js';
13
+ import { icloudSyncParams, handleIcloudSync, keychainParams, handleKeychain, contentSizeParams, handleContentSize, increaseContrastParams, handleIncreaseContrast, locationScenarioParams, handleLocationScenario, locationRouteParams, handleLocationRoute, verboseLoggingParams, handleVerboseLogging, installAppDataParams, handleInstallAppData, getEnvParams, handleGetEnv, memoryWarningParams, handleMemoryWarning, biometricParams, handleBiometric, networkStatusParams, handleNetworkStatus, defaultsReadParams, handleDefaultsRead, defaultsWriteParams, handleDefaultsWrite, } from './tools/advanced.js';
14
+ import { snapshotParams, handleSnapshot, waitForElementParams, handleWaitForElement, elementExistsParams, handleElementExists, } from './tools/playwright.js';
15
+ // Support tool filtering via environment variable
16
+ const filteredTools = new Set((process.env.PREFLIGHT_FILTERED_TOOLS || process.env.IOS_SIMULATOR_MCP_FILTERED_TOOLS || '')
17
+ .split(',').map(s => s.trim()).filter(Boolean));
18
+ const server = new McpServer({
19
+ name: 'preflight-mcp',
20
+ version: '1.0.0',
21
+ });
22
+ // Register a tool, skipping if it's in the filtered list
23
+ function registerTool(name, description, params, handler) {
24
+ if (filteredTools.has(name)) {
25
+ logger.debug('server', `Tool ${name} filtered out by PREFLIGHT_FILTERED_TOOLS`);
26
+ return;
27
+ }
28
+ server.tool(name, description, params, handler);
29
+ }
30
+ // Helper to wrap tool handlers with logging and error handling
31
+ function wrapHandler(name, handler) {
32
+ return async (args) => {
33
+ const start = Date.now();
34
+ logger.toolStart(name, args);
35
+ try {
36
+ const result = await handler(args);
37
+ logger.toolEnd(name, Date.now() - start, true);
38
+ return result;
39
+ }
40
+ catch (err) {
41
+ const e = err;
42
+ logger.toolEnd(name, Date.now() - start, false);
43
+ logger.error(`tool:${name}`, e.message, { stack: e.stack });
44
+ return {
45
+ content: [{
46
+ type: 'text',
47
+ text: `Error: ${e.message}`,
48
+ }],
49
+ isError: true,
50
+ };
51
+ }
52
+ };
53
+ }
54
+ // ========== Observation Tools ==========
55
+ registerTool('simulator_screenshot', 'Take a screenshot of the iOS Simulator screen. Returns the image directly for viewing.', screenshotParams, wrapHandler('simulator_screenshot', handleScreenshot));
56
+ registerTool('simulator_list_devices', 'List iOS Simulator devices. Shows name, UDID, state, and runtime.', listDevicesParams, wrapHandler('simulator_list_devices', handleListDevices));
57
+ registerTool('simulator_list_apps', 'List all installed apps on the simulator with their bundle IDs.', listAppsParams, wrapHandler('simulator_list_apps', handleListApps));
58
+ registerTool('simulator_app_info', 'Get detailed metadata about an installed app (bundle ID, paths, version, etc.).', appInfoParams, wrapHandler('simulator_app_info', handleAppInfo));
59
+ registerTool('simulator_get_clipboard', 'Read the text content of the simulator clipboard.', getClipboardParams, wrapHandler('simulator_get_clipboard', handleGetClipboard));
60
+ registerTool('simulator_get_screen_info', 'Get diagnostic info about the Simulator window geometry and coordinate mapping. Useful for debugging tap/swipe accuracy.', getScreenInfoParams, wrapHandler('simulator_get_screen_info', handleGetScreenInfo));
61
+ // ========== User Interaction Tools ==========
62
+ registerTool('simulator_tap', 'Tap at a point on the simulator screen. Coordinates are in simulator screen points (e.g., 0-393 for iPhone width). Take a screenshot first to identify coordinates.', tapParams, wrapHandler('simulator_tap', handleTap));
63
+ registerTool('simulator_swipe', 'Swipe/drag from one point to another on the simulator screen. Coordinates are in simulator screen points. Use for scrolling, pulling down, or any drag gesture.', swipeParams, wrapHandler('simulator_swipe', handleSwipe));
64
+ registerTool('simulator_long_press', 'Long press at a point on the simulator screen. Useful for context menus, drag-and-drop initiation, etc.', longPressParams, wrapHandler('simulator_long_press', handleLongPress));
65
+ registerTool('simulator_describe_point', 'Returns the accessibility element at given coordinates on the iOS Simulator screen. Shows element type, label, value, and frame.', describePointParams, wrapHandler('simulator_describe_point', handleDescribePoint));
66
+ registerTool('simulator_type_text', 'Type text into the currently focused text field in the simulator. Make sure a text field is focused first (tap on it).', typeTextParams, wrapHandler('simulator_type_text', handleTypeText));
67
+ registerTool('simulator_press_key', 'Press a special key (return, escape, delete, tab, arrows, etc.) with optional modifiers (command, shift, option, control).', pressKeyParams, wrapHandler('simulator_press_key', handlePressKey));
68
+ // ========== Device Management Tools ==========
69
+ registerTool('simulator_boot', 'Boot an iOS Simulator device. Opens the Simulator app. Use simulator_list_devices to find device names/UDIDs.', bootParams, wrapHandler('simulator_boot', handleBoot));
70
+ registerTool('simulator_shutdown', 'Shut down a running simulator device.', shutdownParams, wrapHandler('simulator_shutdown', handleShutdown));
71
+ registerTool('simulator_erase', 'Factory reset a simulator device. Erases all content and settings.', eraseParams, wrapHandler('simulator_erase', handleErase));
72
+ registerTool('simulator_open_url', 'Open a URL or deep link in the simulator (e.g., "https://example.com" or "myapp://screen").', openUrlParams, wrapHandler('simulator_open_url', handleOpenUrl));
73
+ registerTool('simulator_open_simulator', 'Opens the iOS Simulator application.', openSimulatorParams, wrapHandler('simulator_open_simulator', handleOpenSimulator));
74
+ registerTool('simulator_get_booted_sim_id', 'Get the UDID of the currently booted iOS Simulator.', getBootedSimIdParams, wrapHandler('simulator_get_booted_sim_id', handleGetBootedSimId));
75
+ // ========== App Management Tools ==========
76
+ registerTool('simulator_launch_app', 'Launch an app by bundle ID. Optionally pass launch arguments and environment variables.', launchAppParams, wrapHandler('simulator_launch_app', handleLaunchApp));
77
+ registerTool('simulator_terminate_app', 'Force-terminate a running app by bundle ID.', terminateAppParams, wrapHandler('simulator_terminate_app', handleTerminateApp));
78
+ registerTool('simulator_install_app', 'Install a .app bundle onto the simulator from a local file path.', installAppParams, wrapHandler('simulator_install_app', handleInstallApp));
79
+ registerTool('simulator_uninstall_app', 'Uninstall an app from the simulator by bundle ID.', uninstallAppParams, wrapHandler('simulator_uninstall_app', handleUninstallApp));
80
+ // ========== Developer Debugging Tools ==========
81
+ registerTool('simulator_get_logs', 'Get recent device/app logs. Filter by process name, subsystem, log level, time range, and message content. Essential for debugging app behavior.', getLogsParams, wrapHandler('simulator_get_logs', handleGetLogs));
82
+ registerTool('simulator_stream_logs', 'Start/read/stop a live log stream. Use action="start" to begin, "read" to get the buffer, "stop" to end. Great for watching app behavior in real-time.', streamLogsParams, wrapHandler('simulator_stream_logs', handleStreamLogs));
83
+ registerTool('simulator_get_app_container', 'Get the filesystem path to an app\'s container (bundle, data, or shared groups). Use this to find where the app stores its files.', getAppContainerParams, wrapHandler('simulator_get_app_container', handleGetAppContainer));
84
+ registerTool('simulator_list_app_files', 'List files in an app\'s data container. Shows Documents, Library, Preferences, Caches, tmp, etc. Use to find databases, plists, caches.', listAppFilesParams, wrapHandler('simulator_list_app_files', handleListAppFiles));
85
+ registerTool('simulator_read_app_file', 'Read a file from an app\'s data container. Handles plists (converts to JSON), SQLite databases (shows schema), and text files. Specify path relative to data container.', readAppFileParams, wrapHandler('simulator_read_app_file', handleReadAppFile));
86
+ registerTool('simulator_get_crash_logs', 'Retrieve crash reports from ~/Library/Logs/DiagnosticReports/. Shows stack traces, exception info, and thread states. Filter by process name.', getCrashLogsParams, wrapHandler('simulator_get_crash_logs', handleGetCrashLogs));
87
+ registerTool('simulator_diagnose', 'Generate a diagnostic summary: booted devices, Xcode version, disk usage, and system info.', diagnoseParams, wrapHandler('simulator_diagnose', handleDiagnose));
88
+ registerTool('simulator_accessibility_audit', 'Get the accessibility element tree of the current Simulator screen. Shows roles, labels, values, and positions of UI elements.', accessibilityAuditParams, wrapHandler('simulator_accessibility_audit', handleAccessibilityAudit));
89
+ // ========== System Simulation Tools ==========
90
+ registerTool('simulator_set_location', 'Set the simulated GPS location (latitude, longitude). Useful for testing location-based features.', setLocationParams, wrapHandler('simulator_set_location', handleSetLocation));
91
+ registerTool('simulator_send_push', 'Send a push notification to an app. Provide the full APNs payload JSON (e.g., {"aps": {"alert": "Hello"}}).', sendPushParams, wrapHandler('simulator_send_push', handleSendPush));
92
+ registerTool('simulator_set_clipboard', 'Set text on the simulator clipboard. Useful for pasting content into apps.', setClipboardParams, wrapHandler('simulator_set_clipboard', handleSetClipboard));
93
+ registerTool('simulator_add_media', 'Add photos or videos to the simulator\'s camera roll from local file paths.', addMediaParams, wrapHandler('simulator_add_media', handleAddMedia));
94
+ registerTool('simulator_grant_permission', 'Grant, revoke, or reset app permissions (camera, location, photos, contacts, microphone, etc.).', grantPermissionParams, wrapHandler('simulator_grant_permission', handleGrantPermission));
95
+ // ========== UI Configuration Tools ==========
96
+ registerTool('simulator_set_appearance', 'Switch the simulator between light and dark mode.', setAppearanceParams, wrapHandler('simulator_set_appearance', handleSetAppearance));
97
+ registerTool('simulator_override_status_bar', 'Override the simulator status bar: set time, battery, signal bars, carrier name, network type. Use clear=true to reset.', overrideStatusBarParams, wrapHandler('simulator_override_status_bar', handleOverrideStatusBar));
98
+ registerTool('simulator_record_video', 'Start recording the simulator screen to a video file. Use simulator_stop_recording to stop. Supports H.264 and HEVC codecs.', recordVideoParams, wrapHandler('simulator_record_video', handleRecordVideo));
99
+ registerTool('simulator_stop_recording', 'Stop an active video recording and save the file.', stopRecordingParams, wrapHandler('simulator_stop_recording', handleStopRecording));
100
+ registerTool('simulator_navigate_back', 'Navigate back in the current app. Sends Cmd+[ (standard back navigation). Works in Safari and apps with standard UINavigationController. Workaround for edge-swipe-back gesture limitation.', navigateBackParams, wrapHandler('simulator_navigate_back', handleNavigateBack));
101
+ // ========== Advanced Tools ==========
102
+ registerTool('simulator_icloud_sync', 'Trigger iCloud sync on the device. Requires the device to be signed into an Apple ID.', icloudSyncParams, wrapHandler('simulator_icloud_sync', handleIcloudSync));
103
+ registerTool('simulator_keychain', 'Manipulate the device keychain: add root certificates, add certificates, or reset the entire keychain.', keychainParams, wrapHandler('simulator_keychain', handleKeychain));
104
+ registerTool('simulator_set_content_size', 'Set the preferred content size for Dynamic Type testing. Test your app with accessibility text sizes without changing device settings manually.', contentSizeParams, wrapHandler('simulator_set_content_size', handleContentSize));
105
+ registerTool('simulator_set_increase_contrast', 'Enable or disable the Increase Contrast accessibility setting. Test how your app responds to high contrast mode.', increaseContrastParams, wrapHandler('simulator_set_increase_contrast', handleIncreaseContrast));
106
+ registerTool('simulator_location_scenario', 'Run predefined GPS location scenarios (Freeway Drive, City Run, City Bicycle Ride). Simulates realistic movement patterns for testing location features.', locationScenarioParams, wrapHandler('simulator_location_scenario', handleLocationScenario));
107
+ registerTool('simulator_location_route', 'Simulate movement along a custom route with waypoints. Specify GPS coordinates and speed for realistic location testing.', locationRouteParams, wrapHandler('simulator_location_route', handleLocationRoute));
108
+ registerTool('simulator_verbose_logging', 'Enable or disable verbose device logging for deep debugging. Requires device reboot to take effect.', verboseLoggingParams, wrapHandler('simulator_verbose_logging', handleVerboseLogging));
109
+ registerTool('simulator_install_app_data', 'Install an .xcappdata package to replace the current app container contents. Useful for restoring test data snapshots.', installAppDataParams, wrapHandler('simulator_install_app_data', handleInstallAppData));
110
+ registerTool('simulator_get_env', 'Read an environment variable from the running simulator device (e.g., HOME, TMPDIR, PATH).', getEnvParams, wrapHandler('simulator_get_env', handleGetEnv));
111
+ registerTool('simulator_memory_warning', 'Trigger a simulated memory warning. Apps will receive didReceiveMemoryWarning and can be tested for proper memory cleanup.', memoryWarningParams, wrapHandler('simulator_memory_warning', handleMemoryWarning));
112
+ registerTool('simulator_biometric', 'Set Face ID / Touch ID enrollment state. Test biometric authentication flows.', biometricParams, wrapHandler('simulator_biometric', handleBiometric));
113
+ registerTool('simulator_network_status', 'Get the current network configuration inside the simulator — interfaces, IP addresses, DNS config.', networkStatusParams, wrapHandler('simulator_network_status', handleNetworkStatus));
114
+ registerTool('simulator_defaults_read', 'Read UserDefaults values from inside the simulator. Inspect app preferences, feature flags, and configuration.', defaultsReadParams, wrapHandler('simulator_defaults_read', handleDefaultsRead));
115
+ registerTool('simulator_defaults_write', 'Write UserDefaults values inside the simulator. Set feature flags, change app configuration, or inject test data.', defaultsWriteParams, wrapHandler('simulator_defaults_write', handleDefaultsWrite));
116
+ // ========== Playwright-Inspired Tools ==========
117
+ registerTool('simulator_snapshot', 'Capture a structured accessibility snapshot of the current screen — like Playwright\'s browser_snapshot. Returns roles, labels, values, and positions. PREFERRED over screenshots for understanding UI structure and targeting interactions. No vision model needed.', snapshotParams, wrapHandler('simulator_snapshot', handleSnapshot));
118
+ registerTool('simulator_wait_for_element', 'Wait for an accessibility element to appear on screen. Polls until the element matching your criteria (label, role, or text) appears, or times out. Like Playwright\'s browser_wait_for.', waitForElementParams, wrapHandler('simulator_wait_for_element', handleWaitForElement));
119
+ registerTool('simulator_element_exists', 'Quick check: does an element matching your criteria exist on screen right now? Returns true/false. Useful for conditional logic.', elementExistsParams, wrapHandler('simulator_element_exists', handleElementExists));
120
+ // ========== Start Server ==========
121
+ async function main() {
122
+ logger.info('server', 'Preflight MCP server starting...');
123
+ // Detect idb for cursor-free touch injection
124
+ const { checkIdbAvailable } = await import('./helpers/idb.js');
125
+ const hasIdb = await checkIdbAvailable();
126
+ if (hasIdb) {
127
+ logger.info('server', 'idb detected — using cursor-free touch injection (IndigoHID)');
128
+ }
129
+ else {
130
+ logger.warn('server', 'idb not found — using CGEvent fallback. Install for cursor-free touch: brew tap facebook/fb && brew install idb-companion && pip3 install fb-idb');
131
+ }
132
+ const transport = new StdioServerTransport();
133
+ await server.connect(transport);
134
+ logger.info('server', 'Preflight MCP server connected and ready.');
135
+ }
136
+ main().catch((err) => {
137
+ logger.error('server', 'Fatal error', { error: err.message, stack: err.stack });
138
+ process.exit(1);
139
+ });
Binary file
@@ -0,0 +1,203 @@
1
+ import { z } from 'zod';
2
+ export declare const icloudSyncParams: {
3
+ deviceId: z.ZodOptional<z.ZodString>;
4
+ };
5
+ export declare function handleIcloudSync(args: {
6
+ deviceId?: string;
7
+ }): Promise<{
8
+ content: {
9
+ type: "text";
10
+ text: string;
11
+ }[];
12
+ }>;
13
+ export declare const keychainParams: {
14
+ action: z.ZodEnum<["add-root-cert", "add-cert", "reset"]>;
15
+ path: z.ZodOptional<z.ZodString>;
16
+ deviceId: z.ZodOptional<z.ZodString>;
17
+ };
18
+ export declare function handleKeychain(args: {
19
+ action: string;
20
+ path?: string;
21
+ deviceId?: string;
22
+ }): Promise<{
23
+ content: {
24
+ type: "text";
25
+ text: string;
26
+ }[];
27
+ }>;
28
+ export declare const contentSizeParams: {
29
+ size: z.ZodEnum<["extra-small", "small", "medium", "large", "extra-large", "extra-extra-large", "extra-extra-extra-large", "accessibility-medium", "accessibility-large", "accessibility-extra-large", "accessibility-extra-extra-large", "accessibility-extra-extra-extra-large"]>;
30
+ deviceId: z.ZodOptional<z.ZodString>;
31
+ };
32
+ export declare function handleContentSize(args: {
33
+ size: string;
34
+ deviceId?: string;
35
+ }): Promise<{
36
+ content: {
37
+ type: "text";
38
+ text: string;
39
+ }[];
40
+ }>;
41
+ export declare const increaseContrastParams: {
42
+ enabled: z.ZodBoolean;
43
+ deviceId: z.ZodOptional<z.ZodString>;
44
+ };
45
+ export declare function handleIncreaseContrast(args: {
46
+ enabled: boolean;
47
+ deviceId?: string;
48
+ }): Promise<{
49
+ content: {
50
+ type: "text";
51
+ text: string;
52
+ }[];
53
+ }>;
54
+ export declare const locationScenarioParams: {
55
+ action: z.ZodEnum<["list", "run", "clear"]>;
56
+ scenario: z.ZodOptional<z.ZodString>;
57
+ deviceId: z.ZodOptional<z.ZodString>;
58
+ };
59
+ export declare function handleLocationScenario(args: {
60
+ action: string;
61
+ scenario?: string;
62
+ deviceId?: string;
63
+ }): Promise<{
64
+ content: {
65
+ type: "text";
66
+ text: string;
67
+ }[];
68
+ }>;
69
+ export declare const locationRouteParams: {
70
+ waypoints: z.ZodArray<z.ZodObject<{
71
+ lat: z.ZodNumber;
72
+ lng: z.ZodNumber;
73
+ }, "strip", z.ZodTypeAny, {
74
+ lat: number;
75
+ lng: number;
76
+ }, {
77
+ lat: number;
78
+ lng: number;
79
+ }>, "many">;
80
+ speed: z.ZodOptional<z.ZodNumber>;
81
+ deviceId: z.ZodOptional<z.ZodString>;
82
+ };
83
+ export declare function handleLocationRoute(args: {
84
+ waypoints: Array<{
85
+ lat: number;
86
+ lng: number;
87
+ }>;
88
+ speed?: number;
89
+ deviceId?: string;
90
+ }): Promise<{
91
+ content: {
92
+ type: "text";
93
+ text: string;
94
+ }[];
95
+ }>;
96
+ export declare const verboseLoggingParams: {
97
+ enabled: z.ZodBoolean;
98
+ deviceId: z.ZodOptional<z.ZodString>;
99
+ };
100
+ export declare function handleVerboseLogging(args: {
101
+ enabled: boolean;
102
+ deviceId?: string;
103
+ }): Promise<{
104
+ content: {
105
+ type: "text";
106
+ text: string;
107
+ }[];
108
+ }>;
109
+ export declare const installAppDataParams: {
110
+ path: z.ZodString;
111
+ deviceId: z.ZodOptional<z.ZodString>;
112
+ };
113
+ export declare function handleInstallAppData(args: {
114
+ path: string;
115
+ deviceId?: string;
116
+ }): Promise<{
117
+ content: {
118
+ type: "text";
119
+ text: string;
120
+ }[];
121
+ }>;
122
+ export declare const getEnvParams: {
123
+ variable: z.ZodString;
124
+ deviceId: z.ZodOptional<z.ZodString>;
125
+ };
126
+ export declare function handleGetEnv(args: {
127
+ variable: string;
128
+ deviceId?: string;
129
+ }): Promise<{
130
+ content: {
131
+ type: "text";
132
+ text: string;
133
+ }[];
134
+ }>;
135
+ export declare const memoryWarningParams: {
136
+ deviceId: z.ZodOptional<z.ZodString>;
137
+ };
138
+ export declare function handleMemoryWarning(args: {
139
+ deviceId?: string;
140
+ }): Promise<{
141
+ content: {
142
+ type: "text";
143
+ text: string;
144
+ }[];
145
+ }>;
146
+ export declare const biometricParams: {
147
+ enrolled: z.ZodBoolean;
148
+ deviceId: z.ZodOptional<z.ZodString>;
149
+ };
150
+ export declare function handleBiometric(args: {
151
+ enrolled: boolean;
152
+ deviceId?: string;
153
+ }): Promise<{
154
+ content: {
155
+ type: "text";
156
+ text: string;
157
+ }[];
158
+ }>;
159
+ export declare const networkStatusParams: {
160
+ deviceId: z.ZodOptional<z.ZodString>;
161
+ };
162
+ export declare function handleNetworkStatus(args: {
163
+ deviceId?: string;
164
+ }): Promise<{
165
+ content: {
166
+ type: "text";
167
+ text: string;
168
+ }[];
169
+ }>;
170
+ export declare const defaultsReadParams: {
171
+ domain: z.ZodString;
172
+ key: z.ZodOptional<z.ZodString>;
173
+ deviceId: z.ZodOptional<z.ZodString>;
174
+ };
175
+ export declare function handleDefaultsRead(args: {
176
+ domain: string;
177
+ key?: string;
178
+ deviceId?: string;
179
+ }): Promise<{
180
+ content: {
181
+ type: "text";
182
+ text: string;
183
+ }[];
184
+ }>;
185
+ export declare const defaultsWriteParams: {
186
+ domain: z.ZodString;
187
+ key: z.ZodString;
188
+ value: z.ZodString;
189
+ type: z.ZodOptional<z.ZodEnum<["string", "int", "float", "bool"]>>;
190
+ deviceId: z.ZodOptional<z.ZodString>;
191
+ };
192
+ export declare function handleDefaultsWrite(args: {
193
+ domain: string;
194
+ key: string;
195
+ value: string;
196
+ type?: string;
197
+ deviceId?: string;
198
+ }): Promise<{
199
+ content: {
200
+ type: "text";
201
+ text: string;
202
+ }[];
203
+ }>;
@@ -0,0 +1,275 @@
1
+ import { z } from 'zod';
2
+ import { execSimctl, resolveDevice } from '../helpers/simctl.js';
3
+ // --- icloud_sync ---
4
+ export const icloudSyncParams = {
5
+ deviceId: z.string().optional().describe('Device (default: booted)'),
6
+ };
7
+ export async function handleIcloudSync(args) {
8
+ const device = await resolveDevice(args.deviceId);
9
+ try {
10
+ await execSimctl(['icloud_sync', device], 'tool:icloudSync');
11
+ return { content: [{ type: 'text', text: 'iCloud sync triggered.' }] };
12
+ }
13
+ catch (err) {
14
+ const e = err;
15
+ return { content: [{ type: 'text', text: `iCloud sync failed (device may not be signed in): ${e.message}` }] };
16
+ }
17
+ }
18
+ // --- keychain ---
19
+ export const keychainParams = {
20
+ action: z.enum(['add-root-cert', 'add-cert', 'reset']).describe('"add-root-cert" to add trusted root CA, "add-cert" to add certificate, "reset" to clear keychain'),
21
+ path: z.string().optional().describe('Path to certificate file (required for add-root-cert and add-cert)'),
22
+ deviceId: z.string().optional().describe('Device (default: booted)'),
23
+ };
24
+ export async function handleKeychain(args) {
25
+ const device = await resolveDevice(args.deviceId);
26
+ if (args.action === 'reset') {
27
+ await execSimctl(['keychain', device, 'reset'], 'tool:keychain');
28
+ return { content: [{ type: 'text', text: 'Keychain reset.' }] };
29
+ }
30
+ if (!args.path) {
31
+ return { content: [{ type: 'text', text: `Certificate path is required for ${args.action}.` }] };
32
+ }
33
+ await execSimctl(['keychain', device, args.action, args.path], 'tool:keychain');
34
+ return { content: [{ type: 'text', text: `Certificate added via ${args.action}: ${args.path}` }] };
35
+ }
36
+ // --- content_size (Dynamic Type) ---
37
+ export const contentSizeParams = {
38
+ size: z.enum([
39
+ 'extra-small', 'small', 'medium', 'large', 'extra-large', 'extra-extra-large', 'extra-extra-extra-large',
40
+ 'accessibility-medium', 'accessibility-large', 'accessibility-extra-large',
41
+ 'accessibility-extra-extra-large', 'accessibility-extra-extra-extra-large',
42
+ ]).describe('Preferred content size category for Dynamic Type testing'),
43
+ deviceId: z.string().optional().describe('Device (default: booted)'),
44
+ };
45
+ export async function handleContentSize(args) {
46
+ const device = await resolveDevice(args.deviceId);
47
+ await execSimctl(['ui', device, 'content_size', args.size], 'tool:contentSize');
48
+ return { content: [{ type: 'text', text: `Content size set to "${args.size}". Apps using Dynamic Type will update.` }] };
49
+ }
50
+ // --- increase_contrast ---
51
+ export const increaseContrastParams = {
52
+ enabled: z.boolean().describe('Enable or disable Increase Contrast accessibility setting'),
53
+ deviceId: z.string().optional().describe('Device (default: booted)'),
54
+ };
55
+ export async function handleIncreaseContrast(args) {
56
+ const device = await resolveDevice(args.deviceId);
57
+ await execSimctl(['ui', device, 'increase_contrast', args.enabled ? 'enabled' : 'disabled'], 'tool:increaseContrast');
58
+ return { content: [{ type: 'text', text: `Increase Contrast ${args.enabled ? 'enabled' : 'disabled'}.` }] };
59
+ }
60
+ // --- location_scenario ---
61
+ export const locationScenarioParams = {
62
+ action: z.enum(['list', 'run', 'clear']).describe('"list" available scenarios, "run" a scenario, "clear" to stop'),
63
+ scenario: z.string().optional().describe('Scenario name (e.g., "Freeway Drive", "City Run", "City Bicycle Ride", "Apple")'),
64
+ deviceId: z.string().optional().describe('Device (default: booted)'),
65
+ };
66
+ export async function handleLocationScenario(args) {
67
+ const device = await resolveDevice(args.deviceId);
68
+ if (args.action === 'list') {
69
+ const { stdout } = await execSimctl(['location', device, 'list'], 'tool:locationScenario');
70
+ return { content: [{ type: 'text', text: `Available location scenarios:\n${stdout}` }] };
71
+ }
72
+ if (args.action === 'clear') {
73
+ await execSimctl(['location', device, 'clear'], 'tool:locationScenario');
74
+ return { content: [{ type: 'text', text: 'Location scenario stopped and location cleared.' }] };
75
+ }
76
+ if (!args.scenario) {
77
+ return { content: [{ type: 'text', text: 'Scenario name required for "run". Use action="list" to see available scenarios.' }] };
78
+ }
79
+ await execSimctl(['location', device, 'run', args.scenario], 'tool:locationScenario');
80
+ return { content: [{ type: 'text', text: `Running location scenario: "${args.scenario}". Use action="clear" to stop.` }] };
81
+ }
82
+ // --- location_route (simulate movement between waypoints) ---
83
+ export const locationRouteParams = {
84
+ waypoints: z.array(z.object({
85
+ lat: z.number().describe('Latitude'),
86
+ lng: z.number().describe('Longitude'),
87
+ })).describe('Array of {lat, lng} waypoints to traverse'),
88
+ speed: z.number().optional().describe('Speed in meters/second (default: ~walking speed)'),
89
+ deviceId: z.string().optional().describe('Device (default: booted)'),
90
+ };
91
+ export async function handleLocationRoute(args) {
92
+ const device = await resolveDevice(args.deviceId);
93
+ if (args.waypoints.length < 2) {
94
+ return { content: [{ type: 'text', text: 'At least 2 waypoints required.' }] };
95
+ }
96
+ const waypointData = args.waypoints.map(w => `${w.lat},${w.lng}`).join('\n') + '\n';
97
+ const cmdArgs = ['simctl', 'location', device, 'start'];
98
+ if (args.speed)
99
+ cmdArgs.push('--speed=' + args.speed);
100
+ try {
101
+ const { spawn } = await import('node:child_process');
102
+ await new Promise((resolve, reject) => {
103
+ const child = spawn('xcrun', cmdArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
104
+ let stderr = '';
105
+ child.stderr.on('data', (d) => { stderr += d.toString(); });
106
+ child.on('close', (code) => {
107
+ if (code === 0)
108
+ resolve();
109
+ else
110
+ reject(new Error(stderr || `exit code ${code}`));
111
+ });
112
+ child.stdin.write(waypointData);
113
+ child.stdin.end();
114
+ });
115
+ return {
116
+ content: [{
117
+ type: 'text',
118
+ text: `Simulating route with ${args.waypoints.length} waypoints${args.speed ? ` at ${args.speed}m/s` : ''}. Use simulator_location_scenario action="clear" to stop.`,
119
+ }],
120
+ };
121
+ }
122
+ catch (err) {
123
+ const e = err;
124
+ return { content: [{ type: 'text', text: `Route simulation failed: ${e.message}` }] };
125
+ }
126
+ }
127
+ // --- verbose_logging ---
128
+ export const verboseLoggingParams = {
129
+ enabled: z.boolean().describe('Enable or disable verbose logging (device reboot may be required)'),
130
+ deviceId: z.string().optional().describe('Device (default: booted)'),
131
+ };
132
+ export async function handleVerboseLogging(args) {
133
+ const device = await resolveDevice(args.deviceId);
134
+ await execSimctl(['logverbose', device, args.enabled ? 'enable' : 'disable'], 'tool:verboseLogging');
135
+ return {
136
+ content: [{
137
+ type: 'text',
138
+ text: `Verbose logging ${args.enabled ? 'enabled' : 'disabled'}. Reboot the device for changes to take effect.`,
139
+ }],
140
+ };
141
+ }
142
+ // --- install_app_data ---
143
+ export const installAppDataParams = {
144
+ path: z.string().describe('Path to .xcappdata package to install'),
145
+ deviceId: z.string().optional().describe('Device (default: booted)'),
146
+ };
147
+ export async function handleInstallAppData(args) {
148
+ const device = await resolveDevice(args.deviceId);
149
+ await execSimctl(['install_app_data', device, args.path], 'tool:installAppData');
150
+ return { content: [{ type: 'text', text: `App data installed from: ${args.path}` }] };
151
+ }
152
+ // --- get_env ---
153
+ export const getEnvParams = {
154
+ variable: z.string().describe('Environment variable name to read (e.g., "HOME", "TMPDIR", "PATH")'),
155
+ deviceId: z.string().optional().describe('Device (default: booted)'),
156
+ };
157
+ export async function handleGetEnv(args) {
158
+ const device = await resolveDevice(args.deviceId);
159
+ const { stdout } = await execSimctl(['getenv', device, args.variable], 'tool:getEnv');
160
+ return { content: [{ type: 'text', text: `${args.variable}=${stdout.trim()}` }] };
161
+ }
162
+ // --- simulate_memory_warning ---
163
+ export const memoryWarningParams = {
164
+ deviceId: z.string().optional().describe('Device (default: booted)'),
165
+ };
166
+ export async function handleMemoryWarning(args) {
167
+ const device = await resolveDevice(args.deviceId);
168
+ try {
169
+ // Trigger UIKit memory warning via notification
170
+ await execSimctl(['spawn', device, 'notifyutil', '-p', 'com.apple.UIKit.lowMemory'], 'tool:memoryWarning');
171
+ return { content: [{ type: 'text', text: 'Memory warning triggered. Apps will receive didReceiveMemoryWarning.' }] };
172
+ }
173
+ catch {
174
+ // Fallback: try via launchctl
175
+ try {
176
+ await execSimctl(['spawn', device, 'notifyutil', '-p', 'com.apple.system.memorystatus.level.warning'], 'tool:memoryWarning');
177
+ return { content: [{ type: 'text', text: 'Memory pressure warning triggered.' }] };
178
+ }
179
+ catch (err) {
180
+ const e = err;
181
+ return { content: [{ type: 'text', text: `Memory warning simulation failed: ${e.message}. Use Xcode Debug > Simulate Memory Warning as alternative.` }] };
182
+ }
183
+ }
184
+ }
185
+ // --- biometric_enrollment ---
186
+ export const biometricParams = {
187
+ enrolled: z.boolean().describe('Whether Face ID / Touch ID is enrolled'),
188
+ deviceId: z.string().optional().describe('Device (default: booted)'),
189
+ };
190
+ export async function handleBiometric(args) {
191
+ const device = await resolveDevice(args.deviceId);
192
+ // Use notifyutil to toggle biometric enrollment
193
+ try {
194
+ const val = args.enrolled ? '1' : '0';
195
+ await execSimctl(['spawn', device, 'notifyutil', '-s', 'com.apple.BiometricKit.enrollmentChanged', val], 'tool:biometric');
196
+ return {
197
+ content: [{
198
+ type: 'text',
199
+ text: `Biometric enrollment set to ${args.enrolled ? 'enrolled' : 'not enrolled'}.`,
200
+ }],
201
+ };
202
+ }
203
+ catch (err) {
204
+ const e = err;
205
+ return { content: [{ type: 'text', text: `Biometric enrollment change failed: ${e.message}. Use Simulator > Features > Face ID/Touch ID menu.` }] };
206
+ }
207
+ }
208
+ // --- network_status (read/display network configuration) ---
209
+ export const networkStatusParams = {
210
+ deviceId: z.string().optional().describe('Device (default: booted)'),
211
+ };
212
+ export async function handleNetworkStatus(args) {
213
+ const device = await resolveDevice(args.deviceId);
214
+ const results = [];
215
+ // Get network info from host perspective (simulator shares host network)
216
+ try {
217
+ const { execFile: ef } = await import('node:child_process');
218
+ const { promisify: p } = await import('node:util');
219
+ const exec = p(ef);
220
+ const { stdout } = await exec('networksetup', ['-listallhardwareports'], { encoding: 'utf-8', timeout: 5000 });
221
+ const lines = stdout.split('\n').filter(l => l.includes('Hardware Port:') || l.includes('Device:'));
222
+ results.push('Host Network Ports:\n' + lines.join('\n'));
223
+ }
224
+ catch { /* skip */ }
225
+ // Check connectivity from inside simulator
226
+ try {
227
+ const { stdout } = await execSimctl(['spawn', device, 'nslookup', 'apple.com'], 'tool:networkStatus');
228
+ const serverLine = stdout.split('\n').find(l => l.includes('Server:'));
229
+ results.push('\nDNS Resolution: ' + (serverLine || 'working'));
230
+ }
231
+ catch {
232
+ results.push('\nDNS Resolution: failed (may be offline)');
233
+ }
234
+ // Get simulator's env for network-related vars
235
+ try {
236
+ const { stdout: home } = await execSimctl(['getenv', device, 'HOME'], 'tool:networkStatus');
237
+ results.push(`\nSimulator data path: ${home.trim()}`);
238
+ }
239
+ catch { /* skip */ }
240
+ return { content: [{ type: 'text', text: results.join('\n') || 'No network info available.' }] };
241
+ }
242
+ // --- defaults_read (read UserDefaults from inside simulator) ---
243
+ export const defaultsReadParams = {
244
+ domain: z.string().describe('Defaults domain (bundle ID like "com.apple.mobilesafari" or "NSGlobalDomain")'),
245
+ key: z.string().optional().describe('Specific key to read (omit for all keys)'),
246
+ deviceId: z.string().optional().describe('Device (default: booted)'),
247
+ };
248
+ export async function handleDefaultsRead(args) {
249
+ const device = await resolveDevice(args.deviceId);
250
+ const cmdArgs = ['spawn', device, 'defaults', 'read', args.domain];
251
+ if (args.key)
252
+ cmdArgs.push(args.key);
253
+ try {
254
+ const { stdout } = await execSimctl(cmdArgs, 'tool:defaultsRead');
255
+ return { content: [{ type: 'text', text: `${args.domain}${args.key ? '.' + args.key : ''}:\n${stdout.trim()}` }] };
256
+ }
257
+ catch (err) {
258
+ const e = err;
259
+ return { content: [{ type: 'text', text: `defaults read failed: ${e.message}` }] };
260
+ }
261
+ }
262
+ // --- defaults_write (write UserDefaults inside simulator) ---
263
+ export const defaultsWriteParams = {
264
+ domain: z.string().describe('Defaults domain (bundle ID)'),
265
+ key: z.string().describe('Key to write'),
266
+ value: z.string().describe('Value to set'),
267
+ type: z.enum(['string', 'int', 'float', 'bool']).optional().describe('Value type (default: string)'),
268
+ deviceId: z.string().optional().describe('Device (default: booted)'),
269
+ };
270
+ export async function handleDefaultsWrite(args) {
271
+ const device = await resolveDevice(args.deviceId);
272
+ const typeFlag = args.type === 'int' ? '-int' : args.type === 'float' ? '-float' : args.type === 'bool' ? '-bool' : '-string';
273
+ await execSimctl(['spawn', device, 'defaults', 'write', args.domain, args.key, typeFlag, args.value], 'tool:defaultsWrite');
274
+ return { content: [{ type: 'text', text: `Set ${args.domain}.${args.key} = ${args.value} (${args.type || 'string'})` }] };
275
+ }