From 66b7fe29c731815db593a2e2479c85f61b41af1b Mon Sep 17 00:00:00 2001 From: Pushkar Patel Date: Wed, 5 Mar 2025 15:22:11 +0530 Subject: [PATCH 01/14] add editorconfig for consistent code formatting --- .editorconfig | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8c75cdb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*] +indent_style = space + +[*.{js,ts,jsx,tsx}] +indent_size = 2 +quote_style = double +line_length = 120 From 9ae43f605e206d3d39dd207728562ca1c2309ab6 Mon Sep 17 00:00:00 2001 From: Pushkar Patel Date: Wed, 5 Mar 2025 16:18:09 +0530 Subject: [PATCH 02/14] feat: Enable access to browser storage (cookies, localStorage, sessionStorage) - Add the ability to retrieve and analyze cookies, localStorage, and sessionStorage. - Implement new MCP tools and API endpoints for accessing browser storage data. - Enhance the Chrome extension to capture browser storage. --- README.md | 5 +- browser-tools-mcp/README.md | 5 + browser-tools-mcp/mcp-server.ts | 42 +++++ browser-tools-server/README.md | 7 + browser-tools-server/browser-connector.ts | 203 ++++++++++++++++++++++ chrome-extension/devtools.js | 164 ++++++++++++++++- 6 files changed, 422 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 27cbb28..7858eec 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Read our [docs](https://browsertools.agentdesk.ai/) for the full installation, q There are three core components all used to capture and analyze browser data: -1. **Chrome Extension**: A browser extension that captures screenshots, console logs, network activity and DOM elements. +1. **Chrome Extension**: A browser extension that captures screenshots, console logs, network activity, DOM elements, and browser storage (cookies, localStorage, sessionStorage). 2. **Node Server**: An intermediary server that facilitates communication between the Chrome extension and any instance of an MCP server. 3. **MCP Server**: A Model Context Protocol server that provides standardized tools for AI clients to interact with the browser. @@ -37,6 +37,7 @@ All consumers of the BrowserTools MCP Server interface with the same NodeJS API - Tracks selected DOM elements - Sends all logs and current element to the BrowserTools Connector - Connects to Websocket server to capture/send screenshots +- Retrieves cookies, localStorage, and sessionStorage data - Allows user to configure token/truncation limits + screenshot folder path #### Node Server @@ -44,6 +45,7 @@ All consumers of the BrowserTools MCP Server interface with the same NodeJS API - Acts as middleware between the Chrome extension and MCP server - Receives logs and currently selected element from Chrome extension - Processes requests from MCP server to capture logs, screenshot or current element +- Retrieves browser storage data (cookies, localStorage, sessionStorage) - Sends Websocket command to the Chrome extension for capturing a screenshot - Intelligently truncates strings and # of duplicate objects in logs to avoid token limits - Removes cookies and sensitive headers to avoid sending to LLMs in MCP clients @@ -68,6 +70,7 @@ Once installed and configured, the system allows any compatible MCP client to: - Capture network traffic - Take screenshots - Analyze selected elements +- Access browser storage (cookies, localStorage, sessionStorage) - Wipe logs stored in our MCP server ## Compatibility diff --git a/browser-tools-mcp/README.md b/browser-tools-mcp/README.md index 0a4f258..81ee542 100644 --- a/browser-tools-mcp/README.md +++ b/browser-tools-mcp/README.md @@ -9,6 +9,7 @@ A Model Context Protocol (MCP) server that provides AI-powered browser tools int - Network request analysis - Screenshot capture capabilities - Element selection and inspection +- Browser storage access (cookies, localStorage, sessionStorage) - Real-time browser state monitoring ## Installation @@ -44,6 +45,7 @@ npx @agentdeskai/browser-tools-mcp - Screenshot capture - Element selection - Browser state analysis +- Browser storage access ## MCP Functions @@ -55,6 +57,9 @@ The server provides the following MCP functions: - `mcp_getNetworkSuccess` - Get successful network requests - `mcp_getNetworkLogs` - Get all network logs - `mcp_getSelectedElement` - Get the currently selected DOM element +- `mcp_getCookies` - Get cookies from the current page +- `mcp_getLocalStorage` - Get localStorage data +- `mcp_getSessionStorage` - Get sessionStorage data ## Integration diff --git a/browser-tools-mcp/mcp-server.ts b/browser-tools-mcp/mcp-server.ts index 23a9644..a0efd49 100644 --- a/browser-tools-mcp/mcp-server.ts +++ b/browser-tools-mcp/mcp-server.ts @@ -339,6 +339,48 @@ server.tool("wipeLogs", "Wipe all browser logs from memory", async () => { }); }); +// Add new tool for getting cookies +server.tool("getCookies", "Get all cookies from the browser", async () => { + const response = await fetch(`http://127.0.0.1:${PORT}/cookies`); + const json = await response.json(); + return { + content: [ + { + type: "text", + text: JSON.stringify(json, null, 2), + }, + ], + }; +}); + +// Add new tool for getting localStorage +server.tool("getLocalStorage", "Get all localStorage items", async () => { + const response = await fetch(`http://127.0.0.1:${PORT}/local-storage`); + const json = await response.json(); + return { + content: [ + { + type: "text", + text: JSON.stringify(json, null, 2), + }, + ], + }; +}); + +// Add new tool for getting sessionStorage +server.tool("getSessionStorage", "Get all sessionStorage items", async () => { + const response = await fetch(`http://127.0.0.1:${PORT}/session-storage`); + const json = await response.json(); + return { + content: [ + { + type: "text", + text: JSON.stringify(json, null, 2), + }, + ], + }; +}); + // Start receiving messages on stdio (async () => { try { diff --git a/browser-tools-server/README.md b/browser-tools-server/README.md index a55aeec..13e8154 100644 --- a/browser-tools-server/README.md +++ b/browser-tools-server/README.md @@ -8,6 +8,7 @@ A powerful browser tools server for capturing and managing browser events, logs, - Network request monitoring - Screenshot capture - Element selection tracking +- Browser storage access (cookies, localStorage, sessionStorage) - WebSocket real-time communication - Configurable log limits and settings @@ -44,6 +45,9 @@ npx @agentdeskai/browser-tools-server - `/all-xhr` - Get all network requests - `/screenshot` - Capture screenshots - `/selected-element` - Get currently selected DOM element +- `/cookies` - Get cookies from the current page +- `/local-storage` - Get localStorage data +- `/session-storage` - Get sessionStorage data ## API Documentation @@ -55,6 +59,9 @@ npx @agentdeskai/browser-tools-server - `GET /network-success` - Returns recent successful network requests - `GET /all-xhr` - Returns all recent network requests - `GET /selected-element` - Returns the currently selected DOM element +- `GET /cookies` - Returns cookies from the current page +- `GET /local-storage` - Returns localStorage data +- `GET /session-storage` - Returns sessionStorage data ### POST Endpoints diff --git a/browser-tools-server/browser-connector.ts b/browser-tools-server/browser-connector.ts index e30a109..af3883c 100644 --- a/browser-tools-server/browser-connector.ts +++ b/browser-tools-server/browser-connector.ts @@ -596,6 +596,24 @@ export class BrowserConnector { ); screenshotCallbacks.clear(); // Clear all callbacks } + } + // Handle cookies data + else if (data.type === "cookies-data") { + console.log("Received cookies data from extension"); + // Store the cookies data temporarily + this.lastCookiesData = data.cookies; + } + // Handle localStorage data + else if (data.type === "local-storage-data") { + console.log("Received localStorage data from extension"); + // Store the localStorage data temporarily + this.lastLocalStorageData = data.storage; + } + // Handle sessionStorage data + else if (data.type === "session-storage-data") { + console.log("Received sessionStorage data from extension"); + // Store the sessionStorage data temporarily + this.lastSessionStorageData = data.storage; } else { console.log("Unhandled message type:", data.type); } @@ -665,6 +683,191 @@ export class BrowserConnector { } } ); + + // Add endpoint for cookies + this.app.get( + "/cookies", + (req: express.Request, res: express.Response): void => { + console.log("Browser Connector: Received request to /cookies endpoint"); + + if (!this.activeConnection) { + console.log("Browser Connector: No active WebSocket connection to Chrome extension"); + res.status(503).json({ error: "Chrome extension not connected" }); + return; + } + + // Request cookies data from extension + this.getCookies() + .then((cookiesData) => { + console.log("Browser Connector: Received cookies data from extension"); + res.json(cookiesData); + }) + .catch((error) => { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error("Browser Connector: Error getting cookies:", errorMessage); + res.status(500).json({ error: errorMessage }); + }); + } + ); + + // Add endpoint for localStorage + this.app.get( + "/local-storage", + (req: express.Request, res: express.Response): void => { + console.log("Browser Connector: Received request to /local-storage endpoint"); + + if (!this.activeConnection) { + console.log("Browser Connector: No active WebSocket connection to Chrome extension"); + res.status(503).json({ error: "Chrome extension not connected" }); + return; + } + + // Request localStorage data from extension + this.getLocalStorage() + .then((storageData) => { + console.log("Browser Connector: Received localStorage data from extension"); + res.json(storageData); + }) + .catch((error) => { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error("Browser Connector: Error getting localStorage:", errorMessage); + res.status(500).json({ error: errorMessage }); + }); + } + ); + + // Add endpoint for sessionStorage + this.app.get( + "/session-storage", + (req: express.Request, res: express.Response): void => { + console.log("Browser Connector: Received request to /session-storage endpoint"); + + if (!this.activeConnection) { + console.log("Browser Connector: No active WebSocket connection to Chrome extension"); + res.status(503).json({ error: "Chrome extension not connected" }); + return; + } + + // Request sessionStorage data from extension + this.getSessionStorage() + .then((storageData) => { + console.log("Browser Connector: Received sessionStorage data from extension"); + res.json(storageData); + }) + .catch((error) => { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error("Browser Connector: Error getting sessionStorage:", errorMessage); + res.status(500).json({ error: errorMessage }); + }); + } + ); + } + + // Add properties to store the latest data + private lastCookiesData: any = null; + private lastLocalStorageData: any = null; + private lastSessionStorageData: any = null; + + // Add method to get cookies + private getCookies(): Promise { + if (!this.activeConnection) { + throw new Error("Chrome extension not connected"); + } + + // Reset the stored data + this.lastCookiesData = null; + + // Create a promise that will resolve when we get the cookies data + const cookiesPromise = new Promise((resolve, reject) => { + // Set timeout to reject if we don't get a response + const timeout = setTimeout(() => { + reject(new Error("Timeout waiting for cookies data from Chrome extension")); + }, 5000); + + // Set up an interval to check for data + const checkInterval = setInterval(() => { + if (this.lastCookiesData) { + clearTimeout(timeout); + clearInterval(checkInterval); + resolve(this.lastCookiesData); + } + }, 100); + }); + + // Send request to extension + console.log("Browser Connector: Requesting cookies data from extension"); + this.activeConnection.send(JSON.stringify({ type: "get-cookies" })); + + // Wait for data + return cookiesPromise; + } + + // Add method to get localStorage + private getLocalStorage(): Promise { + if (!this.activeConnection) { + throw new Error("Chrome extension not connected"); + } + + // Reset the stored data + this.lastLocalStorageData = null; + + // Create a promise that will resolve when we get the localStorage data + const storagePromise = new Promise((resolve, reject) => { + // Set timeout to reject if we don't get a response + const timeout = setTimeout(() => { + reject(new Error("Timeout waiting for localStorage data from Chrome extension")); + }, 5000); + + // Set up an interval to check for data + const checkInterval = setInterval(() => { + if (this.lastLocalStorageData) { + clearTimeout(timeout); + clearInterval(checkInterval); + resolve(this.lastLocalStorageData); + } + }, 100); + }); + + // Send request to extension + console.log("Browser Connector: Requesting localStorage data from extension"); + this.activeConnection.send(JSON.stringify({ type: "get-local-storage" })); + + // Wait for data + return storagePromise; + } + + // Add method to get sessionStorage + private getSessionStorage(): Promise { + if (!this.activeConnection) { + throw new Error("Chrome extension not connected"); + } + + // Reset the stored data + this.lastSessionStorageData = null; + + // Create a promise that will resolve when we get the sessionStorage data + const storagePromise = new Promise((resolve, reject) => { + // Set timeout to reject if we don't get a response + const timeout = setTimeout(() => { + reject(new Error("Timeout waiting for sessionStorage data from Chrome extension")); + }, 5000); + + // Set up an interval to check for data + const checkInterval = setInterval(() => { + if (this.lastSessionStorageData) { + clearTimeout(timeout); + clearInterval(checkInterval); + resolve(this.lastSessionStorageData); + } + }, 100); + }); + + // Send request to extension + console.log("Browser Connector: Requesting sessionStorage data from extension"); + this.activeConnection.send(JSON.stringify({ type: "get-session-storage" })); + + // Wait for data + return storagePromise; } private async handleScreenshot(req: express.Request, res: express.Response) { diff --git a/chrome-extension/devtools.js b/chrome-extension/devtools.js index 9aa3f4a..4a6b1e0 100644 --- a/chrome-extension/devtools.js +++ b/chrome-extension/devtools.js @@ -752,9 +752,167 @@ async function setupWebSocket() { // Set flag to indicate this is an intentional closure intentionalClosure = true; try { - ws.close(); - } catch (e) { - console.error("Error closing existing WebSocket:", e); + const message = JSON.parse(event.data); + console.log("Chrome Extension: Received WebSocket message:", message); + + if (message.type === "take-screenshot") { + console.log("Chrome Extension: Taking screenshot..."); + // Capture screenshot of the current tab + chrome.tabs.captureVisibleTab(null, { format: "png" }, (dataUrl) => { + if (chrome.runtime.lastError) { + console.error( + "Chrome Extension: Screenshot capture failed:", + chrome.runtime.lastError + ); + ws.send( + JSON.stringify({ + type: "screenshot-error", + error: chrome.runtime.lastError.message, + requestId: message.requestId, + }) + ); + return; + } + + console.log("Chrome Extension: Screenshot captured successfully"); + // Just send the screenshot data, let the server handle paths + const response = { + type: "screenshot-data", + data: dataUrl, + requestId: message.requestId, + // Only include path if it's configured in settings + ...(settings.screenshotPath && { path: settings.screenshotPath }), + }; + + console.log("Chrome Extension: Sending screenshot data response", { + ...response, + data: "[base64 data]", + }); + + ws.send(JSON.stringify(response)); + }); + } else if (message.type === "get-cookies") { + console.log("Chrome Extension: Getting cookies..."); + // Get cookies from the current tab + chrome.devtools.inspectedWindow.eval( + `(function() { + // Check if document.cookie is empty + if (!document.cookie.trim()) { + return []; + } + + // Split the cookie string and filter out any empty entries + return document.cookie.split(';') + .map(cookie => cookie.trim()) + .filter(cookie => cookie) // Remove empty strings + .map(cookie => { + const equalsPos = cookie.indexOf('='); + // Handle cookies with no value (name only) + if (equalsPos === -1) { + return { name: cookie, value: '' }; + } + // Handle normal cookies with name=value + const name = cookie.substring(0, equalsPos); + const value = cookie.substring(equalsPos + 1); + return { name, value }; + }); + })()`, + (result, isException) => { + if (isException || !result) { + console.error("Chrome Extension: Error getting cookies:", isException); + ws.send( + JSON.stringify({ + type: "cookies-data", + cookies: [], + error: isException || "Failed to get cookies" + }) + ); + return; + } + + console.log("Chrome Extension: Cookies retrieved successfully:", result); + ws.send( + JSON.stringify({ + type: "cookies-data", + cookies: result + }) + ); + } + ); + } else if (message.type === "get-local-storage") { + console.log("Chrome Extension: Getting localStorage..."); + // Get localStorage from the current tab + chrome.devtools.inspectedWindow.eval( + `(function() { + const storage = {}; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + storage[key] = localStorage.getItem(key); + } + return storage; + })()`, + (result, isException) => { + if (isException || !result) { + console.error("Chrome Extension: Error getting localStorage:", isException); + ws.send( + JSON.stringify({ + type: "local-storage-data", + storage: {}, + error: isException || "Failed to get localStorage" + }) + ); + return; + } + + console.log("Chrome Extension: localStorage retrieved successfully:", result); + ws.send( + JSON.stringify({ + type: "local-storage-data", + storage: result + }) + ); + } + ); + } else if (message.type === "get-session-storage") { + console.log("Chrome Extension: Getting sessionStorage..."); + // Get sessionStorage from the current tab + chrome.devtools.inspectedWindow.eval( + `(function() { + const storage = {}; + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i); + storage[key] = sessionStorage.getItem(key); + } + return storage; + })()`, + (result, isException) => { + if (isException || !result) { + console.error("Chrome Extension: Error getting sessionStorage:", isException); + ws.send( + JSON.stringify({ + type: "session-storage-data", + storage: {}, + error: isException || "Failed to get sessionStorage" + }) + ); + return; + } + + console.log("Chrome Extension: sessionStorage retrieved successfully:", result); + ws.send( + JSON.stringify({ + type: "session-storage-data", + storage: result + }) + ); + } + ); + } + } catch (error) { + console.error( + "Chrome Extension: Error processing WebSocket message:", + error + ); } ws = null; intentionalClosure = false; // Reset flag From 647927e81bfcfe6bd8a8b03da7deba0a4183f3f9 Mon Sep 17 00:00:00 2001 From: Pushkar Patel Date: Sun, 9 Mar 2025 23:55:08 +0530 Subject: [PATCH 03/14] fix: update codebase to be more consistent with staging branch --- .DS_Store | Bin 6148 -> 0 bytes browser-tools-mcp/mcp-server.ts | 198 ++++++-- browser-tools-server/browser-connector.ts | 552 ++++++++++++++-------- chrome-extension/devtools.js | 255 +++++----- chrome-extension/manifest.json | 37 +- 5 files changed, 683 insertions(+), 359 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index b1f5fe00eaf0f1fa2bdb0b5dfe375dc90c3d28f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK&2G~`5S~o~b($ijQjvN=vc!Q)5>lWMh)W3RfdiKY!2wXPIQaA?Izba9S-#OoF)&o7rdD#7>2R+wQ zo^j64$DE%ERxng696`H=@&+ql9;OsxopIJ)T>D*ne?A=>CO-M&D4ZsZ#%EF3C|^8U>nz9%ivNYMj%3)0=20yR}7)e(eE0)mcgY)ZBD{mK7@I* zFgFyT-VXn+QYX>UXrGn=%RrWa4c)Bp`G2_n{+~6nEz5vq;GbeZ6g%Bc8%r{0>&oKz wtaVXdp|G&tQlm1#!yLz|!bkBQiV}>uTmiNWE;XVDV*dyz8tlU|@JAW=25`3n`2YX_ diff --git a/browser-tools-mcp/mcp-server.ts b/browser-tools-mcp/mcp-server.ts index a0efd49..051b5b3 100644 --- a/browser-tools-mcp/mcp-server.ts +++ b/browser-tools-mcp/mcp-server.ts @@ -341,44 +341,182 @@ server.tool("wipeLogs", "Wipe all browser logs from memory", async () => { // Add new tool for getting cookies server.tool("getCookies", "Get all cookies from the browser", async () => { - const response = await fetch(`http://127.0.0.1:${PORT}/cookies`); - const json = await response.json(); - return { - content: [ - { - type: "text", - text: JSON.stringify(json, null, 2), - }, - ], - }; + return await withServerConnection(async () => { + try { + const response = await fetch( + `http://${discoveredHost}:${discoveredPort}/cookies` + ); + + if (!response.ok) { + const errorData = await response.json(); + return { + content: [ + { + type: "text", + text: `Error getting cookies: ${ + errorData.error || response.statusText + }`, + }, + ], + isError: true, + }; + } + + const json = await response.json(); + + if (json.error) { + return { + content: [ + { + type: "text", + text: `Error getting cookies: ${json.error}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text", + text: JSON.stringify(json, null, 2), + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text", + text: `Error getting cookies: ${message}`, + }, + ], + isError: true, + }; + } + }); }); // Add new tool for getting localStorage server.tool("getLocalStorage", "Get all localStorage items", async () => { - const response = await fetch(`http://127.0.0.1:${PORT}/local-storage`); - const json = await response.json(); - return { - content: [ - { - type: "text", - text: JSON.stringify(json, null, 2), - }, - ], - }; + return await withServerConnection(async () => { + try { + const response = await fetch( + `http://${discoveredHost}:${discoveredPort}/local-storage` + ); + + if (!response.ok) { + const errorData = await response.json(); + return { + content: [ + { + type: "text", + text: `Error getting localStorage: ${ + errorData.error || response.statusText + }`, + }, + ], + isError: true, + }; + } + + const json = await response.json(); + + if (json.error) { + return { + content: [ + { + type: "text", + text: `Error getting localStorage: ${json.error}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text", + text: JSON.stringify(json, null, 2), + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text", + text: `Error getting localStorage: ${message}`, + }, + ], + isError: true, + }; + } + }); }); // Add new tool for getting sessionStorage server.tool("getSessionStorage", "Get all sessionStorage items", async () => { - const response = await fetch(`http://127.0.0.1:${PORT}/session-storage`); - const json = await response.json(); - return { - content: [ - { - type: "text", - text: JSON.stringify(json, null, 2), - }, - ], - }; + return await withServerConnection(async () => { + try { + const response = await fetch( + `http://${discoveredHost}:${discoveredPort}/session-storage` + ); + + if (!response.ok) { + const errorData = await response.json(); + return { + content: [ + { + type: "text", + text: `Error getting sessionStorage: ${ + errorData.error || response.statusText + }`, + }, + ], + isError: true, + }; + } + + const json = await response.json(); + + if (json.error) { + return { + content: [ + { + type: "text", + text: `Error getting sessionStorage: ${json.error}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text", + text: JSON.stringify(json, null, 2), + }, + ], + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text", + text: `Error getting sessionStorage: ${message}`, + }, + ], + isError: true, + }; + } + }); }); // Start receiving messages on stdio diff --git a/browser-tools-server/browser-connector.ts b/browser-tools-server/browser-connector.ts index af3883c..4975517 100644 --- a/browser-tools-server/browser-connector.ts +++ b/browser-tools-server/browser-connector.ts @@ -159,7 +159,25 @@ interface ScreenshotCallback { reject: (reason: Error) => void; } +interface CookiesCallback { + resolve: (value: { cookies: any[] }) => void; + reject: (reason: Error) => void; +} + +interface LocalStorageCallback { + resolve: (value: { storage: any }) => void; + reject: (reason: Error) => void; +} + +interface SessionStorageCallback { + resolve: (value: { storage: any }) => void; + reject: (reason: Error) => void; +} + const screenshotCallbacks = new Map(); +const cookiesCallbacks = new Map(); +const localStorageCallbacks = new Map(); +const sessionStorageCallbacks = new Map(); // Function to get available port starting with the given port async function getAvailablePort( @@ -544,6 +562,49 @@ export class BrowserConnector { } ); + // Add endpoint for cookies + this.app.get( + "/cookies", + async (req: express.Request, res: express.Response): Promise => { + console.log("Browser Connector: Received request to /cookies endpoint"); + console.log( + "Browser Connector: Active WebSocket connection:", + !!this.activeConnection + ); + await this.getCookies(req, res); + } + ); + + // Add endpoint for localStorage + this.app.get( + "/local-storage", + async (req: express.Request, res: express.Response): Promise => { + console.log( + "Browser Connector: Received request to /local-storage endpoint" + ); + console.log( + "Browser Connector: Active WebSocket connection:", + !!this.activeConnection + ); + await this.getLocalStorage(req, res); + } + ); + + // Add endpoint for sessionStorage + this.app.get( + "/session-storage", + async (req: express.Request, res: express.Response): Promise => { + console.log( + "Browser Connector: Received request to /session-storage endpoint" + ); + console.log( + "Browser Connector: Active WebSocket connection:", + !!this.activeConnection + ); + await this.getSessionStorage(req, res); + } + ); + // Handle upgrade requests for WebSocket this.server.on( "upgrade", @@ -595,25 +656,91 @@ export class BrowserConnector { new Error(data.error || "Screenshot capture failed") ); screenshotCallbacks.clear(); // Clear all callbacks + } else { + console.log("No callbacks found for screenshot"); } } // Handle cookies data - else if (data.type === "cookies-data") { + else if (data.type === "cookies-data" && data.cookies) { console.log("Received cookies data from extension"); - // Store the cookies data temporarily - this.lastCookiesData = data.cookies; + const callbacks = Array.from(cookiesCallbacks.values()); + if (callbacks.length > 0) { + const callback = callbacks[0]; + callback.resolve({ cookies: data.cookies }); + cookiesCallbacks.clear(); // Clear all callbacks + } + } + // Handle cookies error + else if (data.type === "cookies-error") { + console.log("Received cookies error from extension: ", data.error); + const callbacks = Array.from(cookiesCallbacks.values()); + if (callbacks.length > 0) { + const callback = callbacks[0]; + callback.reject( + new Error(data.error || "Cookies request failed") + ); + cookiesCallbacks.clear(); // Clear all callbacks + } else { + console.log("No callbacks found for cookies"); + } } // Handle localStorage data - else if (data.type === "local-storage-data") { + else if (data.type === "local-storage-data" && data.storage) { console.log("Received localStorage data from extension"); - // Store the localStorage data temporarily - this.lastLocalStorageData = data.storage; + const callbacks = Array.from(localStorageCallbacks.values()); + if (callbacks.length > 0) { + const callback = callbacks[0]; + callback.resolve({ storage: data.storage }); + localStorageCallbacks.clear(); // Clear all callbacks + } else { + console.log("No callbacks found for localStorage"); + } + } + // Handle localStorage error + else if (data.type === "local-storage-error") { + console.log( + "Received localStorage error from extension: ", + data.error + ); + const callbacks = Array.from(localStorageCallbacks.values()); + if (callbacks.length > 0) { + const callback = callbacks[0]; + callback.reject( + new Error(data.error || "LocalStorage request failed") + ); + localStorageCallbacks.clear(); // Clear all callbacks + } else { + console.log("No callbacks found for localStorage"); + } } // Handle sessionStorage data - else if (data.type === "session-storage-data") { + else if (data.type === "session-storage-data" && data.storage) { console.log("Received sessionStorage data from extension"); - // Store the sessionStorage data temporarily - this.lastSessionStorageData = data.storage; + const callbacks = Array.from(sessionStorageCallbacks.values()); + if (callbacks.length > 0) { + const callback = callbacks[0]; + callback.resolve({ storage: data.storage }); + sessionStorageCallbacks.clear(); // Clear all callbacks + } else { + console.log("No callbacks found for sessionStorage"); + } + } + // Handle sessionStorage error + else if (data.type === "session-storage-error") { + console.log( + "Received sessionStorage error from extension: ", + data.error + ); + const callbacks = Array.from(sessionStorageCallbacks.values()); + if (callbacks.length > 0) { + const callback = callbacks[0]; + callback.reject( + new Error(data.error || "SessionStorage request failed") + ); + sessionStorageCallbacks.clear(); // Clear all callbacks + } else { + console.log("No callbacks found for sessionStorage"); + } } else { console.log("Unhandled message type:", data.type); } @@ -683,191 +810,6 @@ export class BrowserConnector { } } ); - - // Add endpoint for cookies - this.app.get( - "/cookies", - (req: express.Request, res: express.Response): void => { - console.log("Browser Connector: Received request to /cookies endpoint"); - - if (!this.activeConnection) { - console.log("Browser Connector: No active WebSocket connection to Chrome extension"); - res.status(503).json({ error: "Chrome extension not connected" }); - return; - } - - // Request cookies data from extension - this.getCookies() - .then((cookiesData) => { - console.log("Browser Connector: Received cookies data from extension"); - res.json(cookiesData); - }) - .catch((error) => { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("Browser Connector: Error getting cookies:", errorMessage); - res.status(500).json({ error: errorMessage }); - }); - } - ); - - // Add endpoint for localStorage - this.app.get( - "/local-storage", - (req: express.Request, res: express.Response): void => { - console.log("Browser Connector: Received request to /local-storage endpoint"); - - if (!this.activeConnection) { - console.log("Browser Connector: No active WebSocket connection to Chrome extension"); - res.status(503).json({ error: "Chrome extension not connected" }); - return; - } - - // Request localStorage data from extension - this.getLocalStorage() - .then((storageData) => { - console.log("Browser Connector: Received localStorage data from extension"); - res.json(storageData); - }) - .catch((error) => { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("Browser Connector: Error getting localStorage:", errorMessage); - res.status(500).json({ error: errorMessage }); - }); - } - ); - - // Add endpoint for sessionStorage - this.app.get( - "/session-storage", - (req: express.Request, res: express.Response): void => { - console.log("Browser Connector: Received request to /session-storage endpoint"); - - if (!this.activeConnection) { - console.log("Browser Connector: No active WebSocket connection to Chrome extension"); - res.status(503).json({ error: "Chrome extension not connected" }); - return; - } - - // Request sessionStorage data from extension - this.getSessionStorage() - .then((storageData) => { - console.log("Browser Connector: Received sessionStorage data from extension"); - res.json(storageData); - }) - .catch((error) => { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error("Browser Connector: Error getting sessionStorage:", errorMessage); - res.status(500).json({ error: errorMessage }); - }); - } - ); - } - - // Add properties to store the latest data - private lastCookiesData: any = null; - private lastLocalStorageData: any = null; - private lastSessionStorageData: any = null; - - // Add method to get cookies - private getCookies(): Promise { - if (!this.activeConnection) { - throw new Error("Chrome extension not connected"); - } - - // Reset the stored data - this.lastCookiesData = null; - - // Create a promise that will resolve when we get the cookies data - const cookiesPromise = new Promise((resolve, reject) => { - // Set timeout to reject if we don't get a response - const timeout = setTimeout(() => { - reject(new Error("Timeout waiting for cookies data from Chrome extension")); - }, 5000); - - // Set up an interval to check for data - const checkInterval = setInterval(() => { - if (this.lastCookiesData) { - clearTimeout(timeout); - clearInterval(checkInterval); - resolve(this.lastCookiesData); - } - }, 100); - }); - - // Send request to extension - console.log("Browser Connector: Requesting cookies data from extension"); - this.activeConnection.send(JSON.stringify({ type: "get-cookies" })); - - // Wait for data - return cookiesPromise; - } - - // Add method to get localStorage - private getLocalStorage(): Promise { - if (!this.activeConnection) { - throw new Error("Chrome extension not connected"); - } - - // Reset the stored data - this.lastLocalStorageData = null; - - // Create a promise that will resolve when we get the localStorage data - const storagePromise = new Promise((resolve, reject) => { - // Set timeout to reject if we don't get a response - const timeout = setTimeout(() => { - reject(new Error("Timeout waiting for localStorage data from Chrome extension")); - }, 5000); - - // Set up an interval to check for data - const checkInterval = setInterval(() => { - if (this.lastLocalStorageData) { - clearTimeout(timeout); - clearInterval(checkInterval); - resolve(this.lastLocalStorageData); - } - }, 100); - }); - - // Send request to extension - console.log("Browser Connector: Requesting localStorage data from extension"); - this.activeConnection.send(JSON.stringify({ type: "get-local-storage" })); - - // Wait for data - return storagePromise; - } - - // Add method to get sessionStorage - private getSessionStorage(): Promise { - if (!this.activeConnection) { - throw new Error("Chrome extension not connected"); - } - - // Reset the stored data - this.lastSessionStorageData = null; - - // Create a promise that will resolve when we get the sessionStorage data - const storagePromise = new Promise((resolve, reject) => { - // Set timeout to reject if we don't get a response - const timeout = setTimeout(() => { - reject(new Error("Timeout waiting for sessionStorage data from Chrome extension")); - }, 5000); - - // Set up an interval to check for data - const checkInterval = setInterval(() => { - if (this.lastSessionStorageData) { - clearTimeout(timeout); - clearInterval(checkInterval); - resolve(this.lastSessionStorageData); - } - }, 100); - }); - - // Send request to extension - console.log("Browser Connector: Requesting sessionStorage data from extension"); - this.activeConnection.send(JSON.stringify({ type: "get-session-storage" })); - - // Wait for data - return storagePromise; } private async handleScreenshot(req: express.Request, res: express.Response) { @@ -1084,6 +1026,228 @@ export class BrowserConnector { }); } } + + // Add method to get cookies + async getCookies(req: express.Request, res: express.Response) { + if (!this.activeConnection) { + console.log( + "Browser Connector: No active WebSocket connection to Chrome extension" + ); + return res.status(503).json({ error: "Chrome extension not connected" }); + } + + try { + console.log("Browser Connector: Getting cookies"); + const requestId = Date.now().toString(); + console.log("Browser Connector: Generated requestId:", requestId); + + // Create promise that will resolve when we get the cookies data + const cookiesPromise = new Promise<{ cookies: any[] }>( + (resolve, reject) => { + console.log( + `Browser Connector: Setting up cookies callback for requestId: ${requestId}` + ); + // Store callback in map + cookiesCallbacks.set(requestId, { resolve, reject }); + console.log( + "Browser Connector: Current callbacks:", + Array.from(cookiesCallbacks.keys()) + ); + + // Set timeout to clean up if we don't get a response + setTimeout(() => { + if (cookiesCallbacks.has(requestId)) { + console.log( + `Browser Connector: Cookies request timed out for requestId: ${requestId}` + ); + cookiesCallbacks.delete(requestId); + reject( + new Error( + "Cookies request timed out - no response from Chrome extension" + ) + ); + } + }, 10000); + } + ); + + // Send cookies request to extension + const message = JSON.stringify({ + type: "get-cookies", + requestId: requestId, + }); + console.log( + `Browser Connector: Sending WebSocket message to extension:`, + message + ); + this.activeConnection.send(message); + + // Wait for cookies data + console.log("Browser Connector: Waiting for cookies data..."); + const result = await cookiesPromise; + console.log( + "Browser Connector: Received cookies data, returning response..." + ); + + // Return the cookies data + res.json(result.cookies); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + console.error("Browser Connector: Error getting cookies:", errorMessage); + return res.status(500).json({ error: errorMessage }); + } + } + + // Add method to get localStorage + async getLocalStorage(req: express.Request, res: express.Response) { + if (!this.activeConnection) { + console.log( + "Browser Connector: No active WebSocket connection to Chrome extension" + ); + return res.status(503).json({ error: "Chrome extension not connected" }); + } + + try { + console.log("Browser Connector: Getting localStorage"); + const requestId = Date.now().toString(); + console.log("Browser Connector: Generated requestId:", requestId); + + // Create promise that will resolve when we get the localStorage data + const localStoragePromise = new Promise<{ storage: any }>( + (resolve, reject) => { + console.log( + `Browser Connector: Setting up localStorage callback for requestId: ${requestId}` + ); + // Store callback in map + localStorageCallbacks.set(requestId, { resolve, reject }); + console.log( + "Browser Connector: Current callbacks:", + Array.from(localStorageCallbacks.keys()) + ); + + // Set timeout to clean up if we don't get a response + setTimeout(() => { + if (localStorageCallbacks.has(requestId)) { + console.log( + `Browser Connector: LocalStorage request timed out for requestId: ${requestId}` + ); + localStorageCallbacks.delete(requestId); + reject( + new Error( + "LocalStorage request timed out - no response from Chrome extension" + ) + ); + } + }, 10000); + } + ); + + // Send localStorage request to extension + const message = JSON.stringify({ + type: "get-local-storage", + requestId: requestId, + }); + console.log( + `Browser Connector: Sending WebSocket message to extension:`, + message + ); + this.activeConnection.send(message); + + // Wait for localStorage data + console.log("Browser Connector: Waiting for localStorage data..."); + const { storage } = await localStoragePromise; + console.log( + "Browser Connector: Received localStorage data, returning response..." + ); + + // Return the localStorage data + res.json(storage); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + console.error( + "Browser Connector: Error getting localStorage:", + errorMessage + ); + return res.status(500).json({ error: errorMessage }); + } + } + + // Add method to get sessionStorage + async getSessionStorage(req: express.Request, res: express.Response) { + if (!this.activeConnection) { + console.log( + "Browser Connector: No active WebSocket connection to Chrome extension" + ); + return res.status(503).json({ error: "Chrome extension not connected" }); + } + + try { + console.log("Browser Connector: Getting sessionStorage"); + const requestId = Date.now().toString(); + console.log("Browser Connector: Generated requestId:", requestId); + + // Create promise that will resolve when we get the sessionStorage data + const sessionStoragePromise = new Promise<{ storage: any }>( + (resolve, reject) => { + console.log( + `Browser Connector: Setting up sessionStorage callback for requestId: ${requestId}` + ); + // Store callback in map + sessionStorageCallbacks.set(requestId, { resolve, reject }); + console.log( + "Browser Connector: Current callbacks:", + Array.from(sessionStorageCallbacks.keys()) + ); + + // Set timeout to clean up if we don't get a response + setTimeout(() => { + if (sessionStorageCallbacks.has(requestId)) { + console.log( + `Browser Connector: SessionStorage request timed out for requestId: ${requestId}` + ); + sessionStorageCallbacks.delete(requestId); + reject( + new Error( + "SessionStorage request timed out - no response from Chrome extension" + ) + ); + } + }, 10000); + } + ); + + // Send sessionStorage request to extension + const message = JSON.stringify({ + type: "get-session-storage", + requestId: requestId, + }); + console.log( + `Browser Connector: Sending WebSocket message to extension:`, + message + ); + this.activeConnection.send(message); + + // Wait for sessionStorage data + console.log("Browser Connector: Waiting for sessionStorage data..."); + const { storage } = await sessionStoragePromise; + console.log( + "Browser Connector: Received sessionStorage data, returning response..." + ); + + // Return the sessionStorage data + res.json(storage); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + console.error( + "Browser Connector: Error getting sessionStorage:", + errorMessage + ); + return res.status(500).json({ error: errorMessage }); + } + } } // Use an async IIFE to allow for async/await in the initial setup diff --git a/chrome-extension/devtools.js b/chrome-extension/devtools.js index 4a6b1e0..f23acf5 100644 --- a/chrome-extension/devtools.js +++ b/chrome-extension/devtools.js @@ -791,122 +791,6 @@ async function setupWebSocket() { ws.send(JSON.stringify(response)); }); - } else if (message.type === "get-cookies") { - console.log("Chrome Extension: Getting cookies..."); - // Get cookies from the current tab - chrome.devtools.inspectedWindow.eval( - `(function() { - // Check if document.cookie is empty - if (!document.cookie.trim()) { - return []; - } - - // Split the cookie string and filter out any empty entries - return document.cookie.split(';') - .map(cookie => cookie.trim()) - .filter(cookie => cookie) // Remove empty strings - .map(cookie => { - const equalsPos = cookie.indexOf('='); - // Handle cookies with no value (name only) - if (equalsPos === -1) { - return { name: cookie, value: '' }; - } - // Handle normal cookies with name=value - const name = cookie.substring(0, equalsPos); - const value = cookie.substring(equalsPos + 1); - return { name, value }; - }); - })()`, - (result, isException) => { - if (isException || !result) { - console.error("Chrome Extension: Error getting cookies:", isException); - ws.send( - JSON.stringify({ - type: "cookies-data", - cookies: [], - error: isException || "Failed to get cookies" - }) - ); - return; - } - - console.log("Chrome Extension: Cookies retrieved successfully:", result); - ws.send( - JSON.stringify({ - type: "cookies-data", - cookies: result - }) - ); - } - ); - } else if (message.type === "get-local-storage") { - console.log("Chrome Extension: Getting localStorage..."); - // Get localStorage from the current tab - chrome.devtools.inspectedWindow.eval( - `(function() { - const storage = {}; - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - storage[key] = localStorage.getItem(key); - } - return storage; - })()`, - (result, isException) => { - if (isException || !result) { - console.error("Chrome Extension: Error getting localStorage:", isException); - ws.send( - JSON.stringify({ - type: "local-storage-data", - storage: {}, - error: isException || "Failed to get localStorage" - }) - ); - return; - } - - console.log("Chrome Extension: localStorage retrieved successfully:", result); - ws.send( - JSON.stringify({ - type: "local-storage-data", - storage: result - }) - ); - } - ); - } else if (message.type === "get-session-storage") { - console.log("Chrome Extension: Getting sessionStorage..."); - // Get sessionStorage from the current tab - chrome.devtools.inspectedWindow.eval( - `(function() { - const storage = {}; - for (let i = 0; i < sessionStorage.length; i++) { - const key = sessionStorage.key(i); - storage[key] = sessionStorage.getItem(key); - } - return storage; - })()`, - (result, isException) => { - if (isException || !result) { - console.error("Chrome Extension: Error getting sessionStorage:", isException); - ws.send( - JSON.stringify({ - type: "session-storage-data", - storage: {}, - error: isException || "Failed to get sessionStorage" - }) - ); - return; - } - - console.log("Chrome Extension: sessionStorage retrieved successfully:", result); - ws.send( - JSON.stringify({ - type: "session-storage-data", - storage: result - }) - ); - } - ); } } catch (error) { console.error( @@ -1055,6 +939,145 @@ async function setupWebSocket() { ws.send(JSON.stringify(response)); }); + } else if (message.type === "get-cookies") { + console.log("Chrome Extension: Getting cookies..."); + // Get cookies from the current tab + chrome.devtools.inspectedWindow.eval( + `(function() { + // Check if document.cookie is empty + if (!document.cookie.trim()) { + return []; + } + + // Split the cookie string and filter out any empty entries + return document.cookie.split(';') + .map(cookie => cookie.trim()) + .filter(cookie => cookie) // Remove empty strings + .map(cookie => { + const equalsPos = cookie.indexOf('='); + // Handle cookies with no value (name only) + if (equalsPos === -1) { + return { name: cookie, value: '' }; + } + // Handle normal cookies with name=value + const name = cookie.substring(0, equalsPos); + const value = cookie.substring(equalsPos + 1); + return { name, value }; + }); + })()`, + (result, isException) => { + if (isException || !result) { + console.error( + "Chrome Extension: Error getting cookies:", + isException + ); + ws.send( + JSON.stringify({ + type: "cookies-error", + error: isException || "Failed to get cookies", + requestId: message.requestId, + }) + ); + return; + } + + console.log( + "Chrome Extension: Cookies retrieved successfully:", + result + ); + // Make sure cookies is an array, even if empty + const cookies = Array.isArray(result) ? result : []; + ws.send( + JSON.stringify({ + type: "cookies-data", + cookies: cookies, + requestId: message.requestId, + }) + ); + } + ); + } else if (message.type === "get-local-storage") { + console.log("Chrome Extension: Getting localStorage..."); + // Get localStorage from the current tab + chrome.devtools.inspectedWindow.eval( + `(function() { + const storage = {}; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + storage[key] = localStorage.getItem(key); + } + return storage; + })()`, + (result, isException) => { + if (isException || !result) { + console.error( + "Chrome Extension: Error getting localStorage:", + isException + ); + ws.send( + JSON.stringify({ + type: "local-storage-error", + error: isException || "Failed to get localStorage", + requestId: message.requestId, + }) + ); + return; + } + + console.log( + "Chrome Extension: localStorage retrieved successfully:", + result + ); + ws.send( + JSON.stringify({ + type: "local-storage-data", + storage: result, + requestId: message.requestId, + }) + ); + } + ); + } else if (message.type === "get-session-storage") { + console.log("Chrome Extension: Getting sessionStorage..."); + // Get sessionStorage from the current tab + chrome.devtools.inspectedWindow.eval( + `(function() { + const storage = {}; + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i); + storage[key] = sessionStorage.getItem(key); + } + return storage; + })()`, + (result, isException) => { + if (isException || !result) { + console.error( + "Chrome Extension: Error getting sessionStorage:", + isException + ); + ws.send( + JSON.stringify({ + type: "session-storage-error", + error: isException || "Failed to get sessionStorage", + requestId: message.requestId, + }) + ); + return; + } + + console.log( + "Chrome Extension: sessionStorage retrieved successfully:", + result + ); + ws.send( + JSON.stringify({ + type: "session-storage-data", + storage: result, + requestId: message.requestId, + }) + ); + } + ); } } catch (error) { console.error( diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index c0d6f00..e151678 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -1,21 +1,20 @@ { - "name": "BrowserTools MCP", - "version": "1.1.1", - "description": "MCP tool for AI code editors to capture data from a browser such as console logs, network requests, screenshots and more", - "manifest_version": 3, - "devtools_page": "devtools.html", - "permissions": [ - "activeTab", - "debugger", - "storage", - "tabs", - "tabCapture", - "windows" - ], - "host_permissions": [ - "" - ], - "background": { - "service_worker": "background.js" - } + "name": "BrowserTools MCP", + "version": "1.1.1", + "description": "MCP tool for AI code editors to capture data from a browser such as console logs, network requests, screenshots and more", + "manifest_version": 3, + "devtools_page": "devtools.html", + "permissions": [ + "activeTab", + "debugger", + "storage", + "tabs", + "tabCapture", + "windows", + "cookies" + ], + "host_permissions": [""], + "background": { + "service_worker": "background.js" + } } From 3ba1fbbd0701250caa549683d728c916198a1842 Mon Sep 17 00:00:00 2001 From: Pushkar Patel Date: Mon, 10 Mar 2025 00:01:49 +0530 Subject: [PATCH 04/14] fix: remove duplicate code and remove unnecessary formatting fixes --- chrome-extension/devtools.js | 48 +++------------------------------- chrome-extension/manifest.json | 38 ++++++++++++++------------- 2 files changed, 23 insertions(+), 63 deletions(-) diff --git a/chrome-extension/devtools.js b/chrome-extension/devtools.js index f23acf5..c14c123 100644 --- a/chrome-extension/devtools.js +++ b/chrome-extension/devtools.js @@ -752,51 +752,9 @@ async function setupWebSocket() { // Set flag to indicate this is an intentional closure intentionalClosure = true; try { - const message = JSON.parse(event.data); - console.log("Chrome Extension: Received WebSocket message:", message); - - if (message.type === "take-screenshot") { - console.log("Chrome Extension: Taking screenshot..."); - // Capture screenshot of the current tab - chrome.tabs.captureVisibleTab(null, { format: "png" }, (dataUrl) => { - if (chrome.runtime.lastError) { - console.error( - "Chrome Extension: Screenshot capture failed:", - chrome.runtime.lastError - ); - ws.send( - JSON.stringify({ - type: "screenshot-error", - error: chrome.runtime.lastError.message, - requestId: message.requestId, - }) - ); - return; - } - - console.log("Chrome Extension: Screenshot captured successfully"); - // Just send the screenshot data, let the server handle paths - const response = { - type: "screenshot-data", - data: dataUrl, - requestId: message.requestId, - // Only include path if it's configured in settings - ...(settings.screenshotPath && { path: settings.screenshotPath }), - }; - - console.log("Chrome Extension: Sending screenshot data response", { - ...response, - data: "[base64 data]", - }); - - ws.send(JSON.stringify(response)); - }); - } - } catch (error) { - console.error( - "Chrome Extension: Error processing WebSocket message:", - error - ); + ws.close(); + } catch (e) { + console.error("Error closing existing WebSocket:", e); } ws = null; intentionalClosure = false; // Reset flag diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index e151678..9d0c31c 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -1,20 +1,22 @@ { - "name": "BrowserTools MCP", - "version": "1.1.1", - "description": "MCP tool for AI code editors to capture data from a browser such as console logs, network requests, screenshots and more", - "manifest_version": 3, - "devtools_page": "devtools.html", - "permissions": [ - "activeTab", - "debugger", - "storage", - "tabs", - "tabCapture", - "windows", - "cookies" - ], - "host_permissions": [""], - "background": { - "service_worker": "background.js" - } + "name": "BrowserTools MCP", + "version": "1.1.1", + "description": "MCP tool for AI code editors to capture data from a browser such as console logs, network requests, screenshots and more", + "manifest_version": 3, + "devtools_page": "devtools.html", + "permissions": [ + "activeTab", + "debugger", + "storage", + "tabs", + "tabCapture", + "windows", + "cookies" + ], + "host_permissions": [ + "" + ], + "background": { + "service_worker": "background.js" + } } From 6a19a9d91f689a1fcb7c785348c914c1d23d0d65 Mon Sep 17 00:00:00 2001 From: Pushkar Patel Date: Mon, 10 Mar 2025 01:49:55 +0530 Subject: [PATCH 05/14] feat: Add sensitive data filtering and aggressive checking - Introduces settings to control the inclusion of sensitive data. - Implements filtering for cookies, local storage, and session storage. - Adds entropy calculation for more aggressive sensitive data checking. --- browser-tools-server/browser-connector.ts | 2 + chrome-extension/devtools.js | 222 +++++++++++++++++++++- chrome-extension/panel.html | 18 +- chrome-extension/panel.js | 46 ++++- 4 files changed, 274 insertions(+), 14 deletions(-) diff --git a/browser-tools-server/browser-connector.ts b/browser-tools-server/browser-connector.ts index 4975517..8310818 100644 --- a/browser-tools-server/browser-connector.ts +++ b/browser-tools-server/browser-connector.ts @@ -142,6 +142,8 @@ let currentSettings = { queryLimit: 30000, showRequestHeaders: false, showResponseHeaders: false, + showSensitive: false, + aggressiveSensitive: false, model: "claude-3-sonnet", stringSizeLimit: 500, maxLogSize: 20000, diff --git a/chrome-extension/devtools.js b/chrome-extension/devtools.js index c14c123..7fadf83 100644 --- a/chrome-extension/devtools.js +++ b/chrome-extension/devtools.js @@ -8,6 +8,7 @@ let settings = { maxLogSize: 20000, showRequestHeaders: false, showResponseHeaders: false, + showSensitive: false, screenshotPath: "", // Add new setting for screenshot path serverHost: "localhost", // Default server host serverPort: 3025, // Default server port @@ -20,6 +21,189 @@ const currentTabId = chrome.devtools.inspectedWindow.tabId; const MAX_ATTACH_RETRIES = 3; const ATTACH_RETRY_DELAY = 1000; // 1 second +// Sensitive key patterns - these match keys that typically contain sensitive data +const SENSITIVE_KEY_PATTERNS = [ + // Authentication related + /auth/i, + /token/i, + /jwt/i, + /session/i, + /api[-_]?key/i, + /secret/i, + /password/i, + /pwd/i, + /pass/i, + /credential/i, + /oauth/i, + /refresh[-_]?token/i, + /access[-_]?token/i, + /private[-_]?key/i, + + // Personal information + /ssn/i, + /social[-_]?security/i, + /dob/i, + /birth/i, + /phone/i, + /address/i, + /zip/i, + /postal/i, + /license/i, + /credit[-_]?card/i, + /card[-_]?number/i, + /cvv/i, + /ccv/i, + + // Financial + /bank/i, + /account/i, + /payment/i, + /tax/i, + /salary/i, + /income/i, + + // Health related + /medical/i, + /insurance/i, + /diagnos/i, + + // Other common sensitive data keys + /private/i, + /confidential/i, + /secure/i, + /key/i, +]; + +// Value patterns that might indicate sensitive data regardless of key name +const SENSITIVE_VALUE_PATTERNS = [ + // JWT token pattern (three base64-encoded segments separated by periods) + /^ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, + + // API Keys (various formats) + /^[A-Za-z0-9_-]{16,128}$/, // generic api key + /^AKIA[0-9A-Z]{16}$/, // aws access key + /^[A-Za-z0-9/+=]{40}$/, // aws secret key + /^sk-[A-Za-z0-9]{32,}$/, // popular api keys + /^(sk|pk)_(test|live)_[A-Za-z0-9]{24,}$/, // stripe api key + /^AIza[0-9A-Za-z-_]{35}$/, // google api key + /^gh[pousr]_[A-Za-z0-9_]{36,255}$/, // github token + + // General API key patterns + /^[A-Za-z0-9._-]{32,}$/, + /^[A-Za-z0-9]{8,}[-_][A-Za-z0-9]{4,}[-_][A-Za-z0-9]{4,}[-_][A-Za-z0-9]{4,}[-_][A-Za-z0-9]{12,}$/, + + // OAuth token patterns + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, + /^bearer [A-Za-z0-9._-]+$/i, // oauth bearer token + + // Credit card patterns (simplified, real implementation would use Luhn algorithm) + /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})$/, + + // Social Security Number pattern (US) + /^\d{3}-\d{2}-\d{4}$/, + + // Email addresses + /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, +]; + +// Add entropy calculation helper before isSensitiveValue function +function calculateEntropy(str) { + // Create frequency map + const freq = new Map(); + for (const char of str) { + freq.set(char, (freq.get(char) || 0) + 1); + } + + // Calculate entropy using Shannon's formula + let entropy = 0; + const len = str.length; + for (const count of freq.values()) { + const p = count / len; + entropy -= p * Math.log2(p); + } + + return entropy; +} + +function isSensitiveValue(value) { + // Only check strings + if (typeof value !== "string") return false; + + // Skip very short values + if (value.length < 8) return false; + + // Check against regex patterns first + if (SENSITIVE_VALUE_PATTERNS.some((pattern) => pattern.test(value))) { + return true; + } + + if (settings.aggressiveSensitive) { + // Entropy-based checks for different string lengths + const entropy = calculateEntropy(value); + + // Adjust thresholds based on string length + if (value.length >= 32 && entropy > 4.2) { + // High entropy threshold for longer strings + // Likely to catch API keys, tokens, etc. + return true; + } + + if (value.length >= 16 && entropy > 3.8) { + // Medium entropy threshold for medium-length strings + // Catches hashed values, shorter tokens + return true; + } + + if (value.length >= 8 && entropy > 3.3) { + // Lower threshold for shorter strings + // Still catches most sensitive data while reducing false positives + return true; + } + } + + return false; +} + +function isSensitiveKey(key) { + return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key)); +} + +function filterSensitiveCookies(cookies) { + if (!Array.isArray(cookies)) return []; + + return cookies.map((cookie) => { + if (!cookie || typeof cookie !== "object") return cookie; + + const { name, value } = cookie; + + if (isSensitiveKey(name) || isSensitiveValue(value)) { + return { + ...cookie, + value: "[SENSITIVE DATA REDACTED]", + sensitive: true, + }; + } + + return cookie; + }); +} + +function filterSensitiveStorage(storage) { + if (!storage || typeof storage !== "object") return {}; + + const result = {}; + + for (const [key, value] of Object.entries(storage)) { + if (isSensitiveKey(key) || isSensitiveValue(value)) { + result[key] = "[SENSITIVE DATA REDACTED]"; + } else { + result[key] = value; + } + } + + return result; +} + // Load saved settings on startup chrome.storage.local.get(["browserConnectorSettings"], (result) => { if (result.browserConnectorSettings) { @@ -313,6 +497,8 @@ async function sendToBrowserConnector(logData) { queryLimit: settings.queryLimit, showRequestHeaders: settings.showRequestHeaders, showResponseHeaders: settings.showResponseHeaders, + showSensitive: settings.showSensitive, + aggressiveSensitive: settings.aggressiveSensitive, }, }; @@ -943,8 +1129,18 @@ async function setupWebSocket() { "Chrome Extension: Cookies retrieved successfully:", result ); + // Make sure cookies is an array, even if empty - const cookies = Array.isArray(result) ? result : []; + let cookies = Array.isArray(result) ? result : []; + + // Filter sensitive data if showSensitive is false + if (!settings.showSensitive) { + console.log( + "Chrome Extension: Filtering sensitive cookie data" + ); + cookies = filterSensitiveCookies(cookies); + } + ws.send( JSON.stringify({ type: "cookies-data", @@ -986,10 +1182,20 @@ async function setupWebSocket() { "Chrome Extension: localStorage retrieved successfully:", result ); + + // Filter sensitive data if showSensitive is false + let storageData = result; + if (!settings.showSensitive) { + console.log( + "Chrome Extension: Filtering sensitive localStorage data" + ); + storageData = filterSensitiveStorage(result); + } + ws.send( JSON.stringify({ type: "local-storage-data", - storage: result, + storage: storageData, requestId: message.requestId, }) ); @@ -1027,10 +1233,20 @@ async function setupWebSocket() { "Chrome Extension: sessionStorage retrieved successfully:", result ); + + // Filter sensitive data if showSensitive is false + let storageData = result; + if (!settings.showSensitive) { + console.log( + "Chrome Extension: Filtering sensitive sessionStorage data" + ); + storageData = filterSensitiveStorage(result); + } + ws.send( JSON.stringify({ type: "session-storage-data", - storage: result, + storage: storageData, requestId: message.requestId, }) ); diff --git a/chrome-extension/panel.html b/chrome-extension/panel.html index a141bfa..7fed8a7 100644 --- a/chrome-extension/panel.html +++ b/chrome-extension/panel.html @@ -164,7 +164,7 @@

Advanced Settings

- +
@@ -199,9 +199,23 @@

Advanced Settings

Include Response Headers
+ +
+ +
+ +
+ +
- \ No newline at end of file + diff --git a/chrome-extension/panel.js b/chrome-extension/panel.js index 33884ad..3ec0d65 100644 --- a/chrome-extension/panel.js +++ b/chrome-extension/panel.js @@ -5,6 +5,8 @@ let settings = { stringSizeLimit: 500, showRequestHeaders: false, showResponseHeaders: false, + showSensitive: false, + aggressiveSensitive: false, maxLogSize: 20000, screenshotPath: "", // Add server connection settings @@ -165,12 +167,12 @@ function createConnectionBanner() { const banner = document.createElement("div"); banner.id = "connection-banner"; banner.style.cssText = ` - padding: 6px 0px; + padding: 6px 0px; margin-bottom: 4px; - width: 40%; - display: flex; + width: 40%; + display: flex; flex-direction: column; - align-items: flex-start; + align-items: flex-start; background-color:rgba(0,0,0,0); border-radius: 11px; font-size: 11px; @@ -225,13 +227,13 @@ function createConnectionBanner() { const indicator = document.createElement("div"); indicator.id = "banner-status-indicator"; indicator.style.cssText = ` - width: 6px; - height: 6px; + width: 6px; + height: 6px; position: relative; top: 1px; - border-radius: 50%; - background-color: #ccc; - margin-right: 8px; + border-radius: 50%; + background-color: #ccc; + margin-right: 8px; flex-shrink: 0; transition: background-color 0.3s ease; `; @@ -313,6 +315,10 @@ const showRequestHeadersCheckbox = document.getElementById( const showResponseHeadersCheckbox = document.getElementById( "show-response-headers" ); +const showSensitiveCheckbox = document.getElementById("show-sensitive"); +const aggressiveSensitiveCheckbox = document.getElementById( + "aggressive-sensitive" +); const maxLogSizeInput = document.getElementById("max-log-size"); const screenshotPathInput = document.getElementById("screenshot-path"); const captureScreenshotButton = document.getElementById("capture-screenshot"); @@ -347,6 +353,8 @@ function updateUIFromSettings() { stringSizeLimitInput.value = settings.stringSizeLimit; showRequestHeadersCheckbox.checked = settings.showRequestHeaders; showResponseHeadersCheckbox.checked = settings.showResponseHeaders; + showSensitiveCheckbox.checked = settings.showSensitive; + aggressiveSensitiveCheckbox.checked = settings.aggressiveSensitive; maxLogSizeInput.value = settings.maxLogSize; screenshotPathInput.value = settings.screenshotPath; serverHostInput.value = settings.serverHost; @@ -389,6 +397,26 @@ showResponseHeadersCheckbox.addEventListener("change", (e) => { saveSettings(); }); +showSensitiveCheckbox.addEventListener("change", (e) => { + settings.showSensitive = e.target.checked; + // set aggressiveSensitive to false if showSensitive is false + if (settings.showSensitive && settings.aggressiveSensitive) { + settings.aggressiveSensitive = false; + aggressiveSensitiveCheckbox.checked = false; + } + saveSettings(); +}); + +aggressiveSensitiveCheckbox.addEventListener("change", (e) => { + settings.aggressiveSensitive = e.target.checked; + // set showSensitive to true if aggressiveSensitive is true + if (settings.aggressiveSensitive && settings.showSensitive) { + settings.showSensitive = false; + showSensitiveCheckbox.checked = false; + } + saveSettings(); +}); + maxLogSizeInput.addEventListener("change", (e) => { settings.maxLogSize = parseInt(e.target.value, 10); saveSettings(); From 9ed497a80bac65a9b25ae8226c56e1e3bfeff14a Mon Sep 17 00:00:00 2001 From: Pushkar Patel Date: Mon, 10 Mar 2025 14:31:02 +0530 Subject: [PATCH 06/14] feat: Improve sensitive data filtering --- browser-tools-server/browser-connector.ts | 3 +- chrome-extension/devtools.js | 58 +++++++++++----------- chrome-extension/panel.html | 56 +++++++++++++++------ chrome-extension/panel.js | 60 ++++++++++++++--------- 4 files changed, 109 insertions(+), 68 deletions(-) diff --git a/browser-tools-server/browser-connector.ts b/browser-tools-server/browser-connector.ts index 8310818..d11e571 100644 --- a/browser-tools-server/browser-connector.ts +++ b/browser-tools-server/browser-connector.ts @@ -142,8 +142,7 @@ let currentSettings = { queryLimit: 30000, showRequestHeaders: false, showResponseHeaders: false, - showSensitive: false, - aggressiveSensitive: false, + sensitiveDataMode: "hide-all", // hide-all, hide-sensitive, show-all model: "claude-3-sonnet", stringSizeLimit: 500, maxLogSize: 20000, diff --git a/chrome-extension/devtools.js b/chrome-extension/devtools.js index 7fadf83..1e8f23f 100644 --- a/chrome-extension/devtools.js +++ b/chrome-extension/devtools.js @@ -8,7 +8,7 @@ let settings = { maxLogSize: 20000, showRequestHeaders: false, showResponseHeaders: false, - showSensitive: false, + sensitiveDataMode: "hide-all", // hide-all, hide-sensitive, show-all screenshotPath: "", // Add new setting for screenshot path serverHost: "localhost", // Default server host serverPort: 3025, // Default server port @@ -107,7 +107,7 @@ const SENSITIVE_VALUE_PATTERNS = [ ]; // Add entropy calculation helper before isSensitiveValue function -function calculateEntropy(str) { +function calculateNormalizedEntropy(str) { // Create frequency map const freq = new Map(); for (const char of str) { @@ -122,7 +122,13 @@ function calculateEntropy(str) { entropy -= p * Math.log2(p); } - return entropy; + // Calculate normalized entropy + const uniqueChars = new Set(str).size; + if (uniqueChars === 0) { + return 0; + } + const maxEntropy = Math.log2(uniqueChars); + return entropy / maxEntropy; } function isSensitiveValue(value) { @@ -137,26 +143,11 @@ function isSensitiveValue(value) { return true; } - if (settings.aggressiveSensitive) { - // Entropy-based checks for different string lengths - const entropy = calculateEntropy(value); - - // Adjust thresholds based on string length - if (value.length >= 32 && entropy > 4.2) { - // High entropy threshold for longer strings - // Likely to catch API keys, tokens, etc. - return true; - } - - if (value.length >= 16 && entropy > 3.8) { - // Medium entropy threshold for medium-length strings - // Catches hashed values, shorter tokens - return true; - } - - if (value.length >= 8 && entropy > 3.3) { - // Lower threshold for shorter strings - // Still catches most sensitive data while reducing false positives + // Entropy-based checks + if (value.length > 16) { + const normalizedEntropy = calculateNormalizedEntropy(value); + // Strings which achieve > 65% of their maximum possible entropy are likely to contain sensitive data + if (normalizedEntropy > 0.65) { return true; } } @@ -176,7 +167,11 @@ function filterSensitiveCookies(cookies) { const { name, value } = cookie; - if (isSensitiveKey(name) || isSensitiveValue(value)) { + if ( + settings.sensitiveDataMode === "hide-all" || + (settings.sensitiveDataMode === "hide-sensitive" && + (isSensitiveKey(name) || isSensitiveValue(value))) + ) { return { ...cookie, value: "[SENSITIVE DATA REDACTED]", @@ -194,7 +189,11 @@ function filterSensitiveStorage(storage) { const result = {}; for (const [key, value] of Object.entries(storage)) { - if (isSensitiveKey(key) || isSensitiveValue(value)) { + if ( + settings.sensitiveDataMode === "hide-all" || + (settings.sensitiveDataMode === "hide-sensitive" && + (isSensitiveKey(key) || isSensitiveValue(value))) + ) { result[key] = "[SENSITIVE DATA REDACTED]"; } else { result[key] = value; @@ -497,8 +496,7 @@ async function sendToBrowserConnector(logData) { queryLimit: settings.queryLimit, showRequestHeaders: settings.showRequestHeaders, showResponseHeaders: settings.showResponseHeaders, - showSensitive: settings.showSensitive, - aggressiveSensitive: settings.aggressiveSensitive, + sensitiveDataMode: settings.sensitiveDataMode, }, }; @@ -1134,7 +1132,7 @@ async function setupWebSocket() { let cookies = Array.isArray(result) ? result : []; // Filter sensitive data if showSensitive is false - if (!settings.showSensitive) { + if (settings.sensitiveDataMode !== "show-all") { console.log( "Chrome Extension: Filtering sensitive cookie data" ); @@ -1185,7 +1183,7 @@ async function setupWebSocket() { // Filter sensitive data if showSensitive is false let storageData = result; - if (!settings.showSensitive) { + if (settings.sensitiveDataMode !== "show-all") { console.log( "Chrome Extension: Filtering sensitive localStorage data" ); @@ -1236,7 +1234,7 @@ async function setupWebSocket() { // Filter sensitive data if showSensitive is false let storageData = result; - if (!settings.showSensitive) { + if (settings.sensitiveDataMode !== "show-all") { console.log( "Chrome Extension: Filtering sensitive sessionStorage data" ); diff --git a/chrome-extension/panel.html b/chrome-extension/panel.html index 7fed8a7..aa65c82 100644 --- a/chrome-extension/panel.html +++ b/chrome-extension/panel.html @@ -110,6 +110,22 @@ .action-button.danger:hover { background-color: #d32f2f; } + .radio-group { + display: flex; + flex-direction: column; + gap: 16px; + } + .radio-option { + margin-bottom: 12px; + } + .radio-option label { + display: flex; + align-items: flex-start; + } + .radio-option input[type="radio"] { + margin-top: 3px; + margin-right: 8px; + } @@ -157,6 +173,32 @@

Server Connection Settings

+
+

Sensitive Data Settings

+
+
+ +
+ +
+ +
+ +
+ +
+
+
+

Advanced Settings

@@ -199,20 +241,6 @@

Advanced Settings

Include Response Headers
- -
- -
- -
- -
diff --git a/chrome-extension/panel.js b/chrome-extension/panel.js index 3ec0d65..3495148 100644 --- a/chrome-extension/panel.js +++ b/chrome-extension/panel.js @@ -5,8 +5,7 @@ let settings = { stringSizeLimit: 500, showRequestHeaders: false, showResponseHeaders: false, - showSensitive: false, - aggressiveSensitive: false, + sensitiveDataMode: "hide-all", // Options: hide-all, hide-sensitive, hide-nothing maxLogSize: 20000, screenshotPath: "", // Add server connection settings @@ -315,10 +314,6 @@ const showRequestHeadersCheckbox = document.getElementById( const showResponseHeadersCheckbox = document.getElementById( "show-response-headers" ); -const showSensitiveCheckbox = document.getElementById("show-sensitive"); -const aggressiveSensitiveCheckbox = document.getElementById( - "aggressive-sensitive" -); const maxLogSizeInput = document.getElementById("max-log-size"); const screenshotPathInput = document.getElementById("screenshot-path"); const captureScreenshotButton = document.getElementById("capture-screenshot"); @@ -332,6 +327,11 @@ const connectionStatusDiv = document.getElementById("connection-status"); const statusIcon = document.getElementById("status-icon"); const statusText = document.getElementById("status-text"); +// Sensitive data UI elements +const hideAllRadio = document.getElementById("hide-all-data"); +const hideSensitiveRadio = document.getElementById("hide-sensitive-data"); +const hideNothingRadio = document.getElementById("hide-nothing"); + // Initialize collapsible advanced settings const advancedSettingsHeader = document.getElementById( "advanced-settings-header" @@ -353,12 +353,27 @@ function updateUIFromSettings() { stringSizeLimitInput.value = settings.stringSizeLimit; showRequestHeadersCheckbox.checked = settings.showRequestHeaders; showResponseHeadersCheckbox.checked = settings.showResponseHeaders; - showSensitiveCheckbox.checked = settings.showSensitive; - aggressiveSensitiveCheckbox.checked = settings.aggressiveSensitive; maxLogSizeInput.value = settings.maxLogSize; screenshotPathInput.value = settings.screenshotPath; serverHostInput.value = settings.serverHost; serverPortInput.value = settings.serverPort; + hideAllRadio.checked = false; + hideSensitiveRadio.checked = false; + hideNothingRadio.checked = false; + switch (settings.sensitiveDataMode) { + case "hide-all": + hideAllRadio.checked = true; + break; + case "hide-sensitive": + hideSensitiveRadio.checked = true; + break; + case "hide-nothing": + hideNothingRadio.checked = true; + break; + default: + // Default to most secure option if setting is invalid + hideAllRadio.checked = true; + } } // Save settings @@ -397,24 +412,25 @@ showResponseHeadersCheckbox.addEventListener("change", (e) => { saveSettings(); }); -showSensitiveCheckbox.addEventListener("change", (e) => { - settings.showSensitive = e.target.checked; - // set aggressiveSensitive to false if showSensitive is false - if (settings.showSensitive && settings.aggressiveSensitive) { - settings.aggressiveSensitive = false; - aggressiveSensitiveCheckbox.checked = false; +hideAllRadio.addEventListener("change", (e) => { + if (e.target.checked) { + settings.sensitiveDataMode = "hide-all"; + saveSettings(); } - saveSettings(); }); -aggressiveSensitiveCheckbox.addEventListener("change", (e) => { - settings.aggressiveSensitive = e.target.checked; - // set showSensitive to true if aggressiveSensitive is true - if (settings.aggressiveSensitive && settings.showSensitive) { - settings.showSensitive = false; - showSensitiveCheckbox.checked = false; +hideSensitiveRadio.addEventListener("change", (e) => { + if (e.target.checked) { + settings.sensitiveDataMode = "hide-sensitive"; + saveSettings(); + } +}); + +hideNothingRadio.addEventListener("change", (e) => { + if (e.target.checked) { + settings.sensitiveDataMode = "hide-nothing"; + saveSettings(); } - saveSettings(); }); maxLogSizeInput.addEventListener("change", (e) => { From c719219b570646f8fee28825f564fd7d9ae38e3c Mon Sep 17 00:00:00 2001 From: Pushkar Patel Date: Mon, 10 Mar 2025 15:55:33 +0530 Subject: [PATCH 07/14] fix: remove extra sensitive parameter from the cookies response --- chrome-extension/devtools.js | 1 - 1 file changed, 1 deletion(-) diff --git a/chrome-extension/devtools.js b/chrome-extension/devtools.js index 1e8f23f..d495d24 100644 --- a/chrome-extension/devtools.js +++ b/chrome-extension/devtools.js @@ -175,7 +175,6 @@ function filterSensitiveCookies(cookies) { return { ...cookie, value: "[SENSITIVE DATA REDACTED]", - sensitive: true, }; } From 722b516bd6585632634122a7ef5bff9d4365bab8 Mon Sep 17 00:00:00 2001 From: ted Date: Tue, 11 Mar 2025 03:45:41 +0900 Subject: [PATCH 08/14] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7448426..0fdaa9c 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,12 @@ Please make sure to update the version in your IDE / MCP client as so: Also make sure to download the latest version of the chrome extension here: [v1.2.0 BrowserToolsMCP Chrome Extension](https://github.com/AgentDeskAI/browser-tools-mcp/releases/download/v1.1.0/chrome-extension-v1-1-0.zip) -From there you can run the local node server as usual like so: -`npx @agentdeskai/browser-tools-server` +From there you can run the local node server like so: +`npx @agentdeskai/browser-tools-server@1.2.0` -And once you've opened your chrome dev tools, logs should be getting sent to your server! +Make sure to specify version 1.2.0 since NPX caching may prevent you from getting the latest version! + +And once you've opened your chrome dev tools, logs should be getting sent to your server 🦾 If you have any questions or issues, feel free to open an issue ticket! And if you have any ideas to make this better, feel free to reach out or open an issue ticket with an enhancement tag or reach out to me at [@tedx_ai on x](https://x.com/tedx_ai) From 0ca25dd5fff674c0ab207f4e8449d77d02d75052 Mon Sep 17 00:00:00 2001 From: ted Date: Tue, 11 Mar 2025 03:46:29 +0900 Subject: [PATCH 09/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fdaa9c..ac1f41e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Also make sure to download the latest version of the chrome extension here: From there you can run the local node server like so: `npx @agentdeskai/browser-tools-server@1.2.0` -Make sure to specify version 1.2.0 since NPX caching may prevent you from getting the latest version! +Make sure to specify version 1.2.0 since NPX caching may prevent you from getting the latest version! You should only have to do this once for every update. After you do it once, you should be on the latest version. And once you've opened your chrome dev tools, logs should be getting sent to your server 🦾 From b170b7f5a320416cf18c6c50ff99a1e0bc3a9b01 Mon Sep 17 00:00:00 2001 From: ted Date: Tue, 11 Mar 2025 04:27:54 +0900 Subject: [PATCH 10/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac1f41e..4ae15b9 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Please make sure to update the version in your IDE / MCP client as so: `npx @agentdeskai/browser-tools-mcp@1.2.0` Also make sure to download the latest version of the chrome extension here: -[v1.2.0 BrowserToolsMCP Chrome Extension](https://github.com/AgentDeskAI/browser-tools-mcp/releases/download/v1.1.0/chrome-extension-v1-1-0.zip) +[v1.2.0 BrowserToolsMCP Chrome Extension](https://github.com/AgentDeskAI/browser-tools-mcp/releases/download/v1.2.0/BrowserTools-1.2.0-extension.zip) From there you can run the local node server like so: `npx @agentdeskai/browser-tools-server@1.2.0` From b68644db0cf2d6ce98bc0e16ca2f6a7902d30236 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Wed, 12 Mar 2025 04:08:44 +0900 Subject: [PATCH 11/14] docs: update README.md audting -> auditing --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ae15b9..0989714 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Coding agents like Cursor can run these audits against the current page seamless | **SEO** | Evaluates on-page SEO factors (like metadata, headings, and link structure) and suggests improvements for better search visibility. | | **Best Practices** | Checks for general best practices in web development. | | **NextJS Audit** | Injects a prompt used to perform a NextJS audit. | -| **Audit Mode** | Runs all audting tools in a sequence. | +| **Audit Mode** | Runs all auditing tools in a sequence. | | **Debugger Mode** | Runs all debugging tools in a sequence. | --- From 011525af88757b838197e154333d8a276c303e22 Mon Sep 17 00:00:00 2001 From: ted Date: Thu, 13 Mar 2025 12:18:36 +0900 Subject: [PATCH 12/14] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0989714..50cefc1 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ This application is a powerful browser monitoring and interaction tool that enab Read our [docs](https://browsertools.agentdesk.ai/) for the full installation, quickstart and contribution guides. +## Roadmap + +Check out our project roadmap here: [Github Roadmap / Project Board](https://github.com/orgs/AgentDeskAI/projects/1/views/1) + ## Updates v1.2.0 is out! Here's a quick breakdown of the update: From 1b999273c9647deea1f3be1c9363659ef665b2c9 Mon Sep 17 00:00:00 2001 From: daibogh Date: Thu, 13 Mar 2025 06:19:22 -0700 Subject: [PATCH 13/14] chore: update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 89b7b15..63a5c74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist -.port \ No newline at end of file +.port +.DS_Store From 0befce357f2421e928f2a19ee09ab502080a35a5 Mon Sep 17 00:00:00 2001 From: ted Date: Wed, 26 Mar 2025 21:40:13 +0900 Subject: [PATCH 14/14] Update README.md --- README.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 50cefc1..4e3c283 100644 --- a/README.md +++ b/README.md @@ -22,20 +22,32 @@ v1.2.0 is out! Here's a quick breakdown of the update: - Improved networking between BrowserTools server, extension and MCP server with host/port auto-discovery, auto-reconnect, and graceful shutdown mechanisms - Added ability to more easily exit out of the Browser Tools server with Ctrl+C - +## Quickstart Guide -Please make sure to update the version in your IDE / MCP client as so: -`npx @agentdeskai/browser-tools-mcp@1.2.0` +There are three components to run this MCP tool: -Also make sure to download the latest version of the chrome extension here: -[v1.2.0 BrowserToolsMCP Chrome Extension](https://github.com/AgentDeskAI/browser-tools-mcp/releases/download/v1.2.0/BrowserTools-1.2.0-extension.zip) +1. Install our chrome extension from here: [v1.2.0 BrowserToolsMCP Chrome Extension](https://github.com/AgentDeskAI/browser-tools-mcp/releases/download/v1.2.0/BrowserTools-1.2.0-extension.zip) +2. Install the MCP server from this command within your IDE: `npx @agentdeskai/browser-tools-mcp@latest` +3. Open a new terminal and run this command: `npx @agentdeskai/browser-tools-server@latest` -From there you can run the local node server like so: -`npx @agentdeskai/browser-tools-server@1.2.0` +* Different IDEs have different configs but this command is generally a good starting point; please reference your IDEs docs for the proper config setup -Make sure to specify version 1.2.0 since NPX caching may prevent you from getting the latest version! You should only have to do this once for every update. After you do it once, you should be on the latest version. +IMPORTANT TIP - there are two servers you need to install. There's... +- browser-tools-server (local nodejs server that's a middleware for gathering logs) +and +- browser-tools-mcp (MCP server that you install into your IDE that communicates w/ the extension + browser-tools-server) -And once you've opened your chrome dev tools, logs should be getting sent to your server 🦾 +`npx @agentdeskai/browser-tools-mcp@latest` is what you put into your IDE +`npx @agentdeskai/browser-tools-server@latest` is what you run in a new terminal window + +After those three steps, open up your chrome dev tools and then the BrowserToolsMCP panel. + +If you're still having issues try these steps: +- Quit / close down your browser. Not just the window but all of Chrome itself. +- Restart the local node server (browser-tools-server) +- Make sure you only have ONE instance of chrome dev tools panel open + +After that, it should work but if it doesn't let me know and I can share some more steps to gather logs/info about the issue! If you have any questions or issues, feel free to open an issue ticket! And if you have any ideas to make this better, feel free to reach out or open an issue ticket with an enhancement tag or reach out to me at [@tedx_ai on x](https://x.com/tedx_ai)