manage_env
Boot, shutdown, or restart Android and iOS emulators/simulators for mobile app testing and development.
Instructions
Manage device environment: boot, shutdown, or restart emulators and simulators.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Action to perform | |
| platform | Yes | Target platform | |
| deviceId | No | Device ID, name, or AVD name (optional, uses first available) | |
| waitForReady | No | Wait for device to be fully ready after boot (default: true) | |
| timeoutMs | No | Timeout in milliseconds (default: 120000) |
Implementation Reference
- Main handler function for the 'manage_env' tool. Validates inputs and dispatches to platform-specific environment management functions (Android or iOS).export async function manageEnv(args: ManageEnvArgs): Promise<EnvironmentResult> { const { action, platform, deviceId, waitForReady = true, timeoutMs = 120000, } = args; // Validate platform if (!isPlatform(platform)) { throw Errors.invalidArguments(`Invalid platform: ${platform}. Must be 'android' or 'ios'`); } // Validate action if (!['boot', 'shutdown', 'restart'].includes(action)) { throw Errors.invalidArguments(`Invalid action: ${action}. Must be 'boot', 'shutdown', or 'restart'`); } const startTime = Date.now(); if (platform === 'android') { return manageAndroidEnv(action, deviceId, waitForReady, timeoutMs, startTime); } else { return manageIOSEnv(action, deviceId, waitForReady, timeoutMs, startTime); } }
- TypeScript interface defining the input parameters for the manage_env tool.export interface ManageEnvArgs { /** Action to perform */ action: EnvironmentAction; /** Target platform */ platform: string; /** Device ID or name */ deviceId?: string; /** Wait for device to be ready after boot */ waitForReady?: boolean; /** Timeout in milliseconds */ timeoutMs?: number; }
- src/tools/environment/manage-env.ts:388-424 (registration)Registers the 'manage_env' tool with the MCP tool registry, providing description, JSON input schema, and handler reference.export function registerManageEnvTool(): void { getToolRegistry().register( 'manage_env', { description: 'Manage device environment: boot, shutdown, or restart emulators and simulators.', inputSchema: createInputSchema( { action: { type: 'string', enum: ['boot', 'shutdown', 'restart'], description: 'Action to perform', }, platform: { type: 'string', enum: ['android', 'ios'], description: 'Target platform', }, deviceId: { type: 'string', description: 'Device ID, name, or AVD name (optional, uses first available)', }, waitForReady: { type: 'boolean', description: 'Wait for device to be fully ready after boot (default: true)', }, timeoutMs: { type: 'number', description: 'Timeout in milliseconds (default: 120000)', }, }, ['action', 'platform'] ), }, (args) => manageEnv(args as unknown as ManageEnvArgs) ); }
- Helper function implementing Android-specific logic for booting, shutting down, and restarting emulators.async function manageAndroidEnv( action: EnvironmentAction, deviceQuery: string | undefined, waitForReady: boolean, _timeoutMs: number, startTime: number ): Promise<EnvironmentResult> { // Get current devices const devices = await listAndroidDevices(); let targetDevice: Device | undefined; if (deviceQuery) { // Find specific device const found = devices.find( (d) => d.id === deviceQuery || d.name === deviceQuery || d.model === deviceQuery ); if (found) { targetDevice = fromAndroidDevice(found); } } else if (action !== 'boot') { // For shutdown/restart, use first booted device const booted = devices.find((d) => d.status === 'booted'); if (booted) { targetDevice = fromAndroidDevice(booted); } } switch (action) { case 'boot': { // For boot, we need an AVD name let avdName = deviceQuery; if (!avdName) { // List available AVDs and use first one const avds = await listAvds(); if (avds.length === 0) { return { success: false, action, error: 'No Android AVDs available. Create one in Android Studio.', durationMs: Date.now() - startTime, }; } avdName = avds[0]; } // Check if already running const existing = devices.find((d) => d.name === avdName || d.id.includes(avdName)); if (existing && existing.status === 'booted') { return { success: true, action, device: fromAndroidDevice(existing), details: 'Device already running', durationMs: Date.now() - startTime, }; } // Boot the emulator (fire and forget, returns void) await bootEmulator(avdName); // Wait for device to be ready if requested if (waitForReady) { // Find the booted device await new Promise((resolve) => setTimeout(resolve, 5000)); // Initial wait const updatedDevices = await listAndroidDevices(); const bootedDevice = updatedDevices.find((d) => d.status === 'booted'); if (bootedDevice) { await waitForDevice(bootedDevice.id); } } // Get updated device info const finalDevices = await listAndroidDevices(); const bootedDevice = finalDevices.find((d) => d.status === 'booted'); return { success: true, action, device: bootedDevice ? fromAndroidDevice(bootedDevice) : undefined, details: `Booted emulator: ${avdName}`, durationMs: Date.now() - startTime, }; } case 'shutdown': { if (!targetDevice) { return { success: false, action, error: 'No running Android device found to shutdown', durationMs: Date.now() - startTime, }; } await shutdownEmulator(targetDevice.id); return { success: true, action, device: targetDevice, details: `Shutdown device: ${targetDevice.name}`, durationMs: Date.now() - startTime, }; } case 'restart': { if (!targetDevice) { return { success: false, action, error: 'No running Android device found to restart', durationMs: Date.now() - startTime, }; } // Shutdown then boot await shutdownEmulator(targetDevice.id); await new Promise((resolve) => setTimeout(resolve, 2000)); // Get AVD name from device const avdName = targetDevice.name; await bootEmulator(avdName); if (waitForReady) { await new Promise((resolve) => setTimeout(resolve, 5000)); const updatedDevices = await listAndroidDevices(); const bootedDevice = updatedDevices.find((d) => d.status === 'booted'); if (bootedDevice) { await waitForDevice(bootedDevice.id); } } // Get updated device info const finalDevices = await listAndroidDevices(); const restartedDevice = finalDevices.find((d) => d.status === 'booted'); return { success: true, action, device: restartedDevice ? fromAndroidDevice(restartedDevice) : targetDevice, details: `Restarted device: ${targetDevice.name}`, durationMs: Date.now() - startTime, }; } } }
- Helper function implementing iOS-specific logic for booting, shutting down, and restarting simulators.async function manageIOSEnv( action: EnvironmentAction, deviceQuery: string | undefined, waitForReady: boolean, _timeoutMs: number, startTime: number ): Promise<EnvironmentResult> { // Get current devices const devices = await listIOSDevices(); let targetDevice: Device | undefined; if (deviceQuery) { // Find specific device const found = devices.find((d) => d.id === deviceQuery || d.name === deviceQuery); if (found) { targetDevice = fromIOSDevice({ id: found.id, name: found.name, status: found.status, runtime: found.runtime, }); } } else if (action !== 'boot') { // For shutdown/restart, use first booted device const booted = await getIOSBootedDevice(); if (booted) { targetDevice = fromIOSDevice({ id: booted.id, name: booted.name, status: booted.status, runtime: booted.runtime, }); } } switch (action) { case 'boot': { let udid = deviceQuery; if (!udid) { // Find first available simulator const available = devices.find((d) => d.isAvailable !== false); if (!available) { return { success: false, action, error: 'No iOS simulators available', durationMs: Date.now() - startTime, }; } udid = available.id; } // Check if already running const existing = devices.find((d) => d.id === udid || d.name === udid); if (existing && existing.status.toLowerCase() === 'booted') { return { success: true, action, device: fromIOSDevice({ id: existing.id, name: existing.name, status: existing.status, runtime: existing.runtime, }), details: 'Simulator already running', durationMs: Date.now() - startTime, }; } // Get UDID if we have a name const targetSim = devices.find((d) => d.id === udid || d.name === udid); if (!targetSim) { return { success: false, action, error: `iOS simulator not found: ${udid}`, durationMs: Date.now() - startTime, }; } // Boot the simulator await bootSimulator(targetSim.id); // Wait for device to be ready (simple delay since simctl boot is synchronous) if (waitForReady) { await new Promise((resolve) => setTimeout(resolve, 5000)); } return { success: true, action, device: fromIOSDevice({ id: targetSim.id, name: targetSim.name, status: 'Booted', runtime: targetSim.runtime, }), details: `Booted simulator: ${targetSim.name}`, durationMs: Date.now() - startTime, }; } case 'shutdown': { if (!targetDevice) { return { success: false, action, error: 'No running iOS simulator found to shutdown', durationMs: Date.now() - startTime, }; } await shutdownSimulator(targetDevice.id); return { success: true, action, device: targetDevice, details: `Shutdown simulator: ${targetDevice.name}`, durationMs: Date.now() - startTime, }; } case 'restart': { if (!targetDevice) { return { success: false, action, error: 'No running iOS simulator found to restart', durationMs: Date.now() - startTime, }; } // Shutdown then boot await shutdownSimulator(targetDevice.id); await new Promise((resolve) => setTimeout(resolve, 2000)); await bootSimulator(targetDevice.id); if (waitForReady) { await new Promise((resolve) => setTimeout(resolve, 5000)); } return { success: true, action, device: { ...targetDevice, status: 'booted' }, details: `Restarted simulator: ${targetDevice.name}`, durationMs: Date.now() - startTime, }; } } }