45 MCP tools that give AI agents live access to your Rails schema, models, routes & conventions.
Every option, every default, every validation rule.
# config/initializers/rails_ai_context.rb
if defined?(RailsAiContext) && RailsAiContext.respond_to?(:configure)
RailsAiContext.configure do |config|
config.ai_tools = %i[claude cursor copilot opencode codex]
config.tool_mode = :mcp
config.preset = :full
end
end
# .rails-ai-context.yml
ai_tools:
- claude
- cursor
tool_mode: mcp
preset: full
[!IMPORTANT]
configureblock > YAML > Defaults, merged key by key. The file is read once, at boot, beforeconfig/initializers, so an initializer may assign a key or edit it in place (config.skip_tools << "rails_query") and both survive. A block that runs before the file - inconfig/application.rbor an environment file - must assign a key to keep it (config.skip_tools = ["rails_query"]), because an in-place edit there is replaced when the file loads. A key no block assigns keeps the YAML value. Corrupted YAML degrades gracefully with a warning, and a key the gem does not know warns on stderr and is ignored while the rest of the file applies.
| Option | Type | Default | Description |
|---|---|---|---|
ai_tools |
Array of symbols | nil (all five) |
Which AI tools to generate context for. Options: :claude, :cursor, :copilot, :opencode, :codex. Unset, it reads the selection the installer recorded, and all five when there is none |
tool_mode |
Symbol | :mcp |
:mcp (MCP server primary, CLI fallback) or :cli (CLI only, no MCP server) |
context_files |
Boolean | true |
Set false for MCP-only: the server and the CLI still answer, and no context file is written or touched |
Some apps keep their own CLAUDE.md, AGENTS.md and rules files and want the
server and nothing else:
rails generate rails_ai_context:install --mcp-only # or: rails-ai-context init --mcp-only
That writes the MCP config for the tools you pick and records:
RailsAiContext.configure do |config|
config.tool_mode = :mcp
config.context_files = false
end
rails ai:context then writes nothing and exits 0, rails ai:watch writes
nothing, and rails ai:doctor raises no context-file warning. A command that
names a file still writes it: rails ai:context:claude and
rails-ai-context context --format claude.
config.ai_tools = [] means no AI tool at all: no context files and no MCP
config file are written, and rails ai:context does not ask which tools you
use. The installer has no such choice, so set it by hand. Before v5.27.0 an
empty list wrote every tool’s files.
| Option | Type | Default | Description |
|---|---|---|---|
preset |
Symbol | :full |
:full (40 introspectors) or :standard (17 introspectors) |
context_mode |
Symbol | :compact |
:compact (context files capped at ~150 lines) or :full (no line cap) |
introspectors |
Array of symbols | (from preset) | Override the introspector list directly |
generate_root_files |
Boolean | true |
Set false to generate split rules only: no CLAUDE.md, AGENTS.md, .cursorrules or copilot-instructions.md (.ai-context.json is still written) |
anti_hallucination_rules |
Boolean | true |
Embed 6-rule verification protocol in generated context files |
claude_max_lines |
Integer | 150 |
Max non-blank lines in a compact context file’s gem-managed block, the <!-- BEGIN/END rails-ai-context --> markers included. Over budget, data lines are cut and the Commands, Warnings, Rules and MCP-tools sections kept whole. Must be positive |
| Option | Type | Default | Validation | Description |
|---|---|---|---|---|
server_name |
String | "rails-ai-context" |
- | MCP server name |
cache_ttl |
Integer | 60 |
Must be positive | Cache time-to-live in seconds |
max_tool_response_chars |
Integer | 200_000 |
Must be positive | Safety cap for tool responses and MCP resource payloads. An over-cap resource keeps its JSON shape: whole elements are dropped and reported under a _truncated key |
live_reload |
Symbol/Boolean | :auto |
- | :auto (uses listen gem if available), true, or false |
live_reload_debounce |
Float | 1.5 |
- | Seconds to wait before processing file changes |
auto_mount |
Boolean | false |
- | Auto-mount Rack middleware for HTTP transport |
http_path |
String | "/mcp" |
- | HTTP endpoint path |
http_bind |
String | "127.0.0.1" |
- | HTTP bind address |
http_port |
Integer | 6029 |
1 to 65535 | HTTP listen port |
| Option | Type | Default | Description |
|---|---|---|---|
hydration_enabled |
Boolean | true |
Auto-inject schema hints into controller/view tool responses |
hydration_max_hints |
Integer | 5 |
Maximum schema hints per tool response |
| Option | Type | Default | Description |
|---|---|---|---|
excluded_models |
Array | 8 framework models | Models to skip during introspection |
excluded_controllers |
Array | 2 framework controllers | Controllers to skip |
excluded_route_prefixes |
Array | 6 framework prefixes | Route prefixes to skip |
excluded_filters |
Array | 5 framework filters | Controller filters to skip |
excluded_middleware |
Array | 25 framework middleware | Middleware to skip in listing |
excluded_paths |
Array | ["node_modules", "tmp", "log", "vendor", ".git", "doc", "docs"] |
Paths excluded from search |
excluded_association_names |
Array | 7 framework associations | Association names to hide from model output |
excluded_concerns |
Array of Regex or String | Framework concerns | Concerns to hide everywhere they are listed. Each default names a whole namespace (ActiveRecord and ActiveRecord::...), so an app module such as ActiveRecordLikeInterface is not hidden |
A hidden concern’s associations, scopes, callbacks and macros are hidden with
it: both tiers merge what a concern declared into the model, and the walk
never reads a concern the key hides. Model output says how many went, as
“1 concern hidden by excluded_concerns”, without naming them.
[!NOTE] A YAML
excluded_concernslist replaces the framework defaults, soActionText,ActiveStorage,Devise::Models,Turbo::andDEBUGGER__::concerns come back into model and controller output. Use the initializer’sconfig.excluded_concerns += [...]to add to them instead. Each string is compiled to an unanchored pattern, soPostalso hidesPostable; write^Post$when you mean the one concern.
excluded_filters hides a name from a controller’s filter list in both
tiers. A filter the controller explicitly skips is still shown, as a
struck-through ~~name~~ _(skipped)_ line, because a skip is a fact about
the class rather than a filter that runs. A skip carrying if: or unless:
takes the filter out on some requests only, so the filter keeps its place in
the chain and the line names the condition instead:
- `before` **require_functional!** (skipped unless: :limited_federation_mode?)
A condition written as a lambda prints the line the file holds, collapsed to
one line, the same way a filter’s own if: does. A skip carrying only: or
except: takes the filter out on those actions only, so a whole-controller
answer keeps the filter and names them:
- `before` **authenticate!** (skipped on: index)
Ask about one action and the answer is absolute again: on that action the filter either runs or is struck through.
| Option | Type | Default | Description |
|---|---|---|---|
max_file_size |
Integer | 5_000_000 (5 MB) |
General file read limit |
max_test_file_size |
Integer | 1_000_000 (1 MB) |
Test file read limit |
max_schema_file_size |
Integer | 10_000_000 (10 MB) |
Schema file read limit |
max_view_total_size |
Integer | 10_000_000 (10 MB) |
Doctor threshold: app/views above this warns. Not a read cap |
max_view_file_size |
Integer | 1_000_000 (1 MB) |
Accepted and stored; no check reads it. Not a read cap |
| Option | Type | Default | Description |
|---|---|---|---|
max_search_results |
Integer | 200 |
Maximum lines a search may emit, matches and context together |
max_validate_files |
Integer | 50 |
Maximum files for validation |
search_extensions |
Array | nil |
Narrows the Ruby fallback to these extensions. Unset, the fallback searches every non-hidden, non-binary file, as ripgrep does, so both backends return the same lines |
concern_paths |
Array | nil (discovers app/concerns and app/*/concerns) |
Paths to scan for concerns. Setting it replaces discovery, so it can narrow as well as widen |
frontend_paths |
Array | nil (auto-detect) |
Frontend directories, e.g. ["app/frontend", "../web-client"]. One outside the app root is read for its package.json, lockfiles and bundler config, plus its tsconfig.json and the configs it extends inside that directory, and frontend_stack names it |
extra_app_paths |
Array | [] |
Extra directories under the app root to treat as application code |
Directories that carry their own Rails tree - plugins/*, modules/*,
gems/plugins/*, engines/*, anything up to three levels down holding an
app/ plus a gemspec, a plugin.rb or a lib/**/engine.rb - are found from
the layout and need no configuration. extra_app_paths is for a tree that
fits neither that shape nor the conventional one.
| Option | Type | Default | Description |
|---|---|---|---|
instrumentation_include_arguments |
Boolean | false |
Include tool arguments in instrumentation events |
[!WARNING]
instrumentation_include_argumentsforwards raw tool arguments to your subscribers, which forrails_querymeans the SQL text and for other tools can mean environment variable names. Leave it off unless your subscriber is as trusted as your logs.
| Option | Type | Default | Validation | Description |
|---|---|---|---|---|
query_timeout |
Integer | 5 |
- | SQL query timeout in seconds |
query_row_limit |
Integer | 100 |
1 to 1000 | Maximum rows returned |
query_redacted_columns |
Array | 14 patterns | - | Column names that cause a query to be rejected, on top of a fixed built-in list (see SECURITY.md) |
query_allowed_columns |
Array | [] |
- | Column names exempted from the built-in sensitive list and from query_redacted_columns |
allow_query_in_production |
Boolean | false |
- | Allow rails_query tool in production |
| Option | Type | Default | Description |
|---|---|---|---|
log_lines |
Integer | 50 |
Default log lines to return |
| Option | Type | Default | Description |
|---|---|---|---|
sensitive_patterns |
Array | 34 patterns | File patterns blocked from search/read (.env*, *.env, .envrc, *.key, *.pem, config/credentials.yml.enc, config/application.yml, config/settings.local.yml, .ssh/*, etc.) |
A placeholder such as .env.example stays readable unless a path pattern or
its exact name blocks it; see SECURITY.md.
| Option | Type | Default | Description |
|---|---|---|---|
custom_tools |
Array | [] |
Additional MCP::Tool classes to register (initializer only - a class reference cannot be written in YAML) |
skip_tools |
Array | [] |
Built-in tool names to exclude (e.g., %w[rails_security_scan]) |
| Option | Type | Default | Description |
|---|---|---|---|
output_dir |
String | Rails.root | Directory for generated context files |
:full (default) - 40 introspectorsAll available introspectors. Maximum context.
:standard - 17 introspectorsLightweight subset for faster generation:
schema, models, routes, jobs, gems, conventions, controllers, tests, migrations, stimulus, view_templates, config, components, turbo, auth, performance, i18n
| AI Tool | Root File | Split Rules | MCP Config |
|---|---|---|---|
| Claude Code | CLAUDE.md |
.claude/rules/*.md |
.mcp.json |
| Cursor | .cursorrules (legacy fallback) |
.cursor/rules/*.mdc |
.cursor/mcp.json |
| GitHub Copilot | .github/copilot-instructions.md |
.github/instructions/*.instructions.md |
.vscode/mcp.json |
| OpenCode | AGENTS.md |
app/models/AGENTS.md, app/controllers/AGENTS.md |
opencode.json |
| Codex CLI | (shares AGENTS.md) |
(shares OpenCode rules) | .codex/config.toml |
RailsAiContext.configure do |config|
config.ai_tools = %i[claude]
end
RailsAiContext.configure do |config|
# AI tools
config.ai_tools = %i[claude cursor copilot opencode codex]
config.tool_mode = :mcp
# Introspection
config.preset = :full
config.context_mode = :compact
# MCP Server
config.cache_ttl = 120
config.max_tool_response_chars = 300_000
config.live_reload = true
config.http_port = 6029
# Hydration
config.hydration_enabled = true
config.hydration_max_hints = 5
# Query safety
config.query_timeout = 10
config.query_row_limit = 200
config.allow_query_in_production = false
# Extensibility
config.custom_tools = [MyCustomTool]
config.skip_tools = %w[rails_security_scan]
# Filtering
config.excluded_models = %w[ApplicationRecord SolidQueue::Job]
end