mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 14:29:15 +08:00
Feat/mcp enhancement (#5386)
This commit is contained in:
parent
b76a609b97
commit
54cb4d7ac1
@ -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)
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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": "可視化"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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": "Визуализация"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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": "消息网格展示列数",
|
||||||
|
|||||||
@ -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": "訊息網格展示列數",
|
||||||
|
|||||||
@ -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;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user