Skip to content
22 changes: 22 additions & 0 deletions manifests/tools/clone_sims.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
id: clone_sims
module: mcp/tools/simulator-management/clone_sims
names:
mcp: clone_sims
cli: clone
description: Clone an existing simulator.
annotations:
title: Clone Simulator
readOnlyHint: false
destructiveHint: false
openWorldHint: false
nextSteps:
- label: List simulators to see the clone
toolId: list_sims
priority: 1
when: success
- label: Boot the cloned simulator
toolId: boot_sim
params:
simulatorId: NEW_UDID
priority: 2
when: success
22 changes: 22 additions & 0 deletions manifests/tools/create_sim.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
id: create_sim
module: mcp/tools/simulator-management/create_sim
names:
mcp: create_sim
cli: create
description: Create a new simulator.
annotations:
title: Create Simulator
readOnlyHint: false
destructiveHint: false
openWorldHint: false
nextSteps:
- label: List simulators to see the new device
toolId: list_sims
priority: 1
when: success
- label: Boot the new simulator
toolId: boot_sim
params:
simulatorId: NEW_UDID
priority: 2
when: success
16 changes: 16 additions & 0 deletions manifests/tools/delete_sims.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
id: delete_sims
module: mcp/tools/simulator-management/delete_sims
names:
mcp: delete_sims
cli: delete
description: Delete simulators by UDID, all simulators, or unavailable simulators.
annotations:
title: Delete Simulators
readOnlyHint: false
destructiveHint: true
openWorldHint: false
nextSteps:
- label: List remaining simulators
toolId: list_sims
priority: 1
when: success
3 changes: 3 additions & 0 deletions manifests/workflows/simulator-management.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ tools:
- set_sim_location
- reset_sim_location
- set_sim_appearance
- clone_sims
- create_sim
- delete_sims
- sim_statusbar
- toggle_software_keyboard
- toggle_connect_hardware_keyboard
72 changes: 72 additions & 0 deletions src/mcp/tools/simulator-management/clone_sims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as z from 'zod';
import { log } from '../../../utils/logging/index.ts';
import type { CommandExecutor } from '../../../utils/execution/index.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { createTypedTool, getHandlerContext } from '../../../utils/typed-tool-factory.ts';
import { withErrorHandling } from '../../../utils/tool-error-handling.ts';

Check failure on line 6 in src/mcp/tools/simulator-management/clone_sims.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Cannot find module '../../../utils/tool-error-handling.ts' or its corresponding type declarations.
import { header, statusLine } from '../../../utils/tool-event-builders.ts';

Check failure on line 7 in src/mcp/tools/simulator-management/clone_sims.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Cannot find module '../../../utils/tool-event-builders.ts' or its corresponding type declarations.
Comment thread
yjmeqt marked this conversation as resolved.
Outdated

const cloneSimsSchema = z.object({
sourceSimulatorId: z.string().uuid().describe('UDID of the simulator to clone'),
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
newName: z
.string()
.optional()
.describe('Name for the cloned simulator. If omitted, simctl auto-generates one.'),
});

type CloneSimsParams = z.infer<typeof cloneSimsSchema>;

