Skip to content

Commit 15079e0

Browse files
committed
Merge PR #163: Add element inspection with CSS selector
- Add inspectElementsBySelector method for CSS selector-based element inspection - Include computed styles support with configurable properties - Add Chrome debugging API integration for enhanced element inspection - Maintain compatibility with existing storage functionality (cookies, localStorage, sessionStorage) - Resolve merge conflicts between storage and element inspection features
2 parents 2f87e51 + 4393daa commit 15079e0

File tree

9 files changed

+482
-21
lines changed

9 files changed

+482
-21
lines changed

browser-tools-mcp/README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ A Model Context Protocol (MCP) server that provides AI-powered browser tools int
55
## Features
66

77
- MCP protocol implementation
8+
- Get HTML of all matches to a CSS selector (new)
89
- Browser console log access
910
- Network request analysis
1011
- Screenshot capture capabilities
@@ -22,31 +23,32 @@ A Model Context Protocol (MCP) server that provides AI-powered browser tools int
2223
## Installation
2324

2425
```bash
25-
npx @agentdeskai/browser-tools-mcp
26+
npx @munawwar-forks/browser-tools-mcp
2627
```
2728

2829
Or install globally:
2930

3031
```bash
31-
npm install -g @agentdeskai/browser-tools-mcp
32+
npm install -g @munawwar-forks/browser-tools-mcp
3233
```
3334

3435
## Usage
3536

3637
1. First, make sure the Browser Tools Server is running:
3738

3839
```bash
39-
npx @agentdeskai/browser-tools-server
40+
npx @munawwar-forks/browser-tools-server
4041
```
4142

4243
2. Then start the MCP server:
4344

4445
```bash
45-
npx @agentdeskai/browser-tools-mcp
46+
npx @munawwar-forks/browser-tools-mcp
4647
```
4748

4849
3. The MCP server will connect to the Browser Tools Server and provide the following capabilities:
4950

51+
- Get HTML by selector
5052
- Console log retrieval
5153
- Network request monitoring
5254
- Screenshot capture
@@ -59,6 +61,7 @@ npx @agentdeskai/browser-tools-mcp
5961

6062
The server provides the following MCP functions:
6163

64+
- `mcp_getHtmlBySelector` - Retrieve HTML by CSS selector
6265
- `mcp_getConsoleLogs` - Retrieve browser console logs
6366
- `mcp_getConsoleErrors` - Get browser console errors
6467
- `mcp_getNetworkErrors` - Get network error logs

browser-tools-mcp/mcp-server.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
44
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
55
import path from "path";
66
import fs from "fs";
7+
// Using zod from the @modelcontextprotocol/sdk dependencies
8+
// This avoids adding zod as a direct dependency to package.json
9+
import { z } from "zod";
710

