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.
This commit is contained in:
yyh
2026-01-14 14:01:56 +08:00
parent 7ce144f493
commit 18170a1de5
6 changed files with 70 additions and 12 deletions

View File

@ -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(
<QueryClientProvider client={queryClient}>
<MCPToolAvailabilityProvider versionSupported={versionSupported}>
<MCPToolAvailabilityProvider
versionSupported={versionSupported}
sandboxEnabled={sandboxEnabled}
>
<MultipleToolSelector {...props} />
</MCPToolAvailabilityProvider>
</QueryClientProvider>,
@ -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()

View File

@ -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}
>
<WorkflowChildren />
<MCPToolAvailabilityProvider sandboxEnabled={sandboxEnabled}>
<WorkflowChildren />
</MCPToolAvailabilityProvider>
</WorkflowWithInnerContext>
)
}

View File

@ -4,6 +4,7 @@ import { createContext, useContext } from 'react'
type MCPToolAvailabilityContextValue = {
versionSupported?: boolean
sandboxEnabled?: boolean
}
const MCPToolAvailabilityContext = createContext<MCPToolAvailabilityContextValue | undefined>(undefined)
@ -11,28 +12,53 @@ const MCPToolAvailabilityContext = createContext<MCPToolAvailabilityContextValue
export type MCPToolAvailability = {
allowed: boolean
versionSupported?: boolean
sandboxEnabled?: boolean
blockedBy?: 'version' | 'sandbox'
}
export const MCPToolAvailabilityProvider = ({
versionSupported,
sandboxEnabled,
children,
}: {
versionSupported?: boolean
sandboxEnabled?: boolean
children: ReactNode
}) => (
<MCPToolAvailabilityContext.Provider value={{ versionSupported }}>
{children}
</MCPToolAvailabilityContext.Provider>
)
}) => {
const parentContext = useContext(MCPToolAvailabilityContext)
const value = {
versionSupported: versionSupported !== undefined
? versionSupported
: parentContext?.versionSupported,
sandboxEnabled: sandboxEnabled !== undefined
? sandboxEnabled
: parentContext?.sandboxEnabled,
}
return (
<MCPToolAvailabilityContext.Provider value={value}>
{children}
</MCPToolAvailabilityContext.Provider>
)
}
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,
}
}

View File

@ -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 (
<Tooltip
popupContent={(
<div className="w-[256px]">
{t('detailPanel.toolSelector.unsupportedMCPTool', { ns: 'plugin' })}
{t(messageKey, { ns: 'plugin' })}
</div>
)}
>

View File

@ -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.",

View File

@ -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": "当“自动”关闭时,使用默认值。",