refactor(api): migrate MCP listing from DeepNLP to PulseMCP service
- Updated imports to use PulseMCPSearchParams and PulseMCPService instead of DeepNLP equivalents - Replaced DeepNLP API calls with PulseMCP API endpoints and parameters - Modified cached MCP transformation to manual mapping for PulseMCP data format - Enhanced error handling and retry logic in server tools retrieval - Added PulseMCP API base URL constant for centralized configuration This change switches the underlying MCP provider from DeepNLP to PulseMCP, ensuring compatibility with the new API while maintaining existing functionality for MCP listing and tool retrieval.
This commit is contained in:
1 parent
ce010d39ee
commit
1042f2136b
15 files changed
+1237
-398
No files matched your search
+161
-108
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
DeepNLPSearchParams,
|
||||
DeepNLPResponse,
|
||||
MCPServerConfig,
|
||||
MCPTool,
|
||||
MCPProvider
|
||||
import {
|
||||
DeepNLPSearchParams,
|
||||
DeepNLPResponse,
|
||||
MCPServerConfig,
|
||||
MCPTool,
|
||||
MCPProvider,
|
||||
} from "@/lib/types/mcp";
|
||||
import { MCPCache } from "@/lib/cache/mcp-cache";
|
||||
|
||||
@@ -12,30 +12,33 @@ 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']
|
||||
productivity: ["office"],
|
||||
development: ["code", "browser"],
|
||||
data: ["database"],
|
||||
integration: ["search", "payment"],
|
||||
other: ["map", "chart", "finance"],
|
||||
};
|
||||
|
||||
// Mapear categorias DeepNLP para categorias internas
|
||||
private static reverseCategoryMap: Record<string, MCPProvider['category']> = {
|
||||
'office': 'office',
|
||||
'code': 'code',
|
||||
'browser': 'browser',
|
||||
'database': 'database',
|
||||
'search': 'search',
|
||||
'payment': 'payment',
|
||||
'map': 'map',
|
||||
'chart': 'chart',
|
||||
'finance': 'finance'
|
||||
private static reverseCategoryMap: Record<string, MCPProvider["category"]> = {
|
||||
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<DeepNLPResponse> {
|
||||
static async searchMCPs(
|
||||
params: DeepNLPSearchParams,
|
||||
retryCount = 0
|
||||
): Promise<DeepNLPResponse> {
|
||||
const maxRetries = 3;
|
||||
const baseDelay = 1000; // 1 segundo
|
||||
|
||||
@@ -43,67 +46,88 @@ export class DeepNLPService {
|
||||
// Verificar cache primeiro
|
||||
const cacheKey = MCPCache.generateKey(params);
|
||||
const cachedResult = MCPCache.get(cacheKey);
|
||||
|
||||
|
||||
if (cachedResult) {
|
||||
console.log('Returning cached search results');
|
||||
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());
|
||||
// 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',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Ollahub-MCP-Client/1.0'
|
||||
Accept: "application/json",
|
||||
"User-Agent": "Ollahub-MCP-Client/1.0",
|
||||
},
|
||||
// Timeout de 10 segundos
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`DeepNLP API error: ${response.status} ${response.statusText}`);
|
||||
throw new Error(
|
||||
`DeepNLP API error: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('DeepNLP API Response:', data);
|
||||
console.log("DeepNLP API Response:", data);
|
||||
|
||||
// Salvar no cache
|
||||
MCPCache.setWithDataType(cacheKey, data, 'search');
|
||||
MCPCache.setWithDataType(cacheKey, data, "search");
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(`Error calling DeepNLP API (attempt ${retryCount + 1}):`, 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));
|
||||
|
||||
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'}`);
|
||||
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<MCPServerConfig> {
|
||||
static async getServerConfig(
|
||||
id: string,
|
||||
retryCount = 0
|
||||
): Promise<MCPServerConfig> {
|
||||
const maxRetries = 3;
|
||||
const baseDelay = 1000;
|
||||
|
||||
@@ -111,52 +135,66 @@ export class DeepNLPService {
|
||||
// Verificar cache primeiro
|
||||
const cacheKey = MCPCache.generateServerConfigKey(id);
|
||||
const cachedResult = MCPCache.get(cacheKey);
|
||||
|
||||
|
||||
if (cachedResult) {
|
||||
console.log('Returning cached server config');
|
||||
console.log("Returning cached server config");
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
const [owner, repo] = id.split('/');
|
||||
const [owner, repo] = id.split("/");
|
||||
if (!owner || !repo) {
|
||||
throw new Error(`Invalid MCP ID format: ${id}. Expected format: 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);
|
||||
console.log(
|
||||
`DeepNLP Server Config Request (attempt ${retryCount + 1}):`,
|
||||
url
|
||||
);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Ollahub-MCP-Client/1.0'
|
||||
Accept: "application/json",
|
||||
"User-Agent": "Ollahub-MCP-Client/1.0",
|
||||
},
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`DeepNLP Server Config API error: ${response.status} ${response.statusText}`);
|
||||
throw new Error(
|
||||
`DeepNLP Server Config API error: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('DeepNLP Server Config Response:', data);
|
||||
console.log("DeepNLP Server Config Response:", data);
|
||||
|
||||
// Salvar no cache
|
||||
MCPCache.setWithDataType(cacheKey, data, 'config');
|
||||
MCPCache.setWithDataType(cacheKey, data, "config");
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(`Error getting server config from DeepNLP (attempt ${retryCount + 1}):`, 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));
|
||||
|
||||
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'}`);
|
||||
|
||||
throw new Error(
|
||||
`Failed to get server config after ${maxRetries + 1} attempts: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,54 +209,68 @@ export class DeepNLPService {
|
||||
// Verificar cache primeiro
|
||||
const cacheKey = MCPCache.generateServerToolsKey(id);
|
||||
const cachedResult = MCPCache.get(cacheKey);
|
||||
|
||||
|
||||
if (cachedResult) {
|
||||
console.log('Returning cached server tools');
|
||||
console.log("Returning cached server tools");
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
const [owner, repo] = id.split('/');
|
||||
const [owner, repo] = id.split("/");
|
||||
if (!owner || !repo) {
|
||||
throw new Error(`Invalid MCP ID format: ${id}. Expected format: 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);
|
||||
console.log(
|
||||
`DeepNLP Server Tools Request (attempt ${retryCount + 1}):`,
|
||||
url
|
||||
);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Ollahub-MCP-Client/1.0'
|
||||
Accept: "application/json",
|
||||
"User-Agent": "Ollahub-MCP-Client/1.0",
|
||||
},
|
||||
signal: AbortSignal.timeout(10000)
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`DeepNLP Server Tools API error: ${response.status} ${response.statusText}`);
|
||||
throw new Error(
|
||||
`DeepNLP Server Tools API error: ${response.status} ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('DeepNLP Server Tools Response:', data);
|
||||
console.log("DeepNLP Server Tools Response:", data);
|
||||
|
||||
const tools = data.tools || [];
|
||||
|
||||
// Salvar no cache
|
||||
MCPCache.setWithDataType(cacheKey, tools, 'tools');
|
||||
MCPCache.setWithDataType(cacheKey, tools, "tools");
|
||||
|
||||
return tools;
|
||||
} catch (error) {
|
||||
console.error(`Error getting server tools from DeepNLP (attempt ${retryCount + 1}):`, 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));
|
||||
|
||||
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'}`);
|
||||
|
||||
throw new Error(
|
||||
`Failed to get server tools after ${maxRetries + 1} attempts: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,44 +278,45 @@ export class DeepNLPService {
|
||||
* Transformar resposta DeepNLP para formato interno MCPProvider
|
||||
*/
|
||||
static transformToMCPProvider(deepnlpItem: any): MCPProvider {
|
||||
const category = this.mapCategory(deepnlpItem.subfield || 'other');
|
||||
|
||||
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
|
||||
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'),
|
||||
downloads: parseInt(deepnlpItem.review_cnt || '0') * 10, // Estimativa baseada em reviews
|
||||
iconUrl: deepnlpItem.thumbnail_picture,
|
||||
screenshotUrls: [],
|
||||
capabilities: deepnlpItem.ext_info?.tools || [],
|
||||
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 || []
|
||||
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';
|
||||
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];
|
||||
const mappings =
|
||||
this.categoryMap[internalCategory as keyof typeof this.categoryMap];
|
||||
return mappings?.[0]; // Retorna a primeira categoria DeepNLP correspondente
|
||||
}
|
||||
|
||||
@@ -273,15 +326,15 @@ export class DeepNLPService {
|
||||
static async checkHealth(): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${DEEPNLP_API_BASE}?count_per_page=1`, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Ollahub-MCP-Client/1.0'
|
||||
}
|
||||
Accept: "application/json",
|
||||
"User-Agent": "Ollahub-MCP-Client/1.0",
|
||||
},
|
||||
});
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
console.error('DeepNLP API health check failed:', error);
|
||||
console.error("DeepNLP API health check failed:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user