export async function clone_simsLogic(
params: CloneSimsParams,
executor: CommandExecutor,
): Promise<void> {
log(
'info',
`Cloning simulator ${params.sourceSimulatorId}${params.newName ? ` as "${params.newName}"` : ''}`,
);

const headerEvent = header('Clone Simulator', [
{ label: 'Source', value: params.sourceSimulatorId },
...(params.newName ? [{ label: 'New Name', value: params.newName }] : []),
]);

const ctx = getHandlerContext();

return withErrorHandling(
ctx,
async () => {
const command = ['xcrun', 'simctl', 'clone', params.sourceSimulatorId];
if (params.newName) {
command.push(params.newName);
}
Comment thread
cameroncooke marked this conversation as resolved.
Outdated

const result = await executor(command, 'Clone Simulator', false);

if (!result.success) {
ctx.emit(headerEvent);
ctx.emit(statusLine('error', `Clone simulator failed: ${result.error}`));
return;
}

const newUdid = result.output.trim();
ctx.emit(headerEvent);
ctx.emit(statusLine('success', `Simulator cloned successfully. New UDID: ${newUdid}`));
ctx.nextStepParams = {
boot_sim: { simulatorId: newUdid },
open_sim: {},
install_app_sim: { simulatorId: newUdid, appPath: 'PATH_TO_YOUR_APP' },
launch_app_sim: { simulatorId: newUdid, bundleId: 'YOUR_APP_BUNDLE_ID' },
list_sims: {},
};
},
{
header: headerEvent,
errorMessage: ({ message }) => `Clone simulator failed: ${message}`,

Check failure on line 64 in src/mcp/tools/simulator-management/clone_sims.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Binding element 'message' implicitly has an 'any' type.
logMessage: ({ message }) => `Error cloning simulator: ${message}`,

Check failure on line 65 in src/mcp/tools/simulator-management/clone_sims.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Binding element 'message' implicitly has an 'any' type.
},
);
}

export const schema = cloneSimsSchema.shape;

export const handler = createTypedTool(cloneSimsSchema, clone_simsLogic, getDefaultCommandExecutor);
Comment thread
yjmeqt marked this conversation as resolved.
Outdated
77 changes: 77 additions & 0 deletions src/mcp/tools/simulator-management/create_sim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as z from 'zod';
import { log } from '../../../utils/logging/index.ts';
import type { CommandExecutor } from '../../../utils/execution/index.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { createTypedTool, getHandlerContext } from '../../../utils/typed-tool-factory.ts';
import { withErrorHandling } from '../../../utils/tool-error-handling.ts';

Check failure on line 6 in src/mcp/tools/simulator-management/create_sim.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Cannot find module '../../../utils/tool-error-handling.ts' or its corresponding type declarations.
import { header, statusLine } from '../../../utils/tool-event-builders.ts';

Check failure on line 7 in src/mcp/tools/simulator-management/create_sim.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Cannot find module '../../../utils/tool-event-builders.ts' or its corresponding type declarations.

const createSimSchema = z.object({
name: z.string().min(1).describe('Name for the new simulator (e.g., "iPhone 17 Test")'),
deviceType: z
.string()
.min(1)
.describe(
'Device type identifier (e.g., "iPhone 17" or "com.apple.CoreSimulator.SimDeviceType.iPhone-17"). Use list_sims to see available device types.',
),
runtime: z
.string()
.min(1)
.describe(
'Runtime identifier (e.g., "iOS 26" or "com.apple.CoreSimulator.SimRuntime.iOS-26"). Use list_sims to see available runtimes.',
),
});

type CreateSimParams = z.infer<typeof createSimSchema>;

export async function create_simLogic(
params: CreateSimParams,
executor: CommandExecutor,
): Promise<void> {
log(
'info',
`Creating simulator "${params.name}" (device type: ${params.deviceType}, runtime: ${params.runtime})`,
);

const headerEvent = header('Create Simulator', [
{ label: 'Name', value: params.name },
{ label: 'Device Type', value: params.deviceType },
{ label: 'Runtime', value: params.runtime },
]);

const ctx = getHandlerContext();

return withErrorHandling(
ctx,
async () => {
const command = ['xcrun', 'simctl', 'create', params.name, params.deviceType, params.runtime];
const result = await executor(command, 'Create Simulator', false);

if (!result.success) {
ctx.emit(headerEvent);
ctx.emit(statusLine('error', `Create simulator failed: ${result.error}`));
return;
}

const newUdid = result.output.trim();
ctx.emit(headerEvent);
ctx.emit(statusLine('success', `Simulator created successfully. New UDID: ${newUdid}`));
ctx.nextStepParams = {
boot_sim: { simulatorId: newUdid },
open_sim: {},
install_app_sim: { simulatorId: newUdid, appPath: 'PATH_TO_YOUR_APP' },
launch_app_sim: { simulatorId: newUdid, bundleId: 'YOUR_APP_BUNDLE_ID' },
list_sims: {},
};
},
{
header: headerEvent,
errorMessage: ({ message }) => `Create simulator failed: ${message}`,

Check failure on line 69 in src/mcp/tools/simulator-management/create_sim.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Binding element 'message' implicitly has an 'any' type.
logMessage: ({ message }) => `Error creating simulator: ${message}`,

Check failure on line 70 in src/mcp/tools/simulator-management/create_sim.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Binding element 'message' implicitly has an 'any' type.
},
);
}

