Class: TalkToYourApp::Configuration
- Inherits:
-
Object
- Object
- TalkToYourApp::Configuration
- Defined in:
- lib/talk_to_your_app/configuration.rb
Overview
The single mutable configuration object. Held as a memoized singleton on
the TalkToYourApp module, so calling TalkToYourApp.configure more than
once merges into the same instance rather than replacing it.
Constant Summary collapse
- FALSEY_STRINGS =
Stringy env values are common here (
config.enabled = ENV["MCP_ENABLED"]), and every non-empty string is truthy in Ruby — so "false"/"0"/"" would otherwise serve. Coerce the common falsey forms to a real boolean; the disabled side is the safe one to bias toward. %w[false 0 no off].freeze
Instance Attribute Summary collapse
-
#allowed_hosts ⇒ Object
Extra
Hostheader values accepted by the transport's DNS-rebinding protection, beyond the always-allowed loopback defaults (127.0.0.1, ::1, localhost). -
#allowed_origins ⇒ Object
Origins permitted for browser-originated requests (DNS-rebinding protection).
-
#api_keys ⇒ Object
Named API keys, { "principal-name" => "secret-key" }.
-
#connections ⇒ Object
readonly
Declared named connections, keyed by gem-internal symbol name.
-
#enabled ⇒ Object
Global on/off switch for the whole gem.
-
#enabled_plugins ⇒ Object
readonly
Enabled plugins, keyed by name => options hash.
-
#instructions ⇒ Object
MCP server identity, surfaced to clients in the
initializehandshake (serverInfo + instructions). -
#log_level ⇒ Object
Global audit log level (default :info).
-
#logger ⇒ Object
Audit logger.
-
#mount_at ⇒ Object
Path the MCP endpoint is mounted at in the host app's router.
-
#server_description ⇒ Object
MCP server identity, surfaced to clients in the
initializehandshake (serverInfo + instructions). -
#server_name ⇒ Object
MCP server identity, surfaced to clients in the
initializehandshake (serverInfo + instructions). -
#server_title ⇒ Object
MCP server identity, surfaced to clients in the
initializehandshake (serverInfo + instructions). -
#server_version ⇒ Object
MCP server identity, surfaced to clients in the
initializehandshake (serverInfo + instructions). -
#stateless ⇒ Object
When true, the Streamable HTTP transport runs stateless: every request is self-contained, with no per-session state held in the transport.
Instance Method Summary collapse
-
#auth_configured? ⇒ Boolean
True when at least one authentication mechanism is configured.
-
#authorize(&block) ⇒ Object
Optional per-principal tool authorization.
- #authorized?(principal, tool_name) ⇒ Boolean
-
#basic_auth(&block) ⇒ Object
Sets or reads the HTTP Basic auth callable.
-
#connection(name, database:, role: :reading, replica: false, statement_timeout: nil) ⇒ Object
Declares a named connection plugins can reference.
-
#initialize ⇒ Configuration
constructor
A new instance of Configuration.
-
#plugin(name, **options) ⇒ Object
Enables a registered plugin, with optional per-plugin options.
Constructor Details
#initialize ⇒ Configuration
Returns a new instance of Configuration.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/talk_to_your_app/configuration.rb', line 74 def initialize @enabled = true @mount_at = "/mcp" @server_name = "talk_to_your_app" @server_version = TalkToYourApp::VERSION @server_title = nil @server_description = nil @instructions = nil @connections = {} @enabled_plugins = {} @logger = nil @api_keys = {} @allowed_origins = [] @allowed_hosts = [] @stateless = false @basic_auth = nil @log_level = :info @authorizer = nil end |
Instance Attribute Details
#allowed_hosts ⇒ Object
Extra Host header values accepted by the transport's DNS-rebinding
protection, beyond the always-allowed loopback defaults (127.0.0.1, ::1,
localhost). A non-loopback deployment (the endpoint served from a real
domain) MUST list its host here, or every request is rejected with
"Forbidden: Invalid Host header". Each entry matches a bare host name (any
port) or a full host:port. Empty by default (loopback only).
43 44 45 |
# File 'lib/talk_to_your_app/configuration.rb', line 43 def allowed_hosts @allowed_hosts end |
#allowed_origins ⇒ Object
Origins permitted for browser-originated requests (DNS-rebinding protection). Empty allowlist permits non-browser clients (no Origin).
35 36 37 |
# File 'lib/talk_to_your_app/configuration.rb', line 35 def allowed_origins @allowed_origins end |
#api_keys ⇒ Object
Named API keys, { "principal-name" => "secret-key" }. The name is logged as the principal. Supports multiple keys for rotation.
31 32 33 |
# File 'lib/talk_to_your_app/configuration.rb', line 31 def api_keys @api_keys end |
#connections ⇒ Object (readonly)
Declared named connections, keyed by gem-internal symbol name.
129 130 131 |
# File 'lib/talk_to_your_app/configuration.rb', line 129 def connections @connections end |
#enabled ⇒ Object
Global on/off switch for the whole gem. When false, the mounted endpoint serves nothing (503) and boot validation is skipped, so an operator can ship the initializer and disable it per-environment without the gem refusing to boot on otherwise-incomplete configuration. Default true.
49 50 51 |
# File 'lib/talk_to_your_app/configuration.rb', line 49 def enabled @enabled end |
#enabled_plugins ⇒ Object (readonly)
Enabled plugins, keyed by name => options hash. Plugins are off by default.
132 133 134 |
# File 'lib/talk_to_your_app/configuration.rb', line 132 def enabled_plugins @enabled_plugins end |
#instructions ⇒ Object
MCP server identity, surfaced to clients in the initialize handshake
(serverInfo + instructions). All optional except name/version, which
default sensibly.
19 20 21 |
# File 'lib/talk_to_your_app/configuration.rb', line 19 def instructions @instructions end |
#log_level ⇒ Object
Global audit log level (default :info). Overridable per plugin via the
plugin DSL's log_level.
27 28 29 |
# File 'lib/talk_to_your_app/configuration.rb', line 27 def log_level @log_level end |
#logger ⇒ Object
Audit logger. Defaults to Rails.logger at boot; swappable to any object implementing the Logger interface.
23 24 25 |
# File 'lib/talk_to_your_app/configuration.rb', line 23 def logger @logger end |
#mount_at ⇒ Object
Path the MCP endpoint is mounted at in the host app's router. Default "/mcp".
14 15 16 |
# File 'lib/talk_to_your_app/configuration.rb', line 14 def mount_at @mount_at end |
#server_description ⇒ Object
MCP server identity, surfaced to clients in the initialize handshake
(serverInfo + instructions). All optional except name/version, which
default sensibly.
19 20 21 |
# File 'lib/talk_to_your_app/configuration.rb', line 19 def server_description @server_description end |
#server_name ⇒ Object
MCP server identity, surfaced to clients in the initialize handshake
(serverInfo + instructions). All optional except name/version, which
default sensibly.
19 20 21 |
# File 'lib/talk_to_your_app/configuration.rb', line 19 def server_name @server_name end |
#server_title ⇒ Object
MCP server identity, surfaced to clients in the initialize handshake
(serverInfo + instructions). All optional except name/version, which
default sensibly.
19 20 21 |
# File 'lib/talk_to_your_app/configuration.rb', line 19 def server_title @server_title end |
#server_version ⇒ Object
MCP server identity, surfaced to clients in the initialize handshake
(serverInfo + instructions). All optional except name/version, which
default sensibly.
19 20 21 |
# File 'lib/talk_to_your_app/configuration.rb', line 19 def server_version @server_version end |
#stateless ⇒ Object
When true, the Streamable HTTP transport runs stateless: every request is
self-contained, with no per-session state held in the transport. Required
when the host app runs more than one Puma/Unicorn worker or replica, where
a follow-up request can land on a process that never saw the initialize
handshake and would otherwise fail with "Session not found". Trades away
SSE streaming and server-initiated notifications, neither of which the
bundled read-only tools use. Default false.
72 73 74 |
# File 'lib/talk_to_your_app/configuration.rb', line 72 def stateless @stateless end |
Instance Method Details
#auth_configured? ⇒ Boolean
True when at least one authentication mechanism is configured.
104 105 106 |
# File 'lib/talk_to_your_app/configuration.rb', line 104 def auth_configured? api_keys.any? || !@basic_auth.nil? end |
#authorize(&block) ⇒ Object
Optional per-principal tool authorization. The block receives (principal, tool_name) and returns truthy to allow the call. With no authorizer configured, every authenticated principal may call every tool.
config. { |principal, tool| principal == "admin" || tool.start_with?("db.") }
113 114 115 116 |
# File 'lib/talk_to_your_app/configuration.rb', line 113 def (&block) @authorizer = block if block @authorizer end |
#authorized?(principal, tool_name) ⇒ Boolean
118 119 120 121 122 123 124 125 126 |
# File 'lib/talk_to_your_app/configuration.rb', line 118 def (principal, tool_name) return true if @authorizer.nil? @authorizer.call(principal, tool_name) rescue StandardError => e # A raising authorizer denies (fail-closed), mirroring basic_auth handling. warn("talk_to_your_app: authorizer raised: #{e.class}: #{e.}") false end |
#basic_auth(&block) ⇒ Object
Sets or reads the HTTP Basic auth callable. The block receives (username, password) and returns truthy to authenticate.
config.basic_auth { |user, pass| User.authenticate(user, pass) }
98 99 100 101 |
# File 'lib/talk_to_your_app/configuration.rb', line 98 def basic_auth(&block) @basic_auth = block if block @basic_auth end |
#connection(name, database:, role: :reading, replica: false, statement_timeout: nil) ⇒ Object
Declares a named connection plugins can reference.
config.connection :read, database: "primary" # role: :reading (default)
config.connection :write, database: "primary", role: :writing
database is a database.yml config key. role defaults to :reading (the
safe default; a :reading connection prevents writes at the Rails layer) —
pass role: :writing explicitly for a writer. replica: true marks the
connection as pointing at a replica (informational; combining it with
role: :writing is rejected as nonsensical).
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/talk_to_your_app/configuration.rb', line 164 def connection(name, database:, role: :reading, replica: false, statement_timeout: nil) role = role.to_sym unless %i[reading writing].include?(role) raise ConfigurationError, "connection #{name.inspect}: role must be :reading or :writing, got #{role.inspect}." end if replica && role == :writing raise ConfigurationError, "connection #{name.inspect}: `replica: true` with `role: :writing` is nonsensical — a replica cannot accept writes." end @connections[name.to_sym] = ConnectionRegistry::ConnectionSpec.new( name: name.to_sym, database: database.to_sym, role: role, replica: replica, statement_timeout: statement_timeout, ) end |
#plugin(name, **options) ⇒ Object
Enables a registered plugin, with optional per-plugin options.
config.plugin :db, connection: :read
config.plugin :sidekiq, connection: false
Re-declaring a plugin merges options, so a later call can refine an earlier one. But re-wiring it to a different connection is rejected — a silent last-wins overwrite could point :db at a writable connection unnoticed.
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/talk_to_your_app/configuration.rb', line 142 def plugin(name, **) key = name.to_sym existing = @enabled_plugins[key] if existing && .key?(:connection) && existing.key?(:connection) && existing[:connection] != [:connection] raise ConfigurationError, "plugin #{key.inspect} is already wired to connection #{existing[:connection].inspect}; " \ "refusing to silently re-wire it to #{[:connection].inspect}." end @enabled_plugins[key] = (existing || {}).merge() end |