UNPKG

mcp-server-tester-sse-http-stdio

Version:

MCP Server Tester with SSE support - Test MCP servers using HTTP, SSE, and STDIO transports

110 lines (109 loc) 4.78 kB
/** * Configuration loader for YAML test files and JSON server config */ import fs from 'fs'; import path from 'path'; import YAML from 'yaml'; import Ajv from 'ajv'; // Import JSON schemas import testConfigSchema from '../../schemas/tests-schema.json' with { type: 'json' }; import serverConfigSchema from '../../schemas/server-config-schema.json' with { type: 'json' }; export class ConfigLoader { static ajv = new Ajv.default({ allErrors: true, verbose: true }); /** * Load test configuration from YAML file */ static loadTestConfig(filePath) { const resolvedPath = this.resolvePath(filePath); let content = this.readFile(resolvedPath); // Replace environment variables in the raw content before parsing content = this.replaceEnvironmentVariables(content); let config; try { if (this.isYamlFile(resolvedPath)) { config = YAML.parse(content); } else { config = JSON.parse(content); } } catch (error) { throw new Error(`Invalid configuration format in ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`); } return this.validateWithSchema(config, testConfigSchema, resolvedPath, 'Test configuration'); } /** * Load MCP server configuration from JSON file */ static loadServerConfig(filePath, serverName) { const resolvedPath = this.resolvePath(filePath); const content = this.readFile(resolvedPath); let config; try { config = JSON.parse(content); } catch (error) { throw new Error(`Invalid JSON in server config ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`); } const mcpConfig = this.validateWithSchema(config, serverConfigSchema, resolvedPath, 'Server configuration'); if (serverName) { if (!mcpConfig.mcpServers[serverName]) { const availableServers = Object.keys(mcpConfig.mcpServers).join(', '); throw new Error(`Server '${serverName}' not found in config. Available servers: ${availableServers}`); } return mcpConfig.mcpServers[serverName]; } // If no server name specified, use the first (or only) server const serverNames = Object.keys(mcpConfig.mcpServers); if (serverNames.length === 0) { throw new Error('No servers found in configuration'); } if (serverNames.length > 1) { throw new Error(`Multiple servers found in config: ${serverNames.join(', ')}. Please specify a server name.`); } return mcpConfig.mcpServers[serverNames[0]]; } static resolvePath(filePath) { return path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath); } static readFile(filePath) { if (!fs.existsSync(filePath)) { throw new Error(`Configuration file not found: ${filePath}`); } return fs.readFileSync(filePath, 'utf-8'); } static isYamlFile(filePath) { const ext = path.extname(filePath).toLowerCase(); return ext === '.yaml' || ext === '.yml'; } /** * Validate configuration using JSON Schema with helpful error messages */ static validateWithSchema(config, schema, filePath, configType) { const validate = this.ajv.compile(schema); const valid = validate(config); if (!valid) { const errors = validate.errors || []; const errorMessages = errors.map((error) => { const path = error.instancePath ? `at '${error.instancePath}'` : 'at root'; const message = error.message || 'validation failed'; const data = error.data !== undefined ? ` (got: ${JSON.stringify(error.data)})` : ''; return ` - ${path}: ${message}${data}`; }); throw new Error(`${configType} validation failed in ${filePath}:\n${errorMessages.join('\n')}\n\n` + `Please check your configuration format. See the documentation for examples.`); } return config; } static ENV_VAR_PATTERN = /\$\{([^}]+)\}/g; // matches ${VAR_NAME} and replaces them with the corresponding environment variable value static replaceEnvironmentVariables(content) { return content.replace(this.ENV_VAR_PATTERN, (_, varName) => { const envValue = process.env[varName]; if (envValue === undefined) { throw new Error(`Environment variable '${varName}' is not defined but required for config substitution`); } return envValue; }); } }