multi_device
Run the same action on multiple devices and get combined results. Ideal for testing across iOS and Android with a single command.
Instructions
Execute la meme action sur plusieurs devices et retourne les resultats combines. Utile pour tester sur iOS + Android en une seule commande.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| device_ids | Yes | Liste des device IDs a cibler | |
| action | Yes | Action a executer sur chaque device | |
| bundle_id | No | Bundle ID / package name (requis pour launch_app/kill_app) |
Implementation Reference
- src/tools/multi-device.ts:9-114 (handler)The registerMultiDevice function registers the 'multi_device' tool on the MCP server. The handler iterates over provided device_ids, validates each device, and executes the requested action (screenshot, get_ui_tree, launch_app, kill_app) sequentially across iOS and Android devices. It restores the original ADB serial in a finally block.
export function registerMultiDevice(server: McpServer): void { server.tool( "multi_device", "Execute la meme action sur plusieurs devices et retourne les resultats combines. Utile pour tester sur iOS + Android en une seule commande.", { device_ids: z.array(z.string()).min(1).describe("Liste des device IDs a cibler"), action: z.enum(["screenshot", "get_ui_tree", "launch_app", "kill_app"]).describe("Action a executer sur chaque device"), bundle_id: z.string().optional().describe("Bundle ID / package name (requis pour launch_app/kill_app)"), }, async ({ device_ids, action, bundle_id }) => { // Validate bundle_id for app actions if ((action === "launch_app" || action === "kill_app") && !bundle_id) { return { content: [{ type: "text", text: `Le parametre bundle_id est requis pour ${action}.` }], isError: true }; } // Save current state to restore later const savedSerial = getCurrentAdbSerial(); // Resolve all devices const devices: Array<{ device: DeviceInfo; error?: string }> = []; for (const id of device_ids) { const dev = await getDeviceById(id); if (!dev) { devices.push({ device: { id, name: id, platform: "ios", type: "simulator", state: "shutdown" }, error: `Device "${id}" non trouve` }); } else if (dev.state !== "booted") { devices.push({ device: dev, error: `Device "${dev.name}" n'est pas demarre` }); } else { devices.push({ device: dev }); } } const content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = []; let hasError = false; try { // Execute sequentially (ADB serial and WDA are singletons) for (let i = 0; i < devices.length; i++) { const { device: dev, error } = devices[i]; const platform = dev.platform === "ios" ? "🍎" : "🤖"; const header = `[${i + 1}/${devices.length}] ${platform} ${dev.name}`; if (error) { content.push({ type: "text", text: `${header}\n ERREUR : ${error}\n` }); hasError = true; continue; } try { if (dev.platform === "android") setAdbSerial(dev.id); switch (action) { case "screenshot": { const buffer = dev.platform === "ios" ? await iosScreenshot(dev.id) : await androidScreenshot(); content.push({ type: "text", text: `${header} — screenshot :` }); content.push({ type: "image", data: buffer.toString("base64"), mimeType: "image/png" }); break; } case "get_ui_tree": { let elements; if (dev.platform === "ios") { const wda = await ensureWdaRunning(dev); if (!wda.ready) throw new Error(wda.message ?? "WDA indisponible"); elements = await iosGetUiTree(); } else { elements = await androidGetUiTree(); } const lines = elements.map((el, idx) => { const text = el.label || el.name || ""; return ` [${idx}] ${el.type} "${text}" (${el.x},${el.y} ${el.width}x${el.height})`; }); content.push({ type: "text", text: `${header} — ${elements.length} elements :\n${lines.join("\n")}\n` }); break; } case "launch_app": { if (dev.platform === "ios") await iosLaunchApp(dev.id, bundle_id!); else await androidLaunchApp(bundle_id!); content.push({ type: "text", text: `${header} — ${bundle_id} lance\n` }); break; } case "kill_app": { if (dev.platform === "ios") await iosKillApp(dev.id, bundle_id!); else await androidKillApp(bundle_id!); content.push({ type: "text", text: `${header} — ${bundle_id} ferme\n` }); break; } } } catch (err) { const msg = err instanceof Error ? err.message : String(err); content.push({ type: "text", text: `${header}\n ERREUR : ${msg}\n` }); hasError = true; } } } finally { // Always restore original ADB serial, even on unexpected errors setAdbSerial(savedSerial); } return { content, isError: hasError }; } ); } - src/tools/multi-device.ts:13-17 (schema)Input schema for multi_device tool: device_ids (array of strings, min 1), action (enum: screenshot/get_ui_tree/launch_app/kill_app), bundle_id (optional string, required for launch_app/kill_app).
{ device_ids: z.array(z.string()).min(1).describe("Liste des device IDs a cibler"), action: z.enum(["screenshot", "get_ui_tree", "launch_app", "kill_app"]).describe("Action a executer sur chaque device"), bundle_id: z.string().optional().describe("Bundle ID / package name (requis pour launch_app/kill_app)"), }, - src/index.ts:25-70 (registration)Import of registerMultiDevice from the multi-device module, and registration call at line 70.
import { registerMultiDevice } from "./tools/multi-device.js"; const server = new McpServer({ name: "phantom", version: "2.3.0", }); // Device management registerListDevices(server); registerSetDevice(server); registerPrepareDevice(server); // Observation registerScreenshot(server); registerGetUiTree(server); registerWaitForElement(server); registerScrollUntilVisible(server); // Assertions registerAssertVisible(server); registerAssertNotVisible(server); // Interaction registerTap(server); registerLongPress(server); registerTypeText(server); registerSwipe(server); registerDismissKeyboard(server); // Navigation registerDeepLink(server); // Device actions registerShake(server); registerRotate(server); registerVideoRecord(server); // App lifecycle registerLaunchApp(server); registerKillApp(server); // Tier 3 — Analysis & Automation registerAccessibilityAudit(server); registerTestReport(server); registerVisualDiff(server); registerMultiDevice(server); - src/utils/device-manager.ts:90-93 (helper)getDeviceById helper used by multi_device to resolve each device ID from the available devices list.
export async function getDeviceById(id: string): Promise<DeviceInfo | null> { const all = await getAllDevices(); return all.find((d) => d.id === id) ?? null; } - src/platforms/types.ts:1-7 (helper)DeviceInfo type definition used in the multi_device tool for device metadata.
export interface DeviceInfo { id: string; name: string; platform: "ios" | "android"; type: "simulator" | "emulator" | "device"; state: "booted" | "shutdown"; }