From 8c5f61d407a39efdd4711e8f15825aeff76a2538 Mon Sep 17 00:00:00 2001 From: LiuVaayne <10231735+vaayne@users.noreply.github.com> Date: Mon, 31 Mar 2025 21:13:20 +0800 Subject: [PATCH] feat(MCP): add registryUrl support for package management (#4200) --- src/main/services/MCPService.ts | 18 +++ src/renderer/src/i18n/locales/en-us.json | 5 +- src/renderer/src/i18n/locales/ja-jp.json | 5 +- src/renderer/src/i18n/locales/ru-ru.json | 5 +- src/renderer/src/i18n/locales/zh-cn.json | 5 +- src/renderer/src/i18n/locales/zh-tw.json | 5 +- .../settings/MCPSettings/McpSettings.tsx | 152 ++++++++++++++---- src/renderer/src/types/index.ts | 1 + 8 files changed, 158 insertions(+), 38 deletions(-) diff --git a/src/main/services/MCPService.ts b/src/main/services/MCPService.ts index d44fc151..3cb78488 100644 --- a/src/main/services/MCPService.ts +++ b/src/main/services/MCPService.ts @@ -21,6 +21,7 @@ class McpService { baseUrl: server.baseUrl, command: server.command, args: server.args, + registryUrl: server.registryUrl, env: server.env, id: server.id }) @@ -94,6 +95,23 @@ class McpService { Logger.info(`[MCP] Starting server with command: ${cmd} ${args ? args.join(' ') : ''}`) + if (server.registryUrl) { + if (cmd.includes('npx') || cmd.includes('bun') || cmd.includes('bunx')) { + server.env = { + ...server.env, + NPM_CONFIG_REGISTRY: server.registryUrl + } + } else if (cmd.includes('uvx') || cmd.includes('uv')) { + server.env = { + ...server.env, + UV_DEFAULT_INDEX: server.registryUrl, + PIP_INDEX_URL: server.registryUrl + } + } + } + + // Logger.info(`[MCP] Environment variables for server:`, server.env) + transport = new StdioClientTransport({ command: cmd, args, diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index f60db5dc..fd1885e9 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1045,7 +1045,10 @@ "noToolsAvailable": "No tools available" }, "deleteServer": "Delete Server", - "deleteServerConfirm": "Are you sure you want to delete this server?" + "deleteServerConfirm": "Are you sure you want to delete this server?", + "registry": "Package Registry", + "registryTooltip": "Choose the registry for package installation to resolve network issues with the default registry.", + "registryDefault": "Default" }, "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 f7d92b1e..bbd89caf 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1044,7 +1044,10 @@ "noToolsAvailable": "利用可能なツールはありません" }, "deleteServer": "サーバーを削除", - "deleteServerConfirm": "このサーバーを削除してもよろしいですか?" + "deleteServerConfirm": "このサーバーを削除してもよろしいですか?", + "registry": "パッケージ管理レジストリ", + "registryTooltip": "デフォルトのレジストリでネットワークの問題が発生した場合、パッケージインストールに使用するレジストリを選択してください。", + "registryDefault": "デフォルト" }, "messages.divider": "メッセージ間に区切り線を表示", "messages.grid_columns": "メッセージグリッドの表示列数", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index d28f562c..6df12b66 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1044,7 +1044,10 @@ "noToolsAvailable": "нет доступных инструментов" }, "deleteServer": "Удалить сервер", - "deleteServerConfirm": "Вы уверены, что хотите удалить этот сервер?" + "deleteServerConfirm": "Вы уверены, что хотите удалить этот сервер?", + "registry": "Реестр пакетов", + "registryTooltip": "Выберите реестр для установки пакетов, если возникают проблемы с сетью при использовании реестра по умолчанию.", + "registryDefault": "По умолчанию" }, "messages.divider": "Показывать разделитель между сообщениями", "messages.grid_columns": "Количество столбцов сетки сообщений", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 343b8ad0..c8d45e14 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1045,7 +1045,10 @@ "noToolsAvailable": "没有可用工具" }, "deleteServer": "删除服务器", - "deleteServerConfirm": "确定要删除此服务器吗?" + "deleteServerConfirm": "确定要删除此服务器吗?", + "registry": "包管理源", + "registryTooltip": "选择用于安装包的源,以解决默认源的网络问题。", + "registryDefault": "默认" }, "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 d71503a0..bf28d0e4 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1044,7 +1044,10 @@ "noToolsAvailable": "沒有可用工具" }, "deleteServer": "刪除伺服器", - "deleteServerConfirm": "確定要刪除此伺服器嗎?" + "deleteServerConfirm": "確定要刪除此伺服器嗎?", + "registry": "套件管理源", + "registryTooltip": "選擇用於安裝套件的源,以解決預設源的網路問題。", + "registryDefault": "預設" }, "messages.divider": "訊息間顯示分隔線", "messages.grid_columns": "訊息網格展示列數", diff --git a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx index cc321904..ca3cae4b 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx @@ -20,11 +20,26 @@ interface MCPFormValues { serverType: 'sse' | 'stdio' baseUrl?: string command?: string + registryUrl?: string args?: string env?: string isActive: boolean } +interface Registry { + name: string + url: string +} + +const NpmRegistry: Registry[] = [{ name: '淘宝 NPM Mirror', url: 'https://registry.npmmirror.com' }] +const PipRegistry: Registry[] = [ + { name: '清华大学', url: 'https://pypi.tuna.tsinghua.edu.cn/simple' }, + { name: '阿里云', url: 'http://mirrors.aliyun.com/pypi/simple/' }, + { name: '中国科学技术大学', url: 'https://mirrors.ustc.edu.cn/pypi/simple/' }, + { name: '华为云', url: 'https://repo.huaweicloud.com/repository/pypi/simple/' }, + { name: '腾讯云', url: 'https://mirrors.cloud.tencent.com/pypi/simple/' } +] + const McpSettings: React.FC = ({ server }) => { const { t } = useTranslation() const { deleteMCPServer } = useMCPServers() @@ -35,36 +50,42 @@ const McpSettings: React.FC = ({ server }) => { const [loadingServer, setLoadingServer] = useState(null) const { updateMCPServer } = useMCPServers() const [tools, setTools] = useState([]) - - useEffect(() => { - if (server) { - form.setFieldsValue({ - name: server.name, - description: server.description, - serverType: server.baseUrl ? 'sse' : 'stdio', - baseUrl: server.baseUrl || '', - command: server.command || '', - args: server.args ? server.args.join('\n') : '', - env: server.env - ? Object.entries(server.env) - .map(([key, value]) => `${key}=${value}`) - .join('\n') - : '', - isActive: server.isActive - }) - } - }, [form, server]) + const [isShowRegistry, setIsShowRegistry] = useState(false) + const [registry, setRegistry] = useState() useEffect(() => { const serverType = server.baseUrl ? 'sse' : 'stdio' setServerType(serverType) + // Set registry UI state based on command and registryUrl + if (server.command) { + handleCommandChange(server.command) + + // If there's a registryUrl, ensure registry UI is shown + if (server.registryUrl) { + setIsShowRegistry(true) + + // Determine registry type based on command + if (server.command.includes('uv') || server.command.includes('uvx')) { + setRegistry(PipRegistry) + } else if ( + server.command.includes('npx') || + server.command.includes('bun') || + server.command.includes('bunx') + ) { + setRegistry(NpmRegistry) + } + } + } + form.setFieldsValue({ name: server.name, description: server.description, serverType: serverType, baseUrl: server.baseUrl || '', command: server.command || '', + registryUrl: server.registryUrl || '', + isActive: server.isActive, args: server.args ? server.args.join('\n') : '', env: server.env ? Object.entries(server.env) @@ -72,7 +93,8 @@ const McpSettings: React.FC = ({ server }) => { .join('\n') : '' }) - }, [form, server]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [server]) // Watch the serverType field to update the form layout dynamically useEffect(() => { @@ -110,32 +132,36 @@ const McpSettings: React.FC = ({ server }) => { try { const values = await form.validateFields() + // set basic fields const mcpServer: MCPServer = { id: server.id, name: values.name, description: values.description, - isActive: values.isActive + isActive: values.isActive, + registryUrl: values.registryUrl } + // set stdio or sse server if (values.serverType === 'sse') { mcpServer.baseUrl = values.baseUrl } else { mcpServer.command = values.command mcpServer.args = values.args ? values.args.split('\n').filter((arg) => arg.trim() !== '') : [] + } + // set env variables + if (values.env) { const env: Record = {} - if (values.env) { - values.env.split('\n').forEach((line) => { - if (line.trim()) { - const [key, ...chunks] = line.split('=') - const value = chunks.join('=') - if (key && value) { - env[key.trim()] = value.trim() - } + values.env.split('\n').forEach((line) => { + if (line.trim()) { + const [key, ...chunks] = line.split('=') + const value = chunks.join('=') + if (key && value) { + env[key.trim()] = value.trim() } - }) - } - mcpServer.env = Object.keys(env).length > 0 ? env : undefined + } + }) + mcpServer.env = env } try { @@ -156,9 +182,41 @@ const McpSettings: React.FC = ({ server }) => { } } catch (error: any) { setLoading(false) + console.error('Failed to save MCP server settings:', error) } } + // Watch for command field changes + const handleCommandChange = (command: string) => { + if (command.includes('uv') || command.includes('uvx')) { + setIsShowRegistry(true) + setRegistry(PipRegistry) + } else if (command.includes('npx') || command.includes('bun') || command.includes('bunx')) { + setIsShowRegistry(true) + setRegistry(NpmRegistry) + } else { + setIsShowRegistry(false) + setRegistry(undefined) + } + } + + const onSelectRegistry = (url: string) => { + const command = form.getFieldValue('command') || '' + + // Add new registry env variables + if (command.includes('uv') || command.includes('uvx')) { + // envs['PIP_INDEX_URL'] = url + // envs['UV_DEFAULT_INDEX'] = url + form.setFieldsValue({ registryUrl: url }) + } else if (command.includes('npx') || command.includes('bun') || command.includes('bunx')) { + // envs['NPM_CONFIG_REGISTRY'] = url + form.setFieldsValue({ registryUrl: url }) + } + + // Mark form as changed + setIsFormChanged(true) + } + const onDeleteMcpServer = useCallback( async (server: MCPServer) => { try { @@ -281,9 +339,37 @@ const McpSettings: React.FC = ({ server }) => { name="command" label={t('settings.mcp.command')} rules={[{ required: serverType === 'stdio', message: '' }]}> - + handleCommandChange(e.target.value)} /> + {isShowRegistry && registry && ( + + + { + onSelectRegistry(e.target.value) + }}> + {t('settings.mcp.registryDefault')} + + {registry.map((reg) => ( + { + onSelectRegistry(e.target.value) + }}> + {reg.name} + + ))} + + + )} + isActive: boolean