From 18170a1de53312a263e019416b7f12859e4de00b Mon Sep 17 00:00:00 2001 From: yyh Date: Wed, 14 Jan 2026 14:01:56 +0800 Subject: [PATCH] feat(web): add sandbox mode check for MCP tool availability Extend MCP tool availability context to include sandbox mode check alongside version support. MCP tools are now blocked when sandbox is disabled, with appropriate tooltip messages for each blocking condition. --- .../multiple-tool-selector/index.spec.tsx | 25 +++++++++++- .../workflow-app/components/workflow-main.tsx | 8 +++- .../components/mcp-tool-availability.tsx | 40 +++++++++++++++---- .../mcp-tool-not-support-tooltip.tsx | 7 +++- web/i18n/en-US/plugin.json | 1 + web/i18n/zh-Hans/plugin.json | 1 + 6 files changed, 70 insertions(+), 12 deletions(-) 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": "当“自动”关闭时,使用默认值。",