feat(MCPSettings): enhance MCP server management and localization updates

- Added a new SVG icon for npm in the MCP settings.
- Introduced a custom hook `useMCPServer` for retrieving a specific MCP server by ID.
- Updated localization files to include new error messages for tool and prompt loading in English, Japanese, Russian, and Chinese.
- Refactored MCP settings components for improved navigation and state management, including the use of React Router for routing.
- Enhanced the Npx search functionality and UI for better user experience.
This commit is contained in:
kangfenmao 2025-04-12 19:25:20 +08:00
parent b62c59eb52
commit 72e18fbcc1
17 changed files with 248 additions and 257 deletions

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744456106953" class="icon" viewBox="0 0 2633 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1486" xmlns:xlink="http://www.w3.org/1999/xlink" width="514.2578125" height="200"><path d="M0 0v877.843607h731.440328v146.156393H1316.724263v-146.156393h1316.724262V0z m731.440328 731.196014h-146.156393V292.312786h-146.485574v438.883228H146.485574V146.238688h584.954754z m438.880656 0v146.567869h-292.405369V146.238688H1463.209837v585.037049H1170.320984z m1316.888853 0H2341.30033V292.312786h-146.56787v438.883228h-146.485574V292.312786h-145.909508v438.883228H1609.283935V146.238688h878.008197zM1170.238688 292.477377H1316.724263v292.644539h-146.485575z" fill="#CB3837" p-id="1487"></path></svg>

After

Width:  |  Height:  |  Size: 845 B

View File

@ -26,3 +26,10 @@ export const useMCPServers = () => {
updateMcpServers: (servers: MCPServer[]) => dispatch(setMCPServers(servers))
}
}
export const useMCPServer = (id: string) => {
const { mcpServers } = useMCPServers()
return {
server: mcpServers.find((server) => server.id === id)
}
}

View File

@ -1064,7 +1064,6 @@
"newServer": "MCP Server",
"npx_list": {
"actions": "Actions",
"desc": "Search and add npm packages as MCP servers",
"description": "Description",
"no_packages": "No packages found",
"npm": "NPM",
@ -1073,7 +1072,6 @@
"scope_required": "Please enter npm scope",
"search": "Search",
"search_error": "Search error",
"title": "NPX Package List",
"usage": "Usage",
"version": "Version"
},
@ -1099,14 +1097,16 @@
"tools": {
"inputSchema": "Input Schema",
"availableTools": "Available Tools",
"noToolsAvailable": "No tools available"
"noToolsAvailable": "No tools available",
"loadError": "Get tools Error"
},
"prompts": {
"availablePrompts": "Available Prompts",
"noPromptsAvailable": "No prompts available",
"arguments": "Arguments",
"requiredField": "Required Field",
"genericError": "Get prompt Error"
"genericError": "Get prompt Error",
"loadError": "Get prompts Error"
},
"deleteServer": "Delete Server",
"deleteServerConfirm": "Are you sure you want to delete this server?",

View File

@ -1063,7 +1063,6 @@
"newServer": "MCP サーバー",
"npx_list": {
"actions": "アクション",
"desc": "npm パッケージを検索して MCP サーバーとして追加",
"description": "説明",
"no_packages": "パッケージが見つかりません",
"npm": "NPM",
@ -1072,7 +1071,6 @@
"scope_required": "npm スコープを入力してください",
"search": "検索",
"search_error": "パッケージの検索に失敗しました",
"title": "NPX パッケージリスト",
"usage": "使用法",
"version": "バージョン"
},
@ -1098,14 +1096,16 @@
"tools": {
"inputSchema": "入力スキーマ",
"availableTools": "利用可能なツール",
"noToolsAvailable": "利用可能なツールなし"
"noToolsAvailable": "利用可能なツールなし",
"loadError": "ツール取得エラー"
},
"prompts": {
"availablePrompts": "利用可能なプロンプト",
"noPromptsAvailable": "利用可能なプロンプトはありません",
"arguments": "引数",
"requiredField": "必須フィールド",
"genericError": "プロンプト取得エラー"
"genericError": "プロンプト取得エラー",
"loadError": "プロンプト取得エラー"
},
"deleteServer": "サーバーを削除",
"deleteServerConfirm": "このサーバーを削除してもよろしいですか?",

