mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 13:59:28 +08:00
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:
parent
c01642ef22
commit
6376bbb9a7
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 => {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()}`,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user