@cgaspard/webappmcp
Version:
WebApp MCP - Model Context Protocol integration for web applications with server-side debugging tools
3 lines (2 loc) • 31.9 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).WebAppMCP={})}(this,function(exports){"use strict";class MCPDevTools{constructor(e={}){this.container=null,this.isExpanded=!1,this.logs=[],this.maxLogs=500,this.connectionStatus="disconnected",this.config={position:"bottom-right",theme:"dark",...e},this.addStyles(),this.createDevToolsUI(),this.setupEventListeners()}createDevToolsUI(){this.container=document.createElement("div"),this.container.id="mcp-devtools",this.container.className=`mcp-theme-${this.config.theme} mcp-position-${this.config.position}`,this.container.innerHTML='\n <div class="mcp-devtools-indicator" id="mcp-indicator">\n <div class="mcp-status-dot" id="mcp-status-dot"></div>\n <span class="mcp-label">MCP</span>\n </div>\n <div class="mcp-devtools-panel" id="mcp-panel" style="display: none;">\n <div class="mcp-devtools-header">\n <span>MCP DevTools</span>\n <div class="mcp-header-controls">\n <button class="mcp-theme-toggle" id="mcp-theme-btn" title="Toggle theme">🌓</button>\n <button class="mcp-close-btn" id="mcp-close-btn">×</button>\n </div>\n </div>\n <div class="mcp-devtools-content">\n <div class="mcp-status-section">\n <div class="mcp-status-item">\n <label>WebSocket:</label>\n <span id="mcp-ws-status">Disconnected</span>\n </div>\n <div class="mcp-status-item">\n <label>MCP Server:</label>\n <span id="mcp-server-status">Disconnected</span>\n </div>\n </div>\n <div class="mcp-logs-section">\n <div class="mcp-logs-header">\n <span>Activity Log</span>\n <div class="mcp-logs-controls">\n <label class="mcp-checkbox">\n <input type="checkbox" id="mcp-autoscroll" checked>\n Auto-scroll\n </label>\n <button class="mcp-clear-btn" id="mcp-clear-btn">Clear</button>\n </div>\n </div>\n <div class="mcp-logs-container" id="mcp-logs"></div>\n </div>\n </div>\n </div>\n ',this.addStyles(),document.body.appendChild(this.container)}addStyles(){const e=document.createElement("style");e.textContent="\n #mcp-devtools {\n position: fixed;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n \n /* Positioning */\n #mcp-devtools.mcp-position-bottom-right {\n bottom: 20px;\n right: 20px;\n }\n \n #mcp-devtools.mcp-position-bottom-left {\n bottom: 20px;\n left: 20px;\n }\n \n #mcp-devtools.mcp-position-top-right {\n top: 20px;\n right: 20px;\n }\n \n #mcp-devtools.mcp-position-top-left {\n top: 20px;\n left: 20px;\n }\n\n .mcp-devtools-indicator {\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n background: rgba(255, 255, 255, 0.9);\n border-radius: 4px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n\n .mcp-label {\n font-size: 10px;\n font-weight: 600;\n color: #495057;\n user-select: none;\n }\n\n .mcp-status-dot {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #dc3545;\n border: 2px solid rgba(255, 255, 255, 0.8);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n transition: background-color 0.3s ease;\n }\n\n .mcp-status-dot.connecting {\n background-color: #ffc107;\n animation: pulse 1.5s infinite;\n }\n\n .mcp-status-dot.connected {\n background-color: #28a745;\n }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n\n .mcp-devtools-panel {\n position: absolute;\n width: 500px;\n height: 400px;\n background: white;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n display: flex;\n flex-direction: column;\n }\n \n /* Panel positioning based on indicator position */\n .mcp-position-bottom-right .mcp-devtools-panel {\n bottom: 40px;\n right: 0;\n }\n \n .mcp-position-bottom-left .mcp-devtools-panel {\n bottom: 40px;\n left: 0;\n }\n \n .mcp-position-top-right .mcp-devtools-panel {\n top: 40px;\n right: 0;\n }\n \n .mcp-position-top-left .mcp-devtools-panel {\n top: 40px;\n left: 0;\n }\n\n .mcp-devtools-header {\n padding: 12px 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n border-radius: 8px 8px 0 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-weight: 600;\n font-size: 14px;\n }\n\n .mcp-close-btn {\n background: none;\n border: none;\n font-size: 18px;\n cursor: pointer;\n color: #6c757d;\n padding: 0;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .mcp-close-btn:hover {\n color: #495057;\n }\n\n .mcp-devtools-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .mcp-status-section {\n padding: 12px 16px;\n border-bottom: 1px solid #e0e0e0;\n background: #f8f9fa;\n }\n\n .mcp-status-item {\n display: flex;\n justify-content: space-between;\n margin-bottom: 4px;\n font-size: 12px;\n }\n\n .mcp-status-item label {\n font-weight: 500;\n color: #495057;\n }\n\n .mcp-status-item span {\n color: #6c757d;\n }\n\n .mcp-logs-section {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .mcp-logs-header {\n padding: 8px 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: 12px;\n font-weight: 500;\n }\n\n .mcp-clear-btn {\n background: none;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n padding: 2px 8px;\n font-size: 11px;\n cursor: pointer;\n color: #6c757d;\n }\n\n .mcp-clear-btn:hover {\n background: #e9ecef;\n }\n\n .mcp-logs-container {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n font-size: 11px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n }\n\n .mcp-log-entry {\n margin-bottom: 4px;\n padding: 4px 8px;\n border-radius: 4px;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .mcp-log-entry.info {\n background: #e7f3ff;\n color: #0c5460;\n }\n\n .mcp-log-entry.warning {\n background: #fff3cd;\n color: #856404;\n }\n\n .mcp-log-entry.error {\n background: #f8d7da;\n color: #721c24;\n }\n\n .mcp-log-timestamp {\n color: #6c757d;\n font-size: 10px;\n }\n \n /* Header controls */\n .mcp-header-controls {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n \n .mcp-theme-toggle {\n background: none;\n border: none;\n font-size: 16px;\n cursor: pointer;\n padding: 0;\n opacity: 0.6;\n transition: opacity 0.2s;\n }\n \n .mcp-theme-toggle:hover {\n opacity: 1;\n }\n \n /* Log controls */\n .mcp-logs-controls {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n \n .mcp-checkbox {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n cursor: pointer;\n }\n \n .mcp-checkbox input {\n cursor: pointer;\n }\n \n /* Tool execution logs */\n .mcp-log-entry.tool {\n background: #e7f3ff;\n color: #004085;\n border-left: 3px solid #004085;\n }\n \n .mcp-tool-details {\n font-size: 10px;\n margin-top: 4px;\n padding-left: 16px;\n opacity: 0.8;\n }\n \n /* Dark theme */\n .mcp-theme-dark .mcp-devtools-indicator {\n background: rgba(30, 30, 30, 0.9);\n border-color: rgba(255, 255, 255, 0.1);\n }\n \n .mcp-theme-dark .mcp-label {\n color: #e0e0e0;\n }\n \n .mcp-theme-dark .mcp-devtools-panel {\n background: #1e1e1e;\n border-color: #333;\n }\n \n .mcp-theme-dark .mcp-devtools-header {\n background: #2d2d2d;\n border-color: #333;\n color: #e0e0e0;\n }\n \n .mcp-theme-dark .mcp-devtools-content {\n background: #1e1e1e;\n }\n \n .mcp-theme-dark .mcp-status-section {\n background: #2d2d2d;\n border-color: #333;\n }\n \n .mcp-theme-dark .mcp-status-item label {\n color: #b0b0b0;\n }\n \n .mcp-theme-dark .mcp-status-item span {\n color: #e0e0e0;\n }\n \n .mcp-theme-dark .mcp-logs-header {\n background: #2d2d2d;\n border-color: #333;\n color: #e0e0e0;\n }\n \n .mcp-theme-dark .mcp-logs-container {\n background: #252525;\n border-color: #333;\n }\n \n .mcp-theme-dark .mcp-log-entry {\n background: #2d2d2d;\n color: #e0e0e0;\n border-color: #333;\n }\n \n .mcp-theme-dark .mcp-log-entry.warning {\n background: #4a3800;\n color: #ffc107;\n }\n \n .mcp-theme-dark .mcp-log-entry.error {\n background: #4a0000;\n color: #ff6b6b;\n }\n \n .mcp-theme-dark .mcp-log-entry.tool {\n background: #003366;\n color: #66b3ff;\n border-left-color: #66b3ff;\n }\n \n .mcp-theme-dark .mcp-log-timestamp {\n color: #888;\n }\n \n .mcp-theme-dark .mcp-clear-btn,\n .mcp-theme-dark .mcp-close-btn {\n color: #e0e0e0;\n border-color: #555;\n }\n \n .mcp-theme-dark .mcp-clear-btn:hover,\n .mcp-theme-dark .mcp-close-btn:hover {\n background: #444;\n }\n }\n ",document.head.appendChild(e)}setupEventListeners(){const e=this.container?.querySelector("#mcp-indicator"),t=this.container?.querySelector("#mcp-close-btn"),n=this.container?.querySelector("#mcp-clear-btn"),o=this.container?.querySelector("#mcp-theme-btn");e?.addEventListener("click",()=>this.togglePanel()),t?.addEventListener("click",()=>this.togglePanel()),n?.addEventListener("click",()=>this.clearLogs()),o?.addEventListener("click",()=>this.toggleTheme())}toggleTheme(){this.container&&(this.config.theme="light"===this.config.theme?"dark":"light",this.container.className=`mcp-theme-${this.config.theme} mcp-position-${this.config.position}`)}togglePanel(){this.isExpanded=!this.isExpanded;const e=this.container?.querySelector("#mcp-panel");e&&(e.style.display=this.isExpanded?"flex":"none")}setConnectionStatus(e){this.connectionStatus=e;const t=this.container?.querySelector("#mcp-status-dot"),n=this.container?.querySelector("#mcp-ws-status"),o=this.container?.querySelector("#mcp-server-status");if(t&&(t.className=`mcp-status-dot ${e}`),n&&o){const t=e.charAt(0).toUpperCase()+e.slice(1);n.textContent=t,o.textContent=t}this.addLog("info","client",`Connection status: ${e}`)}addLog(e,t,n,o){const s={timestamp:(new Date).toISOString(),level:e,source:t,message:n,data:o};this.logs.unshift(s),this.logs.length>this.maxLogs&&(this.logs=this.logs.slice(0,this.maxLogs)),this.renderLogs()}renderLogs(){const e=this.container?.querySelector("#mcp-logs");if(!e)return;e.innerHTML=this.logs.map(e=>{const t=new Date(e.timestamp).toLocaleTimeString();if("tool"===e.level&&e.data){const n=e.data;let o="";if(n.args&&(o+=`<div class="mcp-tool-details">Args: ${this.safeStringify(n.args)}</div>`),n.error&&(o+=`<div class="mcp-tool-details">Error: ${n.error}</div>`),n.result&&"completed"===n.status){const e=this.safeStringify(n.result);e.length>100?o+=`<div class="mcp-tool-details">Result: ${e.substring(0,100)}...</div>`:o+=`<div class="mcp-tool-details">Result: ${e}</div>`}return`\n <div class="mcp-log-entry ${e.level}">\n <span class="mcp-log-timestamp">[${t}]</span> [${e.source.toUpperCase()}] ${e.message}\n ${o}\n </div>\n `}{const n=e.data?` ${this.safeStringify(e.data)}`:"";return`\n <div class="mcp-log-entry ${e.level}">\n <span class="mcp-log-timestamp">[${t}]</span> [${e.source.toUpperCase()}] ${e.message}${n}\n </div>\n `}}).join("");const t=this.container?.querySelector("#mcp-autoscroll");t?.checked&&(e.scrollTop=0)}safeStringify(e){try{if(null==e)return String(e);if("string"==typeof e||"number"==typeof e||"boolean"==typeof e)return String(e);const t=new Set;return JSON.stringify(e,(e,n)=>{if("object"==typeof n&&null!==n){if(t.has(n))return"[Circular Reference]";t.add(n)}return n})}catch(e){return`[Error stringifying: ${e instanceof Error?e.message:"Unknown error"}]`}}clearLogs(){this.logs=[],this.renderLogs()}logWebSocketEvent(e,t){this.addLog("info","websocket",e,t)}logMCPEvent(e,t){this.addLog("info","mcp",e,t)}logError(e,t,n){this.addLog("error",e,t,n)}logToolExecution(e,t,n,o,s,r){const i=null===n?"started":n?"completed":"failed",c={tool:e,status:i,args:t,executionTime:s?`${s}ms`:void 0,result:n&&r?r:void 0,error:n||"Success"===o?void 0:o},a=`Tool ${e} ${i}${s?` (${s}ms)`:""}`;this.addLog(!1===n?"error":"tool","tool",a,c)}}class WebAppMCPClient{get isConnected(){return this._isConnected&&this.ws?.readyState===WebSocket.OPEN}constructor(e){this.ws=null,this.reconnectAttempts=0,this.messageHandlers=new Map,this.consoleLogs=[],this._isConnected=!1,this.devTools=null,this.pluginHandlers={},this.config={reconnectInterval:5e3,maxReconnectAttempts:10,enableDevTools:!0,debug:!1,enableConnection:!0,interceptConsole:!0,enabledTools:[],devToolsPosition:"bottom-right",devToolsTheme:"dark",...e},this.config.interceptConsole&&this.setupConsoleInterception(),this.loadHtml2Canvas(),this.config.enableDevTools&&this.config.enableConnection&&(this.devTools=new MCPDevTools({position:this.config.devToolsPosition,theme:this.config.devToolsTheme}),this.devTools.setConnectionStatus("disconnected"))}log(...e){this.config.debug&&console.log("[webappmcp]",...e)}logError(...e){console.error("[webappmcp]",...e)}connect(){if(this.config.enableConnection){if(!this.ws||this.ws.readyState!==WebSocket.OPEN){this.devTools?.setConnectionStatus("connecting"),this.devTools?.logWebSocketEvent("Attempting to connect",{url:this.config.serverUrl});try{const e=new URL(this.config.serverUrl),t={};this.config.authToken&&(t.Authorization=`Bearer ${this.config.authToken}`),this.config.authToken&&e.searchParams.set("token",this.config.authToken),this.ws=new WebSocket(e.toString()),this.setupWebSocketHandlers()}catch(e){this.logError("Failed to connect to WebApp MCP server:",e),this.devTools?.logError("websocket","Failed to connect",e),this.scheduleReconnect()}}}else this.log("Connection disabled by configuration")}disconnect(){this._isConnected=!1,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws&&(this.ws.close(),this.ws=null)}setupWebSocketHandlers(){this.ws&&(this.ws.onopen=()=>{this.log("Connected to WebApp MCP server"),this._isConnected=!0,this.reconnectAttempts=0,this.devTools?.setConnectionStatus("connected"),this.devTools?.logWebSocketEvent("Connected to WebApp MCP server"),this.sendMessage({type:"init",url:window.location.href})},this.ws.onmessage=e=>{try{const t=JSON.parse(e.data);this.devTools?.logWebSocketEvent("Message received",t),this.handleMessage(t)}catch(e){this.logError("Failed to parse WebSocket message:",e),this.devTools?.logError("websocket","Failed to parse message",e)}},this.ws.onerror=e=>{this.logError("WebSocket error:",e),this.devTools?.logError("websocket","WebSocket error",e)},this.ws.onclose=()=>{this.log("Disconnected from WebApp MCP server"),this._isConnected=!1,this.devTools?.setConnectionStatus("disconnected"),this.devTools?.logWebSocketEvent("Disconnected from WebApp MCP server"),this.scheduleReconnect()})}scheduleReconnect(){this.reconnectAttempts>=(this.config.maxReconnectAttempts||10)||this.reconnectTimer||(this.reconnectAttempts++,this.log(`Scheduling reconnect attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}`),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},this.config.reconnectInterval))}sendMessage(e){this.ws&&this.ws.readyState===WebSocket.OPEN?(this.ws.send(JSON.stringify(e)),this.devTools?.logWebSocketEvent("Message sent",e)):this.logError("WebSocket is not connected")}handleMessage(e){const{type:t,requestId:n,tool:o,args:s}=e;if(this.log("[WebApp Client] Received message:",JSON.stringify(e)),"connected"===t)return this.log("WebApp MCP client registered:",e.clientId),void this.devTools?.logMCPEvent("Client registered",{clientId:e.clientId});if("execute_tool"===t)return this.log(`[WebApp Client] Executing tool: ${o} with requestId: ${n}`),this.devTools?.logMCPEvent(`Executing tool: ${o}`,{requestId:n,args:s}),void this.executeToolHandler(n,o,s);if("plugin_extension"===t)return this.log("[WebApp Client] Loading plugin extension"),void this.loadPluginExtension(e.extension);const r=this.messageHandlers.get(t);r&&r(e)}async executeToolHandler(e,t,n){if(this.config.enabledTools&&this.config.enabledTools.length>0&&!this.config.enabledTools.includes(t)){const o=`Tool ${t} is not enabled`;return this.logError(o),this.devTools?.logToolExecution(t,n,!1,o),void this.sendMessage({type:"tool_response",requestId:e,success:!1,error:o})}this.log(`[WebApp Client] Executing tool handler for ${t}`),this.log("[WebApp Client] Tool args:",JSON.stringify(n)),this.devTools?.logToolExecution(t,n,null,"Started");const o=Date.now();try{let s;switch(t){case"dom_query":s=await this.domQuery(n);break;case"dom_get_properties":s=await this.domGetProperties(n);break;case"dom_get_text":s=await this.domGetText(n);break;case"dom_get_html":s=await this.domGetHTML(n);break;case"interaction_click":s=await this.interactionClick(n);break;case"interaction_type":s=await this.interactionType(n);break;case"interaction_scroll":s=await this.interactionScroll(n);break;case"interaction_hover":s=await this.interactionHover(n);break;case"capture_screenshot":s=await this.captureScreenshot(n);break;case"capture_element_screenshot":s=await this.captureElementScreenshot(n);break;case"state_get_variable":s=await this.stateGetVariable(n);break;case"state_local_storage":s=await this.stateLocalStorage(n);break;case"console_get_logs":s=await this.consoleGetLogs(n);break;case"console_save_to_file":s=await this.consoleSaveToFile(n);break;case"dom_manipulate":s=await this.domManipulate(n);break;case"javascript_inject":s=await this.javascriptInject(n);break;case"webapp_list_clients":s=await this.webappListClients(n);break;case"execute_javascript":s=await this.executeJavascript(n);break;default:if(!this.pluginHandlers||!this.pluginHandlers[t])throw new Error(`Unknown tool: ${t}`);s=await this.pluginHandlers[t](n)}const r=Date.now()-o;this.log("[WebApp Client] Tool execution successful, sending response"),this.devTools?.logToolExecution(t,n,!0,"Success",r,s),this.sendMessage({type:"tool_response",requestId:e,result:s,success:!0})}catch(s){const r=Date.now()-o,i=s instanceof Error?s.message:String(s);this.logError("[WebApp Client] Tool execution failed:",s),this.devTools?.logToolExecution(t,n,!1,i,r),this.sendMessage({type:"tool_response",requestId:e,success:!1,error:i})}}loadHtml2Canvas(){if(void 0!==window.html2canvas)return;const e=document.createElement("script");e.src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js",e.async=!0,e.onload=()=>{this.config.debug&&console.log("[WebAppMCP] html2canvas loaded successfully")},e.onerror=()=>{console.warn("[WebAppMCP] Failed to load html2canvas - screenshots will use fallback mode")},document.head.appendChild(e)}setupConsoleInterception(){const e={log:console.log,info:console.info,warn:console.warn,error:console.error},t=t=>(...n)=>{this.consoleLogs.push({level:t,timestamp:(new Date).toISOString(),args:n.map(e=>{try{return"object"==typeof e?JSON.stringify(e):String(e)}catch{return String(e)}})}),this.consoleLogs.length>1e3&&this.consoleLogs.shift(),e[t](...n)};console.log=t("log"),console.info=t("info"),console.warn=t("warn"),console.error=t("error")}async domQuery(e){const{selector:t,limit:n=10}=e;return{elements:Array.from(document.querySelectorAll(t)).slice(0,n).map(e=>({selector:t,tagName:e.tagName.toLowerCase(),id:e.id||void 0,className:e.className||void 0,text:e.textContent?.trim().substring(0,100),attributes:(()=>{const t={};for(let n=0;n<e.attributes.length;n++){const o=e.attributes[n];t[o.name]=o.value}return t})()}))}}async domGetProperties(e){const{selector:t,properties:n=[]}=e,o=document.querySelector(t);if(!o)throw new Error(`Element not found: ${t}`);const s={};for(const e of n)try{s[e]=o[e]}catch{s[e]=void 0}return s}async domGetText(e){const{selector:t,includeHidden:n=!1}=e,o=document.querySelectorAll(t),s=[];return o.forEach(e=>{if(n||null!==e.offsetParent){const t=e.textContent?.trim();t&&s.push(t)}}),{texts:s}}async domGetHTML(e){const{selector:t,outerHTML:n=!1}=e,o=document.querySelector(t);if(!o)throw new Error(`Element not found: ${t}`);return{html:n?o.outerHTML:o.innerHTML}}async interactionClick(e){const{selector:t,button:n="left"}=e,o=document.querySelector(t);if(!o)throw new Error(`Element not found: ${t}`);const s=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0,button:"right"===n?2:"middle"===n?1:0});return o.dispatchEvent(s),{success:!0}}async interactionType(e){const{selector:t,text:n,clear:o=!1}=e,s=document.querySelector(t);if(!s)throw new Error(`Element not found: ${t}`);return o&&(s.value=""),s.focus(),s.value+=n,s.dispatchEvent(new Event("input",{bubbles:!0})),s.dispatchEvent(new Event("change",{bubbles:!0})),{success:!0}}async interactionScroll(e){const{selector:t,direction:n,amount:o=100}=e,s=t?document.querySelector(t):window;if(!s&&t)throw new Error(`Element not found: ${t}`);const r={behavior:"smooth"};return"up"===n||"down"===n?r.top="down"===n?o:-o:r.left="right"===n?o:-o,s===window?window.scrollBy(r):s.scrollBy(r),{success:!0}}async interactionHover(e){const{selector:t}=e,n=document.querySelector(t);if(!n)throw new Error(`Element not found: ${t}`);return n.dispatchEvent(new MouseEvent("mouseenter",{view:window,bubbles:!0,cancelable:!0})),n.dispatchEvent(new MouseEvent("mouseover",{view:window,bubbles:!0,cancelable:!0})),{success:!0}}async captureScreenshot(e){const{fullPage:t=!0,format:n="png"}=e;try{const e=t?Math.max(document.documentElement.scrollWidth,document.body.scrollWidth,document.documentElement.offsetWidth,document.body.offsetWidth,document.documentElement.clientWidth):window.innerWidth,o=t?Math.max(document.documentElement.scrollHeight,document.body.scrollHeight,document.documentElement.offsetHeight,document.body.offsetHeight,document.documentElement.clientHeight):window.innerHeight,s=document.createElement("canvas");s.width=e,s.height=o;const r=s.getContext("2d");if(!r)throw new Error("Failed to create canvas context");if(void 0!==window.html2canvas){const s=window.html2canvas,r=await s(document.body,{width:e,height:o,windowWidth:e,windowHeight:o,x:0,y:0,scrollX:t?0:window.scrollX,scrollY:t?0:window.scrollY,useCORS:!0,allowTaint:!0});return{success:!0,dataUrl:r.toDataURL(`image/${n}`),width:e,height:o,message:"Screenshot captured successfully"}}const i=window.getComputedStyle(document.body).backgroundColor||"#ffffff";r.fillStyle=i,r.fillRect(0,0,e,o),r.fillStyle="#666",r.font="14px system-ui, -apple-system, sans-serif",r.textAlign="left";const c=[`Page Title: ${document.title}`,`URL: ${window.location.href}`,`Dimensions: ${e}x${o}`,"","Note: For full screenshot functionality, include html2canvas library:",'<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"><\/script>'];let a=30;c.forEach(e=>{r.fillText(e,20,a),a+=25}),r.strokeStyle="#ddd",r.lineWidth=2,r.strokeRect(1,1,e-2,o-2);return{success:!0,dataUrl:s.toDataURL(`image/${n}`),width:e,height:o,message:"Screenshot captured (basic mode - add html2canvas for full rendering)"}}catch(e){throw new Error(`Failed to capture screenshot: ${e}`)}}async captureElementScreenshot(e){const{selector:t,format:n="png"}=e;if(!t)throw new Error("Selector is required for element screenshot");const o=document.querySelector(t);if(!o)throw new Error(`Element not found: ${t}`);try{const e=o.getBoundingClientRect();if(void 0!==window.html2canvas){const s=window.html2canvas,r=await s(o,{width:e.width,height:e.height,x:e.left+window.scrollX,y:e.top+window.scrollY,scrollX:-e.left,scrollY:-e.top,useCORS:!0,allowTaint:!0});return{success:!0,dataUrl:r.toDataURL(`image/${n}`),width:e.width,height:e.height,selector:t,message:"Element screenshot captured successfully"}}const s=document.createElement("canvas"),r=s.getContext("2d");if(!r)throw new Error("Failed to create canvas context");s.width=e.width,s.height=e.height;const i=window.getComputedStyle(o),c=i.backgroundColor||"#ffffff";r.fillStyle=c,r.fillRect(0,0,e.width,e.height),r.strokeStyle=i.borderColor||"#ddd",r.lineWidth=parseInt(i.borderWidth)||1,r.strokeRect(0,0,e.width,e.height),r.fillStyle="#666",r.font="12px system-ui, -apple-system, sans-serif",r.textAlign="center";const a=[`Element: ${t}`,`Size: ${Math.round(e.width)}x${Math.round(e.height)}`,`Tag: ${o.tagName.toLowerCase()}`,o.className?`Class: ${o.className}`:"","Add html2canvas for full rendering"].filter(Boolean);let l=Math.max(20,e.height/2-15*a.length/2);a.forEach(t=>{r.fillText(t,e.width/2,l),l+=15});return{success:!0,dataUrl:s.toDataURL(`image/${n}`),width:e.width,height:e.height,selector:t,message:"Element screenshot captured (basic mode - add html2canvas for full rendering)"}}catch(e){throw new Error(`Failed to capture element screenshot: ${e}`)}}async stateGetVariable(e){const{path:t}=e,n=t.split(".");let o=window;for(const e of n){if(!o||"object"!=typeof o||!(e in o))throw new Error(`Variable not found: ${t}`);o=o[e]}return{value:o}}async stateLocalStorage(e){const{operation:t,key:n,value:o}=e;switch(t){case"get":return{value:localStorage.getItem(n)};case"set":return localStorage.setItem(n,o),{success:!0};case"remove":return localStorage.removeItem(n),{success:!0};case"clear":return localStorage.clear(),{success:!0};case"getAll":const e={};for(let t=0;t<localStorage.length;t++){const n=localStorage.key(t);n&&(e[n]=localStorage.getItem(n)||"")}return{items:e};default:throw new Error(`Unknown localStorage operation: ${t}`)}}async consoleGetLogs(e){const{level:t="all",limit:n=100,regex:o}=e;let s=this.consoleLogs;if("all"!==t&&(s=s.filter(e=>e.level===t)),o)try{const e=new RegExp(o);s=s.filter(t=>{const n=t.args.map(e=>{try{return"object"==typeof e?JSON.stringify(e):String(e)}catch{return String(e)}}).join(" ");return e.test(n)})}catch(e){throw new Error(`Invalid regex pattern: ${o}`)}return{logs:s.slice(-n)}}async consoleSaveToFile(e){const{level:t="all",format:n="json"}=e;let o=this.consoleLogs;return"all"!==t&&(o=o.filter(e=>e.level===t)),{logs:o,format:n}}async domManipulate(e){const{action:t,selector:n,value:o,attribute:s,property:r}=e;if(!n)throw new Error("Selector is required for DOM manipulation");const i=document.querySelector(n);if(!i)throw new Error(`Element not found: ${n}`);switch(t){case"setAttribute":if(!s||void 0===o)throw new Error("Attribute name and value are required for setAttribute");return i.setAttribute(s,o),{success:!0,message:`Set attribute ${s}="${o}" on ${n}`};case"removeAttribute":if(!s)throw new Error("Attribute name is required for removeAttribute");return i.removeAttribute(s),{success:!0,message:`Removed attribute ${s} from ${n}`};case"setProperty":if(!r||void 0===o)throw new Error("Property name and value are required for setProperty");return i[r]=o,{success:!0,message:`Set property ${r}=${o} on ${n}`};case"addClass":if(!o)throw new Error("Class name is required for addClass");return i.classList.add(o),{success:!0,message:`Added class "${o}" to ${n}`};case"removeClass":if(!o)throw new Error("Class name is required for removeClass");return i.classList.remove(o),{success:!0,message:`Removed class "${o}" from ${n}`};case"setInnerHTML":if(void 0===o)throw new Error("HTML content is required for setInnerHTML");return i.innerHTML=o,{success:!0,message:`Set innerHTML on ${n}`};case"setTextContent":if(void 0===o)throw new Error("Text content is required for setTextContent");return i.textContent=o,{success:!0,message:`Set textContent on ${n}`};case"setStyle":if(!r||void 0===o)throw new Error("Style property and value are required for setStyle");return i.style[r]=o,{success:!0,message:`Set style ${r}=${o} on ${n}`};case"remove":return i.remove(),{success:!0,message:`Removed element ${n}`};default:throw new Error(`Unknown DOM manipulation action: ${t}`)}}async javascriptInject(args){const{code:code,returnValue:returnValue=!1}=args;if(!code)throw new Error("JavaScript code is required");try{let result;if(returnValue)result=eval(code);else{const e=new Function(code);result=e()}return{success:!0,result:void 0!==result?result:null,message:"JavaScript executed successfully"}}catch(e){throw new Error(`JavaScript execution failed: ${e instanceof Error?e.message:"Unknown error"}`)}}async webappListClients(e){return{clients:[{id:"browser-client",type:"browser",url:window.location.href,userAgent:navigator.userAgent,connected:this.isConnected,timestamp:(new Date).toISOString()}]}}async executeJavascript(args){const{code:code,returnValue:returnValue=!1,async:async=!1}=args;if(!code)throw new Error("JavaScript code is required");try{let result;return async?result=returnValue?await eval(`(async () => { return ${code}; })()`):await eval(`(async () => { ${code}; })()`):returnValue?result=eval(`(() => { return ${code}; })()`):(eval(code),result=void 0),{success:!0,result:void 0!==result?result:null,message:"JavaScript executed successfully",executionTime:(new Date).toISOString()}}catch(e){throw new Error(`JavaScript execution failed: ${e instanceof Error?e.message:"Unknown error"}`)}}loadPluginExtension(extension){try{const pluginCode=`\n (function() {\n ${extension.code}\n })();\n `;eval(pluginCode),this.log("[WebApp Client] Plugin extension loaded successfully")}catch(e){this.logError("[WebApp Client] Failed to load plugin extension:",e)}}registerPluginHandler(e,t){this.pluginHandlers[e]=t,this.log(`[WebApp Client] Registered plugin handler for tool: ${e}`)}}"undefined"!=typeof window&&(window.WebAppMCP={WebAppMCPClient:WebAppMCPClient}),"undefined"!=typeof window&&(window.WebAppMCPClient=WebAppMCPClient,window.WebAppMCP={WebAppMCPClient:WebAppMCPClient}),exports.WebAppMCPClient=WebAppMCPClient});
//# sourceMappingURL=browser.min.js.map