feat: add MCP server version display with badges (#8097)

This commit is contained in:
LiuVaayne 2025-07-12 15:35:59 +08:00 committed by GitHub
parent 38cf3869bc
commit 16ca373c55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 110 additions and 5 deletions

View File

@ -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',

View File

@ -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)
})

View File

@ -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()

View File

@ -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) =>

View File

@ -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

View File

@ -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