mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-16 18:45:55 +08:00
Merge branch 'feat/mcp' into deploy/dev
This commit is contained in:
commit
657acdcfb9
8
api/controllers/mcp/__init__.py
Normal file
8
api/controllers/mcp/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
from flask import Blueprint
|
||||
|
||||
from libs.external_api import ExternalApi
|
||||
|
||||
bp = Blueprint("mcp", __name__, url_prefix="/mcp")
|
||||
api = ExternalApi(bp)
|
||||
|
||||
from . import mcp
|
66
api/controllers/mcp/mcp.py
Normal file
66
api/controllers/mcp/mcp.py
Normal file
@ -0,0 +1,66 @@
|
||||
from amqp import NotFound
|
||||
from flask_restful import Resource, reqparse
|
||||
from pydantic import ValidationError
|
||||
|
||||
from controllers.mcp import api
|
||||
from controllers.web.error import (
|
||||
AppUnavailableError,
|
||||
)
|
||||
from core.app.app_config.entities import VariableEntity
|
||||
from core.mcp.server.handler import MCPServerReuqestHandler
|
||||
from core.mcp.types import ClientRequest
|
||||
from extensions.ext_database import db
|
||||
from libs import helper
|
||||
from models.model import App, AppMCPServer, AppMode
|
||||
|
||||
|
||||
class MCPAppApi(Resource):
|
||||
def post(self, server_code):
|
||||
def int_or_str(value):
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
elif isinstance(value, str):
|
||||
return int(value)
|
||||
else:
|
||||
raise ValueError("Invalid id")
|
||||
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument("jsonrpc", type=str, required=True, location="json")
|
||||
parser.add_argument("method", type=str, required=True, location="json")
|
||||
parser.add_argument("params", type=dict, required=True, location="json")
|
||||
parser.add_argument("id", type=int_or_str, required=True, location="json")
|
||||
args = parser.parse_args()
|
||||
server = db.session.query(AppMCPServer).filter(AppMCPServer.server_code == server_code).first()
|
||||
if not server:
|
||||
raise NotFound("Server Not Found")
|
||||
app = db.session.query(App).filter(App.id == server.app_id).first()
|
||||
if not app:
|
||||
raise NotFound("App Not Found")
|
||||
if app.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
|
||||
workflow = app.workflow
|
||||
if workflow is None:
|
||||
raise AppUnavailableError()
|
||||
|
||||
features_dict = workflow.features_dict
|
||||
user_input_form = workflow.user_input_form(to_old_structure=True)
|
||||
else:
|
||||
app_model_config = app.app_model_config
|
||||
if app_model_config is None:
|
||||
raise AppUnavailableError()
|
||||
|
||||
features_dict = app_model_config.to_dict()
|
||||
|
||||
user_input_form = features_dict.get("user_input_form", [])
|
||||
try:
|
||||
user_input_form = [VariableEntity.model_validate(list(item.values())[0]) for item in user_input_form]
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"Invalid user_input_form: {str(e)}")
|
||||
try:
|
||||
request = ClientRequest.model_validate(args)
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"Invalid MCP request: {str(e)}")
|
||||
mcp_server_handler = MCPServerReuqestHandler(app, request, user_input_form)
|
||||
return helper.compact_generate_response(mcp_server_handler.handle())
|
||||
|
||||
|
||||
api.add_resource(MCPAppApi, "/server/<string:server_code>/mcp")
|
@ -1,7 +1,6 @@
|
||||
import logging
|
||||
|
||||
from flask_restful import Resource, reqparse
|
||||
from pydantic import ValidationError
|
||||
from flask_restful import reqparse
|
||||
from werkzeug.exceptions import InternalServerError, NotFound
|
||||
|
||||
import services
|
||||
@ -18,7 +17,6 @@ from controllers.web.error import (
|
||||
)
|
||||
from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError
|
||||
from controllers.web.wraps import WebApiResource
|
||||
from core.app.app_config.entities import VariableEntity
|
||||
from core.app.apps.base_app_queue_manager import AppQueueManager
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from core.errors.error import (
|
||||
@ -26,13 +24,10 @@ from core.errors.error import (
|
||||
ProviderTokenNotInitError,
|
||||
QuotaExceededError,
|
||||
)
|
||||
from core.mcp.server.handler import MCPServerReuqestHandler
|
||||
from core.mcp.types import ClientRequest
|
||||
from core.model_runtime.errors.invoke import InvokeError
|
||||
from extensions.ext_database import db
|
||||
from libs import helper
|
||||
from libs.helper import uuid_value
|
||||
from models.model import App, AppMCPServer, AppMode
|
||||
from models.model import AppMode
|
||||
from services.app_generate_service import AppGenerateService
|
||||
from services.errors.llm import InvokeRateLimitError
|
||||
|
||||
@ -154,57 +149,7 @@ class ChatStopApi(WebApiResource):
|
||||
return {"result": "success"}, 200
|
||||
|
||||
|
||||
class ChatMCPApi(Resource):
|
||||
def post(self, server_code):
|
||||
def int_or_str(value):
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
elif isinstance(value, str):
|
||||
return int(value)
|
||||
else:
|
||||
raise ValueError("Invalid id")
|
||||
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument("jsonrpc", type=str, required=True, location="json")
|
||||
parser.add_argument("method", type=str, required=True, location="json")
|
||||
parser.add_argument("params", type=dict, required=True, location="json")
|
||||
parser.add_argument("id", type=int_or_str, required=True, location="json")
|
||||
args = parser.parse_args()
|
||||
server = db.session.query(AppMCPServer).filter(AppMCPServer.server_code == server_code).first()
|
||||
if not server:
|
||||
raise NotFound("Server Not Found")
|
||||
app = db.session.query(App).filter(App.id == server.app_id).first()
|
||||
if not app:
|
||||
raise NotFound("App Not Found")
|
||||
if app.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
|
||||
workflow = app.workflow
|
||||
if workflow is None:
|
||||
raise AppUnavailableError()
|
||||
|
||||
features_dict = workflow.features_dict
|
||||
user_input_form = workflow.user_input_form(to_old_structure=True)
|
||||
else:
|
||||
app_model_config = app.app_model_config
|
||||
if app_model_config is None:
|
||||
raise AppUnavailableError()
|
||||
|
||||
features_dict = app_model_config.to_dict()
|
||||
|
||||
user_input_form = features_dict.get("user_input_form", [])
|
||||
try:
|
||||
user_input_form = [VariableEntity.model_validate(item) for item in user_input_form]
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"Invalid user_input_form: {str(e)}")
|
||||
try:
|
||||
request = ClientRequest.model_validate(args)
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"Invalid MCP request: {str(e)}")
|
||||
mcp_server_handler = MCPServerReuqestHandler(app, request, user_input_form)
|
||||
return helper.compact_generate_response(mcp_server_handler.handle())
|
||||
|
||||
|
||||
api.add_resource(CompletionApi, "/completion-messages")
|
||||
api.add_resource(CompletionStopApi, "/completion-messages/<string:task_id>/stop")
|
||||
api.add_resource(ChatApi, "/chat-messages")
|
||||
api.add_resource(ChatMCPApi, "/server/<string:server_code>/mcp")
|
||||
api.add_resource(ChatStopApi, "/chat-messages/<string:task_id>/stop")
|
||||
|
@ -22,9 +22,9 @@ class MCPServerReuqestHandler:
|
||||
def __init__(self, app: App, request: types.ClientRequest, user_input_form: list[VariableEntity]):
|
||||
self.app = app
|
||||
self.request = request
|
||||
self.mcp_server: AppMCPServer = self.app.mcp_server
|
||||
if not self.mcp_server:
|
||||
if not self.app.mcp_server:
|
||||
raise ValueError("MCP server not found")
|
||||
self.mcp_server: AppMCPServer = self.app.mcp_server
|
||||
self.end_user = self.retrieve_end_user()
|
||||
self.user_input_form = user_input_form
|
||||
|
||||
@ -47,13 +47,9 @@ class MCPServerReuqestHandler:
|
||||
"required": required,
|
||||
},
|
||||
},
|
||||
"required": "query",
|
||||
"required": ["query", "inputs"],
|
||||
}
|
||||
|
||||
@property
|
||||
def output_parameters(self):
|
||||
return self.app.output_schema
|
||||
|
||||
@property
|
||||
def capabilities(self):
|
||||
return types.ServerCapabilities(
|
||||
@ -160,6 +156,7 @@ class MCPServerReuqestHandler:
|
||||
parameters = {}
|
||||
required = []
|
||||
for item in user_input_form:
|
||||
parameters[item.variable] = {}
|
||||
if item.type in (
|
||||
VariableEntityType.FILE,
|
||||
VariableEntityType.FILE_LIST,
|
||||
@ -168,12 +165,13 @@ class MCPServerReuqestHandler:
|
||||
continue
|
||||
if item.required:
|
||||
required.append(item.variable)
|
||||
parameters[item.variable]["description"] = self.mcp_server.parameters_dict[item.label]["description"]
|
||||
description = self.mcp_server.parameters_dict[item.label]
|
||||
parameters[item.variable]["description"] = description
|
||||
if item.type in (VariableEntityType.TEXT_INPUT, VariableEntityType.PARAGRAPH):
|
||||
parameters[item.variable]["type"] = "string"
|
||||
elif item.type == VariableEntityType.SELECT:
|
||||
parameters[item.variable]["type"] = "string"
|
||||
parameters[item.variable]["enum"] = item.options
|
||||
elif item.type == VariableEntityType.NUMBER:
|
||||
parameters[item.variable]["type"] = "number"
|
||||
parameters[item.variable]["type"] = "float"
|
||||
return parameters, required
|
||||
|
@ -10,6 +10,7 @@ def init_app(app: DifyApp):
|
||||
from controllers.console import bp as console_app_bp
|
||||
from controllers.files import bp as files_bp
|
||||
from controllers.inner_api import bp as inner_api_bp
|
||||
from controllers.mcp import bp as mcp_bp
|
||||
from controllers.service_api import bp as service_api_bp
|
||||
from controllers.web import bp as web_bp
|
||||
|
||||
@ -46,3 +47,4 @@ def init_app(app: DifyApp):
|
||||
app.register_blueprint(files_bp)
|
||||
|
||||
app.register_blueprint(inner_api_bp)
|
||||
app.register_blueprint(mcp_bp)
|
||||
|
@ -10,7 +10,7 @@ from dify_app import DifyApp
|
||||
from extensions.ext_database import db
|
||||
from libs.passport import PassportService
|
||||
from models.account import Account, Tenant, TenantAccountJoin
|
||||
from models.model import EndUser
|
||||
from models.model import AppMCPServer, EndUser
|
||||
from services.account_service import AccountService
|
||||
|
||||
login_manager = flask_login.LoginManager()
|
||||
@ -71,6 +71,21 @@ def load_user_from_request(request_from_flask_login):
|
||||
if not end_user:
|
||||
raise NotFound("End user not found.")
|
||||
return end_user
|
||||
elif request.blueprint == "mcp":
|
||||
server_code = request.view_args.get("server_code") if request.view_args else None
|
||||
if not server_code:
|
||||
raise Unauthorized("Invalid Authorization token.")
|
||||
app_mcp_server = db.session.query(AppMCPServer).filter(AppMCPServer.server_code == server_code).first()
|
||||
if not app_mcp_server:
|
||||
raise NotFound("App MCP server not found.")
|
||||
end_user = (
|
||||
db.session.query(EndUser)
|
||||
.filter(EndUser.external_user_id == app_mcp_server.id, EndUser.type == "mcp")
|
||||
.first()
|
||||
)
|
||||
if not end_user:
|
||||
raise NotFound("End user not found.")
|
||||
return end_user
|
||||
|
||||
|
||||
@user_logged_in.connect
|
||||
|
Loading…
x
Reference in New Issue
Block a user