From bc1fbfa190cc5de0db7a6208f00b428af312627b Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 3 Nov 2025 20:23:53 +0800 Subject: [PATCH] feat: completely remove the fucking mcp market components and refs --- .../plugins/{mcp => mcp-server}/MCPCardVO.ts | 2 +- .../plugins/mcp-server/MCPServerComponent.tsx | 11 +- .../mcp-card/MCPCardComponent.tsx | 2 +- .../MCPServerCardComponent.tsx | 87 ---- .../mcp-server-card/MCPServerCardVO.ts | 29 -- web/src/app/home/plugins/mcp/MCPComponent.tsx | 324 -------------- .../app/home/plugins/mcp/mcp-form/MCPForm.tsx | 408 ------------------ web/src/app/home/plugins/page.tsx | 53 +-- web/src/app/infra/entities/api/index.ts | 24 -- web/src/app/infra/http/BackendClient.ts | 16 - 10 files changed, 26 insertions(+), 930 deletions(-) rename web/src/app/home/plugins/{mcp => mcp-server}/MCPCardVO.ts (99%) rename web/src/app/home/plugins/{mcp => mcp-server}/mcp-card/MCPCardComponent.tsx (99%) delete mode 100644 web/src/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardComponent.tsx delete mode 100644 web/src/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardVO.ts delete mode 100644 web/src/app/home/plugins/mcp/MCPComponent.tsx delete mode 100644 web/src/app/home/plugins/mcp/mcp-form/MCPForm.tsx diff --git a/web/src/app/home/plugins/mcp/MCPCardVO.ts b/web/src/app/home/plugins/mcp-server/MCPCardVO.ts similarity index 99% rename from web/src/app/home/plugins/mcp/MCPCardVO.ts rename to web/src/app/home/plugins/mcp-server/MCPCardVO.ts index 43982a58..48c9aa62 100644 --- a/web/src/app/home/plugins/mcp/MCPCardVO.ts +++ b/web/src/app/home/plugins/mcp-server/MCPCardVO.ts @@ -13,7 +13,7 @@ export class MCPCardVO { this.name = data.name; this.mode = data.mode; this.enable = data.enable; - + this.status = (data.status as string) === 'enabled' ? 'connected' : data.status; this.tools = Array.isArray(data.tools) diff --git a/web/src/app/home/plugins/mcp-server/MCPServerComponent.tsx b/web/src/app/home/plugins/mcp-server/MCPServerComponent.tsx index 1647b3cf..68fad521 100644 --- a/web/src/app/home/plugins/mcp-server/MCPServerComponent.tsx +++ b/web/src/app/home/plugins/mcp-server/MCPServerComponent.tsx @@ -2,13 +2,13 @@ import { useEffect, useState } from 'react'; import styles from '@/app/home/plugins/plugins.module.css'; -import MCPCardComponent from '@/app/home/plugins/mcp/mcp-card/MCPCardComponent'; -import { MCPCardVO } from '@/app/home/plugins/mcp/MCPCardVO'; +import MCPCardComponent from '@/app/home/plugins/mcp-server/mcp-card/MCPCardComponent'; +import { MCPCardVO } from '@/app/home/plugins/mcp-server/MCPCardVO'; import { useTranslation } from 'react-i18next'; import { httpClient } from '@/app/infra/http/HttpClient'; -export default function MCPMarketComponent({ +export default function MCPComponent({ onEditServer, toolsCountCache = {}, }: { @@ -20,7 +20,6 @@ export default function MCPMarketComponent({ const [installedServers, setInstalledServers] = useState([]); const [loading, setLoading] = useState(false); - useEffect(() => { initData(); }, []); @@ -40,7 +39,7 @@ export default function MCPMarketComponent({ .then((resp) => { const servers = resp.servers.map((server) => { const vo = new MCPCardVO(server); - + if (toolsCountCache[server.name] !== undefined) { vo.tools = toolsCountCache[server.name]; } @@ -55,8 +54,6 @@ export default function MCPMarketComponent({ }); } - - return (
{/* 已安装的服务器列表 */} diff --git a/web/src/app/home/plugins/mcp/mcp-card/MCPCardComponent.tsx b/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx similarity index 99% rename from web/src/app/home/plugins/mcp/mcp-card/MCPCardComponent.tsx rename to web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx index 2cffe435..87832ce1 100644 --- a/web/src/app/home/plugins/mcp/mcp-card/MCPCardComponent.tsx +++ b/web/src/app/home/plugins/mcp-server/mcp-card/MCPCardComponent.tsx @@ -1,4 +1,4 @@ -import { MCPCardVO } from '@/app/home/plugins/mcp/MCPCardVO'; +import { MCPCardVO } from '@/app/home/plugins/mcp-server/MCPCardVO'; import { useState, useEffect } from 'react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { Badge } from '@/components/ui/badge'; diff --git a/web/src/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardComponent.tsx b/web/src/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardComponent.tsx deleted file mode 100644 index c28b5f4b..00000000 --- a/web/src/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardComponent.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { MCPMarketCardVO } from '@/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardVO'; -import { Button } from '@/components/ui/button'; -import { useTranslation } from 'react-i18next'; - -export default function MCPMarketCardComponent({ - cardVO, - installServer, -}: { - cardVO: MCPMarketCardVO; - installServer: (serverURL: string) => void; -}) { - const { t } = useTranslation(); - - function handleInstallClick(serverURL: string) { - installServer(serverURL); - } - - return ( -
-
- - - - -
-
-
-
- {cardVO.author} /{' '} -
-
-
{cardVO.name}
-
-
- -
- {cardVO.description} -
-
- -
-
- - - -
- {t('mcp.starCount', { count: cardVO.starCount })} -
-
- -
- window.open(cardVO.githubURL, '_blank')} - > - - - -
-
-
-
-
- ); -} diff --git a/web/src/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardVO.ts b/web/src/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardVO.ts deleted file mode 100644 index 433171c6..00000000 --- a/web/src/app/home/plugins/mcp-server/mcp-server-card/MCPServerCardVO.ts +++ /dev/null @@ -1,29 +0,0 @@ -export interface IMCPMarketCardVO { - serverId: string; - author: string; - name: string; - description: string; - starCount: number; - githubURL: string; - version: string; -} - -export class MCPMarketCardVO implements IMCPMarketCardVO { - serverId: string; - description: string; - name: string; - author: string; - githubURL: string; - starCount: number; - version: string; - - constructor(prop: IMCPMarketCardVO) { - this.description = prop.description; - this.name = prop.name; - this.author = prop.author; - this.githubURL = prop.githubURL; - this.starCount = prop.starCount; - this.serverId = prop.serverId; - this.version = prop.version; - } -} diff --git a/web/src/app/home/plugins/mcp/MCPComponent.tsx b/web/src/app/home/plugins/mcp/MCPComponent.tsx deleted file mode 100644 index 00cf6619..00000000 --- a/web/src/app/home/plugins/mcp/MCPComponent.tsx +++ /dev/null @@ -1,324 +0,0 @@ -'use client'; - -import { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; -import { MCPCardVO } from '@/app/home/plugins/mcp/MCPCardVO'; -import MCPCardComponent from '@/app/home/plugins/mcp/mcp-card/MCPCardComponent'; -import MCPForm from '@/app/home/plugins/mcp/mcp-form/MCPForm'; -import styles from '@/app/home/plugins/plugins.module.css'; -import { httpClient } from '@/app/infra/http/HttpClient'; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog'; -import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; - -export interface MCPComponentRef { - refreshServerList: () => void; - createServer: () => void; -} - -const MCPComponent = forwardRef((_props, ref) => { - const { t } = useTranslation(); - const [serverList, setServerList] = useState([]); - const [modalOpen, setModalOpen] = useState(false); - const [selectedServer, setSelectedServer] = useState(null); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [serverToDelete, setServerToDelete] = useState(null); - const [deleting, setDeleting] = useState(false); - const [autoTestTriggered, setAutoTestTriggered] = useState(false); - const [testingServers, setTestingServers] = useState>(new Set()); - - useEffect(() => { - initData(); - }, []); - - function initData() { - getServerList(true); - } - - function getServerList(shouldAutoTest: boolean = false) { - console.log('[MCP] Fetching server list...'); - httpClient - .getMCPServers() - .then((value) => { - const servers = value.servers.map((server) => new MCPCardVO(server)); - console.log( - '[MCP] Server list updated:', - servers.map((s) => ({ - name: s.name, - status: s.status, - tools: s.tools, - })), - ); - setServerList(servers); - - // 自动测试:仅在初始加载且还未触发过自动测试时执行 - if (shouldAutoTest && !autoTestTriggered && servers.length > 0) { - setAutoTestTriggered(true); - testAllServers(servers); - } - }) - .catch((error) => { - toast.error(t('mcp.getServerListError') + error.message); - }); - } - - async function testAllServers(servers: MCPCardVO[]) { - // 为每个服务器启动测试 - console.log('[MCP] Starting tests for all servers:', servers.length); - const testPromises = servers.map((server) => testServer(server.name)); - - // 等待所有测试完成 - try { - await Promise.all(testPromises); - console.log('[MCP] All tests completed, refreshing server list...'); - // 所有测试完成后,延迟1秒再刷新,确保后端状态已更新 - setTimeout(() => { - console.log('[MCP] Refreshing server list after tests'); - getServerList(false); - }, 1000); - } catch (err) { - console.error('[MCP] Some tests failed:', err); - // 即使有失败,也要刷新列表 - setTimeout(() => { - console.log('[MCP] Refreshing server list after test failures'); - getServerList(false); - }, 1000); - } - } - - function testServer(serverName: string): Promise { - return new Promise((resolve, reject) => { - // 标记为正在测试 - console.log(`[MCP] Starting test for server: ${serverName}`); - setTestingServers((prev) => new Set(prev).add(serverName)); - - httpClient - .testMCPServer(serverName) - .then((resp) => { - const taskId = resp.task_id; - console.log( - `[MCP] Test task created for ${serverName}, task_id: ${taskId}`, - ); - // 监控任务状态 - const interval = setInterval(() => { - httpClient - .getAsyncTask(taskId) - .then((taskResp) => { - if (taskResp.runtime.done) { - clearInterval(interval); - // 标记测试完成 - setTestingServers((prev) => { - const newSet = new Set(prev); - newSet.delete(serverName); - return newSet; - }); - - if (taskResp.runtime.exception) { - console.error( - `[MCP] Test failed for ${serverName}:`, - taskResp.runtime.exception, - ); - reject(new Error(taskResp.runtime.exception)); - } else { - console.log( - `[MCP] Test completed successfully for ${serverName}`, - ); - resolve(); - } - } - }) - .catch((err) => { - clearInterval(interval); - setTestingServers((prev) => { - const newSet = new Set(prev); - newSet.delete(serverName); - return newSet; - }); - console.error( - `[MCP] Error monitoring task for ${serverName}:`, - err, - ); - reject(err); - }); - }, 1000); - }) - .catch((err) => { - console.error(`[MCP] Failed to start test for ${serverName}:`, err); - setTestingServers((prev) => { - const newSet = new Set(prev); - newSet.delete(serverName); - return newSet; - }); - reject(err); - }); - }); - } - - useImperativeHandle(ref, () => ({ - refreshServerList: () => getServerList(false), - createServer: () => { - setSelectedServer(null); - setModalOpen(true); - }, - })); - - function handleServerClick(server: MCPCardVO) { - setSelectedServer(server); - setModalOpen(true); - } - - function handleDeleteClick(server: MCPCardVO, e: React.MouseEvent) { - e.stopPropagation(); - setServerToDelete(server); - setDeleteDialogOpen(true); - } - - async function confirmDelete() { - if (!serverToDelete) return; - - setDeleting(true); - try { - const response = await httpClient.deleteMCPServer(serverToDelete.name); - const taskId = response.task_id; - - // 监控任务状态 - const interval = setInterval(() => { - httpClient.getAsyncTask(taskId).then((taskResp) => { - if (taskResp.runtime.done) { - clearInterval(interval); - setDeleting(false); - setDeleteDialogOpen(false); - - if (taskResp.runtime.exception) { - toast.error(t('mcp.deleteError') + taskResp.runtime.exception); - } else { - toast.success(t('mcp.deleteSuccess')); - getServerList(false); - } - } - }); - }, 1000); - } catch (error: unknown) { - setDeleting(false); - const errorMessage = - error instanceof Error ? error.message : String(error); - toast.error(t('mcp.deleteError') + errorMessage); - } - } - - return ( - <> - {serverList.length === 0 ? ( -
- - - -
{t('mcp.noServerInstalled')}
-
- ) : ( -
- {serverList.map((vo) => { - return ( -
- handleServerClick(vo)} - onRefresh={() => getServerList(false)} - /> - - {/* 删除按钮 */} - -
- ); - })} -
- )} - - - - - - {selectedServer ? t('mcp.editServer') : t('mcp.createServer')} - - -
- { - setModalOpen(false); - getServerList(false); - }} - onFormCancel={() => { - setModalOpen(false); - }} - /> -
-
-
- - - - - - {t('mcp.deleteServer')} - - {t('mcp.confirmDeleteServer', { name: serverToDelete?.name })} - - - - - {t('common.cancel')} - - - {deleting ? t('plugins.deleting') : t('common.delete')} - - - - - - ); -}); - -export default MCPComponent; diff --git a/web/src/app/home/plugins/mcp/mcp-form/MCPForm.tsx b/web/src/app/home/plugins/mcp/mcp-form/MCPForm.tsx deleted file mode 100644 index 2e990235..00000000 --- a/web/src/app/home/plugins/mcp/mcp-form/MCPForm.tsx +++ /dev/null @@ -1,408 +0,0 @@ -'use client'; - -import { useState, useEffect } from 'react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Switch } from '@/components/ui/switch'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { httpClient } from '@/app/infra/http/HttpClient'; -import { MCPServerConfig } from '@/app/infra/entities/api'; -import { toast } from 'sonner'; -import { useTranslation } from 'react-i18next'; -import { PlusIcon, TrashIcon } from 'lucide-react'; - -interface MCPFormProps { - serverName?: string; - isEdit?: boolean; - onFormSubmit: () => void; - onFormCancel: () => void; -} - -export default function MCPForm({ - serverName, - isEdit = false, - onFormSubmit, - onFormCancel, -}: MCPFormProps) { - const { t } = useTranslation(); - const [loading, setLoading] = useState(false); - const [formData, setFormData] = useState({ - name: '', - mode: 'stdio', - enable: true, - command: '', - args: [], - env: {}, - url: '', - headers: {}, - timeout: 10, - }); - - useEffect(() => { - if (isEdit && serverName) { - loadServerConfig(); - } - }, [isEdit, serverName]); - - async function loadServerConfig() { - try { - const response = await httpClient.getMCPServer(serverName!); - setFormData(response.server.config); - } catch (error: unknown) { - const errorMessage = - error instanceof Error ? error.message : String(error); - toast.error(t('mcp.getServerListError') + errorMessage); - } - } - - function handleInputChange(field: keyof MCPServerConfig, value: unknown) { - setFormData((prev) => ({ - ...prev, - [field]: value, - })); - } - - function addArrayItem(field: 'args', value: string = '') { - const currentArray = formData[field] as string[]; - handleInputChange(field, [...currentArray, value]); - } - - function updateArrayItem(field: 'args', index: number, value: string) { - const currentArray = formData[field] as string[]; - const newArray = [...currentArray]; - newArray[index] = value; - handleInputChange(field, newArray); - } - - function removeArrayItem(field: 'args', index: number) { - const currentArray = formData[field] as string[]; - const newArray = currentArray.filter((_, i) => i !== index); - handleInputChange(field, newArray); - } - - function addObjectItem( - field: 'env' | 'headers', - key: string = '', - value: string = '', - ) { - const currentObj = formData[field] as Record; - handleInputChange(field, { - ...currentObj, - [key]: value, - }); - } - - function updateObjectItem( - field: 'env' | 'headers', - oldKey: string, - newKey: string, - value: string, - ) { - const currentObj = formData[field] as Record; - const newObj = { ...currentObj }; - if (oldKey !== newKey) { - delete newObj[oldKey]; - } - newObj[newKey] = value; - handleInputChange(field, newObj); - } - - function removeObjectItem(field: 'env' | 'headers', key: string) { - const currentObj = formData[field] as Record; - const newObj = { ...currentObj }; - delete newObj[key]; - handleInputChange(field, newObj); - } - - async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); - - // 验证表单 - if (!formData.name.trim()) { - toast.error(t('mcp.serverNameRequired')); - return; - } - - if (formData.mode === 'stdio' && !formData.command?.trim()) { - toast.error(t('mcp.commandRequired')); - return; - } - - if (formData.mode === 'sse' && !formData.url?.trim()) { - toast.error(t('mcp.urlRequired')); - return; - } - - setLoading(true); - - try { - let taskId: number; - - if (isEdit) { - const response = await httpClient.updateMCPServer( - serverName!, - formData, - ); - taskId = response.task_id; - } else { - const response = await httpClient.createMCPServer(formData); - taskId = response.task_id; - } - - // 监控任务状态 - const interval = setInterval(() => { - httpClient.getAsyncTask(taskId).then((taskResp) => { - if (taskResp.runtime.done) { - clearInterval(interval); - setLoading(false); - - if (taskResp.runtime.exception) { - toast.error( - (isEdit ? t('mcp.saveError') : t('mcp.createError')) + - taskResp.runtime.exception, - ); - } else { - toast.success( - isEdit ? t('mcp.saveSuccess') : t('mcp.createSuccess'), - ); - onFormSubmit(); - } - } - }); - }, 1000); - } catch (error: unknown) { - setLoading(false); - const errorMessage = - error instanceof Error ? error.message : String(error); - toast.error( - (isEdit ? t('mcp.saveError') : t('mcp.createError')) + errorMessage, - ); - } - } - - return ( -
- {/* 基础配置 */} -
-
- - handleInputChange('name', e.target.value)} - disabled={isEdit} - placeholder={t('mcp.serverName')} - /> -
- -
- -
- - handleInputChange('enable', checked) - } - /> -
-
- -
- - - handleInputChange('mode', value as 'stdio' | 'sse') - } - className="mt-2" - > - - {t('mcp.stdio')} - {t('mcp.sse')} - - - -
- - handleInputChange('command', e.target.value)} - placeholder="python -m your_mcp_server" - /> -
- -
- -
- {(formData.args || []).map((arg, index) => ( -
- - updateArrayItem('args', index, e.target.value) - } - placeholder="参数" - /> - -
- ))} - -
-
- -
- -
- {Object.entries(formData.env || {}).map(([key, value]) => ( -
- - updateObjectItem('env', key, e.target.value, value) - } - placeholder={t('mcp.keyName')} - className="flex-1" - /> - - updateObjectItem('env', key, key, e.target.value) - } - placeholder={t('mcp.value')} - className="flex-1" - /> - -
- ))} - -
-
-
- - -
- - handleInputChange('url', e.target.value)} - placeholder="http://localhost:3000/sse" - /> -
- -
- - - handleInputChange('timeout', parseInt(e.target.value) || 10) - } - placeholder="10" - /> -
- -
- -
- {Object.entries(formData.headers || {}).map( - ([key, value]) => ( -
- - updateObjectItem( - 'headers', - key, - e.target.value, - value, - ) - } - placeholder={t('mcp.keyName')} - className="flex-1" - /> - - updateObjectItem( - 'headers', - key, - key, - e.target.value, - ) - } - placeholder={t('mcp.value')} - className="flex-1" - /> - -
- ), - )} - -
-
-
-
-
-
- -
- - -
-
- ); -} diff --git a/web/src/app/home/plugins/page.tsx b/web/src/app/home/plugins/page.tsx index 6dc59ee7..bca70c3c 100644 --- a/web/src/app/home/plugins/page.tsx +++ b/web/src/app/home/plugins/page.tsx @@ -4,9 +4,7 @@ import PluginInstalledComponent, { } from '@/app/home/plugins/plugin-installed/PluginInstalledComponent'; import MarketPage from '@/app/home/plugins/plugin-market/PluginMarketComponent'; // import PluginSortDialog from '@/app/home/plugins/plugin-sort/PluginSortDialog'; -import MCPComponent, { - MCPComponentRef, -} from '@/app/home/plugins/mcp/MCPComponent'; + import MCPServerComponent from '@/app/home/plugins/mcp-server/MCPServerComponent'; import styles from './plugins.module.css'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; @@ -200,9 +198,7 @@ export default function PluginConfigPage() { }, 1000); } - const pluginInstalledRef = useRef(null); - const mcpComponentRef = useRef(null); const [mcpTesting, setMcpTesting] = useState(false); const [editingServerName, setEditingServerName] = useState( null, @@ -656,14 +652,20 @@ export default function PluginConfigPage() { t('mcp.toolsFound'), ); } else { - toast.error(t('mcp.testError') + ': ' + t('mcp.noToolsFound')); + toast.error( + t('mcp.testError') + ': ' + t('mcp.noToolsFound'), + ); } } catch (parseError) { console.error('Failed to parse test result:', parseError); - toast.error(t('mcp.testError') + ': ' + t('mcp.parseResultFailed')); + toast.error( + t('mcp.testError') + ': ' + t('mcp.parseResultFailed'), + ); } } else { - toast.error(t('mcp.testError') + ': ' + t('mcp.noResultReturned')); + toast.error( + t('mcp.testError') + ': ' + t('mcp.noResultReturned'), + ); } } }) @@ -671,7 +673,11 @@ export default function PluginConfigPage() { console.error('获取测试任务状态失败:', err); clearInterval(interval); setMcpTesting(false); - toast.error(t('mcp.testError') + ': ' + (err.message || t('mcp.getTaskFailed'))); + toast.error( + t('mcp.testError') + + ': ' + + (err.message || t('mcp.getTaskFailed')), + ); }); }, 1000); } else { @@ -682,7 +688,9 @@ export default function PluginConfigPage() { .catch((err) => { console.error('启动测试失败:', err); setMcpTesting(false); - toast.error(t('mcp.testError') + ': ' + (err.message || t('mcp.unknownError'))); + toast.error( + t('mcp.testError') + ': ' + (err.message || t('mcp.unknownError')), + ); }); } @@ -853,15 +861,6 @@ export default function PluginConfigPage() {
- {/* */}