Skip to main content
Glama
config.ts4.28 kB
/** * Iris MCP Dashboard - Config API Routes * GET/PUT endpoints for configuration management */ import { Router } from "express"; import { readFileSync, writeFileSync } from "fs"; import { z } from "zod"; import { parseDocument, stringify } from "yaml"; import type { DashboardStateBridge } from "../state-bridge.js"; import { getChildLogger } from "../../../utils/logger.js"; import { getConfigPath } from "../../../utils/paths.js"; const logger = getChildLogger("dashboard:routes:config"); const router = Router(); // Zod schema for config validation (same as TeamsConfigSchema) const IrisConfigSchema = z.object({ path: z.string().min(1, "Path cannot be empty"), description: z.string(), idleTimeout: z.number().positive().optional(), sessionInitTimeout: z.number().positive().optional(), color: z .string() .regex(/^#[0-9a-fA-F]{6}$/, "Invalid hex color") .optional(), }); const ConfigSchema = z.object({ settings: z.object({ idleTimeout: z.number().positive(), maxProcesses: z.number().int().min(1).max(50), healthCheckInterval: z.number().positive(), sessionInitTimeout: z.number().positive(), httpPort: z.number().int().min(1).max(65535).optional(), defaultTransport: z.enum(["stdio", "http"]).optional(), }), dashboard: z .object({ enabled: z.boolean(), port: z.number().int().min(1).max(65535), host: z.string(), }) .optional(), teams: z.record(z.string(), IrisConfigSchema), }); export function createConfigRouter(bridge: DashboardStateBridge): Router { /** * GET /api/config * Returns current configuration */ router.get("/", (req, res) => { try { const config = bridge.getConfig(); res.json({ success: true, config, }); } catch (error: any) { logger.error( { err: error instanceof Error ? error : new Error(String(error)), }, "Failed to get config", ); res.status(500).json({ success: false, error: error.message || "Failed to retrieve configuration", }); } }); /** * PUT /api/config * Saves new configuration to disk * Does NOT apply changes (requires restart) */ router.put("/", (req, res) => { try { // Validate request body const validation = ConfigSchema.safeParse(req.body); if (!validation.success) { const errors = validation.error.errors.map((e) => ({ path: e.path.join("."), message: e.message, })); logger.warn({ errors }, "Config validation failed"); return res.status(400).json({ success: false, error: "Configuration validation failed", details: errors, }); } const newConfig = validation.data; // Get config file path const configPath = getConfigPath(); // Read existing config to preserve comments const existingContent = readFileSync(configPath, "utf8"); const doc = parseDocument(existingContent); // Update document with new values (preserving comments) // Note: This is a simple implementation - in production we'd want to // surgically update only changed values to preserve all formatting const yamlContent = stringify(newConfig, { defaultStringType: "QUOTE_DOUBLE", defaultKeyType: "PLAIN", }); // For now, just write the new YAML (will lose comments) // TODO Phase 2: Implement surgical update to preserve all comments writeFileSync(configPath, yamlContent, "utf8"); logger.info({ configPath }, "Configuration saved to disk"); // Emit event for WebSocket clients bridge.emit("ws:config-saved", { timestamp: Date.now(), configPath, }); res.json({ success: true, message: "Configuration saved. Restart Iris MCP to apply changes.", configPath, }); } catch (error: any) { logger.error( { err: error instanceof Error ? error : new Error(String(error)), }, "Failed to save config", ); res.status(500).json({ success: false, error: error.message || "Failed to save configuration", }); } }); return router; }

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/jenova-marie/iris-mcp'

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