Class: McpToolkit::Configuration

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

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 = ->() {  }

  @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"

  initialize_authority_hook_defaults
  @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_headerString

Returns:

  • (String)


203
204
205
# File 'lib/mcp_toolkit/configuration.rb', line 203

def 
  @account_id_header
end

#account_meta_keyString

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.

Returns:

  • (String)


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.find_by(synced_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).

Returns:

  • (#call)


96
97
98
# File 'lib/mcp_toolkit/configuration.rb', line 96

def 
  @account_resolver
end

#auth_roleSymbol

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.

Returns:

  • (Symbol)

    :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_storeActiveSupport::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.

Returns:

  • (ActiveSupport::Cache::Store, #read)


124
125
126
# File 'lib/mcp_toolkit/configuration.rb', line 124

def cache_store
  @cache_store
end

#central_app_urlString?

Returns base URL of the central auth app. The satellite POSTs <central_app_url>/mcp/tokens/introspect.

Returns:

  • (String, nil)

    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_nameObject

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_versionObject

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_prefixString

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.

Returns:

  • (String)


298
299
300
# File 'lib/mcp_toolkit/configuration.rb', line 298

def generic_tool_name_prefix
  @generic_tool_name_prefix
end

#introspect_pathString?

Returns the introspect path appended to central_app_url.

Returns:

  • (String, nil)

    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_ttlInteger

Returns seconds to cache an introspection result (positive AND negative) so a burst of tool calls does not hammer the central app.

Returns:

  • (Integer)

    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_timeoutInteger

Returns HTTP open/read timeout for the introspection call.

Returns:

  • (Integer)

    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.

Returns:

  • (#warn, #error, nil)


307
308
309
# File 'lib/mcp_toolkit/configuration.rb', line 307

def logger
  @logger
end

#parent_controllerString

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".

Returns:

  • (String)


193
194
195
# File 'lib/mcp_toolkit/configuration.rb', line 193

def parent_controller
  @parent_controller
end

#protocol_versionString?

Returns protocol version to pin on the underlying MCP::Server. nil lets the gem negotiate (recommended). Set only to force an older spec.

Returns:

  • (String, nil)

    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_requestsInteger?

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.

Returns:

  • (Integer, nil)


141
142
143
# File 'lib/mcp_toolkit/configuration.rb', line 141

def rate_limit_max_requests
  @rate_limit_max_requests
end

#rate_limit_windowInteger

The fixed rate-limit window, in seconds (default 3600 = 1 hour). Ignored while rate_limit_max_requests is nil.

Returns:

  • (Integer)


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.

Returns:

  • (#call, nil)


247
248
249
# File 'lib/mcp_toolkit/configuration.rb', line 247

def rate_limiter
  @rate_limiter
end

#registryMcpToolkit::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_baseObject

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_instructionsString?

Returns human-readable instructions returned on initialize.

Returns:

  • (String, nil)

    human-readable instructions returned on initialize.



26
27
28
# File 'lib/mcp_toolkit/configuration.rb', line 26

def server_instructions
  @server_instructions
end

#server_nameString

Returns the MCP server name advertised in initialize.

Returns:

  • (String)

    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_versionString

Returns the MCP server version advertised in initialize.

Returns:

  • (String)

    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.

Returns:

  • (#call, nil)


269
270
271
# File 'lib/mcp_toolkit/configuration.rb', line 269

def session_data_builder
  @session_data_builder
end

#session_ttlInteger

Returns session sliding-TTL in seconds.

Returns:

  • (Integer)

    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.

Returns:

  • (#sanitize_sql_like)


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.

Returns:

  • (#call, nil)


160
161
162
# File 'lib/mcp_toolkit/configuration.rb', line 160

def superuser_resolver
  @superuser_resolver
end

#supported_protocol_versionsArray<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.

Returns:

  • (Array<String>)


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.

Returns:

  • (#call, nil)


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).

Returns:

  • (#tool_definitions, #find, nil)


284
285
286
# File 'lib/mcp_toolkit/configuration.rb', line 284

def tool_provider
  @tool_provider
end

#upstream_list_ttlInteger

Returns TTL (s) for an upstream's cached, namespaced tool list in McpToolkit::Gateway::Aggregator.

Returns:

  • (Integer)

    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_timeoutInteger

Returns HTTP open/read timeout (s) for a gateway's calls to an upstream MCP server (McpToolkit::Gateway::Client).

Returns:

  • (Integer)

    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

#upstreamsMcpToolkit::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.

Returns:

  • (#call, nil)


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.

Returns:

  • (#call, nil)


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.

Returns:

  • (Boolean)

    whether this app authenticates tokens locally / answers introspection.



401
402
403
# File 'lib/mcp_toolkit/configuration.rb', line 401

def authority?
  auth_role.to_sym == :authority
end

#initialize_authority_hook_defaultsObject

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 initialize_authority_hook_defaults
  @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_urlObject

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.

Returns:

  • (Boolean)

    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