Skip to main content
Glama
server-config-manager.ts4.72 kB
import { makeError } from "@mcpx/toolkit-core/data"; import fs from "fs"; import path from "path"; import { Logger } from "winston"; import { z } from "zod/v4"; import { TargetServer, targetServerConfigSchema, } from "../model/target-servers.js"; import { InvalidSchemaError } from "../errors.js"; import { loggableError } from "@mcpx/toolkit-core/logging"; import { env } from "../env.js"; /** * Manages reading and writing of target server configurations from/to disk */ export class ServerConfigManager { constructor( private configPath: string, private logger: Logger, ) { this.logger = logger.child({ component: "ServerConfigManager" }); } /** * Reads target servers configuration from disk */ readTargetServers(): TargetServer[] { try { // Check if file exists first if (!fs.existsSync(this.configPath)) { this.logger.debug( "Target servers config file does not exist, using empty config", { configPath: this.configPath, }, ); return []; } // Get file stats to check if it's empty const stats = fs.statSync(this.configPath); if (stats.size === 0) { this.logger.debug( "Target servers config file is completely empty (0 bytes), using empty config", { configPath: this.configPath, fileSize: stats.size, }, ); return []; } const file = fs.readFileSync(this.configPath, "utf8"); // Check if file content is empty or only contains whitespace/newlines if (!file || file.trim().length === 0) { this.logger.debug( "Target servers config file is empty or contains only whitespace/newlines, using empty config", { fileLength: file.length, fileContent: `"${file}"`, configPath: this.configPath, }, ); return []; } const config = JSON.parse(file); const parsed = targetServerConfigSchema.parse(config); return Object.entries(parsed.mcpServers).map(([name, config]) => ({ name, ...config, })); } catch (e: unknown) { const error = makeError(e); // Log file not found errors as debug, other errors as error if (e instanceof Error && "code" in e && e.code === "ENOENT") { this.logger.debug( "Target servers config file not found, using empty config", error, ); return []; } else if (e instanceof SyntaxError) { // Handle JSON parsing errors specifically this.logger.error( "Error parsing target server config. Invalid JSON format:", { errorName: error.name, errorMessage: e.message, configPath: this.configPath, }, ); throw new InvalidSchemaError( `Configuration file "${env.SERVERS_CONFIG_PATH}" has an invalid JSON format: ${e.message}`, ); } else if (e instanceof z.ZodError) { // For Zod validation errors, create a simple user-friendly message const userFriendlyMessage = z.prettifyError(e); this.logger.error( "Failed to read target servers config - Validation Error:", { errorName: error.name, errorMessage: e.message, configPath: this.configPath, }, ); throw new InvalidSchemaError( `Configuration validation failed: ${userFriendlyMessage}`, ); } else { // For other errors this.logger.error("Failed to read target servers config", error); throw new InvalidSchemaError( `Error reading target server config: ${error.message}`, ); } } } /** * Writes target servers configuration to disk */ writeTargetServers(servers: TargetServer[]): void { try { const config = targetServerConfigSchema.parse({ mcpServers: Object.fromEntries( servers.map((server) => [server.name, server]), ), }); const configDir = path.dirname(this.configPath); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2)); this.logger.info("Updated target servers config", { configPath: this.configPath, }); } catch (e: unknown) { const error = loggableError(e); this.logger.error("Failed to write target servers config", { error }); throw error; } } /** * Gets the configuration file path */ getConfigPath(): string { return this.configPath; } }

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/TheLunarCompany/lunar'

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