mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 04:31:27 +08:00
feat: add confirmation modal for activating protocol-installed MCP (#11070)
* feat: add confirmation modal for activating protocol-installed MCP * fix: sync i18n * fix(i18n): Auto update translations for PR #11070 * chore: verify ci is working * Revert "chore: verify ci is working" This reverts commita2434a397d. --------- Co-authored-by: GitHub Action <action@github.com> (cherry picked from commit68e0d8b0f1)
This commit is contained in:
parent
8e51f6c598
commit
16252a6263
@ -9,13 +9,20 @@ const logger = loggerService.withContext('URLSchema:handleMcpProtocolUrl')
|
||||
|
||||
function installMCPServer(server: MCPServer) {
|
||||
const mainWindow = windowService.getMainWindow()
|
||||
const now = Date.now()
|
||||
|
||||
if (!server.id) {
|
||||
server.id = nanoid()
|
||||
const payload: MCPServer = {
|
||||
...server,
|
||||
id: server.id ?? nanoid(),
|
||||
installSource: 'protocol',
|
||||
isTrusted: false,
|
||||
isActive: false,
|
||||
trustedAt: undefined,
|
||||
installedAt: server.installedAt ?? now
|
||||
}
|
||||
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send(IpcChannel.Mcp_AddServer, server)
|
||||
mainWindow.webContents.send(IpcChannel.Mcp_AddServer, payload)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
57
src/renderer/src/hooks/useMCPServerTrust.tsx
Normal file
57
src/renderer/src/hooks/useMCPServerTrust.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import ProtocolInstallWarningContent from '@renderer/pages/settings/MCPSettings/ProtocolInstallWarning'
|
||||
import {
|
||||
ensureServerTrusted as ensureServerTrustedCore,
|
||||
getCommandPreview
|
||||
} from '@renderer/pages/settings/MCPSettings/utils'
|
||||
import { MCPServer } from '@renderer/types'
|
||||
import { modalConfirm } from '@renderer/utils'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { useMCPServers } from './useMCPServers'
|
||||
|
||||
/**
|
||||
* Hook for handling MCP server trust verification
|
||||
* Binds UI (modal dialog) to the core trust verification logic
|
||||
*/
|
||||
export const useMCPServerTrust = () => {
|
||||
const { updateMCPServer } = useMCPServers()
|
||||
const { t } = useTranslation()
|
||||
|
||||
/**
|
||||
* Request user confirmation to trust a server
|
||||
* Shows a warning modal with server command preview
|
||||
*/
|
||||
const requestConfirm = useCallback(
|
||||
async (server: MCPServer): Promise<boolean> => {
|
||||
const commandPreview = getCommandPreview(server)
|
||||
return modalConfirm({
|
||||
title: t('settings.mcp.protocolInstallWarning.title'),
|
||||
content: (
|
||||
<ProtocolInstallWarningContent
|
||||
message={t('settings.mcp.protocolInstallWarning.message')}
|
||||
commandLabel={t('settings.mcp.protocolInstallWarning.command')}
|
||||
commandPreview={commandPreview}
|
||||
/>
|
||||
),
|
||||
okText: t('settings.mcp.protocolInstallWarning.run'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: { danger: true }
|
||||
})
|
||||
},
|
||||
[t]
|
||||
)
|
||||
|
||||
/**
|
||||
* Ensures a server is trusted before proceeding
|
||||
* Combines core logic with UI confirmation
|
||||
*/
|
||||
const ensureServerTrusted = useCallback(
|
||||
async (server: MCPServer): Promise<MCPServer | null> => {
|
||||
return ensureServerTrustedCore(server, requestConfirm, updateMCPServer)
|
||||
},
|
||||
[requestConfirm, updateMCPServer]
|
||||
)
|
||||
|
||||
return { ensureServerTrusted }
|
||||
}
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "No prompts available",
|
||||
"requiredField": "Required Field"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "Startup command",
|
||||
"message": "This MCP was installed from an external source via protocol. Running unknown tools may harm your computer.",
|
||||
"run": "Run",
|
||||
"title": "Run external MCP?"
|
||||
},
|
||||
"provider": "Provider",
|
||||
"providerPlaceholder": "Provider name",
|
||||
"providerUrl": "Provider URL",
|
||||
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "无可用提示",
|
||||
"requiredField": "必填字段"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "启动命令",
|
||||
"message": "该 MCP 是通过协议从外部来源安装的,运行来历不明的工具可能对您的计算机造成危害。",
|
||||
"run": "运行",
|
||||
"title": "运行外部 MCP?"
|
||||
},
|
||||
"provider": "提供者",
|
||||
"providerPlaceholder": "提供者名称",
|
||||
"providerUrl": "提供者网址",
|
||||
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "無可用提示",
|
||||
"requiredField": "必填欄位"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "啟動命令",
|
||||
"message": "此 MCP 透過協議從外部來源安裝,執行來源不明的工具可能會對您的電腦造成危害。",
|
||||
"run": "執行",
|
||||
"title": "執行外部 MCP?"
|
||||
},
|
||||
"provider": "提供者",
|
||||
"providerPlaceholder": "提供者名稱",
|
||||
"providerUrl": "提供者網址",
|
||||
|
||||
@ -3655,6 +3655,12 @@
|
||||
"noPromptsAvailable": "Keine Prompts verfügbar",
|
||||
"requiredField": "Pflichtfeld"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "Startbefehl",
|
||||
"message": "Dieses MCP wurde über ein Protokoll aus einer externen Quelle installiert. Das Ausführen unbekannter Tools kann Ihren Computer schädigen.",
|
||||
"run": "Laufen",
|
||||
"title": "Externes MCP ausführen?"
|
||||
},
|
||||
"provider": "Anbieter",
|
||||
"providerPlaceholder": "Anbietername",
|
||||
"providerUrl": "Anbieter-Website",
|
||||
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "Δεν υπάρχουν διαθέσιμες υποδείξεις",
|
||||
"requiredField": "Υποχρεωτικό πεδίο"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "Εντολή εκκίνησης",
|
||||
"message": "Αυτό το MCP εγκαταστάθηκε από εξωτερική πηγή μέσω πρωτοκόλλου. Η εκτέλεση άγνωστων εργαλείων ενδέχεται να βλάψει τον υπολογιστή σας.",
|
||||
"run": "Τρέξε",
|
||||
"title": "Εκτέλεση εξωτερικού MCP;"
|
||||
},
|
||||
"provider": "Πάροχος",
|
||||
"providerPlaceholder": "Όνομα παρόχου",
|
||||
"providerUrl": "URL Παρόχου",
|
||||
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "No hay indicaciones disponibles",
|
||||
"requiredField": "Campo obligatorio"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "Comando de inicio",
|
||||
"message": "Este MCP fue instalado desde una fuente externa a través del protocolo. Ejecutar herramientas desconocidas puede dañar tu computadora.",
|
||||
"run": "Correr",
|
||||
"title": "¿Ejecutar MCP externo?"
|
||||
},
|
||||
"provider": "Proveedor",
|
||||
"providerPlaceholder": "Nombre del proveedor",
|
||||
"providerUrl": "URL del proveedor",
|
||||
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "Aucune invite disponible",
|
||||
"requiredField": "Champ obligatoire"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "Commande de démarrage",
|
||||
"message": "Ce MCP a été installé depuis une source externe via le protocole. L'exécution d'outils inconnus peut endommager votre ordinateur.",
|
||||
"run": "Courir",
|
||||
"title": "Exécuter un MCP externe ?"
|
||||
},
|
||||
"provider": "Поставщик",
|
||||
"providerPlaceholder": "Название поставщика",
|
||||
"providerUrl": "Адрес поставщика",
|
||||
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "利用可能なプロンプトはありません",
|
||||
"requiredField": "必須フィールド"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "起動コマンド",
|
||||
"message": "このMCPは外部ソースからプロトコル経由でインストールされました。不明なツールを実行すると、コンピューターに危害を及ぼす可能性があります。",
|
||||
"run": "走る",
|
||||
"title": "外部のMCPを実行しますか?"
|
||||
},
|
||||
"provider": "プロバイダー",
|
||||
"providerPlaceholder": "プロバイダー名",
|
||||
"providerUrl": "プロバイダーURL",
|
||||
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "Nenhuma dica disponível",
|
||||
"requiredField": "Campo obrigatório"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "Comando de inicialização",
|
||||
"message": "Este MCP foi instalado a partir de uma fonte externa via protocolo. Executar ferramentas desconhecidas pode prejudicar seu computador.",
|
||||
"run": "Correr",
|
||||
"title": "Executar MCP externo?"
|
||||
},
|
||||
"provider": "Fornecedor",
|
||||
"providerPlaceholder": "Nome do Fornecedor",
|
||||
"providerUrl": "URL do Fornecedor",
|
||||
|
||||
@ -3445,6 +3445,12 @@
|
||||
"noPromptsAvailable": "Нет доступных подсказок",
|
||||
"requiredField": "Обязательное поле"
|
||||
},
|
||||
"protocolInstallWarning": {
|
||||
"command": "Команда запуска",
|
||||
"message": "Этот MCP был установлен из внешнего источника через протокол. Запуск неизвестных инструментов может повредить ваш компьютер.",
|
||||
"run": "Беги",
|
||||
"title": "Запускать внешний MCP?"
|
||||
},
|
||||
"provider": "Провайдер",
|
||||
"providerPlaceholder": "Имя провайдера",
|
||||
"providerUrl": "URL провайдера",
|
||||
|
||||
@ -138,6 +138,7 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
|
||||
|
||||
// Process DXT file
|
||||
try {
|
||||
const installTimestamp = Date.now()
|
||||
const result = await window.api.mcp.uploadDxt(dxtFile)
|
||||
|
||||
if (!result.success) {
|
||||
@ -188,7 +189,11 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
|
||||
logoUrl: manifest.icon ? `${extractDir}/${manifest.icon}` : undefined,
|
||||
provider: manifest.author?.name,
|
||||
providerUrl: manifest.homepage || manifest.repository?.url,
|
||||
tags: manifest.keywords
|
||||
tags: manifest.keywords,
|
||||
installSource: 'manual',
|
||||
isTrusted: true,
|
||||
installedAt: installTimestamp,
|
||||
trustedAt: installTimestamp
|
||||
}
|
||||
|
||||
onSuccess(newServer)
|
||||
@ -253,12 +258,17 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
|
||||
}
|
||||
|
||||
// 如果成功解析並通過所有檢查,立即加入伺服器(非啟用狀態)並關閉對話框
|
||||
const installTimestamp = Date.now()
|
||||
const newServer: MCPServer = {
|
||||
id: nanoid(),
|
||||
...serverToAdd,
|
||||
name: serverToAdd.name || t('settings.mcp.newServer'),
|
||||
baseUrl: serverToAdd.baseUrl ?? serverToAdd.url ?? '',
|
||||
isActive: false // 初始狀態為非啟用
|
||||
isActive: false, // 初始狀態為非啟用
|
||||
installSource: 'manual',
|
||||
isTrusted: true,
|
||||
installedAt: installTimestamp,
|
||||
trustedAt: installTimestamp
|
||||
}
|
||||
|
||||
onSuccess(newServer)
|
||||
|
||||
@ -5,6 +5,7 @@ import { Sortable, useDndReorder } from '@renderer/components/dnd'
|
||||
import { EditIcon, RefreshIcon } from '@renderer/components/Icons'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||
import { useMCPServerTrust } from '@renderer/hooks/useMCPServerTrust'
|
||||
import { MCPServer } from '@renderer/types'
|
||||
import { formatMcpError } from '@renderer/utils/error'
|
||||
import { matchKeywordsInString } from '@renderer/utils/match'
|
||||
@ -28,6 +29,7 @@ const logger = loggerService.withContext('McpServersList')
|
||||
|
||||
const McpServersList: FC = () => {
|
||||
const { mcpServers, addMCPServer, deleteMCPServer, updateMcpServers, updateMCPServer } = useMCPServers()
|
||||
const { ensureServerTrusted } = useMCPServerTrust()
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const [isAddModalVisible, setIsAddModalVisible] = useState(false)
|
||||
@ -156,30 +158,37 @@ const McpServersList: FC = () => {
|
||||
)
|
||||
|
||||
const handleToggleActive = async (server: MCPServer, active: boolean) => {
|
||||
setLoadingServerIds((prev) => new Set(prev).add(server.id))
|
||||
const oldActiveState = server.isActive
|
||||
logger.silly('toggle activate', { serverId: server.id, active })
|
||||
let serverForUpdate = server
|
||||
if (active) {
|
||||
const trustedServer = await ensureServerTrusted(server)
|
||||
if (!trustedServer) {
|
||||
return
|
||||
}
|
||||
serverForUpdate = trustedServer
|
||||
}
|
||||
|
||||
setLoadingServerIds((prev) => new Set(prev).add(serverForUpdate.id))
|
||||
const oldActiveState = serverForUpdate.isActive
|
||||
logger.silly('toggle activate', { serverId: serverForUpdate.id, active })
|
||||
try {
|
||||
if (active) {
|
||||
// Fetch version when server is activated
|
||||
await fetchServerVersion({ ...server, isActive: active })
|
||||
await fetchServerVersion({ ...serverForUpdate, isActive: active })
|
||||
} else {
|
||||
await window.api.mcp.stopServer(server)
|
||||
// Clear version when server is deactivated
|
||||
setServerVersions((prev) => ({ ...prev, [server.id]: null }))
|
||||
await window.api.mcp.stopServer(serverForUpdate)
|
||||
setServerVersions((prev) => ({ ...prev, [serverForUpdate.id]: null }))
|
||||
}
|
||||
updateMCPServer({ ...server, isActive: active })
|
||||
updateMCPServer({ ...serverForUpdate, isActive: active })
|
||||
} catch (error: any) {
|
||||
window.modal.error({
|
||||
title: t('settings.mcp.startError'),
|
||||
content: formatMcpError(error),
|
||||
centered: true
|
||||
})
|
||||
updateMCPServer({ ...server, isActive: oldActiveState })
|
||||
updateMCPServer({ ...serverForUpdate, isActive: oldActiveState })
|
||||
} finally {
|
||||
setLoadingServerIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
next.delete(server.id)
|
||||
next.delete(serverForUpdate.id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import type { McpError } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { DeleteIcon } from '@renderer/components/Icons'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useMCPServer, useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||
import { useMCPServerTrust } from '@renderer/hooks/useMCPServerTrust'
|
||||
import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription'
|
||||
import { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types'
|
||||
import { formatMcpError } from '@renderer/utils/error'
|
||||
@ -81,6 +82,7 @@ const McpSettings: React.FC = () => {
|
||||
const decodedServerId = serverId ? decodeURIComponent(serverId) : ''
|
||||
const server = useMCPServer(decodedServerId).server as MCPServer
|
||||
const { deleteMCPServer, updateMCPServer } = useMCPServers()
|
||||
const { ensureServerTrusted } = useMCPServerTrust()
|
||||
const [serverType, setServerType] = useState<MCPServer['type']>('stdio')
|
||||
const [form] = Form.useForm<MCPFormValues>()
|
||||
const [loading, setLoading] = useState(false)
|
||||
@ -266,6 +268,7 @@ const McpSettings: React.FC = () => {
|
||||
|
||||
// set basic fields
|
||||
const mcpServer: MCPServer = {
|
||||
...server,
|
||||
id: server.id,
|
||||
name: values.name,
|
||||
type: values.serverType || server.type,
|
||||
@ -401,34 +404,43 @@ const McpSettings: React.FC = () => {
|
||||
}
|
||||
|
||||
await form.validateFields()
|
||||
setLoadingServer(server.id)
|
||||
const oldActiveState = server.isActive
|
||||
let serverForUpdate = server
|
||||
if (active) {
|
||||
const trustedServer = await ensureServerTrusted(server)
|
||||
if (!trustedServer) {
|
||||
return
|
||||
}
|
||||
serverForUpdate = trustedServer
|
||||
}
|
||||
|
||||
setLoadingServer(serverForUpdate.id)
|
||||
const oldActiveState = serverForUpdate.isActive
|
||||
|
||||
try {
|
||||
if (active) {
|
||||
const localTools = await window.api.mcp.listTools(server)
|
||||
const localTools = await window.api.mcp.listTools(serverForUpdate)
|
||||
setTools(localTools)
|
||||
|
||||
const localPrompts = await window.api.mcp.listPrompts(server)
|
||||
const localPrompts = await window.api.mcp.listPrompts(serverForUpdate)
|
||||
setPrompts(localPrompts)
|
||||
|
||||
const localResources = await window.api.mcp.listResources(server)
|
||||
const localResources = await window.api.mcp.listResources(serverForUpdate)
|
||||
setResources(localResources)
|
||||
|
||||
const version = await window.api.mcp.getServerVersion(server)
|
||||
const version = await window.api.mcp.getServerVersion(serverForUpdate)
|
||||
setServerVersion(version)
|
||||
} else {
|
||||
await window.api.mcp.stopServer(server)
|
||||
await window.api.mcp.stopServer(serverForUpdate)
|
||||
setServerVersion(null)
|
||||
}
|
||||
updateMCPServer({ ...server, isActive: active })
|
||||
updateMCPServer({ ...serverForUpdate, isActive: active })
|
||||
} catch (error: any) {
|
||||
window.modal.error({
|
||||
title: t('settings.mcp.startError'),
|
||||
content: formatMcpError(error as McpError),
|
||||
centered: true
|
||||
})
|
||||
updateMCPServer({ ...server, isActive: oldActiveState })
|
||||
updateMCPServer({ ...serverForUpdate, isActive: oldActiveState })
|
||||
} finally {
|
||||
setLoadingServer(null)
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
|
||||
interface ProtocolInstallWarningContentProps {
|
||||
message: string
|
||||
commandLabel: string
|
||||
commandPreview: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning content component for protocol-installed MCP servers
|
||||
* Displays a security warning and the command that will be executed
|
||||
*/
|
||||
const ProtocolInstallWarningContent: React.FC<ProtocolInstallWarningContentProps> = ({
|
||||
message,
|
||||
commandLabel,
|
||||
commandPreview
|
||||
}) => {
|
||||
return (
|
||||
<div className="space-y-3 text-left">
|
||||
<p>{message}</p>
|
||||
{commandPreview && (
|
||||
<div className="space-y-1">
|
||||
<div className="font-semibold">{commandLabel}</div>
|
||||
<pre className="whitespace-pre-wrap break-all rounded-md bg-[var(--color-fill-secondary)] p-2">
|
||||
{commandPreview}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProtocolInstallWarningContent
|
||||
58
src/renderer/src/pages/settings/MCPSettings/utils.ts
Normal file
58
src/renderer/src/pages/settings/MCPSettings/utils.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { MCPServer } from '@renderer/types'
|
||||
|
||||
const logger = loggerService.withContext('MCPSettings/utils')
|
||||
|
||||
/**
|
||||
* Get command preview string from MCP server configuration
|
||||
* @param server - The MCP server to extract command from
|
||||
* @returns Formatted command string with arguments
|
||||
*/
|
||||
export const getCommandPreview = (server: MCPServer): string => {
|
||||
return [server.command, ...(server.args ?? [])]
|
||||
.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a server is trusted before proceeding (pure logic, no UI)
|
||||
* @param currentServer - The server to verify trust for
|
||||
* @param requestConfirm - Callback to request user confirmation
|
||||
* @param updateServer - Callback to update server state
|
||||
* @returns The trusted server if confirmed, or null if user declined
|
||||
*/
|
||||
export async function ensureServerTrusted(
|
||||
currentServer: MCPServer,
|
||||
requestConfirm: (server: MCPServer) => Promise<boolean>,
|
||||
updateServer: (server: MCPServer) => void
|
||||
): Promise<MCPServer | null> {
|
||||
const isProtocolInstall = currentServer.installSource === 'protocol'
|
||||
|
||||
logger.silly('ensureServerTrusted', {
|
||||
serverId: currentServer.id,
|
||||
installSource: currentServer.installSource,
|
||||
isTrusted: currentServer.isTrusted
|
||||
})
|
||||
|
||||
// Early return if no trust verification needed
|
||||
if (!isProtocolInstall || currentServer.isTrusted) {
|
||||
return currentServer
|
||||
}
|
||||
|
||||
// Request user confirmation via callback
|
||||
const confirmed = await requestConfirm(currentServer)
|
||||
|
||||
if (!confirmed) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Update server with trust information
|
||||
const trustedServer = {
|
||||
...currentServer,
|
||||
installSource: 'protocol' as const,
|
||||
isTrusted: true,
|
||||
trustedAt: Date.now()
|
||||
}
|
||||
updateServer(trustedServer)
|
||||
return trustedServer
|
||||
}
|
||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 162,
|
||||
version: 163,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -79,7 +79,9 @@ export const builtinMCPServers: BuiltinMCPServer[] = [
|
||||
command: 'npx',
|
||||
args: ['-y', '@mcpmarket/mcp-auto-install', 'connect', '--json'],
|
||||
isActive: false,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
@ -91,14 +93,18 @@ export const builtinMCPServers: BuiltinMCPServer[] = [
|
||||
MEMORY_FILE_PATH: 'YOUR_MEMORY_FILE_PATH'
|
||||
},
|
||||
shouldConfig: true,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
name: BuiltinMCPServerNames.sequentialThinking,
|
||||
type: 'inMemory',
|
||||
isActive: true,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
@ -109,14 +115,18 @@ export const builtinMCPServers: BuiltinMCPServer[] = [
|
||||
BRAVE_API_KEY: 'YOUR_API_KEY'
|
||||
},
|
||||
shouldConfig: true,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
name: BuiltinMCPServerNames.fetch,
|
||||
type: 'inMemory',
|
||||
isActive: true,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
@ -125,7 +135,9 @@ export const builtinMCPServers: BuiltinMCPServer[] = [
|
||||
args: ['/Users/username/Desktop', '/path/to/other/allowed/dir'],
|
||||
shouldConfig: true,
|
||||
isActive: false,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
@ -136,14 +148,18 @@ export const builtinMCPServers: BuiltinMCPServer[] = [
|
||||
DIFY_KEY: 'YOUR_DIFY_KEY'
|
||||
},
|
||||
shouldConfig: true,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
name: BuiltinMCPServerNames.python,
|
||||
type: 'inMemory',
|
||||
isActive: false,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
@ -155,7 +171,9 @@ export const builtinMCPServers: BuiltinMCPServer[] = [
|
||||
DIDI_API_KEY: 'YOUR_DIDI_API_KEY'
|
||||
},
|
||||
shouldConfig: true,
|
||||
provider: 'CherryAI'
|
||||
provider: 'CherryAI',
|
||||
installSource: 'builtin',
|
||||
isTrusted: true
|
||||
}
|
||||
] as const
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ import { DEFAULT_ASSISTANT_SETTINGS } from '@renderer/services/AssistantService'
|
||||
import {
|
||||
Assistant,
|
||||
BuiltinOcrProvider,
|
||||
isBuiltinMCPServer,
|
||||
isSystemProvider,
|
||||
Model,
|
||||
Provider,
|
||||
@ -2591,6 +2592,23 @@ const migrateConfig = {
|
||||
logger.error('migrate 162 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'163': (state: RootState) => {
|
||||
try {
|
||||
if (state?.mcp?.servers) {
|
||||
state.mcp.servers = state.mcp.servers.map((server) => {
|
||||
const inferredSource = isBuiltinMCPServer(server) ? 'builtin' : 'unknown'
|
||||
return {
|
||||
...server,
|
||||
installSource: inferredSource
|
||||
}
|
||||
})
|
||||
}
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 169 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import type { StreamTextParams } from './aiCoreTypes'
|
||||
import type { Chunk } from './chunk'
|
||||
import type { FileMetadata } from './file'
|
||||
import { KnowledgeBase, KnowledgeReference } from './knowledge'
|
||||
import { MCPConfigSample, McpServerType } from './mcp'
|
||||
import { MCPConfigSample, MCPServerInstallSource, McpServerType } from './mcp'
|
||||
import type { Message } from './newMessage'
|
||||
import type { BaseTool, MCPTool } from './tool'
|
||||
|
||||
@ -830,6 +830,14 @@ export interface MCPServer {
|
||||
shouldConfig?: boolean
|
||||
/** 用于标记服务器是否运行中 */
|
||||
isActive: boolean
|
||||
/** 标记 MCP 安装来源,例如 builtin/manual/protocol */
|
||||
installSource?: MCPServerInstallSource
|
||||
/** 指示用户是否已信任该 MCP */
|
||||
isTrusted?: boolean
|
||||
/** 首次标记为信任的时间戳 */
|
||||
trustedAt?: number
|
||||
/** 安装时间戳 */
|
||||
installedAt?: number
|
||||
}
|
||||
|
||||
export type BuiltinMCPServer = MCPServer & {
|
||||
|
||||
@ -27,6 +27,9 @@ export const McpServerTypeSchema = z
|
||||
})
|
||||
.pipe(z.union([z.literal('stdio'), z.literal('sse'), z.literal('streamableHttp'), z.literal('inMemory')])) // 大多数情况下默认使用 stdio
|
||||
|
||||
export const MCPServerInstallSourceSchema = z.enum(['builtin', 'manual', 'protocol', 'unknown']).default('unknown')
|
||||
export type MCPServerInstallSource = z.infer<typeof MCPServerInstallSourceSchema>
|
||||
|
||||
/**
|
||||
* 定义单个 MCP 服务器的配置。
|
||||
* FIXME: 为了兼容性,暂时允许用户编辑任意字段,这可能会导致问题。
|
||||
@ -168,7 +171,11 @@ export const McpServerConfigSchema = z
|
||||
* 是否激活
|
||||
* 可选。用于标识服务器是否处于激活状态。
|
||||
*/
|
||||
isActive: z.boolean().optional().describe('Whether the server is active')
|
||||
isActive: z.boolean().optional().describe('Whether the server is active'),
|
||||
installSource: MCPServerInstallSourceSchema.optional().describe('Where the MCP server was installed from'),
|
||||
isTrusted: z.boolean().optional().describe('Whether the MCP server has been trusted by user'),
|
||||
trustedAt: z.number().optional().describe('Timestamp when the server was trusted'),
|
||||
installedAt: z.number().optional().describe('Timestamp when the server was installed')
|
||||
})
|
||||
.strict()
|
||||
// 在这里定义额外的校验逻辑
|
||||
|
||||
Loading…
Reference in New Issue
Block a user