View File

@ -1063,7 +1063,6 @@
"newServer": "MCP сервер",
"npx_list": {
"actions": "Действия",
"desc": "Поиск и добавление npm пакетов в качестве MCP серверов",
"description": "Описание",
"no_packages": "Ничего не найдено",
"npm": "NPM",
@ -1072,7 +1071,6 @@
"scope_required": "Пожалуйста, введите область npm",
"search": "Поиск",
"search_error": "Ошибка поиска",
"title": "Список пакетов NPX",
"usage": "Использование",
"version": "Версия"
},
@ -1098,14 +1096,16 @@
"tools": {
"inputSchema": "Схема ввода",
"availableTools": "Доступные инструменты",
"noToolsAvailable": "Нет доступных инструментов"
"noToolsAvailable": "Нет доступных инструментов",
"loadError": "Ошибка получения инструментов"
},
"prompts": {
"availablePrompts": "Доступные подсказки",
"noPromptsAvailable": "Нет доступных подсказок",
"arguments": "Аргументы",
"requiredField": "Обязательное поле",
"genericError": "Ошибка получения подсказки"
"genericError": "Ошибка получения подсказки",
"loadError": "Ошибка получения подсказок"
},
"deleteServer": "Удалить сервер",
"deleteServerConfirm": "Вы уверены, что хотите удалить этот сервер?",

View File

@ -1064,7 +1064,6 @@
"newServer": "MCP 服务器",
"npx_list": {
"actions": "操作",
"desc": "搜索并添加 npm 包作为 MCP 服务",
"description": "描述",
"no_packages": "未找到包",
"npm": "NPM",
@ -1073,7 +1072,6 @@
"scope_required": "请输入 npm 作用域",
"search": "搜索",
"search_error": "搜索失败",
"title": "NPX 包列表",
"usage": "用法",
"version": "版本"
},
@ -1099,14 +1097,16 @@
"tools": {
"inputSchema": "输入模式",
"availableTools": "可用工具",
"noToolsAvailable": "无可用工具"
"noToolsAvailable": "无可用工具",
"loadError": "获取工具失败"
},
"prompts": {
"availablePrompts": "可用提示",
"noPromptsAvailable": "无可用提示",
"arguments": "参数",
"requiredField": "必填字段",
"genericError": "获取提示错误"
"genericError": "获取提示错误",
"loadError": "获取提示失败"
},
"deleteServer": "删除服务器",
"deleteServerConfirm": "确定要删除此服务器吗?",

View File

@ -1063,7 +1063,6 @@
"newServer": "MCP 伺服器",
"npx_list": {
"actions": "操作",
"desc": "搜索並添加 npm 包作為 MCP 服務",
"description": "描述",
"no_packages": "未找到包",
"npm": "NPM",
@ -1072,7 +1071,6 @@
"scope_required": "請輸入 npm 作用域",
"search": "搜索",
"search_error": "搜索失敗",
"title": "NPX 包列表",
"usage": "用法",
"version": "版本"
},
@ -1098,14 +1096,16 @@
"tools": {
"inputSchema": "輸入模式",
"availableTools": "可用工具",
"noToolsAvailable": "無可用工具"
"noToolsAvailable": "無可用工具",
"loadError": "獲取工具失敗"
},
"prompts": {
"availablePrompts": "可用提示",
"noPromptsAvailable": "無可用提示",
"arguments": "參數",
"requiredField": "必填欄位",
"genericError": "獲取提示錯誤"
"genericError": "獲取提示錯誤",
"loadError": "獲取提示失敗"
},
"deleteServer": "刪除伺服器",
"deleteServerConfirm": "確定要刪除此伺服器嗎?",

