mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 23:59:45 +08:00
feat(MCPSettings): enhance MCP server management and localization
- Added BuiltinMCPServersSection and McpResourcesSection components to display available MCP servers and resources. - Updated navigation logic to redirect users to the MCP settings upon adding a server. - Enhanced localization by adding new keys for built-in servers in multiple languages. - Improved the SettingsPage layout by reordering menu items for better accessibility.
This commit is contained in:
parent
daf134f331
commit
186f0ed06f
@ -44,7 +44,9 @@ export function handleMcpProtocolUrl(url: URL) {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// cherrystudio://mcp/install?servers={base64Encode(JSON.stringify(jsonConfig))}
|
// cherrystudio://mcp/install?servers={base64Encode(JSON.stringify(jsonConfig))}
|
||||||
|
|
||||||
const data = params.get('servers')
|
const data = params.get('servers')
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const stringify = Buffer.from(data, 'base64').toString('utf8')
|
const stringify = Buffer.from(data, 'base64').toString('utf8')
|
||||||
Logger.info('install MCP servers from urlschema: ', stringify)
|
Logger.info('install MCP servers from urlschema: ', stringify)
|
||||||
@ -63,10 +65,8 @@ export function handleMcpProtocolUrl(url: URL) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainWindow = windowService.getMainWindow()
|
windowService.getMainWindow()?.show()
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
||||||
mainWindow.webContents.executeJavaScript("window.navigate('/settings/mcp')")
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit'
|
import { createSelector } from '@reduxjs/toolkit'
|
||||||
|
import NavigationService from '@renderer/services/NavigationService'
|
||||||
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import { addMCPServer, deleteMCPServer, setMCPServers, updateMCPServer } from '@renderer/store/mcp'
|
import { addMCPServer, deleteMCPServer, setMCPServers, updateMCPServer } from '@renderer/store/mcp'
|
||||||
import { MCPServer } from '@renderer/types'
|
import { MCPServer } from '@renderer/types'
|
||||||
@ -8,8 +9,11 @@ import { IpcChannel } from '@shared/IpcChannel'
|
|||||||
window.electron.ipcRenderer.on(IpcChannel.Mcp_ServersChanged, (_event, servers) => {
|
window.electron.ipcRenderer.on(IpcChannel.Mcp_ServersChanged, (_event, servers) => {
|
||||||
store.dispatch(setMCPServers(servers))
|
store.dispatch(setMCPServers(servers))
|
||||||
})
|
})
|
||||||
|
|
||||||
window.electron.ipcRenderer.on(IpcChannel.Mcp_AddServer, (_event, server: MCPServer) => {
|
window.electron.ipcRenderer.on(IpcChannel.Mcp_AddServer, (_event, server: MCPServer) => {
|
||||||
store.dispatch(addMCPServer(server))
|
store.dispatch(addMCPServer(server))
|
||||||
|
NavigationService.navigate?.('/settings/mcp')
|
||||||
|
NavigationService.navigate?.('/settings/mcp/settings', { state: { server } })
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectMcpServers = (state) => state.mcp.servers
|
const selectMcpServers = (state) => state.mcp.servers
|
||||||
|
|||||||
@ -1870,7 +1870,20 @@
|
|||||||
"updateError": "Failed to update server",
|
"updateError": "Failed to update server",
|
||||||
"updateSuccess": "Server updated successfully",
|
"updateSuccess": "Server updated successfully",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"user": "User"
|
"user": "User",
|
||||||
|
"requiresConfig": "Requires Configuration",
|
||||||
|
"builtinServers": "Builtin Servers",
|
||||||
|
"more": {
|
||||||
|
"modelscope": "ModelScope Community MCP Server",
|
||||||
|
"higress": "Higress MCP Server",
|
||||||
|
"mcpso": "MCP Server Discovery Platform",
|
||||||
|
"smithery": "Smithery MCP Tools",
|
||||||
|
"glama": "Glama MCP Server Directory",
|
||||||
|
"pulsemcp": "Pulse MCP Server",
|
||||||
|
"composio": "Composio MCP Development Tools",
|
||||||
|
"official": "Official MCP Server Collection",
|
||||||
|
"awesome": "Curated MCP Server List"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"messages.divider": "Show divider between messages",
|
"messages.divider": "Show divider between messages",
|
||||||
"messages.divider.tooltip": "Not applicable to bubble-style message",
|
"messages.divider.tooltip": "Not applicable to bubble-style message",
|
||||||
|
|||||||
@ -1870,7 +1870,20 @@
|
|||||||
"updateError": "サーバーの更新に失敗しました",
|
"updateError": "サーバーの更新に失敗しました",
|
||||||
"updateSuccess": "サーバーが正常に更新されました",
|
"updateSuccess": "サーバーが正常に更新されました",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"user": "ユーザー"
|
"user": "ユーザー",
|
||||||
|
"requiresConfig": "設定が必要",
|
||||||
|
"builtinServers": "組み込みサーバー",
|
||||||
|
"more": {
|
||||||
|
"modelscope": "魔搭コミュニティ MCP サーバー",
|
||||||
|
"higress": "Higress MCP サーバー",
|
||||||
|
"mcpso": "MCP サーバー発見プラットフォーム",
|
||||||
|
"smithery": "Smithery MCP ツール",
|
||||||
|
"glama": "Glama MCP サーバーディレクトリ",
|
||||||
|
"pulsemcp": "Pulse MCP サーバー",
|
||||||
|
"composio": "Composio MCP 開発ツール",
|
||||||
|
"official": "公式 MCP サーバーコレクション",
|
||||||
|
"awesome": "厳選された MCP サーバーリスト"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"messages.divider": "メッセージ間に区切り線を表示",
|
"messages.divider": "メッセージ間に区切り線を表示",
|
||||||
"messages.divider.tooltip": "バブルスタイルのメッセージには適用されません",
|
"messages.divider.tooltip": "バブルスタイルのメッセージには適用されません",
|
||||||
|
|||||||
@ -1870,7 +1870,20 @@
|
|||||||
"updateError": "Ошибка обновления сервера",
|
"updateError": "Ошибка обновления сервера",
|
||||||
"updateSuccess": "Сервер успешно обновлен",
|
"updateSuccess": "Сервер успешно обновлен",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"user": "Пользователь"
|
"user": "Пользователь",
|
||||||
|
"requiresConfig": "Требуется настройка",
|
||||||
|
"builtinServers": "Встроенные серверы",
|
||||||
|
"more": {
|
||||||
|
"modelscope": "Сервер MCP сообщества ModelScope",
|
||||||
|
"higress": "Сервер Higress MCP",
|
||||||
|
"mcpso": "Платформа поиска серверов MCP",
|
||||||
|
"smithery": "Инструменты Smithery MCP",
|
||||||
|
"glama": "Каталог серверов Glama MCP",
|
||||||
|
"pulsemcp": "Сервер Pulse MCP",
|
||||||
|
"composio": "Инструменты разработки Composio MCP",
|
||||||
|
"official": "Официальная коллекция серверов MCP",
|
||||||
|
"awesome": "Кураторский список серверов MCP"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"messages.divider": "Показывать разделитель между сообщениями",
|
"messages.divider": "Показывать разделитель между сообщениями",
|
||||||
"messages.divider.tooltip": "Не применимо к сообщениям в стиле пузырей",
|
"messages.divider.tooltip": "Не применимо к сообщениям в стиле пузырей",
|
||||||
|
|||||||
@ -1870,7 +1870,20 @@
|
|||||||
"updateError": "更新服务器失败",
|
"updateError": "更新服务器失败",
|
||||||
"updateSuccess": "服务器更新成功",
|
"updateSuccess": "服务器更新成功",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"user": "用户"
|
"user": "用户",
|
||||||
|
"requiresConfig": "需要配置",
|
||||||
|
"builtinServers": "内置服务器",
|
||||||
|
"more": {
|
||||||
|
"modelscope": "魔搭社区 MCP 服务器",
|
||||||
|
"higress": "Higress MCP 服务器",
|
||||||
|
"mcpso": "MCP 服务器发现平台",
|
||||||
|
"smithery": "Smithery MCP 工具",
|
||||||
|
"glama": "Glama MCP 服务器目录",
|
||||||
|
"pulsemcp": "Pulse MCP 服务器",
|
||||||
|
"composio": "Composio MCP 开发工具",
|
||||||
|
"official": "官方 MCP 服务器集合",
|
||||||
|
"awesome": "精选的 MCP 服务器列表"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"messages.divider": "消息分割线",
|
"messages.divider": "消息分割线",
|
||||||
"messages.divider.tooltip": "不适用于气泡样式消息",
|
"messages.divider.tooltip": "不适用于气泡样式消息",
|
||||||
|
|||||||
@ -1870,7 +1870,20 @@
|
|||||||
"updateError": "更新伺服器失敗",
|
"updateError": "更新伺服器失敗",
|
||||||
"updateSuccess": "伺服器更新成功",
|
"updateSuccess": "伺服器更新成功",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"user": "用戶"
|
"user": "用戶",
|
||||||
|
"requiresConfig": "需要配置",
|
||||||
|
"builtinServers": "內置伺服器",
|
||||||
|
"more": {
|
||||||
|
"modelscope": "魔搭社區 MCP 伺服器",
|
||||||
|
"higress": "Higress MCP 伺服器",
|
||||||
|
"mcpso": "MCP 伺服器發現平台",
|
||||||
|
"smithery": "Smithery MCP 工具",
|
||||||
|
"glama": "Glama MCP 伺服器目錄",
|
||||||
|
"pulsemcp": "Pulse MCP 伺服器",
|
||||||
|
"composio": "Composio MCP 開發工具",
|
||||||
|
"official": "官方 MCP 伺服器集合",
|
||||||
|
"awesome": "精選的 MCP 伺服器清單"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"messages.divider": "訊息間顯示分隔線",
|
"messages.divider": "訊息間顯示分隔線",
|
||||||
"messages.divider.tooltip": "不適用於氣泡樣式消息",
|
"messages.divider.tooltip": "不適用於氣泡樣式消息",
|
||||||
|
|||||||
@ -126,6 +126,7 @@ const Container = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-bottom: 8px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const UserWrap = styled.div`
|
const UserWrap = styled.div`
|
||||||
|
|||||||
@ -0,0 +1,170 @@
|
|||||||
|
import { CheckOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
|
import { builtinMCPServers } from '@renderer/store/mcp'
|
||||||
|
import { Button, Popover, Tag } from 'antd'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import { SettingTitle } from '..'
|
||||||
|
|
||||||
|
const BuiltinMCPServersSection: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { addMCPServer, mcpServers } = useMCPServers()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingTitle style={{ gap: 3 }}>{t('settings.mcp.builtinServers')}</SettingTitle>
|
||||||
|
<ServersGrid>
|
||||||
|
{builtinMCPServers.map((server) => {
|
||||||
|
const isInstalled = mcpServers.some((existingServer) => existingServer.name === server.name)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServerCard key={server.id}>
|
||||||
|
<ServerHeader>
|
||||||
|
<ServerName>
|
||||||
|
<ServerNameText>{server.name}</ServerNameText>
|
||||||
|
</ServerName>
|
||||||
|
<StatusIndicator>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={isInstalled ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <PlusOutlined />}
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
if (isInstalled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addMCPServer(server)
|
||||||
|
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-builtin-server' })
|
||||||
|
}}
|
||||||
|
disabled={isInstalled}
|
||||||
|
/>
|
||||||
|
</StatusIndicator>
|
||||||
|
</ServerHeader>
|
||||||
|
<Popover
|
||||||
|
content={<PopoverContent>{server.description}</PopoverContent>}
|
||||||
|
title={server.name}
|
||||||
|
trigger="hover"
|
||||||
|
placement="topLeft"
|
||||||
|
overlayStyle={{ maxWidth: 400 }}>
|
||||||
|
<ServerDescription>
|
||||||
|
{server.description}
|
||||||
|
<MoreIndicator>...</MoreIndicator>
|
||||||
|
</ServerDescription>
|
||||||
|
</Popover>
|
||||||
|
<ServerFooter>
|
||||||
|
<Tag color="processing" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>
|
||||||
|
{t(`settings.mcp.types.${server.type || 'stdio'}`)}
|
||||||
|
</Tag>
|
||||||
|
{server.env && Object.keys(server.env).length > 0 && (
|
||||||
|
<Tag color="warning" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>
|
||||||
|
{t('settings.mcp.requiresConfig')}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</ServerFooter>
|
||||||
|
</ServerCard>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ServersGrid>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServersGrid = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerCard = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 0.5px solid var(--color-border);
|
||||||
|
border-radius: var(--list-item-border-radius);
|
||||||
|
padding: 10px 16px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
height: 125px;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerHeader = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerName = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerNameText = styled.span`
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
`
|
||||||
|
|
||||||
|
const StatusIndicator = styled.div`
|
||||||
|
margin-left: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerDescription = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
width: 100%;
|
||||||
|
word-break: break-word;
|
||||||
|
height: 50px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const MoreIndicator = styled.span`
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--color-background);
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
padding-left: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const PopoverContent = styled.div`
|
||||||
|
max-width: 350px;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ServerFooter = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-top: 10px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default BuiltinMCPServersSection
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
import { ExternalLink } from 'lucide-react'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
import { SettingTitle } from '..'
|
||||||
|
|
||||||
|
const mcpResources = [
|
||||||
|
{
|
||||||
|
name: 'modelscope.cn',
|
||||||
|
url: 'https://www.modelscope.cn/mcp',
|
||||||
|
logo: 'https://g.alicdn.com/sail-web/maas/2.7.35/favicon/128.ico',
|
||||||
|
descriptionKey: 'settings.mcp.more.modelscope'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mcp.higress.ai',
|
||||||
|
url: 'https://mcp.higress.ai/',
|
||||||
|
logo: 'https://framerusercontent.com/images/FD5yBobiBj4Evn0qf11X7iQ9csk.png',
|
||||||
|
descriptionKey: 'settings.mcp.more.higress'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mcp.so',
|
||||||
|
url: 'https://mcp.so/',
|
||||||
|
logo: 'https://mcp.so/favicon.ico',
|
||||||
|
descriptionKey: 'settings.mcp.more.mcpso'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'smithery.ai',
|
||||||
|
url: 'https://smithery.ai/',
|
||||||
|
logo: 'https://smithery.ai/logo.svg',
|
||||||
|
descriptionKey: 'settings.mcp.more.smithery'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'glama.ai',
|
||||||
|
url: 'https://glama.ai/mcp/servers',
|
||||||
|
logo: 'https://glama.ai/favicon.ico',
|
||||||
|
descriptionKey: 'settings.mcp.more.glama'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pulsemcp.com',
|
||||||
|
url: 'https://www.pulsemcp.com',
|
||||||
|
logo: 'https://www.pulsemcp.com/favicon.svg',
|
||||||
|
descriptionKey: 'settings.mcp.more.pulsemcp'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mcp.composio.dev',
|
||||||
|
url: 'https://mcp.composio.dev/',
|
||||||
|
logo: 'https://composio.dev/wp-content/uploads/2025/02/Fevicon-composio.png',
|
||||||
|
descriptionKey: 'settings.mcp.more.composio'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Model Context Protocol Servers',
|
||||||
|
url: 'https://github.com/modelcontextprotocol/servers',
|
||||||
|
logo: 'https://avatars.githubusercontent.com/u/182288589',
|
||||||
|
descriptionKey: 'settings.mcp.more.official'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Awesome MCP Servers',
|
||||||
|
url: 'https://github.com/punkpeye/awesome-mcp-servers',
|
||||||
|
logo: 'https://github.githubassets.com/assets/github-logo-55c5b9a1fe52.png',
|
||||||
|
descriptionKey: 'settings.mcp.more.awesome'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const McpResourcesSection: FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingTitle style={{ gap: 3 }}>{t('settings.mcp.findMore')}</SettingTitle>
|
||||||
|
<ResourcesGrid>
|
||||||
|
{mcpResources.map((resource) => (
|
||||||
|
<ResourceCard key={resource.name} onClick={() => window.open(resource.url, '_blank', 'noopener,noreferrer')}>
|
||||||
|
<ResourceHeader>
|
||||||
|
<ResourceLogo src={resource.logo} alt={`${resource.name} logo`} />
|
||||||
|
<ResourceName>{resource.name}</ResourceName>
|
||||||
|
<ExternalLinkIcon>
|
||||||
|
<ExternalLink size={14} />
|
||||||
|
</ExternalLinkIcon>
|
||||||
|
</ResourceHeader>
|
||||||
|
<ResourceDescription>{t(resource.descriptionKey)}</ResourceDescription>
|
||||||
|
</ResourceCard>
|
||||||
|
))}
|
||||||
|
</ResourcesGrid>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResourcesGrid = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ResourceCard = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 0.5px solid var(--color-border);
|
||||||
|
border-radius: var(--list-item-border-radius);
|
||||||
|
padding: 12px 16px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
cursor: pointer;
|
||||||
|
height: 80px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ResourceHeader = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ResourceLogo = styled.img`
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
margin-right: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ResourceName = styled.span`
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ExternalLinkIcon = styled.div`
|
||||||
|
color: var(--color-text-3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ResourceDescription = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
line-height: 1.4;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default McpResourcesSection
|
||||||
@ -14,7 +14,9 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
import { SettingTitle } from '..'
|
import { SettingTitle } from '..'
|
||||||
import AddMcpServerModal from './AddMcpServerModal'
|
import AddMcpServerModal from './AddMcpServerModal'
|
||||||
|
import BuiltinMCPServersSection from './BuiltinMCPServersSection'
|
||||||
import EditMcpJsonPopup from './EditMcpJsonPopup'
|
import EditMcpJsonPopup from './EditMcpJsonPopup'
|
||||||
|
import McpResourcesSection from './McpResourcesSection'
|
||||||
import SyncServersPopup from './SyncServersPopup'
|
import SyncServersPopup from './SyncServersPopup'
|
||||||
|
|
||||||
const McpServersList: FC = () => {
|
const McpServersList: FC = () => {
|
||||||
@ -179,6 +181,10 @@ const McpServersList: FC = () => {
|
|||||||
style={{ marginTop: 20 }}
|
style={{ marginTop: 20 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<McpResourcesSection />
|
||||||
|
<BuiltinMCPServersSection />
|
||||||
|
|
||||||
<AddMcpServerModal
|
<AddMcpServerModal
|
||||||
visible={isAddModalVisible}
|
visible={isAddModalVisible}
|
||||||
onClose={() => setIsAddModalVisible(false)}
|
onClose={() => setIsAddModalVisible(false)}
|
||||||
@ -295,6 +301,7 @@ const ServerFooter = styled.div`
|
|||||||
|
|
||||||
const ButtonGroup = styled.div`
|
const ButtonGroup = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@ -2,77 +2,17 @@ import { NavbarRight } from '@renderer/components/app/Navbar'
|
|||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { isLinux, isWin } from '@renderer/config/constant'
|
import { isLinux, isWin } from '@renderer/config/constant'
|
||||||
import { useFullscreen } from '@renderer/hooks/useFullscreen'
|
import { useFullscreen } from '@renderer/hooks/useFullscreen'
|
||||||
import { Button, Dropdown, Menu, type MenuProps } from 'antd'
|
import { Button } from 'antd'
|
||||||
import { ChevronDown, Search } from 'lucide-react'
|
import { Search } from 'lucide-react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
import InstallNpxUv from './InstallNpxUv'
|
import InstallNpxUv from './InstallNpxUv'
|
||||||
|
|
||||||
const mcpResources = [
|
|
||||||
{
|
|
||||||
name: 'Model Context Protocol Servers',
|
|
||||||
url: 'https://github.com/modelcontextprotocol/servers',
|
|
||||||
logo: 'https://avatars.githubusercontent.com/u/182288589'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Awesome MCP Servers',
|
|
||||||
url: 'https://github.com/punkpeye/awesome-mcp-servers',
|
|
||||||
logo: 'https://github.githubassets.com/assets/github-logo-55c5b9a1fe52.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mcp.so',
|
|
||||||
url: 'https://mcp.so/',
|
|
||||||
logo: 'https://mcp.so/favicon.ico'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'modelscope.cn',
|
|
||||||
url: 'https://www.modelscope.cn/mcp',
|
|
||||||
logo: 'https://g.alicdn.com/sail-web/maas/2.7.35/favicon/128.ico'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mcp.higress.ai',
|
|
||||||
url: 'https://mcp.higress.ai/',
|
|
||||||
logo: 'https://framerusercontent.com/images/FD5yBobiBj4Evn0qf11X7iQ9csk.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'smithery.ai',
|
|
||||||
url: 'https://smithery.ai/',
|
|
||||||
logo: 'https://smithery.ai/logo.svg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'glama.ai',
|
|
||||||
url: 'https://glama.ai/mcp/servers',
|
|
||||||
logo: 'https://glama.ai/favicon.ico'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pulsemcp.com',
|
|
||||||
url: 'https://www.pulsemcp.com',
|
|
||||||
logo: 'https://www.pulsemcp.com/favicon.svg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mcp.composio.dev',
|
|
||||||
url: 'https://mcp.composio.dev/',
|
|
||||||
logo: 'https://composio.dev/wp-content/uploads/2025/02/Fevicon-composio.png'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export const McpSettingsNavbar = () => {
|
export const McpSettingsNavbar = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const resourceMenuItems: MenuProps['items'] = mcpResources.map(({ name, url, logo }) => ({
|
|
||||||
key: name,
|
|
||||||
label: (
|
|
||||||
<Menu.Item
|
|
||||||
onClick={() => window.open(url, '_blank', 'noopener,noreferrer')}
|
|
||||||
style={{ backgroundColor: 'transparent' }}
|
|
||||||
icon={<img src={logo} alt={name} style={{ width: 20, height: 20, borderRadius: 5, marginRight: 10 }} />}>
|
|
||||||
{name}
|
|
||||||
</Menu.Item>
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavbarRight style={{ paddingRight: useFullscreen() ? '12px' : isWin ? 150 : isLinux ? 120 : 12 }}>
|
<NavbarRight style={{ paddingRight: useFullscreen() ? '12px' : isWin ? 150 : isLinux ? 120 : 12 }}>
|
||||||
<HStack alignItems="center" gap={5}>
|
<HStack alignItems="center" gap={5}>
|
||||||
@ -85,16 +25,6 @@ export const McpSettingsNavbar = () => {
|
|||||||
style={{ fontSize: 13, height: 28, borderRadius: 20 }}>
|
style={{ fontSize: 13, height: 28, borderRadius: 20 }}>
|
||||||
{t('settings.mcp.searchNpx')}
|
{t('settings.mcp.searchNpx')}
|
||||||
</Button>
|
</Button>
|
||||||
<Dropdown menu={{ items: resourceMenuItems }} trigger={['click']}>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
type="text"
|
|
||||||
className="nodrag"
|
|
||||||
style={{ fontSize: 13, height: 28, borderRadius: 20, display: 'flex', alignItems: 'center' }}>
|
|
||||||
{t('settings.mcp.findMore')}
|
|
||||||
<ChevronDown size={16} />
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
|
||||||
<InstallNpxUv mini />
|
<InstallNpxUv mini />
|
||||||
</HStack>
|
</HStack>
|
||||||
</NavbarRight>
|
</NavbarRight>
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { nanoid } from '@reduxjs/toolkit'
|
|||||||
import logo from '@renderer/assets/images/cherry-text-logo.svg'
|
import logo from '@renderer/assets/images/cherry-text-logo.svg'
|
||||||
import { Center, HStack } from '@renderer/components/Layout'
|
import { Center, HStack } from '@renderer/components/Layout'
|
||||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
import { builtinMCPServers } from '@renderer/store/mcp'
|
|
||||||
import { MCPServer } from '@renderer/types'
|
import { MCPServer } from '@renderer/types'
|
||||||
import { getMcpConfigSampleFromReadme } from '@renderer/utils'
|
import { getMcpConfigSampleFromReadme } from '@renderer/utils'
|
||||||
import { Button, Card, Flex, Input, Space, Spin, Tag, Typography } from 'antd'
|
import { Button, Card, Flex, Input, Space, Spin, Tag, Typography } from 'antd'
|
||||||
@ -23,7 +22,7 @@ interface SearchResult {
|
|||||||
configSample?: MCPServer['configSample']
|
configSample?: MCPServer['configSample']
|
||||||
}
|
}
|
||||||
|
|
||||||
const npmScopes = ['@cherry', '@modelcontextprotocol', '@gongrzhe', '@mcpmarket']
|
const npmScopes = ['@modelcontextprotocol', '@gongrzhe', '@mcpmarket']
|
||||||
|
|
||||||
let _searchResults: SearchResult[] = []
|
let _searchResults: SearchResult[] = []
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ const NpxSearch: FC = () => {
|
|||||||
const { Text, Link } = Typography
|
const { Text, Link } = Typography
|
||||||
|
|
||||||
// Add new state variables for npm scope search
|
// Add new state variables for npm scope search
|
||||||
const [npmScope, setNpmScope] = useState('@cherry')
|
const [npmScope, setNpmScope] = useState('@modelcontextprotocol')
|
||||||
const [searchLoading, setSearchLoading] = useState(false)
|
const [searchLoading, setSearchLoading] = useState(false)
|
||||||
const [searchResults, setSearchResults] = useState<SearchResult[]>(_searchResults)
|
const [searchResults, setSearchResults] = useState<SearchResult[]>(_searchResults)
|
||||||
const { addMCPServer, mcpServers } = useMCPServers()
|
const { addMCPServer, mcpServers } = useMCPServers()
|
||||||
@ -52,22 +51,6 @@ const NpxSearch: FC = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchScope === '@cherry') {
|
|
||||||
setSearchResults(
|
|
||||||
builtinMCPServers.map((server) => ({
|
|
||||||
key: server.id,
|
|
||||||
name: server.name,
|
|
||||||
description: server.description || '',
|
|
||||||
version: '1.0.0',
|
|
||||||
usage: '参考下方链接中的使用说明',
|
|
||||||
npmLink: 'https://docs.cherry-ai.com/advanced-basic/mcp/in-memory',
|
|
||||||
fullName: server.name,
|
|
||||||
type: server.type || 'inMemory'
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchLoading(true)
|
setSearchLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -190,14 +173,6 @@ const NpxSearch: FC = () => {
|
|||||||
return
|
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' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const newServer = {
|
const newServer = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
name: record.name,
|
name: record.name,
|
||||||
|
|||||||
@ -71,18 +71,18 @@ const SettingsPage: FC = () => {
|
|||||||
{t('settings.display.title')}
|
{t('settings.display.title')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/tool">
|
|
||||||
<MenuItem className={isRoute('/settings/tool')}>
|
|
||||||
<PencilRuler size={18} />
|
|
||||||
{t('settings.tool.title')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuItemLink>
|
|
||||||
<MenuItemLink to="/settings/mcp">
|
<MenuItemLink to="/settings/mcp">
|
||||||
<MenuItem className={isRoute('/settings/mcp')}>
|
<MenuItem className={isRoute('/settings/mcp')}>
|
||||||
<SquareTerminal size={18} />
|
<SquareTerminal size={18} />
|
||||||
{t('settings.mcp.title')}
|
{t('settings.mcp.title')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
|
<MenuItemLink to="/settings/tool">
|
||||||
|
<MenuItem className={isRoute('/settings/tool')}>
|
||||||
|
<PencilRuler size={18} />
|
||||||
|
{t('settings.tool.title')}
|
||||||
|
</MenuItem>
|
||||||
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/shortcut">
|
<MenuItemLink to="/settings/shortcut">
|
||||||
<MenuItem className={isRoute('/settings/shortcut')}>
|
<MenuItem className={isRoute('/settings/shortcut')}>
|
||||||
<Command size={18} />
|
<Command size={18} />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user