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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user