Class: RailsAiContext::McpController

Inherits:
ActionController::API
  • Object
show all
Includes:
ActionController::Live
Defined in:
app/controllers/rails_ai_context/mcp_controller.rb

Overview

Rails controller for serving MCP over Streamable HTTP. Alternative to the Rack middleware - integrates with Rails routing, authentication, and middleware stack.

Mount in routes: mount RailsAiContext::Engine, at: "/mcp"

Defined Under Namespace

Classes: FlushableStream

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.mcp_transportObject

Class-level memoization - transport persists across requests. Thread-safe: MCP::Server and transport are stateless for reads.



135
136
137
# File 'app/controllers/rails_ai_context/mcp_controller.rb', line 135

def mcp_transport
  @transport_mutex.synchronize { @mcp_transport ||= McpEdge.build_transport }
end

.reset_transport!Object



139
140
141
# File 'app/controllers/rails_ai_context/mcp_controller.rb', line 139

def reset_transport!
  @transport_mutex.synchronize { @mcp_transport = nil }
end

Instance Method Details

#handleObject



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'app/controllers/rails_ai_context/mcp_controller.rb', line 29

def handle
  Tools::BaseTool.with_session_for(request.env) do
    serve_transport
  end
rescue => e
  # Once the response is committed the status and headers are already on
  # the wire, so a JSON-RPC frame written here cannot reach the client.
  # Worse, assigning a body swaps the stream out from under the thread
  # still draining the old one, turning a truncated SSE stream into a
  # garbled one. The streaming branch's ensure always closes the stream,
  # and closing commits, so every streaming failure lands here committed.
  # Hand those to Live, which logs them with a backtrace and tears the
  # connection down.
  raise if response.committed?

  # A transport failure must still answer in JSON-RPC shape. Without this
  # the exception escapes into a generic Rails 500 (HTML), breaking the
  # client's JSON-RPC loop.
  RailsAiContext.log_warn "[rails-ai-context] MCP request failed: #{e.class}: #{e.message}"
  self.status = 500
  response.headers["Content-Type"] = "application/json"
  self.response_body = McpEdge.internal_error_frame(e)
end