Feat/mcp enhancement (#5386)

This commit is contained in:
LiuVaayne 2025-04-27 12:15:00 +08:00 committed by GitHub
parent b76a609b97
commit 54cb4d7ac1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 376 additions and 40 deletions

View File

@ -395,7 +395,9 @@ class McpService {
try { try {
Logger.info('[MCP] Calling:', server.name, name, args) Logger.info('[MCP] Calling:', server.name, name, args)
const client = await this.initClient(server) const client = await this.initClient(server)
const result = await client.callTool({ name, arguments: args }) const result = await client.callTool({ name, arguments: args }, undefined, {
timeout: server.timeout ? server.timeout * 1000 : 60000 // Default timeout of 1 minute
})
return result as MCPCallToolResponse return result as MCPCallToolResponse
} catch (error) { } catch (error) {
Logger.error(`[MCP] Error calling tool ${name} on ${server.name}:`, error) Logger.error(`[MCP] Error calling tool ${name} on ${server.name}:`, error)

View File

@ -1187,7 +1187,16 @@
"success": "Sync MCP Servers successful", "success": "Sync MCP Servers successful",
"unauthorized": "Sync Unauthorized", "unauthorized": "Sync Unauthorized",
"noServersAvailable": "No MCP servers available" "noServersAvailable": "No MCP servers available"
} },
"timeout": "Timeout",
"timeoutTooltip": "Timeout in seconds for requests to this server, default is 60 seconds",
"provider": "Provider",
"providerUrl": "Provider URL",
"logoUrl": "Logo URL",
"tags": "Tags",
"tagsPlaceholder": "Enter tags",
"providerPlaceholder": "Provider name",
"advancedSettings": "Advanced Settings"
}, },
"messages.divider": "Show divider between messages", "messages.divider": "Show divider between messages",
"messages.grid_columns": "Message grid display columns", "messages.grid_columns": "Message grid display columns",

View File

@ -1185,7 +1185,16 @@
"success": "MCPサーバーの同期成功", "success": "MCPサーバーの同期成功",
"unauthorized": "同期が許可されていません", "unauthorized": "同期が許可されていません",
"noServersAvailable": "利用可能な MCP サーバーがありません" "noServersAvailable": "利用可能な MCP サーバーがありません"
} },
"timeout": "タイムアウト",
"timeoutTooltip": "このサーバーへのリクエストのタイムアウト時間、デフォルトは60秒です",
"provider": "プロバイダー",
"providerUrl": "プロバイダーURL",
"logoUrl": "ロゴURL",
"tags": "タグ",
"tagsPlaceholder": "タグを入力",
"providerPlaceholder": "プロバイダー名",
"advancedSettings": "詳細設定"
}, },
"messages.divider": "メッセージ間に区切り線を表示", "messages.divider": "メッセージ間に区切り線を表示",
"messages.grid_columns": "メッセージグリッドの表示列数", "messages.grid_columns": "メッセージグリッドの表示列数",
@ -1479,4 +1488,4 @@
"visualization": "可視化" "visualization": "可視化"
} }
} }
} }

View File

@ -1185,7 +1185,16 @@
"success": "Синхронизация серверов MCP успешна", "success": "Синхронизация серверов MCP успешна",
"unauthorized": "Синхронизация не разрешена", "unauthorized": "Синхронизация не разрешена",
"noServersAvailable": "Нет доступных серверов MCP" "noServersAvailable": "Нет доступных серверов MCP"
} },
"timeout": "Тайм-аут",
"timeoutTooltip": "Тайм-аут в секундах для запросов к этому серверу, по умолчанию 60 секунд",
"provider": "Провайдер",
"providerUrl": "URL провайдера",
"logoUrl": "URL логотипа",
"tags": "Теги",
"tagsPlaceholder": "Введите теги",
"providerPlaceholder": "Имя провайдера",
"advancedSettings": "Расширенные настройки"
}, },
"messages.divider": "Показывать разделитель между сообщениями", "messages.divider": "Показывать разделитель между сообщениями",
"messages.grid_columns": "Количество столбцов сетки сообщений", "messages.grid_columns": "Количество столбцов сетки сообщений",
@ -1479,4 +1488,4 @@
"visualization": "Визуализация" "visualization": "Визуализация"
} }
} }
} }

