Skip to main content
Glama
bridge.js9.73 kB
// MCP Bridge: Enables eval() contexts to communicate with Tauri IPC // This bridge is automatically injected by the mcp-bridge plugin // It forwards DOM events from eval() contexts to Tauri IPC and back (function() { 'use strict'; var origLog, origDebug, origInfo, origWarn, origError, bridgeLogger; // MCP bridge logger - scoped with levels and tags function createMcpLogger(scope) { return { info: function() { var args = Array.prototype.slice.call(arguments); args.unshift('[MCP][' + scope + '][INFO]'); console.log.apply(console, args); }, warn: function() { var args = Array.prototype.slice.call(arguments); args.unshift('[MCP][' + scope + '][WARN]'); console.warn.apply(console, args); }, error: function() { var args = Array.prototype.slice.call(arguments); args.unshift('[MCP][' + scope + '][ERROR]'); console.error.apply(console, args); }, tag: function(tag, message) { console.error('[MCP][' + scope + '][' + tag + ']', message); }, }; } bridgeLogger = createMcpLogger('BRIDGE'); // Initialize console capture so logs are captured from app startup function initConsoleCapture() { var args, message; if (window.__MCP_CONSOLE_LOGS__) { return; // Already initialized } origLog = console.log; origDebug = console.debug; origInfo = console.info; origWarn = console.warn; origError = console.error; window.__MCP_CONSOLE_LOGS__ = []; function captureLog(level, origFn) { return function() { args = Array.prototype.slice.call(arguments); try { message = args .map(function(a) { return typeof a === 'object' ? JSON.stringify(a) : String(a); }) .join(' '); } catch(e) { message = args.map(String).join(' '); } window.__MCP_CONSOLE_LOGS__.push({ level: level, message: message, timestamp: Date.now(), }); origFn.apply(console, args); }; } console.log = captureLog('log', origLog); console.debug = captureLog('debug', origDebug); console.info = captureLog('info', origInfo); console.warn = captureLog('warn', origWarn); console.error = captureLog('error', origError); bridgeLogger.info('Console capture initialized'); } // Wait for Tauri API to be available function waitForTauri(callback) { if (window.__TAURI__ && window.__TAURI__.core && window.__TAURI__.core.invoke) { // eslint-disable-next-line callback-return callback(); } else { setTimeout(function() { waitForTauri(callback); }, 50); } } waitForTauri(function() { bridgeLogger.info('Tauri API available, initializing bridge'); // Initialize console capture immediately so logs are captured from the start initConsoleCapture(); // Capture unhandled JS errors and promise rejections while preserving // default behavior if (!window.__MCP_UNHANDLED_ERRORS_CAPTURED__) { window.__MCP_UNHANDLED_ERRORS_CAPTURED__ = true; window.addEventListener('error', function(event) { var message, source, line; try { message = event.message || 'Unhandled error'; source = event.filename ? ' at ' + event.filename : ''; line = typeof event.lineno === 'number' ? ':' + event.lineno : ''; bridgeLogger.tag('UNHANDLED_ERROR', message + source + line); } catch(e) { // Best-effort logging; do not interfere with default handling } }); window.addEventListener('unhandledrejection', function(event) { var reason, reasonMessage; try { reason = event.reason; if (reason && typeof reason === 'object') { if (reason instanceof Error && reason.message) { reasonMessage = reason.message; } else { try { reasonMessage = JSON.stringify(reason); } catch(e) { reasonMessage = String(reason); } } } else { reasonMessage = String(reason); } bridgeLogger.tag('UNHANDLED_REJECTION', reasonMessage); } catch(e) { // Best-effort logging; do not interfere with default handling } }); } // Listen for execution requests from eval() contexts window.addEventListener('__mcp_exec_request', async function(event) { const request = event.detail; bridgeLogger.info('Received request:', request); try { // Forward to Tauri IPC using the global API const result = await window.__TAURI__.core.invoke( request.command, request.args ); bridgeLogger.info('Command succeeded, sending response'); // Send success response back via DOM event window.dispatchEvent(new CustomEvent('__mcp_exec_response', { detail: { execId: request.execId, success: true, data: result, }, })); } catch(error) { bridgeLogger.error('Command failed:', error); // Send error response back via DOM event window.dispatchEvent(new CustomEvent('__mcp_exec_response', { detail: { execId: request.execId, success: false, error: error.message || String(error), }, })); } }); // Mark bridge as ready window.__MCP_BRIDGE_READY__ = true; bridgeLogger.info('Ready'); // Notify Rust that the page has loaded and scripts should be re-injected // This is called after the bridge is ready to ensure Tauri IPC is available notifyPageLoaded(); }); // ========================================================================= // Script Injection Functions // ========================================================================= /** * Injects scripts into the DOM. Called by Rust when scripts need to be injected. * @param {Array<{id: string, type: 'inline'|'url', content: string}>} scripts */ window.__MCP_INJECT_SCRIPTS__ = function(scripts) { var script; if (!Array.isArray(scripts)) { bridgeLogger.error('Invalid scripts array'); return; } scripts.forEach(function(entry) { if (!entry || !entry.id) { return; } // Check if script already exists if (document.querySelector('script[data-mcp-script-id="' + entry.id + '"]')) { bridgeLogger.info('Script already exists:', entry.id); return; } script = document.createElement('script'); script.setAttribute('data-mcp-script-id', entry.id); if (entry.type === 'url') { script.src = entry.content; script.async = true; script.onload = function() { bridgeLogger.info('URL script loaded:', entry.id); }; script.onerror = function() { bridgeLogger.error('Failed to load URL script:', entry.id); }; } else { // Inline script script.textContent = entry.content; } document.head.appendChild(script); bridgeLogger.info('Injected script:', entry.id); }); }; /** * Removes a script from the DOM by ID. * @param {string} scriptId */ window.__MCP_REMOVE_SCRIPT__ = function(scriptId) { var script = document.querySelector('script[data-mcp-script-id="' + scriptId + '"]'); if (script) { script.remove(); bridgeLogger.info('Removed script:', scriptId); } }; /** * Removes all MCP-managed scripts from the DOM. */ window.__MCP_CLEAR_SCRIPTS__ = function() { var scripts = document.querySelectorAll('script[data-mcp-script-id]'); scripts.forEach(function(s) { s.remove(); }); bridgeLogger.info('Cleared', scripts.length, 'scripts'); }; /** * Notifies Rust that the page has loaded and scripts should be re-injected. * Uses the Tauri event system to communicate with the plugin. */ function notifyPageLoaded() { // Use Tauri's invoke to request script re-injection. // The plugin responds by calling __MCP_INJECT_SCRIPTS__ with registered scripts. if (window.__TAURI__ && window.__TAURI__.core && window.__TAURI__.core.invoke) { window.__TAURI__.core.invoke('plugin:mcp-bridge|request_script_injection') .catch(function(err) { // This command may not exist in older versions, which is fine bridgeLogger.warn('Script injection request:', err.message || 'not available'); }); } } // Also listen for navigation events to re-inject scripts // This handles SPA-style navigation where the page doesn't fully reload window.addEventListener('popstate', function() { bridgeLogger.info('Navigation detected (popstate)'); notifyPageLoaded(); }); }());

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hypothesi/mcp-server-tauri'

If you have feedback or need assistance with the MCP directory API, please join our Discord server