Skip to content

Commit 2ff0179

Browse files
authored
Implemented configuration export (#764)
Signed-off-by: Mihai Criveti <[email protected]>
1 parent e972dbd commit 2ff0179

File tree

2 files changed

+907
-203
lines changed

2 files changed

+907
-203
lines changed

mcpgateway/static/admin.js

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6397,6 +6397,298 @@ window.runToolTest = runToolTest;
63976397
window.closeModal = closeModal;
63986398
window.testGateway = testGateway;
63996399

6400+
// ===============================================
6401+
// CONFIG EXPORT FUNCTIONALITY
6402+
// ===============================================
6403+
6404+
/**
6405+
* Global variables to store current config data
6406+
*/
6407+
let currentConfigData = null;
6408+
let currentConfigType = null;
6409+
let currentServerName = null;
6410+
let currentServerId = null;
6411+
6412+
/**
6413+
* Show the config selection modal
6414+
* @param {string} serverId - The server UUID
6415+
* @param {string} serverName - The server name
6416+
*/
6417+
function showConfigSelectionModal(serverId, serverName) {
6418+
currentServerId = serverId;
6419+
currentServerName = serverName;
6420+
6421+
const serverNameDisplay = safeGetElement("server-name-display");
6422+
if (serverNameDisplay) {
6423+
serverNameDisplay.textContent = serverName;
6424+
}
6425+
6426+
openModal("config-selection-modal");
6427+
}
6428+
6429+
/**
6430+
* Generate and show configuration for selected type
6431+
* @param {string} configType - Configuration type: 'stdio', 'sse', or 'http'
6432+
*/
6433+
async function generateAndShowConfig(configType) {
6434+
try {
6435+
console.log(
6436+
`Generating ${configType} config for server ${currentServerId}`,
6437+
);
6438+
6439+
// First, fetch the server details
6440+
const response = await fetchWithTimeout(
6441+
`${window.ROOT_PATH}/admin/servers/${currentServerId}`,
6442+
);
6443+
6444+
if (!response.ok) {
6445+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
6446+
}
6447+
6448+
const server = await response.json();
6449+
6450+
// Generate the configuration
6451+
const config = generateConfig(server, configType);
6452+
6453+
// Store data for modal
6454+
currentConfigData = config;
6455+
currentConfigType = configType;
6456+
6457+
// Close selection modal and show config display modal
6458+
closeModal("config-selection-modal");
6459+
showConfigDisplayModal(server, configType, config);
6460+
6461+
console.log("✓ Config generated successfully");
6462+
} catch (error) {
6463+
console.error("Error generating config:", error);
6464+
const errorMessage = handleFetchError(error, "generate configuration");
6465+
showErrorMessage(errorMessage);
6466+
}
6467+
}
6468+
6469+
/**
6470+
* Export server configuration in specified format
6471+
* @param {string} serverId - The server UUID
6472+
* @param {string} configType - Configuration type: 'stdio', 'sse', or 'http'
6473+
*/
6474+
async function exportServerConfig(serverId, configType) {
6475+
try {
6476+
console.log(`Exporting ${configType} config for server ${serverId}`);
6477+
6478+
// First, fetch the server details
6479+
const response = await fetchWithTimeout(
6480+
`${window.ROOT_PATH}/admin/servers/${serverId}`,
6481+
);
6482+
6483+
if (!response.ok) {
6484+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
6485+
}
6486+
6487+
const server = await response.json();
6488+
6489+
// Generate the configuration
6490+
const config = generateConfig(server, configType);
6491+
6492+
// Store data for modal
6493+
currentConfigData = config;
6494+
currentConfigType = configType;
6495+
currentServerName = server.name;
6496+
6497+
// Show the modal with the config
6498+
showConfigDisplayModal(server, configType, config);
6499+
6500+
console.log("✓ Config generated successfully");
6501+
} catch (error) {
6502+
console.error("Error generating config:", error);
6503+
const errorMessage = handleFetchError(error, "generate configuration");
6504+
showErrorMessage(errorMessage);
6505+
}
6506+
}
6507+
6508+
/**
6509+
* Generate configuration object based on server and type
6510+
* @param {Object} server - Server object from API
6511+
* @param {string} configType - Configuration type
6512+
* @returns {Object} - Generated configuration object
6513+
*/
6514+
function generateConfig(server, configType) {
6515+
const currentHost = window.location.hostname;
6516+
const currentPort =
6517+
window.location.port ||
6518+
(window.location.protocol === "https:" ? "443" : "80");
6519+
const protocol = window.location.protocol;
6520+
const baseUrl = `${protocol}//${currentHost}${currentPort !== "80" && currentPort !== "443" ? ":" + currentPort : ""}`;
6521+
6522+
// Clean server name for use as config key (alphanumeric and hyphens only)
6523+
const cleanServerName = server.name
6524+
.toLowerCase()
6525+
.replace(/[^a-z0-9-]/g, "-")
6526+
.replace(/-+/g, "-")
6527+
.replace(/^-|-$/g, "");
6528+
6529+
switch (configType) {
6530+
case "stdio":
6531+
return {
6532+
mcpServers: {
6533+
[cleanServerName]: {
6534+
command: "python",
6535+
args: ["-m", "mcpgateway.wrapper"],
6536+
env: {
6537+
MCP_AUTH_TOKEN: "your-token-here",
6538+
MCP_SERVER_CATALOG_URLS: `${baseUrl}/servers/${server.id}`,
6539+
MCP_TOOL_CALL_TIMEOUT: "120",
6540+
},
6541+
},
6542+
},
6543+
};
6544+
6545+
case "sse":
6546+
return {
6547+
mcpServers: {
6548+
[cleanServerName]: {
6549+
type: "sse",
6550+
url: `${baseUrl}/servers/${server.id}/sse`,
6551+
headers: {
6552+
Authorization: "Bearer your-token-here",
6553+
},
6554+
},
6555+
},
6556+
};
6557+
6558+
case "http":
6559+
return {
6560+
mcpServers: {
6561+
[cleanServerName]: {
6562+
type: "http",
6563+
url: `${baseUrl}/servers/${server.id}`,
6564+
headers: {
6565+
Authorization: "Bearer your-token-here",
6566+
},
6567+
},
6568+
},
6569+
};
6570+
6571+
default:
6572+
throw new Error(`Unknown config type: ${configType}`);
6573+
}
6574+
}
6575+
6576+
/**
6577+
* Show the config display modal with generated configuration
6578+
* @param {Object} server - Server object
6579+
* @param {string} configType - Configuration type
6580+
* @param {Object} config - Generated configuration
6581+
*/
6582+
function showConfigDisplayModal(server, configType, config) {
6583+
const descriptions = {
6584+
stdio: "Configuration for Claude Desktop, CLI tools, and stdio-based MCP clients",
6585+
sse: "Configuration for LangChain, LlamaIndex, and other SSE-based frameworks",
6586+
http: "Configuration for REST clients and HTTP-based MCP integrations",
6587+
};
6588+
6589+
const usageInstructions = {
6590+
stdio: "Save as .mcp.json in your user directory or use in Claude Desktop settings",
6591+
sse: "Use with MCP client libraries that support Server-Sent Events transport",
6592+
http: "Use with HTTP clients or REST API wrappers for MCP protocol",
6593+
};
6594+
6595+
// Update modal content
6596+
const descriptionEl = safeGetElement("config-description");
6597+
const usageEl = safeGetElement("config-usage");
6598+
const contentEl = safeGetElement("config-content");
6599+
6600+
if (descriptionEl) {
6601+
descriptionEl.textContent = `${descriptions[configType]} for server "${server.name}"`;
6602+
}
6603+
6604+
if (usageEl) {
6605+
usageEl.textContent = usageInstructions[configType];
6606+
}
6607+
6608+
if (contentEl) {
6609+
contentEl.value = JSON.stringify(config, null, 2);
6610+
}
6611+
6612+
// Update title and open the modal
6613+
const titleEl = safeGetElement("config-display-title");
6614+
if (titleEl) {
6615+
titleEl.textContent = `${configType.toUpperCase()} Configuration for ${server.name}`;
6616+
}
6617+
openModal("config-display-modal");
6618+
}
6619+
6620+
/**
6621+
* Copy configuration to clipboard
6622+
*/
6623+
async function copyConfigToClipboard() {
6624+
try {
6625+
const contentEl = safeGetElement("config-content");
6626+
if (!contentEl) {
6627+
throw new Error("Config content not found");
6628+
}
6629+
6630+
await navigator.clipboard.writeText(contentEl.value);
6631+
showSuccessMessage("Configuration copied to clipboard!");
6632+
} catch (error) {
6633+
console.error("Error copying to clipboard:", error);
6634+
6635+
// Fallback: select the text for manual copying
6636+
const contentEl = safeGetElement("config-content");
6637+
if (contentEl) {
6638+
contentEl.select();
6639+
contentEl.setSelectionRange(0, 99999); // For mobile devices
6640+
showErrorMessage("Please copy the selected text manually (Ctrl+C)");
6641+
} else {
6642+
showErrorMessage("Failed to copy configuration");
6643+
}
6644+
}
6645+
}
6646+
6647+
/**
6648+
* Download configuration as JSON file
6649+
*/
6650+
function downloadConfig() {
6651+
if (!currentConfigData || !currentConfigType || !currentServerName) {
6652+
showErrorMessage("No configuration data available");
6653+
return;
6654+
}
6655+
6656+
try {
6657+
const content = JSON.stringify(currentConfigData, null, 2);
6658+
const blob = new Blob([content], { type: "application/json" });
6659+
const url = window.URL.createObjectURL(blob);
6660+
6661+
const a = document.createElement("a");
6662+
a.href = url;
6663+
a.download = `${currentServerName}-${currentConfigType}-config.json`;
6664+
document.body.appendChild(a);
6665+
a.click();
6666+
document.body.removeChild(a);
6667+
window.URL.revokeObjectURL(url);
6668+
6669+
showSuccessMessage(`Configuration downloaded as ${a.download}`);
6670+
} catch (error) {
6671+
console.error("Error downloading config:", error);
6672+
showErrorMessage("Failed to download configuration");
6673+
}
6674+
}
6675+
6676+
/**
6677+
* Go back to config selection modal
6678+
*/
6679+
function goBackToSelection() {
6680+
closeModal("config-display-modal");
6681+
openModal("config-selection-modal");
6682+
}
6683+
6684+
// Export functions to global scope immediately after definition
6685+
window.showConfigSelectionModal = showConfigSelectionModal;
6686+
window.generateAndShowConfig = generateAndShowConfig;
6687+
window.exportServerConfig = exportServerConfig;
6688+
window.copyConfigToClipboard = copyConfigToClipboard;
6689+
window.downloadConfig = downloadConfig;
6690+
window.goBackToSelection = goBackToSelection;
6691+
64006692
// ===============================================
64016693
// TAG FILTERING FUNCTIONALITY
64026694
// ===============================================

0 commit comments

Comments
 (0)