export const schema = createSimSchema.shape;

export const handler = createTypedTool(createSimSchema, create_simLogic, getDefaultCommandExecutor);
101 changes: 101 additions & 0 deletions src/mcp/tools/simulator-management/delete_sims.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as z from 'zod';
import { log } from '../../../utils/logging/index.ts';
import type { CommandExecutor } from '../../../utils/execution/index.ts';
import { getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
import { createTypedTool, getHandlerContext } from '../../../utils/typed-tool-factory.ts';
import { withErrorHandling } from '../../../utils/tool-error-handling.ts';

Check failure on line 6 in src/mcp/tools/simulator-management/delete_sims.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Cannot find module '../../../utils/tool-error-handling.ts' or its corresponding type declarations.
import { header, section, statusLine } from '../../../utils/tool-event-builders.ts';

Check failure on line 7 in src/mcp/tools/simulator-management/delete_sims.ts

View workflow job for this annotation

GitHub Actions / build-and-test (24.x)

Cannot find module '../../../utils/tool-event-builders.ts' or its corresponding type declarations.

const deleteSimsSchema = z.object({
target: z
.string()
.min(1)
.describe(
'UDID of the simulator to delete, "all" to delete all simulators, or "unavailable" to delete unavailable simulators.',
),
shutdownFirst: z
.boolean()
.optional()
.describe('Shutdown the simulator before deleting. Useful for booted simulators.'),
});

type DeleteSimsParams = z.infer<typeof deleteSimsSchema>;

export async function delete_simsLogic(
params: DeleteSimsParams,
executor: CommandExecutor,
): Promise<void> {
const target = params.target;
const headerEvent = header('Delete Simulator', [
{ label: 'Target', value: target },
...(params.shutdownFirst ? [{ label: 'Shutdown First', value: 'true' }] : []),
]);

const ctx = getHandlerContext();

return withErrorHandling(
ctx,
async () => {
log(
'info',
`Deleting simulator(s) ${target}${params.shutdownFirst ? ' (shutdownFirst=true)' : ''}`,
);

if (params.shutdownFirst && target !== 'all' && target !== 'unavailable') {
try {
await executor(
['xcrun', 'simctl', 'shutdown', target],
'Shutdown Simulator',
true,
undefined,
);
} catch {
// ignore shutdown errors; proceed to delete attempt
}
}
Comment thread
cursor[bot] marked this conversation as resolved.

const result = await executor(
['xcrun', 'simctl', 'delete', target],
'Delete Simulator',
true,
undefined,
);
if (result.success) {
ctx.emit(headerEvent);
ctx.emit(statusLine('success', 'Simulator(s) deleted successfully'));
ctx.nextStepParams = {
list_sims: {},
};
return;
}

const errText = result.error ?? 'Unknown error';
if (/Unable to delete.*Booted/i.test(errText) && !params.shutdownFirst) {
ctx.emit(headerEvent);
ctx.emit(statusLine('error', `Failed to delete simulator: ${errText}`));
ctx.emit(
section('Hint', [
`The simulator appears to be Booted. Re-run delete_sims with { target: '${target}', shutdownFirst: true } to shut it down before deleting.`,
]),
);
return;
}

ctx.emit(headerEvent);
ctx.emit(statusLine('error', `Failed to delete simulator: ${errText}`));
},
{
header: headerEvent,
errorMessage: ({ message }) => `Failed to delete simulator: ${message}`,
logMessage: ({ message }) => `Error deleting simulators: ${message}`,
},
);
}

export const schema = deleteSimsSchema.shape;

export const handler = createTypedTool(
deleteSimsSchema,
delete_simsLogic,
getDefaultCommandExecutor,
);
Loading