Module: McpToolkit::Authority::ControllerMethods

Extended by:
ActiveSupport::Concern
Defined in:
lib/mcp_toolkit/authority/controller_methods.rb

Overview

The AUTHORITY-side MCP Streamable-HTTP transport, provided as an includable concern. Unlike the satellite transport (McpToolkit::Transport::ControllerMethods, which forwards a token to a central app for per-tool introspection), the authority AUTHENTICATES the token locally and dispatches through the hand-rolled McpToolkit::Dispatcher, serving its own tools and (as a gateway) proxying upstreams.

Because the POST endpoint is the billing/tenancy boundary of a first-party server, EVERY billing/tenancy step is an overridable hook. A pure host can drive the whole thing from config callables (rate_limiter, usage_recorder, usage_flusher, tool_provider, token_authenticator); a host whose metering touches its own models subclasses McpToolkit::Authority::ServerController and overrides the hook methods directly (the recommended path).

Endpoints POST /mcp - JSON-RPC requests/responses (single or batch) GET /mcp - server-initiated SSE stream (none emitted; 405) DELETE /mcp - terminate the current session GET /mcp/health - unauthenticated health probe

Per-request loop (the metering-critical invariant) Each JSON-RPC call — including every element of a batch — RE-RESOLVES its account from its own _meta / account_id argument, then tracks usage, then dispatches with a fresh Authority::Context. The batch is deliberately NOT delegated to a bulk handler that can't re-resolve per element, so a mixed- account batch still meters one usage event per call against the right account.

Overridable hooks (defaults in parentheses) mcp_config -> the McpToolkit::Configuration (McpToolkit.config) mcp_authenticate! -> set @mcp_principal or render 401 (local token auth via config.token_authenticator, through Auth::Authority) mcp_rate_limit! -> throttle (built-in McpToolkit::RateLimiter when config.rate_limit_max_requests is set; config.rate_limiter escape hatch takes precedence; no-op when neither) mcp_track_usage -> record one usage event (config.usage_recorder&.call) mcp_flush_usage -> persist accumulated usage (config.usage_flusher&.call) mcp_resolve_account -> the account for one call (principal#default_account / principal#authorize_account(id)) mcp_session_data -> opaque payload bound to the session (config.session_data_builder, else {}; a host binds e.g. { token_id: principal.id } so a revoked token kills the session) mcp_dispatch -> run one JSON-RPC call (Dispatcher + Authority::Context) mcp_health_payload -> the GET /mcp/health body

CSRF: the concern disables forgery protection (this is a token-authenticated JSON API). Its host controller should inherit from ActionController::API (or ::Base with null_session) via config.parent_controller.

Constant Summary collapse

SESSION_HEADER =
"Mcp-Session-Id"

Instance Method Summary collapse

Instance Method Details

#createObject



74
75
76
77
78
79
80
81
82
# File 'lib/mcp_toolkit/authority/controller_methods.rb', line 74

def create
  request_body = mcp_parse_body

  if request_body.is_a?(Array)
    mcp_handle_batch(request_body)
  else
    mcp_handle_single(request_body)
  end
end

#destroyObject

DELETE /mcp — terminate the current session.



92
93
94
95
# File 'lib/mcp_toolkit/authority/controller_methods.rb', line 92

def destroy
  McpToolkit::Session.delete(request.headers[SESSION_HEADER], config: mcp_config)
  head :no_content
end

#healthObject

GET /mcp/health — unauthenticated health probe.



98
99
100
# File 'lib/mcp_toolkit/authority/controller_methods.rb', line 98

def health
  render json: mcp_health_payload
end

#streamObject

GET /mcp — opens an SSE stream for server-initiated messages. None are emitted (no sampling, progress, or notifications today), so we reply 405 as the MCP spec explicitly allows.



87
88
89
# File 'lib/mcp_toolkit/authority/controller_methods.rb', line 87

def stream
  head :method_not_allowed
end