connect_and_screenshot
Connect to an Android device over WiFi, pair with it, and capture a screenshot for UI debugging or visual inspection during app development.
Instructions
Connect to an Android device and take a screenshot. Use when no device is connected.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| debugPort | No | Wireless debugging port (different from pairing port) | |
| deviceIP | Yes | IP address of the Android device. Can include port like "192.168.1.100:12345" | |
| outputPath | No | Path where to save the screenshot (optional) | |
| pairingCode | No | Pairing code shown on device for initial pairing | |
| pairingPort | No | Pairing port shown on device for initial pairing |
Implementation Reference
- src/index.js:351-463 (handler)The primary handler function for the 'connect_and_screenshot' tool. It manages device connection (parsing IP/port, checking existing connections, pairing if needed), prevents overlapping operations, and delegates screenshot capture.async function connectAndTakeScreenshot(args) { // Prevent overlapping screenshot operations if (isScreenshotInProgress) { return { content: [ { type: 'text', text: 'Screenshot operation already in progress. Please wait for it to complete.', }, ], }; } isScreenshotInProgress = true; try { let deviceIP = args?.deviceIP; let debugPort = args?.debugPort; const pairingPort = args?.pairingPort; const pairingCode = args?.pairingCode; const outputPath = args?.outputPath; // If no device info provided, try to use saved connection if (!deviceIP && !debugPort) { const reconnected = await tryReconnectToSaved(); if (reconnected) { return await captureScreenshotFromDevice(outputPath); } } // Parse IP:PORT format if (deviceIP && deviceIP.includes(':')) { const parts = deviceIP.split(':'); deviceIP = parts[0]; debugPort = debugPort || parseInt(parts[1]); } // Check existing connections first const devices = await getConnectedDevices(); if (deviceIP && debugPort) { const deviceAddress = `${deviceIP}:${debugPort}`; const device = devices.find(d => d.address === deviceAddress); if (device && device.status === 'device') { return await captureScreenshotFromDevice(outputPath); } else if (device && device.status === 'offline') { await execAsync(`adb disconnect ${deviceAddress}`); await new Promise(resolve => setTimeout(resolve, 1000)); } } else { const onlineDevice = devices.find(d => d.status === 'device'); if (onlineDevice) { return await captureScreenshotFromDevice(outputPath); } } // Try to connect if (deviceIP && debugPort) { try { await connectToDevice(deviceIP, debugPort); return await captureScreenshotFromDevice(outputPath); } catch (error) { if (pairingPort && pairingCode) { try { await pairAndConnect(deviceIP, debugPort, pairingPort, pairingCode); return await captureScreenshotFromDevice(outputPath); } catch (pairingError) { return { isError: true, content: [ { type: 'text', text: `Failed to pair and connect: ${pairingError.message}. Please check your pairing code and ports.`, }, ], }; } } else { return { content: [ { type: 'text', text: `Failed to connect to ${deviceIP}:${debugPort}. I need the pairing code and pairing port. Please go to Settings > Developer Options > Wireless debugging > Pair device with pairing code, then provide me with the 6-digit pairing code and pairing port so I can connect automatically.`, }, ], }; } } } return { content: [ { type: 'text', text: 'I need your Android device connection details to take a screenshot. Please provide:\n\n1. IP address and port from Settings > Developer Options > Wireless debugging (e.g., "192.168.1.100:12345")\n2. If first time connecting, also provide the pairing code and pairing port from "Pair device with pairing code"\n\nOnce you give me these details, I\'ll automatically connect and take the screenshot.', }, ], }; } catch (error) { return { isError: true, content: [ { type: 'text', text: `Error: ${error.message}`, }, ], }; } finally { isScreenshotInProgress = false; } }
- src/index.js:149-179 (schema)Input schema defining parameters for the connect_and_screenshot tool: deviceIP (required), debugPort, pairingPort, pairingCode, outputPath with validation patterns and constraints.inputSchema: { type: 'object', properties: { deviceIP: { type: 'string', description: 'IP address of the Android device. Can include port like "192.168.1.100:12345"', pattern: '^(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(:\\d{1,5})?$', }, debugPort: { type: 'number', description: 'Wireless debugging port (different from pairing port)', minimum: 1, maximum: 65535, }, pairingPort: { type: 'number', description: 'Pairing port shown on device for initial pairing', minimum: 1, maximum: 65535, }, pairingCode: { type: 'string', description: 'Pairing code shown on device for initial pairing', pattern: '^\\d{6}$', }, outputPath: { type: 'string', description: 'Path where to save the screenshot (optional)', }, }, required: ['deviceIP'],
- src/index.js:146-181 (registration)Registration of the 'connect_and_screenshot' tool in the ListToolsRequestSchema response, including name, description, and input schema.{ name: 'connect_and_screenshot', description: 'Connect to an Android device and take a screenshot. Use when no device is connected.', inputSchema: { type: 'object', properties: { deviceIP: { type: 'string', description: 'IP address of the Android device. Can include port like "192.168.1.100:12345"', pattern: '^(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})(:\\d{1,5})?$', }, debugPort: { type: 'number', description: 'Wireless debugging port (different from pairing port)', minimum: 1, maximum: 65535, }, pairingPort: { type: 'number', description: 'Pairing port shown on device for initial pairing', minimum: 1, maximum: 65535, }, pairingCode: { type: 'string', description: 'Pairing code shown on device for initial pairing', pattern: '^\\d{6}$', }, outputPath: { type: 'string', description: 'Path where to save the screenshot (optional)', }, }, required: ['deviceIP'], }, },
- src/index.js:206-207 (registration)Dispatch registration in the CallToolRequestSchema switch statement mapping tool name to handler.case 'connect_and_screenshot': return await connectAndTakeScreenshot(toolArgs);
- src/index.js:465-581 (helper)Key helper function called by the handler to capture screenshot from the first connected device using ADB screencap and pull, handles base64 encoding for small images or saves to disk.async function captureScreenshotFromDevice(outputPath) { let tempPath = null; let finalOutputPath = null; let targetDevice = null; try { // Get the first connected device const devices = await getConnectedDevices(); const onlineDevices = devices.filter(d => d.status === 'device'); if (onlineDevices.length === 0) { throw new Error('No connected Android devices found'); } // Use the first available device targetDevice = onlineDevices[0].address; const timestamp = Date.now(); tempPath = `/sdcard/temp_screenshot_${timestamp}.png`; const defaultOutputPath = path.join(os.tmpdir(), `screenshot-${timestamp}.png`); finalOutputPath = outputPath || defaultOutputPath; // Take screenshot with unique temp filename, specifying the device await execAsync(`adb -s ${targetDevice} shell screencap -p ${tempPath}`); // Pull screenshot to local machine, specifying the device await execAsync(`adb -s ${targetDevice} pull ${tempPath} "${finalOutputPath}"`); // Get file stats first const stats = await fs.stat(finalOutputPath); const fileSizeKB = (stats.size / 1024).toFixed(2); // Clean up temp file on device immediately try { await execAsync(`adb -s ${targetDevice} shell rm ${tempPath}`); } catch (cleanupError) { } // For images over 2MB, skip base64 encoding entirely to prevent call stack overflow if (stats.size > 2 * 1024 * 1024) { // 2MB threshold return { content: [ { type: 'text', text: `Screenshot captured (${fileSizeKB} KB) and saved to: ${finalOutputPath}\n\nImage too large for inline display (>2MB). File saved to disk. This typically happens with photo wallpapers - consider using a simpler background.`, }, ], }; } // For smaller images, try base64 encoding with error handling let base64Image; try { const imageBuffer = await fs.readFile(finalOutputPath); base64Image = imageBuffer.toString('base64'); // Clear buffer immediately imageBuffer.fill(0); } catch (readError) { // If base64 conversion fails, just return file path return { content: [ { type: 'text', text: `Screenshot captured (${fileSizeKB} KB) and saved to: ${finalOutputPath}\n\nCould not display image inline: ${readError.message}`, }, ], }; } // Clean up temp file if it was auto-generated if (!outputPath) { try { await fs.unlink(finalOutputPath); } catch (unlinkError) { } } return { content: [ { type: 'text', text: `Screenshot captured (${fileSizeKB} KB) and saved to: ${finalOutputPath}`, }, { type: 'image', data: base64Image, mimeType: 'image/png', }, ], }; } catch (error) { // Emergency cleanup on error if (tempPath && targetDevice) { try { await execAsync(`adb -s ${targetDevice} shell rm ${tempPath}`); } catch (e) { } } if (finalOutputPath && !outputPath) { try { await fs.unlink(finalOutputPath); } catch (e) { } } return { isError: true, content: [ { type: 'text', text: `Failed to capture screenshot: ${error.message}`, }, ], }; } }