View File

@ -1187,7 +1187,16 @@
"success": "同步MCP服务器成功", "success": "同步MCP服务器成功",
"unauthorized": "同步未授权", "unauthorized": "同步未授权",
"noServersAvailable": "无可用的 MCP 服务器" "noServersAvailable": "无可用的 MCP 服务器"
} },
"timeout": "超时",
"timeoutTooltip": "对该服务器请求的超时时间默认为60秒",
"provider": "提供者",
"providerUrl": "提供者网址",
"logoUrl": "标志网址",
"tags": "标签",
"tagsPlaceholder": "输入标签",
"providerPlaceholder": "提供者名称",
"advancedSettings": "高级设置"
}, },
"messages.divider": "消息分割线", "messages.divider": "消息分割线",
"messages.grid_columns": "消息网格展示列数", "messages.grid_columns": "消息网格展示列数",

View File

@ -1186,7 +1186,16 @@
"success": "同步MCP伺服器成功", "success": "同步MCP伺服器成功",
"unauthorized": "同步未授權", "unauthorized": "同步未授權",
"noServersAvailable": "無可用的 MCP 伺服器" "noServersAvailable": "無可用的 MCP 伺服器"
} },
"timeout": "超時",
"timeoutTooltip": "對該伺服器請求的超時時間預設為60秒",
"provider": "提供者",
"providerUrl": "提供者網址",
"logoUrl": "標誌網址",
"tags": "標籤",
"tagsPlaceholder": "輸入標籤",
"providerPlaceholder": "提供者名稱",
"advancedSettings": "高級設定"
}, },
"messages.divider": "訊息間顯示分隔線", "messages.divider": "訊息間顯示分隔線",
"messages.grid_columns": "訊息網格展示列數", "messages.grid_columns": "訊息網格展示列數",

View File

