diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js index 6667d3f62..437869e21 100644 --- a/mcpgateway/static/admin.js +++ b/mcpgateway/static/admin.js @@ -6397,6 +6397,298 @@ window.runToolTest = runToolTest; window.closeModal = closeModal; window.testGateway = testGateway; +// =============================================== +// CONFIG EXPORT FUNCTIONALITY +// =============================================== + +/** + * Global variables to store current config data + */ +let currentConfigData = null; +let currentConfigType = null; +let currentServerName = null; +let currentServerId = null; + +/** + * Show the config selection modal + * @param {string} serverId - The server UUID + * @param {string} serverName - The server name + */ +function showConfigSelectionModal(serverId, serverName) { + currentServerId = serverId; + currentServerName = serverName; + + const serverNameDisplay = safeGetElement("server-name-display"); + if (serverNameDisplay) { + serverNameDisplay.textContent = serverName; + } + + openModal("config-selection-modal"); +} + +/** + * Generate and show configuration for selected type + * @param {string} configType - Configuration type: 'stdio', 'sse', or 'http' + */ +async function generateAndShowConfig(configType) { + try { + console.log( + `Generating ${configType} config for server ${currentServerId}`, + ); + + // First, fetch the server details + const response = await fetchWithTimeout( + `${window.ROOT_PATH}/admin/servers/${currentServerId}`, + ); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const server = await response.json(); + + // Generate the configuration + const config = generateConfig(server, configType); + + // Store data for modal + currentConfigData = config; + currentConfigType = configType; + + // Close selection modal and show config display modal + closeModal("config-selection-modal"); + showConfigDisplayModal(server, configType, config); + + console.log("βœ“ Config generated successfully"); + } catch (error) { + console.error("Error generating config:", error); + const errorMessage = handleFetchError(error, "generate configuration"); + showErrorMessage(errorMessage); + } +} + +/** + * Export server configuration in specified format + * @param {string} serverId - The server UUID + * @param {string} configType - Configuration type: 'stdio', 'sse', or 'http' + */ +async function exportServerConfig(serverId, configType) { + try { + console.log(`Exporting ${configType} config for server ${serverId}`); + + // First, fetch the server details + const response = await fetchWithTimeout( + `${window.ROOT_PATH}/admin/servers/${serverId}`, + ); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const server = await response.json(); + + // Generate the configuration + const config = generateConfig(server, configType); + + // Store data for modal + currentConfigData = config; + currentConfigType = configType; + currentServerName = server.name; + + // Show the modal with the config + showConfigDisplayModal(server, configType, config); + + console.log("βœ“ Config generated successfully"); + } catch (error) { + console.error("Error generating config:", error); + const errorMessage = handleFetchError(error, "generate configuration"); + showErrorMessage(errorMessage); + } +} + +/** + * Generate configuration object based on server and type + * @param {Object} server - Server object from API + * @param {string} configType - Configuration type + * @returns {Object} - Generated configuration object + */ +function generateConfig(server, configType) { + const currentHost = window.location.hostname; + const currentPort = + window.location.port || + (window.location.protocol === "https:" ? "443" : "80"); + const protocol = window.location.protocol; + const baseUrl = `${protocol}//${currentHost}${currentPort !== "80" && currentPort !== "443" ? ":" + currentPort : ""}`; + + // Clean server name for use as config key (alphanumeric and hyphens only) + const cleanServerName = server.name + .toLowerCase() + .replace(/[^a-z0-9-]/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, ""); + + switch (configType) { + case "stdio": + return { + mcpServers: { + [cleanServerName]: { + command: "python", + args: ["-m", "mcpgateway.wrapper"], + env: { + MCP_AUTH_TOKEN: "your-token-here", + MCP_SERVER_CATALOG_URLS: `${baseUrl}/servers/${server.id}`, + MCP_TOOL_CALL_TIMEOUT: "120", + }, + }, + }, + }; + + case "sse": + return { + mcpServers: { + [cleanServerName]: { + type: "sse", + url: `${baseUrl}/servers/${server.id}/sse`, + headers: { + Authorization: "Bearer your-token-here", + }, + }, + }, + }; + + case "http": + return { + mcpServers: { + [cleanServerName]: { + type: "http", + url: `${baseUrl}/servers/${server.id}`, + headers: { + Authorization: "Bearer your-token-here", + }, + }, + }, + }; + + default: + throw new Error(`Unknown config type: ${configType}`); + } +} + +/** + * Show the config display modal with generated configuration + * @param {Object} server - Server object + * @param {string} configType - Configuration type + * @param {Object} config - Generated configuration + */ +function showConfigDisplayModal(server, configType, config) { + const descriptions = { + stdio: "Configuration for Claude Desktop, CLI tools, and stdio-based MCP clients", + sse: "Configuration for LangChain, LlamaIndex, and other SSE-based frameworks", + http: "Configuration for REST clients and HTTP-based MCP integrations", + }; + + const usageInstructions = { + stdio: "Save as .mcp.json in your user directory or use in Claude Desktop settings", + sse: "Use with MCP client libraries that support Server-Sent Events transport", + http: "Use with HTTP clients or REST API wrappers for MCP protocol", + }; + + // Update modal content + const descriptionEl = safeGetElement("config-description"); + const usageEl = safeGetElement("config-usage"); + const contentEl = safeGetElement("config-content"); + + if (descriptionEl) { + descriptionEl.textContent = `${descriptions[configType]} for server "${server.name}"`; + } + + if (usageEl) { + usageEl.textContent = usageInstructions[configType]; + } + + if (contentEl) { + contentEl.value = JSON.stringify(config, null, 2); + } + + // Update title and open the modal + const titleEl = safeGetElement("config-display-title"); + if (titleEl) { + titleEl.textContent = `${configType.toUpperCase()} Configuration for ${server.name}`; + } + openModal("config-display-modal"); +} + +/** + * Copy configuration to clipboard + */ +async function copyConfigToClipboard() { + try { + const contentEl = safeGetElement("config-content"); + if (!contentEl) { + throw new Error("Config content not found"); + } + + await navigator.clipboard.writeText(contentEl.value); + showSuccessMessage("Configuration copied to clipboard!"); + } catch (error) { + console.error("Error copying to clipboard:", error); + + // Fallback: select the text for manual copying + const contentEl = safeGetElement("config-content"); + if (contentEl) { + contentEl.select(); + contentEl.setSelectionRange(0, 99999); // For mobile devices + showErrorMessage("Please copy the selected text manually (Ctrl+C)"); + } else { + showErrorMessage("Failed to copy configuration"); + } + } +} + +/** + * Download configuration as JSON file + */ +function downloadConfig() { + if (!currentConfigData || !currentConfigType || !currentServerName) { + showErrorMessage("No configuration data available"); + return; + } + + try { + const content = JSON.stringify(currentConfigData, null, 2); + const blob = new Blob([content], { type: "application/json" }); + const url = window.URL.createObjectURL(blob); + + const a = document.createElement("a"); + a.href = url; + a.download = `${currentServerName}-${currentConfigType}-config.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + + showSuccessMessage(`Configuration downloaded as ${a.download}`); + } catch (error) { + console.error("Error downloading config:", error); + showErrorMessage("Failed to download configuration"); + } +} + +/** + * Go back to config selection modal + */ +function goBackToSelection() { + closeModal("config-display-modal"); + openModal("config-selection-modal"); +} + +// Export functions to global scope immediately after definition +window.showConfigSelectionModal = showConfigSelectionModal; +window.generateAndShowConfig = generateAndShowConfig; +window.exportServerConfig = exportServerConfig; +window.copyConfigToClipboard = copyConfigToClipboard; +window.downloadConfig = downloadConfig; +window.goBackToSelection = goBackToSelection; + // =============================================== // TAG FILTERING FUNCTIONALITY // =============================================== diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html index 541ed2f90..8a063e0c4 100644 --- a/mcpgateway/templates/admin.html +++ b/mcpgateway/templates/admin.html @@ -11,12 +11,18 @@ MCP Gateway Admin - + - + @@ -165,13 +175,23 @@

