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:
Evandro Fonseca Junior committed 2025-10-28 20:19:00 -03:00
1 parent ce010d39ee
commit 1042f2136b
15 files changed
+1237 -398

No files matched your search

+47 -36
View File
@@ -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 (
<div className="bg-[var(--surface)] border border-[var(--border)] rounded-lg p-4 hover:shadow-md transition-all duration-200 hover:border-[var(--primary)]/50 mcp-card">
{/* Header */}
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
{mcp.iconUrl ? (
<img
src={mcp.iconUrl}
alt={mcp.name}
className="h-8 w-8 rounded-md object-cover"
onError={(e) => {
e.currentTarget.style.display = 'none';
e.currentTarget.nextElementSibling?.classList.remove('hidden');
}}
/>
) : null}
<div className={`p-2 bg-[var(--primary)]/10 rounded-md ${mcp.iconUrl ? 'hidden' : ''}`}>
<div className="p-2 bg-[var(--primary)]/10 rounded-md">
<Package className="h-4 w-4 text-[var(--primary)]" />
</div>
<div>
<div className="flex items-center gap-2">
<h3 className="font-semibold text-sm">{mcp.name}</h3>
{isOfficial && (
<CheckCircle className="h-3 w-3 text-green-500" />
)}
{isOfficial && <CheckCircle className="h-3 w-3 text-green-500" />}
</div>
<p className="text-xs text-[var(--muted-foreground)]">by {mcp.author}</p>
<p className="text-xs text-[var(--muted-foreground)]">
by {mcp.author}
</p>
</div>
</div>
<span className={`px-2 py-1 rounded-full text-xs font-medium mcp-category-badge ${getCategoryColor(mcp.category)}`}>
<span
className={`px-2 py-1 rounded-full text-xs font-medium mcp-category-badge ${getCategoryColor(
mcp.category
)}`}
>
{mcp.category}
</span>
</div>
@@ -81,10 +96,6 @@ export function MCPCard({ mcp, onInstall, onUninstall, onViewDetails }: MCPCardP
<span>{mcp.rating}</span>
<span>({mcp.totalRatings})</span>
</div>
<div className="flex items-center gap-1">
<Download className="h-3 w-3" />
<span>{formatDownloads(mcp.downloads)}</span>
</div>
{toolCount > 0 && (
<div className="flex items-center gap-1">
<Zap className="h-3 w-3" />
@@ -122,7 +133,7 @@ export function MCPCard({ mcp, onInstall, onUninstall, onViewDetails }: MCPCardP
<Eye className="h-4 w-4" />
Details
</button>
{mcp.installed ? (
<button
onClick={onUninstall}
+113 -72
View File
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { X, Package, Star, Download, ExternalLink, CheckCircle } from "lucide-react";
import { X, Package, Star, ExternalLink, CheckCircle } from "lucide-react";
import { MCPProvider } from "@/lib/types/mcp";
import { MCPRatingStars } from "./MCPRatingStars";
@@ -12,36 +12,47 @@ interface MCPDetailModalProps {
onInstall: () => void;
}
export function MCPDetailModal({ mcp, isOpen, onClose, onInstall }: MCPDetailModalProps) {
const [activeTab, setActiveTab] = useState<"about" | "capabilities" | "screenshots" | "configuration">("about");
export function MCPDetailModal({
mcp,
isOpen,
onClose,
onInstall,
}: MCPDetailModalProps) {
const [activeTab, setActiveTab] = useState<
"about" | "capabilities" | "screenshots" | "configuration"
>("about");
if (!isOpen) return null;
const formatDownloads = (downloads: number) => {
if (downloads >= 1000000) return `${(downloads / 1000000).toFixed(1)}M`;
if (downloads >= 1000) return `${(downloads / 1000).toFixed(1)}K`;
return downloads.toString();
};
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";
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="fixed inset-0 bg-black/50" onClick={onClose} />
<div className="relative w-full max-w-4xl h-[90vh] bg-[var(--background)] border border-[var(--border)] rounded-lg shadow-xl mcp-detail-modal">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-[var(--border)]">
@@ -53,7 +64,11 @@ export function MCPDetailModal({ mcp, isOpen, onClose, onInstall }: MCPDetailMod
<h2 className="text-2xl font-bold">{mcp.name}</h2>
<p className="text-[var(--muted-foreground)]">by {mcp.author}</p>
</div>
<span className={`px-3 py-1 rounded-full text-sm font-medium mcp-category-badge ${getCategoryColor(mcp.category)}`}>
<span
className={`px-3 py-1 rounded-full text-sm font-medium mcp-category-badge ${getCategoryColor(
mcp.category
)}`}
>
{mcp.category}
</span>
{mcp.installed && (
@@ -80,11 +95,9 @@ export function MCPDetailModal({ mcp, isOpen, onClose, onInstall }: MCPDetailMod
({mcp.totalRatings} reviews)
</span>
</div>
<div className="flex items-center gap-2">
<Download className="h-4 w-4 text-[var(--muted-foreground)]" />
<span className="text-sm">{formatDownloads(mcp.downloads)} downloads</span>
</div>
<span className="text-sm text-[var(--muted-foreground)]">Version {mcp.version}</span>
<span className="text-sm text-[var(--muted-foreground)]">
Version {mcp.version}
</span>
</div>
</div>
@@ -171,14 +184,16 @@ export function MCPDetailModal({ mcp, isOpen, onClose, onInstall }: MCPDetailMod
<div>
<h3 className="text-lg font-semibold mb-3">Capabilities</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{(mcp.tools || mcp.capabilities || []).map((capability, index) => (
{(mcp.tools || []).map((capability, index) => (
<div
key={index}
className="flex items-center gap-2 p-3 bg-[var(--surface)] rounded-md border border-[var(--border)]"
>
<CheckCircle className="h-4 w-4 text-green-500" />
<span className="text-sm">
{typeof capability === 'string' ? capability : (capability as any).name || String(capability)}
{typeof capability === "string"
? capability
: (capability as any).name || String(capability)}
</span>
</div>
))}
@@ -189,35 +204,54 @@ export function MCPDetailModal({ mcp, isOpen, onClose, onInstall }: MCPDetailMod
{activeTab === "configuration" && (
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-3">Installation Configuration</h3>
<h3 className="text-lg font-semibold mb-3">
Installation Configuration
</h3>
{mcp.config && mcp.config.length > 0 ? (
<div className="space-y-4">
{mcp.config.map((configItem, index) => (
<div key={index} className="bg-[var(--surface)] border border-[var(--border)] rounded-lg p-4">
<h4 className="font-medium mb-3">Configuration {index + 1}</h4>
<div
key={index}
className="bg-[var(--surface)] border border-[var(--border)] rounded-lg p-4"
>
<h4 className="font-medium mb-3">
Configuration {index + 1}
</h4>
<div className="space-y-3">
{configItem.mcpServers && (
<div>
<h5 className="text-sm font-medium text-[var(--muted-foreground)] mb-2">Command</h5>
<h5 className="text-sm font-medium text-[var(--muted-foreground)] mb-2">
Command
</h5>
<code className="block bg-[var(--background)] p-2 rounded text-sm">
{configItem.mcpServers.command} {configItem.mcpServers.arguments?.join(' ')}
{configItem.mcpServers.command}{" "}
{configItem.mcpServers.arguments?.join(" ")}
</code>
</div>
)}
{configItem.mcpServers?.env && Object.keys(configItem.mcpServers.env).length > 0 && (
<div>
<h5 className="text-sm font-medium text-[var(--muted-foreground)] mb-2">Environment Variables</h5>
<div className="space-y-2">
{Object.entries(configItem.mcpServers.env).map(([key, value]) => (
<div key={key} className="flex items-center gap-2">
<code className="bg-[var(--background)] px-2 py-1 rounded text-sm font-mono">
{key}={String(value)}
</code>
</div>
))}
{configItem.mcpServers?.env &&
Object.keys(configItem.mcpServers.env).length >
0 && (
<div>
<h5 className="text-sm font-medium text-[var(--muted-foreground)] mb-2">
Environment Variables
</h5>
<div className="space-y-2">
{Object.entries(
configItem.mcpServers.env
).map(([key, value]) => (
<div
key={key}
className="flex items-center gap-2"
>
<code className="bg-[var(--background)] px-2 py-1 rounded text-sm font-mono">
{key}={String(value)}
</code>
</div>
))}
</div>
</div>
</div>
)}
)}
</div>
</div>
))}
@@ -232,25 +266,45 @@ export function MCPDetailModal({ mcp, isOpen, onClose, onInstall }: MCPDetailMod
{mcp.tools && mcp.tools.length > 0 && (
<div>
<h3 className="text-lg font-semibold mb-3">Available Tools</h3>
<h3 className="text-lg font-semibold mb-3">
Available Tools
</h3>
<div className="space-y-3">
{mcp.tools.map((tool, index) => (
<div key={index} className="bg-[var(--surface)] border border-[var(--border)] rounded-lg p-4">
<div
key={index}
className="bg-[var(--surface)] border border-[var(--border)] rounded-lg p-4"
>
<h4 className="font-medium mb-2">{tool.name}</h4>
<p className="text-sm text-[var(--muted-foreground)] mb-3">{tool.description}</p>
<p className="text-sm text-[var(--muted-foreground)] mb-3">
{tool.description}
</p>
{tool.input_schema && (
<div>
<h5 className="text-sm font-medium text-[var(--muted-foreground)] mb-2">Parameters</h5>
<h5 className="text-sm font-medium text-[var(--muted-foreground)] mb-2">
Parameters
</h5>
<div className="space-y-2">
{Object.entries(tool.input_schema.properties || {}).map(([param, schema]) => (
<div key={param} className="flex items-center gap-2">
{Object.entries(
tool.input_schema.properties || {}
).map(([param, schema]) => (
<div
key={param}
className="flex items-center gap-2"
>
<code className="bg-[var(--background)] px-2 py-1 rounded text-sm font-mono">
{param}
</code>
<span className="text-xs text-[var(--muted-foreground)]">
{typeof schema === 'object' && schema ? (schema as any).type || 'any' : 'any'}
{tool.input_schema.required?.includes(param) && (
<span className="text-red-500 ml-1">*</span>
{typeof schema === "object" && schema
? (schema as any).type || "any"
: "any"}
{tool.input_schema.required?.includes(
param
) && (
<span className="text-red-500 ml-1">
*
</span>
)}
</span>
</div>
@@ -269,23 +323,10 @@ export function MCPDetailModal({ mcp, isOpen, onClose, onInstall }: MCPDetailMod
{activeTab === "screenshots" && (
<div>
<h3 className="text-lg font-semibold mb-3">Screenshots</h3>
{mcp.screenshotUrls && mcp.screenshotUrls.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{mcp.screenshotUrls.map((url, index) => (
<img
key={index}
src={url}
alt={`Screenshot ${index + 1}`}
className="w-full h-48 object-cover rounded-md border border-[var(--border)]"
/>
))}
</div>
) : (
<div className="text-center py-12 text-[var(--muted-foreground)]">
<Package className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No screenshots available</p>
</div>
)}
<div className="text-center py-12 text-[var(--muted-foreground)]">
<Package className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No screenshots available</p>
</div>
</div>
)}
</div>
+2 -3
View File
@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect, useCallback, useMemo } from "react";
import { X, Search, Filter, Package, Star, Download, Eye } from "lucide-react";
import { X, Search, Filter, Package, Star, Eye } from "lucide-react";
import { MCPProvider } from "@/lib/types/mcp";
import { MCPCard } from "./MCPCard";
import { MCPCategoryFilter } from "./MCPCategoryFilter";
@@ -22,7 +22,7 @@ export function MCPMarketplaceModal({
const [searchTerm, setSearchTerm] = useState("");
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
const [sortBy, setSortBy] = useState<
"rating" | "downloads" | "name" | "recent" | "total_ratings" | "updated_at"
"rating" | "name" | "recent" | "total_ratings" | "updated_at"
>("rating");
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
const [selectedMCP, setSelectedMCP] = useState<MCPProvider | null>(null);
@@ -283,7 +283,6 @@ export function MCPMarketplaceModal({
className="px-3 py-2 bg-[var(--surface)] border border-[var(--border)] rounded-md text-sm"
>
<option value="rating">Por Avaliação</option>
<option value="downloads">Por Downloads</option>
<option value="name">Por Nome</option>
<option value="recent">Mais Recentes</option>
<option value="total_ratings">Por Nº de Reviews</option>