Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion apps/tools/serializers/tool_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
@date:2026/3/6 13:59
@desc:
"""
import asyncio
import json
# coding=utf-8
import pickle
from functools import reduce
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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
1 change: 1 addition & 0 deletions apps/tools/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@
path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version', views.ToolWorkflowVersionView.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version/<int:current_page>/<int:page_size>', views.ToolWorkflowVersionView.Page.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version/<str:tool_version_id>', views.ToolWorkflowVersionView.Operate.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/mcp_tools', views.McpServers.as_view()),

]
29 changes: 28 additions & 1 deletion apps/tools/views/tool_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -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))
12 changes: 11 additions & 1 deletion ui/src/api/tool/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,16 @@ const generateCode: (data: any) => Promise<Result<any>> = (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<boolean>,
) => Promise<Result<any>> = (tool_id, mcp_servers, loading) => {
return post(`${prefix.value}/${tool_id}/mcp_tools`, { mcp_servers }, {}, loading)
}
export default {
getToolList,
getAllToolList,
Expand Down Expand Up @@ -318,4 +327,5 @@ export default {
publish,
debugToolWorkflow,
generateCode,
getMcpTools,
}
24 changes: 18 additions & 6 deletions ui/src/workflow/nodes/mcp-node/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@
</el-avatar>
<ToolIcon v-else :size="20" :type="mcpTool?.tool_type" class="mr-8" />
<span>{{ mcpTool.name }}</span>
<el-tag v-if="mcpTool.scope === 'SHARED'" size="small" type="info" class="info-tag ml-8">
<el-tag
v-if="mcpTool.scope === 'SHARED'"
size="small"
type="info"
class="info-tag ml-8"
>
{{ t('views.shared.title') }}
</el-tag>
</div>
Expand Down Expand Up @@ -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)
Expand Down
Loading