Class: McpAuthorization::Configuration
- Inherits:
-
Object
- Object
- McpAuthorization::Configuration
- Defined in:
- lib/mcp_authorization/configuration.rb
Overview
Holds gem-wide settings. A single global instance is created lazily by McpAuthorization.configuration and configured in a Rails initializer:
McpAuthorization.configure do |c|
c.server_name = "my-app"
c.server_version = MyApp::VERSION
c.tool_paths = %w[app/mcp]
c.context_builder = ->(request) { ... }
end
Required settings
context_builder must be set before the first MCP request. Everything
else has sensible defaults.
The context contract
Both context_builder and cli_context_builder must return an object
whose current_user responds to:
current_user.can?(:symbol) # required — gates field/tool visibility
current_user.default_for(:symbol) # optional — populates @default_for tags
The context object itself can implement predicate methods for generic
tag filtering. Any @tag(:value) not in the known constraint list
calls context.tag?(value):
context.requires?(flag) # optional — for @requires, falls back to current_user.can?
context.feature?(flag) # optional — for @feature (account-level feature flags)
context.tier?(name) # optional — for @tier (plan-level gating)
For public/anonymous MCP interfaces, supply a context with minimum-viable
permissions rather than current_user: nil. A nil user causes @requires
fields to be silently excluded (no user = no permissions).
See RbsSchemaCompiler.predicate_excluded? for the full protocol.
Defined Under Namespace
Classes: CategoryCollector
Constant Summary collapse
- SCHEMA_STRATEGIES =
Schema strategies FacadeBuilder knows how to emit. LLM tool
input_schemamust have an object root — Anthropic and OpenAI rejectoneOf/allOf/anyOfat the top level — so both facade strategies keep a flat object root and differ only in where the per-tool schemas go::vendor_extensioncarries them on the facade's_meta;:lazyomits them (enforced at dispatch). A correlated inline shape (tool_name → its argument schema) would require a root combinator and is therefore not offered. %i[vendor_extension lazy].freeze
- UNCATEGORIZED_MODES =
Behaviors for a tool in a faceted domain that declares no
category. %i[fallback error].freeze
- DEFAULT_FACADE_SUFFIX =
Default suffix appended to a category to form its facade tool name (e.g. category
:orders→orders_tools). Overridable per domain viafacet_domain(..., facade_suffix:). "tools"- FACADE_SUFFIX_FORMAT =
A facade suffix must be a bare identifier fragment so the derived facade name (+"#category_#suffix"+) stays a valid MCP tool name.
/\A[a-z0-9]+(?:_[a-z0-9]+)*\z/
Instance Attribute Summary collapse
-
#category_summaries ⇒ Object
readonly
Group summaries keyed by category symbol.
-
#cli_context_builder ⇒ Object
Lambda that builds a server context for CLI/rake usage.
-
#context_builder ⇒ Object
Lambda that builds a server context from a Rack request.
-
#default_domain ⇒ Object
Domain name used when the request URL has no
:domainsegment. -
#faceted_domains ⇒ Object
readonly
Per-domain facet (tool-grouping) configuration, keyed by domain name.
-
#mount_path ⇒ Object
URL prefix where the Engine mounts its routes.
-
#server_name ⇒ Object
Server name reported in the MCP
initializehandshake. -
#server_version ⇒ Object
Server version reported in the MCP
initializehandshake. -
#shared_type_paths ⇒ Object
Directories (relative to
Rails.root) where shared.rbstype files live. -
#strict_schema ⇒ Object
When true, strips JSON Schema keywords that cause 400 errors in Anthropic's strict tool use mode (minLength, maximum, maxItems, etc.) and adds additionalProperties: false to all objects.
-
#tool_paths ⇒ Object
Directories (relative to
Rails.root) that contain tool classes. -
#tools_list_cache ⇒ Object
Cache for the
tools/listresponse. -
#tools_list_cache_redis ⇒ Object
Optional explicit Redis client for the :redis store.
-
#tools_list_cache_redis_url ⇒ Object
Optional explicit Redis URL for the :redis store.
-
#tools_list_cache_ttl ⇒ Object
TTL (seconds) for cached
tools/listentries.
Instance Method Summary collapse
-
#categories(&block) ⇒ Object
Declare one summary line per group.
-
#category_summary(category) ⇒ Object
The group summary for a category, or nil when none was declared.
-
#facet_config(domain) ⇒ Object
Facet config Hash for a domain, or nil when the domain is not faceted.
-
#facet_domain(domain, group_by:, schema_strategy: :vendor_extension, uncategorized: :fallback, facade_suffix: DEFAULT_FACADE_SUFFIX) ⇒ Object
Present a domain as grouped facade tools instead of a flat tool list.
-
#faceted?(domain) ⇒ Boolean
True when the given domain is presented as grouped facades.
-
#initialize ⇒ Configuration
constructor
: () -> void.
Constructor Details
#initialize ⇒ Configuration
: () -> void
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/mcp_authorization/configuration.rb', line 145 def initialize @server_name = "mcp-authorization" @server_version = "1.0.0" @tool_paths = %w[app/mcp] @shared_type_paths = %w[sig/shared] @default_domain = "default" @mount_path = "/mcp" @context_builder = nil @cli_context_builder = nil @strict_schema = false @tools_list_cache = nil @tools_list_cache_ttl = 3600 @tools_list_cache_redis = nil @tools_list_cache_redis_url = nil @faceted_domains = {} @category_summaries = {} end |
Instance Attribute Details
#category_summaries ⇒ Object (readonly)
Group summaries keyed by category symbol. Populated by categories.
: Hash[Symbol, String]
120 121 122 |
# File 'lib/mcp_authorization/configuration.rb', line 120 def category_summaries @category_summaries end |
#cli_context_builder ⇒ Object
Lambda that builds a server context for CLI/rake usage.
Same duck-type contract as context_builder.
: (^(domain: String, role: String) -> untyped)?
74 75 76 |
# File 'lib/mcp_authorization/configuration.rb', line 74 def cli_context_builder @cli_context_builder end |
#context_builder ⇒ Object
Lambda that builds a server context from a Rack request. The returned object must satisfy the context contract above. : (^(untyped) -> untyped)?
69 70 71 |
# File 'lib/mcp_authorization/configuration.rb', line 69 def context_builder @context_builder end |
#default_domain ⇒ Object
Domain name used when the request URL has no :domain segment.
: String
60 61 62 |
# File 'lib/mcp_authorization/configuration.rb', line 60 def default_domain @default_domain end |
#faceted_domains ⇒ Object (readonly)
Per-domain facet (tool-grouping) configuration, keyed by domain name.
Each value is a Hash: { group_by:, schema_strategy:, uncategorized:,
facade_suffix: }.
Populated by facet_domain; read by ToolRegistry / FacadeBuilder.
See docs/designs/tool-grouping-facades.md.
: Hash[String, Hash[Symbol, untyped]]
116 117 118 |
# File 'lib/mcp_authorization/configuration.rb', line 116 def faceted_domains @faceted_domains end |
#mount_path ⇒ Object
URL prefix where the Engine mounts its routes. : String
64 65 66 |
# File 'lib/mcp_authorization/configuration.rb', line 64 def mount_path @mount_path end |
#server_name ⇒ Object
Server name reported in the MCP initialize handshake.
: String
42 43 44 |
# File 'lib/mcp_authorization/configuration.rb', line 42 def server_name @server_name end |
#server_version ⇒ Object
Server version reported in the MCP initialize handshake.
: String
46 47 48 |
# File 'lib/mcp_authorization/configuration.rb', line 46 def server_version @server_version end |
#shared_type_paths ⇒ Object
Directories (relative to Rails.root) where shared .rbs type
files live. Used by RbsSchemaCompiler to resolve # @rbs import.
: Array
56 57 58 |
# File 'lib/mcp_authorization/configuration.rb', line 56 def shared_type_paths @shared_type_paths end |
#strict_schema ⇒ Object
When true, strips JSON Schema keywords that cause 400 errors in Anthropic's strict tool use mode (minLength, maximum, maxItems, etc.) and adds additionalProperties: false to all objects. : bool
80 81 82 |
# File 'lib/mcp_authorization/configuration.rb', line 80 def strict_schema @strict_schema end |
#tool_paths ⇒ Object
Directories (relative to Rails.root) that contain tool classes.
Added to autoload_paths and eager_load_paths by the Engine.
: Array
51 52 53 |
# File 'lib/mcp_authorization/configuration.rb', line 51 def tool_paths @tool_paths end |
#tools_list_cache ⇒ Object
Cache for the tools/list response. Opt-in; defaults to no caching.
Accepts:
nil / false — no caching (default)
:memory — process-local MemoryStore
:redis — shared RedisStore (connection resolved from
+tools_list_cache_redis+ / +tools_list_cache_redis_url+ /
ENV["REDIS_URL"] / a bare Redis.new — the Rails redis config)
<object> — any store responding to +get+/+set+
See McpAuthorization::Cache for the keying strategy. : untyped
92 93 94 |
# File 'lib/mcp_authorization/configuration.rb', line 92 def tools_list_cache @tools_list_cache end |
#tools_list_cache_redis ⇒ Object
Optional explicit Redis client for the :redis store. When nil, the store
resolves a connection from tools_list_cache_redis_url, then
ENV, then a bare Redis.new.
: untyped
104 105 106 |
# File 'lib/mcp_authorization/configuration.rb', line 104 def tools_list_cache_redis @tools_list_cache_redis end |
#tools_list_cache_redis_url ⇒ Object
Optional explicit Redis URL for the :redis store. : String?
108 109 110 |
# File 'lib/mcp_authorization/configuration.rb', line 108 def tools_list_cache_redis_url @tools_list_cache_redis_url end |
#tools_list_cache_ttl ⇒ Object
TTL (seconds) for cached tools/list entries. Bounds staleness from
out-of-band changes (e.g. a feature flag toggled with no deploy); the
deploy digest invalidates on tool/schema changes independently.
: Integer
98 99 100 |
# File 'lib/mcp_authorization/configuration.rb', line 98 def tools_list_cache_ttl @tools_list_cache_ttl end |
Instance Method Details
#categories(&block) ⇒ Object
Declare one summary line per group. Evaluated in a small collector so the block reads declaratively:
config.categories do
summary :orders, "Create, inspect, and update orders."
summary :billing, "Invoices, payments, refunds."
end
: () { () -> void } -> void
232 233 234 235 |
# File 'lib/mcp_authorization/configuration.rb', line 232 def categories(&block) collector = CategoryCollector.new(@category_summaries) collector.instance_eval(&block) end |
#category_summary(category) ⇒ Object
The group summary for a category, or nil when none was declared. : (Symbol) -> String?
239 240 241 |
# File 'lib/mcp_authorization/configuration.rb', line 239 def category_summary(category) @category_summaries[category.to_sym] end |
#facet_config(domain) ⇒ Object
Facet config Hash for a domain, or nil when the domain is not faceted. : (String) -> Hash[Symbol, untyped]?
220 221 222 |
# File 'lib/mcp_authorization/configuration.rb', line 220 def facet_config(domain) @faceted_domains[domain.to_s] end |
#facet_domain(domain, group_by:, schema_strategy: :vendor_extension, uncategorized: :fallback, facade_suffix: DEFAULT_FACADE_SUFFIX) ⇒ Object
Present a domain as grouped facade tools instead of a flat tool list.
config.facet_domain :admin, group_by: :category
config.facet_domain :admin, group_by: :category,
schema_strategy: :lazy,
uncategorized: :error
group_by is currently always :category (the only grouping key the
category DSL provides); it is accepted explicitly so future grouping
keys are an additive change rather than a behavior switch.
schema_strategy selects where the per-tool argument schemas go (the
facade inputSchema is a flat object either way — see SCHEMA_STRATEGIES).
:vendor_extension (default) carries them on the facade's _meta;
:lazy omits them.
uncategorized controls what happens to a tool in this domain with no
category: :fallback (default) collects them into an uncategorized
group; :error raises at facade-build time.
facade_suffix is the token appended to a category to form its facade
tool name — category :orders → orders_#{suffix}. Defaults to
"tools" (+orders_tools+). Must be a lowercase identifier fragment
(+[a-z0-9_]+) so the derived name stays a valid MCP tool name.
: (Symbol | String, group_by: Symbol, ?schema_strategy: Symbol, ?uncategorized: Symbol, ?facade_suffix: String | Symbol) -> void
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/mcp_authorization/configuration.rb', line 188 def facet_domain(domain, group_by:, schema_strategy: :vendor_extension, uncategorized: :fallback, facade_suffix: DEFAULT_FACADE_SUFFIX) unless SCHEMA_STRATEGIES.include?(schema_strategy) raise ArgumentError, "unknown schema_strategy #{schema_strategy.inspect}; " \ "expected one of #{SCHEMA_STRATEGIES.inspect}" end unless UNCATEGORIZED_MODES.include?(uncategorized) raise ArgumentError, "unknown uncategorized mode #{uncategorized.inspect}; " \ "expected one of #{UNCATEGORIZED_MODES.inspect}" end suffix = facade_suffix.to_s unless FACADE_SUFFIX_FORMAT.match?(suffix) raise ArgumentError, "invalid facade_suffix #{facade_suffix.inspect}; " \ "expected a lowercase identifier fragment matching #{FACADE_SUFFIX_FORMAT.inspect}" end @faceted_domains[domain.to_s] = { group_by: group_by.to_sym, schema_strategy: schema_strategy, uncategorized: uncategorized, facade_suffix: suffix } end |
#faceted?(domain) ⇒ Boolean
True when the given domain is presented as grouped facades. : (String) -> bool
214 215 216 |
# File 'lib/mcp_authorization/configuration.rb', line 214 def faceted?(domain) @faceted_domains.key?(domain.to_s) end |