feat: add MCP marketplace and custom scrollbar styles

Add comprehensive CSS styles for the MCP Marketplace interface, including card hover effects, responsive grid layout, modal backdrop blur, category badges, rating stars, install buttons, and loading animations. Also include custom scrollbar styling for the model selector to enhance user experience and visual consistency.
This commit is contained in:
Evandro Fonseca Junior committed 2025-10-28 19:40:45 -03:00
1 parent d5dd3a9f09
commit ce010d39ee
31 files changed
+4127 -45

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
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 }
);
}
}
+62
View File
@@ -0,0 +1,62 @@
import { NextResponse } from "next/server";
import { MCPInstallationStatus } from "@/lib/types/mcp";
import { DeepNLPService } from "@/lib/services/deepnlp";
import { MCPRepository } from "@/database/repositories/mcp";
export async function POST(request: Request) {
try {
const { mcpId } = await request.json();
if (!mcpId) {
return NextResponse.json(
{ success: false, error: "MCP ID is required" },
{ status: 400 }
);
}
console.log(`Installing MCP: ${mcpId}`);
// Verificar se já está instalado
if (MCPRepository.isInstalled(mcpId)) {
return NextResponse.json({
success: false,
error: `MCP ${mcpId} is already installed`,
}, { status: 400 });
}
// Obter configuração completa do servidor
console.log(`Fetching server config for ${mcpId}`);
const serverConfig = await DeepNLPService.getServerConfig(mcpId);
if (!serverConfig.items || serverConfig.items.length === 0) {
throw new Error(`No configuration found for MCP ${mcpId}`);
}
// Obter ferramentas disponíveis
console.log(`Fetching server tools for ${mcpId}`);
const tools = await DeepNLPService.getServerTools(mcpId);
// Salvar configuração localmente
console.log(`Saving MCP ${mcpId} to local database`);
MCPRepository.saveInstallation(mcpId, serverConfig.items[0], tools);
const result: MCPInstallationStatus = {
success: true,
message: `MCP ${mcpId} installed successfully`,
mcpId,
};
return NextResponse.json({
success: true,
installation: result,
config: serverConfig.items[0],
tools: tools,
});
} catch (error: any) {
console.error("Error installing MCP:", error);
return NextResponse.json(
{ success: false, error: error.message || "Failed to install MCP" },
{ status: 500 }
);
}
}
+158
View File
@@ -0,0 +1,158 @@
import { NextResponse } from "next/server";
import {
MCPProvider,
MCPSearchParams,
DeepNLPSearchParams,
} from "@/lib/types/mcp";
import { DeepNLPService } from "@/lib/services/deepnlp";
import { MCPRepository } from "@/database/repositories/mcp";
import { MCPCacheRepository } from "@/database/repositories/mcp-cache";
import { MCPSyncService } from "@/lib/services/mcp-sync";
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`);
// Transformar para formato interno
const mcps = cachedMcps.map((item) =>
DeepNLPService.transformToMCPProvider(item)
);
// 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 se cache vazio
console.log("Cache empty, falling back to DeepNLP API");
const deepnlpCategory =
category && category !== "all"
? DeepNLPService.getCategoryMapping(category)
: undefined;
const deepnlpParams: DeepNLPSearchParams = {
query: search,
category: deepnlpCategory,
page_id: page,
count_per_page: limit,
mode: "list",
};
console.log("Calling DeepNLP API with params:", deepnlpParams);
const response = await DeepNLPService.searchMCPs(deepnlpParams);
const mcps = response.items.map((item) =>
DeepNLPService.transformToMCPProvider(item)
);
// Adicionar status de instalação
const installedMCPs = MCPRepository.listInstalled();
const mcpsWithStatus = mcps.map((mcp) => ({
...mcp,
installed: installedMCPs.includes(mcp.id),
}));
return NextResponse.json({
success: true,
mcps: mcpsWithStatus,
total: response.total_hits,
hasMore: response.total_hits > (page + 1) * limit,
cached: false,
});
} catch (error: any) {
console.error("Error fetching MCPs from DeepNLP:", 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,
downloads: 20,
capabilities: ["geocoding", "directions", "places"],
repository:
"https://github.com/modelcontextprotocol/servers/tree/main/src/google-maps",
installed: false,
subfield: "MAP",
field: "MCP SERVER",
},
];
return NextResponse.json({
success: true,
mcps: mockMCPs,
total: mockMCPs.length,
hasMore: false,
fallback: true,
error: error.message,
});
}
}
+91
View File
@@ -0,0 +1,91 @@
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 }
);
}
}
+46
View File
@@ -0,0 +1,46 @@
import { NextResponse } from "next/server";
import { MCPInstallationStatus } from "@/lib/types/mcp";
import { MCPRepository } from "@/database/repositories/mcp";
export async function DELETE(request: Request) {
try {
const { mcpId } = await request.json();
if (!mcpId) {
return NextResponse.json(
{ success: false, error: "MCP ID is required" },
{ status: 400 }
);
}
console.log(`Uninstalling MCP: ${mcpId}`);
// Verificar se está instalado
if (!MCPRepository.isInstalled(mcpId)) {
return NextResponse.json({
success: false,
error: `MCP ${mcpId} is not installed`,
}, { status: 400 });
}
// Remover do banco local
MCPRepository.removeInstallation(mcpId);
const result: MCPInstallationStatus = {
success: true,
message: `MCP ${mcpId} uninstalled successfully`,
mcpId,
};
return NextResponse.json({
success: true,
installation: result,
});
} catch (error: any) {
console.error("Error uninstalling MCP:", error);
return NextResponse.json(
{ success: false, error: error.message || "Failed to uninstall MCP" },
{ status: 500 }
);
}
}