Module: McpToolkit
- Defined in:
- lib/mcp_toolkit.rb,
lib/mcp_toolkit/version.rb,
lib/mcp_toolkit/engine_controllers.rb,
sig/mcp_toolkit.rbs
Overview
Lazy parent_controller builder (Constraint B).
The engine's controllers (McpToolkit::ServerController,
McpToolkit::TokensController) and the authority base
(McpToolkit::Authority::ServerController) all subclass
config.parent_controller. If that superclass were resolved in a class body of
an autoloaded/eager-loaded file, it could be read BEFORE the host's
initializer/to_prepare had set it — defaulting to ActionController::Base and, in
turn, breaking CSRF handling on the introspection endpoint.
Instead, none of these controllers is a Zeitwerk-managed file. They are built here from the CURRENT config, and the build is triggered LAZILY:
* `const_missing` (below, and on McpToolkit::Authority) builds them the first
time they are referenced — which, in a host, is at eager-load or first-
request time, i.e. AFTER the app's initializers/to_prepare have run;
* the engine's `config.to_prepare` RESETS them on every code reload so a
changed `parent_controller` (or a reloaded app parent class) takes effect on
the next reference.
The whole config/initializers/mcp_toolkit.rb of a host can therefore live in
to_prepare: the parent is only ever read at build time.
This file reopens McpToolkit to add module methods, so it is Zeitwerk-IGNORED
(like engine.rb) and required explicitly from the gem entry point.
Defined Under Namespace
Modules: Authority, Filtering, Protocol, Server, TokenKinds Classes: Configuration, Dispatcher, Engine, Error, FieldSelection, GetExecutor, ListExecutor, RateLimiter, Registry, Resource, ResourceSchema, Serialization, Session, SqlSanitizer, UnknownResourceMessage
Constant Summary collapse
- VERSION =
"0.4.0"- ENGINE_CONTROLLER_NAMES =
The controllers built directly under McpToolkit (the engine's routes point at these). McpToolkit::Authority::ServerController is built alongside them but is fetched through McpToolkit::Authority's own const_missing.
%i[ServerController TokensController].freeze
Class Method Summary collapse
-
.build_authority_server_controller(parent) ⇒ Object
The AUTHORITY base controller a host subclasses (the recommended path for a host whose rate-limit/usage/account hooks touch app models).
-
.build_engine_controllers! ⇒ Object
(Re)builds the engine controllers + the authority base from the current config.
-
.build_server_controller(parent) ⇒ Object
The SATELLITE transport controller the engine mounts (unchanged behavior; the SDK-backed path).
-
.build_tokens_controller(parent) ⇒ Object
The AUTHORITY introspection endpoint the engine mounts at
POST /mcp/tokens/introspect. -
.config ⇒ Object
The active Configuration (created on first access).
-
.configure {|config| ... } ⇒ Object
Yields the active configuration for mutation, returning it.
-
.const_missing(name) ⇒ Object
Backstop: build the engine controllers the first time one is referenced before any
to_prepare/eager-load pass has built them (e.g. a bespoke route that names McpToolkit::ServerController directly). -
.define_controller(mod, name, klass) ⇒ Object
Removes an existing same-named constant (avoiding a redefinition warning on a rebuild) before setting the freshly-built class.
-
.registry ⇒ Object
The active config's resource registry.
-
.reset_config! ⇒ Object
Replaces the active configuration with a fresh default.
-
.reset_engine_controllers! ⇒ Object
Undefines the built controllers so the next reference rebuilds them from the then-current config.
Class Method Details
.build_authority_server_controller(parent) ⇒ Object
The AUTHORITY base controller a host subclasses (the recommended path for a host whose rate-limit/usage/account hooks touch app models).
93 94 95 |
# File 'lib/mcp_toolkit/engine_controllers.rb', line 93 def self.(parent) Class.new(parent) { include McpToolkit::Authority::ControllerMethods } end |
.build_engine_controllers! ⇒ Object
(Re)builds the engine controllers + the authority base from the current
config. Idempotent: an existing constant is replaced so a rebuild reflects a
changed parent_controller. Reads config.parent_controller at call time.
36 37 38 39 40 41 42 |
# File 'lib/mcp_toolkit/engine_controllers.rb', line 36 def self.build_engine_controllers! parent = config.parent_controller.constantize define_controller(self, :ServerController, build_server_controller(parent)) define_controller(self, :TokensController, build_tokens_controller(parent)) define_controller(Authority, :ServerController, (parent)) ServerController end |
.build_server_controller(parent) ⇒ Object
The SATELLITE transport controller the engine mounts (unchanged behavior; the SDK-backed path). Built lazily only so its parent is read after config.
54 55 56 |
# File 'lib/mcp_toolkit/engine_controllers.rb', line 54 def self.build_server_controller(parent) Class.new(parent) { include McpToolkit::Transport::ControllerMethods } end |
.build_tokens_controller(parent) ⇒ Object
The AUTHORITY introspection endpoint the engine mounts at
POST /mcp/tokens/introspect. Behavior is preserved exactly: it authenticates
the bearer against config.token_authenticator (via Auth::Authority) and
renders the introspection payload; a non-authority app answers { valid: false } rather than erroring.
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 |
# File 'lib/mcp_toolkit/engine_controllers.rb', line 63 def self.build_tokens_controller(parent) Class.new(parent) do def introspect token = McpToolkit::Auth::Authority.authenticate(mcp_extract_token, config: mcp_config) return render(json: McpToolkit::Auth::Authority.invalid_payload, status: :unauthorized) if token.nil? render json: McpToolkit::Auth::Authority.introspection_payload(token) rescue McpToolkit::Errors::ConfigurationError # Not configured as an authority (no token_authenticator): behave as if the # token were invalid instead of surfacing a 500. render json: McpToolkit::Auth::Authority.invalid_payload, status: :unauthorized end private def mcp_config McpToolkit.config end def mcp_extract_token auth_header = request.headers["Authorization"] return auth_header.sub("Bearer ", "") if auth_header&.start_with?("Bearer ") request.headers["X-MCP-Token"].presence || params[:token].presence end end end |
.config ⇒ Object
The active Configuration (created on first access).
107 108 109 |
# File 'lib/mcp_toolkit.rb', line 107 def self.config @config ||= Configuration.new end |
.configure {|config| ... } ⇒ Object
Yields the active configuration for mutation, returning it.
McpToolkit.configure do |c|
c.server_name = "my-app-mcp"
c.central_app_url = ENV.fetch("MCP_CENTRAL_APP_URL")
c.registry. "my_app__read"
end
101 102 103 104 |
# File 'lib/mcp_toolkit.rb', line 101 def self.configure yield(config) if block_given? config end |
.const_missing(name) ⇒ Object
Backstop: build the engine controllers the first time one is referenced before
any to_prepare/eager-load pass has built them (e.g. a bespoke route that
names McpToolkit::ServerController directly). McpToolkit::Authority defines the
sibling backstop for its ServerController.
108 109 110 111 112 113 114 115 |
# File 'lib/mcp_toolkit/engine_controllers.rb', line 108 def self.const_missing(name) if ENGINE_CONTROLLER_NAMES.include?(name) build_engine_controllers! return const_get(name) if const_defined?(name, false) end super end |
.define_controller(mod, name, klass) ⇒ Object
Removes an existing same-named constant (avoiding a redefinition warning on a rebuild) before setting the freshly-built class.
99 100 101 102 |
# File 'lib/mcp_toolkit/engine_controllers.rb', line 99 def self.define_controller(mod, name, klass) mod.send(:remove_const, name) if mod.const_defined?(name, false) mod.const_set(name, klass) end |
.registry ⇒ Object
The active config's resource registry. Register resources against this in a boot initializer.
113 114 115 |
# File 'lib/mcp_toolkit.rb', line 113 def self.registry config.registry end |
.reset_config! ⇒ Object
Replaces the active configuration with a fresh default. Primarily for tests.
118 119 120 |
# File 'lib/mcp_toolkit.rb', line 118 def self.reset_config! @config = Configuration.new end |
.reset_engine_controllers! ⇒ Object
Undefines the built controllers so the next reference rebuilds them from the
then-current config. Called from the engine's to_prepare on every reload.
46 47 48 49 50 |
# File 'lib/mcp_toolkit/engine_controllers.rb', line 46 def self.reset_engine_controllers! [[self, :ServerController], [self, :TokensController], [Authority, :ServerController]].each do |mod, name| mod.send(:remove_const, name) if mod.const_defined?(name, false) end end |