diff --git a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.spec.tsx b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.spec.tsx index 288289b64d..90e2ec2b03 100644 --- a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.spec.tsx @@ -192,10 +192,11 @@ type RenderOptions = { availableNodes?: Node[] nodeId?: string versionSupported?: boolean + sandboxEnabled?: boolean } const renderComponent = (options: RenderOptions = {}) => { - const { versionSupported, ...overrides } = options + const { versionSupported, sandboxEnabled, ...overrides } = options const defaultProps = { disabled: false, value: [], @@ -216,7 +217,10 @@ const renderComponent = (options: RenderOptions = {}) => { return { ...render( - + , @@ -447,6 +451,23 @@ describe('MultipleToolSelector', () => { expect(screen.getByText('1/2')).toBeInTheDocument() }) + it('should not count MCP tools when sandbox is disabled', () => { + // Arrange + const mcpTools = [createMCPTool({ id: 'mcp-provider' })] + mockMCPToolsData.mockReturnValue(mcpTools) + + const tools = [ + createToolValue({ tool_name: 'tool-1', provider_name: 'regular-provider', enabled: true }), + createToolValue({ tool_name: 'mcp-tool', provider_name: 'mcp-provider', enabled: true }), + ] + + // Act + renderComponent({ value: tools, sandboxEnabled: false }) + + // Assert + expect(screen.getByText('1/2')).toBeInTheDocument() + }) + it('should manage open state for add tool panel', () => { // Arrange const { container } = renderComponent() diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index 38a044f088..825ffebc20 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -3,8 +3,9 @@ import { useCallback, useMemo, } from 'react' -import { useFeaturesStore } from '@/app/components/base/features/hooks' +import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import { WorkflowWithInnerContext } from '@/app/components/workflow' +import { MCPToolAvailabilityProvider } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability' import { useWorkflowStore } from '@/app/components/workflow/store' import { useAvailableNodesMetaData, @@ -26,6 +27,7 @@ const WorkflowMain = ({ edges, viewport, }: WorkflowMainProps) => { + const sandboxEnabled = useFeatures(state => state.features.sandbox?.enabled) ?? false const featuresStore = useFeaturesStore() const workflowStore = useWorkflowStore() @@ -183,7 +185,9 @@ const WorkflowMain = ({ onWorkflowDataUpdate={handleWorkflowDataUpdate} hooksStore={hooksStore as any} > - + + + ) } diff --git a/web/app/components/workflow/nodes/_base/components/mcp-tool-availability.tsx b/web/app/components/workflow/nodes/_base/components/mcp-tool-availability.tsx index d32899a945..8a2ab8fd9a 100644 --- a/web/app/components/workflow/nodes/_base/components/mcp-tool-availability.tsx +++ b/web/app/components/workflow/nodes/_base/components/mcp-tool-availability.tsx @@ -4,6 +4,7 @@ import { createContext, useContext } from 'react' type MCPToolAvailabilityContextValue = { versionSupported?: boolean + sandboxEnabled?: boolean } const MCPToolAvailabilityContext = createContext(undefined) @@ -11,28 +12,53 @@ const MCPToolAvailabilityContext = createContext ( - - {children} - -) +}) => { + const parentContext = useContext(MCPToolAvailabilityContext) + const value = { + versionSupported: versionSupported !== undefined + ? versionSupported + : parentContext?.versionSupported, + sandboxEnabled: sandboxEnabled !== undefined + ? sandboxEnabled + : parentContext?.sandboxEnabled, + } + return ( + + {children} + + ) +} export const useMCPToolAvailability = (): MCPToolAvailability => { const context = useContext(MCPToolAvailabilityContext) if (context === undefined) return { allowed: true } - const { versionSupported } = context + const { versionSupported, sandboxEnabled } = context + const versionAllowed = versionSupported ?? true + const sandboxAllowed = sandboxEnabled ?? true + const allowed = versionAllowed && sandboxAllowed + let blockedBy: MCPToolAvailability['blockedBy'] + if (!versionAllowed) + blockedBy = 'version' + else if (!sandboxAllowed) + blockedBy = 'sandbox' return { - allowed: versionSupported === true, + allowed, versionSupported, + sandboxEnabled, + blockedBy, } } diff --git a/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx b/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx index 671459bbbd..1c628c749e 100644 --- a/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx +++ b/web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx @@ -4,14 +4,19 @@ import { RiAlertFill } from '@remixicon/react' import * as React from 'react' import { useTranslation } from 'react-i18next' import Tooltip from '@/app/components/base/tooltip' +import { useMCPToolAvailability } from './mcp-tool-availability' const McpToolNotSupportTooltip: FC = () => { const { t } = useTranslation() + const { blockedBy } = useMCPToolAvailability() + const messageKey = blockedBy === 'sandbox' + ? 'detailPanel.toolSelector.mcpToolSandboxOnly' + : 'detailPanel.toolSelector.unsupportedMCPTool' return ( - {t('detailPanel.toolSelector.unsupportedMCPTool', { ns: 'plugin' })} + {t(messageKey, { ns: 'plugin' })} )} > diff --git a/web/i18n/en-US/plugin.json b/web/i18n/en-US/plugin.json index c7f091a442..95c911224b 100644 --- a/web/i18n/en-US/plugin.json +++ b/web/i18n/en-US/plugin.json @@ -122,6 +122,7 @@ "detailPanel.toolSelector.descriptionLabel": "Tool description", "detailPanel.toolSelector.descriptionPlaceholder": "Brief description of the tool's purpose, e.g., get the temperature for a specific location.", "detailPanel.toolSelector.empty": "Click the '+' button to add tools. You can add multiple tools.", + "detailPanel.toolSelector.mcpToolSandboxOnly": "MCP tools are only available in sandbox mode.", "detailPanel.toolSelector.params": "REASONING CONFIG", "detailPanel.toolSelector.paramsTip1": "Controls LLM inference parameters.", "detailPanel.toolSelector.paramsTip2": "When 'Auto' is off, the default value is used.", diff --git a/web/i18n/zh-Hans/plugin.json b/web/i18n/zh-Hans/plugin.json index 703bd4e6ea..e3efaa53eb 100644 --- a/web/i18n/zh-Hans/plugin.json +++ b/web/i18n/zh-Hans/plugin.json @@ -122,6 +122,7 @@ "detailPanel.toolSelector.descriptionLabel": "工具描述", "detailPanel.toolSelector.descriptionPlaceholder": "简要描述工具目的,例如,获取特定位置的温度。", "detailPanel.toolSelector.empty": "点击 \"+\" 按钮添加工具。您可以添加多个工具。", + "detailPanel.toolSelector.mcpToolSandboxOnly": "MCP 工具仅在沙箱模式下可用。", "detailPanel.toolSelector.params": "推理配置", "detailPanel.toolSelector.paramsTip1": "控制 LLM 推理参数。", "detailPanel.toolSelector.paramsTip2": "当“自动”关闭时,使用默认值。",