Class: Woods::Console::RackMiddleware
- Inherits:
-
Object
- Object
- Woods::Console::RackMiddleware
- Defined in:
- lib/woods/console/rack_middleware.rb
Overview
Rack middleware that serves the embedded console MCP server over HTTP.
Lazy-builds the MCP server on first request so Rails has fully booted and all models are loaded. Uses ActiveRecord connection pool for thread safety under Puma.
Basic setup (Tier 1 tools only)
Add to config/application.rb or an initializer:
config.middleware.use Woods::Console::RackMiddleware, path: '/mcp/console'
This mounts 31 console tools at /mcp/console. By default, console_sql and console_query are blocked in embedded mode and return an “unsupported” error pointing users to enable the flag.
Enabling the feature
The Console MCP is disabled by default. Enable it in your Woods initializer:
Woods.configure do |config|
config.console_mcp_enabled = true
config.console_blocked_tables = %w[authorizations credentials]
config.console_redacted_columns = %w[api_token password_digest]
end
With the flag off, requests to the mounted path return 410 Gone so operators can see the endpoint exists but is gated. See docs/CONSOLE_MCP_SETUP.md for the full security posture (blocked tables, credential scanner, column/EAV redaction, SafeContext rollback).
Enabling read tools (console_sql + console_query)
Set embedded_read_tools: true to unlock the sql and query tools:
# config/initializers/woods_console.rb
Rails.application.config.middleware.use \
Woods::Console::RackMiddleware,
path: '/mcp/console',
embedded_read_tools: true
Security posture with embedded_read_tools: true:
-
SqlValidator denylist — console_sql rejects INSERT/UPDATE/DELETE/DROP/TRUNCATE/ ALTER/CREATE/REPLACE and similar DML/DDL at the string level before any database interaction. Only SELECT and WITH…SELECT are allowed.
-
SafeContext rollback — every request (including console_query) runs inside a database transaction that is always rolled back on completion. Even if a query somehow mutated state (e.g. a function with side effects), the rollback ensures nothing persists.
-
Per-request connection pooling — each HTTP request draws a connection from ActiveRecord::Base’s pool and returns it after the response. No shared mutable state leaks between requests.
These three layers make embedded_read_tools: true safe for read-only workloads. If your threat model requires stricter isolation, use the bridge mode instead (docs/CONSOLE_MCP_SETUP.md) which runs the executor in a separate process.
Constant Summary collapse
- DISABLED_BODY =
JSON.generate( error: 'woods_console_disabled', message: 'Woods Console MCP is disabled. Set ' \ 'Woods.configuration.console_mcp_enabled = true to enable. ' \ 'See docs/CONSOLE_MCP_SETUP.md for the full security posture.' ).freeze
Instance Method Summary collapse
-
#call(env) ⇒ Array
Rack interface — intercepts requests at the configured path.
-
#initialize(app, path: '/mcp/console', embedded_read_tools: false, unsafe_eval_confirmation: nil, unsafe_eval_audit_log_path: nil) ⇒ RackMiddleware
constructor
A new instance of RackMiddleware.
Constructor Details
#initialize(app, path: '/mcp/console', embedded_read_tools: false, unsafe_eval_confirmation: nil, unsafe_eval_audit_log_path: nil) ⇒ RackMiddleware
Returns a new instance of RackMiddleware.
79 80 81 82 83 84 85 86 87 88 |
# File 'lib/woods/console/rack_middleware.rb', line 79 def initialize(app, path: '/mcp/console', embedded_read_tools: false, unsafe_eval_confirmation: nil, unsafe_eval_audit_log_path: nil) @app = app @path = path @embedded_read_tools = @unsafe_eval_confirmation = unsafe_eval_confirmation @unsafe_eval_audit_log_path = unsafe_eval_audit_log_path @mutex = Mutex.new @transport = nil end |
Instance Method Details
#call(env) ⇒ Array
Rack interface — intercepts requests at the configured path.
Returns 410 Gone when Woods.configuration.console_mcp_enabled is false (the default). This keeps the middleware inert on hosts that have mounted it but not yet opted into the feature. All other requests at non-matching paths pass through to the wrapped app unchanged.
106 107 108 109 110 111 |
# File 'lib/woods/console/rack_middleware.rb', line 106 def call(env) return @app.call(env) unless env['PATH_INFO'].start_with?(@path) return [410, { 'content-type' => 'application/json' }, [DISABLED_BODY]] unless enabled? ensure_transport.handle_request(Rack::Request.new(env)) end |