fix: mcp-auto-install cannot start (#9015)

* 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): 使用枚举替换硬编码的服务器名称字符串
This commit is contained in:
Phantom 2025-08-27 22:46:42 +08:00 committed by GitHub
parent c01642ef22
commit 6376bbb9a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 75 additions and 33 deletions

View File

@ -1,5 +1,6 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { BuiltinMCPServerName, BuiltinMCPServerNames } from '@types'
import BraveSearchServer from './brave-search' import BraveSearchServer from './brave-search'
import DifyKnowledgeServer from './dify-knowledge' import DifyKnowledgeServer from './dify-knowledge'
@ -11,30 +12,34 @@ import ThinkingServer from './sequentialthinking'
const logger = loggerService.withContext('MCPFactory') const logger = loggerService.withContext('MCPFactory')
export function createInMemoryMCPServer(name: string, args: string[] = [], envs: Record<string, string> = {}): Server { export function createInMemoryMCPServer(
name: BuiltinMCPServerName,
args: string[] = [],
envs: Record<string, string> = {}
): Server {
logger.debug(`[MCP] Creating in-memory MCP server: ${name} with args: ${args} and envs: ${JSON.stringify(envs)}`) logger.debug(`[MCP] Creating in-memory MCP server: ${name} with args: ${args} and envs: ${JSON.stringify(envs)}`)
switch (name) { switch (name) {
case '@cherry/memory': { case BuiltinMCPServerNames.memory: {
const envPath = envs.MEMORY_FILE_PATH const envPath = envs.MEMORY_FILE_PATH
return new MemoryServer(envPath).server return new MemoryServer(envPath).server
} }
case '@cherry/sequentialthinking': { case BuiltinMCPServerNames.sequentialThinking: {
return new ThinkingServer().server return new ThinkingServer().server
} }
case '@cherry/brave-search': { case BuiltinMCPServerNames.braveSearch: {
return new BraveSearchServer(envs.BRAVE_API_KEY).server return new BraveSearchServer(envs.BRAVE_API_KEY).server
} }
case '@cherry/fetch': { case BuiltinMCPServerNames.fetch: {
return new FetchServer().server return new FetchServer().server
} }
case '@cherry/filesystem': { case BuiltinMCPServerNames.filesystem: {
return new FileSystemServer(args).server return new FileSystemServer(args).server
} }
case '@cherry/dify-knowledge': { case BuiltinMCPServerNames.difyKnowledge: {
const difyKey = envs.DIFY_KEY const difyKey = envs.DIFY_KEY
return new DifyKnowledgeServer(difyKey, args).server return new DifyKnowledgeServer(difyKey, args).server
} }
case '@cherry/python': { case BuiltinMCPServerNames.python: {
return new PythonServer().server return new PythonServer().server
} }
default: default:

View File

@ -27,7 +27,16 @@ import {
ToolListChangedNotificationSchema ToolListChangedNotificationSchema
} from '@modelcontextprotocol/sdk/types.js' } from '@modelcontextprotocol/sdk/types.js'
import { nanoid } from '@reduxjs/toolkit' 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 { app, net } from 'electron'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { memoize } from 'lodash' import { memoize } from 'lodash'
@ -162,7 +171,7 @@ class McpService {
StdioClientTransport | SSEClientTransport | InMemoryTransport | StreamableHTTPClientTransport StdioClientTransport | SSEClientTransport | InMemoryTransport | StreamableHTTPClientTransport
> => { > => {
// Create appropriate transport based on configuration // 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}`) logger.debug(`Using in-memory transport for server: ${server.name}`)
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair() const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
// start the in-memory server with the given name and environment variables // start the in-memory server with the given name and environment variables

View File

@ -5,6 +5,7 @@
*/ */
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { BuiltinMCPServerName, BuiltinMCPServerNames } from '@renderer/types'
import { ThinkingOption } from '@renderer/types' import { ThinkingOption } from '@renderer/types'
import i18n from './index' import i18n from './index'
@ -292,15 +293,15 @@ export const getFileFieldLabel = (key: string): string => {
return getLabel(key, fileFieldKeyMap) return getLabel(key, fileFieldKeyMap)
} }
const builtInMcpDescriptionKeyMap = { const builtInMcpDescriptionKeyMap: Record<BuiltinMCPServerName, string> = {
'@cherry/mcp-auto-install': 'settings.mcp.builtinServersDescriptions.mcp_auto_install', [BuiltinMCPServerNames.mcpAutoInstall]: 'settings.mcp.builtinServersDescriptions.mcp_auto_install',
'@cherry/memory': 'settings.mcp.builtinServersDescriptions.memory', [BuiltinMCPServerNames.memory]: 'settings.mcp.builtinServersDescriptions.memory',
'@cherry/sequentialthinking': 'settings.mcp.builtinServersDescriptions.sequentialthinking', [BuiltinMCPServerNames.sequentialThinking]: 'settings.mcp.builtinServersDescriptions.sequentialthinking',
'@cherry/brave-search': 'settings.mcp.builtinServersDescriptions.brave_search', [BuiltinMCPServerNames.braveSearch]: 'settings.mcp.builtinServersDescriptions.brave_search',
'@cherry/fetch': 'settings.mcp.builtinServersDescriptions.fetch', [BuiltinMCPServerNames.fetch]: 'settings.mcp.builtinServersDescriptions.fetch',
'@cherry/filesystem': 'settings.mcp.builtinServersDescriptions.filesystem', [BuiltinMCPServerNames.filesystem]: 'settings.mcp.builtinServersDescriptions.filesystem',
'@cherry/dify-knowledge': 'settings.mcp.builtinServersDescriptions.dify_knowledge', [BuiltinMCPServerNames.difyKnowledge]: 'settings.mcp.builtinServersDescriptions.dify_knowledge',
'@cherry/python': 'settings.mcp.builtinServersDescriptions.python' [BuiltinMCPServerNames.python]: 'settings.mcp.builtinServersDescriptions.python'
} as const } as const
export const getBuiltInMcpServerDescriptionLabel = (key: string): string => { export const getBuiltInMcpServerDescriptionLabel = (key: string): string => {

View File

@ -1,6 +1,6 @@
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { createSlice, nanoid, type PayloadAction } from '@reduxjs/toolkit' 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') const logger = loggerService.withContext('Store:MCP')
@ -70,10 +70,10 @@ export { mcpSlice }
// Export the reducer as default export // Export the reducer as default export
export default mcpSlice.reducer export default mcpSlice.reducer
export const builtinMCPServers: MCPServer[] = [ export const builtinMCPServers: BuiltinMCPServer[] = [
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/mcp-auto-install', name: BuiltinMCPServerNames.mcpAutoInstall,
reference: 'https://docs.cherry-ai.com/advanced-basic/mcp/auto-install', reference: 'https://docs.cherry-ai.com/advanced-basic/mcp/auto-install',
type: 'inMemory', type: 'inMemory',
command: 'npx', command: 'npx',
@ -83,7 +83,7 @@ export const builtinMCPServers: MCPServer[] = [
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/memory', name: BuiltinMCPServerNames.memory,
reference: 'https://github.com/modelcontextprotocol/servers/tree/main/src/memory', reference: 'https://github.com/modelcontextprotocol/servers/tree/main/src/memory',
type: 'inMemory', type: 'inMemory',
isActive: true, isActive: true,
@ -95,14 +95,14 @@ export const builtinMCPServers: MCPServer[] = [
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/sequentialthinking', name: BuiltinMCPServerNames.sequentialThinking,
type: 'inMemory', type: 'inMemory',
isActive: true, isActive: true,
provider: 'CherryAI' provider: 'CherryAI'
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/brave-search', name: BuiltinMCPServerNames.braveSearch,
type: 'inMemory', type: 'inMemory',
isActive: false, isActive: false,
env: { env: {
@ -113,14 +113,14 @@ export const builtinMCPServers: MCPServer[] = [
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/fetch', name: BuiltinMCPServerNames.fetch,
type: 'inMemory', type: 'inMemory',
isActive: true, isActive: true,
provider: 'CherryAI' provider: 'CherryAI'
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/filesystem', name: BuiltinMCPServerNames.filesystem,
type: 'inMemory', type: 'inMemory',
args: ['/Users/username/Desktop', '/path/to/other/allowed/dir'], args: ['/Users/username/Desktop', '/path/to/other/allowed/dir'],
shouldConfig: true, shouldConfig: true,
@ -129,7 +129,7 @@ export const builtinMCPServers: MCPServer[] = [
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/dify-knowledge', name: BuiltinMCPServerNames.difyKnowledge,
type: 'inMemory', type: 'inMemory',
isActive: false, isActive: false,
env: { env: {
@ -140,12 +140,12 @@ export const builtinMCPServers: MCPServer[] = [
}, },
{ {
id: nanoid(), id: nanoid(),
name: '@cherry/python', name: BuiltinMCPServerNames.python,
type: 'inMemory', type: 'inMemory',
isActive: false, isActive: false,
provider: 'CherryAI' provider: 'CherryAI'
} }
] ] as const
/** /**
* Utility function to add servers to the MCP store during app initialization * Utility function to add servers to the MCP store during app initialization

View File

@ -852,6 +852,34 @@ export interface MCPServer {
reference?: string // Reference link for the server, e.g., documentation or homepage 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 { export interface MCPToolInputSchema {
type: string type: string
title: string title: string

View File

@ -8,6 +8,7 @@ import store from '@renderer/store'
import { addMCPServer } from '@renderer/store/mcp' import { addMCPServer } from '@renderer/store/mcp'
import { import {
Assistant, Assistant,
BuiltinMCPServerNames,
MCPCallToolResponse, MCPCallToolResponse,
MCPServer, MCPServer,
MCPTool, MCPTool,
@ -34,8 +35,6 @@ import { filterProperties, processSchemaForO3 } from './mcp-schema'
const logger = loggerService.withContext('Utils:MCPTools') const logger = loggerService.withContext('Utils:MCPTools')
const MCP_AUTO_INSTALL_SERVER_NAME = '@cherry/mcp-auto-install'
export function mcpToolsToOpenAIResponseTools(mcpTools: MCPTool[]): OpenAI.Responses.Tool[] { export function mcpToolsToOpenAIResponseTools(mcpTools: MCPTool[]): OpenAI.Responses.Tool[] {
return mcpTools.map((tool) => { return mcpTools.map((tool) => {
const parameters = processSchemaForO3(tool.inputSchema) const parameters = processSchemaForO3(tool.inputSchema)
@ -147,7 +146,7 @@ export async function callMCPTool(
}, },
topicId ? currentSpan(topicId, modelName)?.spanContext() : undefined topicId ? currentSpan(topicId, modelName)?.spanContext() : undefined
) )
if (toolResponse.tool.serverName === MCP_AUTO_INSTALL_SERVER_NAME) { if (toolResponse.tool.serverName === BuiltinMCPServerNames.mcpAutoInstall) {
if (resp.data) { if (resp.data) {
const mcpServer: MCPServer = { const mcpServer: MCPServer = {
id: `f${nanoid()}`, id: `f${nanoid()}`,