diff --git a/web/src/interfaces/database/agent.ts b/web/src/interfaces/database/agent.ts index 088564bc1..0c1b9b29b 100644 --- a/web/src/interfaces/database/agent.ts +++ b/web/src/interfaces/database/agent.ts @@ -159,6 +159,10 @@ export interface IAgentForm { component_name: string; params: Record; }>; + mcp: Array<{ + mcp_id: string; + tools: Record>; + }>; outputs: { structured_output: Record>; content: Record; diff --git a/web/src/pages/agent/canvas/node/tool-node.tsx b/web/src/pages/agent/canvas/node/tool-node.tsx index ba3f621be..d8b7ef87d 100644 --- a/web/src/pages/agent/canvas/node/tool-node.tsx +++ b/web/src/pages/agent/canvas/node/tool-node.tsx @@ -4,18 +4,15 @@ import { get } from 'lodash'; import { memo, useCallback } from 'react'; import { NodeHandleId } from '../../constant'; import { ToolCard } from '../../form/agent-form/agent-tools'; +import { useFindMcpById } from '../../hooks/use-find-mcp-by-id'; import useGraphStore from '../../store'; import { NodeWrapper } from './node-wrapper'; -function InnerToolNode({ - id, - data, - isConnectable = true, - selected, -}: NodeProps) { +function InnerToolNode({ id, isConnectable = true }: NodeProps) { const { edges, getNode } = useGraphStore((state) => state); const upstreamAgentNodeId = edges.find((x) => x.target === id)?.source; const upstreamAgentNode = getNode(upstreamAgentNodeId); + const { findMcpById } = useFindMcpById(); const handleClick = useCallback(() => {}, []); @@ -25,6 +22,12 @@ function InnerToolNode({ [], ); + const mcpList: IAgentForm['mcp'] = get( + upstreamAgentNode, + 'data.form.mcp', + [], + ); + return ( ))} + {mcpList.map((x) => ( + + {findMcpById(x.mcp_id)?.name} + + ))} ); diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx index ec074c2a2..effbe240c 100644 --- a/web/src/pages/agent/constant.tsx +++ b/web/src/pages/agent/constant.tsx @@ -697,6 +697,7 @@ export const initialAgentValues = { exception_comment: '', exception_goto: '', tools: [], + mcp: [], outputs: { structured_output: { // topic: { diff --git a/web/src/pages/agent/form/agent-form/agent-tools.tsx b/web/src/pages/agent/form/agent-form/agent-tools.tsx index 65666de3e..09e1c23a5 100644 --- a/web/src/pages/agent/form/agent-form/agent-tools.tsx +++ b/web/src/pages/agent/form/agent-form/agent-tools.tsx @@ -5,12 +5,14 @@ import { PencilLine, X } from 'lucide-react'; import { PropsWithChildren, useCallback, useContext, useMemo } from 'react'; import { Operator } from '../../constant'; import { AgentInstanceContext } from '../../context'; +import { useFindMcpById } from '../../hooks/use-find-mcp-by-id'; import { INextOperatorForm } from '../../interface'; import useGraphStore from '../../store'; import { filterDownstreamAgentNodeIds } from '../../utils/filter-downstream-nodes'; import { ToolPopover } from './tool-popover'; +import { useDeleteAgentNodeMCP } from './tool-popover/use-update-mcp'; import { useDeleteAgentNodeTools } from './tool-popover/use-update-tools'; -import { useGetAgentToolNames } from './use-get-tools'; +import { useGetAgentMCPIds, useGetAgentToolNames } from './use-get-tools'; export function ToolCard({ children, @@ -59,6 +61,9 @@ function ActionButton({ edit, deleteRecord, record }: ActionButtonProps) { export function AgentTools() { const { toolNames } = useGetAgentToolNames(); const { deleteNodeTool } = useDeleteAgentNodeTools(); + const { mcpIds } = useGetAgentMCPIds(); + const { findMcpById } = useFindMcpById(); + const { deleteNodeMCP } = useDeleteAgentNodeMCP(); return (
@@ -74,6 +79,16 @@ export function AgentTools() { > ))} + {mcpIds.map((id) => ( + + {findMcpById(id)?.name} + {}} + deleteRecord={deleteNodeMCP(id)} + > + + ))} Add Tool diff --git a/web/src/pages/agent/form/agent-form/tool-popover/index.tsx b/web/src/pages/agent/form/agent-form/tool-popover/index.tsx index a640d8770..298d929b0 100644 --- a/web/src/pages/agent/form/agent-form/tool-popover/index.tsx +++ b/web/src/pages/agent/form/agent-form/tool-popover/index.tsx @@ -3,15 +3,22 @@ import { PopoverContent, PopoverTrigger, } from '@/components/ui/popover'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Operator } from '@/pages/agent/constant'; import { AgentFormContext, AgentInstanceContext } from '@/pages/agent/context'; import useGraphStore from '@/pages/agent/store'; import { Position } from '@xyflow/react'; -import { PropsWithChildren, useCallback, useContext } from 'react'; -import { useGetAgentToolNames } from '../use-get-tools'; -import { ToolCommand } from './tool-command'; +import { PropsWithChildren, useCallback, useContext, useEffect } from 'react'; +import { useGetAgentMCPIds, useGetAgentToolNames } from '../use-get-tools'; +import { MCPCommand, ToolCommand } from './tool-command'; +import { useUpdateAgentNodeMCP } from './use-update-mcp'; import { useUpdateAgentNodeTools } from './use-update-tools'; +enum ToolType { + Common = 'common', + MCP = 'mcp', +} + export function ToolPopover({ children }: PropsWithChildren) { const { addCanvasNode } = useContext(AgentInstanceContext); const node = useContext(AgentFormContext); @@ -20,29 +27,57 @@ export function ToolPopover({ children }: PropsWithChildren) { const deleteAgentToolNodeById = useGraphStore( (state) => state.deleteAgentToolNodeById, ); + const { mcpIds } = useGetAgentMCPIds(); + const { updateNodeMCP } = useUpdateAgentNodeMCP(); const handleChange = useCallback( (value: string[]) => { if (Array.isArray(value) && node?.id) { updateNodeTools(value); - if (value.length > 0) { - addCanvasNode(Operator.Tool, { - position: Position.Bottom, - nodeId: node?.id, - })(); - } else { - deleteAgentToolNodeById(node.id); // TODO: The tool node should be derived from the agent tools data - } } }, - [addCanvasNode, deleteAgentToolNodeById, node?.id, updateNodeTools], + [node?.id, updateNodeTools], ); + useEffect(() => { + const total = toolNames.length + mcpIds.length; + if (node?.id) { + if (total > 0) { + addCanvasNode(Operator.Tool, { + position: Position.Bottom, + nodeId: node?.id, + })(); + } else { + deleteAgentToolNodeById(node.id); + } + } + }, [ + addCanvasNode, + deleteAgentToolNodeById, + mcpIds.length, + node?.id, + toolNames.length, + ]); + return ( {children} - - + + + + Built-in + MCP + + + + + + + + ); diff --git a/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx b/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx index 729527947..b23d15b5e 100644 --- a/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx +++ b/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx @@ -8,9 +8,10 @@ import { CommandItem, CommandList, } from '@/components/ui/command'; +import { useListMcpServer } from '@/hooks/use-mcp-request'; import { cn } from '@/lib/utils'; import { Operator } from '@/pages/agent/constant'; -import { useCallback, useEffect, useState } from 'react'; +import { PropsWithChildren, useCallback, useEffect, useState } from 'react'; const Menus = [ { @@ -52,7 +53,36 @@ type ToolCommandProps = { onChange?(values: string[]): void; }; -export function ToolCommand({ value, onChange }: ToolCommandProps) { +type ToolCommandItemProps = { + toggleOption(id: string): void; + id: string; + isSelected: boolean; +} & ToolCommandProps; + +function ToolCommandItem({ + toggleOption, + id, + isSelected, + children, +}: ToolCommandItemProps & PropsWithChildren) { + return ( + toggleOption(id)}> +
+ +
+ {children} +
+ ); +} + +function useHandleSelectChange({ onChange, value }: ToolCommandProps) { const [currentValue, setCurrentValue] = useState([]); const toggleOption = useCallback( @@ -72,8 +102,20 @@ export function ToolCommand({ value, onChange }: ToolCommandProps) { } }, [value]); + return { + toggleOption, + currentValue, + }; +} + +export function ToolCommand({ value, onChange }: ToolCommandProps) { + const { toggleOption, currentValue } = useHandleSelectChange({ + onChange, + value, + }); + return ( - + No results found. @@ -82,28 +124,17 @@ export function ToolCommand({ value, onChange }: ToolCommandProps) { {x.list.map((y) => { const isSelected = currentValue.includes(y); return ( - toggleOption(y)} + id={y} + toggleOption={toggleOption} + isSelected={isSelected} > -
- -
- {/* {option.icon && ( - - )} */} - {/* {option.label} */} - - {y} -
+ <> + + {y} + + ); })} @@ -112,3 +143,34 @@ export function ToolCommand({ value, onChange }: ToolCommandProps) {
); } + +export function MCPCommand({ onChange, value }: ToolCommandProps) { + const { data } = useListMcpServer(); + const { toggleOption, currentValue } = useHandleSelectChange({ + onChange, + value, + }); + + return ( + + + + No results found. + {data.mcp_servers.map((item) => { + const isSelected = currentValue.includes(item.id); + + return ( + + {item.name} + + ); + })} + + + ); +} diff --git a/web/src/pages/agent/form/agent-form/tool-popover/use-update-mcp.ts b/web/src/pages/agent/form/agent-form/tool-popover/use-update-mcp.ts new file mode 100644 index 000000000..dbb3e5b54 --- /dev/null +++ b/web/src/pages/agent/form/agent-form/tool-popover/use-update-mcp.ts @@ -0,0 +1,74 @@ +import { useListMcpServer } from '@/hooks/use-mcp-request'; +import { IAgentForm } from '@/interfaces/database/agent'; +import { AgentFormContext } from '@/pages/agent/context'; +import useGraphStore from '@/pages/agent/store'; +import { get } from 'lodash'; +import { useCallback, useContext, useMemo } from 'react'; + +export function useGetNodeMCP() { + const node = useContext(AgentFormContext); + + return useMemo(() => { + const mcp: IAgentForm['mcp'] = get(node, 'data.form.mcp'); + return mcp; + }, [node]); +} + +export function useUpdateAgentNodeMCP() { + const { updateNodeForm } = useGraphStore((state) => state); + const node = useContext(AgentFormContext); + const mcpList = useGetNodeMCP(); + const { data } = useListMcpServer(); + const mcpServers = data.mcp_servers; + + const findMcpTools = useCallback( + (mcpId: string) => { + const mcp = mcpServers.find((x) => x.id === mcpId); + return mcp?.variables.tools; + }, + [mcpServers], + ); + + const updateNodeMCP = useCallback( + (value: string[]) => { + if (node?.id) { + const nextValue = value.reduce((pre, cur) => { + const mcp = mcpList.find((x) => x.mcp_id === cur); + const tools = findMcpTools(cur); + if (mcp) { + pre.push(mcp); + } else if (tools) { + pre.push({ + mcp_id: cur, + tools, + }); + } + return pre; + }, []); + + updateNodeForm(node?.id, nextValue, ['mcp']); + } + }, + [node?.id, updateNodeForm, mcpList, findMcpTools], + ); + + return { updateNodeMCP }; +} + +export function useDeleteAgentNodeMCP() { + const { updateNodeForm } = useGraphStore((state) => state); + const mcpList = useGetNodeMCP(); + const node = useContext(AgentFormContext); + + const deleteNodeMCP = useCallback( + (value: string) => () => { + const nextMCP = mcpList.filter((x) => x.mcp_id !== value); + if (node?.id) { + updateNodeForm(node?.id, nextMCP, ['mcp']); + } + }, + [node?.id, mcpList, updateNodeForm], + ); + + return { deleteNodeMCP }; +} diff --git a/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts b/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts index 52d893cc2..90948235a 100644 --- a/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts +++ b/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts @@ -45,35 +45,22 @@ export function useUpdateAgentNodeTools() { [node?.id, tools, updateNodeForm], ); - const deleteNodeTool = useCallback( - (value: string) => { - updateNodeTools([value]); - }, - [updateNodeTools], - ); - - return { updateNodeTools, deleteNodeTool }; + return { updateNodeTools }; } export function useDeleteAgentNodeTools() { const { updateNodeForm } = useGraphStore((state) => state); const tools = useGetNodeTools(); const node = useContext(AgentFormContext); - const deleteAgentToolNodeById = useGraphStore( - (state) => state.deleteAgentToolNodeById, - ); const deleteNodeTool = useCallback( (value: string) => () => { const nextTools = tools.filter((x) => x.component_name !== value); if (node?.id) { updateNodeForm(node?.id, nextTools, ['tools']); - if (nextTools.length === 0) { - deleteAgentToolNodeById(node?.id); - } } }, - [deleteAgentToolNodeById, node?.id, tools, updateNodeForm], + [node?.id, tools, updateNodeForm], ); return { deleteNodeTool }; diff --git a/web/src/pages/agent/form/agent-form/use-get-tools.ts b/web/src/pages/agent/form/agent-form/use-get-tools.ts index c9f37113d..32bf3f0ef 100644 --- a/web/src/pages/agent/form/agent-form/use-get-tools.ts +++ b/web/src/pages/agent/form/agent-form/use-get-tools.ts @@ -13,3 +13,14 @@ export function useGetAgentToolNames() { return { toolNames }; } + +export function useGetAgentMCPIds() { + const node = useContext(AgentFormContext); + + const mcpIds = useMemo(() => { + const ids: IAgentForm['mcp'] = get(node, 'data.form.mcp', []); + return ids.map((x) => x.mcp_id); + }, [node]); + + return { mcpIds }; +} diff --git a/web/src/pages/agent/form/tool-form/mcp-form/index.tsx b/web/src/pages/agent/form/tool-form/mcp-form/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/web/src/pages/agent/hooks/use-find-mcp-by-id.ts b/web/src/pages/agent/hooks/use-find-mcp-by-id.ts new file mode 100644 index 000000000..e4c5aed5a --- /dev/null +++ b/web/src/pages/agent/hooks/use-find-mcp-by-id.ts @@ -0,0 +1,12 @@ +import { useListMcpServer } from '@/hooks/use-mcp-request'; + +export function useFindMcpById() { + const { data } = useListMcpServer(); + + const findMcpById = (id: string) => + data.mcp_servers.find((item) => item.id === id); + + return { + findMcpById, + }; +} diff --git a/web/src/pages/dataset/dataset/index.tsx b/web/src/pages/dataset/dataset/index.tsx index da8a09dd9..670f7306a 100644 --- a/web/src/pages/dataset/dataset/index.tsx +++ b/web/src/pages/dataset/dataset/index.tsx @@ -90,7 +90,7 @@ export default function Dataset() { - {t('fileManager.newFolder')} + {t('knowledgeDetails.emptyFiles')} diff --git a/web/src/pages/profile-setting/mcp/edit-mcp-form.tsx b/web/src/pages/profile-setting/mcp/edit-mcp-form.tsx index ceb2b0cf8..05c3f7491 100644 --- a/web/src/pages/profile-setting/mcp/edit-mcp-form.tsx +++ b/web/src/pages/profile-setting/mcp/edit-mcp-form.tsx @@ -15,9 +15,12 @@ import { Input } from '@/components/ui/input'; import { RAGFlowSelect } from '@/components/ui/select'; import { IModalProps } from '@/interfaces/common'; import { buildOptions } from '@/utils/form'; +import { Editor, loader } from '@monaco-editor/react'; import { Dispatch, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; +loader.config({ paths: { vs: '/vs' } }); + export const FormId = 'EditMcpForm'; export enum ServerType { @@ -50,7 +53,7 @@ export function useBuildFormSchema() { message: t('common.namePlaceholder'), }) .trim(), - // variables: z.object({}).optional(), + headers: z.record(z.string(), z.any()).optional(), }); return FormSchema; @@ -137,6 +140,28 @@ export function EditMcpForm({ )} /> + ( + + Headers + + { + field.onChange(value); + setFieldChanged(true); + }} + /> + + + + )} + /> );