Skip to content

Commit 9d0ce65

Browse files
authored
fix: Workflow tool - MCP plugin cannot retrieve the tool (#4984)
1 parent 3ea15a4 commit 9d0ce65

File tree

5 files changed

+100
-9
lines changed

5 files changed

+100
-9
lines changed

apps/tools/serializers/tool_workflow.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
@date:2026/3/6 13:59
77
@desc:
88
"""
9+
import asyncio
10+
import json
911
# coding=utf-8
1012
import pickle
1113
from functools import reduce
@@ -24,6 +26,7 @@
2426
from application.flow.i_step_node import ToolWorkflowPostHandler
2527
from application.flow.tool_workflow_manage import ToolWorkflowManage
2628
from application.models import ChatRecord
29+
from application.serializers.application import McpServersSerializer, get_mcp_tools
2730
from application.serializers.common import ToolExecute
2831
from common.exception.app_exception import AppApiException
2932
from common.field.common import UploadedFileField
@@ -169,6 +172,9 @@ def edit(self, instance: Dict):
169172
'work_flow': instance.get('work_flow',
170173
{}), },
171174
defaults={
175+
'tool_id': self.data.get("tool_id"),
176+
'workspace_id': self.data.get(
177+
'workspace_id'),
172178
'work_flow': instance.get('work_flow')
173179
})
174180
return self.one()
@@ -192,5 +198,40 @@ def edit(self, instance: Dict):
192198

193199
def one(self):
194200
self.is_valid(raise_exception=True)
195-
workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get('knowledge_id')).first()
201+
workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get('tool_id')).first()
196202
return {**ToolWorkflowModelSerializer(workflow).data}
203+
204+
205+
class ToolWorkflowMcpSerializer(serializers.Serializer):
206+
tool_id = serializers.UUIDField(required=True, label=_('Tool id'))
207+
user_id = serializers.UUIDField(required=True, label=_("User ID"))
208+
workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID"))
209+
210+
def is_valid(self, *, raise_exception=False):
211+
super().is_valid(raise_exception=True)
212+
workspace_id = self.data.get('workspace_id')
213+
query_set = QuerySet(Tool).filter(id=self.data.get('tool_id'))
214+
if workspace_id:
215+
query_set = query_set.filter(workspace_id=workspace_id)
216+
if not query_set.exists():
217+
raise AppApiException(500, _('Tool id does not exist'))
218+
219+
def get_mcp_servers(self, instance, with_valid=True):
220+
if with_valid:
221+
self.is_valid(raise_exception=True)
222+
McpServersSerializer(data=instance).is_valid(raise_exception=True)
223+
servers = json.loads(instance.get('mcp_servers'))
224+
for server, config in servers.items():
225+
if config.get('transport') not in ['sse', 'streamable_http']:
226+
raise AppApiException(500, _('Only support transport=sse or transport=streamable_http'))
227+
tools = []
228+
for server in servers:
229+
tools += [
230+
{
231+
'server': server,
232+
'name': tool.name,
233+
'description': tool.description,
234+
'args_schema': tool.args_schema,
235+
}
236+
for tool in asyncio.run(get_mcp_tools({server: servers[server]}))]
237+
return tools

apps/tools/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@
3333
path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version', views.ToolWorkflowVersionView.as_view()),
3434
path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version/<int:current_page>/<int:page_size>', views.ToolWorkflowVersionView.Page.as_view()),
3535
path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version/<str:tool_version_id>', views.ToolWorkflowVersionView.Operate.as_view()),
36+
path('workspace/<str:workspace_id>/tool/<str:tool_id>/mcp_tools', views.McpServers.as_view()),
3637

3738
]

apps/tools/views/tool_workflow.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from rest_framework.request import Request
66
from rest_framework.views import APIView
77

8+
from application.api.application_api import SpeechToTextAPI
89
from common.auth import TokenAuth
910
from common.auth.authentication import has_permissions, get_is_permissions
1011
from common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants
@@ -13,7 +14,7 @@
1314
from knowledge.api.knowledge_workflow import KnowledgeWorkflowApi
1415
from knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer
1516
from tools.api.tool_workflow import ToolWorkflowApi, ToolWorkflowExportApi, ToolWorkflowImportApi
16-
from tools.serializers.tool_workflow import ToolWorkflowSerializer
17+
from tools.serializers.tool_workflow import ToolWorkflowSerializer, ToolWorkflowMcpSerializer
1718
from tools.views import get_tool_operation_object
1819

1920

@@ -160,3 +161,29 @@ def post(self, request: Request, workspace_id: str, tool_id: str):
160161
request.data,
161162
request.user,
162163
True)
164+
165+
166+
class McpServers(APIView):
167+
authentication_classes = [TokenAuth]
168+
169+
@extend_schema(
170+
methods=['GET'],
171+
description=_("Get the list of MCP tools"),
172+
summary=_("Get the list of MCP tools"),
173+
operation_id=_("Get the list of MCP tools"), # type: ignore
174+
parameters=SpeechToTextAPI.get_parameters(),
175+
request=SpeechToTextAPI.get_request(),
176+
responses=SpeechToTextAPI.get_response(),
177+
tags=[_('Tool')] # type: ignore
178+
)
179+
@has_permissions(PermissionConstants.TOOL_READ.get_workspace_tool_permission(),
180+
PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),
181+
ViewPermission([RoleConstants.USER.get_workspace_role()],
182+
[PermissionConstants.TOOL.get_workspace_tool_permission()],
183+
CompareConstants.AND),
184+
RoleConstants.WORKSPACE_MANAGE.get_workspace_role())
185+
def post(self, request: Request, workspace_id, tool_id: str):
186+
return result.success(ToolWorkflowMcpSerializer(
187+
data={'mcp_servers': request.query_params.get('mcp_servers'), 'workspace_id': workspace_id,
188+
'user_id': request.user.id,
189+
'tool_id': tool_id}).get_mcp_servers(request.data))

ui/src/api/tool/tool.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,16 @@ const generateCode: (data: any) => Promise<Result<any>> = (data: any) => {
290290
const p = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api'
291291
return postStream(`${p}${prefix.value}/generate_code`, data)
292292
}
293-
293+
/**
294+
* mcp 节点
295+
*/
296+
const getMcpTools: (
297+
tool_id: string,
298+
mcp_servers: any,
299+
loading?: Ref<boolean>,
300+
) => Promise<Result<any>> = (tool_id, mcp_servers, loading) => {
301+
return post(`${prefix.value}/${tool_id}/mcp_tools`, { mcp_servers }, {}, loading)
302+
}
294303
export default {
295304
getToolList,
296305
getAllToolList,
@@ -318,4 +327,5 @@ export default {
318327
publish,
319328
debugToolWorkflow,
320329
generateCode,
330+
getMcpTools,
321331
}

ui/src/workflow/nodes/mcp-node/index.vue

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,12 @@
6464
</el-avatar>
6565
<ToolIcon v-else :size="20" :type="mcpTool?.tool_type" class="mr-8" />
6666
<span>{{ mcpTool.name }}</span>
67-
<el-tag v-if="mcpTool.scope === 'SHARED'" size="small" type="info" class="info-tag ml-8">
67+
<el-tag
68+
v-if="mcpTool.scope === 'SHARED'"
69+
size="small"
70+
type="info"
71+
class="info-tag ml-8"
72+
>
6873
{{ t('views.shared.title') }}
6974
</el-tag>
7075
</div>
@@ -371,15 +376,22 @@ function getTools() {
371376
}
372377
373378
function _getTools(mcp_servers: any) {
374-
console.log({ type: [WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflow_mode)
379+
console.log({
380+
type: [WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflow_mode)
375381
? 'application'
376382
: 'knowledge',
377-
systemType: apiType.value
383+
systemType: apiType.value,
378384
})
385+
const resourceDict = {
386+
[WorkflowMode.Application]: 'application',
387+
[WorkflowMode.ApplicationLoop]: 'application',
388+
[WorkflowMode.Knowledge]: 'knowledge',
389+
[WorkflowMode.KnowledgeLoop]: 'knowledge',
390+
[WorkflowMode.Tool]: 'tool',
391+
[WorkflowMode.ToolLoop]: 'tool',
392+
}
379393
loadSharedApi({
380-
type: [WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflow_mode)
381-
? 'application'
382-
: 'knowledge',
394+
type: resourceDict[workflow_mode],
383395
systemType: apiType.value,
384396
})
385397
.getMcpTools(id, mcp_servers, loading)

0 commit comments

Comments
 (0)