From e55e0a06f88629393369acf0ccce3880602fd58e Mon Sep 17 00:00:00 2001 From: shaohuzhang1 Date: Tue, 31 Mar 2026 10:46:12 +0800 Subject: [PATCH] fix: Workflow tool - MCP plugin cannot retrieve the tool --- apps/tools/serializers/tool_workflow.py | 43 +++++++++++++++++++++++- apps/tools/urls.py | 1 + apps/tools/views/tool_workflow.py | 29 +++++++++++++++- ui/src/api/tool/tool.ts | 12 ++++++- ui/src/workflow/nodes/mcp-node/index.vue | 24 +++++++++---- 5 files changed, 100 insertions(+), 9 deletions(-) diff --git a/apps/tools/serializers/tool_workflow.py b/apps/tools/serializers/tool_workflow.py index a80cdee4ff1..ec64cb593dd 100644 --- a/apps/tools/serializers/tool_workflow.py +++ b/apps/tools/serializers/tool_workflow.py @@ -6,6 +6,8 @@ @date:2026/3/6 13:59 @desc: """ +import asyncio +import json # coding=utf-8 import pickle from functools import reduce @@ -24,6 +26,7 @@ from application.flow.i_step_node import ToolWorkflowPostHandler from application.flow.tool_workflow_manage import ToolWorkflowManage from application.models import ChatRecord +from application.serializers.application import McpServersSerializer, get_mcp_tools from application.serializers.common import ToolExecute from common.exception.app_exception import AppApiException from common.field.common import UploadedFileField @@ -169,6 +172,9 @@ def edit(self, instance: Dict): 'work_flow': instance.get('work_flow', {}), }, defaults={ + 'tool_id': self.data.get("tool_id"), + 'workspace_id': self.data.get( + 'workspace_id'), 'work_flow': instance.get('work_flow') }) return self.one() @@ -192,5 +198,40 @@ def edit(self, instance: Dict): def one(self): self.is_valid(raise_exception=True) - workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get('knowledge_id')).first() + workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get('tool_id')).first() return {**ToolWorkflowModelSerializer(workflow).data} + + +class ToolWorkflowMcpSerializer(serializers.Serializer): + tool_id = serializers.UUIDField(required=True, label=_('Tool id')) + user_id = serializers.UUIDField(required=True, label=_("User ID")) + workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) + + def is_valid(self, *, raise_exception=False): + super().is_valid(raise_exception=True) + workspace_id = self.data.get('workspace_id') + query_set = QuerySet(Tool).filter(id=self.data.get('tool_id')) + if workspace_id: + query_set = query_set.filter(workspace_id=workspace_id) + if not query_set.exists(): + raise AppApiException(500, _('Tool id does not exist')) + + def get_mcp_servers(self, instance, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + McpServersSerializer(data=instance).is_valid(raise_exception=True) + servers = json.loads(instance.get('mcp_servers')) + for server, config in servers.items(): + if config.get('transport') not in ['sse', 'streamable_http']: + raise AppApiException(500, _('Only support transport=sse or transport=streamable_http')) + tools = [] + for server in servers: + tools += [ + { + 'server': server, + 'name': tool.name, + 'description': tool.description, + 'args_schema': tool.args_schema, + } + for tool in asyncio.run(get_mcp_tools({server: servers[server]}))] + return tools diff --git a/apps/tools/urls.py b/apps/tools/urls.py index c199707dc15..00d25838cb7 100644 --- a/apps/tools/urls.py +++ b/apps/tools/urls.py @@ -33,5 +33,6 @@ path('workspace//tool//tool_version', views.ToolWorkflowVersionView.as_view()), path('workspace//tool//tool_version//', views.ToolWorkflowVersionView.Page.as_view()), path('workspace//tool//tool_version/', views.ToolWorkflowVersionView.Operate.as_view()), + path('workspace//tool//mcp_tools', views.McpServers.as_view()), ] diff --git a/apps/tools/views/tool_workflow.py b/apps/tools/views/tool_workflow.py index 3f009489886..a263bdcf96d 100644 --- a/apps/tools/views/tool_workflow.py +++ b/apps/tools/views/tool_workflow.py @@ -5,6 +5,7 @@ from rest_framework.request import Request from rest_framework.views import APIView +from application.api.application_api import SpeechToTextAPI from common.auth import TokenAuth from common.auth.authentication import has_permissions, get_is_permissions from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants @@ -13,7 +14,7 @@ from knowledge.api.knowledge_workflow import KnowledgeWorkflowApi from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer from tools.api.tool_workflow import ToolWorkflowApi, ToolWorkflowExportApi, ToolWorkflowImportApi -from tools.serializers.tool_workflow import ToolWorkflowSerializer +from tools.serializers.tool_workflow import ToolWorkflowSerializer, ToolWorkflowMcpSerializer from tools.views import get_tool_operation_object @@ -160,3 +161,29 @@ def post(self, request: Request, workspace_id: str, tool_id: str): request.data, request.user, True) + + +class McpServers(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['GET'], + description=_("Get the list of MCP tools"), + summary=_("Get the list of MCP tools"), + operation_id=_("Get the list of MCP tools"), # type: ignore + parameters=SpeechToTextAPI.get_parameters(), + request=SpeechToTextAPI.get_request(), + responses=SpeechToTextAPI.get_response(), + tags=[_('Tool')] # type: ignore + ) + @has_permissions(PermissionConstants.TOOL_READ.get_workspace_tool_permission(), + PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(), + ViewPermission([RoleConstants.USER.get_workspace_role()], + [PermissionConstants.TOOL.get_workspace_tool_permission()], + CompareConstants.AND), + RoleConstants.WORKSPACE_MANAGE.get_workspace_role()) + def post(self, request: Request, workspace_id, tool_id: str): + return result.success(ToolWorkflowMcpSerializer( + data={'mcp_servers': request.query_params.get('mcp_servers'), 'workspace_id': workspace_id, + 'user_id': request.user.id, + 'tool_id': tool_id}).get_mcp_servers(request.data)) diff --git a/ui/src/api/tool/tool.ts b/ui/src/api/tool/tool.ts index f5b2ced6b42..b9e071906cb 100644 --- a/ui/src/api/tool/tool.ts +++ b/ui/src/api/tool/tool.ts @@ -290,7 +290,16 @@ const generateCode: (data: any) => Promise> = (data: any) => { const p = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api' return postStream(`${p}${prefix.value}/generate_code`, data) } - +/** + * mcp 节点 + */ +const getMcpTools: ( + tool_id: string, + mcp_servers: any, + loading?: Ref, +) => Promise> = (tool_id, mcp_servers, loading) => { + return post(`${prefix.value}/${tool_id}/mcp_tools`, { mcp_servers }, {}, loading) +} export default { getToolList, getAllToolList, @@ -318,4 +327,5 @@ export default { publish, debugToolWorkflow, generateCode, + getMcpTools, } diff --git a/ui/src/workflow/nodes/mcp-node/index.vue b/ui/src/workflow/nodes/mcp-node/index.vue index 3d0d3f1d06c..5e5a64e9b4f 100644 --- a/ui/src/workflow/nodes/mcp-node/index.vue +++ b/ui/src/workflow/nodes/mcp-node/index.vue @@ -64,7 +64,12 @@ {{ mcpTool.name }} - + {{ t('views.shared.title') }} @@ -371,15 +376,22 @@ function getTools() { } function _getTools(mcp_servers: any) { - console.log({ type: [WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflow_mode) + console.log({ + type: [WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflow_mode) ? 'application' : 'knowledge', - systemType: apiType.value + systemType: apiType.value, }) + const resourceDict = { + [WorkflowMode.Application]: 'application', + [WorkflowMode.ApplicationLoop]: 'application', + [WorkflowMode.Knowledge]: 'knowledge', + [WorkflowMode.KnowledgeLoop]: 'knowledge', + [WorkflowMode.Tool]: 'tool', + [WorkflowMode.ToolLoop]: 'tool', + } loadSharedApi({ - type: [WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflow_mode) - ? 'application' - : 'knowledge', + type: resourceDict[workflow_mode], systemType: apiType.value, }) .getMcpTools(id, mcp_servers, loading)