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
This commit is contained in:
parent
aa75f90294
commit
aae12a21ac
@ -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<string, any> = 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<void> | 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Props> = ({ provider: _provider, resolve }) => {
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch (filterType) {
|
||||
case 'reasoning':
|
||||
return isReasoningModel(model)
|
||||
@ -66,6 +68,8 @@ const PopupContainer: React.FC<Props> = ({ 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<Props> = ({ provider: _provider, resolve }) => {
|
||||
<Radio.Button value="websearch">{t('models.websearch')}</Radio.Button>
|
||||
<Radio.Button value="free">{t('models.free')}</Radio.Button>
|
||||
<Radio.Button value="embedding">{t('models.embedding')}</Radio.Button>
|
||||
<Radio.Button value="function_calling">{t('models.function_calling')}</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Center>
|
||||
<Search
|
||||
|
||||
@ -113,7 +113,7 @@ const ModelEditContent: FC<ModelEditContentProps> = ({ model, onUpdateModel, ope
|
||||
...(isVisionModel(model) ? ['vision'] : []),
|
||||
...(isEmbeddingModel(model) ? ['embedding'] : []),
|
||||
...(isReasoningModel(model) ? ['reasoning'] : []),
|
||||
...(isFunctionCallingModel(model) ? ['tools'] : [])
|
||||
...(isFunctionCallingModel(model) ? ['function_calling'] : [])
|
||||
] as ModelType[]
|
||||
|
||||
// 合并现有选择和默认类型
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user