mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 13:19:33 +08:00
refactor: mcp servers settings
This commit is contained in:
parent
f42afe28d7
commit
3417acafe2
@ -16,7 +16,7 @@ const BuiltinMCPServerList: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingTitle style={{ gap: 3 }}>{t('settings.mcp.builtinServers')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 10 }}>{t('settings.mcp.builtinServers')}</SettingTitle>
|
||||||
<ServersGrid>
|
<ServersGrid>
|
||||||
{builtinMCPServers.map((server) => {
|
{builtinMCPServers.map((server) => {
|
||||||
const isInstalled = mcpServers.some((existingServer) => existingServer.name === server.name)
|
const isInstalled = mcpServers.some((existingServer) => existingServer.name === server.name)
|
||||||
|
|||||||
@ -74,7 +74,7 @@ const McpMarketList: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingTitle style={{ gap: 3 }}>{t('settings.mcp.findMore')}</SettingTitle>
|
<SettingTitle style={{ marginBottom: 10 }}>{t('settings.mcp.findMore')}</SettingTitle>
|
||||||
<MarketGrid>
|
<MarketGrid>
|
||||||
{mcpMarkets.map((resource) => (
|
{mcpMarkets.map((resource) => (
|
||||||
<MarketCard key={resource.name} onClick={() => window.open(resource.url, '_blank', 'noopener,noreferrer')}>
|
<MarketCard key={resource.name} onClick={() => window.open(resource.url, '_blank', 'noopener,noreferrer')}>
|
||||||
|
|||||||
@ -0,0 +1,186 @@
|
|||||||
|
import { Flex, RowFlex } from '@cherrystudio/ui'
|
||||||
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
|
import type { MCPServer } from '@renderer/types'
|
||||||
|
import { Button, Divider, Input, Space } from 'antd'
|
||||||
|
import Link from 'antd/es/typography/Link'
|
||||||
|
import { SquareArrowOutUpRight } from 'lucide-react'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import { SettingHelpLink, SettingHelpTextRow, SettingSubtitle } from '..'
|
||||||
|
import type { ProviderConfig } from './providers/config'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
provider: ProviderConfig
|
||||||
|
existingServers: MCPServer[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const McpProviderSettings: React.FC<Props> = ({ provider, existingServers }) => {
|
||||||
|
const { addMCPServer, updateMCPServer } = useMCPServers()
|
||||||
|
const [isFetching, setIsFetching] = useState(false)
|
||||||
|
const [token, setToken] = useState<string>('')
|
||||||
|
const [availableServers, setAvailableServers] = useState<MCPServer[]>([])
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const savedToken = provider.getToken()
|
||||||
|
if (savedToken) {
|
||||||
|
setToken(savedToken)
|
||||||
|
}
|
||||||
|
}, [provider])
|
||||||
|
|
||||||
|
const handleFetch = useCallback(async () => {
|
||||||
|
if (!token.trim()) {
|
||||||
|
window.toast.error(t('settings.mcp.sync.tokenRequired', 'API Token is required'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsFetching(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
provider.saveToken(token)
|
||||||
|
const result = await provider.syncServers(token, existingServers)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
setAvailableServers(result.addedServers || [])
|
||||||
|
window.toast.success(t('settings.mcp.fetch.success', 'Successfully fetched MCP servers'))
|
||||||
|
} else {
|
||||||
|
window.toast.error(result.message)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
window.toast.error(`${t('settings.mcp.sync.error')}: ${error.message}`)
|
||||||
|
} finally {
|
||||||
|
setIsFetching(false)
|
||||||
|
}
|
||||||
|
}, [existingServers, provider, t, token])
|
||||||
|
|
||||||
|
const isFetchDisabled = !token
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DetailContainer>
|
||||||
|
<ProviderHeader>
|
||||||
|
<Flex className="items-center gap-2">
|
||||||
|
<ProviderName>{provider.name}</ProviderName>
|
||||||
|
{provider.discoverUrl && (
|
||||||
|
<Link target="_blank" href={provider.discoverUrl} style={{ display: 'flex' }}>
|
||||||
|
<Button type="text" size="small" icon={<SquareArrowOutUpRight size={14} />} />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Button type="primary" onClick={handleFetch} loading={isFetching} disabled={isFetchDisabled}>
|
||||||
|
{t('settings.mcp.fetch.button', 'Fetch Servers')}
|
||||||
|
</Button>
|
||||||
|
</ProviderHeader>
|
||||||
|
<Divider style={{ width: '100%', margin: '10px 0' }} />
|
||||||
|
<SettingSubtitle style={{ marginTop: 5 }}>{t('settings.provider.api_key.label')}</SettingSubtitle>
|
||||||
|
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
|
||||||
|
<Input.Password
|
||||||
|
value={token}
|
||||||
|
placeholder={t('settings.mcp.sync.tokenPlaceholder', 'Enter API token here')}
|
||||||
|
onChange={(e) => setToken(e.target.value)}
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</Space.Compact>
|
||||||
|
<SettingHelpTextRow>
|
||||||
|
<RowFlex>
|
||||||
|
{provider.apiKeyUrl && (
|
||||||
|
<SettingHelpLink target="_blank" href={provider.apiKeyUrl}>
|
||||||
|
{t('settings.provider.get_api_key')}
|
||||||
|
</SettingHelpLink>
|
||||||
|
)}
|
||||||
|
</RowFlex>
|
||||||
|
</SettingHelpTextRow>
|
||||||
|
|
||||||
|
{availableServers.length > 0 && (
|
||||||
|
<>
|
||||||
|
<SettingSubtitle style={{ marginTop: 20 }}>
|
||||||
|
{t('settings.mcp.available.servers', 'Available MCP Servers')}
|
||||||
|
</SettingSubtitle>
|
||||||
|
<ServerList>
|
||||||
|
{availableServers.map((server) => (
|
||||||
|
<ServerItem key={server.id}>
|
||||||
|
<ServerInfo>
|
||||||
|
<ServerName>{server.name}</ServerName>
|
||||||
|
<ServerDescription>{server.description}</ServerDescription>
|
||||||
|
</ServerInfo>
|
||||||
|
{(() => {
|
||||||
|
const isAlreadyAdded = existingServers.some(existing => existing.id === server.id)
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type={isAlreadyAdded ? 'default' : 'primary'}
|
||||||
|
size="small"
|
||||||
|
disabled={isAlreadyAdded}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isAlreadyAdded) {
|
||||||
|
addMCPServer(server)
|
||||||
|
window.toast.success(t('settings.mcp.server.added', 'MCP server added'))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isAlreadyAdded ? t('settings.mcp.server.added', 'Added') : t('settings.mcp.add.server', 'Add')}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</ServerItem>
|
||||||
|
))}
|
||||||
|
</ServerList>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DetailContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DetailContainer = styled.div`
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderHeader = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderName = styled.span`
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: -2px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerList = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerItem = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerInfo = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerName = styled.div`
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerDescription = styled.div`
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default McpProviderSettings
|
||||||
@ -1,39 +1,128 @@
|
|||||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined } from '@ant-design/icons'
|
||||||
import { Button } from '@cherrystudio/ui'
|
import { Button, DividerWithText, ListItem } from '@cherrystudio/ui'
|
||||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
import { RowFlex, Scrollbar } from '@cherrystudio/ui'
|
||||||
|
import Ai302ProviderLogo from '@renderer/assets/images/providers/302ai.webp'
|
||||||
|
import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png'
|
||||||
|
import LanyunProviderLogo from '@renderer/assets/images/providers/lanyun.png'
|
||||||
|
import ModelScopeProviderLogo from '@renderer/assets/images/providers/modelscope.png'
|
||||||
|
import TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.png'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
|
import { FolderCog, Package, ShoppingBag } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { Route, Routes, useLocation } from 'react-router'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { SettingContainer } from '..'
|
import { SettingContainer } from '..'
|
||||||
|
import BuiltinMCPServerList from './BuiltinMCPServerList'
|
||||||
import InstallNpxUv from './InstallNpxUv'
|
import InstallNpxUv from './InstallNpxUv'
|
||||||
|
import McpMarketList from './McpMarketList'
|
||||||
|
import ProviderDetail from './McpProviderSettings'
|
||||||
import McpServersList from './McpServersList'
|
import McpServersList from './McpServersList'
|
||||||
import McpSettings from './McpSettings'
|
import McpSettings from './McpSettings'
|
||||||
import NpxSearch from './NpxSearch'
|
import NpxSearch from './NpxSearch'
|
||||||
|
import { providers } from './providers/config'
|
||||||
|
|
||||||
const MCPSettings: FC = () => {
|
const MCPSettings: FC = () => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { mcpServers } = useMCPServers()
|
||||||
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const pathname = location.pathname
|
|
||||||
|
|
||||||
const isHome = pathname === '/settings/mcp'
|
// 获取当前激活的页面
|
||||||
|
const getActiveView = () => {
|
||||||
|
const path = location.pathname
|
||||||
|
|
||||||
|
// 精确匹配路径
|
||||||
|
if (path === '/settings/mcp/builtin') return 'builtin'
|
||||||
|
if (path === '/settings/mcp/marketplaces') return 'marketplaces'
|
||||||
|
|
||||||
|
// 检查是否是服务商页面 - 精确匹配
|
||||||
|
for (const provider of providers) {
|
||||||
|
if (path === `/settings/mcp/${provider.key}`) {
|
||||||
|
return provider.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他所有情况(包括 servers、settings/:serverId、npx-search、mcp-install)都属于 servers
|
||||||
|
return 'servers'
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeView = getActiveView()
|
||||||
|
|
||||||
|
// 判断是否为主页面(是否显示返回按钮)
|
||||||
|
const isHomePage = () => {
|
||||||
|
const path = location.pathname
|
||||||
|
// 主页面不显示返回按钮
|
||||||
|
if (path === '/settings/mcp' || path === '/settings/mcp/servers') return true
|
||||||
|
if (path === '/settings/mcp/builtin' || path === '/settings/mcp/marketplaces') return true
|
||||||
|
|
||||||
|
// 服务商页面也是主页面
|
||||||
|
return providers.some((p) => path === `/settings/mcp/${p.key}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider icons map
|
||||||
|
const providerIcons: Record<string, React.ReactNode> = {
|
||||||
|
modelscope: <ProviderIcon src={ModelScopeProviderLogo} alt="ModelScope" />,
|
||||||
|
tokenflux: <ProviderIcon src={TokenFluxProviderLogo} alt="TokenFlux" />,
|
||||||
|
lanyun: <ProviderIcon src={LanyunProviderLogo} alt="Lanyun" />,
|
||||||
|
'302ai': <ProviderIcon src={Ai302ProviderLogo} alt="302AI" />,
|
||||||
|
bailian: <ProviderIcon src={BailianProviderLogo} alt="Bailian" />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer theme={theme} style={{ padding: 0, position: 'relative' }}>
|
<Container>
|
||||||
{!isHome && (
|
|
||||||
<BackButtonContainer>
|
|
||||||
<Link to="/settings/mcp">
|
|
||||||
<Button variant="solid" startContent={<ArrowLeftOutlined />} radius="full" isIconOnly />
|
|
||||||
</Link>
|
|
||||||
</BackButtonContainer>
|
|
||||||
)}
|
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<ErrorBoundary>
|
<MenuList>
|
||||||
|
<DividerWithText text={t('settings.mcp.management', 'Management')} style={{ margin: '8px 0' }} />
|
||||||
|
<ListItem
|
||||||
|
title={t('settings.mcp.servers', 'MCP Servers')}
|
||||||
|
active={activeView === 'servers'}
|
||||||
|
onClick={() => navigate('/settings/mcp/servers')}
|
||||||
|
icon={<FolderCog size={16} />}
|
||||||
|
titleStyle={{ fontWeight: 500 }}
|
||||||
|
/>
|
||||||
|
<DividerWithText text={t('settings.mcp.discover', 'Discover')} style={{ margin: '16px 0 8px 0' }} />
|
||||||
|
<ListItem
|
||||||
|
title={t('settings.mcp.builtinServers', 'Built-in Servers')}
|
||||||
|
active={activeView === 'builtin'}
|
||||||
|
onClick={() => navigate('/settings/mcp/builtin')}
|
||||||
|
icon={<Package size={16} />}
|
||||||
|
titleStyle={{ fontWeight: 500 }}
|
||||||
|
/>
|
||||||
|
<ListItem
|
||||||
|
title={t('settings.mcp.marketplaces', 'Marketplaces')}
|
||||||
|
active={activeView === 'marketplaces'}
|
||||||
|
onClick={() => navigate('/settings/mcp/marketplaces')}
|
||||||
|
icon={<ShoppingBag size={16} />}
|
||||||
|
titleStyle={{ fontWeight: 500 }}
|
||||||
|
/>
|
||||||
|
<DividerWithText text={t('settings.mcp.providers', 'Providers')} style={{ margin: '16px 0 8px 0' }} />
|
||||||
|
{providers.map((provider) => (
|
||||||
|
<ListItem
|
||||||
|
key={provider.key}
|
||||||
|
title={provider.name}
|
||||||
|
active={activeView === provider.key}
|
||||||
|
onClick={() => navigate(`/settings/mcp/${provider.key}`)}
|
||||||
|
icon={providerIcons[provider.key] || <FolderCog size={16} />}
|
||||||
|
titleStyle={{ fontWeight: 500 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</MenuList>
|
||||||
|
<RightContainer>
|
||||||
|
{!isHomePage() && (
|
||||||
|
<BackButtonContainer>
|
||||||
|
<Link to="/settings/mcp/servers">
|
||||||
|
<Button variant="solid" startContent={<ArrowLeftOutlined />} radius="full" isIconOnly />
|
||||||
|
</Link>
|
||||||
|
</BackButtonContainer>
|
||||||
|
)}
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<McpServersList />} />
|
<Route index element={<Navigate to="servers" replace />} />
|
||||||
|
<Route path="servers" element={<McpServersList />} />
|
||||||
<Route path="settings/:serverId" element={<McpSettings />} />
|
<Route path="settings/:serverId" element={<McpSettings />} />
|
||||||
<Route
|
<Route
|
||||||
path="npx-search"
|
path="npx-search"
|
||||||
@ -51,13 +140,79 @@ const MCPSettings: FC = () => {
|
|||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="builtin"
|
||||||
|
element={
|
||||||
|
<ContentWrapper>
|
||||||
|
<BuiltinMCPServerList />
|
||||||
|
</ContentWrapper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="marketplaces"
|
||||||
|
element={
|
||||||
|
<ContentWrapper>
|
||||||
|
<McpMarketList />
|
||||||
|
</ContentWrapper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{providers.map((provider) => (
|
||||||
|
<Route
|
||||||
|
key={provider.key}
|
||||||
|
path={provider.key}
|
||||||
|
element={<ProviderDetail provider={provider} existingServers={mcpServers} />}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</Routes>
|
</Routes>
|
||||||
</ErrorBoundary>
|
</RightContainer>
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
</SettingContainer>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Container = styled(RowFlex)`
|
||||||
|
flex: 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
const MainContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - var(--navbar-height) - 6px);
|
||||||
|
overflow: hidden;
|
||||||
|
`
|
||||||
|
|
||||||
|
const MenuList = styled(Scrollbar)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
width: var(--settings-width);
|
||||||
|
padding: 12px;
|
||||||
|
padding-bottom: 48px;
|
||||||
|
border-right: 0.5px solid var(--color-border);
|
||||||
|
height: calc(100vh - var(--navbar-height));
|
||||||
|
`
|
||||||
|
|
||||||
|
const RightContainer = styled(Scrollbar)`
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderIcon = styled.img`
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
`
|
||||||
|
|
||||||
|
const ContentWrapper = styled.div`
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
|
`
|
||||||
|
|
||||||
const BackButtonContainer = styled.div`
|
const BackButtonContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -70,10 +225,4 @@ const BackButtonContainer = styled.div`
|
|||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
`
|
`
|
||||||
|
|
||||||
const MainContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
width: 100%;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default MCPSettings
|
export default MCPSettings
|
||||||
|
|||||||
@ -0,0 +1,76 @@
|
|||||||
|
import { getAI302Token, saveAI302Token, syncAi302Servers } from './302ai'
|
||||||
|
import { getBailianToken, saveBailianToken, syncBailianServers } from './bailian'
|
||||||
|
import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './lanyun'
|
||||||
|
import { getModelScopeToken, MODELSCOPE_HOST, saveModelScopeToken, syncModelScopeServers } from './modelscope'
|
||||||
|
import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './tokenflux'
|
||||||
|
import type { MCPServer } from '@renderer/types'
|
||||||
|
|
||||||
|
export interface ProviderConfig {
|
||||||
|
key: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
discoverUrl: string
|
||||||
|
apiKeyUrl: string
|
||||||
|
tokenFieldName: string
|
||||||
|
getToken: () => string | null
|
||||||
|
saveToken: (token: string) => void
|
||||||
|
syncServers: (token: string, existingServers: MCPServer[]) => Promise<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const providers: ProviderConfig[] = [
|
||||||
|
{
|
||||||
|
key: 'modelscope',
|
||||||
|
name: 'ModelScope',
|
||||||
|
description: 'ModelScope 平台 MCP 服务',
|
||||||
|
discoverUrl: `${MODELSCOPE_HOST}/mcp?hosted=1&page=1`,
|
||||||
|
apiKeyUrl: `${MODELSCOPE_HOST}/my/myaccesstoken`,
|
||||||
|
tokenFieldName: 'modelScopeToken',
|
||||||
|
getToken: getModelScopeToken,
|
||||||
|
saveToken: saveModelScopeToken,
|
||||||
|
syncServers: syncModelScopeServers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tokenflux',
|
||||||
|
name: 'TokenFlux',
|
||||||
|
description: 'TokenFlux 平台 MCP 服务',
|
||||||
|
discoverUrl: `${TOKENFLUX_HOST}/mcps`,
|
||||||
|
apiKeyUrl: `${TOKENFLUX_HOST}/dashboard/api-keys`,
|
||||||
|
tokenFieldName: 'tokenfluxToken',
|
||||||
|
getToken: getTokenFluxToken,
|
||||||
|
saveToken: saveTokenFluxToken,
|
||||||
|
syncServers: syncTokenFluxServers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lanyun',
|
||||||
|
name: '蓝耘科技',
|
||||||
|
description: '蓝耘科技云平台 MCP 服务',
|
||||||
|
discoverUrl: 'https://mcp.lanyun.net',
|
||||||
|
apiKeyUrl: LANYUN_KEY_HOST,
|
||||||
|
tokenFieldName: 'tokenLanyunToken',
|
||||||
|
getToken: getTokenLanYunToken,
|
||||||
|
saveToken: saveTokenLanYunToken,
|
||||||
|
syncServers: syncTokenLanYunServers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '302ai',
|
||||||
|
name: '302.AI',
|
||||||
|
description: '302.AI 平台 MCP 服务',
|
||||||
|
discoverUrl: 'https://302.ai',
|
||||||
|
apiKeyUrl: 'https://dash.302.ai/apis/list',
|
||||||
|
tokenFieldName: 'token302aiToken',
|
||||||
|
getToken: getAI302Token,
|
||||||
|
saveToken: saveAI302Token,
|
||||||
|
syncServers: syncAi302Servers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bailian',
|
||||||
|
name: '阿里云百炼',
|
||||||
|
description: '百炼平台服务',
|
||||||
|
discoverUrl: `https://bailian.console.aliyun.com/?tab=mcp#/mcp-market`,
|
||||||
|
apiKeyUrl: `https://bailian.console.aliyun.com/?tab=app#/api-key`,
|
||||||
|
tokenFieldName: 'bailianToken',
|
||||||
|
getToken: getBailianToken,
|
||||||
|
saveToken: saveBailianToken,
|
||||||
|
syncServers: syncBailianServers
|
||||||
|
}
|
||||||
|
]
|
||||||
51
src/renderer/src/pages/settings/MCPSettings/utils.ts
Normal file
51
src/renderer/src/pages/settings/MCPSettings/utils.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* MCP Settings 页面导航工具函数
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const MCPRoutes = {
|
||||||
|
// 管理类页面
|
||||||
|
servers: '/settings/mcp/servers',
|
||||||
|
npxSearch: '/settings/mcp/npx-search',
|
||||||
|
mcpInstall: '/settings/mcp/mcp-install',
|
||||||
|
|
||||||
|
// 发现类页面
|
||||||
|
builtin: '/settings/mcp/builtin',
|
||||||
|
marketplaces: '/settings/mcp/marketplaces',
|
||||||
|
|
||||||
|
// 服务商页面
|
||||||
|
modelscope: '/settings/mcp/modelscope',
|
||||||
|
tokenflux: '/settings/mcp/tokenflux',
|
||||||
|
lanyun: '/settings/mcp/lanyun',
|
||||||
|
'302ai': '/settings/mcp/302ai',
|
||||||
|
bailian: '/settings/mcp/bailian',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导航到 MCP 设置页面的指定部分
|
||||||
|
* @param page 页面标识
|
||||||
|
* @returns 路由路径
|
||||||
|
*/
|
||||||
|
export function getMCPRoute(page: keyof typeof MCPRoutes): string {
|
||||||
|
return MCPRoutes[page]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 MCP 服务商页面路由
|
||||||
|
* @param providerKey 服务商标识
|
||||||
|
* @returns 路由路径
|
||||||
|
*/
|
||||||
|
export function getMCPProviderRoute(providerKey: string): string {
|
||||||
|
return `/settings/mcp/${providerKey}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 MCP 服务器设置页面路由
|
||||||
|
* @param serverId 服务器ID
|
||||||
|
* @returns 路由路径
|
||||||
|
*/
|
||||||
|
export function getMCPServerSettingsRoute(serverId: string): string {
|
||||||
|
return `/settings/mcp/settings/${serverId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型定义
|
||||||
|
export type MCPPage = keyof typeof MCPRoutes
|
||||||
Loading…
Reference in New Issue
Block a user