Skip to main content
Glama
transformation.ts15.6 kB
import type { OpenAPISpec, MCPServer, MCPTool, ServerConfig, TestCase, ConfigFile, ImportResult, ApiEndpoint, ParameterSchema, PropertySchema, GlobalSettings, } from "@/types"; import { transformToMCPTools, parseFromString, parseFromUrl, parseFromFile, type TransformerOptions, type MCPTool as ParserMCPTool, DEFAULT_TRANSFORMER_OPTIONS, } from "mcp-swagger-parser"; import { generateId } from "./index"; // ============================================================================ // OpenAPI 到 MCP 工具转换 // ============================================================================ /** * 将 OpenAPI 规范转换为 MCP 工具 */ export const convertOpenAPIToMCPTools = async ( spec: OpenAPISpec | string | object, serverId: string, options?: TransformerOptions, ): Promise<MCPTool[]> => { try { const transformerOptions = { ...DEFAULT_TRANSFORMER_OPTIONS, ...options }; let openApiSpec: any; if (typeof spec === "string") { const parseResult = await parseFromString(spec); openApiSpec = parseResult.spec; } else if ("content" in spec) { openApiSpec = spec.content; } else { openApiSpec = spec; } const parserTools: ParserMCPTool[] = await transformToMCPTools( openApiSpec, transformerOptions, ); // 转换为 UI 层的 MCP 工具格式 return parserTools.map((tool) => convertParserToolToUITool(tool, serverId)); } catch (error) { console.error("OpenAPI 转换失败:", error); throw new Error( `OpenAPI 转换失败: ${error instanceof Error ? error.message : "未知错误"}`, ); } }; /** * 将解析器工具转换为 UI 工具格式 */ const convertParserToolToUITool = ( parserTool: ParserMCPTool, serverId: string, ): MCPTool => { return { id: generateId("tool"), name: parserTool.name, description: parserTool.description || "", parameters: convertToParameterSchema(parserTool.inputSchema), serverId, endpoint: parserTool.metadata?.path || "", method: parserTool.metadata?.method?.toUpperCase() || "GET", path: parserTool.metadata?.path || "", createdAt: new Date(), }; }; /** * 转换输入模式为参数模式 */ const convertToParameterSchema = (inputSchema: any): ParameterSchema => { if (!inputSchema || typeof inputSchema !== "object") { return { type: "object", properties: {}, required: [], }; } return { type: "object", properties: convertProperties(inputSchema.properties || {}), required: inputSchema.required || [], }; }; /** * 转换属性定义 */ const convertProperties = (properties: any): Record<string, PropertySchema> => { const converted: Record<string, PropertySchema> = {}; for (const [key, value] of Object.entries(properties)) { if (typeof value === "object" && value !== null) { const prop = value as any; converted[key] = { type: prop.type || "string", description: prop.description, enum: prop.enum, default: prop.default, format: prop.format, items: prop.items ? convertToPropertySchema(prop.items) : undefined, properties: prop.properties ? convertProperties(prop.properties) : undefined, }; } } return converted; }; /** * 转换为属性模式 */ const convertToPropertySchema = (schema: any): PropertySchema => { return { type: schema.type || "string", description: schema.description, enum: schema.enum, default: schema.default, format: schema.format, items: schema.items ? convertToPropertySchema(schema.items) : undefined, properties: schema.properties ? convertProperties(schema.properties) : undefined, }; }; // ============================================================================ // 服务器数据转换 // ============================================================================ /** * 创建新的 MCP 服务器实例 */ export const createMCPServer = (config: ServerConfig): MCPServer => { return { id: generateId("server"), name: config.name || "", version: config.version || "1.0.0", description: config.description || "", port: config.port || 3000, transport: config.transport || "streamable", status: "stopped", healthy: false, endpoint: config.endpoint || "", toolCount: 0, autoStart: config.autoStart || false, tags: config.tags || [], errorMessage: undefined, lastHealthCheck: undefined, createdAt: new Date(), updatedAt: new Date(), // 兼容旧字段 config, tools: [], metrics: { totalRequests: 0, successfulRequests: 0, failedRequests: 0, averageResponseTime: 0, errorRate: 0, activeConnections: 0, uptime: 0, }, }; }; /** * 更新服务器配置 */ export const updateServerConfig = ( server: MCPServer, newConfig: Partial<ServerConfig>, ): MCPServer => { // 确保config中的必需字段不为undefined const mergedConfig: ServerConfig = { name: newConfig.name || server.config?.name || server.name, version: newConfig.version || server.config?.version || server.version, description: newConfig.description || server.config?.description || server.description, port: newConfig.port || server.config?.port || server.port, transport: newConfig.transport || server.config?.transport || server.transport, openApiData: newConfig.openApiData || server.config?.openApiData || {}, config: newConfig.config || server.config?.config || {}, authConfig: newConfig.authConfig || server.config?.authConfig || "", autoStart: newConfig.autoStart !== undefined ? newConfig.autoStart : server.config?.autoStart || server.autoStart, tags: newConfig.tags || server.config?.tags || server.tags, endpoint: newConfig.endpoint || server.config?.endpoint || server.endpoint, authentication: newConfig.authentication || server.config?.authentication, customHeaders: newConfig.customHeaders || server.config?.customHeaders, }; return { ...server, ...newConfig, config: mergedConfig, updatedAt: new Date(), }; }; /** * 克隆服务器配置 */ export const cloneServerConfig = ( server: MCPServer, newName: string, ): MCPServer => { return { ...server, id: generateId("server"), name: newName, status: "stopped", healthy: false, toolCount: 0, errorMessage: undefined, lastHealthCheck: undefined, createdAt: new Date(), updatedAt: new Date(), // 兼容旧字段 tools: [], metrics: { totalRequests: 0, successfulRequests: 0, failedRequests: 0, averageResponseTime: 0, errorRate: 0, activeConnections: 0, uptime: 0, }, }; }; // ============================================================================ // 测试用例数据转换 // ============================================================================ /** * 创建新的测试用例 */ export const createTestCase = ( name: string, toolId: string, parameters: Record<string, any>, tags: string[] = [], ): TestCase => { return { id: generateId("test"), name, toolId, parameters, tags, createdAt: new Date(), updatedAt: new Date(), }; }; /** * 更新测试用例 */ export const updateTestCase = ( testCase: TestCase, updates: Partial<TestCase>, ): TestCase => { return { ...testCase, ...updates, updatedAt: new Date(), }; }; /** * 从工具参数生成测试用例模板 */ export const generateTestCaseTemplate = (tool: MCPTool): Partial<TestCase> => { const parameters: Record<string, any> = {}; // 为每个参数生成默认值 if (tool.parameters.properties) { for (const [key, prop] of Object.entries(tool.parameters.properties)) { parameters[key] = generateDefaultValue(prop); } } return { name: `${tool.name} 测试用例`, toolId: tool.id, parameters, tags: ["auto-generated"], }; }; /** * 根据属性类型生成默认值 */ const generateDefaultValue = (prop: PropertySchema): any => { if (prop.default !== undefined) { return prop.default; } if (prop.enum && prop.enum.length > 0) { return prop.enum[0]; } switch (prop.type) { case "string": return prop.format === "email" ? "example@example.com" : prop.format === "uri" ? "https://example.com" : prop.format === "date" ? "2024-01-01" : prop.format === "date-time" ? "2024-01-01T00:00:00Z" : "example"; case "number": return 0; case "boolean": return false; case "array": return []; case "object": const obj: Record<string, any> = {}; if (prop.properties) { for (const [key, subProp] of Object.entries(prop.properties)) { obj[key] = generateDefaultValue(subProp); } } return obj; default: return null; } }; // ============================================================================ // 配置导入导出转换 // ============================================================================ /** * 将服务器列表转换为配置文件 */ export const serversToConfigFile = ( servers: MCPServer[], options: { includeSensitiveData?: boolean; encryptSensitiveData?: boolean; } = {}, ): ConfigFile => { const { includeSensitiveData = false, encryptSensitiveData = false } = options; const processedServers = servers.map((server) => { const processedServer = { ...server }; // 确保config存在 if (!processedServer.config) { processedServer.config = { name: server.name, version: server.version, description: server.description, port: server.port, transport: server.transport, openApiData: {}, config: {}, authConfig: "", autoStart: server.autoStart, tags: server.tags, endpoint: server.endpoint, }; } // 处理敏感数据 if (!includeSensitiveData && processedServer.config?.authentication) { processedServer.config = { ...processedServer.config, authentication: { ...processedServer.config.authentication, credentials: {}, // 清空凭据 }, }; } return processedServer; }); return { version: "1.0.0", exportedAt: new Date(), servers: processedServers, globalSettings: { theme: "light", language: "zh", autoRefresh: true, refreshInterval: 30000, logLevel: "info", maxLogEntries: 1000, enableNotifications: true, enableWebSocket: true, enableSounds: false, }, encrypted: encryptSensitiveData, }; }; /** * 从配置文件解析服务器列表 */ export const configFileToServers = (configFile: ConfigFile): ImportResult => { try { const servers = configFile.servers || []; const importedServers = servers.length; // 验证每个服务器配置 const errors: string[] = []; const validServers: MCPServer[] = []; servers.forEach((server, index) => { try { // 确保必要的字段存在 if (!server.id) server.id = generateId("server"); if (!server.createdAt) server.createdAt = new Date(); if (!server.updatedAt) server.updatedAt = new Date(); if (!server.status) server.status = "stopped"; if (!server.tools) server.tools = []; if (!server.metrics) { server.metrics = { totalRequests: 0, successfulRequests: 0, failedRequests: 0, averageResponseTime: 0, errorRate: 0, activeConnections: 0, uptime: 0, }; } validServers.push(server); } catch (error) { errors.push( `服务器 ${index + 1}: ${error instanceof Error ? error.message : "未知错误"}`, ); } }); return { success: errors.length === 0, importedServers, errors: errors.length > 0 ? errors : undefined, }; } catch (error) { return { success: false, importedServers: 0, errors: [ `配置文件解析失败: ${error instanceof Error ? error.message : "未知错误"}`, ], }; } }; // ============================================================================ // 数据格式转换工具 // ============================================================================ /** * 将对象转换为 JSON 字符串(格式化) */ export const toFormattedJSON = (obj: any, indent = 2): string => { return JSON.stringify(obj, null, indent); }; /** * 将对象转换为 YAML 字符串 */ export const toYAML = (obj: any): string => { // 简单的 YAML 转换实现 const yamlify = (value: any, indent = 0): string => { const spaces = " ".repeat(indent); if (value === null || value === undefined) { return "null"; } if (typeof value === "string") { return value.includes("\n") || value.includes('"') || value.includes("'") ? `"${value.replace(/"/g, '\\"')}"` : value; } if (typeof value === "number" || typeof value === "boolean") { return String(value); } if (Array.isArray(value)) { if (value.length === 0) return "[]"; return ( "\n" + value .map((item) => `${spaces}- ${yamlify(item, indent + 1)}`) .join("\n") ); } if (typeof value === "object") { const entries = Object.entries(value); if (entries.length === 0) return "{}"; return ( "\n" + entries .map(([key, val]) => { const yamlValue = yamlify(val, indent + 1); return yamlValue.startsWith("\n") ? `${spaces}${key}:${yamlValue}` : `${spaces}${key}: ${yamlValue}`; }) .join("\n") ); } return String(value); }; return yamlify(obj).trim(); }; /** * 从 JSON 或 YAML 字符串解析对象 */ export const parseConfigString = (content: string): any => { const trimmed = content.trim(); // 尝试解析 JSON if (trimmed.startsWith("{") || trimmed.startsWith("[")) { try { return JSON.parse(trimmed); } catch (error) { throw new Error( `JSON 解析失败: ${error instanceof Error ? error.message : "未知错误"}`, ); } } // 简单的 YAML 解析(仅支持基本格式) try { const lines = trimmed.split("\n"); const result: any = {}; const currentPath: string[] = []; for (const line of lines) { const trimmedLine = line.trim(); if (!trimmedLine || trimmedLine.startsWith("#")) continue; const indent = line.length - line.trimStart().length; const colonIndex = trimmedLine.indexOf(":"); if (colonIndex > 0) { const key = trimmedLine.substring(0, colonIndex).trim(); const value = trimmedLine.substring(colonIndex + 1).trim(); // 简化处理,仅支持一级对象 if (value) { result[key] = value === "true" ? true : value === "false" ? false : !isNaN(Number(value)) ? Number(value) : value; } } } return result; } catch (error) { throw new Error( `YAML 解析失败: ${error instanceof Error ? error.message : "未知错误"}`, ); } };

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