View File

@ -912,7 +912,6 @@
"noServers": "Δεν έχουν ρυθμιστεί διακομιστές",
"npx_list": {
"actions": "Ενέργειες",
"desc": "Αναζητήστε και προσθέστε πακέτα npm ως υπηρεσίες MCP",
"description": "Περιγραφή",
"no_packages": "Δεν βρέθηκαν πακέτα",
"npm": "NPM",
@ -921,7 +920,6 @@
"scope_required": "Παρακαλώ εισαγάγετε το σκοπό του npm",
"search": "Αναζήτηση",
"search_error": "Η αναζήτηση απέτυχε",
"title": "Λίστα πακέτων NPX",
"usage": "Χρήση",
"version": "Έκδοση"
},

View File

@ -912,7 +912,6 @@
"noServers": "No se han configurado servidores",
"npx_list": {
"actions": "Acciones",
"desc": "Buscar y agregar paquetes npm como servicios MCP",
"description": "Descripción",
"no_packages": "No se encontraron paquetes",
"npm": "NPM",
@ -921,7 +920,6 @@
"scope_required": "Por favor ingrese el ámbito npm",
"search": "Buscar",
"search_error": "Error de búsqueda",
"title": "Lista de paquetes NPX",
"usage": "Uso",
"version": "Versión"
},

View File

@ -912,7 +912,6 @@
"noServers": "Aucun serveur configuré",
"npx_list": {
"actions": "Actions",
"desc": "Rechercher et ajouter un package npm en tant que service MCP",
"description": "Description",
"no_packages": "Aucun package trouvé",
"npm": "NPM",
@ -921,7 +920,6 @@
"scope_required": "Veuillez entrer le scope npm",
"search": "Rechercher",
"search_error": "La recherche a échoué",
"title": "Liste des packages NPX",
"usage": "Utilisation",
"version": "Version"
},

View File

@ -912,7 +912,6 @@
"noServers": "Nenhum servidor configurado",
"npx_list": {
"actions": "Ações",
"desc": "Pesquise e adicione pacotes npm como serviço MCP",
"description": "Descrição",
"no_packages": "Nenhum pacote encontrado",
"npm": "NPM",
@ -921,7 +920,6 @@
"scope_required": "Insira o escopo npm",
"search": "Pesquisar",
"search_error": "Falha na pesquisa",
"title": "Lista de Pacotes NPX",
"usage": "Uso",
"version": "Versão"
},

View File

@ -1,9 +1,9 @@
import { CheckCircleOutlined, QuestionCircleOutlined, WarningOutlined } from '@ant-design/icons'
import { Center, VStack } from '@renderer/components/Layout'
import { EventEmitter } from '@renderer/services/EventService'
import { Alert, Button } from 'antd'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import styled from 'styled-components'
import { SettingDescription, SettingRow, SettingSubtitle } from '..'
@ -21,6 +21,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
const [bunPath, setBunPath] = useState<string | null>(null)
const [binariesDir, setBinariesDir] = useState<string | null>(null)
const { t } = useTranslation()
const navigate = useNavigate()
const checkBinaries = async () => {
const uvExists = await window.api.isBinaryExist('uv')
@ -78,7 +79,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
icon={installed ? <CheckCircleOutlined /> : <WarningOutlined />}
className="nodrag"
color={installed ? 'green' : 'danger'}
onClick={() => EventEmitter.emit('mcp:mcp-install')}
onClick={() => navigate('/settings/mcp/mcp-install')}
/>
)
}

View File

