Skip to main content
Glama

Curupira

by drzln
yaml-loader.ts6.18 kB
/** * YAML configuration loader for Curupira MCP server */ import { readFileSync, existsSync } from 'fs' import yaml from 'js-yaml' import { z } from 'zod' import type { ServerConfig } from '../server/server.js' /** * YAML configuration schema */ const YamlConfigSchema = z.object({ server: z.object({ name: z.string().optional(), version: z.string().optional(), host: z.string().optional(), port: z.number().optional(), environment: z.enum(['development', 'staging', 'production']).optional(), logLevel: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).optional(), }).optional(), transport: z.enum(['stdio', 'http', 'sse']).optional(), transports: z.object({ websocket: z.object({ enabled: z.boolean().default(true), path: z.string().default('/mcp'), pingInterval: z.number().optional(), pongTimeout: z.number().optional(), }).optional(), http: z.object({ enabled: z.boolean().default(false), path: z.string().default('/mcp'), timeout: z.number().optional(), }).optional(), sse: z.object({ enabled: z.boolean().default(false), path: z.string().default('/mcp/sse'), keepAliveInterval: z.number().default(30000), }).optional(), }).optional(), healthCheck: z.object({ enabled: z.boolean().default(true), path: z.string().default('/health'), interval: z.number().default(30000), }).optional(), cors: z.object({ origins: z.array(z.string()).optional(), credentials: z.boolean().default(true), }).optional(), rateLimit: z.object({ max: z.number().default(100), window: z.number().default(60000), }).optional(), auth: z.object({ enabled: z.boolean().default(false), jwtSecret: z.string().optional(), tokenExpiry: z.string().default('24h'), }).optional(), chrome: z.object({ enabled: z.boolean().default(false), serviceUrl: z.string().optional(), connectTimeout: z.number().default(5000), pageTimeout: z.number().default(30000), defaultViewport: z.object({ width: z.number().default(1920), height: z.number().default(1080), }).optional(), discovery: z.object({ enabled: z.boolean().default(true), hosts: z.array(z.string()).default(['localhost']), ports: z.array(z.number()).default([3000]), timeout: z.number().default(5000), autoConnect: z.boolean().default(false), preferredPatterns: z.array(z.string()).default(['localhost', 'react', 'vite', 'next']), }).optional(), }).optional(), }).strict() export type YamlConfig = z.infer<typeof YamlConfigSchema> /** * Load configuration from YAML file */ export function loadYamlConfig(configPath: string): Partial<ServerConfig> { if (!existsSync(configPath)) { throw new Error(`Configuration file not found: ${configPath}`) } try { const configContent = readFileSync(configPath, 'utf-8') const rawConfig = yaml.load(configContent) // Validate against schema const validatedConfig = YamlConfigSchema.parse(rawConfig) // Transform to ServerConfig format const serverConfig: Partial<ServerConfig> = { name: validatedConfig.server?.name, version: validatedConfig.server?.version, host: validatedConfig.server?.host, port: validatedConfig.server?.port, transport: validatedConfig.transport, environment: validatedConfig.server?.environment, logLevel: validatedConfig.server?.logLevel, healthCheck: validatedConfig.healthCheck?.enabled, healthCheckPath: validatedConfig.healthCheck?.path, healthCheckInterval: validatedConfig.healthCheck?.interval, } // Configure transports based on YAML if (validatedConfig.transports) { serverConfig.mcp = serverConfig.mcp || {} if (validatedConfig.transports.websocket?.enabled) { serverConfig.mcp.websocket = { enabled: validatedConfig.transports.websocket.enabled, path: validatedConfig.transports.websocket.path, enablePing: validatedConfig.transports.websocket.pingInterval !== undefined, pingInterval: validatedConfig.transports.websocket.pingInterval, pongTimeout: validatedConfig.transports.websocket.pongTimeout, } } if (validatedConfig.transports.http?.enabled || validatedConfig.transports.sse?.enabled) { serverConfig.mcp.http = { enabled: true, httpPath: validatedConfig.transports.http?.path || '/mcp', ssePath: validatedConfig.transports.sse?.path || '/mcp/sse', sseEnabled: validatedConfig.transports.sse?.enabled !== false, // Default to true if not explicitly disabled timeout: validatedConfig.transports.http?.timeout, keepAliveInterval: validatedConfig.transports.sse?.keepAliveInterval, } } } // Configure Chrome if specified (handled separately now) if (validatedConfig.chrome) { // Chrome config is handled by ChromeManager /* serverConfig.chrome = { enabled: validatedConfig.chrome.enabled, serviceUrl: validatedConfig.chrome.serviceUrl, connectTimeout: validatedConfig.chrome.connectTimeout, pageTimeout: validatedConfig.chrome.pageTimeout, defaultViewport: validatedConfig.chrome.defaultViewport, } */ } return serverConfig } catch (error) { if (error instanceof z.ZodError) { throw new Error(`Invalid configuration: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`) } throw new Error(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`) } } /** * Merge YAML config with other config sources */ export function mergeWithYamlConfig( baseConfig: Partial<ServerConfig>, yamlPath?: string ): Partial<ServerConfig> { if (!yamlPath) { return baseConfig } const yamlConfig = loadYamlConfig(yamlPath) // Deep merge, with YAML taking precedence return { ...baseConfig, ...yamlConfig, mcp: { ...baseConfig.mcp, ...yamlConfig.mcp, }, } }

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/drzln/curupira'

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