From 6376bbb9a7ae7dcaf30543aad2f2cd5ad24fa343 Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Wed, 27 Aug 2025 22:46:42 +0800 Subject: [PATCH] fix: mcp-auto-install cannot start (#9015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(types): 将内置MCPServer类型从MCPServer分离并添加类型守卫 将内置MCPServer相关逻辑从通用MCPServer类型中分离,新增BuiltinMCPServer类型和类型守卫函数 * refactor(MCPService): 使用isBuiltinMCPServer检查内置服务器类型 修改传输层创建逻辑,通过isBuiltinMCPServer函数判断是否为内置服务器,并排除特定服务器 * refactor(types): 统一内置MCP服务器名称的键值格式并优化类型定义 将BuiltinMCPServers对象的键改为与值相同的格式,使结构更一致 同时简化BuiltinMCPServerName类型定义和isBuiltinMCPServerName检查逻辑 * refactor(types): 为内置MCP服务器名称添加类型定义 为`MCP_AUTO_INSTALL_SERVER_NAME`和`builtInMcpDescriptionKeyMap`添加`BuiltinMCPServerName`类型定义,提高类型安全性 * refactor(types): 重命名BuiltinMCPServers为BuiltinMCPServerNames以更准确描述用途 * style: 移除 TabContainer 组件中的多余空行 * refactor(mcpServers): 使用类型化的BuiltinMCPServerName替换字符串参数 将createInMemoryMCPServer函数的name参数从string类型改为BuiltinMCPServerName类型,提高类型安全性 * refactor(types): 重构内置MCPServer名称常量及类型定义 将BuiltinMCPServerNames的键名改为驼峰命名,并添加BuiltinMCPServerNamesArray常量 修改isBuiltinMCPServerName函数使用数组进行判断 * refactor(mcp): 使用枚举替换硬编码的服务器名称字符串 --- src/main/mcpServers/factory.ts | 21 +++++++++++++-------- src/main/services/MCPService.ts | 13 +++++++++++-- src/renderer/src/i18n/label.ts | 19 ++++++++++--------- src/renderer/src/store/mcp.ts | 22 +++++++++++----------- src/renderer/src/types/index.ts | 28 ++++++++++++++++++++++++++++ src/renderer/src/utils/mcp-tools.ts | 5 ++--- 6 files changed, 75 insertions(+), 33 deletions(-) diff --git a/src/main/mcpServers/factory.ts b/src/main/mcpServers/factory.ts index fe1269cec2..46d3bb87d2 100644 --- a/src/main/mcpServers/factory.ts +++ b/src/main/mcpServers/factory.ts @@ -1,5 +1,6 @@ import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' +import { BuiltinMCPServerName, BuiltinMCPServerNames } from '@types' import BraveSearchServer from './brave-search' import DifyKnowledgeServer from './dify-knowledge' @@ -11,30 +12,34 @@ import ThinkingServer from './sequentialthinking' const logger = loggerService.withContext('MCPFactory') -export function createInMemoryMCPServer(name: string, args: string[] = [], envs: Record = {}): Server { +export function createInMemoryMCPServer( + name: BuiltinMCPServerName, + args: string[] = [], + envs: Record = {} +): Server { logger.debug(`[MCP] Creating in-memory MCP server: ${name} with args: ${args} and envs: ${JSON.stringify(envs)}`) switch (name) { - case '@cherry/memory': { + case BuiltinMCPServerNames.memory: { const envPath = envs.MEMORY_FILE_PATH return new MemoryServer(envPath).server } - case '@cherry/sequentialthinking': { + case BuiltinMCPServerNames.sequentialThinking: { return new ThinkingServer().server } - case '@cherry/brave-search': { + case BuiltinMCPServerNames.braveSearch: { return new BraveSearchServer(envs.BRAVE_API_KEY).server } - case '@cherry/fetch': { + case BuiltinMCPServerNames.fetch: { return new FetchServer().server } - case '@cherry/filesystem': { + case BuiltinMCPServerNames.filesystem: { return new FileSystemServer(args).server } - case '@cherry/dify-knowledge': { + case BuiltinMCPServerNames.difyKnowledge: { const difyKey = envs.DIFY_KEY return new DifyKnowledgeServer(difyKey, args).server } - case '@cherry/python': { + case BuiltinMCPServerNames.python: { return new PythonServer().server } default: diff --git a/src/main/services/MCPService.ts b/src/main/services/MCPService.ts index 679bfd9b35..9e2c3f88d7 100644 --- a/src/main/services/MCPService.ts +++ b/src/main/services/MCPService.ts @@ -27,7 +27,16 @@ import { ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js' import { nanoid } from '@reduxjs/toolkit' -import type { GetResourceResponse, MCPCallToolResponse, MCPPrompt, MCPResource, MCPServer, MCPTool } from '@types' +import { + BuiltinMCPServerNames, + type GetResourceResponse, + isBuiltinMCPServer, + type MCPCallToolResponse, + type MCPPrompt, + type MCPResource, + type MCPServer, + type MCPTool +} from '@types' import { app, net } from 'electron' import { EventEmitter } from 'events' import { memoize } from 'lodash' @@ -162,7 +171,7 @@ class McpService { StdioClientTransport | SSEClientTransport | InMemoryTransport | StreamableHTTPClientTransport > => { // Create appropriate transport based on configuration - if (server.type === 'inMemory') { + if (isBuiltinMCPServer(server) && server.name !== BuiltinMCPServerNames.mcpAutoInstall) { logger.debug(`Using in-memory transport for server: ${server.name}`) const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair() // start the in-memory server with the given name and environment variables diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index 3724ef04c5..31bcd44287 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -5,6 +5,7 @@ */ import { loggerService } from '@logger' +import { BuiltinMCPServerName, BuiltinMCPServerNames } from '@renderer/types' import { ThinkingOption } from '@renderer/types' import i18n from './index' @@ -292,15 +293,15 @@ export const getFileFieldLabel = (key: string): string => { return getLabel(key, fileFieldKeyMap) } -const builtInMcpDescriptionKeyMap = { - '@cherry/mcp-auto-install': 'settings.mcp.builtinServersDescriptions.mcp_auto_install', - '@cherry/memory': 'settings.mcp.builtinServersDescriptions.memory', - '@cherry/sequentialthinking': 'settings.mcp.builtinServersDescriptions.sequentialthinking', - '@cherry/brave-search': 'settings.mcp.builtinServersDescriptions.brave_search', - '@cherry/fetch': 'settings.mcp.builtinServersDescriptions.fetch', - '@cherry/filesystem': 'settings.mcp.builtinServersDescriptions.filesystem', - '@cherry/dify-knowledge': 'settings.mcp.builtinServersDescriptions.dify_knowledge', - '@cherry/python': 'settings.mcp.builtinServersDescriptions.python' +const builtInMcpDescriptionKeyMap: Record = { + [BuiltinMCPServerNames.mcpAutoInstall]: 'settings.mcp.builtinServersDescriptions.mcp_auto_install', + [BuiltinMCPServerNames.memory]: 'settings.mcp.builtinServersDescriptions.memory', + [BuiltinMCPServerNames.sequentialThinking]: 'settings.mcp.builtinServersDescriptions.sequentialthinking', + [BuiltinMCPServerNames.braveSearch]: 'settings.mcp.builtinServersDescriptions.brave_search', + [BuiltinMCPServerNames.fetch]: 'settings.mcp.builtinServersDescriptions.fetch', + [BuiltinMCPServerNames.filesystem]: 'settings.mcp.builtinServersDescriptions.filesystem', + [BuiltinMCPServerNames.difyKnowledge]: 'settings.mcp.builtinServersDescriptions.dify_knowledge', + [BuiltinMCPServerNames.python]: 'settings.mcp.builtinServersDescriptions.python' } as const export const getBuiltInMcpServerDescriptionLabel = (key: string): string => { diff --git a/src/renderer/src/store/mcp.ts b/src/renderer/src/store/mcp.ts index 1f4a64f0bc..2bae82c147 100644 --- a/src/renderer/src/store/mcp.ts +++ b/src/renderer/src/store/mcp.ts @@ -1,6 +1,6 @@ import { loggerService } from '@logger' import { createSlice, nanoid, type PayloadAction } from '@reduxjs/toolkit' -import type { MCPConfig, MCPServer } from '@renderer/types' +import { type BuiltinMCPServer, BuiltinMCPServerNames, type MCPConfig, type MCPServer } from '@renderer/types' const logger = loggerService.withContext('Store:MCP') @@ -70,10 +70,10 @@ export { mcpSlice } // Export the reducer as default export export default mcpSlice.reducer -export const builtinMCPServers: MCPServer[] = [ +export const builtinMCPServers: BuiltinMCPServer[] = [ { id: nanoid(), - name: '@cherry/mcp-auto-install', + name: BuiltinMCPServerNames.mcpAutoInstall, reference: 'https://docs.cherry-ai.com/advanced-basic/mcp/auto-install', type: 'inMemory', command: 'npx', @@ -83,7 +83,7 @@ export const builtinMCPServers: MCPServer[] = [ }, { id: nanoid(), - name: '@cherry/memory', + name: BuiltinMCPServerNames.memory, reference: 'https://github.com/modelcontextprotocol/servers/tree/main/src/memory', type: 'inMemory', isActive: true, @@ -95,14 +95,14 @@ export const builtinMCPServers: MCPServer[] = [ }, { id: nanoid(), - name: '@cherry/sequentialthinking', + name: BuiltinMCPServerNames.sequentialThinking, type: 'inMemory', isActive: true, provider: 'CherryAI' }, { id: nanoid(), - name: '@cherry/brave-search', + name: BuiltinMCPServerNames.braveSearch, type: 'inMemory', isActive: false, env: { @@ -113,14 +113,14 @@ export const builtinMCPServers: MCPServer[] = [ }, { id: nanoid(), - name: '@cherry/fetch', + name: BuiltinMCPServerNames.fetch, type: 'inMemory', isActive: true, provider: 'CherryAI' }, { id: nanoid(), - name: '@cherry/filesystem', + name: BuiltinMCPServerNames.filesystem, type: 'inMemory', args: ['/Users/username/Desktop', '/path/to/other/allowed/dir'], shouldConfig: true, @@ -129,7 +129,7 @@ export const builtinMCPServers: MCPServer[] = [ }, { id: nanoid(), - name: '@cherry/dify-knowledge', + name: BuiltinMCPServerNames.difyKnowledge, type: 'inMemory', isActive: false, env: { @@ -140,12 +140,12 @@ export const builtinMCPServers: MCPServer[] = [ }, { id: nanoid(), - name: '@cherry/python', + name: BuiltinMCPServerNames.python, type: 'inMemory', isActive: false, provider: 'CherryAI' } -] +] as const /** * Utility function to add servers to the MCP store during app initialization diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 76fbf9a687..ab7905fcee 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -852,6 +852,34 @@ export interface MCPServer { reference?: string // Reference link for the server, e.g., documentation or homepage } +export type BuiltinMCPServer = MCPServer & { + type: 'inMemory' + name: BuiltinMCPServerName +} + +export const isBuiltinMCPServer = (server: MCPServer): server is BuiltinMCPServer => { + return server.type === 'inMemory' && isBuiltinMCPServerName(server.name) +} + +export const BuiltinMCPServerNames = { + mcpAutoInstall: '@cherry/mcp-auto-install', + memory: '@cherry/memory', + sequentialThinking: '@cherry/sequentialthinking', + braveSearch: '@cherry/brave-search', + fetch: '@cherry/fetch', + filesystem: '@cherry/filesystem', + difyKnowledge: '@cherry/dify-knowledge', + python: '@cherry/python' +} as const + +export type BuiltinMCPServerName = (typeof BuiltinMCPServerNames)[keyof typeof BuiltinMCPServerNames] + +export const BuiltinMCPServerNamesArray = Object.values(BuiltinMCPServerNames) + +export const isBuiltinMCPServerName = (name: string): name is BuiltinMCPServerName => { + return BuiltinMCPServerNamesArray.some((n) => n === name) +} + export interface MCPToolInputSchema { type: string title: string diff --git a/src/renderer/src/utils/mcp-tools.ts b/src/renderer/src/utils/mcp-tools.ts index 3be44b1091..a399e6dc65 100644 --- a/src/renderer/src/utils/mcp-tools.ts +++ b/src/renderer/src/utils/mcp-tools.ts @@ -8,6 +8,7 @@ import store from '@renderer/store' import { addMCPServer } from '@renderer/store/mcp' import { Assistant, + BuiltinMCPServerNames, MCPCallToolResponse, MCPServer, MCPTool, @@ -34,8 +35,6 @@ import { filterProperties, processSchemaForO3 } from './mcp-schema' const logger = loggerService.withContext('Utils:MCPTools') -const MCP_AUTO_INSTALL_SERVER_NAME = '@cherry/mcp-auto-install' - export function mcpToolsToOpenAIResponseTools(mcpTools: MCPTool[]): OpenAI.Responses.Tool[] { return mcpTools.map((tool) => { const parameters = processSchemaForO3(tool.inputSchema) @@ -147,7 +146,7 @@ export async function callMCPTool( }, topicId ? currentSpan(topicId, modelName)?.spanContext() : undefined ) - if (toolResponse.tool.serverName === MCP_AUTO_INSTALL_SERVER_NAME) { + if (toolResponse.tool.serverName === BuiltinMCPServerNames.mcpAutoInstall) { if (resp.data) { const mcpServer: MCPServer = { id: `f${nanoid()}`,