Class: Pgbus::MCP::RackApp
- Inherits:
-
Object
- Object
- Pgbus::MCP::RackApp
- Defined in:
- lib/pgbus/mcp/rack_app.rb
Overview
A turnkey, gated Rack app that serves the read-only pgbus diagnostic MCP server over HTTP. Mount it inside your existing Rails app — no second process, same DB credentials, same connection pool:
# config/routes.rb
mount Pgbus::MCP.rack_app(token: ENV["PGBUS_MCP_TOKEN"]) => "/pgbus/mcp"
The underlying transport runs in stateless + JSON-response mode, which:
* makes every request a self-contained POST returning a single JSON
object (no in-memory session, no SSE stream, no reaper thread), so it
is safe behind multiple Puma/Falcon workers — any worker can answer
any request;
* is exactly what a read-only diagnostic server needs (it never pushes
server-initiated messages to the client).
Security: requests are rejected with 401 unless they carry the configured token (or pass the supplied auth callable). Run it on an internal network / behind your VPN, never internet-exposed.
Constant Summary collapse
- BEARER_PREFIX =
"Bearer "- UNAUTHORIZED_BODY =
Only the JSON body string is frozen and reused. The outer response triple and its headers hash MUST be built fresh per call (#unauthorized) so downstream Rack middleware can mutate them — Rack::TempfileReaper assigns response and Rack::ETag adds headers. Returning a frozen array/hash raised FrozenError → 500 instead of 401 (issue #304).
{ jsonrpc: "2.0", id: nil, error: { code: -32_001, message: "Unauthorized" } }.to_json.freeze
Instance Method Summary collapse
-
#call(env) ⇒ Object
Mount THIS object, never the bare transport.
-
#initialize(data_source: Pgbus::Web::DataSource.new, allow_payloads: false, token: nil, auth: nil) ⇒ RackApp
constructor
A new instance of RackApp.
Constructor Details
#initialize(data_source: Pgbus::Web::DataSource.new, allow_payloads: false, token: nil, auth: nil) ⇒ RackApp
Returns a new instance of RackApp.
43 44 45 46 47 48 49 50 51 |
# File 'lib/pgbus/mcp/rack_app.rb', line 43 def initialize(data_source: Pgbus::Web::DataSource.new, allow_payloads: false, token: nil, auth: nil) @token = token @auth = auth @server = Server.build(data_source: data_source, allow_payloads: allow_payloads) @transport = ::MCP::Server::Transports::StreamableHTTPTransport.new( @server, stateless: true, enable_json_response: true ) warn_unauthenticated! if @token.nil? && @auth.nil? end |
Instance Method Details
#call(env) ⇒ Object
Mount THIS object, never the bare transport. The auth gate lives here and runs before handle_request for every HTTP method (POST/GET/ DELETE/...). Mounting @transport directly would skip authorization — the gem's transport has no auth of its own.
57 58 59 60 61 62 |
# File 'lib/pgbus/mcp/rack_app.rb', line 57 def call(env) request = Rack::Request.new(env) return unless (request) @transport.handle_request(request) end |