launch.ts•7.23 kB
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { executeCommand } from '../../utils/command.js';
import { simulatorCache } from '../../state/simulator-cache.js';
interface SimctlLaunchToolArgs {
udid: string;
bundleId: string;
arguments?: string[];
environment?: Record<string, string>;
}
/**
* Launch an iOS app on a simulator
*
* Examples:
* - Launch app: udid: "device-123", bundleId: "com.example.MyApp"
* - Launch with arguments: udid: "device-123", bundleId: "com.example.MyApp", arguments: ["--verbose", "--debug"]
* - Launch with environment: udid: "device-123", bundleId: "com.example.MyApp", environment: { "DEBUG": "1" }
*
* Returns the process ID of the launched app
*
* **Full documentation:** See simctl/launch.md for detailed parameters and examples
*/
export async function simctlLaunchTool(args: any) {
const {
udid,
bundleId,
arguments: appArgs = [],
environment = {},
} = args as SimctlLaunchToolArgs;
try {
// Validate inputs
if (!udid || udid.trim().length === 0) {
throw new McpError(ErrorCode.InvalidRequest, 'UDID is required and cannot be empty');
}
if (!bundleId || bundleId.trim().length === 0) {
throw new McpError(ErrorCode.InvalidRequest, 'Bundle ID is required and cannot be empty');
}
// Validate bundle ID format
if (!bundleId.includes('.')) {
throw new McpError(
ErrorCode.InvalidRequest,
'Bundle ID should follow the format: com.company.appname'
);
}
// Validate simulator exists
const simulator = await simulatorCache.findSimulatorByUdid(udid);
if (!simulator) {
throw new McpError(
ErrorCode.InvalidRequest,
`Simulator with UDID "${udid}" not found. Use simctl-list to see available simulators.`
);
}
// Build environment variables with SIMCTL_CHILD_ prefix
// simctl requires environment variables to be prefixed with SIMCTL_CHILD_
let envPrefix = '';
for (const [key, value] of Object.entries(environment)) {
envPrefix += `SIMCTL_CHILD_${key}="${value}" `;
}
// Build arguments string
const argsString = appArgs.map(arg => `"${arg}"`).join(' ');
// Build launch command with environment variables prefixed
let command = `${envPrefix}xcrun simctl launch ${udid} "${bundleId}"`;
if (argsString) {
command += ` ${argsString}`;
}
console.error(`[simctl-launch] Executing: ${command}`);
const result = await executeCommand(command, {
timeout: 30000,
});
const success = result.code === 0;
// Extract process ID from output
const processIdMatch = result.stdout.match(/\d+/);
const processId = processIdMatch ? parseInt(processIdMatch[0], 10) : null;
const responseData = {
success,
udid,
bundleId,
processId: processId || undefined,
arguments: appArgs.length > 0 ? appArgs : undefined,
environment: Object.keys(environment).length > 0 ? environment : undefined,
simulatorInfo: {
name: simulator.name,
udid: simulator.udid,
state: simulator.state,
isAvailable: simulator.isAvailable,
},
command,
output: result.stdout,
error: result.stderr || undefined,
exitCode: result.code,
guidance: success
? [
`✅ App "${bundleId}" launched on "${simulator.name}"`,
`Process ID: ${processId}`,
`Terminate app: simctl-terminate ${udid} ${bundleId}`,
`Open URL in app: simctl-openurl ${udid} myapp://deeplink?param=value`,
`Check app container: simctl-get-app-container ${udid} ${bundleId}`,
]
: [
`❌ Failed to launch app: ${result.stderr || 'Unknown error'}`,
simulator.state !== 'Booted'
? `Simulator is not booted. Boot it first: simctl-boot ${udid}`
: `Verify app is installed: simctl-install ${udid} /path/to/App.app`,
`Check simulator health: simctl-health-check`,
],
};
const responseText = JSON.stringify(responseData, null, 2);
return {
content: [
{
type: 'text' as const,
text: responseText,
},
],
isError: !success,
};
} catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`simctl-launch failed: ${error instanceof Error ? error.message : String(error)}`
);
}
}
export const SIMCTL_LAUNCH_DOCS = `
# simctl-launch
Launch an iOS app on a simulator with support for custom arguments and environment variables.
## What it does
Starts an iOS app on a booted simulator, optionally passing command-line arguments and
environment variables. Returns the process ID of the launched app for tracking.
## Parameters
- **udid** (string, required): Simulator UDID (from simctl-list)
- **bundleId** (string, required): App bundle ID (e.g., com.example.MyApp)
- **arguments** (string[], optional): Command-line arguments to pass to the app
- **environment** (object, optional): Environment variables to set (automatically prefixed with SIMCTL_CHILD_)
## Returns
JSON response with:
- Process ID of the launched app
- Launch status and command executed
- Guidance for next steps (terminating, opening URLs, checking container)
## Examples
### Simple app launch
\`\`\`typescript
await simctlLaunchTool({
udid: 'device-123',
bundleId: 'com.example.MyApp'
})
\`\`\`
### Launch with debug arguments
\`\`\`typescript
await simctlLaunchTool({
udid: 'device-123',
bundleId: 'com.example.MyApp',
arguments: ['--verbose', '--debug']
})
\`\`\`
### Launch with environment variables
\`\`\`typescript
await simctlLaunchTool({
udid: 'device-123',
bundleId: 'com.example.MyApp',
environment: { DEBUG: '1', API_URL: 'https://staging.example.com' }
})
\`\`\`
## Common Use Cases
1. **Debug launches**: Start app with debug flags enabled
2. **API environment switching**: Set staging/production API URLs
3. **Feature flags**: Enable experimental features via environment
4. **Test scenarios**: Configure app behavior for specific test cases
5. **Deep link testing**: Launch app then open URLs with simctl-openurl
## Important Notes
- **Simulator must be booted**: Use simctl-boot first if simulator is not running
- **App must be installed**: Use simctl-install to install app first
- **Environment variables**: Automatically prefixed with SIMCTL_CHILD_ for simctl compatibility
- **Process ID tracking**: Returned PID can be used to monitor or terminate the app
## Error Handling
- **App not installed**: Returns error if app bundle is not found
- **Simulator not booted**: Indicates simulator must be booted first
- **Invalid bundle ID**: Validates bundle ID format (must contain '.')
- **Simulator not found**: Validates simulator exists in cache
## Next Steps After Launch
1. **Terminate app**: \`simctl-terminate <udid> <bundleId>\`
2. **Open URL/deep link**: \`simctl-openurl <udid> myapp://deeplink\`
3. **Check app container**: \`simctl-get-app-container <udid> <bundleId>\`
4. **Send push notification**: \`simctl-push <udid> <bundleId> <payload>\`
`;