diff --git a/app/api/mcp/list/route.ts b/app/api/mcp/list/route.ts index 96424ee..d1fe336 100644 --- a/app/api/mcp/list/route.ts +++ b/app/api/mcp/list/route.ts @@ -2,13 +2,15 @@ import { NextResponse } from "next/server"; import { MCPProvider, MCPSearchParams, - DeepNLPSearchParams, + PulseMCPSearchParams, } from "@/lib/types/mcp"; -import { DeepNLPService } from "@/lib/services/deepnlp"; +import { PulseMCPService } from "@/lib/services/pulsemcp"; import { MCPRepository } from "@/database/repositories/mcp"; import { MCPCacheRepository } from "@/database/repositories/mcp-cache"; import { MCPSyncService } from "@/lib/services/mcp-sync"; +const PULSEMCP_API_BASE = "https://api.pulsemcp.com/v0beta"; + export const dynamic = "force-dynamic"; export async function GET(request: Request) { @@ -55,10 +57,25 @@ export async function GET(request: Request) { if (cachedMcps.length > 0 || cacheValid) { console.log(`Found ${cachedMcps.length} MCPs in cache`); - // Transformar para formato interno - const mcps = cachedMcps.map((item) => - DeepNLPService.transformToMCPProvider(item) - ); + // Os dados já estão no formato interno após transformação no saveBatch + const mcps = cachedMcps.map((item) => ({ + id: item.id, + name: item.content_name, + author: item.publisher_id, + description: item.description || "", + version: "1.0.0", + category: item.category as MCPProvider["category"], + tags: item.content_tag_list ? item.content_tag_list.split(",") : [], + rating: item.rating, + totalRatings: item.review_cnt, + repository: item.detail_url || "", + homepage: item.website || "", + installed: false, // Será definido abaixo + subfield: item.subfield || "", + field: item.field || "", + config: [], + tools: [], + })); // Adicionar status de instalação const installedMCPs = MCPRepository.listInstalled(); @@ -81,32 +98,46 @@ export async function GET(request: Request) { }); } - // 3. Fallback: buscar da API se cache vazio - console.log("Cache empty, falling back to DeepNLP API"); + // 3. Fallback: buscar da API PulseMCP se cache vazio + console.log("Cache empty, falling back to PulseMCP API"); - const deepnlpCategory = - category && category !== "all" - ? DeepNLPService.getCategoryMapping(category) - : undefined; - - const deepnlpParams: DeepNLPSearchParams = { + const pulseMCPParams: PulseMCPSearchParams = { query: search, - category: deepnlpCategory, - page_id: page, count_per_page: limit, - mode: "list", + offset: page * limit, }; - console.log("Calling DeepNLP API with params:", deepnlpParams); + console.log("Calling PulseMCP API with params:", pulseMCPParams); - const response = await DeepNLPService.searchMCPs(deepnlpParams); - const mcps = response.items.map((item) => - DeepNLPService.transformToMCPProvider(item) + const response = await fetch( + `${PULSEMCP_API_BASE}/servers?${new URLSearchParams({ + ...(search && { query: search }), + count_per_page: limit.toString(), + offset: (page * limit).toString(), + })}`, + { + method: "GET", + headers: { + Accept: "application/json", + "User-Agent": "Ollahub-MCP-Client/1.0 (https://ollahub.com)", + }, + } + ); + + if (!response.ok) { + throw new Error( + `PulseMCP API error: ${response.status} ${response.statusText}` + ); + } + + const data = await response.json(); + const mcps = data.servers.map((item: any) => + PulseMCPService.transformToMCPProvider(item) ); // Adicionar status de instalação const installedMCPs = MCPRepository.listInstalled(); - const mcpsWithStatus = mcps.map((mcp) => ({ + const mcpsWithStatus = mcps.map((mcp: MCPProvider) => ({ ...mcp, installed: installedMCPs.includes(mcp.id), })); @@ -114,12 +145,12 @@ export async function GET(request: Request) { return NextResponse.json({ success: true, mcps: mcpsWithStatus, - total: response.total_hits, - hasMore: response.total_hits > (page + 1) * limit, + total: data.total_count, + hasMore: data.total_count > (page + 1) * limit, cached: false, }); } catch (error: any) { - console.error("Error fetching MCPs from DeepNLP:", error); + console.error("Error fetching MCPs from PulseMCP:", error); // Fallback para dados mock em caso de erro console.log("Falling back to mock data due to API error"); @@ -136,13 +167,13 @@ export async function GET(request: Request) { tags: ["maps", "location", "directions"], rating: 4.5, totalRatings: 2, - downloads: 20, - capabilities: ["geocoding", "directions", "places"], repository: "https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps", installed: false, subfield: "MAP", field: "MCP SERVER", + config: [], + tools: [], }, ]; diff --git a/app/api/mcp/test-pulsemcp/route.ts b/app/api/mcp/test-pulsemcp/route.ts new file mode 100644 index 0000000..c3d8028 --- /dev/null +++ b/app/api/mcp/test-pulsemcp/route.ts @@ -0,0 +1,69 @@ +import { NextResponse } from "next/server"; +import { PulseMCPService } from "@/lib/services/pulsemcp"; + +const PULSEMCP_API_BASE = "https://api.pulsemcp.com/v0beta"; + +export const dynamic = "force-dynamic"; + +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const limit = parseInt(searchParams.get("limit") || "20"); + const offset = parseInt(searchParams.get("offset") || "0"); + + console.log("🔍 Testando PulseMCP API diretamente..."); + + // Chamar API PulseMCP diretamente + const response = await fetch( + `${PULSEMCP_API_BASE}/servers?${new URLSearchParams({ + count_per_page: limit.toString(), + offset: offset.toString(), + })}`, + { + method: "GET", + headers: { + Accept: "application/json", + "User-Agent": "Ollahub-MCP-Client/1.0 (https://ollahub.com)", + }, + } + ); + + if (!response.ok) { + throw new Error( + `PulseMCP API error: ${response.status} ${response.statusText}` + ); + } + + const data = await response.json(); + console.log( + `✅ PulseMCP API respondeu com ${data.servers.length} servidores` + ); + + // Transformar para formato interno + const mcps = data.servers.map((item: any) => + PulseMCPService.transformToMCPProvider(item) + ); + + return NextResponse.json({ + success: true, + mcps: mcps, + total: data.total_count, + hasMore: data.total_count > offset + limit, + source: "pulsemcp-direct", + message: `Listando ${mcps.length} servidores diretamente da API PulseMCP`, + }); + } catch (error: any) { + console.error("❌ Erro ao chamar PulseMCP API:", error); + + return NextResponse.json( + { + success: false, + error: error.message, + mcps: [], + total: 0, + source: "pulsemcp-direct-error", + }, + { status: 500 } + ); + } +} diff --git a/components/mcp/MCPCard.tsx b/components/mcp/MCPCard.tsx index 3271b81..42de537 100644 --- a/components/mcp/MCPCard.tsx +++ b/components/mcp/MCPCard.tsx @@ -1,6 +1,13 @@ "use client"; -import { Package, Star, Download, Eye, ExternalLink, CheckCircle, Zap } from "lucide-react"; +import { + Package, + Star, + Eye, + ExternalLink, + CheckCircle, + Zap, +} from "lucide-react"; import { MCPProvider } from "@/lib/types/mcp"; interface MCPCardProps { @@ -10,19 +17,34 @@ interface MCPCardProps { onViewDetails: () => void; } -export function MCPCard({ mcp, onInstall, onUninstall, onViewDetails }: MCPCardProps) { +export function MCPCard({ + mcp, + onInstall, + onUninstall, + onViewDetails, +}: MCPCardProps) { const getCategoryColor = (category: string) => { switch (category) { - case "map": return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"; - case "browser": return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"; - case "office": return "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200"; - case "search": return "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"; - case "database": return "bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200"; - case "finance": return "bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200"; - case "code": return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"; - case "chart": return "bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-200"; - case "payment": return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"; - default: return "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200"; + case "map": + return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"; + case "browser": + return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"; + case "office": + return "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200"; + case "search": + return "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"; + case "database": + return "bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200"; + case "finance": + return "bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200"; + case "code": + return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"; + case "chart": + return "bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-200"; + case "payment": + return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"; + default: + return "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200"; } }; @@ -32,39 +54,32 @@ export function MCPCard({ mcp, onInstall, onUninstall, onViewDetails }: MCPCardP return downloads.toString(); }; - const isOfficial = mcp.tags?.includes('official') || false; - const toolCount = mcp.tools?.length || mcp.capabilities?.length || 0; + const isOfficial = mcp.tags?.includes("official") || false; + const toolCount = mcp.tools?.length || 0; return (
{/* Header */}
- {mcp.iconUrl ? ( - {mcp.name} { - e.currentTarget.style.display = 'none'; - e.currentTarget.nextElementSibling?.classList.remove('hidden'); - }} - /> - ) : null} -
+

{mcp.name}

- {isOfficial && ( - - )} + {isOfficial && }
-

by {mcp.author}

+

+ by {mcp.author} +

- + {mcp.category}
@@ -81,10 +96,6 @@ export function MCPCard({ mcp, onInstall, onUninstall, onViewDetails }: MCPCardP {mcp.rating} ({mcp.totalRatings})
-
- - {formatDownloads(mcp.downloads)} -
{toolCount > 0 && (
@@ -122,7 +133,7 @@ export function MCPCard({ mcp, onInstall, onUninstall, onViewDetails }: MCPCardP Details - + {mcp.installed ? (