-

System Logs

+

+ System Logs +

- - @@ -182,8 +202,14 @@

System Logs
- - @@ -193,56 +219,97 @@

System Logs
- - + +

- - - - -
-
- Loading stats... +
+ Loading stats...
- +
- - - - - +
+ Time + Level + Entity + Message
@@ -251,13 +318,26 @@

System Logs
- 0 logs + 0 logs
- -
@@ -511,6 +591,14 @@

> Edit + +
- {% set clean_desc = (tool.description or "") | replace('\n', ' ') | replace('\r', ' ') %} - {% set refactor_desc = clean_desc | striptags | trim | escape %} - {% if refactor_desc | length is greaterthan 120 %} - {{ refactor_desc[:120] }}... - {% else %} - {{ refactor_desc }} - {% endif %} + {% set clean_desc = (tool.description or "") | replace('\n', + ' ') | replace('\r', ' ') %} {% set refactor_desc = + clean_desc | striptags | trim | escape %} {% if + refactor_desc | length is greaterthan 120 %} {{ + refactor_desc[:120] }}... {% else %} {{ refactor_desc }} {% + endif %} {% if tool.annotations %} {% if tool.annotations.title %} @@ -1086,17 +1173,15 @@

Add New Tool

- - +
-
@@ -1145,7 +1230,6 @@

Add New Tool

class="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300" onchange="updateRequestTypeOptions()" > -
@@ -1276,7 +1360,9 @@

Add New Tool

onclick="addAuthHeader('auth-headers-container')" class="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" > - - + + Add Header @@ -1293,7 +1389,11 @@

Add New Tool

- +

@@ -1328,46 +1428,98 @@

Add New Tool