Skip to content

Fix/screenshot background handler #187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ Check out our project roadmap here: [Github Roadmap / Project Board](https://git

## Updates

v1.3.0 is out! recent Highlights:

- **Configurable screenshot save path** – the `takeScreenshot` MCP tool now accepts an optional `path` parameter so you can store PNGs anywhere (e.g. `await tools.takeScreenshot({ path: "F:/screens/home.png" })`).
- **Works with DevTools closed** – a background WebSocket handler in the Chrome extension lets you capture screenshots even after you close or undock the BrowserTools panel.
- Misc. merge-conflict clean-ups and stability improvements.

v1.2.0 is out! Here's a quick breakdown of the update:
- You can now enable "Allow Auto-Paste into Cursor" within the DevTools panel. Screenshots will be automatically pasted into Cursor (just make sure to focus/click into the Agent input field in Cursor, otherwise it won't work!)
- Integrated a suite of SEO, performance, accessibility, and best practice analysis tools via Lighthouse
Expand Down
23 changes: 23 additions & 0 deletions browser-tools-mcp-command.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
To use Browser Tools MCP in Cursor, you have two options:

Option 1 (Recommended - Using NPX with cmd):
In Cursor MCP settings, use:
cmd /c npx @agentdeskai/browser-tools-mcp@1.1.0

Make sure the Browser Tools Server is running first with:
cmd /c npx @agentdeskai/browser-tools-server

Option 2 (Using direct paths):
In Cursor MCP settings, use:
"C:\Program Files\nodejs\node.exe" "F:\Projects\MCP-Servers\browser-tools-mcp\browser-tools-mcp\dist\mcp-server.js"

Make sure the Browser Tools Server is running first with:
"C:\Program Files\nodejs\node.exe" "F:\Projects\MCP-Servers\browser-tools-mcp\browser-tools-server\dist\browser-connector.js"

Or use the provided scripts to run both at once:
- run-browser-tools.bat (double-click)
- .\run-browser-tools.ps1 (in PowerShell)

Note: Both scripts now have options to use either NPX or direct paths.
- In run-browser-tools.ps1: Set $useNpx to $true or $false
- In run-browser-tools.bat: Set USE_NPX to 1 or 0
8 changes: 8 additions & 0 deletions browser-tools-mcp.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}
80 changes: 78 additions & 2 deletions browser-tools-mcp/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import path from "path";
import fs from "fs";
import { z } from "zod";
import { spawn, ChildProcess } from "child_process";

// Create the MCP server
const server = new McpServer({
Expand All @@ -16,6 +18,65 @@ let discoveredHost = "127.0.0.1";
let discoveredPort = 3025;
let serverDiscovered = false;

// ======== Connector auto-launch support ========
let connectorProcess: ChildProcess | null = null;

/**
* Ensures the browser-connector process is running. If it is already
* started (or a previous instance is still alive) the call is a no-op.
*
* On Windows/macOS/Linux it spawns: `node <repoRoot>/browser-tools-server/dist/browser-connector.js`
* in detached mode so that it will keep running independently of the MCP
* server STDIO transport handling.
*/
function ensureConnectorRunning() {
try {
if (connectorProcess && !connectorProcess.killed) {
return; // already running
}

// Resolve the connector script path relative to this file's compiled dir
// dist/mcp-server.js -> ../../browser-tools-server/dist/browser-connector.js
const connectorPath = path.resolve(
__dirname,
"..",
"browser-tools-server",
"dist",
"browser-connector.js"
);

if (!fs.existsSync(connectorPath)) {
console.error(
"Browser-tools connector script not found at:",
connectorPath
);
return;
}

console.error("Starting browser-tools connector at", connectorPath);
connectorProcess = spawn(process.execPath, [connectorPath], {
env: { ...process.env },
stdio: "ignore", // change to 'inherit' if you want logs in the same console
detached: true,
});

connectorProcess.unref();

// Make sure we clean up on exit
process.on("exit", () => {
if (connectorProcess && !connectorProcess.killed) {
try {
connectorProcess.kill();
} catch {
/* ignore */
}
}
});
} catch (err) {
console.error("Failed to start browser-tools connector:", err);
}
}

// Function to get the default port from environment variable or default
function getDefaultServerPort(): number {
// Check environment variable first
Expand Down Expand Up @@ -58,6 +119,9 @@ function getDefaultServerHost(): string {
async function discoverServer(): Promise<boolean> {
console.log("Starting server discovery process");

// Try to ensure the connector is running before scanning
ensureConnectorRunning();

// Common hosts to try
const hosts = [getDefaultServerHost(), "127.0.0.1", "localhost"];

Expand Down Expand Up @@ -251,13 +315,25 @@ server.tool("getNetworkLogs", "Check ALL our network logs", async () => {
server.tool(
"takeScreenshot",
"Take a screenshot of the current browser tab",
async () => {
{
path: z
.string()
.optional()
.describe(
"Optional absolute or relative file path where the screenshot should be stored on the connector host."
),
},
async ({ path }) => {
return await withServerConnection(async () => {
try {
const response = await fetch(
`http://${discoveredHost}:${discoveredPort}/capture-screenshot`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ...(path ? { path } : {}) }),
}
);

Expand All @@ -268,7 +344,7 @@ server.tool(
content: [
{
type: "text",
text: "Successfully saved screenshot",
text: `Successfully saved screenshot${result.path ? ` to: ${result.path}` : ""}`,
},
],
};
Expand Down
26 changes: 12 additions & 14 deletions browser-tools-server/browser-connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,11 @@ export class BrowserConnector {
const requestId = Date.now().toString();
console.log("Browser Connector: Generated requestId:", requestId);

const clientPath = req.body?.path;
if (clientPath) {
console.log("Browser Connector: Client provided path:", clientPath);
}

// Create promise that will resolve when we get the screenshot data
const screenshotPromise = new Promise<{
data: string;
Expand Down Expand Up @@ -990,6 +995,7 @@ export class BrowserConnector {
const message = JSON.stringify({
type: "take-screenshot",
requestId: requestId,
...(clientPath ? { path: clientPath } : {}),
});
console.log(
`Browser Connector: Sending WebSocket message to extension:`,
Expand All @@ -1001,26 +1007,18 @@ export class BrowserConnector {
console.log("Browser Connector: Waiting for screenshot data...");
const {
data: base64Data,
path: customPath,
path: customPathFromExt,
autoPaste,
} = await screenshotPromise;
console.log("Browser Connector: Received screenshot data, saving...");
console.log("Browser Connector: Custom path from extension:", customPath);
console.log("Browser Connector: Custom path from extension:", customPathFromExt);
console.log("Browser Connector: Auto-paste setting:", autoPaste);

// Always prioritize the path from the Chrome extension
let targetPath = customPath;

// If no path provided by extension, fall back to defaults
if (!targetPath) {
targetPath =
currentSettings.screenshotPath || getDefaultDownloadsFolder();
}

// Convert the path for the current platform
targetPath = convertPathForCurrentPlatform(targetPath);
// Determine target path prioritizing: extension path > client path > settings/default
let targetPath =
customPathFromExt || clientPath || currentSettings.screenshotPath || getDefaultDownloadsFolder();

console.log(`Browser Connector: Using path: ${targetPath}`);
console.log("Browser Connector: Resolved target path:", targetPath);

if (!base64Data) {
throw new Error("No screenshot data received from Chrome extension");
Expand Down
Loading