From aae12a21ac0c153485b83be4ffc00804c65a741d Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Wed, 12 Mar 2025 18:25:04 +0800 Subject: [PATCH] feat(MCPService, ModelSettings): Enhance path handling and model filtering - Add enhanced PATH generation for MCP service across different platforms - Improve model filtering with new function calling model type - Refactor MCP service type definitions and transport initialization - Add platform-specific path handling for various development environments --- src/main/services/MCPService.ts | 89 +++++++++++++++---- .../ProviderSettings/EditModelsPopup.tsx | 5 ++ .../ProviderSettings/ModelEditContent.tsx | 2 +- 3 files changed, 80 insertions(+), 16 deletions(-) diff --git a/src/main/services/MCPService.ts b/src/main/services/MCPService.ts index 89c0ff10..2d8f5f4e 100644 --- a/src/main/services/MCPService.ts +++ b/src/main/services/MCPService.ts @@ -1,3 +1,7 @@ +import { isLinux, isMac, isWin } from '@main/constant' +import type { Client } from '@modelcontextprotocol/sdk/client/index.js' +import type { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' +import type { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { MCPServer, MCPTool } from '@types' import log from 'electron-log' import { EventEmitter } from 'events' @@ -12,9 +16,9 @@ export default class MCPService extends EventEmitter { private servers: MCPServer[] = [] private activeServers: Map = new Map() private clients: { [key: string]: any } = {} - private Client: any - private stoioTransport: any - private sseTransport: any + private Client: typeof Client | undefined + private stdioTransport: typeof StdioClientTransport | undefined + private sseTransport: typeof SSEClientTransport | undefined private initialized = false private initPromise: Promise | null = null @@ -84,7 +88,7 @@ export default class MCPService extends EventEmitter { ]) this.Client = Client - this.stoioTransport = StdioTransport + this.stdioTransport = StdioTransport this.sseTransport = SSETransport // Mark as initialized before loading servers @@ -295,35 +299,33 @@ export default class MCPService extends EventEmitter { return } - let transport: any = null + let transport: StdioClientTransport | SSEClientTransport try { // Create appropriate transport based on configuration if (baseUrl) { - transport = new this.sseTransport(new URL(baseUrl)) + transport = new this.sseTransport!(new URL(baseUrl)) } else if (command) { let cmd: string = command if (command === 'npx') { cmd = process.platform === 'win32' ? `${command}.cmd` : command } - const mergedEnv = { - ...env, - PATH: process.env.PATH - } - - transport = new this.stoioTransport({ + transport = new this.stdioTransport!({ command: cmd, args, - stderr: process.platform === 'win32' ? 'pipe' : 'inherit', - env: mergedEnv + stderr: 'pipe', + env: { + PATH: this.getEnhancedPath(process.env.PATH || ''), + ...env + } }) } else { throw new Error('Either baseUrl or command must be provided') } // Create and connect client - const client = new this.Client({ name, version: '1.0.0' }, { capabilities: {} }) + const client = new this.Client!({ name, version: '1.0.0' }, { capabilities: {} }) await client.connect(transport) @@ -491,4 +493,61 @@ export default class MCPService extends EventEmitter { log.info(`[MCP] Loaded and activated ${Object.keys(this.clients).length} servers`) } + + /** + * Get enhanced PATH including common tool locations + */ + private getEnhancedPath(originalPath: string): string { + // 将原始 PATH 按分隔符分割成数组 + const pathSeparator = process.platform === 'win32' ? ';' : ':' + const existingPaths = new Set(originalPath.split(pathSeparator).filter(Boolean)) + const homeDir = process.env.HOME || process.env.USERPROFILE || '' + + // 定义要添加的新路径 + const newPaths: string[] = [] + + if (isMac) { + newPaths.push( + '/bin', + '/usr/bin', + '/usr/local/bin', + '/usr/local/sbin', + '/opt/homebrew/bin', + '/opt/homebrew/sbin', + '/usr/local/opt/node/bin', + `${homeDir}/.nvm/current/bin`, + `${homeDir}/.npm-global/bin`, + `${homeDir}/.yarn/bin`, + `${homeDir}/.cargo/bin`, + '/opt/local/bin' + ) + } + + if (isLinux) { + newPaths.push( + '/bin', + '/usr/bin', + '/usr/local/bin', + `${homeDir}/.nvm/current/bin`, + `${homeDir}/.npm-global/bin`, + `${homeDir}/.yarn/bin`, + `${homeDir}/.cargo/bin`, + '/snap/bin' + ) + } + + if (isWin) { + newPaths.push(`${process.env.APPDATA}\\npm`, `${homeDir}\\AppData\\Local\\Yarn\\bin`, `${homeDir}\\.cargo\\bin`) + } + + // 只添加不存在的路径 + newPaths.forEach((path) => { + if (path && !existingPaths.has(path)) { + existingPaths.add(path) + } + }) + + // 转换回字符串 + return Array.from(existingPaths).join(pathSeparator) + } } diff --git a/src/renderer/src/pages/settings/ProviderSettings/EditModelsPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/EditModelsPopup.tsx index aa338a30..a0f0283d 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/EditModelsPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/EditModelsPopup.tsx @@ -4,6 +4,7 @@ import ModelTags from '@renderer/components/ModelTags' import { getModelLogo, isEmbeddingModel, + isFunctionCallingModel, isReasoningModel, isVisionModel, isWebSearchModel, @@ -55,6 +56,7 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { ) { return false } + switch (filterType) { case 'reasoning': return isReasoningModel(model) @@ -66,6 +68,8 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { return isFreeModel(model) case 'embedding': return isEmbeddingModel(model) + case 'function_calling': + return isFunctionCallingModel(model) default: return true } @@ -159,6 +163,7 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { {t('models.websearch')} {t('models.free')} {t('models.embedding')} + {t('models.function_calling')} = ({ model, onUpdateModel, ope ...(isVisionModel(model) ? ['vision'] : []), ...(isEmbeddingModel(model) ? ['embedding'] : []), ...(isReasoningModel(model) ? ['reasoning'] : []), - ...(isFunctionCallingModel(model) ? ['tools'] : []) + ...(isFunctionCallingModel(model) ? ['function_calling'] : []) ] as ModelType[] // 合并现有选择和默认类型