Class: ActionMCP::ApplicationController
- Inherits:
-
ActionController::API
- Object
- ActionController::API
- ActionMCP::ApplicationController
- Includes:
- ActionController::Instrumentation, JSONRPC_Rails::ControllerHelpers
- Defined in:
- app/controllers/action_mcp/application_controller.rb
Overview
Implements the MCP endpoints according to the 2025-03-26 specification. Supports GET for server-initiated SSE streams, POST for client messages (responding with JSON or SSE), and optionally DELETE for session termination.
Constant Summary collapse
- MCP_SESSION_ID_HEADER =
"Mcp-Session-Id"
Instance Method Summary collapse
-
#create ⇒ Object
Handles POST requests containing client JSON-RPC messages according to 2025-03-26 spec.
-
#destroy ⇒ Object
Handles DELETE requests for session termination (2025-03-26 spec).
-
#mcp_session ⇒ ActionMCP::Session
Provides the ActionMCP::Session for the current request.
-
#session_key ⇒ String
Provides a unique key for caching or pub/sub based on the session ID.
-
#show ⇒ Object
Handles GET requests - returns 405 Method Not Allowed as per MCP spec.
Instance Method Details
#create ⇒ Object
Handles POST requests containing client JSON-RPC messages according to 2025-03-26 spec. <rails-lens:routes:begin> ROUTE: /, name: mcp_post, via: POST <rails-lens:routes:end>
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'app/controllers/action_mcp/application_controller.rb', line 44 def create unless post_accept_headers_valid? id = extract_jsonrpc_id_from_request return render_not_acceptable(, id) end # Reject JSON-RPC batch requests as per MCP 2025-06-18 spec return render_bad_request("JSON-RPC batch requests are not supported", nil) if jsonrpc_params_batch? is_initialize_request = check_if_initialize_request(jsonrpc_params) session_initially_missing = extract_session_id.nil? session = mcp_session # Validate MCP-Protocol-Version header for non-initialize requests return unless validate_protocol_version_header unless (jsonrpc_params) if session_initially_missing id = jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil return render_bad_request("Mcp-Session-Id header is required for this request.", id) elsif session.nil? || session.new_record? id = jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil return render_not_found("Session not found.", id) elsif session.status == "closed" id = jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil return render_not_found("Session has been terminated.", id) end end if session.new_record? session.save! response.headers[MCP_SESSION_ID_HEADER] = session.id end # Authenticate the request via gateway (skipped for initialization-related requests) if (jsonrpc_params) # Skipping authentication for initialization request: #{jsonrpc_params.method} else authenticate_gateway! return if performed? end # Use return mode for the transport handler when we need to capture responses transport_handler = Server::TransportHandler.new(session, messaging_mode: :return) json_rpc_handler = Server::JsonRpcHandler.new(transport_handler) result = json_rpc_handler.call(jsonrpc_params) process_handler_results(result, session, session_initially_missing, is_initialize_request) rescue StandardError => e Rails.logger.error "Unified POST Error: #{e.class} - #{e.message}\n#{e.backtrace.join("\n")}" id = begin jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil rescue StandardError nil end render_internal_server_error("An unexpected error occurred.", id) unless performed? end |
#destroy ⇒ Object
Handles DELETE requests for session termination (2025-03-26 spec). <rails-lens:routes:begin> ROUTE: /, name: mcp_delete, via: DELETE <rails-lens:routes:end>
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'app/controllers/action_mcp/application_controller.rb', line 106 def destroy session_id_from_header = extract_session_id return render_bad_request("Mcp-Session-Id header is required for DELETE requests.") unless session_id_from_header session = Server.session_store.load_session(session_id_from_header) if session.nil? return render_not_found("Session not found.") elsif session.status == "closed" return head :no_content end # Authenticate the request via gateway authenticate_gateway! return if performed? begin session.close! Rails.logger.info "Unified DELETE: Terminated session: #{session.id}" if ActionMCP.configuration.verbose_logging head :no_content rescue StandardError => e Rails.logger.error "Unified DELETE: Error terminating session #{session.id}: #{e.class} - #{e.message}" render_internal_server_error("Failed to terminate session.") end end |
#mcp_session ⇒ ActionMCP::Session
Provides the ActionMCP::Session for the current request. Handles finding existing sessions via header/param or initializing a new one. Specific controllers/handlers might need to enforce session ID presence based on context.
18 19 20 |
# File 'app/controllers/action_mcp/application_controller.rb', line 18 def mcp_session @mcp_session ||= find_or_initialize_session end |
#session_key ⇒ String
Provides a unique key for caching or pub/sub based on the session ID. Ensures mcp_session is called first to establish the session ID.
25 26 27 |
# File 'app/controllers/action_mcp/application_controller.rb', line 25 def session_key @session_key ||= "action_mcp-sessions-#{mcp_session.id}" end |
#show ⇒ Object
Handles GET requests - returns 405 Method Not Allowed as per MCP spec. SSE streaming is not supported. Clients should use Tasks for async operations. <rails-lens:routes:begin> ROUTE: /, name: mcp_get, via: GET <rails-lens:routes:end>
34 35 36 37 38 |
# File 'app/controllers/action_mcp/application_controller.rb', line 34 def show # MCP Streamable HTTP spec allows servers to return 405 if they don't support SSE. # ActionMCP uses Tasks for async operations instead of SSE streaming. head :method_not_allowed end |