Skip to main content
Glama
Winds-AI

autonomous-frontend-browser-tools

browser.screenshot

Capture the current browser tab as an image, save it to a structured path, and return the image. Requires DevTools connection for extension integration.

Instructions

Capture current browser tab; saves to structured path and returns image. Requires extension connection with DevTools open.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
randomStringYesany string (ignored)

Implementation Reference

  • Registration of the MCP tool 'browser.screenshot', defining its name, description, input schema (dummy param), and references the handler function.
    // New name server.tool( "browser.screenshot", "Capture current browser tab; saves to structured path and returns image. Requires extension connection with DevTools open.", { randomString: z.string().describe("any string (ignored)") }, handleCaptureBrowserScreenshot );
  • MCP handler for browser.screenshot: POSTs to browser-connector /capture-screenshot with project config, receives result, returns MCP content block with text summary and embedded PNG image.
    async function handleCaptureBrowserScreenshot() { return await withServerConnection(async () => { try { const targetUrl = `http://${discoveredHost}:${discoveredPort}/capture-screenshot`; const activeProjectName = getActiveProjectName(); const requestPayload = { returnImageData: true, // Always return image data projectName: activeProjectName, // Pass active project name }; const response = await fetch(targetUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(requestPayload), }); const result = await response.json(); if (response.ok) { const responseContent: any[] = [ { type: "text", text: `📁 Project: ${ result.projectDirectory || "default-project" }\n📌 Now Analyze the UI Layout and it's structure properly given the task at hand, then continue`, }, ]; responseContent.push({ type: "image", data: result.imageData, mimeType: "image/png", }); return { content: responseContent, } as any; } else { return { content: [ { type: "text", text: `Error taking screenshot: ${result.error}`, }, ], isError: true, } as any; } } catch (error: any) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `Failed to take screenshot: ${errorMessage}`, }, ], isError: true, } as any; } }); }
  • Primary execution handler: Validates WS connection health, sends 'take-screenshot' message to Chrome extension with requestId, awaits base64 response via callback registry, saves file using ScreenshotService, optionally auto-pastes to Cursor on macOS, builds and returns structured response with filePath and imageData.
    async captureScreenshot(req: express.Request, res: express.Response) { logInfo("Browser Connector: Starting captureScreenshot method"); if (!this.activeConnection) { logInfo( "Browser Connector: No active WebSocket connection to Chrome extension " ); return res.status(503).json({ error: "Chrome extension not connected. Please open Chrome DevTools and ensure the extension is loaded.", }); } // Extra health checks to avoid sending requests into a stale socket during reconnects if (!this.hasActiveConnection()) { return res.status(503).json({ error: "Chrome extension not connected (WebSocket not open). Please open DevTools on the target tab.", }); } const timeSinceHeartbeat = Date.now() - this.lastHeartbeatTime; if (timeSinceHeartbeat > this.HEARTBEAT_TIMEOUT) { logInfo( `Browser Connector: Connection unhealthy (no heartbeat for ${timeSinceHeartbeat}ms)` ); return res.status(503).json({ error: "Chrome extension connection is unhealthy. Open DevTools on the page and try again.", }); } // Probe for a quick heartbeat response to ensure we're not racing a reconnect const heartbeatOk = await this.awaitHeartbeatResponse(1200); if (!heartbeatOk) { return res.status(503).json({ error: "Chrome extension connection is not ready. Please ensure DevTools is open and retry.", }); } try { // Extract parameters from request body logDebug("Browser Connector: Starting screenshot capture..."); const { projectName, returnImageData, baseDirectory } = req.body || {}; const requestId = `${Date.now()}_${Math.random() .toString(36) .slice(2, 9)}`; logDebug("Browser Connector: Generated requestId:", requestId); // Create promise that will resolve when we get the screenshot data const screenshotPromise = new Promise<{ data: string; path?: string; autoPaste?: boolean; }>((resolve, reject) => { logDebug( `Browser Connector: Setting up screenshot callback for requestId: ${requestId}` ); // Store callback in map screenshotCallbacks.set(requestId, { resolve, reject }); logDebug( "Browser Connector: Current callbacks:", Array.from(screenshotCallbacks.keys()) ); // Set timeout to clean up if we don't get a response - increased for autonomous operation setTimeout(() => { if (screenshotCallbacks.has(requestId)) { logInfo( `Browser Connector: Screenshot capture timed out for requestId: ${requestId} [${this.connectionId}]` ); screenshotCallbacks.delete(requestId); reject( new Error( `Screenshot capture timed out - no response from Chrome extension [${this.connectionId}] after 30 seconds` ) ); } }, 30000); }); // Send screenshot request to extension const message = JSON.stringify({ type: "take-screenshot", requestId: requestId, }); logDebug( `Browser Connector: Sending WebSocket message to extension:`, message ); if ( !this.activeConnection || this.activeConnection.readyState !== WebSocket.OPEN ) { throw new Error( "WebSocket connection is not open to send screenshot request" ); } this.activeConnection.send(message); // Wait for screenshot data logDebug("Browser Connector: Waiting for screenshot data..."); const { data: base64Data, path: customPath, autoPaste, } = await screenshotPromise; logDebug( "Browser Connector: Received screenshot data, processing with unified service..." ); if (!base64Data) { throw new Error("No screenshot data received from Chrome extension"); } // Use the unified screenshot service const screenshotService = ScreenshotService.getInstance(); // Prepare configuration for screenshot service // Use project configuration for screenshot path, fallback to customPath if needed const projectScreenshotPath = getScreenshotStoragePath(); // Build config using tool helper (statically imported) const screenshotConfig = buildScreenshotConfig( projectScreenshotPath, customPath, projectName ); // Save screenshot using unified service const result = await screenshotService.saveScreenshot( base64Data, currentUrl, screenshotConfig ); logInfo( `Browser Connector: Screenshot saved successfully to: ${result.filePath}` ); logDebug( `Browser Connector: Project directory: ${result.projectDirectory}` ); logDebug(`Browser Connector: URL category: ${result.urlCategory}`); // Execute auto-paste if requested and on macOS if (os.platform() === "darwin" && autoPaste === true) { logDebug("Browser Connector: Executing auto-paste to Cursor..."); try { await screenshotService.executeAutoPaste(result.filePath); logDebug("Browser Connector: Auto-paste executed successfully"); } catch (autoPasteError) { console.error( "[error] Browser Connector: Auto-paste failed:", autoPasteError ); // Don't fail the screenshot save for auto-paste errors } } else { if (os.platform() === "darwin" && !autoPaste) { logDebug("Browser Connector: Auto-paste disabled, skipping"); } else { logDebug("Browser Connector: Not on macOS, skipping auto-paste"); } } // Build response object via tool helper const response: any = buildScreenshotResponse(result); logInfo("Browser Connector: Screenshot capture completed successfully"); res.json(response); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error( "[error] Browser Connector: Error capturing screenshot:", errorMessage ); res.status(500).json({ error: errorMessage, }); } }
  • ScreenshotService.saveScreenshot: Core saving logic - resolves organized path (project/url-category/timestamp-filename.png), cleans/writes base64 PNG to disk, returns path/filename/projectDirectory/urlCategory/imageData.
    public async saveScreenshot( base64Data: string, url?: string, config: ScreenshotConfig = {} ): Promise<ScreenshotResult> { // Clean base64 data const cleanBase64 = this.cleanBase64Data(base64Data); // Resolve the complete path structure const pathResolution = this.resolveScreenshotPath(url, config); // Ensure directory exists await this.ensureDirectoryExists(path.dirname(pathResolution.fullPath)); // Save the file await this.writeScreenshotFile(pathResolution.fullPath, cleanBase64); // Build result object const result: ScreenshotResult = { filePath: pathResolution.fullPath, filename: pathResolution.filename, projectDirectory: pathResolution.projectDirectory, urlCategory: pathResolution.urlCategory, }; // Include image data if requested if (config.returnImageData) { result.imageData = cleanBase64; } console.log(`Screenshot saved: ${pathResolution.fullPath}`); return result; }
  • buildScreenshotResponse: Pure helper to shape ScreenshotSaveResult into HTTP/WS response format, conditionally includes imageData.
    export function buildScreenshotResponse(result: ScreenshotSaveResult) { const response: any = { filePath: result.filePath, filename: result.filename, projectDirectory: result.projectDirectory, urlCategory: result.urlCategory, }; if (result.imageData) { response.imageData = result.imageData; } return response; }

Other Tools

Related 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/Winds-AI/Frontend-development-MCP-tools-public'

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