UNPKG

@gorizond/mcp-rancher-multi

Version:

MCP server for multiple Rancher Manager backends with Fleet GitOps support

182 lines (157 loc) 5.04 kB
import { config } from 'dotenv'; import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; export type RancherServerConfig = { id: string; // short id (e.g. "prod", "lab") name?: string; // human friendly name baseUrl: string; // https://rancher.example.com token: string; // API token; supports ${ENV:NAME} insecureSkipTlsVerify?: boolean; caCertPemBase64?: string; // optional custom CA (base64 PEM) }; /** * Load environment variables from .env file * Supports multiple .env files in order of priority: * 1. .env.local (highest priority) * 2. .env */ export function loadEnvFiles(): void { const envFiles = ['.env.local', '.env']; const cwd = process.cwd(); for (const envFile of envFiles) { const envPath = join(cwd, envFile); if (existsSync(envPath)) { console.error(`[mcp-rancher-multi] Loading environment from ${envFile}`); config({ path: envPath }); } } } /** * Resolve token value, supporting ${ENV:NAME} syntax */ export function resolveToken(tok: string): string { const m = tok?.match(/^\$\{ENV:([A-Za-z_][A-Za-z0-9_]*)\}$/); if (m) return process.env[m[1]] || ""; return tok; } /** * Obfuscate sensitive data like tokens for safe logging */ export function obfuscateConfig(config: Record<string, RancherServerConfig>): Record<string, any> { const obfuscated: Record<string, any> = {}; for (const [id, serverConfig] of Object.entries(config)) { obfuscated[id] = { ...serverConfig, token: serverConfig.token ? '***' + serverConfig.token.slice(-4) : undefined }; } return obfuscated; } /** * Remove Kubernetes metadata.managedFields recursively. */ export function stripMetadataManagedFields<T>(value: T): T { const seen = new WeakSet(); const walk = (node: any) => { if (!node || typeof node !== 'object') return; if (seen.has(node)) return; seen.add(node); if (Array.isArray(node)) { for (const item of node) { walk(item); } return; } if (node.metadata && typeof node.metadata === 'object') { if ('managedFields' in node.metadata) { delete node.metadata.managedFields; } walk(node.metadata); } for (const key of Object.keys(node)) { if (key === 'metadata') continue; walk(node[key]); } }; walk(value as any); return value; } /** * Load Rancher server configuration from environment variables */ export function loadConfigFromEnv(): Record<string, RancherServerConfig> { const config: Record<string, RancherServerConfig> = {}; // Look for RANCHER_SERVERS environment variable const serversEnv = process.env.RANCHER_SERVERS; if (serversEnv) { try { const servers = JSON.parse(serversEnv); for (const [id, serverConfig] of Object.entries(servers)) { config[id] = serverConfig as RancherServerConfig; } } catch (e) { console.warn("Cannot parse RANCHER_SERVERS JSON:", e); } } // Look for individual server configurations // Format: RANCHER_SERVER_<ID>_<PROPERTY> const envVars = Object.keys(process.env); const serverPattern = /^RANCHER_SERVER_([A-Za-z0-9_]+)_(.+)$/; for (const envVar of envVars) { const match = envVar.match(serverPattern); if (match) { const [, serverId, property] = match; const propertyLower = property.toLowerCase(); if (!config[serverId]) { config[serverId] = { id: serverId, baseUrl: '', // Will be set below token: '' // Will be set below }; } const value = process.env[envVar]; if (value !== undefined) { switch (propertyLower) { case 'name': config[serverId].name = value; break; case 'baseurl': config[serverId].baseUrl = value; break; case 'token': config[serverId].token = value; break; case 'insecureskiptlsverify': config[serverId].insecureSkipTlsVerify = value.toLowerCase() === 'true'; break; case 'cacertpembase64': config[serverId].caCertPemBase64 = value; break; } } } } return config; } export function loadStore(file: string): Record<string, RancherServerConfig> { try { const fs = require('node:fs'); if (!fs.existsSync(file)) return {}; const raw = fs.readFileSync(file, "utf-8"); const parsed = JSON.parse(raw); return parsed || {}; } catch (e) { console.warn("Cannot read store:", e); return {}; } } export function saveStore(data: Record<string, RancherServerConfig>, file: string) { const fs = require('node:fs'); const path = require('node:path'); // Create directory if it doesn't exist const dir = path.dirname(file); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(file, JSON.stringify(data, null, 2)); }