diff --git a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx index e2b6b06fd6..114b47a431 100644 --- a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx @@ -26,6 +26,7 @@ type Props = { nodeOutputVars: NodeOutPutVar[], availableNodes: Node[], nodeId?: string + canChooseMCPTool?: boolean } const MultipleToolSelector = ({ @@ -40,6 +41,7 @@ const MultipleToolSelector = ({ nodeOutputVars, availableNodes, nodeId, + canChooseMCPTool, }: Props) => { const { t } = useTranslation() const enabledCount = value.filter(item => item.enabled).length @@ -155,7 +157,7 @@ const MultipleToolSelector = ({ } panelShowState={panelShowState} onPanelShowStateChange={setPanelShowState} - + canChooseMCPTool={canChooseMCPTool} /> {value.length === 0 && (
{t('plugin.detailPanel.toolSelector.empty')}
@@ -173,6 +175,7 @@ const MultipleToolSelector = ({ onSelectMultiple={handleAddMultiple} onDelete={() => handleDelete(index)} supportEnableSwitch + canChooseMCPTool={canChooseMCPTool} /> ))} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index 5ec1797351..fba99f4e68 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -68,6 +68,7 @@ type Props = { nodeOutputVars: NodeOutPutVar[], availableNodes: Node[], nodeId?: string, + canChooseMCPTool?: boolean, } const ToolSelector: FC = ({ value, @@ -88,6 +89,7 @@ const ToolSelector: FC = ({ nodeOutputVars, availableNodes, nodeId = '', + canChooseMCPTool, }) => { const { t } = useTranslation() const [isShow, onShowChange] = useState(false) @@ -308,6 +310,7 @@ const ToolSelector: FC = ({ onSelectMultiple={handleSelectMultipleTool} scope={scope} selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} />
diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index f552d7c17a..4e97d71151 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -449,9 +449,14 @@ export type StrategyDeclaration = { strategies: StrategyDetail[] } +export type PluginMeta = { + version: string // the version of dify sdk +} + export type StrategyPluginDetail = { provider: string plugin_unique_identifier: string plugin_id: string declaration: StrategyDeclaration + meta: PluginMeta } diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index f32ab89692..d4e6a40163 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -83,6 +83,7 @@ const Tabs: FC = ({ customTools={customTools || []} workflowTools={workflowTools || []} mcpTools={mcpTools || []} + isHideMCPTools={false} /> ) } diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index bd24e204ce..95ac24f599 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -39,6 +39,7 @@ type Props = { supportAddCustomTool?: boolean scope?: string selectedTools?: ToolValue[] + canChooseMCPTool?: boolean } const ToolPicker: FC = ({ @@ -54,6 +55,7 @@ const ToolPicker: FC = ({ scope = 'all', selectedTools, panelClassName, + canChooseMCPTool, }) => { const { t } = useTranslation() const [searchText, setSearchText] = useState('') @@ -175,8 +177,8 @@ const ToolPicker: FC = ({ customTools={customToolList || []} workflowTools={workflowToolList || []} mcpTools={mcpTools || []} - selectedTools={selectedTools} + isHideMCPTools={!canChooseMCPTool} />
diff --git a/web/app/components/workflow/block-selector/tool/action-item.tsx b/web/app/components/workflow/block-selector/tool/action-item.tsx index 9a52e820f7..7c0ebd7465 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -69,6 +69,7 @@ const ToolItem: FC = ({ output_schema: payload.output_schema, paramSchemas: payload.parameters, params, + meta: provider.meta, }) }} > diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 398a7e0c71..42ab833a5b 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -1,3 +1,5 @@ +import type { PluginMeta } from '../../plugins/types' + export enum TabsEnum { Blocks = 'blocks', Tools = 'tools', @@ -31,6 +33,7 @@ export type ToolDefaultValue = { params: Record paramSchemas: Record[] output_schema: Record + meta: PluginMeta } export type ToolValue = { diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index dd6a1c6a22..ca09cb2f3e 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -67,6 +67,7 @@ function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => s icon: getIcon(item.declaration.identity.icon), label: item.declaration.identity.label as any, type: CollectionType.all, + meta: item.meta, tools: item.declaration.strategies.map(strategy => ({ name: strategy.identity.name, author: strategy.identity.author, @@ -210,6 +211,7 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => agent_strategy_label: tool!.tool_label, agent_output_schema: tool!.output_schema, plugin_unique_identifier: tool!.provider_id, + meta: tool!.meta, }) setOpen(false) }} diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index 1e9612b7c7..b9d42558c1 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -22,6 +22,8 @@ import type { Node } from 'reactflow' import { useContext } from 'use-context-selector' import I18n from '@/context/i18n' import { LanguagesSupported } from '@/i18n/language' +import type { PluginMeta } from '@/app/components/plugins/types' +import { noop } from 'lodash' export type Strategy = { agent_strategy_provider_name: string @@ -29,6 +31,7 @@ export type Strategy = { agent_strategy_label: string agent_output_schema: Record plugin_unique_identifier: string + meta?: PluginMeta } export type AgentStrategyProps = { @@ -40,6 +43,7 @@ export type AgentStrategyProps = { nodeOutputVars?: NodeOutPutVar[], availableNodes?: Node[], nodeId?: string + canChooseMCPTool?: boolean } type CustomSchema = Omit & { type: Type } & Field @@ -50,7 +54,7 @@ type MultipleToolSelectorSchema = CustomSchema<'array[tools]'> type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema export const AgentStrategy = memo((props: AgentStrategyProps) => { - const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId } = props + const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId, canChooseMCPTool } = props const { t } = useTranslation() const { locale } = useContext(I18n) const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration) @@ -166,6 +170,8 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { value={value} onSelect={item => onChange(item)} onDelete={() => onChange(null)} + canChooseMCPTool={canChooseMCPTool} + onSelectMultiple={noop} /> ) @@ -187,6 +193,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { onChange={onChange} supportCollapse required={schema.required} + canChooseMCPTool={canChooseMCPTool} /> ) } diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index f92e92dbcb..a213ffc24b 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -53,6 +53,7 @@ const AgentPanel: FC> = (props) => { varInputs, outputSchema, handleMemoryChange, + canChooseMCPTool, } = useConfig(props.id, props.data) const { t } = useTranslation() const nodeInfo = useMemo(() => { @@ -79,7 +80,7 @@ const AgentPanel: FC> = (props) => { })() const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey) - + console.log(canChooseMCPTool) return
> = (props) => { agent_strategy_label: inputs.agent_strategy_label!, agent_output_schema: inputs.output_schema, plugin_unique_identifier: inputs.plugin_unique_identifier!, + meta: inputs.meta, } : undefined} onStrategyChange={(strategy) => { setInputs({ @@ -102,6 +104,7 @@ const AgentPanel: FC> = (props) => { agent_strategy_label: strategy?.agent_strategy_label, output_schema: strategy!.agent_output_schema, plugin_unique_identifier: strategy!.plugin_unique_identifier, + meta: strategy?.meta, }) resetEditor(Date.now()) }} diff --git a/web/app/components/workflow/nodes/agent/types.ts b/web/app/components/workflow/nodes/agent/types.ts index ca8bb5e71d..e50586bd27 100644 --- a/web/app/components/workflow/nodes/agent/types.ts +++ b/web/app/components/workflow/nodes/agent/types.ts @@ -1,11 +1,13 @@ import type { CommonNodeType, Memory } from '@/app/components/workflow/types' import type { ToolVarInputs } from '../tool/types' +import type { PluginMeta } from '@/app/components/plugins/types' export type AgentNodeType = CommonNodeType & { agent_strategy_provider_name?: string agent_strategy_name?: string agent_strategy_label?: string agent_parameters?: ToolVarInputs + meta?: PluginMeta output_schema: Record plugin_unique_identifier?: string memory?: Memory diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index 8196caa3f5..1dc20fcbcd 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -14,6 +14,7 @@ import type { Memory, Var } from '../../types' import { VarType as VarKindType } from '../../types' import useAvailableVarList from '../_base/hooks/use-available-var-list' import produce from 'immer' +import { isSupportMCP } from '@/utils/plugin-version-feature' export type StrategyStatus = { plugin: { @@ -214,6 +215,7 @@ const useConfig = (id: string, payload: AgentNodeType) => { outputSchema, handleMemoryChange, isChatMode, + canChooseMCPTool: isSupportMCP(inputs.meta?.version), } } diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 884bdfbd10..56cd9ffdf9 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -15,6 +15,7 @@ import type { } from '@/app/components/workflow/nodes/_base/components/error-handle/types' import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types' import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types' +import type { PluginMeta } from '../plugins/types' export enum BlockEnum { Start = 'start', @@ -400,6 +401,7 @@ export type MoreInfo = { export type ToolWithProvider = Collection & { tools: Tool[] + meta: PluginMeta } export enum SupportUploadFileTypes { diff --git a/web/utils/plugin-version-feature.spec.ts b/web/utils/plugin-version-feature.spec.ts new file mode 100644 index 0000000000..12ca239aa9 --- /dev/null +++ b/web/utils/plugin-version-feature.spec.ts @@ -0,0 +1,26 @@ +import { isSupportMCP } from './plugin-version-feature' + +describe('plugin-version-feature', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('isSupportMCP', () => { + it('should call isEqualOrLaterThanVersion with the correct parameters', () => { + expect(isSupportMCP('0.0.3')).toBe(true) + expect(isSupportMCP('1.0.0')).toBe(true) + }) + + it('should return true when version is equal to the supported MCP version', () => { + const mockVersion = '0.0.2' + const result = isSupportMCP(mockVersion) + expect(result).toBe(true) + }) + + it('should return false when version is less than the supported MCP version', () => { + const mockVersion = '0.0.1' + const result = isSupportMCP(mockVersion) + expect(result).toBe(false) + }) + }) +}) diff --git a/web/utils/plugin-version-feature.ts b/web/utils/plugin-version-feature.ts new file mode 100644 index 0000000000..51d366bf9c --- /dev/null +++ b/web/utils/plugin-version-feature.ts @@ -0,0 +1,10 @@ +import { isEqualOrLaterThanVersion } from './semver' + +const SUPPORT_MCP_VERSION = '0.0.2' + +export const isSupportMCP = (version?: string): boolean => { + if (!version) + return false + + return isEqualOrLaterThanVersion(version, SUPPORT_MCP_VERSION) +} diff --git a/web/utils/semver.spec.ts b/web/utils/semver.spec.ts new file mode 100644 index 0000000000..c2188a976c --- /dev/null +++ b/web/utils/semver.spec.ts @@ -0,0 +1,75 @@ +import { compareVersion, getLatestVersion, isEqualOrLaterThanVersion } from './semver' + +describe('semver utilities', () => { + describe('getLatestVersion', () => { + it('should return the latest version from a list of versions', () => { + expect(getLatestVersion(['1.0.0', '1.1.0', '1.0.1'])).toBe('1.1.0') + expect(getLatestVersion(['2.0.0', '1.9.9', '1.10.0'])).toBe('2.0.0') + expect(getLatestVersion(['1.0.0-alpha', '1.0.0-beta', '1.0.0'])).toBe('1.0.0') + }) + + it('should handle patch versions correctly', () => { + expect(getLatestVersion(['1.0.1', '1.0.2', '1.0.0'])).toBe('1.0.2') + expect(getLatestVersion(['1.0.10', '1.0.9', '1.0.11'])).toBe('1.0.11') + }) + + it('should handle mixed version formats', () => { + expect(getLatestVersion(['v1.0.0', '1.1.0', 'v1.2.0'])).toBe('v1.2.0') + expect(getLatestVersion(['1.0.0-rc.1', '1.0.0', '1.0.0-beta'])).toBe('1.0.0') + }) + + it('should return the only version if only one version is provided', () => { + expect(getLatestVersion(['1.0.0'])).toBe('1.0.0') + }) + }) + + describe('compareVersion', () => { + it('should return 1 when first version is greater', () => { + expect(compareVersion('1.1.0', '1.0.0')).toBe(1) + expect(compareVersion('2.0.0', '1.9.9')).toBe(1) + expect(compareVersion('1.0.1', '1.0.0')).toBe(1) + }) + + it('should return -1 when first version is less', () => { + expect(compareVersion('1.0.0', '1.1.0')).toBe(-1) + expect(compareVersion('1.9.9', '2.0.0')).toBe(-1) + expect(compareVersion('1.0.0', '1.0.1')).toBe(-1) + }) + + it('should return 0 when versions are equal', () => { + expect(compareVersion('1.0.0', '1.0.0')).toBe(0) + expect(compareVersion('2.1.3', '2.1.3')).toBe(0) + }) + + it('should handle pre-release versions correctly', () => { + expect(compareVersion('1.0.0-beta', '1.0.0-alpha')).toBe(1) + expect(compareVersion('1.0.0', '1.0.0-beta')).toBe(1) + expect(compareVersion('1.0.0-alpha', '1.0.0-beta')).toBe(-1) + }) + }) + + describe('isEqualOrLaterThanVersion', () => { + it('should return true when baseVersion is greater than targetVersion', () => { + expect(isEqualOrLaterThanVersion('1.1.0', '1.0.0')).toBe(true) + expect(isEqualOrLaterThanVersion('2.0.0', '1.9.9')).toBe(true) + expect(isEqualOrLaterThanVersion('1.0.1', '1.0.0')).toBe(true) + }) + + it('should return true when baseVersion is equal to targetVersion', () => { + expect(isEqualOrLaterThanVersion('1.0.0', '1.0.0')).toBe(true) + expect(isEqualOrLaterThanVersion('2.1.3', '2.1.3')).toBe(true) + }) + + it('should return false when baseVersion is less than targetVersion', () => { + expect(isEqualOrLaterThanVersion('1.0.0', '1.1.0')).toBe(false) + expect(isEqualOrLaterThanVersion('1.9.9', '2.0.0')).toBe(false) + expect(isEqualOrLaterThanVersion('1.0.0', '1.0.1')).toBe(false) + }) + + it('should handle pre-release versions correctly', () => { + expect(isEqualOrLaterThanVersion('1.0.0', '1.0.0-beta')).toBe(true) + expect(isEqualOrLaterThanVersion('1.0.0-beta', '1.0.0-alpha')).toBe(true) + expect(isEqualOrLaterThanVersion('1.0.0-alpha', '1.0.0')).toBe(false) + }) + }) +}) diff --git a/web/utils/semver.ts b/web/utils/semver.ts index f1b9eb8d7e..aea84153ec 100644 --- a/web/utils/semver.ts +++ b/web/utils/semver.ts @@ -7,3 +7,7 @@ export const getLatestVersion = (versionList: string[]) => { export const compareVersion = (v1: string, v2: string) => { return semver.compare(v1, v2) } + +export const isEqualOrLaterThanVersion = (baseVersion: string, targetVersion: string) => { + return semver.gte(baseVersion, targetVersion) +}