refactor(api): remove MCP marketplace cache integration
Removed dependencies on MCPCacheRepository and PulseMCPService from MCP routes to simplify installation and listing logic, avoiding reliance on potentially stale cached data and streamlining the codebase. Updated available-for-chat filter to include MCPs without explicit status.
This commit is contained in:
1 parent
4739a13458
commit
f96d4ed696
34 files changed
+260
-3775
No files matched your search
@@ -15,7 +15,7 @@ export async function GET() {
|
||||
const allMcps = MCPRepository.getAllInstalled();
|
||||
|
||||
const availableMcps = allMcps
|
||||
.filter((mcp: any) => mcp.status === "ready")
|
||||
.filter((mcp: any) => !mcp.status || mcp.status === "ready")
|
||||
.map((mcp: any) => {
|
||||
const tools = MCPExecutor.getAvailableTools(mcp.id);
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { MCPCacheRepository } from "@/database/repositories/mcp-cache";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* GET /api/mcp/categories - Obter categorias dinâmicas do cache
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
console.log("📊 Fetching dynamic categories from cache...");
|
||||
|
||||
// Verificar se cache existe e tem dados
|
||||
const cacheExists = MCPCacheRepository.tableExists("mcp_marketplace_cache");
|
||||
if (!cacheExists) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: "Cache not initialized",
|
||||
categories: {
|
||||
primary: [],
|
||||
others: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Obter categorias dinâmicas
|
||||
const categories = MCPCacheRepository.getCategories();
|
||||
const stats = MCPCacheRepository.getCategoryStats();
|
||||
|
||||
// Verificar se há dados no cache
|
||||
if (stats.totalMCPs === 0) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: "No MCPs found in cache",
|
||||
categories: {
|
||||
primary: [],
|
||||
others: [],
|
||||
},
|
||||
stats,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(
|
||||
`📊 Categories extracted: ${categories.primary.length} primary, ${categories.others.length} others`
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
categories,
|
||||
stats,
|
||||
lastSync: MCPCacheRepository.getLastSync(),
|
||||
cacheValid: MCPCacheRepository.isCacheValid(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("Error fetching categories:", error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error.message || "Failed to fetch categories",
|
||||
categories: {
|
||||
primary: [],
|
||||
others: [],
|
||||
},
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ import { MCPRepository } from "@/database/repositories/mcp";
|
||||
import { MCPInstallerService } from "@/lib/services/mcp-installer";
|
||||
import { MCPValidatorService } from "@/lib/services/mcp-validator";
|
||||
import { DeepNLPService } from "@/lib/services/deepnlp";
|
||||
import { PulseMCPService } from "@/lib/services/pulsemcp";
|
||||
import { MCPCacheRepository } from "@/database/repositories/mcp-cache";
|
||||
import type {
|
||||
InstallationConfig,
|
||||
InstallationProgress,
|
||||
@@ -71,17 +69,6 @@ export async function POST(request: Request) {
|
||||
console.log(`DeepNLP config not found, trying cache...`);
|
||||
}
|
||||
|
||||
// Se não encontrou, tentar cache PulseMCP
|
||||
if (!serverConfig) {
|
||||
const cachedMCP = MCPCacheRepository.findById(mcpId);
|
||||
if (cachedMCP) {
|
||||
serverConfig = PulseMCPService.transformToMCPProvider(cachedMCP);
|
||||
packageName = cachedMCP.package_name || mcpId;
|
||||
packageRegistry = cachedMCP.package_registry;
|
||||
repositoryUrl = cachedMCP.source_code_url;
|
||||
}
|
||||
}
|
||||
|
||||
if (!serverConfig) {
|
||||
throw new Error(`No configuration found for MCP ${mcpId}`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { MCPRepository } from "@/database/repositories/mcp";
|
||||
import { MCPCacheRepository } from "@/database/repositories/mcp-cache";
|
||||
// Marketplace cache removido
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -11,48 +11,20 @@ export async function GET() {
|
||||
// Buscar todos os MCPs instalados com detalhes
|
||||
const installedMcps = MCPRepository.listInstalledWithDetails();
|
||||
|
||||
// Enriquecer com dados do cache do marketplace
|
||||
const enrichedMcps = installedMcps.map((mcp) => {
|
||||
// Tentar encontrar no cache usando diferentes critérios
|
||||
const cacheEntry =
|
||||
MCPCacheRepository.findByOwnerRepo(mcp.owner, mcp.repo) ||
|
||||
MCPCacheRepository.findByName(mcp.name) ||
|
||||
MCPCacheRepository.findById(mcp.id);
|
||||
|
||||
if (cacheEntry) {
|
||||
return {
|
||||
...mcp,
|
||||
// Usar dados do cache quando disponíveis
|
||||
author:
|
||||
cacheEntry.publisher_id !== "Unknown"
|
||||
? cacheEntry.publisher_id
|
||||
: mcp.owner,
|
||||
description: cacheEntry.description || `MCP instalado: ${mcp.name}`,
|
||||
repository: cacheEntry.detail_url || cacheEntry.website,
|
||||
homepage: cacheEntry.website,
|
||||
rating: cacheEntry.rating || 0,
|
||||
totalRatings: cacheEntry.review_cnt || 0,
|
||||
category: cacheEntry.category || "other",
|
||||
tags: cacheEntry.content_tag_list
|
||||
? cacheEntry.content_tag_list.split(",")
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...mcp,
|
||||
author: mcp.owner !== "unknown" ? mcp.owner : "Unknown",
|
||||
description: `MCP instalado: ${mcp.name}`,
|
||||
repository: "",
|
||||
homepage: "",
|
||||
rating: 0,
|
||||
totalRatings: 0,
|
||||
category: "other",
|
||||
tags: [],
|
||||
status: mcp.status || "unknown",
|
||||
statusMessage: mcp.status_message || "",
|
||||
};
|
||||
});
|
||||
// Retornar dados de instalados sem enriquecimento de marketplace
|
||||
const enrichedMcps = installedMcps.map((mcp) => ({
|
||||
...mcp,
|
||||
author: mcp.owner !== "unknown" ? mcp.owner : "Unknown",
|
||||
description: `MCP instalado: ${mcp.name}`,
|
||||
repository: "",
|
||||
homepage: "",
|
||||
rating: 0,
|
||||
totalRatings: 0,
|
||||
category: "other",
|
||||
tags: [],
|
||||
status: mcp.status || "unknown",
|
||||
statusMessage: mcp.status_message || "",
|
||||
}));
|
||||
|
||||
console.log(`Found ${enrichedMcps.length} installed MCPs`);
|
||||
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import {
|
||||
MCPProvider,
|
||||
MCPSearchParams,
|
||||
PulseMCPSearchParams,
|
||||
} from "@/lib/types/mcp";
|
||||
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) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const category = searchParams.get("category") || undefined;
|
||||
const search = searchParams.get("search") || undefined;
|
||||
const sort =
|
||||
(searchParams.get("sort") as MCPSearchParams["sort"]) || "rating";
|
||||
const order = (searchParams.get("order") as "asc" | "desc") || "desc";
|
||||
const page = parseInt(searchParams.get("page") || "0");
|
||||
const limit = parseInt(searchParams.get("limit") || "2000"); // Aumentado para 2000
|
||||
|
||||
console.log("MCP List API called with params:", {
|
||||
category,
|
||||
search,
|
||||
sort,
|
||||
order,
|
||||
page,
|
||||
limit,
|
||||
});
|
||||
|
||||
// 1. Verificar se cache do DB é válido
|
||||
const cacheValid = MCPCacheRepository.isCacheValid();
|
||||
|
||||
if (!cacheValid) {
|
||||
// Iniciar sincronização em background se necessário
|
||||
if (!MCPSyncService.isSyncing()) {
|
||||
console.log("Cache invalid, starting background sync...");
|
||||
MCPSyncService.backgroundSync().catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Tentar buscar do banco de dados primeiro
|
||||
const cachedMcps = MCPCacheRepository.search({
|
||||
query: search,
|
||||
category: category !== "all" ? category : undefined,
|
||||
limit,
|
||||
offset: page * limit,
|
||||
sortBy: sort,
|
||||
order: order,
|
||||
});
|
||||
|
||||
if (cachedMcps.length > 0 || cacheValid) {
|
||||
console.log(`Found ${cachedMcps.length} MCPs in cache`);
|
||||
|
||||
// Os dados já estão no formato interno após transformação no saveBatch
|
||||
const mcps = cachedMcps.map((item) => {
|
||||
// Construir ID no formato owner/repo a partir dos campos do cache
|
||||
// Se owner estiver vazio, usar apenas o repo
|
||||
const correctId =
|
||||
item.owner && item.owner.trim() !== ""
|
||||
? `${item.owner}/${item.repo}`
|
||||
: item.repo;
|
||||
|
||||
return {
|
||||
id: correctId, // Usar owner/repo como ID principal ou apenas repo se owner vazio
|
||||
originalId: item.id, // Manter ID original para referência
|
||||
owner: item.owner || "",
|
||||
repo: item.repo,
|
||||
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();
|
||||
const mcpsWithStatus = mcps.map((mcp) => ({
|
||||
...mcp,
|
||||
installed: installedMCPs.includes(mcp.id),
|
||||
}));
|
||||
|
||||
const total = MCPCacheRepository.count({
|
||||
category: category !== "all" ? category : undefined,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
mcps: mcpsWithStatus,
|
||||
total,
|
||||
hasMore: (page + 1) * limit < total,
|
||||
cached: true,
|
||||
lastSync: MCPCacheRepository.getLastSync(),
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Fallback: buscar da API PulseMCP se cache vazio
|
||||
console.log("Cache empty, falling back to PulseMCP API");
|
||||
|
||||
const pulseMCPParams: PulseMCPSearchParams = {
|
||||
query: search,
|
||||
count_per_page: limit,
|
||||
offset: page * limit,
|
||||
};
|
||||
|
||||
console.log("Calling PulseMCP API with params:", pulseMCPParams);
|
||||
|
||||
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: MCPProvider) => ({
|
||||
...mcp,
|
||||
installed: installedMCPs.includes(mcp.id),
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
mcps: mcpsWithStatus,
|
||||
total: data.total_count,
|
||||
hasMore: data.total_count > (page + 1) * limit,
|
||||
cached: false,
|
||||
});
|
||||
} catch (error: any) {
|
||||
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");
|
||||
|
||||
const mockMCPs: MCPProvider[] = [
|
||||
{
|
||||
id: "google-maps/google-maps",
|
||||
name: "Google Maps",
|
||||
author: "Google",
|
||||
description:
|
||||
"Google Maps Location services, directions, and place details",
|
||||
version: "1.0.0",
|
||||
category: "map",
|
||||
tags: ["maps", "location", "directions"],
|
||||
rating: 4.5,
|
||||
totalRatings: 2,
|
||||
repository:
|
||||
"https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps",
|
||||
installed: false,
|
||||
subfield: "MAP",
|
||||
field: "MCP SERVER",
|
||||
config: [],
|
||||
tools: [],
|
||||
},
|
||||
];
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
mcps: mockMCPs,
|
||||
total: mockMCPs.length,
|
||||
hasMore: false,
|
||||
fallback: true,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { DeepNLPService } from "@/lib/services/deepnlp";
|
||||
import { MCPCacheRepository } from "@/database/repositories/mcp-cache";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
@@ -16,67 +15,9 @@ export async function GET(request: Request) {
|
||||
|
||||
console.log(`Fetching server config for: ${mcpId}`);
|
||||
|
||||
// Primeiro, tentar buscar do cache local (PulseMCP)
|
||||
const [owner, repo] = mcpId.includes("/") ? mcpId.split("/") : ["", mcpId];
|
||||
// Marketplace removido: pular cache local
|
||||
|
||||
// Buscar no cache usando ID completo ou apenas repo
|
||||
const cachedMcps = MCPCacheRepository.search({
|
||||
query: repo,
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (cachedMcps.length > 0) {
|
||||
const cachedMCP = cachedMcps[0];
|
||||
|
||||
// Verificar se tem configuração no ext_info
|
||||
if (cachedMCP.ext_info) {
|
||||
try {
|
||||
const extInfo =
|
||||
typeof cachedMCP.ext_info === "string"
|
||||
? JSON.parse(cachedMCP.ext_info)
|
||||
: cachedMCP.ext_info;
|
||||
|
||||
if (
|
||||
extInfo.config &&
|
||||
Array.isArray(extInfo.config) &&
|
||||
extInfo.config.length > 0
|
||||
) {
|
||||
console.log(`Found config in cache for ${mcpId}`);
|
||||
// Converter formato PulseMCP para formato esperado pelo modal
|
||||
const serverConfig = extInfo.config.find(
|
||||
(c: any) => c.type === "server"
|
||||
);
|
||||
const envConfig = extInfo.config.find((c: any) => c.type === "env");
|
||||
|
||||
if (serverConfig) {
|
||||
// Usar o nome do servidor config como chave
|
||||
const serverName =
|
||||
serverConfig.name || extInfo.package_name || cachedMCP.id;
|
||||
|
||||
const formattedConfig = {
|
||||
mcpServers: {
|
||||
[serverName]: {
|
||||
command: serverConfig.command || "npx",
|
||||
args: serverConfig.args || [],
|
||||
env: envConfig?.variables || {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
config: formattedConfig,
|
||||
source: "pulsemcp",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing ext_info:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Se não encontrou no cache, tentar DeepNLP como fallback
|
||||
// Tentar DeepNLP
|
||||
try {
|
||||
const serverConfig = await DeepNLPService.getServerConfig(mcpId);
|
||||
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { MCPSyncService } from "@/lib/services/mcp-sync";
|
||||
import { MCPCacheRepository } from "@/database/repositories/mcp-cache";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* POST /api/mcp/sync - Iniciar sincronização manual
|
||||
*/
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
if (MCPSyncService.isSyncing()) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: "Sync already in progress"
|
||||
}, { status: 409 });
|
||||
}
|
||||
|
||||
console.log("🔄 Starting manual MCP sync...");
|
||||
|
||||
const result = await MCPSyncService.syncAll((progress) => {
|
||||
console.log(`📥 Progress: ${progress.current}/${progress.total} (${progress.page}/${progress.totalPages} pages)`);
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: result.success,
|
||||
totalDownloaded: result.totalDownloaded,
|
||||
errors: result.errors,
|
||||
duration: result.duration
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("Manual sync failed:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error.message || "Failed to sync MCPs" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/mcp/sync - Obter status da sincronização
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const stats = MCPCacheRepository.getStats();
|
||||
const healthCheck = await MCPSyncService.healthCheck();
|
||||
|
||||
return NextResponse.json({
|
||||
isSyncing: MCPSyncService.isSyncing(),
|
||||
lastSync: stats.lastSync,
|
||||
totalCached: stats.totalItems,
|
||||
needsSync: MCPCacheRepository.needsSync(),
|
||||
cacheValid: stats.isValid,
|
||||
categories: stats.categories,
|
||||
healthCheck
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("Error getting sync status:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error.message || "Failed to get sync status" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/mcp/sync - Cancelar sincronização atual
|
||||
*/
|
||||
export async function DELETE(request: Request) {
|
||||
try {
|
||||
if (!MCPSyncService.isSyncing()) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: "No sync in progress"
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
MCPSyncService.cancelSync();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Sync cancelled successfully"
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error("Error cancelling sync:", error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error.message || "Failed to cancel sync" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user