import { DeepNLPSearchParams, DeepNLPResponse, MCPServerConfig, MCPTool, MCPProvider, } from "@/lib/types/mcp"; import { MCPCache } from "@/lib/cache/mcp-cache"; const DEEPNLP_API_BASE = "https://www.deepnlp.org/api/mcp_marketplace/v1"; export class DeepNLPService { // Mapear categorias do projeto para categorias DeepNLP private static categoryMap = { productivity: ["office"], development: ["code", "browser"], data: ["database"], integration: ["search", "payment"], other: ["map", "chart", "finance"], }; // Mapear categorias DeepNLP para categorias internas private static reverseCategoryMap: Record = { office: "office", code: "code", browser: "browser", database: "database", search: "search", payment: "payment", map: "map", chart: "chart", finance: "finance", }; /** * Buscar servidores MCP na API DeepNLP */ static async searchMCPs( params: DeepNLPSearchParams, retryCount = 0 ): Promise { const maxRetries = 3; const baseDelay = 1000; // 1 segundo try { // Verificar cache primeiro const cacheKey = MCPCache.generateKey(params); const cachedResult = MCPCache.get(cacheKey); if (cachedResult) { console.log("Returning cached search results"); return cachedResult; } const url = new URL(DEEPNLP_API_BASE); // Adicionar parâmetros de busca if (params.query) url.searchParams.set("query", params.query); if (params.category) url.searchParams.set("category", params.category); if (params.id) url.searchParams.set("id", params.id); if (params.page_id !== undefined) url.searchParams.set("page_id", params.page_id.toString()); if (params.count_per_page !== undefined) url.searchParams.set( "count_per_page", params.count_per_page.toString() ); if (params.offset !== undefined) url.searchParams.set("offset", params.offset.toString()); if (params.mode) url.searchParams.set("mode", params.mode); console.log( `DeepNLP API Request (attempt ${retryCount + 1}):`, url.toString() ); const response = await fetch(url.toString(), { method: "GET", headers: { Accept: "application/json", "User-Agent": "Ollahub-MCP-Client/1.0", }, // Timeout de 10 segundos signal: AbortSignal.timeout(10000), }); if (!response.ok) { throw new Error( `DeepNLP API error: ${response.status} ${response.statusText}` ); } const data = await response.json(); console.log("DeepNLP API Response:", data); // Salvar no cache MCPCache.setWithDataType(cacheKey, data, "search"); return data; } catch (error) { console.error( `Error calling DeepNLP API (attempt ${retryCount + 1}):`, error ); // Se ainda temos tentativas restantes, tentar novamente com backoff exponencial if (retryCount < maxRetries) { const delay = baseDelay * Math.pow(2, retryCount); // Backoff exponencial console.log(`Retrying in ${delay}ms...`); await new Promise((resolve) => setTimeout(resolve, delay)); return this.searchMCPs(params, retryCount + 1); } // Se esgotamos as tentativas, lançar erro throw new Error( `Failed to fetch MCPs after ${maxRetries + 1} attempts: ${ error instanceof Error ? error.message : "Unknown error" }` ); } } /** * Obter configuração completa de um servidor específico */ static async getServerConfig( id: string, retryCount = 0 ): Promise { const maxRetries = 3; const baseDelay = 1000; try { // Verificar cache primeiro const cacheKey = MCPCache.generateServerConfigKey(id); const cachedResult = MCPCache.get(cacheKey); if (cachedResult) { console.log("Returning cached server config"); return cachedResult; } const [owner, repo] = id.split("/"); if (!owner || !repo) { throw new Error( `Invalid MCP ID format: ${id}. Expected format: owner/repo` ); } const url = `${DEEPNLP_API_BASE}/server/${owner}/${repo}`; console.log( `DeepNLP Server Config Request (attempt ${retryCount + 1}):`, url ); const response = await fetch(url, { method: "GET", headers: { Accept: "application/json", "User-Agent": "Ollahub-MCP-Client/1.0", }, signal: AbortSignal.timeout(10000), }); if (!response.ok) { throw new Error( `DeepNLP Server Config API error: ${response.status} ${response.statusText}` ); } const data = await response.json(); console.log("DeepNLP Server Config Response:", data); // Salvar no cache MCPCache.setWithDataType(cacheKey, data, "config"); return data; } catch (error) { console.error( `Error getting server config from DeepNLP (attempt ${retryCount + 1}):`, error ); if (retryCount < maxRetries) { const delay = baseDelay * Math.pow(2, retryCount); console.log(`Retrying server config in ${delay}ms...`); await new Promise((resolve) => setTimeout(resolve, delay)); return this.getServerConfig(id, retryCount + 1); } throw new Error( `Failed to get server config after ${maxRetries + 1} attempts: ${ error instanceof Error ? error.message : "Unknown error" }` ); } } /** * Listar ferramentas de um servidor específico */ static async getServerTools(id: string, retryCount = 0): Promise { const maxRetries = 3; const baseDelay = 1000; try { // Verificar cache primeiro const cacheKey = MCPCache.generateServerToolsKey(id); const cachedResult = MCPCache.get(cacheKey); if (cachedResult) { console.log("Returning cached server tools"); return cachedResult; } const [owner, repo] = id.split("/"); if (!owner || !repo) { throw new Error( `Invalid MCP ID format: ${id}. Expected format: owner/repo` ); } const url = `${DEEPNLP_API_BASE}/tools/${owner}/${repo}`; console.log( `DeepNLP Server Tools Request (attempt ${retryCount + 1}):`, url ); const response = await fetch(url, { method: "GET", headers: { Accept: "application/json", "User-Agent": "Ollahub-MCP-Client/1.0", }, signal: AbortSignal.timeout(10000), }); if (!response.ok) { throw new Error( `DeepNLP Server Tools API error: ${response.status} ${response.statusText}` ); } const data = await response.json(); console.log("DeepNLP Server Tools Response:", data); const tools = data.tools || []; // Salvar no cache MCPCache.setWithDataType(cacheKey, tools, "tools"); return tools; } catch (error) { console.error( `Error getting server tools from DeepNLP (attempt ${retryCount + 1}):`, error ); if (retryCount < maxRetries) { const delay = baseDelay * Math.pow(2, retryCount); console.log(`Retrying server tools in ${delay}ms...`); await new Promise((resolve) => setTimeout(resolve, delay)); return this.getServerTools(id, retryCount + 1); } throw new Error( `Failed to get server tools after ${maxRetries + 1} attempts: ${ error instanceof Error ? error.message : "Unknown error" }` ); } } /** * Transformar resposta DeepNLP para formato interno MCPProvider */ static transformToMCPProvider(deepnlpItem: any): MCPProvider { const category = this.mapCategory(deepnlpItem.subfield || "other"); return { id: deepnlpItem.id || "", name: deepnlpItem.content_name || "", author: deepnlpItem.publisher_id || "", description: deepnlpItem.description || "", version: "1.0.0", // DeepNLP não fornece versão específica category, tags: deepnlpItem.content_tag_list ? deepnlpItem.content_tag_list .split(",") .map((tag: string) => tag.trim()) : [], rating: parseFloat(deepnlpItem.rating || "0"), totalRatings: parseInt(deepnlpItem.review_cnt || "0"), repository: deepnlpItem.website, homepage: deepnlpItem.website, installed: false, // Será verificado localmente subfield: deepnlpItem.subfield, field: deepnlpItem.field, config: deepnlpItem.config || [], tools: deepnlpItem.ext_info?.tools || [], }; } /** * Mapear categoria DeepNLP para categoria interna */ static mapCategory(subfield: string): MCPProvider["category"] { return this.reverseCategoryMap[subfield?.toLowerCase()] || "other"; } /** * Obter mapeamento de categoria interna para DeepNLP */ static getCategoryMapping(internalCategory: string): string | undefined { const mappings = this.categoryMap[internalCategory as keyof typeof this.categoryMap]; return mappings?.[0]; // Retorna a primeira categoria DeepNLP correspondente } /** * Verificar se a API está disponível */ static async checkHealth(): Promise { try { const response = await fetch(`${DEEPNLP_API_BASE}?count_per_page=1`, { method: "GET", headers: { Accept: "application/json", "User-Agent": "Ollahub-MCP-Client/1.0", }, }); return response.ok; } catch (error) { console.error("DeepNLP API health check failed:", error); return false; } } }