diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 85a6667d34..f3894dc97b 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -77,7 +77,6 @@ export enum IpcChannel { Mcp_ServersUpdated = 'mcp:servers-updated', Mcp_CheckConnectivity = 'mcp:check-connectivity', Mcp_UploadDxt = 'mcp:upload-dxt', - Mcp_SetProgress = 'mcp:set-progress', Mcp_AbortTool = 'mcp:abort-tool', Mcp_GetServerVersion = 'mcp:get-server-version', diff --git a/src/main/ipc.ts b/src/main/ipc.ts index a71f3cc8cf..899a280187 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -575,9 +575,6 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle(IpcChannel.Mcp_CheckConnectivity, mcpService.checkMcpConnectivity) ipcMain.handle(IpcChannel.Mcp_AbortTool, mcpService.abortTool) ipcMain.handle(IpcChannel.Mcp_GetServerVersion, mcpService.getServerVersion) - ipcMain.handle(IpcChannel.Mcp_SetProgress, (_, progress: number) => { - mainWindow.webContents.send('mcp-progress', progress) - }) // DXT upload handler ipcMain.handle(IpcChannel.Mcp_UploadDxt, async (event, fileBuffer: ArrayBuffer, fileName: string) => { diff --git a/src/main/services/MCPService.ts b/src/main/services/MCPService.ts index 0db4f43228..0be226ccd6 100644 --- a/src/main/services/MCPService.ts +++ b/src/main/services/MCPService.ts @@ -46,6 +46,7 @@ import DxtService from './DxtService' import { CallBackServer } from './mcp/oauth/callback' import { McpOAuthClientProvider } from './mcp/oauth/provider' import getLoginShellEnvironment from './mcp/shell-env' +import { windowService } from './WindowService' // Generic type for caching wrapped functions type CachedFunction = (...args: T) => Promise @@ -440,6 +441,10 @@ class McpService { // Set up progress notification handler client.setNotificationHandler(ProgressNotificationSchema, async (notification) => { logger.debug(`Progress notification received for server: ${server.name}`, notification.params) + const mainWindow = windowService.getMainWindow() + if (mainWindow) { + mainWindow.webContents.send('mcp-progress', notification.params.progress / (notification.params.total || 1)) + } }) // Set up cancelled notification handler @@ -614,7 +619,7 @@ class McpService { const callToolFunc = async ({ server, name, args }: CallToolArgs) => { try { - logger.debug(`Calling: ${server.name} ${name} ${JSON.stringify(args)} callId: ${toolCallId}`) + logger.debug(`Calling: ${server.name} ${name} ${JSON.stringify(args)} callId: ${toolCallId}`, server) if (typeof args === 'string') { try { args = JSON.parse(args) @@ -629,9 +634,12 @@ class McpService { const result = await client.callTool({ name, arguments: args }, undefined, { onprogress: (process) => { logger.debug(`Progress: ${process.progress / (process.total || 1)}`) - window.api.mcp.setProgress(process.progress / (process.total || 1)) }, - timeout: server.timeout ? server.timeout * 1000 : 60000, // Default timeout of 1 minute + timeout: server.timeout ? server.timeout * 1000 : 60000, // Default timeout of 1 minute, + // 需要服务端支持: https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#timeouts + // Need server side support: https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#timeouts + resetTimeoutOnProgress: server.longRunning, + maxTotalTimeout: server.longRunning ? 10 * 60 * 1000 : undefined, signal: this.activeToolCalls.get(toolCallId)?.signal }) return result as MCPCallToolResponse diff --git a/src/preload/index.ts b/src/preload/index.ts index 6d0f59f270..abf3d39593 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -292,7 +292,6 @@ const api = { return ipcRenderer.invoke(IpcChannel.Mcp_UploadDxt, buffer, file.name) }, abortTool: (callId: string) => ipcRenderer.invoke(IpcChannel.Mcp_AbortTool, callId), - setProgress: (progress: number) => ipcRenderer.invoke(IpcChannel.Mcp_SetProgress, progress), getServerVersion: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_GetServerVersion, server) }, python: { diff --git a/src/renderer/src/pages/home/Messages/MessageTools.tsx b/src/renderer/src/pages/home/Messages/MessageTools.tsx index 4f2d54ee50..41c26e7bda 100644 --- a/src/renderer/src/pages/home/Messages/MessageTools.tsx +++ b/src/renderer/src/pages/home/Messages/MessageTools.tsx @@ -6,7 +6,18 @@ import { useSettings } from '@renderer/hooks/useSettings' import type { ToolMessageBlock } from '@renderer/types/newMessage' import { isToolAutoApproved } from '@renderer/utils/mcp-tools' import { cancelToolAction, confirmToolAction } from '@renderer/utils/userConfirmation' -import { Button, Collapse, ConfigProvider, Dropdown, Flex, message as antdMessage, Modal, Tabs, Tooltip } from 'antd' +import { + Button, + Collapse, + ConfigProvider, + Dropdown, + Flex, + message as antdMessage, + Modal, + Progress, + Tabs, + Tooltip +} from 'antd' import { message } from 'antd' import { ChevronDown, ChevronRight, CirclePlay, CircleX, PauseCircle, ShieldCheck } from 'lucide-react' import { FC, memo, useEffect, useMemo, useRef, useState } from 'react' @@ -29,6 +40,7 @@ const MessageTools: FC = ({ block }) => { const { messageFont, fontSize } = useSettings() const { mcpServers, updateMCPServer } = useMCPServers() const [expandedResponse, setExpandedResponse] = useState<{ content: string; title: string } | null>(null) + const [progress, setProgress] = useState(0) const toolResponse = block.metadata?.rawMcpToolResponse @@ -58,6 +70,19 @@ const MessageTools: FC = ({ block }) => { } }, [countdown, id, isPending]) + useEffect(() => { + const removeListener = window.electron.ipcRenderer.on( + 'mcp-progress', + (_event: Electron.IpcRendererEvent, value: number) => { + setProgress(value) + } + ) + return () => { + setProgress(0) + removeListener() + } + }, []) + const cancelCountdown = () => { if (timer.current) { clearTimeout(timer.current) @@ -221,9 +246,11 @@ const MessageTools: FC = ({ block }) => { - - {renderStatusIndicator(status, hasError)} - + {progress > 0 ? ( + + ) : ( + renderStatusIndicator(status, hasError) + )} { command: server.command || '', registryUrl: server.registryUrl || '', isActive: server.isActive, + longRunning: server.longRunning, timeout: server.timeout, args: server.args ? server.args.join('\n') : '', env: server.env @@ -271,6 +273,7 @@ const McpSettings: React.FC = () => { registryUrl: values.registryUrl, searchKey: server.searchKey, timeout: values.timeout || server.timeout, + longRunning: values.longRunning, // Preserve existing advanced properties if not set in the form provider: values.provider || server.provider, providerUrl: values.providerUrl || server.providerUrl, @@ -630,6 +633,9 @@ const McpSettings: React.FC = () => { )} + + +