Skip to main content
Glama
validation.ts11.8 kB
import type { OpenAPISpec, MCPServer, ServerConfig, AuthConfig, TestCase, ValidationError as UIValidationError, ConfigFile, ImportResult, ConfigConflict, } from "@/types"; import { Validator, parseFromString, parseFromUrl, type ValidationResult as ParserValidationResult, type ParserConfig, DEFAULT_PARSER_CONFIG, } from "mcp-swagger-parser"; // ============================================================================ // OpenAPI 规范验证 // ============================================================================ /** * 验证 OpenAPI 规范内容 */ export const validateOpenAPISpec = async ( content: string | object, config?: ParserConfig, ): Promise<{ isValid: boolean; errors: UIValidationError[]; warnings: UIValidationError[]; }> => { try { const parserConfig = { ...DEFAULT_PARSER_CONFIG, ...config }; const validator = new Validator(parserConfig); let spec: any; if (typeof content === "string") { const parseResult = await parseFromString(content, parserConfig); spec = parseResult.spec; } else { spec = content; } const result: ParserValidationResult = await validator.validate(spec); return { isValid: result.isValid, errors: result.errors.map((err) => ({ path: err.path, message: err.message, severity: "error" as const, code: err.code, })), warnings: result.warnings.map((warn) => ({ path: warn.path, message: warn.message, severity: "warning" as const, code: warn.code, })), }; } catch (error) { return { isValid: false, errors: [ { path: "root", message: error instanceof Error ? error.message : "验证过程中发生未知错误", severity: "error" as const, code: "VALIDATION_EXCEPTION", }, ], warnings: [], }; } }; /** * 验证 OpenAPI 规范 URL */ export const validateOpenAPIUrl = async ( url: string, config?: ParserConfig, ): Promise<{ isValid: boolean; errors: UIValidationError[]; warnings: UIValidationError[]; }> => { try { const parserConfig = { ...DEFAULT_PARSER_CONFIG, ...config }; const parseResult = await parseFromUrl(url, parserConfig); const validator = new Validator(parserConfig); const result: ParserValidationResult = await validator.validate( parseResult.spec, ); return { isValid: result.isValid, errors: result.errors.map((err) => ({ path: err.path, message: err.message, severity: "error" as const, code: err.code, })), warnings: result.warnings.map((warn) => ({ path: warn.path, message: warn.message, severity: "warning" as const, code: warn.code, })), }; } catch (error) { return { isValid: false, errors: [ { path: "root", message: error instanceof Error ? error.message : "URL 验证过程中发生未知错误", severity: "error" as const, code: "URL_VALIDATION_EXCEPTION", }, ], warnings: [], }; } }; // ============================================================================ // 服务器配置验证 // ============================================================================ /** * 验证服务器配置 */ export const validateServerConfig = ( config: Partial<ServerConfig>, ): UIValidationError[] => { const errors: UIValidationError[] = []; // 验证服务器名称 if (!config.name) { errors.push({ path: "name", message: "服务器名称不能为空", severity: "error", code: "REQUIRED_FIELD", }); } else if (config.name.length < 2 || config.name.length > 50) { errors.push({ path: "name", message: "服务器名称长度必须在 2-50 个字符之间", severity: "error", code: "INVALID_LENGTH", }); } else if (!/^[a-zA-Z0-9\u4e00-\u9fa5_-]+$/.test(config.name)) { errors.push({ path: "name", message: "服务器名称只能包含字母、数字、中文、下划线和连字符", severity: "error", code: "INVALID_FORMAT", }); } // 验证端点 URL if (!config.endpoint) { errors.push({ path: "endpoint", message: "服务器端点不能为空", severity: "error", code: "REQUIRED_FIELD", }); } else { try { const url = new URL(config.endpoint); if (!["http:", "https:"].includes(url.protocol)) { errors.push({ path: "endpoint", message: "端点必须使用 HTTP 或 HTTPS 协议", severity: "error", code: "INVALID_PROTOCOL", }); } } catch { errors.push({ path: "endpoint", message: "端点 URL 格式无效", severity: "error", code: "INVALID_URL", }); } } // 验证 OpenAPI 规范 if (!config.openApiSpec) { errors.push({ path: "openApiSpec", message: "OpenAPI 规范不能为空", severity: "error", code: "REQUIRED_FIELD", }); } // 验证认证配置 if (config.authentication) { const authErrors = validateAuthConfig(config.authentication); errors.push( ...authErrors.map((err) => ({ ...err, path: `authentication.${err.path}`, })), ); } return errors; }; /** * 验证认证配置 */ export const validateAuthConfig = ( config: Partial<AuthConfig>, ): UIValidationError[] => { const errors: UIValidationError[] = []; if (!config.type) { errors.push({ path: "type", message: "认证类型不能为空", severity: "error", code: "REQUIRED_FIELD", }); return errors; } const { type, credentials } = config; if (!credentials) { errors.push({ path: "credentials", message: "认证凭据不能为空", severity: "error", code: "REQUIRED_FIELD", }); return errors; } switch (type) { case "bearer": if (!credentials.token) { errors.push({ path: "credentials.token", message: "Bearer Token 不能为空", severity: "error", code: "REQUIRED_FIELD", }); } break; case "apikey": if (!credentials.apiKey) { errors.push({ path: "credentials.apiKey", message: "API Key 不能为空", severity: "error", code: "REQUIRED_FIELD", }); } break; case "basic": if (!credentials.username) { errors.push({ path: "credentials.username", message: "用户名不能为空", severity: "error", code: "REQUIRED_FIELD", }); } if (!credentials.password) { errors.push({ path: "credentials.password", message: "密码不能为空", severity: "error", code: "REQUIRED_FIELD", }); } break; case "oauth2": if (!credentials.clientId) { errors.push({ path: "credentials.clientId", message: "Client ID 不能为空", severity: "error", code: "REQUIRED_FIELD", }); } if (!credentials.clientSecret) { errors.push({ path: "credentials.clientSecret", message: "Client Secret 不能为空", severity: "error", code: "REQUIRED_FIELD", }); } break; } return errors; }; // ============================================================================ // 测试用例验证 // ============================================================================ /** * 验证测试用例 */ export const validateTestCase = ( testCase: Partial<TestCase>, ): UIValidationError[] => { const errors: UIValidationError[] = []; if (!testCase.name) { errors.push({ path: "name", message: "测试用例名称不能为空", severity: "error", code: "REQUIRED_FIELD", }); } else if (testCase.name.length < 2 || testCase.name.length > 100) { errors.push({ path: "name", message: "测试用例名称长度必须在 2-100 个字符之间", severity: "error", code: "INVALID_LENGTH", }); } if (!testCase.toolId) { errors.push({ path: "toolId", message: "工具 ID 不能为空", severity: "error", code: "REQUIRED_FIELD", }); } if (!testCase.parameters || typeof testCase.parameters !== "object") { errors.push({ path: "parameters", message: "参数必须是有效的对象", severity: "error", code: "INVALID_TYPE", }); } return errors; }; // ============================================================================ // 配置文件验证 // ============================================================================ /** * 验证配置文件格式 */ export const validateConfigFile = ( config: any, ): { isValid: boolean; errors: UIValidationError[] } => { const errors: UIValidationError[] = []; if (!config || typeof config !== "object") { errors.push({ path: "root", message: "配置文件必须是有效的 JSON 对象", severity: "error", code: "INVALID_FORMAT", }); return { isValid: false, errors }; } if (!config.version) { errors.push({ path: "version", message: "配置文件缺少版本信息", severity: "error", code: "MISSING_VERSION", }); } if (!config.exportedAt) { errors.push({ path: "exportedAt", message: "配置文件缺少导出时间", severity: "error", code: "MISSING_TIMESTAMP", }); } if (config.servers && Array.isArray(config.servers)) { config.servers.forEach((server: any, index: number) => { const serverErrors = validateServerConfig(server); errors.push( ...serverErrors.map((err) => ({ ...err, path: `servers[${index}].${err.path}`, })), ); }); } return { isValid: errors.length === 0, errors, }; }; /** * 检测配置冲突 */ export const detectConfigConflicts = ( existingServers: MCPServer[], importedServers: MCPServer[], ): ConfigConflict[] => { const conflicts: ConfigConflict[] = []; importedServers.forEach((importedServer) => { const existingServer = existingServers.find( (s) => s.id === importedServer.id, ); if (existingServer) { // 检查名称冲突 if (existingServer.name !== importedServer.name) { conflicts.push({ type: "server", id: importedServer.id, field: `servers.${importedServer.id}.name`, currentValue: existingServer.name, existingValue: existingServer.name, newValue: importedServer.name, }); } // 检查端点冲突 if (existingServer.endpoint !== importedServer.endpoint) { conflicts.push({ type: "server", id: importedServer.id, field: `servers.${importedServer.id}.endpoint`, currentValue: existingServer.endpoint, existingValue: existingServer.endpoint, newValue: importedServer.endpoint, }); } // 检查配置冲突 if ( JSON.stringify(existingServer.config) !== JSON.stringify(importedServer.config) ) { conflicts.push({ type: "server", id: importedServer.id, field: `servers.${importedServer.id}.config`, currentValue: existingServer.config, existingValue: existingServer.config, newValue: importedServer.config, }); } } }); return conflicts; };

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/zaizaizhao/mcp-swagger-server'

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