mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
refactor(web): MCP tool availability to context-based version gating (#30955)
This commit is contained in:
@ -7,6 +7,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// ==================== Imports (after mocks) ====================
|
||||
|
||||
import { MCPToolAvailabilityProvider } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
|
||||
import MultipleToolSelector from './index'
|
||||
|
||||
// ==================== Mock Setup ====================
|
||||
@ -190,10 +191,11 @@ type RenderOptions = {
|
||||
nodeOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
nodeId?: string
|
||||
canChooseMCPTool?: boolean
|
||||
versionSupported?: boolean
|
||||
}
|
||||
|
||||
const renderComponent = (options: RenderOptions = {}) => {
|
||||
const { versionSupported, ...overrides } = options
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
value: [],
|
||||
@ -206,16 +208,17 @@ const renderComponent = (options: RenderOptions = {}) => {
|
||||
nodeOutputVars: [createNodeOutputVar()],
|
||||
availableNodes: [createNode()],
|
||||
nodeId: 'test-node-id',
|
||||
canChooseMCPTool: false,
|
||||
}
|
||||
|
||||
const props = { ...defaultProps, ...options }
|
||||
const props = { ...defaultProps, ...overrides }
|
||||
const queryClient = createQueryClient()
|
||||
|
||||
return {
|
||||
...render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MultipleToolSelector {...props} />
|
||||
<MCPToolAvailabilityProvider versionSupported={versionSupported}>
|
||||
<MultipleToolSelector {...props} />
|
||||
</MCPToolAvailabilityProvider>
|
||||
</QueryClientProvider>,
|
||||
),
|
||||
props,
|
||||
@ -410,7 +413,7 @@ describe('MultipleToolSelector', () => {
|
||||
expect(screen.getByText('2/3')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should track enabled count with MCP tools when canChooseMCPTool is true', () => {
|
||||
it('should track enabled count with MCP tools when version is supported', () => {
|
||||
// Arrange
|
||||
const mcpTools = [createMCPTool({ id: 'mcp-provider' })]
|
||||
mockMCPToolsData.mockReturnValue(mcpTools)
|
||||
@ -421,13 +424,13 @@ describe('MultipleToolSelector', () => {
|
||||
]
|
||||
|
||||
// Act
|
||||
renderComponent({ value: tools, canChooseMCPTool: true })
|
||||
renderComponent({ value: tools, versionSupported: true })
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('2/2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not count MCP tools when canChooseMCPTool is false', () => {
|
||||
it('should not count MCP tools when version is unsupported', () => {
|
||||
// Arrange
|
||||
const mcpTools = [createMCPTool({ id: 'mcp-provider' })]
|
||||
mockMCPToolsData.mockReturnValue(mcpTools)
|
||||
@ -438,7 +441,7 @@ describe('MultipleToolSelector', () => {
|
||||
]
|
||||
|
||||
// Act
|
||||
renderComponent({ value: tools, canChooseMCPTool: false })
|
||||
renderComponent({ value: tools, versionSupported: false })
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('1/2')).toBeInTheDocument()
|
||||
@ -721,14 +724,6 @@ describe('MultipleToolSelector', () => {
|
||||
expect(screen.getByTestId('tool-selector-add')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass canChooseMCPTool prop correctly', () => {
|
||||
// Arrange & Act
|
||||
renderComponent({ canChooseMCPTool: true })
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('tool-selector-add')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with supportEnableSwitch for edit selectors', () => {
|
||||
// Arrange
|
||||
const tools = [createToolValue()]
|
||||
@ -771,13 +766,13 @@ describe('MultipleToolSelector', () => {
|
||||
]
|
||||
|
||||
// Act
|
||||
renderComponent({ value: tools, canChooseMCPTool: true })
|
||||
renderComponent({ value: tools, versionSupported: true })
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('2/2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should exclude MCP tools from enabled count when canChooseMCPTool is false', () => {
|
||||
it('should exclude MCP tools from enabled count when strategy version is unsupported', () => {
|
||||
// Arrange
|
||||
const mcpTools = [createMCPTool({ id: 'mcp-provider' })]
|
||||
mockMCPToolsData.mockReturnValue(mcpTools)
|
||||
@ -788,7 +783,7 @@ describe('MultipleToolSelector', () => {
|
||||
]
|
||||
|
||||
// Act
|
||||
renderComponent({ value: tools, canChooseMCPTool: false })
|
||||
renderComponent({ value: tools, versionSupported: false })
|
||||
|
||||
// Assert - Only regular tool should be counted
|
||||
expect(screen.getByText('1/2')).toBeInTheDocument()
|
||||
|
||||
@ -12,6 +12,7 @@ import Divider from '@/app/components/base/divider'
|
||||
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
|
||||
import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
|
||||
import { useAllMCPTools } from '@/service/use-tools'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
@ -27,7 +28,6 @@ type Props = {
|
||||
nodeOutputVars: NodeOutPutVar[]
|
||||
availableNodes: Node[]
|
||||
nodeId?: string
|
||||
canChooseMCPTool?: boolean
|
||||
}
|
||||
|
||||
const MultipleToolSelector = ({
|
||||
@ -42,14 +42,14 @@ const MultipleToolSelector = ({
|
||||
nodeOutputVars,
|
||||
availableNodes,
|
||||
nodeId,
|
||||
canChooseMCPTool,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { allowed: isMCPToolAllowed } = useMCPToolAvailability()
|
||||
const { data: mcpTools } = useAllMCPTools()
|
||||
const enabledCount = value.filter((item) => {
|
||||
const isMCPTool = mcpTools?.find(tool => tool.id === item.provider_name)
|
||||
if (isMCPTool)
|
||||
return item.enabled && canChooseMCPTool
|
||||
return item.enabled && isMCPToolAllowed
|
||||
return item.enabled
|
||||
}).length
|
||||
// collapse control
|
||||
@ -167,7 +167,6 @@ const MultipleToolSelector = ({
|
||||
onSelectMultiple={handleAddMultiple}
|
||||
onDelete={() => handleDelete(index)}
|
||||
supportEnableSwitch
|
||||
canChooseMCPTool={canChooseMCPTool}
|
||||
isEdit
|
||||
/>
|
||||
</div>
|
||||
@ -190,7 +189,6 @@ const MultipleToolSelector = ({
|
||||
panelShowState={panelShowState}
|
||||
onPanelShowStateChange={setPanelShowState}
|
||||
isEdit={false}
|
||||
canChooseMCPTool={canChooseMCPTool}
|
||||
onSelectMultiple={handleAddMultiple}
|
||||
/>
|
||||
</>
|
||||
|
||||
@ -64,7 +64,6 @@ type Props = {
|
||||
nodeOutputVars: NodeOutPutVar[]
|
||||
availableNodes: Node[]
|
||||
nodeId?: string
|
||||
canChooseMCPTool?: boolean
|
||||
}
|
||||
const ToolSelector: FC<Props> = ({
|
||||
value,
|
||||
@ -86,7 +85,6 @@ const ToolSelector: FC<Props> = ({
|
||||
nodeOutputVars,
|
||||
availableNodes,
|
||||
nodeId = '',
|
||||
canChooseMCPTool,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isShow, onShowChange] = useState(false)
|
||||
@ -267,7 +265,6 @@ const ToolSelector: FC<Props> = ({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
canChooseMCPTool={canChooseMCPTool}
|
||||
/>
|
||||
)}
|
||||
</PortalToFollowElemTrigger>
|
||||
@ -300,7 +297,6 @@ const ToolSelector: FC<Props> = ({
|
||||
onSelectMultiple={handleSelectMultipleTool}
|
||||
scope={scope}
|
||||
selectedTools={selectedTools}
|
||||
canChooseMCPTool={canChooseMCPTool}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
|
||||
@ -16,6 +16,7 @@ import Tooltip from '@/app/components/base/tooltip'
|
||||
import { ToolTipContent } from '@/app/components/base/tooltip/content'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button'
|
||||
import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
|
||||
import McpToolNotSupportTooltip from '@/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip'
|
||||
import { SwitchPluginVersion } from '@/app/components/workflow/nodes/_base/components/switch-plugin-version'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -39,7 +40,6 @@ type Props = {
|
||||
versionMismatch?: boolean
|
||||
open: boolean
|
||||
authRemoved?: boolean
|
||||
canChooseMCPTool?: boolean
|
||||
}
|
||||
|
||||
const ToolItem = ({
|
||||
@ -61,13 +61,13 @@ const ToolItem = ({
|
||||
errorTip,
|
||||
versionMismatch,
|
||||
authRemoved,
|
||||
canChooseMCPTool,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { allowed: isMCPToolAllowed } = useMCPToolAvailability()
|
||||
const providerNameText = isMCPTool ? providerShowName : providerName?.split('/').pop()
|
||||
const isTransparent = uninstalled || versionMismatch || isError
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const isShowCanNotChooseMCPTip = isMCPTool && !canChooseMCPTool
|
||||
const isShowCanNotChooseMCPTip = isMCPTool && !isMCPToolAllowed
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
@ -125,9 +125,7 @@ const ToolItem = ({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isShowCanNotChooseMCPTip && (
|
||||
<McpToolNotSupportTooltip />
|
||||
)}
|
||||
{isShowCanNotChooseMCPTip && <McpToolNotSupportTooltip />}
|
||||
{!isError && !uninstalled && !versionMismatch && noAuth && (
|
||||
<Button variant="secondary" size="small">
|
||||
{t('notAuthorized', { ns: 'tools' })}
|
||||
|
||||
Reference in New Issue
Block a user