Skip to main content
Glama
adamlj
by adamlj

connect_and_screenshot

Connect to an Android device and capture screenshots for UI debugging and 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
NameRequiredDescriptionDefault
deviceIPYesIP address of the Android device. Can include port like "192.168.1.100:12345"
debugPortNoWireless debugging port (different from pairing port)
pairingPortNoPairing port shown on device for initial pairing
pairingCodeNoPairing code shown on device for initial pairing
outputPathNoPath where to save the screenshot (optional)

Implementation Reference

  • Primary handler function that orchestrates device connection (parsing IP/port, reconnecting saved, pairing if needed) and screenshot capture for the 'connect_and_screenshot' tool.
    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: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)
    Handler dispatch registration in the CallToolRequestSchema switch statement.
    case 'connect_and_screenshot': return await connectAndTakeScreenshot(toolArgs);
  • Key helper function that performs the actual screenshot capture using ADB screencap and pull, handles large file fallback to disk save, base64 encoding for small images, and cleanup.
    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}`, }, ], }; } }
  • Helper function to connect to Android device via ADB and verify connection, saves successful connection.
    async function connectToDevice(deviceIP, debugPort) { const deviceAddress = `${deviceIP}:${debugPort}`; await execAsync(`adb connect ${deviceAddress}`); await new Promise(resolve => setTimeout(resolve, 2000)); const { stdout } = await execAsync('adb devices'); const isConnected = stdout.includes(deviceAddress) && stdout.includes('\tdevice'); if (!isConnected) { throw new Error('Failed to establish connection. Device might need pairing.'); } // Save successful connection await saveLastConnection(deviceAddress); }
Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/adamlj/android-screenshot-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server