browser-console-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bin/browser-mcp-server.js +75 -0
- package/bin/cli.js +65 -0
- package/bin/copy-html2canvas.js +51 -0
- package/dist/browser/browser-inject.js +34 -0
- package/dist/browser/browser-mcp-server.js +718 -0
- package/dist/browser/index.d.ts +10 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/client/browser-console-mcp.js +2 -0
- package/dist/client/browser-console-mcp.js.map +1 -0
- package/dist/client/index.d.ts +78 -0
- package/dist/client/index.js +451 -0
- package/dist/server/index.d.ts +48 -0
- package/dist/server/index.js +154 -0
- package/dist/server/static-server.d.ts +44 -0
- package/dist/server/static-server.js +205 -0
- package/dist/static/html2canvas/dist/html2canvas.min.js +20 -0
- package/dist/static/html2canvas.min.js +20 -0
- package/mcp.json +15 -0
- package/package.json +61 -0
- package/readme.md +347 -0
- package/rollup.browser.config.js +23 -0
- package/rollup.config.js +22 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var BrowserConsoleMCP=function(){"use strict";const e=new class{constructor(e="ws://localhost:7898/browser"){this.ws=null,this.connected=!1,this.commandHistory=[],this.historyIndex=-1,this.serverUrl=e}connect(){try{this.loadHtml2Canvas().then((()=>{this.ws=new WebSocket(this.serverUrl),this.ws.onopen=()=>{this.connected=!0,console.info("%c[MCP Client] Connected to server","color: green"),this.registerConsoleCommands()},this.ws.onmessage=e=>{try{const t=JSON.parse(e.data);this.handleServerMessage(t)}catch(e){console.error("[MCP Client] Message parsing error:",e)}},this.ws.onclose=()=>{this.connected=!1,console.info("%c[MCP Client] Disconnected from server","color: orange")},this.ws.onerror=e=>{console.error("[MCP Client] WebSocket error:",e)}})).catch((e=>{console.error("[MCP Client] Failed to load html2canvas:",e)}))}catch(e){console.error("[MCP Client] Connection error:",e)}}loadHtml2Canvas(){return new Promise(((e,t)=>{if(void 0!==window.html2canvas)return console.info("[MCP Client] html2canvas already loaded"),void e(!0);console.info("[MCP Client] Loading html2canvas...");const n=document.createElement("script");n.src="https://html2canvas.hertzen.com/dist/html2canvas.min.js",n.onload=()=>{console.info("[MCP Client] html2canvas loaded from CDN"),e(!0)},n.onerror=()=>{console.info("[MCP Client] Trying local path for html2canvas...");const n=document.createElement("script");n.src="/html2canvas.min.js",n.onload=()=>{console.info("[MCP Client] html2canvas loaded from local path"),e(!0)},n.onerror=n=>{console.info("[MCP Client] Trying unpkg CDN for html2canvas...");const o=document.createElement("script");o.src="https://unpkg.com/html2canvas/dist/html2canvas.min.js",o.onload=()=>{console.info("[MCP Client] html2canvas loaded from unpkg CDN"),e(!0)},o.onerror=e=>{console.error("[MCP Client] Failed to load html2canvas:",e),t(new Error("Unable to load html2canvas library, please ensure your network connection is working"))},document.head.appendChild(o)},document.head.appendChild(n)},document.head.appendChild(n)}))}handleServerMessage(e){if("get_page_html"!==e.type)if("execute_js"!==e.type)if("get_page_title"!==e.type)if("get_elements"!==e.type)if("capture_screenshot"!==e.type)if("get_page_url"!==e.type)if("click_element"!==e.type)if("input_text"!==e.type)switch(e.type){case"command_result":console.info(`%c[MCP Server] ${e.payload?.result}`,"color: blue");break;case"error":console.error(`[MCP Server] Error: ${e.payload?.error}`);break;case"connection_status":console.info(`%c[MCP Server] ${e.message}`,"color: green");break;default:console.info(`[MCP Server] Received message type: ${e.type}`)}else{const t=e.requestId,n=e.selector,o=e.text;try{const e=document.querySelector(n);if(!e)return void this.sendError(t,`Input field not found: ${n}`);if("INPUT"!==e.tagName&&"TEXTAREA"!==e.tagName)return void this.sendError(t,`Selected element is not an input field: ${e.tagName}`);e.value=o,e.dispatchEvent(new Event("input",{bubbles:!0})),this.sendResponse(t,{success:!0,message:`Successfully input text to: ${n}`})}catch(e){this.sendError(t,`Error inputting text: ${e.message}`)}}else{const t=e.requestId,n=e.selector;try{const e=document.querySelector(n);if(!e)return void this.sendError(t,`Element not found: ${n}`);e.click(),this.sendResponse(t,{success:!0,message:`Successfully clicked element: ${n}`})}catch(e){this.sendError(t,`Error clicking element: ${e.message}`)}}else{const t=e.requestId;try{const e=window.location.href;this.sendResponse(t,{url:e})}catch(e){this.sendError(t,`Error getting URL: ${e.message}`)}}else{const t=e.requestId,n=e.selector||"body";(async()=>{try{await this.loadHtml2Canvas();const e=document.querySelector(n);if(!e)return void this.sendError(t,`Element not found: ${n}`);const o={allowTaint:!0,useCORS:!0,logging:!1,scale:window.devicePixelRatio||1,backgroundColor:null,removeContainer:!0,x:0,y:0,scrollX:0,scrollY:0,width:e.offsetWidth,height:e.offsetHeight},s=window.html2canvas;if(!s)return void this.sendError(t,"html2canvas library not properly loaded");let r=await s(e,o);if(!r)return void this.sendError(t,"Screenshot failed: unable to create canvas");const c=r.getContext("2d");if(c){const e=c.getImageData(0,0,r.width,r.height),t=this.getContentBounds(e);if(t){const e=document.createElement("canvas");e.width=t.width,e.height=t.height;const n=e.getContext("2d");n&&(n.drawImage(r,t.left,t.top,t.width,t.height,0,0,t.width,t.height),r=e)}}const i=r.toDataURL("image/png");this.sendResponse(t,{imageDataUrl:i})}catch(e){console.error("[MCP Client] Screenshot error:",e),this.sendError(t,`Screenshot error: ${e.message}`)}})()}else{const t=e.requestId,n=e.selector;try{const e=[...document.querySelectorAll(n)].map((e=>({tagName:e.tagName,id:e.id,className:e.className,textContent:e.textContent?.trim().substring(0,500)||"",attributes:[...e.attributes].reduce(((e,t)=>(e[t.name]=t.value,e)),{})})));this.sendResponse(t,{elements:e})}catch(e){this.sendError(t,`Error getting elements: ${e.message}`)}}else{const t=e.requestId;try{const e=document.title;this.sendResponse(t,{title:e})}catch(e){this.sendError(t,`Error getting title: ${e.message}`)}}else{const t=e.requestId,n=e.code;try{const e=new Function(n)();let o;try{o=JSON.stringify(e)}catch(t){o=String(e)}const s={requestId:t,result:o};this.ws?.send(JSON.stringify(s))}catch(e){console.error("[MCP Client] Error executing JavaScript:",e),this.sendError(t,`Error executing JavaScript: ${e.message}`)}}else{const t=e.requestId;try{const e=document.documentElement.outerHTML;this.sendResponse(t,{html:e})}catch(e){this.sendError(t,`Error getting HTML: ${e.message}`)}}}sendResponse(e,t){if(!this.connected||!this.ws)return void console.error("[MCP Client] Not connected to server, cannot send response");const n={requestId:e,...t};this.ws.send(JSON.stringify(n))}sendError(e,t){if(!this.connected||!this.ws)return void console.error("[MCP Client] Not connected to server, cannot send error response");const n={requestId:e,error:t};this.ws.send(JSON.stringify(n))}sendCommand(e){if(!this.connected||!this.ws)return void console.error("[MCP Client] Not connected to server");const t={type:"command",payload:{command:e}};this.ws.send(JSON.stringify(t)),this.commandHistory.push(e),this.historyIndex=this.commandHistory.length}registerConsoleCommands(){window.mcp={exec:e=>(this.sendCommand(e),"Command sent"),disconnect:()=>(this.ws&&(this.ws.close(),this.ws=null),"Disconnected"),reconnect:()=>(this.connect(),"Reconnecting..."),help:()=>"\nMCP Client Commands:\n mcp.exec(command) - Execute a command\n mcp.disconnect() - Disconnect from server\n mcp.reconnect() - Reconnect to server\n mcp.help() - Show help information\n "},console.info("%c[MCP Client] Console commands registered, use mcp.help() to see available commands","color: green")}getContentBounds(e){const{width:t,height:n,data:o}=e;let s=t,r=n,c=0,i=0,a=!1;for(let e=0;e<n;e++)for(let n=0;n<t;n++){o[4*(e*t+n)+3]>10&&(a=!0,s=Math.min(s,n),r=Math.min(r,e),c=Math.max(c,n),i=Math.max(i,e))}if(!a)return null;return s=Math.max(0,s-10),r=Math.max(0,r-10),c=Math.min(t-1,c+10),i=Math.min(n-1,i+10),{left:s,top:r,width:c-s+1,height:i-r+1}}};return e.connect(),e}();
|
|
2
|
+
//# sourceMappingURL=browser-console-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-console-mcp.js","sources":["../../src/client/index.ts"],"sourcesContent":["/**\n * Browser Console MCP Client\n *\n * This client runs in the browser console and communicates with the MCP server\n */\n\ninterface MCPMessage {\n\ttype: string;\n\tpayload?: Record<string, unknown>;\n\trequestId?: string;\n\tcode?: string;\n\tmessage?: string;\n\tselector?: string;\n\ttext?: string;\n}\n\ninterface MCPConsole {\n\texec: (command: string) => string;\n\tdisconnect: () => string;\n\treconnect: () => string;\n\thelp: () => string;\n}\n\n// html2canvas options type\ninterface Html2CanvasOptions {\n\tallowTaint?: boolean;\n\tuseCORS?: boolean;\n\tlogging?: boolean;\n\tscale?: number;\n\tbackgroundColor?: string | null;\n\tremoveContainer?: boolean;\n\tscrollX?: number;\n\tscrollY?: number;\n\twindowWidth?: number;\n\twindowHeight?: number;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n\t[key: string]: boolean | number | string | undefined | null;\n}\n\n// Extend global Window interface\ndeclare global {\n\tinterface Window {\n\t\tmcp: MCPConsole;\n\t}\n\n\tinterface WindowWithHtml2Canvas extends Window {\n\t\thtml2canvas?: (\n\t\t\telement: HTMLElement,\n\t\t\toptions?: Html2CanvasOptions,\n\t\t) => Promise<HTMLCanvasElement>;\n\t}\n}\n\nclass BrowserConsoleMCP {\n\tprivate ws: WebSocket | null = null;\n\tprivate serverUrl: string;\n\tprivate connected = false;\n\tprivate commandHistory: string[] = [];\n\tprivate historyIndex = -1;\n\n\tconstructor(serverUrl = \"ws://localhost:7898/browser\") {\n\t\tthis.serverUrl = serverUrl;\n\t}\n\n\t/**\n\t * Connect to MCP server\n\t */\n\tconnect(): void {\n\t\ttry {\n\t\t\t// First load html2canvas\n\t\t\tthis.loadHtml2Canvas()\n\t\t\t\t.then(() => {\n\t\t\t\t\tthis.ws = new WebSocket(this.serverUrl);\n\n\t\t\t\t\tthis.ws.onopen = () => {\n\t\t\t\t\t\tthis.connected = true;\n\t\t\t\t\t\tconsole.info(\"%c[MCP Client] Connected to server\", \"color: green\");\n\t\t\t\t\t\tthis.registerConsoleCommands();\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.ws.onmessage = (event) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst message: MCPMessage = JSON.parse(event.data);\n\t\t\t\t\t\t\tthis.handleServerMessage(message);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error(\"[MCP Client] Message parsing error:\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.ws.onclose = () => {\n\t\t\t\t\t\tthis.connected = false;\n\t\t\t\t\t\tconsole.info(\n\t\t\t\t\t\t\t\"%c[MCP Client] Disconnected from server\",\n\t\t\t\t\t\t\t\"color: orange\",\n\t\t\t\t\t\t);\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.ws.onerror = (error) => {\n\t\t\t\t\t\tconsole.error(\"[MCP Client] WebSocket error:\", error);\n\t\t\t\t\t};\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(\"[MCP Client] Failed to load html2canvas:\", error);\n\t\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[MCP Client] Connection error:\", error);\n\t\t}\n\t}\n\n\t/**\n\t * Load html2canvas library\n\t */\n\tprivate loadHtml2Canvas(): Promise<boolean> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (\n\t\t\t\ttypeof (window as WindowWithHtml2Canvas).html2canvas !== \"undefined\"\n\t\t\t) {\n\t\t\t\tconsole.info(\"[MCP Client] html2canvas already loaded\");\n\t\t\t\tresolve(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconsole.info(\"[MCP Client] Loading html2canvas...\");\n\n\t\t\t// Prioritize loading html2canvas from CDN\n\t\t\tconst script = document.createElement(\"script\");\n\t\t\tscript.src = \"https://html2canvas.hertzen.com/dist/html2canvas.min.js\";\n\t\t\tscript.onload = () => {\n\t\t\t\tconsole.info(\"[MCP Client] html2canvas loaded from CDN\");\n\t\t\t\tresolve(true);\n\t\t\t};\n\t\t\tscript.onerror = () => {\n\t\t\t\t// If CDN fails, try local path\n\t\t\t\tconsole.info(\"[MCP Client] Trying local path for html2canvas...\");\n\t\t\t\tconst localScript = document.createElement(\"script\");\n\t\t\t\tlocalScript.src = \"/html2canvas.min.js\";\n\t\t\t\tlocalScript.onload = () => {\n\t\t\t\t\tconsole.info(\"[MCP Client] html2canvas loaded from local path\");\n\t\t\t\t\tresolve(true);\n\t\t\t\t};\n\t\t\t\tlocalScript.onerror = (err) => {\n\t\t\t\t\t// Try using unpkg CDN\n\t\t\t\t\tconsole.info(\"[MCP Client] Trying unpkg CDN for html2canvas...\");\n\t\t\t\t\tconst unpkgScript = document.createElement(\"script\");\n\t\t\t\t\tunpkgScript.src =\n\t\t\t\t\t\t\"https://unpkg.com/html2canvas/dist/html2canvas.min.js\";\n\t\t\t\t\tunpkgScript.onload = () => {\n\t\t\t\t\t\tconsole.info(\"[MCP Client] html2canvas loaded from unpkg CDN\");\n\t\t\t\t\t\tresolve(true);\n\t\t\t\t\t};\n\t\t\t\t\tunpkgScript.onerror = (err) => {\n\t\t\t\t\t\tconsole.error(\"[MCP Client] Failed to load html2canvas:\", err);\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\"Unable to load html2canvas library, please ensure your network connection is working\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t};\n\t\t\t\t\tdocument.head.appendChild(unpkgScript);\n\t\t\t\t};\n\t\t\t\tdocument.head.appendChild(localScript);\n\t\t\t};\n\t\t\tdocument.head.appendChild(script);\n\t\t});\n\t}\n\n\t/**\n\t * Handle messages from server\n\t */\n\tprivate handleServerMessage(message: MCPMessage): void {\n\t\t// Handle special request types\n\t\tif (message.type === \"get_page_html\") {\n\t\t\t// Handle request to get page HTML\n\t\t\tconst requestId = message.requestId as string;\n\t\t\ttry {\n\t\t\t\tconst html = document.documentElement.outerHTML;\n\t\t\t\tthis.sendResponse(requestId, { html });\n\t\t\t} catch (error) {\n\t\t\t\tthis.sendError(\n\t\t\t\t\trequestId,\n\t\t\t\t\t`Error getting HTML: ${(error as Error).message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"execute_js\") {\n\t\t\t// Handle request to execute JavaScript\n\t\t\tconst requestId = message.requestId as string;\n\t\t\tconst code = message.code as string;\n\t\t\ttry {\n\t\t\t\t// Use Function constructor to create function, then execute\n\t\t\t\tconst result = new Function(code)();\n\t\t\t\t// Ensure result can be JSON serialized\n\t\t\t\tlet serializedResult: string;\n\t\t\t\ttry {\n\t\t\t\t\tserializedResult = JSON.stringify(result);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// If result cannot be serialized, return string representation\n\t\t\t\t\tserializedResult = String(result);\n\t\t\t\t}\n\n\t\t\t\t// Send response\n\t\t\t\tconst response = {\n\t\t\t\t\trequestId,\n\t\t\t\t\tresult: serializedResult,\n\t\t\t\t};\n\n\t\t\t\tthis.ws?.send(JSON.stringify(response));\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"[MCP Client] Error executing JavaScript:\", error);\n\t\t\t\tthis.sendError(\n\t\t\t\t\trequestId,\n\t\t\t\t\t`Error executing JavaScript: ${(error as Error).message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"get_page_title\") {\n\t\t\t// Handle request to get page title\n\t\t\tconst requestId = message.requestId as string;\n\t\t\ttry {\n\t\t\t\tconst title = document.title;\n\t\t\t\tthis.sendResponse(requestId, { title });\n\t\t\t} catch (error) {\n\t\t\t\tthis.sendError(\n\t\t\t\t\trequestId,\n\t\t\t\t\t`Error getting title: ${(error as Error).message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"get_elements\") {\n\t\t\t// Handle request to get elements\n\t\t\tconst requestId = message.requestId as string;\n\t\t\tconst selector = message.selector as string;\n\t\t\ttry {\n\t\t\t\tconst elements = [...document.querySelectorAll(selector)];\n\t\t\t\tconst result = elements.map((el) => ({\n\t\t\t\t\ttagName: el.tagName,\n\t\t\t\t\tid: el.id,\n\t\t\t\t\tclassName: el.className,\n\t\t\t\t\ttextContent: el.textContent?.trim().substring(0, 500) || \"\",\n\t\t\t\t\tattributes: [...el.attributes].reduce(\n\t\t\t\t\t\t(attrs: Record<string, string>, attr) => {\n\t\t\t\t\t\t\tattrs[attr.name] = attr.value;\n\t\t\t\t\t\t\treturn attrs;\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{},\n\t\t\t\t\t),\n\t\t\t\t}));\n\t\t\t\tthis.sendResponse(requestId, { elements: result });\n\t\t\t} catch (error) {\n\t\t\t\tthis.sendError(\n\t\t\t\t\trequestId,\n\t\t\t\t\t`Error getting elements: ${(error as Error).message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"capture_screenshot\") {\n\t\t\t// Handle screenshot request\n\t\t\tconst requestId = message.requestId as string;\n\t\t\tconst selector = (message.selector as string) || \"body\";\n\n\t\t\t// Process screenshot asynchronously\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// Ensure html2canvas is loaded\n\t\t\t\t\tawait this.loadHtml2Canvas();\n\n\t\t\t\t\tconst element = document.querySelector(selector);\n\t\t\t\t\tif (!element) {\n\t\t\t\t\t\tthis.sendError(requestId, `Element not found: ${selector}`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Use html2canvas for screenshot, add config for better compatibility\n\t\t\t\t\tconst html2canvasOptions: Html2CanvasOptions = {\n\t\t\t\t\t\tallowTaint: true,\n\t\t\t\t\t\tuseCORS: true,\n\t\t\t\t\t\tlogging: false,\n\t\t\t\t\t\tscale: window.devicePixelRatio || 1,\n\t\t\t\t\t\tbackgroundColor: null, // Transparent background\n\t\t\t\t\t\tremoveContainer: true, // Remove temporary container\n\t\t\t\t\t\t// Calculate actual element size and position\n\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\tscrollX: 0,\n\t\t\t\t\t\tscrollY: 0,\n\t\t\t\t\t\t// Get actual element width and height\n\t\t\t\t\t\twidth: (element as HTMLElement).offsetWidth,\n\t\t\t\t\t\theight: (element as HTMLElement).offsetHeight,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst html2canvas = (window as WindowWithHtml2Canvas).html2canvas;\n\n\t\t\t\t\tif (!html2canvas) {\n\t\t\t\t\t\tthis.sendError(\n\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\t\"html2canvas library not properly loaded\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet canvas = await html2canvas(\n\t\t\t\t\t\telement as HTMLElement,\n\t\t\t\t\t\thtml2canvasOptions,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!canvas) {\n\t\t\t\t\t\tthis.sendError(\n\t\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t\t\"Screenshot failed: unable to create canvas\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Crop canvas, remove excess white space\n\t\t\t\t\tconst context = canvas.getContext(\"2d\");\n\t\t\t\t\tif (context) {\n\t\t\t\t\t\t// Try to detect content area, remove whitespace\n\t\t\t\t\t\tconst imageData = context.getImageData(\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tcanvas.width,\n\t\t\t\t\t\t\tcanvas.height,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst bounds = this.getContentBounds(imageData);\n\n\t\t\t\t\t\tif (bounds) {\n\t\t\t\t\t\t\t// If content boundaries found, create a new cropped canvas\n\t\t\t\t\t\t\tconst croppedCanvas = document.createElement(\"canvas\");\n\t\t\t\t\t\t\tcroppedCanvas.width = bounds.width;\n\t\t\t\t\t\t\tcroppedCanvas.height = bounds.height;\n\n\t\t\t\t\t\t\tconst croppedContext = croppedCanvas.getContext(\"2d\");\n\t\t\t\t\t\t\tif (croppedContext) {\n\t\t\t\t\t\t\t\tcroppedContext.drawImage(\n\t\t\t\t\t\t\t\t\tcanvas,\n\t\t\t\t\t\t\t\t\tbounds.left,\n\t\t\t\t\t\t\t\t\tbounds.top,\n\t\t\t\t\t\t\t\t\tbounds.width,\n\t\t\t\t\t\t\t\t\tbounds.height,\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\tbounds.width,\n\t\t\t\t\t\t\t\t\tbounds.height,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t// Use cropped canvas\n\t\t\t\t\t\t\t\tcanvas = croppedCanvas;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Compress image quality to reduce data size, use PNG format to maintain transparency\n\t\t\t\t\tconst dataUrl = canvas.toDataURL(\"image/png\");\n\t\t\t\t\tthis.sendResponse(requestId, { imageDataUrl: dataUrl });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"[MCP Client] Screenshot error:\", error);\n\t\t\t\t\tthis.sendError(\n\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t`Screenshot error: ${(error as Error).message}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t})();\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"get_page_url\") {\n\t\t\t// Handle request to get page URL\n\t\t\tconst requestId = message.requestId as string;\n\t\t\ttry {\n\t\t\t\tconst url = window.location.href;\n\t\t\t\tthis.sendResponse(requestId, { url });\n\t\t\t} catch (error) {\n\t\t\t\tthis.sendError(\n\t\t\t\t\trequestId,\n\t\t\t\t\t`Error getting URL: ${(error as Error).message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"click_element\") {\n\t\t\t// Handle request to click element\n\t\t\tconst requestId = message.requestId as string;\n\t\t\tconst selector = message.selector as string;\n\t\t\ttry {\n\t\t\t\tconst element = document.querySelector(selector);\n\t\t\t\tif (!element) {\n\t\t\t\t\tthis.sendError(requestId, `Element not found: ${selector}`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t(element as HTMLElement).click();\n\t\t\t\tthis.sendResponse(requestId, {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tmessage: `Successfully clicked element: ${selector}`,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tthis.sendError(\n\t\t\t\t\trequestId,\n\t\t\t\t\t`Error clicking element: ${(error as Error).message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.type === \"input_text\") {\n\t\t\t// Handle request to input text\n\t\t\tconst requestId = message.requestId as string;\n\t\t\tconst selector = message.selector as string;\n\t\t\tconst text = message.text as string;\n\n\t\t\ttry {\n\t\t\t\tconst input = document.querySelector(selector);\n\t\t\t\tif (!input) {\n\t\t\t\t\tthis.sendError(requestId, `Input field not found: ${selector}`);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (input.tagName !== \"INPUT\" && input.tagName !== \"TEXTAREA\") {\n\t\t\t\t\tthis.sendError(\n\t\t\t\t\t\trequestId,\n\t\t\t\t\t\t`Selected element is not an input field: ${input.tagName}`,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t(input as HTMLInputElement).value = text;\n\t\t\t\tinput.dispatchEvent(new Event(\"input\", { bubbles: true }));\n\n\t\t\t\tthis.sendResponse(requestId, {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tmessage: `Successfully input text to: ${selector}`,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tthis.sendError(\n\t\t\t\t\trequestId,\n\t\t\t\t\t`Error inputting text: ${(error as Error).message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle other message types\n\t\tswitch (message.type) {\n\t\t\tcase \"command_result\":\n\t\t\t\tconsole.info(\n\t\t\t\t\t`%c[MCP Server] ${message.payload?.result}`,\n\t\t\t\t\t\"color: blue\",\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"error\":\n\t\t\t\tconsole.error(`[MCP Server] Error: ${message.payload?.error}`);\n\t\t\t\tbreak;\n\t\t\tcase \"connection_status\":\n\t\t\t\tconsole.info(`%c[MCP Server] ${message.message}`, \"color: green\");\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// Only log message type, not full message content\n\t\t\t\tconsole.info(`[MCP Server] Received message type: ${message.type}`);\n\t\t}\n\t}\n\n\t/**\n\t * Send response\n\t */\n\tprivate sendResponse(requestId: string, data: Record<string, unknown>): void {\n\t\tif (!this.connected || !this.ws) {\n\t\t\tconsole.error(\n\t\t\t\t\"[MCP Client] Not connected to server, cannot send response\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst response = {\n\t\t\trequestId,\n\t\t\t...data,\n\t\t};\n\n\t\tthis.ws.send(JSON.stringify(response));\n\t}\n\n\t/**\n\t * Send error response\n\t */\n\tprivate sendError(requestId: string, errorMessage: string): void {\n\t\tif (!this.connected || !this.ws) {\n\t\t\tconsole.error(\n\t\t\t\t\"[MCP Client] Not connected to server, cannot send error response\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst response = {\n\t\t\trequestId,\n\t\t\terror: errorMessage,\n\t\t};\n\n\t\tthis.ws.send(JSON.stringify(response));\n\t}\n\n\t/**\n\t * Send command to server\n\t */\n\tsendCommand(command: string): void {\n\t\tif (!this.connected || !this.ws) {\n\t\t\tconsole.error(\"[MCP Client] Not connected to server\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst message: MCPMessage = {\n\t\t\ttype: \"command\",\n\t\t\tpayload: { command },\n\t\t};\n\n\t\tthis.ws.send(JSON.stringify(message));\n\t\tthis.commandHistory.push(command);\n\t\tthis.historyIndex = this.commandHistory.length;\n\t}\n\n\t/**\n\t * Register console commands\n\t */\n\tprivate registerConsoleCommands(): void {\n\t\t// Define global commands\n\t\twindow.mcp = {\n\t\t\texec: (command: string) => {\n\t\t\t\tthis.sendCommand(command);\n\t\t\t\treturn \"Command sent\";\n\t\t\t},\n\t\t\tdisconnect: () => {\n\t\t\t\tif (this.ws) {\n\t\t\t\t\tthis.ws.close();\n\t\t\t\t\tthis.ws = null;\n\t\t\t\t}\n\t\t\t\treturn \"Disconnected\";\n\t\t\t},\n\t\t\treconnect: () => {\n\t\t\t\tthis.connect();\n\t\t\t\treturn \"Reconnecting...\";\n\t\t\t},\n\t\t\thelp: () => {\n\t\t\t\treturn `\nMCP Client Commands:\n mcp.exec(command) - Execute a command\n mcp.disconnect() - Disconnect from server\n mcp.reconnect() - Reconnect to server\n mcp.help() - Show help information\n `;\n\t\t\t},\n\t\t};\n\n\t\tconsole.info(\n\t\t\t\"%c[MCP Client] Console commands registered, use mcp.help() to see available commands\",\n\t\t\t\"color: green\",\n\t\t);\n\t}\n\n\t/**\n\t * Get image content boundaries, remove excess whitespace\n\t */\n\tprivate getContentBounds(\n\t\timageData: ImageData,\n\t): { left: number; top: number; width: number; height: number } | null {\n\t\tconst { width, height, data } = imageData;\n\t\tlet minX = width;\n\t\tlet minY = height;\n\t\tlet maxX = 0;\n\t\tlet maxY = 0;\n\t\tlet hasContent = false;\n\n\t\t// Iterate through pixel data, find boundaries of non-transparent pixels\n\t\tfor (let y = 0; y < height; y++) {\n\t\t\tfor (let x = 0; x < width; x++) {\n\t\t\t\tconst alpha = data[(y * width + x) * 4 + 3]; // Alpha channel\n\t\t\t\tif (alpha > 10) {\n\t\t\t\t\t// Non-transparent pixel (allowing some slight transparency)\n\t\t\t\t\thasContent = true;\n\t\t\t\t\tminX = Math.min(minX, x);\n\t\t\t\t\tminY = Math.min(minY, y);\n\t\t\t\t\tmaxX = Math.max(maxX, x);\n\t\t\t\t\tmaxY = Math.max(maxY, y);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!hasContent) {\n\t\t\treturn null; // No content found\n\t\t}\n\n\t\t// Add some padding\n\t\tconst padding = 10;\n\t\tminX = Math.max(0, minX - padding);\n\t\tminY = Math.max(0, minY - padding);\n\t\tmaxX = Math.min(width - 1, maxX + padding);\n\t\tmaxY = Math.min(height - 1, maxY + padding);\n\n\t\treturn {\n\t\t\tleft: minX,\n\t\t\ttop: minY,\n\t\t\twidth: maxX - minX + 1,\n\t\t\theight: maxY - minY + 1,\n\t\t};\n\t}\n}\n\n// Create and connect client instance\nconst client = new BrowserConsoleMCP();\nclient.connect();\n\n// Export client instance\nexport default client;\n"],"names":["client","constructor","serverUrl","this","ws","connected","commandHistory","historyIndex","connect","loadHtml2Canvas","then","WebSocket","onopen","console","info","registerConsoleCommands","onmessage","event","message","JSON","parse","data","handleServerMessage","error","onclose","onerror","catch","Promise","resolve","reject","window","html2canvas","script","document","createElement","src","onload","localScript","err","unpkgScript","Error","head","appendChild","type","payload","result","requestId","selector","text","input","querySelector","sendError","tagName","value","dispatchEvent","Event","bubbles","sendResponse","success","element","click","url","location","href","html2canvasOptions","allowTaint","useCORS","logging","scale","devicePixelRatio","backgroundColor","removeContainer","x","y","scrollX","scrollY","width","offsetWidth","height","offsetHeight","canvas","context","getContext","imageData","getImageData","bounds","getContentBounds","croppedCanvas","croppedContext","drawImage","left","top","dataUrl","toDataURL","imageDataUrl","querySelectorAll","map","el","id","className","textContent","trim","substring","attributes","reduce","attrs","attr","name","elements","title","code","Function","serializedResult","stringify","String","response","send","html","documentElement","outerHTML","errorMessage","sendCommand","command","push","length","mcp","exec","disconnect","close","reconnect","help","minX","minY","maxX","maxY","hasContent","Math","min","max"],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Console MCP Client
|
|
3
|
+
*
|
|
4
|
+
* This client runs in the browser console and communicates with the MCP server
|
|
5
|
+
*/
|
|
6
|
+
interface MCPConsole {
|
|
7
|
+
exec: (command: string) => string;
|
|
8
|
+
disconnect: () => string;
|
|
9
|
+
reconnect: () => string;
|
|
10
|
+
help: () => string;
|
|
11
|
+
}
|
|
12
|
+
interface Html2CanvasOptions {
|
|
13
|
+
allowTaint?: boolean;
|
|
14
|
+
useCORS?: boolean;
|
|
15
|
+
logging?: boolean;
|
|
16
|
+
scale?: number;
|
|
17
|
+
backgroundColor?: string | null;
|
|
18
|
+
removeContainer?: boolean;
|
|
19
|
+
scrollX?: number;
|
|
20
|
+
scrollY?: number;
|
|
21
|
+
windowWidth?: number;
|
|
22
|
+
windowHeight?: number;
|
|
23
|
+
x?: number;
|
|
24
|
+
y?: number;
|
|
25
|
+
width?: number;
|
|
26
|
+
height?: number;
|
|
27
|
+
[key: string]: boolean | number | string | undefined | null;
|
|
28
|
+
}
|
|
29
|
+
declare global {
|
|
30
|
+
interface Window {
|
|
31
|
+
mcp: MCPConsole;
|
|
32
|
+
}
|
|
33
|
+
interface WindowWithHtml2Canvas extends Window {
|
|
34
|
+
html2canvas?: (element: HTMLElement, options?: Html2CanvasOptions) => Promise<HTMLCanvasElement>;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
declare class BrowserConsoleMCP {
|
|
38
|
+
private ws;
|
|
39
|
+
private serverUrl;
|
|
40
|
+
private connected;
|
|
41
|
+
private commandHistory;
|
|
42
|
+
private historyIndex;
|
|
43
|
+
constructor(serverUrl?: string);
|
|
44
|
+
/**
|
|
45
|
+
* Connect to MCP server
|
|
46
|
+
*/
|
|
47
|
+
connect(): void;
|
|
48
|
+
/**
|
|
49
|
+
* Load html2canvas library
|
|
50
|
+
*/
|
|
51
|
+
private loadHtml2Canvas;
|
|
52
|
+
/**
|
|
53
|
+
* Handle messages from server
|
|
54
|
+
*/
|
|
55
|
+
private handleServerMessage;
|
|
56
|
+
/**
|
|
57
|
+
* Send response
|
|
58
|
+
*/
|
|
59
|
+
private sendResponse;
|
|
60
|
+
/**
|
|
61
|
+
* Send error response
|
|
62
|
+
*/
|
|
63
|
+
private sendError;
|
|
64
|
+
/**
|
|
65
|
+
* Send command to server
|
|
66
|
+
*/
|
|
67
|
+
sendCommand(command: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Register console commands
|
|
70
|
+
*/
|
|
71
|
+
private registerConsoleCommands;
|
|
72
|
+
/**
|
|
73
|
+
* Get image content boundaries, remove excess whitespace
|
|
74
|
+
*/
|
|
75
|
+
private getContentBounds;
|
|
76
|
+
}
|
|
77
|
+
declare const client: BrowserConsoleMCP;
|
|
78
|
+
export default client;
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Console MCP Client
|
|
3
|
+
*
|
|
4
|
+
* This client runs in the browser console and communicates with the MCP server
|
|
5
|
+
*/
|
|
6
|
+
class BrowserConsoleMCP {
|
|
7
|
+
constructor(serverUrl = "ws://localhost:7898/browser") {
|
|
8
|
+
this.ws = null;
|
|
9
|
+
this.connected = false;
|
|
10
|
+
this.commandHistory = [];
|
|
11
|
+
this.historyIndex = -1;
|
|
12
|
+
this.serverUrl = serverUrl;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Connect to MCP server
|
|
16
|
+
*/
|
|
17
|
+
connect() {
|
|
18
|
+
try {
|
|
19
|
+
// First load html2canvas
|
|
20
|
+
this.loadHtml2Canvas()
|
|
21
|
+
.then(() => {
|
|
22
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
23
|
+
this.ws.onopen = () => {
|
|
24
|
+
this.connected = true;
|
|
25
|
+
console.info("%c[MCP Client] Connected to server", "color: green");
|
|
26
|
+
this.registerConsoleCommands();
|
|
27
|
+
};
|
|
28
|
+
this.ws.onmessage = (event) => {
|
|
29
|
+
try {
|
|
30
|
+
const message = JSON.parse(event.data);
|
|
31
|
+
this.handleServerMessage(message);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error("[MCP Client] Message parsing error:", error);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
this.ws.onclose = () => {
|
|
38
|
+
this.connected = false;
|
|
39
|
+
console.info("%c[MCP Client] Disconnected from server", "color: orange");
|
|
40
|
+
};
|
|
41
|
+
this.ws.onerror = (error) => {
|
|
42
|
+
console.error("[MCP Client] WebSocket error:", error);
|
|
43
|
+
};
|
|
44
|
+
})
|
|
45
|
+
.catch((error) => {
|
|
46
|
+
console.error("[MCP Client] Failed to load html2canvas:", error);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error("[MCP Client] Connection error:", error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Load html2canvas library
|
|
55
|
+
*/
|
|
56
|
+
loadHtml2Canvas() {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
if (typeof window.html2canvas !== "undefined") {
|
|
59
|
+
console.info("[MCP Client] html2canvas already loaded");
|
|
60
|
+
resolve(true);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
console.info("[MCP Client] Loading html2canvas...");
|
|
64
|
+
// Prioritize loading html2canvas from CDN
|
|
65
|
+
const script = document.createElement("script");
|
|
66
|
+
script.src = "https://html2canvas.hertzen.com/dist/html2canvas.min.js";
|
|
67
|
+
script.onload = () => {
|
|
68
|
+
console.info("[MCP Client] html2canvas loaded from CDN");
|
|
69
|
+
resolve(true);
|
|
70
|
+
};
|
|
71
|
+
script.onerror = () => {
|
|
72
|
+
// If CDN fails, try local path
|
|
73
|
+
console.info("[MCP Client] Trying local path for html2canvas...");
|
|
74
|
+
const localScript = document.createElement("script");
|
|
75
|
+
localScript.src = "/html2canvas.min.js";
|
|
76
|
+
localScript.onload = () => {
|
|
77
|
+
console.info("[MCP Client] html2canvas loaded from local path");
|
|
78
|
+
resolve(true);
|
|
79
|
+
};
|
|
80
|
+
localScript.onerror = (err) => {
|
|
81
|
+
// Try using unpkg CDN
|
|
82
|
+
console.info("[MCP Client] Trying unpkg CDN for html2canvas...");
|
|
83
|
+
const unpkgScript = document.createElement("script");
|
|
84
|
+
unpkgScript.src =
|
|
85
|
+
"https://unpkg.com/html2canvas/dist/html2canvas.min.js";
|
|
86
|
+
unpkgScript.onload = () => {
|
|
87
|
+
console.info("[MCP Client] html2canvas loaded from unpkg CDN");
|
|
88
|
+
resolve(true);
|
|
89
|
+
};
|
|
90
|
+
unpkgScript.onerror = (err) => {
|
|
91
|
+
console.error("[MCP Client] Failed to load html2canvas:", err);
|
|
92
|
+
reject(new Error("Unable to load html2canvas library, please ensure your network connection is working"));
|
|
93
|
+
};
|
|
94
|
+
document.head.appendChild(unpkgScript);
|
|
95
|
+
};
|
|
96
|
+
document.head.appendChild(localScript);
|
|
97
|
+
};
|
|
98
|
+
document.head.appendChild(script);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Handle messages from server
|
|
103
|
+
*/
|
|
104
|
+
handleServerMessage(message) {
|
|
105
|
+
// Handle special request types
|
|
106
|
+
if (message.type === "get_page_html") {
|
|
107
|
+
// Handle request to get page HTML
|
|
108
|
+
const requestId = message.requestId;
|
|
109
|
+
try {
|
|
110
|
+
const html = document.documentElement.outerHTML;
|
|
111
|
+
this.sendResponse(requestId, { html });
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
this.sendError(requestId, `Error getting HTML: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (message.type === "execute_js") {
|
|
119
|
+
// Handle request to execute JavaScript
|
|
120
|
+
const requestId = message.requestId;
|
|
121
|
+
const code = message.code;
|
|
122
|
+
try {
|
|
123
|
+
// Use Function constructor to create function, then execute
|
|
124
|
+
const result = new Function(code)();
|
|
125
|
+
// Ensure result can be JSON serialized
|
|
126
|
+
let serializedResult;
|
|
127
|
+
try {
|
|
128
|
+
serializedResult = JSON.stringify(result);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
// If result cannot be serialized, return string representation
|
|
132
|
+
serializedResult = String(result);
|
|
133
|
+
}
|
|
134
|
+
// Send response
|
|
135
|
+
const response = {
|
|
136
|
+
requestId,
|
|
137
|
+
result: serializedResult,
|
|
138
|
+
};
|
|
139
|
+
this.ws?.send(JSON.stringify(response));
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.error("[MCP Client] Error executing JavaScript:", error);
|
|
143
|
+
this.sendError(requestId, `Error executing JavaScript: ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (message.type === "get_page_title") {
|
|
148
|
+
// Handle request to get page title
|
|
149
|
+
const requestId = message.requestId;
|
|
150
|
+
try {
|
|
151
|
+
const title = document.title;
|
|
152
|
+
this.sendResponse(requestId, { title });
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
this.sendError(requestId, `Error getting title: ${error.message}`);
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (message.type === "get_elements") {
|
|
160
|
+
// Handle request to get elements
|
|
161
|
+
const requestId = message.requestId;
|
|
162
|
+
const selector = message.selector;
|
|
163
|
+
try {
|
|
164
|
+
const elements = [...document.querySelectorAll(selector)];
|
|
165
|
+
const result = elements.map((el) => ({
|
|
166
|
+
tagName: el.tagName,
|
|
167
|
+
id: el.id,
|
|
168
|
+
className: el.className,
|
|
169
|
+
textContent: el.textContent?.trim().substring(0, 500) || "",
|
|
170
|
+
attributes: [...el.attributes].reduce((attrs, attr) => {
|
|
171
|
+
attrs[attr.name] = attr.value;
|
|
172
|
+
return attrs;
|
|
173
|
+
}, {}),
|
|
174
|
+
}));
|
|
175
|
+
this.sendResponse(requestId, { elements: result });
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
this.sendError(requestId, `Error getting elements: ${error.message}`);
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (message.type === "capture_screenshot") {
|
|
183
|
+
// Handle screenshot request
|
|
184
|
+
const requestId = message.requestId;
|
|
185
|
+
const selector = message.selector || "body";
|
|
186
|
+
// Process screenshot asynchronously
|
|
187
|
+
(async () => {
|
|
188
|
+
try {
|
|
189
|
+
// Ensure html2canvas is loaded
|
|
190
|
+
await this.loadHtml2Canvas();
|
|
191
|
+
const element = document.querySelector(selector);
|
|
192
|
+
if (!element) {
|
|
193
|
+
this.sendError(requestId, `Element not found: ${selector}`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// Use html2canvas for screenshot, add config for better compatibility
|
|
197
|
+
const html2canvasOptions = {
|
|
198
|
+
allowTaint: true,
|
|
199
|
+
useCORS: true,
|
|
200
|
+
logging: false,
|
|
201
|
+
scale: window.devicePixelRatio || 1,
|
|
202
|
+
backgroundColor: null, // Transparent background
|
|
203
|
+
removeContainer: true, // Remove temporary container
|
|
204
|
+
// Calculate actual element size and position
|
|
205
|
+
x: 0,
|
|
206
|
+
y: 0,
|
|
207
|
+
scrollX: 0,
|
|
208
|
+
scrollY: 0,
|
|
209
|
+
// Get actual element width and height
|
|
210
|
+
width: element.offsetWidth,
|
|
211
|
+
height: element.offsetHeight,
|
|
212
|
+
};
|
|
213
|
+
const html2canvas = window.html2canvas;
|
|
214
|
+
if (!html2canvas) {
|
|
215
|
+
this.sendError(requestId, "html2canvas library not properly loaded");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
let canvas = await html2canvas(element, html2canvasOptions);
|
|
219
|
+
if (!canvas) {
|
|
220
|
+
this.sendError(requestId, "Screenshot failed: unable to create canvas");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// Crop canvas, remove excess white space
|
|
224
|
+
const context = canvas.getContext("2d");
|
|
225
|
+
if (context) {
|
|
226
|
+
// Try to detect content area, remove whitespace
|
|
227
|
+
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
228
|
+
const bounds = this.getContentBounds(imageData);
|
|
229
|
+
if (bounds) {
|
|
230
|
+
// If content boundaries found, create a new cropped canvas
|
|
231
|
+
const croppedCanvas = document.createElement("canvas");
|
|
232
|
+
croppedCanvas.width = bounds.width;
|
|
233
|
+
croppedCanvas.height = bounds.height;
|
|
234
|
+
const croppedContext = croppedCanvas.getContext("2d");
|
|
235
|
+
if (croppedContext) {
|
|
236
|
+
croppedContext.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
|
|
237
|
+
// Use cropped canvas
|
|
238
|
+
canvas = croppedCanvas;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Compress image quality to reduce data size, use PNG format to maintain transparency
|
|
243
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
244
|
+
this.sendResponse(requestId, { imageDataUrl: dataUrl });
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.error("[MCP Client] Screenshot error:", error);
|
|
248
|
+
this.sendError(requestId, `Screenshot error: ${error.message}`);
|
|
249
|
+
}
|
|
250
|
+
})();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (message.type === "get_page_url") {
|
|
254
|
+
// Handle request to get page URL
|
|
255
|
+
const requestId = message.requestId;
|
|
256
|
+
try {
|
|
257
|
+
const url = window.location.href;
|
|
258
|
+
this.sendResponse(requestId, { url });
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
this.sendError(requestId, `Error getting URL: ${error.message}`);
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (message.type === "click_element") {
|
|
266
|
+
// Handle request to click element
|
|
267
|
+
const requestId = message.requestId;
|
|
268
|
+
const selector = message.selector;
|
|
269
|
+
try {
|
|
270
|
+
const element = document.querySelector(selector);
|
|
271
|
+
if (!element) {
|
|
272
|
+
this.sendError(requestId, `Element not found: ${selector}`);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
element.click();
|
|
276
|
+
this.sendResponse(requestId, {
|
|
277
|
+
success: true,
|
|
278
|
+
message: `Successfully clicked element: ${selector}`,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
this.sendError(requestId, `Error clicking element: ${error.message}`);
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (message.type === "input_text") {
|
|
287
|
+
// Handle request to input text
|
|
288
|
+
const requestId = message.requestId;
|
|
289
|
+
const selector = message.selector;
|
|
290
|
+
const text = message.text;
|
|
291
|
+
try {
|
|
292
|
+
const input = document.querySelector(selector);
|
|
293
|
+
if (!input) {
|
|
294
|
+
this.sendError(requestId, `Input field not found: ${selector}`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (input.tagName !== "INPUT" && input.tagName !== "TEXTAREA") {
|
|
298
|
+
this.sendError(requestId, `Selected element is not an input field: ${input.tagName}`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
input.value = text;
|
|
302
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
303
|
+
this.sendResponse(requestId, {
|
|
304
|
+
success: true,
|
|
305
|
+
message: `Successfully input text to: ${selector}`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.sendError(requestId, `Error inputting text: ${error.message}`);
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
// Handle other message types
|
|
314
|
+
switch (message.type) {
|
|
315
|
+
case "command_result":
|
|
316
|
+
console.info(`%c[MCP Server] ${message.payload?.result}`, "color: blue");
|
|
317
|
+
break;
|
|
318
|
+
case "error":
|
|
319
|
+
console.error(`[MCP Server] Error: ${message.payload?.error}`);
|
|
320
|
+
break;
|
|
321
|
+
case "connection_status":
|
|
322
|
+
console.info(`%c[MCP Server] ${message.message}`, "color: green");
|
|
323
|
+
break;
|
|
324
|
+
default:
|
|
325
|
+
// Only log message type, not full message content
|
|
326
|
+
console.info(`[MCP Server] Received message type: ${message.type}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Send response
|
|
331
|
+
*/
|
|
332
|
+
sendResponse(requestId, data) {
|
|
333
|
+
if (!this.connected || !this.ws) {
|
|
334
|
+
console.error("[MCP Client] Not connected to server, cannot send response");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const response = {
|
|
338
|
+
requestId,
|
|
339
|
+
...data,
|
|
340
|
+
};
|
|
341
|
+
this.ws.send(JSON.stringify(response));
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Send error response
|
|
345
|
+
*/
|
|
346
|
+
sendError(requestId, errorMessage) {
|
|
347
|
+
if (!this.connected || !this.ws) {
|
|
348
|
+
console.error("[MCP Client] Not connected to server, cannot send error response");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const response = {
|
|
352
|
+
requestId,
|
|
353
|
+
error: errorMessage,
|
|
354
|
+
};
|
|
355
|
+
this.ws.send(JSON.stringify(response));
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Send command to server
|
|
359
|
+
*/
|
|
360
|
+
sendCommand(command) {
|
|
361
|
+
if (!this.connected || !this.ws) {
|
|
362
|
+
console.error("[MCP Client] Not connected to server");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const message = {
|
|
366
|
+
type: "command",
|
|
367
|
+
payload: { command },
|
|
368
|
+
};
|
|
369
|
+
this.ws.send(JSON.stringify(message));
|
|
370
|
+
this.commandHistory.push(command);
|
|
371
|
+
this.historyIndex = this.commandHistory.length;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Register console commands
|
|
375
|
+
*/
|
|
376
|
+
registerConsoleCommands() {
|
|
377
|
+
// Define global commands
|
|
378
|
+
window.mcp = {
|
|
379
|
+
exec: (command) => {
|
|
380
|
+
this.sendCommand(command);
|
|
381
|
+
return "Command sent";
|
|
382
|
+
},
|
|
383
|
+
disconnect: () => {
|
|
384
|
+
if (this.ws) {
|
|
385
|
+
this.ws.close();
|
|
386
|
+
this.ws = null;
|
|
387
|
+
}
|
|
388
|
+
return "Disconnected";
|
|
389
|
+
},
|
|
390
|
+
reconnect: () => {
|
|
391
|
+
this.connect();
|
|
392
|
+
return "Reconnecting...";
|
|
393
|
+
},
|
|
394
|
+
help: () => {
|
|
395
|
+
return `
|
|
396
|
+
MCP Client Commands:
|
|
397
|
+
mcp.exec(command) - Execute a command
|
|
398
|
+
mcp.disconnect() - Disconnect from server
|
|
399
|
+
mcp.reconnect() - Reconnect to server
|
|
400
|
+
mcp.help() - Show help information
|
|
401
|
+
`;
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
console.info("%c[MCP Client] Console commands registered, use mcp.help() to see available commands", "color: green");
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Get image content boundaries, remove excess whitespace
|
|
408
|
+
*/
|
|
409
|
+
getContentBounds(imageData) {
|
|
410
|
+
const { width, height, data } = imageData;
|
|
411
|
+
let minX = width;
|
|
412
|
+
let minY = height;
|
|
413
|
+
let maxX = 0;
|
|
414
|
+
let maxY = 0;
|
|
415
|
+
let hasContent = false;
|
|
416
|
+
// Iterate through pixel data, find boundaries of non-transparent pixels
|
|
417
|
+
for (let y = 0; y < height; y++) {
|
|
418
|
+
for (let x = 0; x < width; x++) {
|
|
419
|
+
const alpha = data[(y * width + x) * 4 + 3]; // Alpha channel
|
|
420
|
+
if (alpha > 10) {
|
|
421
|
+
// Non-transparent pixel (allowing some slight transparency)
|
|
422
|
+
hasContent = true;
|
|
423
|
+
minX = Math.min(minX, x);
|
|
424
|
+
minY = Math.min(minY, y);
|
|
425
|
+
maxX = Math.max(maxX, x);
|
|
426
|
+
maxY = Math.max(maxY, y);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!hasContent) {
|
|
431
|
+
return null; // No content found
|
|
432
|
+
}
|
|
433
|
+
// Add some padding
|
|
434
|
+
const padding = 10;
|
|
435
|
+
minX = Math.max(0, minX - padding);
|
|
436
|
+
minY = Math.max(0, minY - padding);
|
|
437
|
+
maxX = Math.min(width - 1, maxX + padding);
|
|
438
|
+
maxY = Math.min(height - 1, maxY + padding);
|
|
439
|
+
return {
|
|
440
|
+
left: minX,
|
|
441
|
+
top: minY,
|
|
442
|
+
width: maxX - minX + 1,
|
|
443
|
+
height: maxY - minY + 1,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Create and connect client instance
|
|
448
|
+
const client = new BrowserConsoleMCP();
|
|
449
|
+
client.connect();
|
|
450
|
+
// Export client instance
|
|
451
|
+
export default client;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Console MCP Server
|
|
3
|
+
*
|
|
4
|
+
* This server runs in Cursor and handles commands from the browser console
|
|
5
|
+
*/
|
|
6
|
+
import "node:process";
|
|
7
|
+
declare class BrowserConsoleMCPServer {
|
|
8
|
+
private wss;
|
|
9
|
+
private clients;
|
|
10
|
+
private port;
|
|
11
|
+
private staticServer;
|
|
12
|
+
private httpServer;
|
|
13
|
+
constructor(port?: number, staticPort?: number);
|
|
14
|
+
/**
|
|
15
|
+
* Start server
|
|
16
|
+
*/
|
|
17
|
+
start(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Setup WebSocket server
|
|
20
|
+
*/
|
|
21
|
+
private setupWebSocketServer;
|
|
22
|
+
/**
|
|
23
|
+
* Handle client messages
|
|
24
|
+
*/
|
|
25
|
+
private handleClientMessage;
|
|
26
|
+
/**
|
|
27
|
+
* Execute command
|
|
28
|
+
*/
|
|
29
|
+
private executeCommand;
|
|
30
|
+
/**
|
|
31
|
+
* Send result to client
|
|
32
|
+
*/
|
|
33
|
+
private sendResultToClient;
|
|
34
|
+
/**
|
|
35
|
+
* Send error to client
|
|
36
|
+
*/
|
|
37
|
+
private sendErrorToClient;
|
|
38
|
+
/**
|
|
39
|
+
* Generate client ID
|
|
40
|
+
*/
|
|
41
|
+
private generateClientId;
|
|
42
|
+
/**
|
|
43
|
+
* Stop server
|
|
44
|
+
*/
|
|
45
|
+
stop(): void;
|
|
46
|
+
}
|
|
47
|
+
declare const server: BrowserConsoleMCPServer;
|
|
48
|
+
export default server;
|