Skip to main content
Glama

Browser Control MCP

by eyalzh
options.ts18.7 kB
/** * Options page script for Browser Control MCP extension */ import { getSecret, AVAILABLE_TOOLS, getAllToolSettings, setToolEnabled, getDomainDenyList, setDomainDenyList, getPorts, setPorts, getAuditLog, clearAuditLog, getToolNameById, } from "./extension-config"; const secretDisplay = document.getElementById( "secret-display" ) as HTMLDivElement; const copyButton = document.getElementById("copy-button") as HTMLButtonElement; const statusElement = document.getElementById("status") as HTMLDivElement; const toolSettingsContainer = document.getElementById( "tool-settings-container" ) as HTMLDivElement; const domainDenyListTextarea = document.getElementById( "domain-deny-list" ) as HTMLTextAreaElement; const saveDomainListsButton = document.getElementById( "save-domain-lists" ) as HTMLButtonElement; const domainStatusElement = document.getElementById( "domain-status" ) as HTMLDivElement; const portsInput = document.getElementById("ports-input") as HTMLInputElement; const savePortsButton = document.getElementById("save-ports") as HTMLButtonElement; const portsStatusElement = document.getElementById("ports-status") as HTMLDivElement; const auditLogContainer = document.getElementById("audit-log-container") as HTMLDivElement; const clearAuditLogButton = document.getElementById("clear-audit-log") as HTMLButtonElement; const auditLogStatusElement = document.getElementById("audit-log-status") as HTMLDivElement; /** * Loads the secret from storage and displays it */ async function loadSecret() { try { const secret = await getSecret(); // Check if secret exists if (secret) { secretDisplay.textContent = secret; } else { secretDisplay.textContent = "No secret found. Please reinstall the extension."; secretDisplay.style.color = "red"; copyButton.disabled = true; } } catch (error) { console.error("Error loading secret:", error); secretDisplay.textContent = "Error loading secret. Please check console for details."; secretDisplay.style.color = "red"; copyButton.disabled = true; } } /** * Copies the secret to clipboard */ async function copyToClipboard(event: MouseEvent) { if (!event.isTrusted) { return; } try { const secret = secretDisplay.textContent; if ( !secret || secret === "Loading..." || secret.includes("No secret found") || secret.includes("Error loading") ) { return; } await navigator.clipboard.writeText(secret); // Show success message statusElement.textContent = "Secret copied to clipboard!"; setTimeout(() => { statusElement.textContent = ""; }, 3000); } catch (error) { console.error("Error copying to clipboard:", error); statusElement.textContent = "Failed to copy to clipboard"; statusElement.style.color = "red"; setTimeout(() => { statusElement.textContent = ""; statusElement.style.color = ""; }, 3000); } } /** * Creates the tool settings UI */ async function createToolSettingsUI() { const toolSettings = await getAllToolSettings(); // Clear existing content toolSettingsContainer.innerHTML = ""; // Create a toggle switch for each tool AVAILABLE_TOOLS.forEach((tool) => { const isEnabled = toolSettings[tool.id] !== false; // Default to true if not set const toolRow = document.createElement("div"); toolRow.className = "tool-row"; const labelContainer = document.createElement("div"); labelContainer.className = "tool-label-container"; const toolName = document.createElement("div"); toolName.className = "tool-name"; toolName.textContent = tool.name; const toolDescription = document.createElement("div"); toolDescription.className = "tool-description"; toolDescription.textContent = tool.description; labelContainer.appendChild(toolName); labelContainer.appendChild(toolDescription); const toggleContainer = document.createElement("label"); toggleContainer.className = "toggle-switch"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = isEnabled; checkbox.dataset.toolId = tool.id; checkbox.addEventListener("change", handleToolToggle); const slider = document.createElement("span"); slider.className = "slider"; toggleContainer.appendChild(checkbox); toggleContainer.appendChild(slider); toolRow.appendChild(labelContainer); toolRow.appendChild(toggleContainer); toolSettingsContainer.appendChild(toolRow); }); } /** * Handles toggling a tool on/off */ async function handleToolToggle(event: Event) { const checkbox = event.target as HTMLInputElement; const toolId = checkbox.dataset.toolId; const isEnabled = checkbox.checked; if (!toolId) { console.error("Tool ID not found"); return; } try { await setToolEnabled(toolId, isEnabled); // No status message displayed } catch (error) { console.error("Error saving tool setting:", error); // Revert the checkbox state checkbox.checked = !isEnabled; } } /** * Loads the domain lists from storage and displays them */ async function loadDomainLists() { try { // Load deny list const denyList = await getDomainDenyList(); domainDenyListTextarea.value = denyList.join("\n"); } catch (error) { console.error("Error loading domain lists:", error); domainStatusElement.textContent = "Error loading domain lists. Please check console for details."; domainStatusElement.style.color = "red"; setTimeout(() => { domainStatusElement.textContent = ""; domainStatusElement.style.color = ""; }, 3000); } } /** * Saves the domain lists to storage */ async function saveDomainLists(event: MouseEvent) { if (!event.isTrusted) { return; } try { // Parse deny list (split by newlines and filter out empty lines) const denyListText = domainDenyListTextarea.value.trim(); const denyList = denyListText ? denyListText .split("\n") .map((domain) => domain.trim()) .filter(Boolean) : []; // Save to storage await setDomainDenyList(denyList); // Show success message domainStatusElement.textContent = "Domain deny list saved successfully!"; domainStatusElement.style.color = "#4caf50"; setTimeout(() => { domainStatusElement.textContent = ""; domainStatusElement.style.color = ""; }, 3000); } catch (error) { console.error("Error saving domain lists:", error); domainStatusElement.textContent = "Failed to save domain lists"; domainStatusElement.style.color = "red"; setTimeout(() => { domainStatusElement.textContent = ""; domainStatusElement.style.color = ""; }, 3000); } } /** * Loads the ports from storage and displays them */ async function loadPorts() { try { const ports = await getPorts(); portsInput.value = ports.join(", "); } catch (error) { console.error("Error loading ports:", error); portsStatusElement.textContent = "Error loading ports. Please check console for details."; portsStatusElement.style.color = "red"; setTimeout(() => { portsStatusElement.textContent = ""; portsStatusElement.style.color = ""; }, 3000); } } /** * Saves the ports to storage */ async function savePorts(event: MouseEvent) { if (!event.isTrusted) { return; } try { // Parse ports (split by commas and filter out empty values) const portsText = portsInput.value.trim(); const portStrings = portsText ? portsText .split(",") .map((port) => port.trim()) .filter(Boolean) : []; // Validate and convert to numbers const ports: number[] = []; for (const portStr of portStrings) { const port = parseInt(portStr, 10); if (isNaN(port) || port < 1 || port > 65535) { throw new Error(`Invalid port number: ${portStr}. Ports must be between 1 and 65535.`); } ports.push(port); } // Ensure at least one port is provided if (ports.length === 0) { throw new Error("At least one port must be specified."); } // Save to storage await setPorts(ports); // Reload the extension: browser.runtime.reload(); } catch (error) { console.error("Error saving ports:", error); portsStatusElement.textContent = error instanceof Error ? error.message : "Failed to save ports"; portsStatusElement.style.color = "red"; setTimeout(() => { portsStatusElement.textContent = ""; portsStatusElement.style.color = ""; }, 3000); } } /** * Loads the audit log from storage and displays it */ async function loadAuditLog() { try { const auditLog = await getAuditLog(); // Clear existing content auditLogContainer.innerHTML = ""; if (auditLog.length === 0) { // Show empty state const emptyDiv = document.createElement("div"); emptyDiv.className = "audit-log-empty"; emptyDiv.textContent = "No tool usage recorded yet."; auditLogContainer.appendChild(emptyDiv); return; } // Create table const table = document.createElement("table"); table.className = "audit-log-table"; // Create header const thead = document.createElement("thead"); const headerRow = document.createElement("tr"); const headers = ["Tool", "Timestamp", "Domain"]; headers.forEach(headerText => { const th = document.createElement("th"); th.textContent = headerText; headerRow.appendChild(th); }); thead.appendChild(headerRow); table.appendChild(thead); // Create body const tbody = document.createElement("tbody"); auditLog.forEach(entry => { const row = document.createElement("tr"); // Tool name const toolCell = document.createElement("td"); toolCell.textContent = getToolNameById(entry.toolId); row.appendChild(toolCell); // Timestamp const timestampCell = document.createElement("td"); timestampCell.className = "audit-log-timestamp"; const date = new Date(entry.timestamp); timestampCell.textContent = date.toLocaleString(); row.appendChild(timestampCell); // URL Domain const urlCell = document.createElement("td"); urlCell.className = "audit-log-url"; if (entry.url) { // Show only the domain part of the URL try { const urlObj = new URL(entry.url); urlCell.textContent = urlObj.hostname; } catch (e) { console.error("Invalid URL in audit log entry:", e); urlCell.textContent = "Invalid URL"; } } else { urlCell.textContent = "-"; } row.appendChild(urlCell); tbody.appendChild(row); }); table.appendChild(tbody); auditLogContainer.appendChild(table); } catch (error) { console.error("Error loading audit log:", error); auditLogContainer.innerHTML = '<div class="audit-log-empty">Error loading audit log. Please check console for details.</div>'; } } /** * Clears the audit log */ async function handleClearAuditLog(event: MouseEvent) { if (!event.isTrusted) { return; } try { await clearAuditLog(); // Reload the audit log display await loadAuditLog(); // Show success message auditLogStatusElement.textContent = "Audit log cleared successfully!"; auditLogStatusElement.style.color = "#4caf50"; setTimeout(() => { auditLogStatusElement.textContent = ""; auditLogStatusElement.style.color = ""; }, 3000); } catch (error) { console.error("Error clearing audit log:", error); auditLogStatusElement.textContent = "Failed to clear audit log"; auditLogStatusElement.style.color = "red"; setTimeout(() => { auditLogStatusElement.textContent = ""; auditLogStatusElement.style.color = ""; }, 3000); } } /** * Initializes the collapsible sections */ function initializeCollapsibleSections() { const sectionHeaders = document.querySelectorAll(".section-container > h2"); sectionHeaders.forEach((header) => { // Add click event listener to toggle section visibility header.addEventListener("click", (event) => { event.preventDefault(); // Toggle the collapsed class on the header header.classList.toggle("collapsed"); // Toggle the collapsed class on the section content const sectionContent = header.nextElementSibling as HTMLElement; sectionContent.classList.toggle("collapsed"); }); }); } function showPermissionRequest(url: string) { const domain = new URL(url).hostname; const origin = new URL(url).origin; // Show the modal and hide the main content const modal = document.getElementById("permission-modal") as HTMLDivElement; const mainContent = document.getElementById("main-content") as HTMLDivElement; const domainElement = document.getElementById("permission-domain") as HTMLDivElement; const grantBtn = document.getElementById("grant-btn") as HTMLButtonElement; const cancelBtn = document.getElementById("cancel-btn") as HTMLButtonElement; const permissionText = document.getElementById("permission-text") as HTMLParagraphElement; // Set the domain in the modal domainElement.textContent = domain; // Update permission text for URL permission permissionText.textContent = "This will allow the extension to interact with pages on this domain as requested by the MCP server."; // Show modal and blur main content modal.classList.remove("hidden"); mainContent.classList.add("modal-open"); // Handle grant permission button click const handleGrant = async () => { try { const granted = await browser.permissions.request({ origins: [`${origin}/*`], }); if (granted) { // Permission granted, close the window or redirect back window.close(); } else { // Permission denied, hide modal and show main content hidePermissionModal(); } } catch (error) { console.error("Error requesting permission:", error); hidePermissionModal(); } }; // Handle cancel button click const handleCancel = () => { hidePermissionModal(); }; // Add event listeners grantBtn.addEventListener("click", handleGrant); cancelBtn.addEventListener("click", handleCancel); // Store references to remove listeners later (window as any).permissionHandlers = { handleGrant, handleCancel, grantBtn, cancelBtn }; } function showGlobalPermissionRequest(permissions: string[]) { // Show the modal and hide the main content const modal = document.getElementById("permission-modal") as HTMLDivElement; const mainContent = document.getElementById("main-content") as HTMLDivElement; const domainElement = document.getElementById("permission-domain") as HTMLDivElement; const grantBtn = document.getElementById("grant-btn") as HTMLButtonElement; const cancelBtn = document.getElementById("cancel-btn") as HTMLButtonElement; const permissionText = document.getElementById("permission-text") as HTMLParagraphElement; // Set the permissions in the modal domainElement.textContent = permissions.join(", "); // Update permission text for global permissions permissionText.textContent = "This will allow the extension to use these browser capabilities as requested by the MCP server."; // Show modal and blur main content modal.classList.remove("hidden"); mainContent.classList.add("modal-open"); // Handle grant permission button click const handleGrant = async () => { try { const granted = await browser.permissions.request({ permissions: permissions as browser.permissions.Permissions["permissions"], }); if (granted) { // Permission granted, close the window or redirect back window.close(); } else { // Permission denied, hide modal and show main content hidePermissionModal(); } } catch (error) { console.error("Error requesting permission:", error); hidePermissionModal(); } }; // Handle cancel button click const handleCancel = () => { hidePermissionModal(); }; // Add event listeners grantBtn.addEventListener("click", handleGrant); cancelBtn.addEventListener("click", handleCancel); // Store references to remove listeners later (window as any).permissionHandlers = { handleGrant, handleCancel, grantBtn, cancelBtn }; } function hidePermissionModal() { const modal = document.getElementById("permission-modal") as HTMLDivElement; const mainContent = document.getElementById("main-content") as HTMLDivElement; // Hide modal and restore main content modal.classList.add("hidden"); mainContent.classList.remove("modal-open"); // Clean up event listeners const handlers = (window as any).permissionHandlers; if (handlers) { handlers.grantBtn.removeEventListener("click", handlers.handleGrant); handlers.cancelBtn.removeEventListener("click", handlers.handleCancel); delete (window as any).permissionHandlers; } } // Initialize the page copyButton.addEventListener("click", copyToClipboard); saveDomainListsButton.addEventListener("click", saveDomainLists); savePortsButton.addEventListener("click", savePorts); clearAuditLogButton.addEventListener("click", handleClearAuditLog); document.addEventListener("DOMContentLoaded", () => { loadSecret(); createToolSettingsUI(); loadDomainLists(); loadPorts(); loadAuditLog(); initializeCollapsibleSections(); // Ensure modal is hidden by default const modal = document.getElementById("permission-modal") as HTMLDivElement; const mainContent = document.getElementById("main-content") as HTMLDivElement; modal.classList.add("hidden"); mainContent.classList.remove("modal-open"); const params = new URLSearchParams(window.location.search); const requestUrl = params.get("requestUrl"); const requestPermissions = params.get("requestPermissions"); if (requestUrl) { // Show UI for requesting permission for this specific URL showPermissionRequest(requestUrl); } else if (requestPermissions) { // Show UI for requesting global permissions try { const permissions = JSON.parse(decodeURIComponent(requestPermissions)); showGlobalPermissionRequest(permissions); } catch (error) { console.error("Error parsing requestPermissions:", error); } } // Add interval to refresh the audit log every 5 seconds: setInterval(() => { loadAuditLog(); }, 5000); });

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/eyalzh/browser-control-mcp'

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