From 44299a8f5ba183d0094362f624fe8f6314c0f651 Mon Sep 17 00:00:00 2001 From: LiuVaayne <10231735+vaayne@users.noreply.github.com> Date: Sun, 27 Apr 2025 12:15:00 +0800 Subject: [PATCH] Feat/mcp enhancement (#5386) --- src/main/services/MCPService.ts | 4 +- src/renderer/src/i18n/locales/en-us.json | 11 +- src/renderer/src/i18n/locales/ja-jp.json | 13 +- src/renderer/src/i18n/locales/ru-ru.json | 13 +- src/renderer/src/i18n/locales/zh-cn.json | 11 +- src/renderer/src/i18n/locales/zh-tw.json | 11 +- .../settings/MCPSettings/McpServersList.tsx | 38 ++- .../settings/MCPSettings/McpSettings.tsx | 281 ++++++++++++++++-- .../MCPSettings/modelscopeSyncUtils.ts | 8 +- src/renderer/src/store/mcp.ts | 21 +- src/renderer/src/types/index.ts | 5 + 11 files changed, 376 insertions(+), 40 deletions(-) diff --git a/src/main/services/MCPService.ts b/src/main/services/MCPService.ts index 60848ddb22..49e648336c 100644 --- a/src/main/services/MCPService.ts +++ b/src/main/services/MCPService.ts @@ -395,7 +395,9 @@ class McpService { try { Logger.info('[MCP] Calling:', server.name, name, args) const client = await this.initClient(server) - const result = await client.callTool({ name, arguments: args }) + const result = await client.callTool({ name, arguments: args }, undefined, { + timeout: server.timeout ? server.timeout * 1000 : 60000 // Default timeout of 1 minute + }) return result as MCPCallToolResponse } catch (error) { Logger.error(`[MCP] Error calling tool ${name} on ${server.name}:`, error) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index b7bc9d67bb..b8e7dd67e5 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1187,7 +1187,16 @@ "success": "Sync MCP Servers successful", "unauthorized": "Sync Unauthorized", "noServersAvailable": "No MCP servers available" - } + }, + "timeout": "Timeout", + "timeoutTooltip": "Timeout in seconds for requests to this server, default is 60 seconds", + "provider": "Provider", + "providerUrl": "Provider URL", + "logoUrl": "Logo URL", + "tags": "Tags", + "tagsPlaceholder": "Enter tags", + "providerPlaceholder": "Provider name", + "advancedSettings": "Advanced Settings" }, "messages.divider": "Show divider between messages", "messages.grid_columns": "Message grid display columns", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index e31e59f559..1c1164ac96 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1185,7 +1185,16 @@ "success": "MCPサーバーの同期成功", "unauthorized": "同期が許可されていません", "noServersAvailable": "利用可能な MCP サーバーがありません" - } + }, + "timeout": "タイムアウト", + "timeoutTooltip": "このサーバーへのリクエストのタイムアウト時間(秒)、デフォルトは60秒です", + "provider": "プロバイダー", + "providerUrl": "プロバイダーURL", + "logoUrl": "ロゴURL", + "tags": "タグ", + "tagsPlaceholder": "タグを入力", + "providerPlaceholder": "プロバイダー名", + "advancedSettings": "詳細設定" }, "messages.divider": "メッセージ間に区切り線を表示", "messages.grid_columns": "メッセージグリッドの表示列数", @@ -1479,4 +1488,4 @@ "visualization": "可視化" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 73bb4c7f20..d6d156af12 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1185,7 +1185,16 @@ "success": "Синхронизация серверов MCP успешна", "unauthorized": "Синхронизация не разрешена", "noServersAvailable": "Нет доступных серверов MCP" - } + }, + "timeout": "Тайм-аут", + "timeoutTooltip": "Тайм-аут в секундах для запросов к этому серверу, по умолчанию 60 секунд", + "provider": "Провайдер", + "providerUrl": "URL провайдера", + "logoUrl": "URL логотипа", + "tags": "Теги", + "tagsPlaceholder": "Введите теги", + "providerPlaceholder": "Имя провайдера", + "advancedSettings": "Расширенные настройки" }, "messages.divider": "Показывать разделитель между сообщениями", "messages.grid_columns": "Количество столбцов сетки сообщений", @@ -1479,4 +1488,4 @@ "visualization": "Визуализация" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index c97bd1fc02..de8e422e9f 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1187,7 +1187,16 @@ "success": "同步MCP服务器成功", "unauthorized": "同步未授权", "noServersAvailable": "无可用的 MCP 服务器" - } + }, + "timeout": "超时", + "timeoutTooltip": "对该服务器请求的超时时间(秒),默认为60秒", + "provider": "提供者", + "providerUrl": "提供者网址", + "logoUrl": "标志网址", + "tags": "标签", + "tagsPlaceholder": "输入标签", + "providerPlaceholder": "提供者名称", + "advancedSettings": "高级设置" }, "messages.divider": "消息分割线", "messages.grid_columns": "消息网格展示列数", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 644d2977fe..c6ac493d34 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1186,7 +1186,16 @@ "success": "同步MCP伺服器成功", "unauthorized": "同步未授權", "noServersAvailable": "無可用的 MCP 伺服器" - } + }, + "timeout": "超時", + "timeoutTooltip": "對該伺服器請求的超時時間(秒),預設為60秒", + "provider": "提供者", + "providerUrl": "提供者網址", + "logoUrl": "標誌網址", + "tags": "標籤", + "tagsPlaceholder": "輸入標籤", + "providerPlaceholder": "提供者名稱", + "advancedSettings": "高級設定" }, "messages.divider": "訊息間顯示分隔線", "messages.grid_columns": "訊息網格展示列數", diff --git a/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx b/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx index fda6dc4fae..399e9c7678 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx @@ -5,7 +5,7 @@ import Scrollbar from '@renderer/components/Scrollbar' import { useMCPServers } from '@renderer/hooks/useMCPServers' import { MCPServer } from '@renderer/types' import { Button, Empty, Tag } from 'antd' -import { MonitorCheck, Plus, RefreshCw, Settings2 } from 'lucide-react' +import { MonitorCheck, Plus, RefreshCw, Settings2, SquareArrowOutUpRight } from 'lucide-react' import { FC, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router' @@ -61,7 +61,17 @@ const McpServersList: FC = () => { navigate(`/settings/mcp/settings`, { state: { server } })}> + {server.logoUrl && } {server.name} + {server.providerUrl && ( + + )} @@ -76,9 +86,20 @@ const McpServersList: FC = () => { {server.description} - + {t(`settings.mcp.types.${server.type || 'stdio'}`)} + {server.provider && ( + + {server.provider} + + )} + {server.tags && + server.tags.map((tag) => ( + + {tag} + + ))} )} @@ -136,6 +157,14 @@ const ServerCard = styled.div` } ` +const ServerLogo = styled.img` + width: 24px; + height: 24px; + border-radius: 4px; + object-fit: cover; + margin-right: 8px; +` + const ServerHeader = styled.div` display: flex; align-items: center; @@ -155,7 +184,7 @@ const ServerName = styled.div` text-overflow: ellipsis; display: flex; align-items: center; - gap: 10px; + gap: 4px; ` const ServerNameText = styled.span` @@ -183,7 +212,8 @@ const ServerDescription = styled.div` const ServerFooter = styled.div` display: flex; align-items: center; - justify-content: space-between; + gap: 4px; + justify-content: flex-start; margin-top: 10px; ` diff --git a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx index ef9503c5ae..5e125c39b6 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx @@ -3,8 +3,28 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { useMCPServers } from '@renderer/hooks/useMCPServers' import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription' import { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types' -import { Button, Flex, Form, Input, Radio, Switch, Tabs } from 'antd' +import { Button, Collapse, Flex, Form, Input, Radio, Select, Switch, Tabs } from 'antd' import TextArea from 'antd/es/input/TextArea' +import { + AlignLeft, + Building2, + Clock, + Code, + Database, + FileText, + Globe, + Image, + Link, + ListPlus, + MessageSquare, + Package, + Server, + Settings, + Tag, + Terminal, + Type, + Wrench +} from 'lucide-react' import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useLocation, useNavigate } from 'react-router' @@ -26,6 +46,12 @@ interface MCPFormValues { env?: string isActive: boolean headers?: string + timeout?: number + + provider?: string + providerUrl?: string + logoUrl?: string + tags?: string[] } interface Registry { @@ -84,6 +110,7 @@ const McpSettings: React.FC = () => { const navigate = useNavigate() + // Initialize form values whenever the server changes useEffect(() => { const serverType: MCPServer['type'] = server.type || (server.baseUrl ? 'sse' : 'stdio') setServerType(serverType) @@ -109,6 +136,7 @@ const McpSettings: React.FC = () => { } } + // Initialize basic fields form.setFieldsValue({ name: server.name, description: server.description, @@ -117,6 +145,7 @@ const McpSettings: React.FC = () => { command: server.command || '', registryUrl: server.registryUrl || '', isActive: server.isActive, + timeout: server.timeout, args: server.args ? server.args.join('\n') : '', env: server.env ? Object.entries(server.env) @@ -129,8 +158,18 @@ const McpSettings: React.FC = () => { .join('\n') : '' }) + + // Initialize advanced fields separately to ensure they're captured + // even if the Collapse panel is closed + form.setFieldsValue({ + provider: server.provider || '', + providerUrl: server.providerUrl || '', + logoUrl: server.logoUrl || '', + tags: server.tags || [] + }) }, [server, form]) + // Watch for serverType changes useEffect(() => { const currentServerType = form.getFieldValue('serverType') if (currentServerType) { @@ -219,7 +258,13 @@ const McpSettings: React.FC = () => { description: values.description, isActive: values.isActive, registryUrl: values.registryUrl, - searchKey: server.searchKey + searchKey: server.searchKey, + timeout: values.timeout || server.timeout, + // Preserve existing advanced properties if not set in the form + provider: values.provider || server.provider, + providerUrl: values.providerUrl || server.providerUrl, + logoUrl: values.logoUrl || server.logoUrl, + tags: values.tags || server.tags } // set stdio or sse server @@ -392,7 +437,12 @@ const McpSettings: React.FC = () => { const tabs = [ { key: 'settings', - label: t('settings.mcp.tabs.general'), + label: ( + + + {t('settings.mcp.tabs.general')} + + ), children: (
{ width: 'calc(100% + 10px)', paddingRight: '10px' }}> - + + + {t('settings.mcp.name')} + + } + rules={[{ required: true, message: '' }]}> - + + + {t('settings.mcp.description')} + + }>