mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-31 16:49:07 +08:00
✨ feat: add MCP server version display with badges (#8097)
This commit is contained in:
parent
38cf3869bc
commit
16ca373c55
@ -76,6 +76,7 @@ export enum IpcChannel {
|
||||
Mcp_CheckConnectivity = 'mcp:check-connectivity',
|
||||
Mcp_SetProgress = 'mcp:set-progress',
|
||||
Mcp_AbortTool = 'mcp:abort-tool',
|
||||
Mcp_GetServerVersion = 'mcp:get-server-version',
|
||||
|
||||
// Python
|
||||
Python_Execute = 'python:execute',
|
||||
|
||||
@ -503,6 +503,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
ipcMain.handle(IpcChannel.Mcp_GetInstallInfo, mcpService.getInstallInfo)
|
||||
ipcMain.handle(IpcChannel.Mcp_CheckConnectivity, mcpService.checkMcpConnectivity)
|
||||
ipcMain.handle(IpcChannel.Mcp_AbortTool, mcpService.abortTool)
|
||||
ipcMain.handle(IpcChannel.Mcp_GetServerVersion, mcpService.getServerVersion)
|
||||
ipcMain.handle(IpcChannel.Mcp_SetProgress, (_, progress: number) => {
|
||||
mainWindow.webContents.send('mcp-progress', progress)
|
||||
})
|
||||
|
||||
@ -88,6 +88,7 @@ class McpService {
|
||||
this.stopServer = this.stopServer.bind(this)
|
||||
this.abortTool = this.abortTool.bind(this)
|
||||
this.cleanup = this.cleanup.bind(this)
|
||||
this.getServerVersion = this.getServerVersion.bind(this)
|
||||
}
|
||||
|
||||
private getServerKey(server: MCPServer): string {
|
||||
@ -692,6 +693,31 @@ class McpService {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server version information
|
||||
*/
|
||||
public async getServerVersion(_: Electron.IpcMainInvokeEvent, server: MCPServer): Promise<string | null> {
|
||||
try {
|
||||
Logger.info(`[MCP] Getting server version for: ${server.name}`)
|
||||
const client = await this.initClient(server)
|
||||
|
||||
// Try to get server information which may include version
|
||||
const serverInfo = client.getServerVersion()
|
||||
Logger.info(`[MCP] Server info for ${server.name}:`, serverInfo)
|
||||
|
||||
if (serverInfo && serverInfo.version) {
|
||||
Logger.info(`[MCP] Server version for ${server.name}: ${serverInfo.version}`)
|
||||
return serverInfo.version
|
||||
}
|
||||
|
||||
Logger.warn(`[MCP] No version information available for server: ${server.name}`)
|
||||
return null
|
||||
} catch (error: any) {
|
||||
Logger.error(`[MCP] Failed to get server version for ${server.name}:`, error?.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new McpService()
|
||||
|
||||
@ -241,7 +241,8 @@ const api = {
|
||||
getInstallInfo: () => ipcRenderer.invoke(IpcChannel.Mcp_GetInstallInfo),
|
||||
checkMcpConnectivity: (server: any) => ipcRenderer.invoke(IpcChannel.Mcp_CheckConnectivity, server),
|
||||
abortTool: (callId: string) => ipcRenderer.invoke(IpcChannel.Mcp_AbortTool, callId),
|
||||
setProgress: (progress: number) => ipcRenderer.invoke(IpcChannel.Mcp_SetProgress, progress)
|
||||
setProgress: (progress: number) => ipcRenderer.invoke(IpcChannel.Mcp_SetProgress, progress),
|
||||
getServerVersion: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_GetServerVersion, server)
|
||||
},
|
||||
python: {
|
||||
execute: (script: string, context?: Record<string, any>, timeout?: number) =>
|
||||
|
||||
@ -5,9 +5,9 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||
import { MCPServer } from '@renderer/types'
|
||||
import { formatMcpError } from '@renderer/utils/error'
|
||||
import { Button, Dropdown, Empty, Switch, Tag } from 'antd'
|
||||
import { Badge, Button, Dropdown, Empty, Switch, Tag } from 'antd'
|
||||
import { MonitorCheck, Plus, RefreshCw, Settings2, SquareArrowOutUpRight } from 'lucide-react'
|
||||
import { FC, useCallback, useState } from 'react'
|
||||
import { FC, useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
@ -25,6 +25,27 @@ const McpServersList: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const [isAddModalVisible, setIsAddModalVisible] = useState(false)
|
||||
const [loadingServerIds, setLoadingServerIds] = useState<Set<string>>(new Set())
|
||||
const [serverVersions, setServerVersions] = useState<Record<string, string | null>>({})
|
||||
|
||||
const fetchServerVersion = useCallback(async (server: MCPServer) => {
|
||||
if (!server.isActive) return
|
||||
|
||||
try {
|
||||
const version = await window.api.mcp.getServerVersion(server)
|
||||
setServerVersions((prev) => ({ ...prev, [server.id]: version }))
|
||||
} catch (error) {
|
||||
setServerVersions((prev) => ({ ...prev, [server.id]: null }))
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Fetch versions for all active servers
|
||||
useEffect(() => {
|
||||
mcpServers.forEach((server) => {
|
||||
if (server.isActive) {
|
||||
fetchServerVersion(server)
|
||||
}
|
||||
})
|
||||
}, [mcpServers, fetchServerVersion])
|
||||
|
||||
const onAddMcpServer = useCallback(async () => {
|
||||
const newServer = {
|
||||
@ -64,8 +85,12 @@ const McpServersList: FC = () => {
|
||||
try {
|
||||
if (active) {
|
||||
await window.api.mcp.listTools(server)
|
||||
// Fetch version when server is activated
|
||||
fetchServerVersion({ ...server, isActive: active })
|
||||
} else {
|
||||
await window.api.mcp.stopServer(server)
|
||||
// Clear version when server is deactivated
|
||||
setServerVersions((prev) => ({ ...prev, [server.id]: null }))
|
||||
}
|
||||
updateMCPServer({ ...server, isActive: active })
|
||||
} catch (error: any) {
|
||||
@ -126,6 +151,7 @@ const McpServersList: FC = () => {
|
||||
<ServerName>
|
||||
{server.logoUrl && <ServerLogo src={server.logoUrl} alt={`${server.name} logo`} />}
|
||||
<ServerNameText>{server.name}</ServerNameText>
|
||||
{serverVersions[server.id] && <VersionBadge count={serverVersions[server.id]} color="blue" />}
|
||||
{server.providerUrl && (
|
||||
<Button
|
||||
size="small"
|
||||
@ -305,4 +331,19 @@ const ButtonGroup = styled.div`
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const VersionBadge = styled(Badge)`
|
||||
.ant-badge-count {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
padding: 0 5px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
border-radius: 8px;
|
||||
min-width: 16px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`
|
||||
|
||||
export default McpServersList
|
||||
|
||||
@ -4,7 +4,7 @@ import { useMCPServer, useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||
import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription'
|
||||
import { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types'
|
||||
import { formatMcpError } from '@renderer/utils/error'
|
||||
import { Button, Flex, Form, Input, Radio, Select, Switch, Tabs } from 'antd'
|
||||
import { Badge, Button, Flex, Form, Input, Radio, Select, Switch, Tabs } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
@ -93,6 +93,7 @@ const McpSettings: React.FC = () => {
|
||||
const [selectedRegistryType, setSelectedRegistryType] = useState<string>('')
|
||||
|
||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||
const [serverVersion, setServerVersion] = useState<string | null>(null)
|
||||
|
||||
const { theme } = useTheme()
|
||||
|
||||
@ -227,11 +228,23 @@ const McpSettings: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchServerVersion = async () => {
|
||||
if (server.isActive) {
|
||||
try {
|
||||
const version = await window.api.mcp.getServerVersion(server)
|
||||
setServerVersion(version)
|
||||
} catch (error) {
|
||||
setServerVersion(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (server.isActive) {
|
||||
fetchTools()
|
||||
fetchPrompts()
|
||||
fetchResources()
|
||||
fetchServerVersion()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [server.id, server.isActive])
|
||||
@ -398,8 +411,12 @@ const McpSettings: React.FC = () => {
|
||||
|
||||
const localResources = await window.api.mcp.listResources(server)
|
||||
setResources(localResources)
|
||||
|
||||
const version = await window.api.mcp.getServerVersion(server)
|
||||
setServerVersion(version)
|
||||
} else {
|
||||
await window.api.mcp.stopServer(server)
|
||||
setServerVersion(null)
|
||||
}
|
||||
updateMCPServer({ ...server, isActive: active })
|
||||
} catch (error: any) {
|
||||
@ -703,7 +720,10 @@ const McpSettings: React.FC = () => {
|
||||
<SettingGroup style={{ marginBottom: 0, borderRadius: 'var(--list-item-border-radius)' }}>
|
||||
<SettingTitle>
|
||||
<Flex justify="space-between" align="center" gap={5} style={{ marginRight: 10 }}>
|
||||
<ServerName className="text-nowrap">{server?.name}</ServerName>
|
||||
<Flex align="center" gap={8}>
|
||||
<ServerName className="text-nowrap">{server?.name}</ServerName>
|
||||
{serverVersion && <VersionBadge count={serverVersion} color="blue" />}
|
||||
</Flex>
|
||||
<Button danger icon={<DeleteOutlined />} type="text" onClick={() => onDeleteMcpServer(server)} />
|
||||
</Flex>
|
||||
<Flex align="center" gap={16}>
|
||||
@ -750,4 +770,19 @@ const AdvancedSettingsButton = styled.div`
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const VersionBadge = styled(Badge)`
|
||||
.ant-badge-count {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 0 6px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
border-radius: 9px;
|
||||
min-width: 18px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
`
|
||||
|
||||
export default McpSettings
|
||||
|
||||
Loading…
Reference in New Issue
Block a user