@ -5,7 +5,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { MCPServer } from '@renderer/types' import { MCPServer } from '@renderer/types'
import { Button, Empty, Tag } from 'antd' import { Button, Empty, Tag } from 'antd'
import { MonitorCheck, Plus, RefreshCw, Settings2 } from 'lucide-react' import { MonitorCheck, Plus, RefreshCw, Settings2, SquareArrowOutUpRight } from 'lucide-react'
import { FC, useCallback } from 'react' import { FC, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router' import { useNavigate } from 'react-router'
@ -61,7 +61,17 @@ const McpServersList: FC = () => {
<ServerCard key={server.id} onClick={() => navigate(`/settings/mcp/settings`, { state: { server } })}> <ServerCard key={server.id} onClick={() => navigate(`/settings/mcp/settings`, { state: { server } })}>
<ServerHeader> <ServerHeader>
<ServerName> <ServerName>
{server.logoUrl && <ServerLogo src={server.logoUrl} alt={`${server.name} logo`} />}
<ServerNameText>{server.name}</ServerNameText> <ServerNameText>{server.name}</ServerNameText>
{server.providerUrl && (
<Button
size="small"
type="text"
onClick={() => window.open(server.providerUrl, '_blank')}
icon={<SquareArrowOutUpRight size={14} />}
className="nodrag"
style={{ fontSize: 13, height: 28, borderRadius: 20 }}></Button>
)}
<ServerIcon> <ServerIcon>
<MonitorCheck size={16} color={server.isActive ? 'var(--color-primary)' : 'var(--color-text-3)'} /> <MonitorCheck size={16} color={server.isActive ? 'var(--color-primary)' : 'var(--color-text-3)'} />
</ServerIcon> </ServerIcon>
@ -76,9 +86,20 @@ const McpServersList: FC = () => {
</ServerHeader> </ServerHeader>
<ServerDescription>{server.description}</ServerDescription> <ServerDescription>{server.description}</ServerDescription>
<ServerFooter> <ServerFooter>
<Tag color="default" style={{ borderRadius: 20, margin: 0 }}> <Tag color="processing" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>
{t(`settings.mcp.types.${server.type || 'stdio'}`)} {t(`settings.mcp.types.${server.type || 'stdio'}`)}
</Tag> </Tag>
{server.provider && (
<Tag color="success" style={{ borderRadius: 20, margin: 0, fontWeight: 500 }}>
{server.provider}
</Tag>
)}
{server.tags &&
server.tags.map((tag) => (
<Tag key={tag} color="default" style={{ borderRadius: 20, margin: 0 }}>
{tag}
</Tag>
))}
</ServerFooter> </ServerFooter>
</ServerCard> </ServerCard>
)} )}
@ -136,6 +157,14 @@ const ServerCard = styled.div`
} }
` `
const ServerLogo = styled.img`
width: 24px;
height: 24px;
border-radius: 4px;
object-fit: cover;
margin-right: 8px;
`
const ServerHeader = styled.div` const ServerHeader = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
@ -155,7 +184,7 @@ const ServerName = styled.div`
text-overflow: ellipsis; text-overflow: ellipsis;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 4px;
` `
const ServerNameText = styled.span` const ServerNameText = styled.span`
@ -183,7 +212,8 @@ const ServerDescription = styled.div`
const ServerFooter = styled.div` const ServerFooter = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; gap: 4px;
justify-content: flex-start;
margin-top: 10px; margin-top: 10px;
` `

View File

@ -3,8 +3,28 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useMCPServers } from '@renderer/hooks/useMCPServers'
import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription' import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription'
import { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types' import { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types'
import { Button, Flex, Form, Input, Radio, Switch, Tabs } from 'antd' import { Button, Collapse, Flex, Form, Input, Radio, Select, Switch, Tabs } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import {
AlignLeft,
Building2,
Clock,
Code,
Database,
FileText,
Globe,
Image,
Link,
ListPlus,
MessageSquare,
Package,
Server,
Settings,
Tag,
Terminal,
Type,
Wrench
} from 'lucide-react'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate } from 'react-router' import { useLocation, useNavigate } from 'react-router'
@ -26,6 +46,12 @@ interface MCPFormValues {
env?: string env?: string
isActive: boolean isActive: boolean
headers?: string headers?: string
timeout?: number
provider?: string
providerUrl?: string
logoUrl?: string
tags?: string[]
} }
interface Registry { interface Registry {
@ -84,6 +110,7 @@ const McpSettings: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
// Initialize form values whenever the server changes
useEffect(() => { useEffect(() => {
const serverType: MCPServer['type'] = server.type || (server.baseUrl ? 'sse' : 'stdio') const serverType: MCPServer['type'] = server.type || (server.baseUrl ? 'sse' : 'stdio')
setServerType(serverType) setServerType(serverType)
@ -109,6 +136,7 @@ const McpSettings: React.FC = () => {
} }
} }
// Initialize basic fields
form.setFieldsValue({ form.setFieldsValue({
name: server.name, name: server.name,
description: server.description, description: server.description,
@ -117,6 +145,7 @@ const McpSettings: React.FC = () => {
command: server.command || '', command: server.command || '',
registryUrl: server.registryUrl || '', registryUrl: server.registryUrl || '',
isActive: server.isActive, isActive: server.isActive,
timeout: server.timeout,
args: server.args ? server.args.join('\n') : '', args: server.args ? server.args.join('\n') : '',
env: server.env env: server.env
? Object.entries(server.env) ? Object.entries(server.env)
@ -129,8 +158,18 @@ const McpSettings: React.FC = () => {
.join('\n') .join('\n')
: '' : ''
}) })
// Initialize advanced fields separately to ensure they're captured
// even if the Collapse panel is closed
form.setFieldsValue({
provider: server.provider || '',
providerUrl: server.providerUrl || '',
logoUrl: server.logoUrl || '',
tags: server.tags || []
})
}, [server, form]) }, [server, form])
// Watch for serverType changes
useEffect(() => { useEffect(() => {
const currentServerType = form.getFieldValue('serverType') const currentServerType = form.getFieldValue('serverType')
if (currentServerType) { if (currentServerType) {
@ -219,7 +258,13 @@ const McpSettings: React.FC = () => {
description: values.description, description: values.description,
isActive: values.isActive, isActive: values.isActive,
registryUrl: values.registryUrl, registryUrl: values.registryUrl,
searchKey: server.searchKey searchKey: server.searchKey,
timeout: values.timeout || server.timeout,
// Preserve existing advanced properties if not set in the form
provider: values.provider || server.provider,
providerUrl: values.providerUrl || server.providerUrl,
logoUrl: values.logoUrl || server.logoUrl,
tags: values.tags || server.tags
} }
// set stdio or sse server // set stdio or sse server
@ -392,7 +437,12 @@ const McpSettings: React.FC = () => {
const tabs = [ const tabs = [
{ {
key: 'settings', key: 'settings',
label: t('settings.mcp.tabs.general'), label: (
<Flex align="center" gap={8}>
<Settings size={16} />
{t('settings.mcp.tabs.general')}
</Flex>
),
children: ( children: (
<Form <Form
form={form} form={form}
@ -403,16 +453,36 @@ const McpSettings: React.FC = () => {
width: 'calc(100% + 10px)', width: 'calc(100% + 10px)',
paddingRight: '10px' paddingRight: '10px'
}}> }}>
<Form.Item name="name" label={t('settings.mcp.name')} rules={[{ required: true, message: '' }]}> <Form.Item
name="name"
label={
<FormLabelWithIcon>
<Type size={16} />
{t('settings.mcp.name')}
</FormLabelWithIcon>
}
rules={[{ required: true, message: '' }]}>
<Input placeholder={t('common.name')} disabled={server.type === 'inMemory'} /> <Input placeholder={t('common.name')} disabled={server.type === 'inMemory'} />
</Form.Item> </Form.Item>
<Form.Item name="description" label={t('settings.mcp.description')}> <Form.Item
name="description"
label={
<FormLabelWithIcon>
<AlignLeft size={16} />
{t('settings.mcp.description')}
</FormLabelWithIcon>
}>
<TextArea rows={2} placeholder={t('common.description')} /> <TextArea rows={2} placeholder={t('common.description')} />
</Form.Item> </Form.Item>
{server.type !== 'inMemory' && ( {server.type !== 'inMemory' && (
<Form.Item <Form.Item
name="serverType" name="serverType"
label={t('settings.mcp.type')} label={
<FormLabelWithIcon>
<Server size={16} />
{t('settings.mcp.type')}
</FormLabelWithIcon>
}
rules={[{ required: true }]} rules={[{ required: true }]}
initialValue="stdio"> initialValue="stdio">
<Radio.Group <Radio.Group
@ -429,12 +499,25 @@ const McpSettings: React.FC = () => {
<> <>
<Form.Item <Form.Item
name="baseUrl" name="baseUrl"
label={t('settings.mcp.url')} label={
<FormLabelWithIcon>
<Link size={16} />
{t('settings.mcp.url')}
</FormLabelWithIcon>
}
rules={[{ required: serverType === 'sse', message: '' }]} rules={[{ required: serverType === 'sse', message: '' }]}
tooltip={t('settings.mcp.baseUrlTooltip')}> tooltip={t('settings.mcp.baseUrlTooltip')}>
<Input placeholder="http://localhost:3000/sse" /> <Input placeholder="http://localhost:3000/sse" />
</Form.Item> </Form.Item>
<Form.Item name="headers" label={t('settings.mcp.headers')} tooltip={t('settings.mcp.headersTooltip')}> <Form.Item
name="headers"
label={
<FormLabelWithIcon>
<Code size={16} />
{t('settings.mcp.headers')}
</FormLabelWithIcon>
}
tooltip={t('settings.mcp.headersTooltip')}>
<TextArea <TextArea
rows={3} rows={3}
placeholder={`Content-Type=application/json\nAuthorization=Bearer token`} placeholder={`Content-Type=application/json\nAuthorization=Bearer token`}
@ -447,12 +530,25 @@ const McpSettings: React.FC = () => {
<> <>
<Form.Item <Form.Item
name="baseUrl" name="baseUrl"
label={t('settings.mcp.url')} label={
<FormLabelWithIcon>
<Link size={16} />
{t('settings.mcp.url')}
</FormLabelWithIcon>
}
rules={[{ required: serverType === 'streamableHttp', message: '' }]} rules={[{ required: serverType === 'streamableHttp', message: '' }]}
tooltip={t('settings.mcp.baseUrlTooltip')}> tooltip={t('settings.mcp.baseUrlTooltip')}>
<Input placeholder="http://localhost:3000/mcp" /> <Input placeholder="http://localhost:3000/mcp" />
</Form.Item> </Form.Item>
<Form.Item name="headers" label={t('settings.mcp.headers')} tooltip={t('settings.mcp.headersTooltip')}> <Form.Item
name="headers"
label={
<FormLabelWithIcon>
<Code size={16} />
{t('settings.mcp.headers')}
</FormLabelWithIcon>
}
tooltip={t('settings.mcp.headersTooltip')}>
<TextArea <TextArea
rows={3} rows={3}
placeholder={`Content-Type=application/json\nAuthorization=Bearer token`} placeholder={`Content-Type=application/json\nAuthorization=Bearer token`}
@ -465,7 +561,12 @@ const McpSettings: React.FC = () => {
<> <>
<Form.Item <Form.Item
name="command" name="command"
label={t('settings.mcp.command')} label={
<FormLabelWithIcon>
<Terminal size={16} />
{t('settings.mcp.command')}
</FormLabelWithIcon>
}
rules={[{ required: serverType === 'stdio', message: '' }]}> rules={[{ required: serverType === 'stdio', message: '' }]}>
<Input placeholder="uvx or npx" onChange={(e) => handleCommandChange(e.target.value)} /> <Input placeholder="uvx or npx" onChange={(e) => handleCommandChange(e.target.value)} />
</Form.Item> </Form.Item>
@ -473,7 +574,12 @@ const McpSettings: React.FC = () => {
{isShowRegistry && registry && ( {isShowRegistry && registry && (
<Form.Item <Form.Item
name="registryUrl" name="registryUrl"
label={t('settings.mcp.registry')} label={
<FormLabelWithIcon>
<Package size={16} />
{t('settings.mcp.registry')}
</FormLabelWithIcon>
}
tooltip={t('settings.mcp.registryTooltip')}> tooltip={t('settings.mcp.registryTooltip')}>
<Radio.Group> <Radio.Group>
<Radio <Radio
@ -498,26 +604,136 @@ const McpSettings: React.FC = () => {
</Form.Item> </Form.Item>
)} )}
<Form.Item name="args" label={t('settings.mcp.args')} tooltip={t('settings.mcp.argsTooltip')}> <Form.Item
name="args"
label={
<FormLabelWithIcon>
<ListPlus size={16} />
{t('settings.mcp.args')}
</FormLabelWithIcon>
}
tooltip={t('settings.mcp.argsTooltip')}>
<TextArea rows={3} placeholder={`arg1\narg2`} style={{ fontFamily: 'monospace' }} /> <TextArea rows={3} placeholder={`arg1\narg2`} style={{ fontFamily: 'monospace' }} />
</Form.Item> </Form.Item>
<Form.Item name="env" label={t('settings.mcp.env')} tooltip={t('settings.mcp.envTooltip')}> <Form.Item
name="env"
label={
<FormLabelWithIcon>
<Settings size={16} />
{t('settings.mcp.env')}
</FormLabelWithIcon>
}
tooltip={t('settings.mcp.envTooltip')}>
<TextArea rows={3} placeholder={`KEY1=value1\nKEY2=value2`} style={{ fontFamily: 'monospace' }} /> <TextArea rows={3} placeholder={`KEY1=value1\nKEY2=value2`} style={{ fontFamily: 'monospace' }} />
</Form.Item> </Form.Item>
</> </>
)} )}
{serverType === 'inMemory' && ( {serverType === 'inMemory' && (
<> <>
<Form.Item name="args" label={t('settings.mcp.args')} tooltip={t('settings.mcp.argsTooltip')}> <Form.Item
name="args"
label={
<FormLabelWithIcon>
<ListPlus size={16} />
{t('settings.mcp.args')}
</FormLabelWithIcon>
}
tooltip={t('settings.mcp.argsTooltip')}>
<TextArea rows={3} placeholder={`arg1\narg2`} style={{ fontFamily: 'monospace' }} /> <TextArea rows={3} placeholder={`arg1\narg2`} style={{ fontFamily: 'monospace' }} />
</Form.Item> </Form.Item>
<Form.Item name="env" label={t('settings.mcp.env')} tooltip={t('settings.mcp.envTooltip')}> <Form.Item
name="env"
label={
<FormLabelWithIcon>
<Settings size={16} />
{t('settings.mcp.env')}
</FormLabelWithIcon>
}
tooltip={t('settings.mcp.envTooltip')}>
<TextArea rows={3} placeholder={`KEY1=value1\nKEY2=value2`} style={{ fontFamily: 'monospace' }} /> <TextArea rows={3} placeholder={`KEY1=value1\nKEY2=value2`} style={{ fontFamily: 'monospace' }} />
</Form.Item> </Form.Item>
</> </>
)} )}
<Form.Item
name="timeout"
label={
<FormLabelWithIcon>
<Clock size={16} />
{t('settings.mcp.timeout', 'Timeout')}
</FormLabelWithIcon>
}
tooltip={t(
'settings.mcp.timeoutTooltip',
'Timeout in seconds for requests to this server, default is 60 seconds'
)}>
<Input type="number" min={1} placeholder="60" addonAfter="s" />
</Form.Item>
<Collapse
ghost
style={{ marginBottom: 16 }}
defaultActiveKey={[]}
items={[
{
key: 'advanced',
label: t('settings.mcp.advancedSettings', 'Advanced Settings'),
children: (
<>
<Form.Item
name="provider"
label={
<FormLabelWithIcon>
<Building2 size={16} />
{t('settings.mcp.provider', 'Provider')}
</FormLabelWithIcon>
}>
<Input placeholder={t('settings.mcp.providerPlaceholder', 'Provider name')} />
</Form.Item>
<Form.Item
name="providerUrl"
label={
<FormLabelWithIcon>
<Globe size={16} />
{t('settings.mcp.providerUrl', 'Provider URL')}
</FormLabelWithIcon>
}>
<Input placeholder={t('settings.mcp.providerUrlPlaceholder', 'https://provider-website.com')} />
</Form.Item>
<Form.Item
name="logoUrl"
label={
<FormLabelWithIcon>
<Image size={16} />
{t('settings.mcp.logoUrl', 'Logo URL')}
</FormLabelWithIcon>
}>
<Input placeholder={t('settings.mcp.logoUrlPlaceholder', 'https://example.com/logo.png')} />
</Form.Item>
<Form.Item
name="tags"
label={
<FormLabelWithIcon>
<Tag size={16} />
{t('settings.mcp.tags', 'Tags')}
</FormLabelWithIcon>
}>
<Select
mode="tags"
style={{ width: '100%' }}
placeholder={t('settings.mcp.tagsPlaceholder', 'Enter tags')}
tokenSeparators={[',']}
/>
</Form.Item>
</>
)
}
]}
/>
</Form> </Form>
) )
} }
@ -525,7 +741,12 @@ const McpSettings: React.FC = () => {
if (server.searchKey) { if (server.searchKey) {
tabs.push({ tabs.push({
key: 'description', key: 'description',
label: t('settings.mcp.tabs.description'), label: (
<Flex align="center" gap={8}>
<FileText size={16} />
{t('settings.mcp.tabs.description')}
</Flex>
),
children: <MCPDescription searchKey={server.searchKey} /> children: <MCPDescription searchKey={server.searchKey} />
}) })
} }
@ -534,17 +755,32 @@ const McpSettings: React.FC = () => {
tabs.push( tabs.push(
{ {
key: 'tools', key: 'tools',
label: t('settings.mcp.tabs.tools'), label: (
<Flex align="center" gap={8}>
<Wrench size={16} />
{t('settings.mcp.tabs.tools')}
</Flex>
),
children: <MCPToolsSection tools={tools} server={server} onToggleTool={handleToggleTool} /> children: <MCPToolsSection tools={tools} server={server} onToggleTool={handleToggleTool} />
}, },
{ {
key: 'prompts', key: 'prompts',
label: t('settings.mcp.tabs.prompts'), label: (
<Flex align="center" gap={8}>
<MessageSquare size={16} />
{t('settings.mcp.tabs.prompts')}
</Flex>
),
children: <MCPPromptsSection prompts={prompts} /> children: <MCPPromptsSection prompts={prompts} />
}, },
{ {
key: 'resources', key: 'resources',
label: t('settings.mcp.tabs.resources'), label: (
<Flex align="center" gap={8}>
<Database size={16} />
{t('settings.mcp.tabs.resources')}
</Flex>
),
children: <MCPResourcesSection resources={resources} /> children: <MCPResourcesSection resources={resources} />
} }
) )
@ -593,4 +829,9 @@ const ServerName = styled.span`
font-weight: 500; font-weight: 500;
` `
const FormLabelWithIcon = styled(Flex)`
align-items: center;
gap: 8px;
`
export default McpSettings export default McpSettings

