UNPKG

@cgaspard/webappmcp

Version:

WebApp MCP - Model Context Protocol integration for web applications with server-side debugging tools

596 lines (538 loc) 17.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MCPDevTools = void 0; class MCPDevTools { constructor(config = {}) { this.container = null; this.isExpanded = false; this.logs = []; this.maxLogs = 500; this.connectionStatus = 'disconnected'; this.config = { position: 'bottom-right', theme: 'dark', ...config }; 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 = ` <div class="mcp-devtools-indicator" id="mcp-indicator"> <div class="mcp-status-dot" id="mcp-status-dot"></div> <span class="mcp-label">MCP</span> </div> <div class="mcp-devtools-panel" id="mcp-panel" style="display: none;"> <div class="mcp-devtools-header"> <span>MCP DevTools</span> <div class="mcp-header-controls"> <button class="mcp-theme-toggle" id="mcp-theme-btn" title="Toggle theme">🌓</button> <button class="mcp-close-btn" id="mcp-close-btn">×</button> </div> </div> <div class="mcp-devtools-content"> <div class="mcp-status-section"> <div class="mcp-status-item"> <label>WebSocket:</label> <span id="mcp-ws-status">Disconnected</span> </div> <div class="mcp-status-item"> <label>MCP Server:</label> <span id="mcp-server-status">Disconnected</span> </div> </div> <div class="mcp-logs-section"> <div class="mcp-logs-header"> <span>Activity Log</span> <div class="mcp-logs-controls"> <label class="mcp-checkbox"> <input type="checkbox" id="mcp-autoscroll" checked> Auto-scroll </label> <button class="mcp-clear-btn" id="mcp-clear-btn">Clear</button> </div> </div> <div class="mcp-logs-container" id="mcp-logs"></div> </div> </div> </div> `; this.addStyles(); document.body.appendChild(this.container); } addStyles() { const style = document.createElement('style'); style.textContent = ` #mcp-devtools { position: fixed; z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } /* Positioning */ #mcp-devtools.mcp-position-bottom-right { bottom: 20px; right: 20px; } #mcp-devtools.mcp-position-bottom-left { bottom: 20px; left: 20px; } #mcp-devtools.mcp-position-top-right { top: 20px; right: 20px; } #mcp-devtools.mcp-position-top-left { top: 20px; left: 20px; } .mcp-devtools-indicator { cursor: pointer; display: flex; align-items: center; gap: 4px; padding: 4px 8px; background: rgba(255, 255, 255, 0.9); border-radius: 4px; border: 1px solid rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .mcp-label { font-size: 10px; font-weight: 600; color: #495057; user-select: none; } .mcp-status-dot { width: 12px; height: 12px; border-radius: 50%; background-color: #dc3545; border: 2px solid rgba(255, 255, 255, 0.8); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); transition: background-color 0.3s ease; } .mcp-status-dot.connecting { background-color: #ffc107; animation: pulse 1.5s infinite; } .mcp-status-dot.connected { background-color: #28a745; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .mcp-devtools-panel { position: absolute; width: 500px; height: 400px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; } /* Panel positioning based on indicator position */ .mcp-position-bottom-right .mcp-devtools-panel { bottom: 40px; right: 0; } .mcp-position-bottom-left .mcp-devtools-panel { bottom: 40px; left: 0; } .mcp-position-top-right .mcp-devtools-panel { top: 40px; right: 0; } .mcp-position-top-left .mcp-devtools-panel { top: 40px; left: 0; } .mcp-devtools-header { padding: 12px 16px; background: #f8f9fa; border-bottom: 1px solid #e0e0e0; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center; font-weight: 600; font-size: 14px; } .mcp-close-btn { background: none; border: none; font-size: 18px; cursor: pointer; color: #6c757d; padding: 0; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; } .mcp-close-btn:hover { color: #495057; } .mcp-devtools-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; } .mcp-status-section { padding: 12px 16px; border-bottom: 1px solid #e0e0e0; background: #f8f9fa; } .mcp-status-item { display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 12px; } .mcp-status-item label { font-weight: 500; color: #495057; } .mcp-status-item span { color: #6c757d; } .mcp-logs-section { flex: 1; display: flex; flex-direction: column; overflow: hidden; } .mcp-logs-header { padding: 8px 16px; background: #f8f9fa; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; font-size: 12px; font-weight: 500; } .mcp-clear-btn { background: none; border: 1px solid #dee2e6; border-radius: 4px; padding: 2px 8px; font-size: 11px; cursor: pointer; color: #6c757d; } .mcp-clear-btn:hover { background: #e9ecef; } .mcp-logs-container { flex: 1; overflow-y: auto; padding: 8px; font-size: 11px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; } .mcp-log-entry { margin-bottom: 4px; padding: 4px 8px; border-radius: 4px; white-space: pre-wrap; word-break: break-word; } .mcp-log-entry.info { background: #e7f3ff; color: #0c5460; } .mcp-log-entry.warning { background: #fff3cd; color: #856404; } .mcp-log-entry.error { background: #f8d7da; color: #721c24; } .mcp-log-timestamp { color: #6c757d; font-size: 10px; } /* Header controls */ .mcp-header-controls { display: flex; gap: 8px; align-items: center; } .mcp-theme-toggle { background: none; border: none; font-size: 16px; cursor: pointer; padding: 0; opacity: 0.6; transition: opacity 0.2s; } .mcp-theme-toggle:hover { opacity: 1; } /* Log controls */ .mcp-logs-controls { display: flex; gap: 8px; align-items: center; } .mcp-checkbox { display: flex; align-items: center; gap: 4px; font-size: 11px; cursor: pointer; } .mcp-checkbox input { cursor: pointer; } /* Tool execution logs */ .mcp-log-entry.tool { background: #e7f3ff; color: #004085; border-left: 3px solid #004085; } .mcp-tool-details { font-size: 10px; margin-top: 4px; padding-left: 16px; opacity: 0.8; } /* Dark theme */ .mcp-theme-dark .mcp-devtools-indicator { background: rgba(30, 30, 30, 0.9); border-color: rgba(255, 255, 255, 0.1); } .mcp-theme-dark .mcp-label { color: #e0e0e0; } .mcp-theme-dark .mcp-devtools-panel { background: #1e1e1e; border-color: #333; } .mcp-theme-dark .mcp-devtools-header { background: #2d2d2d; border-color: #333; color: #e0e0e0; } .mcp-theme-dark .mcp-devtools-content { background: #1e1e1e; } .mcp-theme-dark .mcp-status-section { background: #2d2d2d; border-color: #333; } .mcp-theme-dark .mcp-status-item label { color: #b0b0b0; } .mcp-theme-dark .mcp-status-item span { color: #e0e0e0; } .mcp-theme-dark .mcp-logs-header { background: #2d2d2d; border-color: #333; color: #e0e0e0; } .mcp-theme-dark .mcp-logs-container { background: #252525; border-color: #333; } .mcp-theme-dark .mcp-log-entry { background: #2d2d2d; color: #e0e0e0; border-color: #333; } .mcp-theme-dark .mcp-log-entry.warning { background: #4a3800; color: #ffc107; } .mcp-theme-dark .mcp-log-entry.error { background: #4a0000; color: #ff6b6b; } .mcp-theme-dark .mcp-log-entry.tool { background: #003366; color: #66b3ff; border-left-color: #66b3ff; } .mcp-theme-dark .mcp-log-timestamp { color: #888; } .mcp-theme-dark .mcp-clear-btn, .mcp-theme-dark .mcp-close-btn { color: #e0e0e0; border-color: #555; } .mcp-theme-dark .mcp-clear-btn:hover, .mcp-theme-dark .mcp-close-btn:hover { background: #444; } } `; document.head.appendChild(style); } setupEventListeners() { const indicator = this.container?.querySelector('#mcp-indicator'); const closeBtn = this.container?.querySelector('#mcp-close-btn'); const clearBtn = this.container?.querySelector('#mcp-clear-btn'); const themeBtn = this.container?.querySelector('#mcp-theme-btn'); indicator?.addEventListener('click', () => this.togglePanel()); closeBtn?.addEventListener('click', () => this.togglePanel()); clearBtn?.addEventListener('click', () => this.clearLogs()); themeBtn?.addEventListener('click', () => this.toggleTheme()); } toggleTheme() { if (!this.container) return; this.config.theme = this.config.theme === 'light' ? 'dark' : 'light'; this.container.className = `mcp-theme-${this.config.theme} mcp-position-${this.config.position}`; } togglePanel() { this.isExpanded = !this.isExpanded; const panel = this.container?.querySelector('#mcp-panel'); if (panel) { panel.style.display = this.isExpanded ? 'flex' : 'none'; } } setConnectionStatus(status) { this.connectionStatus = status; const dot = this.container?.querySelector('#mcp-status-dot'); const wsStatus = this.container?.querySelector('#mcp-ws-status'); const serverStatus = this.container?.querySelector('#mcp-server-status'); if (dot) { dot.className = `mcp-status-dot ${status}`; } if (wsStatus && serverStatus) { const statusText = status.charAt(0).toUpperCase() + status.slice(1); wsStatus.textContent = statusText; serverStatus.textContent = statusText; } this.addLog('info', 'client', `Connection status: ${status}`); } addLog(level, source, message, data) { const log = { timestamp: new Date().toISOString(), level, source, message, data }; this.logs.unshift(log); if (this.logs.length > this.maxLogs) { this.logs = this.logs.slice(0, this.maxLogs); } this.renderLogs(); } renderLogs() { const logsContainer = this.container?.querySelector('#mcp-logs'); if (!logsContainer) return; logsContainer.innerHTML = this.logs.map(log => { const time = new Date(log.timestamp).toLocaleTimeString(); if (log.level === 'tool' && log.data) { // Special formatting for tool logs const details = log.data; let detailsHtml = ''; if (details.args) { detailsHtml += `<div class="mcp-tool-details">Args: ${this.safeStringify(details.args)}</div>`; } if (details.error) { detailsHtml += `<div class="mcp-tool-details">Error: ${details.error}</div>`; } if (details.result && details.status === 'completed') { const resultStr = this.safeStringify(details.result); if (resultStr.length > 100) { detailsHtml += `<div class="mcp-tool-details">Result: ${resultStr.substring(0, 100)}...</div>`; } else { detailsHtml += `<div class="mcp-tool-details">Result: ${resultStr}</div>`; } } return ` <div class="mcp-log-entry ${log.level}"> <span class="mcp-log-timestamp">[${time}]</span> [${log.source.toUpperCase()}] ${log.message} ${detailsHtml} </div> `; } else { // Regular log formatting const dataStr = log.data ? ` ${this.safeStringify(log.data)}` : ''; return ` <div class="mcp-log-entry ${log.level}"> <span class="mcp-log-timestamp">[${time}]</span> [${log.source.toUpperCase()}] ${log.message}${dataStr} </div> `; } }).join(''); // Auto-scroll to top if enabled (latest items are at the top) const autoScrollCheckbox = this.container?.querySelector('#mcp-autoscroll'); if (autoScrollCheckbox?.checked) { logsContainer.scrollTop = 0; } } safeStringify(obj) { try { // Handle simple types if (obj === null || obj === undefined) { return String(obj); } if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') { return String(obj); } // Handle arrays and objects with circular reference protection const seen = new Set(); return JSON.stringify(obj, (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return '[Circular Reference]'; } seen.add(value); } return value; }); } catch (error) { return `[Error stringifying: ${error instanceof Error ? error.message : 'Unknown error'}]`; } } clearLogs() { this.logs = []; this.renderLogs(); } logWebSocketEvent(event, data) { this.addLog('info', 'websocket', event, data); } logMCPEvent(event, data) { this.addLog('info', 'mcp', event, data); } logError(source, message, error) { this.addLog('error', source, message, error); } logToolExecution(toolName, args, success, message, executionTime, result) { const status = success === null ? 'started' : (success ? 'completed' : 'failed'); const details = { tool: toolName, status, args, executionTime: executionTime ? `${executionTime}ms` : undefined, result: success && result ? result : undefined, error: !success && message !== 'Success' ? message : undefined }; const logMessage = `Tool ${toolName} ${status}${executionTime ? ` (${executionTime}ms)` : ''}`; this.addLog(success === false ? 'error' : 'tool', 'tool', logMessage, details); } } exports.MCPDevTools = MCPDevTools; //# sourceMappingURL=devtools.js.map