11import io
2+ from urllib .parse import urlparse
23
3- from flask import send_file
4+ from flask import redirect , send_file
45from flask_login import current_user
56from flask_restful import Resource , reqparse
67from sqlalchemy .orm import Session
910from configs import dify_config
1011from controllers .console import api
1112from controllers .console .wraps import account_initialization_required , enterprise_license_required , setup_required
13+ from core .mcp .auth .auth_flow import auth , handle_callback
14+ from core .mcp .auth .auth_provider import OAuthClientProvider
15+ from core .mcp .error import MCPAuthError , MCPError
16+ from core .mcp .mcp_client import MCPClient
1217from core .model_runtime .utils .encoders import jsonable_encoder
1318from extensions .ext_database import db
1419from libs .helper import alphanumeric , uuid_value
1520from libs .login import login_required
1621from services .tools .api_tools_manage_service import ApiToolManageService
1722from services .tools .builtin_tools_manage_service import BuiltinToolManageService
23+ from services .tools .mcp_tools_mange_service import MCPToolManageService
1824from services .tools .tool_labels_service import ToolLabelsService
1925from services .tools .tools_manage_service import ToolCommonService
26+ from services .tools .tools_transform_service import ToolTransformService
2027from services .tools .workflow_tools_manage_service import WorkflowToolManageService
2128
2229
30+ def is_valid_url (url : str ) -> bool :
31+ if not url :
32+ return False
33+
34+ try :
35+ parsed = urlparse (url )
36+ return all ([parsed .scheme , parsed .netloc ]) and parsed .scheme in ["http" , "https" ]
37+ except Exception :
38+ return False
39+
40+
2341class ToolProviderListApi (Resource ):
2442 @setup_required
2543 @login_required
@@ -34,7 +52,7 @@ def get(self):
3452 req .add_argument (
3553 "type" ,
3654 type = str ,
37- choices = ["builtin" , "model" , "api" , "workflow" ],
55+ choices = ["builtin" , "model" , "api" , "workflow" , "mcp" ],
3856 required = False ,
3957 nullable = True ,
4058 location = "args" ,
@@ -613,6 +631,166 @@ def get(self):
613631 return jsonable_encoder (ToolLabelsService .list_tool_labels ())
614632
615633
634+ class ToolProviderMCPApi (Resource ):
635+ @setup_required
636+ @login_required
637+ @account_initialization_required
638+ def post (self ):
639+ parser = reqparse .RequestParser ()
640+ parser .add_argument ("server_url" , type = str , required = True , nullable = False , location = "json" )
641+ parser .add_argument ("name" , type = str , required = True , nullable = False , location = "json" )
642+ parser .add_argument ("icon" , type = str , required = True , nullable = False , location = "json" )
643+ parser .add_argument ("icon_type" , type = str , required = True , nullable = False , location = "json" )
644+ parser .add_argument ("icon_background" , type = str , required = False , nullable = True , location = "json" , default = "" )
645+ parser .add_argument ("server_identifier" , type = str , required = True , nullable = False , location = "json" )
646+ args = parser .parse_args ()
647+ user = current_user
648+ if not is_valid_url (args ["server_url" ]):
649+ raise ValueError ("Server URL is not valid." )
650+ return jsonable_encoder (
651+ MCPToolManageService .create_mcp_provider (
652+ tenant_id = user .current_tenant_id ,
653+ server_url = args ["server_url" ],
654+ name = args ["name" ],
655+ icon = args ["icon" ],
656+ icon_type = args ["icon_type" ],
657+ icon_background = args ["icon_background" ],
658+ user_id = user .id ,
659+ server_identifier = args ["server_identifier" ],
660+ )
661+ )
662+
663+ @setup_required
664+ @login_required
665+ @account_initialization_required
666+ def put (self ):
667+ parser = reqparse .RequestParser ()
668+ parser .add_argument ("server_url" , type = str , required = True , nullable = False , location = "json" )
669+ parser .add_argument ("name" , type = str , required = True , nullable = False , location = "json" )
670+ parser .add_argument ("icon" , type = str , required = True , nullable = False , location = "json" )
671+ parser .add_argument ("icon_type" , type = str , required = True , nullable = False , location = "json" )
672+ parser .add_argument ("icon_background" , type = str , required = False , nullable = True , location = "json" )
673+ parser .add_argument ("provider_id" , type = str , required = True , nullable = False , location = "json" )
674+ parser .add_argument ("server_identifier" , type = str , required = True , nullable = False , location = "json" )
675+ args = parser .parse_args ()
676+ if not is_valid_url (args ["server_url" ]):
677+ if "[__HIDDEN__]" in args ["server_url" ]:
678+ pass
679+ else :
680+ raise ValueError ("Server URL is not valid." )
681+ MCPToolManageService .update_mcp_provider (
682+ tenant_id = current_user .current_tenant_id ,
683+ provider_id = args ["provider_id" ],
684+ server_url = args ["server_url" ],
685+ name = args ["name" ],
686+ icon = args ["icon" ],
687+ icon_type = args ["icon_type" ],
688+ icon_background = args ["icon_background" ],
689+ server_identifier = args ["server_identifier" ],
690+ )
691+ return {"result" : "success" }
692+
693+ @setup_required
694+ @login_required
695+ @account_initialization_required
696+ def delete (self ):
697+ parser = reqparse .RequestParser ()
698+ parser .add_argument ("provider_id" , type = str , required = True , nullable = False , location = "json" )
699+ args = parser .parse_args ()
700+ MCPToolManageService .delete_mcp_tool (tenant_id = current_user .current_tenant_id , provider_id = args ["provider_id" ])
701+ return {"result" : "success" }
702+
703+
704+ class ToolMCPAuthApi (Resource ):
705+ @setup_required
706+ @login_required
707+ @account_initialization_required
708+ def post (self ):
709+ parser = reqparse .RequestParser ()
710+ parser .add_argument ("provider_id" , type = str , required = True , nullable = False , location = "json" )
711+ parser .add_argument ("authorization_code" , type = str , required = False , nullable = True , location = "json" )
712+ args = parser .parse_args ()
713+ provider_id = args ["provider_id" ]
714+ tenant_id = current_user .current_tenant_id
715+ provider = MCPToolManageService .get_mcp_provider_by_provider_id (provider_id , tenant_id )
716+ if not provider :
717+ raise ValueError ("provider not found" )
718+ try :
719+ with MCPClient (
720+ provider .decrypted_server_url ,
721+ provider_id ,
722+ tenant_id ,
723+ authed = False ,
724+ authorization_code = args ["authorization_code" ],
725+ for_list = True ,
726+ ):
727+ MCPToolManageService .update_mcp_provider_credentials (
728+ mcp_provider = provider ,
729+ credentials = provider .decrypted_credentials ,
730+ authed = True ,
731+ )
732+ return {"result" : "success" }
733+
734+ except MCPAuthError :
735+ auth_provider = OAuthClientProvider (provider_id , tenant_id , for_list = True )
736+ return auth (auth_provider , provider .decrypted_server_url , args ["authorization_code" ])
737+ except MCPError as e :
738+ MCPToolManageService .update_mcp_provider_credentials (
739+ mcp_provider = provider ,
740+ credentials = {},
741+ authed = False ,
742+ )
743+ raise ValueError (f"Failed to connect to MCP server: { e } " ) from e
744+
745+
746+ class ToolMCPDetailApi (Resource ):
747+ @setup_required
748+ @login_required
749+ @account_initialization_required
750+ def get (self , provider_id ):
751+ user = current_user
752+ provider = MCPToolManageService .get_mcp_provider_by_provider_id (provider_id , user .current_tenant_id )
753+ return jsonable_encoder (ToolTransformService .mcp_provider_to_user_provider (provider , for_list = True ))
754+
755+
756+ class ToolMCPListAllApi (Resource ):
757+ @setup_required
758+ @login_required
759+ @account_initialization_required
760+ def get (self ):
761+ user = current_user
762+ tenant_id = user .current_tenant_id
763+
764+ tools = MCPToolManageService .retrieve_mcp_tools (tenant_id = tenant_id )
765+
766+ return [tool .to_dict () for tool in tools ]
767+
768+
769+ class ToolMCPUpdateApi (Resource ):
770+ @setup_required
771+ @login_required
772+ @account_initialization_required
773+ def get (self , provider_id ):
774+ tenant_id = current_user .current_tenant_id
775+ tools = MCPToolManageService .list_mcp_tool_from_remote_server (
776+ tenant_id = tenant_id ,
777+ provider_id = provider_id ,
778+ )
779+ return jsonable_encoder (tools )
780+
781+
782+ class ToolMCPCallbackApi (Resource ):
783+ def get (self ):
784+ parser = reqparse .RequestParser ()
785+ parser .add_argument ("code" , type = str , required = True , nullable = False , location = "args" )
786+ parser .add_argument ("state" , type = str , required = True , nullable = False , location = "args" )
787+ args = parser .parse_args ()
788+ state_key = args ["state" ]
789+ authorization_code = args ["code" ]
790+ handle_callback (state_key , authorization_code )
791+ return redirect (f"{ dify_config .CONSOLE_WEB_URL } /oauth-callback" )
792+
793+
616794# tool provider
617795api .add_resource (ToolProviderListApi , "/workspaces/current/tool-providers" )
618796
@@ -647,8 +825,15 @@ def get(self):
647825api .add_resource (ToolWorkflowProviderGetApi , "/workspaces/current/tool-provider/workflow/get" )
648826api .add_resource (ToolWorkflowProviderListToolApi , "/workspaces/current/tool-provider/workflow/tools" )
649827
828+ # mcp tool provider
829+ api .add_resource (ToolMCPDetailApi , "/workspaces/current/tool-provider/mcp/tools/<path:provider_id>" )
830+ api .add_resource (ToolProviderMCPApi , "/workspaces/current/tool-provider/mcp" )
831+ api .add_resource (ToolMCPUpdateApi , "/workspaces/current/tool-provider/mcp/update/<path:provider_id>" )
832+ api .add_resource (ToolMCPAuthApi , "/workspaces/current/tool-provider/mcp/auth" )
833+ api .add_resource (ToolMCPCallbackApi , "/mcp/oauth/callback" )
834+
650835api .add_resource (ToolBuiltinListApi , "/workspaces/current/tools/builtin" )
651836api .add_resource (ToolApiListApi , "/workspaces/current/tools/api" )
837+ api .add_resource (ToolMCPListAllApi , "/workspaces/current/tools/mcp" )
652838api .add_resource (ToolWorkflowListApi , "/workspaces/current/tools/workflow" )
653-
654839api .add_resource (ToolLabelsApi , "/workspaces/current/tool-labels" )
0 commit comments