View File

@ -27,6 +27,8 @@ interface ModelScopeServer {
chinese_name?: string chinese_name?: string
description?: string description?: string
operational_urls?: { url: string }[] operational_urls?: { url: string }[]
tags?: string[]
logo_url?: string
} }
interface ModelScopeSyncResult { interface ModelScopeSyncResult {
@ -103,7 +105,11 @@ export const syncModelScopeServers = async (
command: '', command: '',
args: [], args: [],
env: {}, env: {},
isActive: true isActive: true,
provider: 'ModelScope',
providerUrl: `https://www.modelscope.cn/mcp/servers/@${server.id}`,
logoUrl: server.logo_url || '',
tags: server.tags || []
} }
addedServers.push(mcpServer) addedServers.push(mcpServer)

View File

@ -59,7 +59,8 @@ export const builtinMCPServers: MCPServer[] = [
type: 'stdio', type: 'stdio',
command: 'npx', command: 'npx',
args: ['-y', '@mcpmarket/mcp-auto-install', 'connect', '--json'], args: ['-y', '@mcpmarket/mcp-auto-install', 'connect', '--json'],
isActive: false isActive: false,
provider: 'CherryAI'
}, },
{ {
id: nanoid(), id: nanoid(),
@ -70,14 +71,16 @@ export const builtinMCPServers: MCPServer[] = [
isActive: true, isActive: true,
env: { env: {
MEMORY_FILE_PATH: 'YOUR_MEMORY_FILE_PATH' MEMORY_FILE_PATH: 'YOUR_MEMORY_FILE_PATH'
} },
provider: 'CherryAI'
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/sequentialthinking', name: '@cherry/sequentialthinking',
type: 'inMemory', type: 'inMemory',
description: '一个 MCP 服务器实现,提供了通过结构化思维过程进行动态和反思性问题解决的工具', description: '一个 MCP 服务器实现,提供了通过结构化思维过程进行动态和反思性问题解决的工具',
isActive: true isActive: true,
provider: 'CherryAI'
}, },
{ {
id: nanoid(), id: nanoid(),
@ -88,21 +91,24 @@ export const builtinMCPServers: MCPServer[] = [
isActive: false, isActive: false,
env: { env: {
BRAVE_API_KEY: 'YOUR_API_KEY' BRAVE_API_KEY: 'YOUR_API_KEY'
} },
provider: 'CherryAI'
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/fetch', name: '@cherry/fetch',
type: 'inMemory', type: 'inMemory',
description: '用于获取 URL 网页内容的 MCP 服务器', description: '用于获取 URL 网页内容的 MCP 服务器',
isActive: true isActive: true,
provider: 'CherryAI'
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/filesystem', name: '@cherry/filesystem',
type: 'inMemory', type: 'inMemory',
description: '实现文件系统操作的模型上下文协议MCP的 Node.js 服务器', description: '实现文件系统操作的模型上下文协议MCP的 Node.js 服务器',
isActive: false isActive: false,
provider: 'CherryAI'
}, },
{ {
id: nanoid(), id: nanoid(),
@ -112,7 +118,8 @@ export const builtinMCPServers: MCPServer[] = [
isActive: false, isActive: false,
env: { env: {
DIFY_KEY: 'YOUR_DIFY_KEY' DIFY_KEY: 'YOUR_DIFY_KEY'
} },
provider: 'CherryAI'
} }
] ]

View File

@ -404,6 +404,11 @@ export interface MCPServer {
configSample?: MCPConfigSample configSample?: MCPConfigSample
headers?: Record<string, string> // Custom headers to be sent with requests to this server headers?: Record<string, string> // Custom headers to be sent with requests to this server
searchKey?: string searchKey?: string
provider?: string // Provider name for this server like ModelScope, Higress, etc.
providerUrl?: string // URL of the MCP server in provider's website or documentation
logoUrl?: string // URL of the MCP server's logo
tags?: string[] // List of tags associated with this server
timeout?: number // Timeout in seconds for requests to this server, default is 60 seconds
} }
export interface MCPToolInputSchema { export interface MCPToolInputSchema {