Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

mcp.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. from typing import Optional, Union
  2. from flask_restx import Resource, reqparse
  3. from pydantic import ValidationError
  4. from controllers.console.app.mcp_server import AppMCPServerStatus
  5. from controllers.mcp import mcp_ns
  6. from core.app.app_config.entities import VariableEntity
  7. from core.mcp import types
  8. from core.mcp.server.streamable_http import MCPServerStreamableHTTPRequestHandler
  9. from core.mcp.types import ClientNotification, ClientRequest
  10. from core.mcp.utils import create_mcp_error_response
  11. from extensions.ext_database import db
  12. from libs import helper
  13. from models.model import App, AppMCPServer, AppMode
  14. def int_or_str(value):
  15. """Validate that a value is either an integer or string."""
  16. if isinstance(value, (int, str)):
  17. return value
  18. else:
  19. return None
  20. # Define parser for both documentation and validation
  21. mcp_request_parser = reqparse.RequestParser()
  22. mcp_request_parser.add_argument(
  23. "jsonrpc", type=str, required=True, location="json", help="JSON-RPC version (should be '2.0')"
  24. )
  25. mcp_request_parser.add_argument("method", type=str, required=True, location="json", help="The method to invoke")
  26. mcp_request_parser.add_argument("params", type=dict, required=False, location="json", help="Parameters for the method")
  27. mcp_request_parser.add_argument(
  28. "id", type=int_or_str, required=False, location="json", help="Request ID for tracking responses"
  29. )
  30. @mcp_ns.route("/server/<string:server_code>/mcp")
  31. class MCPAppApi(Resource):
  32. @mcp_ns.expect(mcp_request_parser)
  33. @mcp_ns.doc("handle_mcp_request")
  34. @mcp_ns.doc(description="Handle Model Context Protocol (MCP) requests for a specific server")
  35. @mcp_ns.doc(params={"server_code": "Unique identifier for the MCP server"})
  36. @mcp_ns.doc(
  37. responses={
  38. 200: "MCP response successfully processed",
  39. 400: "Invalid MCP request or parameters",
  40. 404: "Server or app not found",
  41. }
  42. )
  43. def post(self, server_code: str):
  44. """Handle MCP requests for a specific server.
  45. Processes JSON-RPC formatted requests according to the Model Context Protocol specification.
  46. Validates the server status and associated app before processing the request.
  47. Args:
  48. server_code: Unique identifier for the MCP server
  49. Returns:
  50. dict: JSON-RPC response from the MCP handler
  51. Raises:
  52. ValidationError: Invalid request format or parameters
  53. """
  54. # Parse and validate all arguments
  55. args = mcp_request_parser.parse_args()
  56. request_id: Optional[Union[int, str]] = args.get("id")
  57. server = db.session.query(AppMCPServer).where(AppMCPServer.server_code == server_code).first()
  58. if not server:
  59. return helper.compact_generate_response(
  60. create_mcp_error_response(request_id, types.INVALID_REQUEST, "Server Not Found")
  61. )
  62. if server.status != AppMCPServerStatus.ACTIVE:
  63. return helper.compact_generate_response(
  64. create_mcp_error_response(request_id, types.INVALID_REQUEST, "Server is not active")
  65. )
  66. app = db.session.query(App).where(App.id == server.app_id).first()
  67. if not app:
  68. return helper.compact_generate_response(
  69. create_mcp_error_response(request_id, types.INVALID_REQUEST, "App Not Found")
  70. )
  71. if app.mode in {AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value}:
  72. workflow = app.workflow
  73. if workflow is None:
  74. return helper.compact_generate_response(
  75. create_mcp_error_response(request_id, types.INVALID_REQUEST, "App is unavailable")
  76. )
  77. user_input_form = workflow.user_input_form(to_old_structure=True)
  78. else:
  79. app_model_config = app.app_model_config
  80. if app_model_config is None:
  81. return helper.compact_generate_response(
  82. create_mcp_error_response(request_id, types.INVALID_REQUEST, "App is unavailable")
  83. )
  84. features_dict = app_model_config.to_dict()
  85. user_input_form = features_dict.get("user_input_form", [])
  86. converted_user_input_form: list[VariableEntity] = []
  87. try:
  88. for item in user_input_form:
  89. variable_type = item.get("type", "") or list(item.keys())[0]
  90. variable = item[variable_type]
  91. converted_user_input_form.append(
  92. VariableEntity(
  93. type=variable_type,
  94. variable=variable.get("variable"),
  95. description=variable.get("description") or "",
  96. label=variable.get("label"),
  97. required=variable.get("required", False),
  98. max_length=variable.get("max_length"),
  99. options=variable.get("options") or [],
  100. )
  101. )
  102. except ValidationError as e:
  103. return helper.compact_generate_response(
  104. create_mcp_error_response(request_id, types.INVALID_PARAMS, f"Invalid user_input_form: {str(e)}")
  105. )
  106. try:
  107. request: ClientRequest | ClientNotification = ClientRequest.model_validate(args)
  108. except ValidationError as e:
  109. try:
  110. notification = ClientNotification.model_validate(args)
  111. request = notification
  112. except ValidationError as e:
  113. return helper.compact_generate_response(
  114. create_mcp_error_response(request_id, types.INVALID_PARAMS, f"Invalid MCP request: {str(e)}")
  115. )
  116. mcp_server_handler = MCPServerStreamableHTTPRequestHandler(app, request, converted_user_input_form)
  117. response = mcp_server_handler.handle()
  118. return helper.compact_generate_response(response)