@ -5,6 +5,7 @@ import { Button, Flex, Form, Input, Radio, Switch, Tabs } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '..'
@ -58,6 +59,8 @@ const McpSettings: React.FC<Props> = ({ server }) => {
const [isShowRegistry, setIsShowRegistry] = useState(false)
const [registry, setRegistry] = useState<Registry[]>()
const navigate = useNavigate()
useEffect(() => {
const serverType: MCPServer['type'] = server.type || (server.baseUrl ? 'sse' : 'stdio')
setServerType(serverType)
@ -114,10 +117,9 @@ const McpSettings: React.FC<Props> = ({ server }) => {
setLoadingServer(server.id)
const localTools = await window.api.mcp.listTools(server)
setTools(localTools)
// window.message.success(t('settings.mcp.toolsLoaded'))
} catch (error) {
window.message.error({
content: t('settings.mcp.toolsLoadError') + formatError(error),
content: t('settings.mcp.tools.loadError') + ' ' + formatError(error),
key: 'mcp-tools-error'
})
} finally {
@ -134,7 +136,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
setPrompts(localPrompts)
} catch (error) {
window.message.error({
content: t('settings.mcp.promptsLoadError') + formatError(error),
content: t('settings.mcp.prompts.loadError') + ' ' + formatError(error),
key: 'mcp-prompts-error'
})
setPrompts([])
@ -258,6 +260,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
await window.api.mcp.removeServer(server)
deleteMCPServer(server.id)
window.message.success({ content: t('settings.mcp.deleteSuccess'), key: 'mcp-list' })
navigate('/settings/mcp')
}
})
} catch (error: any) {

View File

@ -2,15 +2,16 @@ import { EditOutlined, ExportOutlined, SearchOutlined } from '@ant-design/icons'
import { NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import { isWindows } from '@renderer/config/constant'
import { EventEmitter } from '@renderer/services/EventService'
import { Button } from 'antd'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import EditMcpJsonPopup from './EditMcpJsonPopup'
import InstallNpxUv from './InstallNpxUv'
export const McpSettingsNavbar = () => {
const { t } = useTranslation()
const navigate = useNavigate()
const onClick = () => window.open('https://mcp.so/', '_blank')
return (
@ -19,7 +20,7 @@ export const McpSettingsNavbar = () => {
<Button
size="small"
type="text"
onClick={() => EventEmitter.emit('mcp:npx-search')}
onClick={() => navigate('/settings/mcp/npx-search')}
icon={<SearchOutlined />}
className="nodrag"
style={{ fontSize: 13, height: 28, borderRadius: 20 }}>

View File

@ -1,7 +1,7 @@
import { PlusOutlined, SearchOutlined } from '@ant-design/icons'
import { CheckOutlined, PlusOutlined } from '@ant-design/icons'
import { nanoid } from '@reduxjs/toolkit'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import npmLogo from '@renderer/assets/images/mcp/npm.svg'
import { Center, HStack } from '@renderer/components/Layout'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { builtinMCPServers } from '@renderer/store/mcp'
import { MCPServer } from '@renderer/types'
@ -11,8 +11,6 @@ import { type FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { SettingDivider, SettingGroup, SettingTitle } from '..'
interface SearchResult {
name: string
description: string
@ -29,9 +27,7 @@ let _searchResults: SearchResult[] = []
const NpxSearch: FC<{
setSelectedMcpServer: (server: MCPServer) => void
setRoute: (route: string | null) => void
}> = ({ setSelectedMcpServer, setRoute }) => {
const { theme } = useTheme()
}> = ({ setSelectedMcpServer }) => {
const { t } = useTranslation()
const { Text, Link } = Typography
@ -39,7 +35,7 @@ const NpxSearch: FC<{
const [npmScope, setNpmScope] = useState('@cherry')
const [searchLoading, setSearchLoading] = useState(false)
const [searchResults, setSearchResults] = useState<SearchResult[]>(_searchResults)
const { addMCPServer } = useMCPServers()
const { addMCPServer, mcpServers } = useMCPServers()
_searchResults = searchResults
@ -119,48 +115,52 @@ const NpxSearch: FC<{
}, [])
return (
<SettingGroup theme={theme} css={SettingGroup}>
<div>
<SettingTitle>
{t('settings.mcp.npx_list.title')} <Text type="secondary">{t('settings.mcp.npx_list.desc')}</Text>
</SettingTitle>
<SettingDivider />
<Space direction="vertical" style={{ width: '100%' }}>
<Space.Compact style={{ width: '100%', marginBottom: 10 }}>
<Container>
<Center>
<Space direction="vertical" style={{ marginBottom: 20, width: 500 }}>
<Center style={{ marginBottom: 20 }}>
<img src={npmLogo} alt="npm" width={100} />
</Center>
<Space.Compact style={{ width: '100%' }}>
<Input
placeholder={t('settings.mcp.npx_list.scope_placeholder')}
value={npmScope}
onChange={(e) => setNpmScope(e.target.value)}
onPressEnter={() => handleNpmSearch(npmScope)}
size="large"
styles={{ input: { borderRadius: 100 } }}
/>
<Button icon={<SearchOutlined />} onClick={() => handleNpmSearch(npmScope)} disabled={searchLoading}>
{t('settings.mcp.npx_list.search')}
</Button>
</Space.Compact>
<HStack alignItems="center" mt="-5px" mb="5px">
<HStack alignItems="center" justifyContent="center">
{npmScopes.map((scope) => (
<Tag
key={scope}
bordered={false}
onClick={() => {
setNpmScope(scope)
handleNpmSearch(scope)
}}
style={{ cursor: searchLoading ? 'not-allowed' : 'pointer' }}>
style={{
cursor: searchLoading ? 'not-allowed' : 'pointer',
borderRadius: 100,
backgroundColor: 'var(--color-background-mute)'
}}>
{scope}
</Tag>
))}
</HStack>
</Space>
</div>
<ResultList>
{searchLoading ? (
<div style={{ textAlign: 'center', padding: '20px' }}>
</Center>
{searchLoading && (
<Center>
<Spin />
</div>
) : (
searchResults?.map((record) => (
</Center>
)}
{!searchLoading && (
<ResultList>
{searchResults?.map((record) => {
const isInstalled = mcpServers.some((server) => server.name === record.name)
return (
<Card
size="small"
key={record.name}
@ -176,18 +176,24 @@ const NpxSearch: FC<{
</Tag>
<Button
type="text"
icon={<PlusOutlined />}
icon={
isInstalled ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <PlusOutlined />
}
size="small"
onClick={() => {
if (isInstalled) {
return
}
const buildInServer = builtinMCPServers.find((server) => server.name === record.name)
if (buildInServer) {
addMCPServer(buildInServer)
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-server' })
setSelectedMcpServer(buildInServer)
setRoute(null)
return
}
const newServer = {
id: nanoid(),
name: record.name,
@ -201,7 +207,6 @@ const NpxSearch: FC<{
addMCPServer(newServer)
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-server' })
setSelectedMcpServer(newServer)
setRoute(null)
}}
/>
</Flex>
@ -216,21 +221,28 @@ const NpxSearch: FC<{
</Link>
</Space>
</Card>
))
)}
)
})}
</ResultList>
</SettingGroup>
)}
</Container>
)
}
const ResultList = styled.div`
flex: 1;
const Container = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
width: calc(100% + 10px);
`
const ResultList = styled.div`
flex: 1;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
width: 100%;
padding-right: 4px;
overflow-y: scroll;
overflow-y: auto;
`
export default NpxSearch

View File

@ -1,17 +1,17 @@
import { ArrowLeftOutlined, CodeOutlined, PlusOutlined } from '@ant-design/icons'
import { nanoid } from '@reduxjs/toolkit'
import IndicatorLight from '@renderer/components/IndicatorLight'
import { HStack, VStack } from '@renderer/components/Layout'
import { VStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { EventEmitter } from '@renderer/services/EventService'
import { MCPServer } from '@renderer/types'
import { isEmpty } from 'lodash'
import { FC, useEffect, useMemo, useState } from 'react'
import { FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Route, Routes, useLocation, useNavigate } from 'react-router'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { SettingContainer } from '..'
import { SettingContainer, SettingTitle } from '..'
import InstallNpxUv from './InstallNpxUv'
import McpSettings from './McpSettings'
import NpxSearch from './NpxSearch'
@ -20,18 +20,13 @@ const MCPSettings: FC = () => {
const { t } = useTranslation()
const { mcpServers, addMCPServer } = useMCPServers()
const [selectedMcpServer, setSelectedMcpServer] = useState<MCPServer | null>(null)
const [route, setRoute] = useState<'npx-search' | 'mcp-install' | null>(null)
const { theme } = useTheme()
const navigate = useNavigate()
useEffect(() => {
const unsubs = [
EventEmitter.on('mcp:npx-search', () => setRoute('npx-search')),
EventEmitter.on('mcp:mcp-install', () => setRoute('mcp-install'))
]
return () => unsubs.forEach((unsub) => unsub())
}, [])
const location = useLocation()
const pathname = location.pathname
const onAddMcpServer = async () => {
const onAddMcpServer = useCallback(async () => {
const newServer = {
id: nanoid(),
name: t('settings.mcp.newServer'),
@ -45,13 +40,12 @@ const MCPSettings: FC = () => {
addMCPServer(newServer)
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-list' })
setSelectedMcpServer(newServer)
setRoute(null)
}
}, [addMCPServer, t])
useEffect(() => {
const _selectedMcpServer = mcpServers.find((server) => server.id === selectedMcpServer?.id)
setSelectedMcpServer(_selectedMcpServer || mcpServers[0])
}, [mcpServers, route, selectedMcpServer])
}, [mcpServers, selectedMcpServer])
useEffect(() => {
// Check if the selected server still exists in the updated mcpServers list
@ -65,69 +59,23 @@ const MCPSettings: FC = () => {
}
}, [mcpServers, selectedMcpServer])
const MainContent = useMemo(() => {
if (route === 'npx-search' || isEmpty(mcpServers)) {
return (
<SettingContainer theme={theme}>
<NpxSearch
setRoute={(route) => setRoute(route as 'npx-search' | 'mcp-install' | null)}
setSelectedMcpServer={setSelectedMcpServer}
/>
</SettingContainer>
)
}
if (route === 'mcp-install') {
return (
<SettingContainer theme={theme}>
<InstallNpxUv />
</SettingContainer>
)
}
if (selectedMcpServer) {
return <McpSettings server={selectedMcpServer} />
}
return (
<NpxSearch
setRoute={(route) => setRoute(route as 'npx-search' | 'mcp-install' | null)}
setSelectedMcpServer={setSelectedMcpServer}
/>
)
}, [mcpServers, route, selectedMcpServer, theme])
const goBackToGrid = () => {
setSelectedMcpServer(null)
}
return (
<Container>
{selectedMcpServer ? (
<DetailViewContainer>
<BackButtonContainer>
<BackButton onClick={goBackToGrid}>
<ArrowLeftOutlined /> {t('common.back')}
</BackButton>
</BackButtonContainer>
<DetailContent>{MainContent}</DetailContent>
</DetailViewContainer>
) : (
const McpServersList = useCallback(
() => (
<GridContainer>
<GridHeader>
<h2>{t('settings.mcp.newServer')}</h2>
<SettingTitle>{t('settings.mcp.newServer')}</SettingTitle>
</GridHeader>
<ServersGrid>
<AddServerCard onClick={onAddMcpServer}>
<PlusOutlined style={{ fontSize: 24 }} />
<AddServerText>{t('settings.mcp.addServer')}</AddServerText>
</AddServerCard>
{mcpServers.map((server) => (
<ServerCard
key={server.id}
onClick={() => {
setSelectedMcpServer(server)
setRoute(null)
navigate(`/settings/mcp/server/${server.id}`)
}}>
<ServerHeader>
<ServerIcon>
@ -151,12 +99,50 @@ const MCPSettings: FC = () => {
))}
</ServersGrid>
</GridContainer>
),
[mcpServers, navigate, onAddMcpServer, t]
)
const isHome = pathname === '/settings/mcp'
return (
<Container>
{!isHome && (
<BackButtonContainer>
<Link to="/settings/mcp">
<BackButton>
<ArrowLeftOutlined /> {t('common.back')}
</BackButton>
</Link>
</BackButtonContainer>
)}
<MainContainer>
<Routes>
<Route path="/" element={<McpServersList />} />
<Route path="server/:id" element={selectedMcpServer ? <McpSettings server={selectedMcpServer} /> : null} />
<Route
path="npx-search"
element={
<SettingContainer theme={theme}>
<NpxSearch setSelectedMcpServer={setSelectedMcpServer} />
</SettingContainer>
}
/>
<Route
path="mcp-install"
element={
<SettingContainer theme={theme}>
<InstallNpxUv />
</SettingContainer>
}
/>
</Routes>
</MainContainer>
</Container>
)
}
const Container = styled(HStack)`
const Container = styled(VStack)`
flex: 1;
`
@ -251,20 +237,13 @@ const AddServerText = styled.div`
font-weight: 500;
`
const DetailViewContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
position: relative;
`
const BackButtonContainer = styled.div`
padding: 16px 0 0 20px;
padding: 12px 0 0 12px;
width: 100%;
background-color: var(--color-background);
`
const DetailContent = styled.div`
const MainContainer = styled.div`
flex: 1;
width: 100%;
`

View File

@ -12,7 +12,6 @@ import {
ThunderboltOutlined
} from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { isLocalAi } from '@renderer/config/env'
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings'
// 导入useAppSelector
@ -46,12 +45,10 @@ const SettingsPage: FC = () => {
<Container>
<Navbar>
<NavbarCenter style={{ borderRight: 'none' }}>{t('settings.title')}</NavbarCenter>
{pathname === '/settings/mcp' && <McpSettingsNavbar />}
{pathname.includes('/settings/mcp') && <McpSettingsNavbar />}
</Navbar>
<ContentContainer id="content-container">
<SettingMenus>
{!isLocalAi && (
<>
<MenuItemLink to="/settings/provider">
<MenuItem className={isRoute('/settings/provider')}>
<CloudOutlined />
@ -64,8 +61,6 @@ const SettingsPage: FC = () => {
{t('settings.model')}
</MenuItem>
</MenuItemLink>
</>
)}
<MenuItemLink to="/settings/web-search">
<MenuItem className={isRoute('/settings/web-search')}>
<GlobalOutlined />
@ -134,13 +129,13 @@ const SettingsPage: FC = () => {
<Route path="provider" element={<ProvidersList />} />
<Route path="model" element={<ModelSettings />} />
<Route path="web-search" element={<WebSearchSettings />} />
<Route path="mcp" element={<MCPSettings />} />
<Route path="general/*" element={<GeneralSettings />} />
<Route path="mcp/*" element={<MCPSettings />} />
<Route path="general" element={<GeneralSettings />} />
<Route path="display" element={<DisplaySettings />} />
{showMiniAppSettings && <Route path="miniapps" element={<MiniAppSettings />} />}
<Route path="shortcut" element={<ShortcutSettings />} />
<Route path="quickAssistant" element={<QuickAssistantSettings />} />
<Route path="data/*" element={<DataSettings />} />
<Route path="data" element={<DataSettings />} />
<Route path="about" element={<AboutSettings />} />
<Route path="quickPhrase" element={<QuickPhraseSettings />} />
</Routes>