811
// Create the MCP server
912
const server = new McpServer({
@@ -319,6 +322,62 @@ server.tool(
319322
}
320323
);
321324

325+
server.tool(
326+
"inspectElementsBySelector",
327+
"Get HTML elements and their CSS styles matching a CSS selector",
328+
{
329+
selector: z.string().describe("CSS selector to find elements (e.g., '.classname', '#id', 'div.container > p')"),
330+
resultLimit: z.number().optional().default(1).describe("Maximum number of elements to process (default: 1)"),
331+
includeComputedStyles: z.array(z.string()).optional().default([]).describe("Array of specific CSS properties to include in the computed styles output (empty array means no computed styles)")
332+
},
333+
async ({ selector, resultLimit = 1, includeComputedStyles = [] }) => {
334+
return await withServerConnection(async () => {
335+
try {
336+
// Call the browser-connector endpoint
337+
const response = await fetch(
338+
`http://${discoveredHost}:${discoveredPort}/inspect-elements-by-selector`,
339+
{
340+
method: "POST",
341+
headers: {
342+
"Content-Type": "application/json"
343+
},
344+
body: JSON.stringify({ selector, resultLimit, includeComputedStyles })
345+
}
346+
);
347+
348+
const result = await response.json().catch(() => null);
349+
if (result?.error) {
350+
throw new Error(result.error);
351+
}
352+
if (!response.ok || result === null) {
353+
throw new Error(`Server returned error: ${response.status}`);
354+
}
355+
356+
return {
357+
content: [
358+
{
359+
type: "text",
360+
text: JSON.stringify(result.data || {}, null, 2)
361+
}
362+
]
363+
};
364+
} catch (error) {
365+
const errorMessage = error instanceof Error ? error.message : String(error);
366+
console.error("Error inspecting elements by selector:", errorMessage);
367+
return {
368+
content: [
369+
{
370+
type: "text",
371+
text: `Failed to inspect elements by selector: ${errorMessage}`
372+
}
373+
],
374+
isError: true
375+
};
376+
}
377+
});
378+
}
379+
);
380+
322381
server.tool("wipeLogs", "Wipe all browser logs from memory", async () => {
323382
return await withServerConnection(async () => {
324383
const response = await fetch(

browser-tools-mcp/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser-tools-mcp/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
{
2-
"name": "@agentdeskai/browser-tools-mcp",
3-
"version": "1.2.0",
2+
"name": "@munawwar-forks/browser-tools-mcp",
3+
"version": "1.2.1",
44
"description": "MCP (Model Context Protocol) server for browser tools integration",
55
"main": "dist/mcp-server.js",
66
"bin": {
77
"browser-tools-mcp": "dist/mcp-server.js"
88
},
9+
"publishConfig": {
10+
"access": "public"
11+
},
912
"scripts": {
1013
"inspect": "tsc && npx @modelcontextprotocol/inspector node -- dist/mcp-server.js",
11-
"inspect-live": "npx @modelcontextprotocol/inspector npx -- @agentdeskai/browser-tools-mcp",
14+
"inspect-live": "npx @modelcontextprotocol/inspector npx -- @munawwar-forks/browser-tools-mcp",
1215
"build": "tsc",
1316
"start": "tsc && node dist/mcp-server.js",
1417
"prepublishOnly": "npm run build",

browser-tools-server/browser-connector.ts

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,14 @@ const cookiesCallbacks = new Map<string, CookiesCallback>();
200200
const localStorageCallbacks = new Map<string, LocalStorageCallback>();
201201
const sessionStorageCallbacks = new Map<string, SessionStorageCallback>();
202202

203+
// Add new state for tracking selector requests
204+
interface SelectorCallback {
205+
resolve: (value: string[]) => void;
206+
reject: (reason: Error) => void;
207+
}
208+
209+
const selectorCallbacks = new Map<string, SelectorCallback>();
210+
203211
// Function to get available port starting with the given port
204212
async function getAvailablePort(
205213
startPort: number,
@@ -651,6 +659,18 @@ export class BrowserConnector {
651659
}
652660
);
653661

662+
// Register the inspect-elements-by-selector endpoint
663+
this.app.post(
664+
"/inspect-elements-by-selector",
665+
async (req: express.Request, res: express.Response) => {
666+
console.log(
667+
"Browser Connector: Received request to /inspect-elements-by-selector endpoint"
668+
);
669+
console.log("Browser Connector: Request body:", req.body);
670+
await this.inspectElementsBySelector(req, res);
671+
}
672+
);
673+
654674
// Set up accessibility audit endpoint
655675
this.setupAccessibilityAudit();
656676

@@ -887,7 +907,28 @@ export class BrowserConnector {
887907
} else {
888908
console.log("No callbacks found for sessionStorage");
889909
}
890-
} else {
910+
}
911+
// Handle selector response
912+
if (data.type === "html-by-selector" && data.requestId) {
913+
console.log("Received HTML by selector response");
914+
const callback = selectorCallbacks.get(data.requestId);
915+
if (callback) {
916+
callback.resolve(data.html || []);
917+
selectorCallbacks.delete(data.requestId);
918+
} else {
919+
console.log("No callback found for selector request:", data.requestId);
920+
}
921+
}
922+
// Handle selector error
923+
else if (data.type === "selector-error" && data.requestId) {
924+
console.log("Received selector error:", data.error);
925+
const callback = selectorCallbacks.get(data.requestId);
926+
if (callback) {
927+
callback.reject(new Error(data.error || "Failed to get HTML by selector"));
928+
selectorCallbacks.delete(data.requestId);
929+
}
930+
}
931+
else {
891932
console.log("Unhandled message type:", data.type);
892933
}
893934
} catch (error) {
@@ -1548,6 +1589,113 @@ export class BrowserConnector {
15481589
});
15491590
}
15501591
1592+
// Add method to handle elements with styles requests
1593+
private async inspectElementsBySelector(req: express.Request, res: express.Response) {
1594+
if (!this.activeConnection) {
1595+
return res.status(503).json({ error: "Chrome extension not connected" });
1596+
}
1597+
1598+
const { selector, resultLimit = 1, includeComputedStyles = [] } = req.body;
1599+
if (!selector) {
1600+
return res.status(400).json({ error: "No selector provided" });
1601+
}
1602+
1603+
try {
1604+
const requestId = Date.now().toString();
1605+
console.log("Browser Connector: Generated requestId for elements with styles request:", requestId);
1606+
1607+
// Create promise that will resolve when we get the elements and styles data
1608+
const elementsBySelectorPromise = new Promise<any>((resolve, reject) => {
1609+
console.log(
1610+
`Browser Connector: Setting up elements with styles callback for requestId: ${requestId}`
1611+
);
1612+
1613+
// Store callback in a map
1614+
const elementsBySelectorCallbacks = new Map<string, {
1615+
resolve: (value: any) => void;
1616+
reject: (reason: Error) => void;
1617+
}>();
1618+
1619+
// Store callback in map
1620+
elementsBySelectorCallbacks.set(requestId, { resolve, reject });
1621+
1622+
// Add a message listener for inspect-elements-by-selector response
1623+
const messageHandler = (event: WebSocket.MessageEvent) => {
1624+
try {
1625+
const response = JSON.parse(event.data as string);
1626+
1627+
if (response.type === "inspect-elements-response" && response.requestId === requestId) {
1628+
console.log("Browser Connector: Received inspect-elements-by-selector response");
1629+
const callback = elementsBySelectorCallbacks.get(requestId);
1630+
if (callback) {
1631+
callback.resolve(response.data);
1632+
elementsBySelectorCallbacks.delete(requestId);
1633+
this.activeConnection?.removeEventListener("message", messageHandler);
1634+
}
1635+
}
1636+
else if (response.type === "inspect-elements-error" && response.requestId === requestId) {
1637+
console.error("Browser Connector: inspect-elements-by-selector error:", response.error);
1638+
const callback = elementsBySelectorCallbacks.get(requestId);
1639+
if (callback) {
1640+
callback.reject(new Error(response.error || "Failed to get inspect-elements-by-selector"));
1641+
elementsBySelectorCallbacks.delete(requestId);
1642+
this.activeConnection?.removeEventListener("message", messageHandler);
1643+
}
1644+
}
1645+
} catch (error) {
1646+
console.error("Error processing inspect-elements-by-selector response:", error);
1647+
}
1648+
};
1649+
1650+
// Add the message listener
1651+
this.activeConnection?.addEventListener("message", messageHandler);
1652+
1653+
// Set timeout to clean up if we don't get a response
1654+
setTimeout(() => {
1655+
if (elementsBySelectorCallbacks.has(requestId)) {
1656+
console.log(
1657+
`Browser Connector: inspect-elements-by-selector request timed out for requestId: ${requestId}`
1658+
);
1659+
elementsBySelectorCallbacks.delete(requestId);
1660+
this.activeConnection?.removeEventListener("message", messageHandler);
1661+
reject(new Error("inspect-elements-by-selector request timed out - no response from Chrome extension"));
1662+
}
1663+
}, 10000); // 10 second timeout
1664+
});
1665+
1666+
// Send request to extension
1667+
const message = JSON.stringify({
1668+
type: "inspect-elements-by-selector",
1669+
selector,
1670+
resultLimit,
1671+
includeComputedStyles,
1672+
requestId,
1673+
});
1674+
console.log(
1675+
`Browser Connector: Sending WebSocket message to extension:`,
1676+
message
1677+
);
1678+
this.activeConnection.send(message);
1679+
1680+
// Wait for inspect-elements-by-selector data
1681+
console.log("Browser Connector: Waiting for inspect-elements-by-selector response...");
1682+
const elementsBySelector = await elementsBySelectorPromise;
1683+
console.log(`Browser Connector: Received inspect-elements-by-selector data with ${elementsBySelector.elements.length} elements`);
1684+
1685+
res.json({ data: elementsBySelector });
1686+
} catch (error: unknown) {
1687+
const errorMessage = error instanceof Error ? error.message : String(error);
1688+
console.error("Browser Connector: Error inspecting elements by selector:", errorMessage);
1689+
if (errorMessage.includes("Invalid selector")) {
1690+
return res.status(400).json({ error: errorMessage });
1691+
} else if (errorMessage.includes("timed out")) {
1692+
return res.status(504).json({ error: errorMessage });
1693+
} else {
1694+
return res.status(500).json({ error: errorMessage });
1695+
}
1696+
}
1697+
}
1698+
15511699
// Add method to get cookies
15521700
async getCookies(req: express.Request, res: express.Response) {
15531701
if (!this.activeConnection) {

browser-tools-server/package-lock.json

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser-tools-server/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
{
2-
"name": "@agentdeskai/browser-tools-server",
3-
"version": "1.2.0",
2+
"name": "@munawwar-forks/browser-tools-server",
3+
"version": "1.2.1",
44
"description": "A browser tools server for capturing and managing browser events, logs, and screenshots",
55
"type": "module",
66
"main": "dist/browser-connector.js",
77
"bin": {
88
"browser-tools-server": "./dist/browser-connector.js"
99
},
10+
"publishConfig": {
11+
"access": "public"
12+
},
1013
"scripts": {
1114
"build": "tsc",
1215
"start": "tsc && node dist/browser-connector.js",
@@ -38,13 +41,13 @@
3841
"chrome-launcher": "^1.1.2"
3942
},
4043
"devDependencies": {
41-
"@types/ws": "^8.5.14",
4244
"@types/body-parser": "^1.19.5",
4345
"@types/cors": "^2.8.17",
4446
"@types/express": "^5.0.0",
4547
"@types/node": "^22.13.1",
4648
"@types/node-fetch": "^2.6.11",
4749
"@types/puppeteer-core": "^7.0.4",
50+
"@types/ws": "^8.5.14",
4851
"typescript": "^5.7.3"
4952
}
5053
}

0 commit comments

Comments
 (0)