Class: McpToolkit::Configuration
- Inherits:
-
Object
- Object
- McpToolkit::Configuration
- Defined in:
- lib/mcp_toolkit/configuration.rb
Overview
The single, injectable configuration object for an app's MCP server.
Generic but OPINIONATED: every setting has a sensible, vendor-neutral default,
so a satellite needs to override only a handful of values. The two things an
app almost always sets are
server_name and the auth wiring (central_app_url for a satellite, or
token_authenticator for the authority).
Whether ANY scope is required is decided PER TOOL, not per app: a resource
declares required_permissions_scope "notifications__read" (or the registry
declares default_required_permissions_scope once for all resources). There is
no app-wide permission setting here.
Accessed through McpToolkit.config (or MCPToolkit.config) and mutated in a
McpToolkit.configure { |c| ... } block.
Instance Attribute Summary collapse
- #account_id_header ⇒ String
-
#account_meta_key ⇒ String
Header / meta-key constants.
-
#account_resolver ⇒ #call
Resolves the central account id to the satellite's LOCAL scope root.
-
#auth_role ⇒ Symbol
:satellite (introspect tokens against a central app) or :authority (be the introspection provider + authenticate local tokens).
-
#cache_store ⇒ ActiveSupport::Cache::Store, #read
The cache store backing sessions and introspection results.
-
#central_app_url ⇒ String?
Base URL of the central auth app.
-
#gateway_client_name ⇒ Object
The gateway handshake client name, defaulting to the server identity when the host hasn't split it.
-
#gateway_client_version ⇒ Object
The gateway handshake client version, defaulting to the server version.
-
#generic_tool_name_prefix ⇒ String
A prefix prepended to the four GENERIC, Registry-backed authority tool names (
resources,resource_schema,get,list) served by McpToolkit::Authority::RegistryToolProvider. -
#introspect_path ⇒ String?
The introspect path appended to
central_app_url. -
#introspection_cache_ttl ⇒ Integer
Seconds to cache an introspection result (positive AND negative) so a burst of tool calls does not hammer the central app.
-
#introspection_timeout ⇒ Integer
HTTP open/read timeout for the introspection call.
-
#logger ⇒ #warn, ...
Optional logger for gateway/session diagnostics.
-
#parent_controller ⇒ String
The parent class (as a String, resolved via
constantize) of the gem-provided McpToolkit::ServerController that McpToolkit::Engine mounts. -
#protocol_version ⇒ String?
Protocol version to pin on the underlying MCP::Server.
-
#rate_limit_max_requests ⇒ Integer?
The built-in per-principal request cap enforced by the authority transport (McpToolkit::Authority::ControllerMethods#mcp_rate_limit!), counted against
cache_storevia McpToolkit::RateLimiter. -
#rate_limit_window ⇒ Integer
The fixed rate-limit window, in seconds (default 3600 = 1 hour).
-
#rate_limiter ⇒ #call?
OPTIONAL escape hatch that FULLY REPLACES the built-in limiter: a
->(controller:, principal:)that renders + halts when over the limit (or sets rate-limit headers when under). -
#registry ⇒ McpToolkit::Registry
The resource registry for this configuration.
-
#serializer_base ⇒ Object
The serializer base, lazily defaulting to the gem's bundled DSL base.
-
#server_instructions ⇒ String?
Human-readable
instructionsreturned oninitialize. -
#server_name ⇒ String
The MCP server name advertised in
initialize. -
#server_version ⇒ String
The MCP server version advertised in
initialize. -
#session_data_builder ⇒ #call?
Builds the opaque payload bound to a session on
initialize. -
#session_ttl ⇒ Integer
Session sliding-TTL in seconds.
-
#sql_sanitizer ⇒ #sanitize_sql_like
Escapes LIKE wildcards in
matches/does_not_matchfilter values so they match literally. -
#superuser_resolver ⇒ #call?
Optional resolver deciding whether a principal is a SUPERUSER — a cross-tenant caller that may reach
superusers_only!resources. -
#supported_protocol_versions ⇒ Array<String>
The protocol versions the hand-rolled AUTHORITY dispatcher (McpToolkit::Dispatcher) negotiates, newest first.
-
#token_authenticator ⇒ #call?
Looks up + verifies a plaintext bearer token locally, returning a token object (duck-typed, see below) or nil.
-
#tool_provider ⇒ #tool_definitions, ...
The host's tool catalog — the api-agnostic seam.
-
#upstream_list_ttl ⇒ Integer
TTL (s) for an upstream's cached, namespaced tool list in McpToolkit::Gateway::Aggregator.
-
#upstream_timeout ⇒ Integer
HTTP open/read timeout (s) for a gateway's calls to an upstream MCP server (McpToolkit::Gateway::Client).
-
#upstreams ⇒ McpToolkit::Gateway::UpstreamRegistry
readonly
The registry of upstream MCP servers this gateway aggregates + proxies to.
-
#usage_flusher ⇒ #call?
Persists accumulated usage after the response (an after_action).
-
#usage_recorder ⇒ #call?
Records ONE usage event for a single JSON-RPC call (called per batch element).
Instance Method Summary collapse
-
#authority? ⇒ Boolean
Whether this app authenticates tokens locally / answers introspection.
-
#initialize ⇒ Configuration
constructor
Vendor-neutral defaults; apps override the auth wiring + identity as needed.
-
#initialize_authority_hook_defaults ⇒ Object
The authority transport's injection points all default to nil (a no-op): a pure satellite/gateway never touches them.
-
#introspect_url ⇒ Object
Full introspection URL the satellite POSTs to.
-
#register_upstream(key:, url:, public_tool_list: true) ⇒ Object
Config sugar: register a gateway upstream.
-
#satellite? ⇒ Boolean
Whether this app introspects tokens against a central app.
Constructor Details
#initialize ⇒ Configuration
Vendor-neutral defaults; apps override the auth wiring + identity as needed.
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/mcp_toolkit/configuration.rb', line 310 def initialize @server_name = "mcp-server" @server_version = "1.0.0" @server_instructions = nil @gateway_client_name = nil @gateway_client_version = nil @serializer_base = nil # set lazily in #serializer_base to avoid load-order issues @auth_role = :satellite @central_app_url = nil @introspect_path = "/mcp/tokens/introspect" @introspection_cache_ttl = 45 @introspection_timeout = 10 @account_resolver = ->(synced_account_id) { synced_account_id } @token_authenticator = nil @cache_store = ActiveSupport::Cache::MemoryStore.new @session_ttl = 3600 # 1 hour @sql_sanitizer = McpToolkit::SqlSanitizer.new @protocol_version = nil @supported_protocol_versions = McpToolkit::Protocol::SUPPORTED_VERSIONS @parent_controller = "ActionController::Base" @account_meta_key = "mcp-toolkit/account-id" @account_id_header = "X-MCP-Account-ID" @generic_tool_name_prefix = "" @upstream_timeout = 10 @upstream_list_ttl = 900 # 15 minutes @logger = nil @registry = McpToolkit::Registry.new @upstreams = McpToolkit::Gateway::UpstreamRegistry.new end |
Instance Attribute Details
#account_id_header ⇒ String
203 204 205 |
# File 'lib/mcp_toolkit/configuration.rb', line 203 def account_id_header @account_id_header end |
#account_meta_key ⇒ String
Header / meta-key constants. Vendor-neutral defaults; an app on a specific central authority can rename them to match that authority's convention. These are the selectors a superuser/multi-account token uses to pin the active account.
201 202 203 |
# File 'lib/mcp_toolkit/configuration.rb', line 201 def @account_meta_key end |
#account_resolver ⇒ #call
Resolves the central account id to the satellite's LOCAL scope root.
A satellite stores rows keyed by the central app's account id (synced via
Kafka etc.). This callable receives the resolved central account_id and
MUST return the object that Resource#scope blocks root on (typically the
local Account). Return nil to signal "no local account" (=> Unauthorized).
c.account_resolver = ->(synced_account_id) { Account.find_by(synced_id: synced_account_id) }
Defaults to the identity function: the resolved central account id is used directly as the scope root (suitable for an app whose scope blocks key on the central id, or for the authority itself).
96 97 98 |
# File 'lib/mcp_toolkit/configuration.rb', line 96 def account_resolver @account_resolver end |
#auth_role ⇒ Symbol
Returns :satellite (introspect tokens against a central app) or
:authority (be the introspection provider + authenticate local tokens).
A single app MAY be both — set :authority and still configure a
central_app_url if it also exposes its own tools as a satellite.
67 68 69 |
# File 'lib/mcp_toolkit/configuration.rb', line 67 def auth_role @auth_role end |
#cache_store ⇒ ActiveSupport::Cache::Store, #read
The cache store backing sessions and introspection results. Must satisfy the
ActiveSupport::Cache::Store contract (read/write/delete with
expires_in:). Defaults to an in-process MemoryStore; a real deployment
should set this to Rails.cache (or any shared store) so sessions survive
across Puma workers.
124 125 126 |
# File 'lib/mcp_toolkit/configuration.rb', line 124 def cache_store @cache_store end |
#central_app_url ⇒ String?
Returns base URL of the central auth app.
The satellite POSTs <central_app_url>/mcp/tokens/introspect.
73 74 75 |
# File 'lib/mcp_toolkit/configuration.rb', line 73 def central_app_url @central_app_url end |
#gateway_client_name ⇒ Object
The gateway handshake client name, defaulting to the server identity when the
host hasn't split it. Read (not stored) so a server_name change before the
split is set still flows through.
378 379 380 |
# File 'lib/mcp_toolkit/configuration.rb', line 378 def gateway_client_name @gateway_client_name || server_name end |
#gateway_client_version ⇒ Object
The gateway handshake client version, defaulting to the server version.
383 384 385 |
# File 'lib/mcp_toolkit/configuration.rb', line 383 def gateway_client_version @gateway_client_version || server_version end |
#generic_tool_name_prefix ⇒ String
A prefix prepended to the four GENERIC, Registry-backed authority tool names
(resources, resource_schema, get, list) served by
McpToolkit::Authority::RegistryToolProvider. Lets a host NAMESPACE its generic
tools — e.g. set "foo_" and they advertise (and resolve) as foo_resources,
foo_resource_schema, foo_get, foo_list — so distinct MCP surfaces don't
collide and existing clients keep a stable, host-chosen name. Empty by default,
so the tools keep their bare base names. The prefix value is the host's; the gem
names no app concept.
298 299 300 |
# File 'lib/mcp_toolkit/configuration.rb', line 298 def generic_tool_name_prefix @generic_tool_name_prefix end |
#introspect_path ⇒ String?
Returns the introspect path appended to central_app_url.
75 76 77 |
# File 'lib/mcp_toolkit/configuration.rb', line 75 def introspect_path @introspect_path end |
#introspection_cache_ttl ⇒ Integer
Returns seconds to cache an introspection result (positive AND negative) so a burst of tool calls does not hammer the central app.
78 79 80 |
# File 'lib/mcp_toolkit/configuration.rb', line 78 def introspection_cache_ttl @introspection_cache_ttl end |
#introspection_timeout ⇒ Integer
Returns HTTP open/read timeout for the introspection call.
80 81 82 |
# File 'lib/mcp_toolkit/configuration.rb', line 80 def introspection_timeout @introspection_timeout end |
#logger ⇒ #warn, ...
Optional logger for gateway/session diagnostics. All call sites guard with
logger&.warn / logger&.error, so nil (the default) silences them. A Rails
host typically sets this to Rails.logger.
307 308 309 |
# File 'lib/mcp_toolkit/configuration.rb', line 307 def logger @logger end |
#parent_controller ⇒ String
The parent class (as a String, resolved via constantize) of the
gem-provided McpToolkit::ServerController that McpToolkit::Engine mounts.
Doorkeeper-style indirection so a satellite mounting the engine can keep
ActionController::Base (NOT ::API) — e.g. for a logstasher helper_method
hook — by setting c.parent_controller = "ApplicationController".
193 194 195 |
# File 'lib/mcp_toolkit/configuration.rb', line 193 def parent_controller @parent_controller end |
#protocol_version ⇒ String?
Returns protocol version to pin on the underlying MCP::Server. nil lets the gem negotiate (recommended). Set only to force an older spec.
176 177 178 |
# File 'lib/mcp_toolkit/configuration.rb', line 176 def protocol_version @protocol_version end |
#rate_limit_max_requests ⇒ Integer?
The built-in per-principal request cap enforced by the authority transport
(McpToolkit::Authority::ControllerMethods#mcp_rate_limit!), counted against
cache_store via McpToolkit::RateLimiter. nil (the default) DISABLES rate
limiting entirely, so a pure host is unaffected until it opts in. Set an
Integer to cap each principal to that many requests per rate_limit_window.
The default mcp_rate_limit! reads this through the overridable
mcp_rate_limit_max_requests hook, so a host that keeps the cap in its own
constant/model overrides that hook rather than this value.
141 142 143 |
# File 'lib/mcp_toolkit/configuration.rb', line 141 def rate_limit_max_requests @rate_limit_max_requests end |
#rate_limit_window ⇒ Integer
The fixed rate-limit window, in seconds (default 3600 = 1 hour). Ignored
while rate_limit_max_requests is nil.
147 148 149 |
# File 'lib/mcp_toolkit/configuration.rb', line 147 def rate_limit_window @rate_limit_window end |
#rate_limiter ⇒ #call?
OPTIONAL escape hatch that FULLY REPLACES the built-in limiter: a
->(controller:, principal:) that renders + halts when over the limit (or
sets rate-limit headers when under). When set, mcp_rate_limit! delegates to
it and the built-in (rate_limit_max_requests) is skipped. nil (the default)
means the built-in runs instead. Most hosts want the built-in; reach for this
only when the counting itself must live in app code.
247 248 249 |
# File 'lib/mcp_toolkit/configuration.rb', line 247 def rate_limiter @rate_limiter end |
#registry ⇒ McpToolkit::Registry
The resource registry for this configuration. Each config carries its own so
tests (and, in principle, multiple mounted servers) don't collide. The
process-wide convenience McpToolkit.registry delegates to the active
config's registry.
211 212 213 |
# File 'lib/mcp_toolkit/configuration.rb', line 211 def registry @registry end |
#serializer_base ⇒ Object
The serializer base, lazily defaulting to the gem's bundled DSL base. Lazy so
McpToolkit::Serializer::Base is referenced after it has been required,
regardless of file load order.
390 391 392 |
# File 'lib/mcp_toolkit/configuration.rb', line 390 def serializer_base @serializer_base ||= McpToolkit::Serializer::Base end |
#server_instructions ⇒ String?
Returns human-readable instructions returned on initialize.
26 27 28 |
# File 'lib/mcp_toolkit/configuration.rb', line 26 def server_instructions @server_instructions end |
#server_name ⇒ String
Returns the MCP server name advertised in initialize.
22 23 24 |
# File 'lib/mcp_toolkit/configuration.rb', line 22 def server_name @server_name end |
#server_version ⇒ String
Returns the MCP server version advertised in initialize.
24 25 26 |
# File 'lib/mcp_toolkit/configuration.rb', line 24 def server_version @server_version end |
#session_data_builder ⇒ #call?
Builds the opaque payload bound to a session on initialize. ->(principal:)
returning a Hash (or nil for none). Lets a host bind e.g.
{ token_id: principal.id } so a revoked token can kill an in-flight session,
WITHOUT overriding the controller's mcp_session_data. nil (the default) =>
an empty session payload.
269 270 271 |
# File 'lib/mcp_toolkit/configuration.rb', line 269 def session_data_builder @session_data_builder end |
#session_ttl ⇒ Integer
Returns session sliding-TTL in seconds.
127 128 129 |
# File 'lib/mcp_toolkit/configuration.rb', line 127 def session_ttl @session_ttl end |
#sql_sanitizer ⇒ #sanitize_sql_like
Escapes LIKE wildcards in matches / does_not_match filter values so they
match literally. Must respond to sanitize_sql_like(string). Defaults to the
ActiveRecord-backed McpToolkit::SqlSanitizer; a non-Rails host (or a test) can
inject its own.
170 171 172 |
# File 'lib/mcp_toolkit/configuration.rb', line 170 def sql_sanitizer @sql_sanitizer end |
#superuser_resolver ⇒ #call?
Optional resolver deciding whether a principal is a SUPERUSER — a cross-tenant
caller that may reach superusers_only! resources. ->(principal) -> Boolean.
When set, McpToolkit::Authority::Context#superuser? calls it; when nil (the
default) the context falls back to duck-typing principal.superuser? (false
when the principal doesn't respond to it). Superuser is FULLY OPTIONAL: a host
with no such concept leaves this nil and flags no superusers_only! resource,
so no caller is ever a superuser.
160 161 162 |
# File 'lib/mcp_toolkit/configuration.rb', line 160 def superuser_resolver @superuser_resolver end |
#supported_protocol_versions ⇒ Array<String>
The protocol versions the hand-rolled AUTHORITY dispatcher
(McpToolkit::Dispatcher) negotiates, newest first. initialize echoes the
requested version when it is in this set, else the first (latest). Defaults to
McpToolkit::Protocol::SUPPORTED_VERSIONS; override to pin a host's own set.
184 185 186 |
# File 'lib/mcp_toolkit/configuration.rb', line 184 def supported_protocol_versions @supported_protocol_versions end |
#token_authenticator ⇒ #call?
Looks up + verifies a plaintext bearer token locally, returning a token
object (duck-typed, see below) or nil. This is the authority's
AccessToken.authenticate(plaintext) equivalent. Required for the :authority
role; unused by a pure satellite.
c.token_authenticator = ->(plaintext) { AccessToken.authenticate(plaintext) }
The returned token object must respond to the methods
McpToolkit::Auth::Authority#introspection_payload reads (see that module for
the contract): kind, account_id, account_ids, expires_at, scopes. A
touch_last_used! method, if present, is called.
113 114 115 |
# File 'lib/mcp_toolkit/configuration.rb', line 113 def token_authenticator @token_authenticator end |
#tool_provider ⇒ #tool_definitions, ...
The host's tool catalog — the api-agnostic seam. Duck-typed; the dispatcher calls:
provider.tool_definitions(context) -> [{ name:, description:, inputSchema: }]
provider.find(name) -> a tool object, or nil
where a tool object responds to #required_permissions_scope (String|nil, the
gem's scope gate) and #call(context:, **arguments) (returns Hash|String,
which the gem wraps into { content: [{ type: "text", text: }] }). See
McpToolkit::Tools::AuthorityBase for a base that satisfies this. nil = the host
contributes no own tools (a pure gateway).
284 285 286 |
# File 'lib/mcp_toolkit/configuration.rb', line 284 def tool_provider @tool_provider end |
#upstream_list_ttl ⇒ Integer
Returns TTL (s) for an upstream's cached, namespaced tool list in McpToolkit::Gateway::Aggregator.
220 221 222 |
# File 'lib/mcp_toolkit/configuration.rb', line 220 def upstream_list_ttl @upstream_list_ttl end |
#upstream_timeout ⇒ Integer
Returns HTTP open/read timeout (s) for a gateway's calls to an upstream MCP server (McpToolkit::Gateway::Client).
217 218 219 |
# File 'lib/mcp_toolkit/configuration.rb', line 217 def upstream_timeout @upstream_timeout end |
#upstreams ⇒ McpToolkit::Gateway::UpstreamRegistry (readonly)
The registry of upstream MCP servers this gateway aggregates + proxies to.
Each config carries its own (like registry), so it resets with a fresh
config. Register via the register_upstream sugar below or directly on this
instance. Empty unless the app registers upstreams, so a non-gateway app is
unaffected.
229 230 231 |
# File 'lib/mcp_toolkit/configuration.rb', line 229 def upstreams @upstreams end |
#usage_flusher ⇒ #call?
Persists accumulated usage after the response (an after_action).
->(controller:). MUST never affect the MCP response. nil = no flush.
260 261 262 |
# File 'lib/mcp_toolkit/configuration.rb', line 260 def usage_flusher @usage_flusher end |
#usage_recorder ⇒ #call?
Records ONE usage event for a single JSON-RPC call (called per batch element).
->(request_data:, account:, principal:, controller:). MUST never affect the
MCP response. nil = no metering.
254 255 256 |
# File 'lib/mcp_toolkit/configuration.rb', line 254 def usage_recorder @usage_recorder end |
Instance Method Details
#authority? ⇒ Boolean
Returns whether this app authenticates tokens locally / answers introspection.
401 402 403 |
# File 'lib/mcp_toolkit/configuration.rb', line 401 def auth_role.to_sym == :authority end |
#initialize_authority_hook_defaults ⇒ Object
The authority transport's injection points all default to nil (a no-op): a
pure satellite/gateway never touches them. rate_limit_window is the sole
non-nil default (the window size only matters once a cap opts in).
354 355 356 357 358 359 360 361 362 363 |
# File 'lib/mcp_toolkit/configuration.rb', line 354 def @rate_limiter = nil @usage_recorder = nil @usage_flusher = nil @session_data_builder = nil @tool_provider = nil @rate_limit_max_requests = nil # nil = rate limiting disabled @rate_limit_window = 3600 # 1 hour @superuser_resolver = nil # nil = duck-type principal.superuser? end |
#introspect_url ⇒ Object
Full introspection URL the satellite POSTs to. Raises a clear error if the central URL was never configured.
407 408 409 410 411 |
# File 'lib/mcp_toolkit/configuration.rb', line 407 def introspect_url raise McpToolkit::Errors::ConfigurationError, "central_app_url is not configured" if central_app_url.to_s.empty? "#{central_app_url.chomp("/")}#{introspect_path}" end |
#register_upstream(key:, url:, public_tool_list: true) ⇒ Object
Config sugar: register a gateway upstream. Delegates to upstreams.register,
so a blank url is ignored (an unconfigured upstream is simply absent). Pass
public_tool_list: false for an upstream whose tool list varies by caller
privilege, to opt it out of the shared list cache.
c.register_upstream(key: "notifications", url: ENV["NOTIFICATIONS_SERVER_URL"])
371 372 373 |
# File 'lib/mcp_toolkit/configuration.rb', line 371 def register_upstream(key:, url:, public_tool_list: true) upstreams.register(key:, url:, public_tool_list:) end |
#satellite? ⇒ Boolean
Returns whether this app introspects tokens against a central app.
395 396 397 |
# File 'lib/mcp_toolkit/configuration.rb', line 395 def satellite? auth_role.to_sym == :satellite || central_app_url end |