Rails MCP Server: Context-Efficient Tool Architecture
Learn how Rails MCP Server's new architecture reduces context consumption by 67% through progressive tool discovery, Rails introspection, and Prism static analysis.
Learn how Rails MCP Server’s new architecture reduces context consumption through progressive tool discovery, Rails introspection, and Prism static analysis.
Table of Contents
- The Context Budget Problem
- Progressive Tool Discovery
- Rails Introspection: Beyond Regex Parsing
- Prism Static Analysis
- Detail Levels and Filtering
- Sandboxed Ruby Execution
- Improved Discovery UX
- Interactive Configuration Tool
- How I Use the New Architecture
- Upgrading to the New Version
- Looking Forward
The Context Budget Problem
Last week I came across an Anthropic blog post that fundamentally changed how I think about MCP server design. The insight was simple but profound: every tool you register with an MCP server gets sent to Claude at the start of each session.
With the Rails MCP Server’s previous 12 tools, that meant roughly 2,400 tokens consumed before asking a single question. On large Rails codebases where I’m already sharing multiple files and documentation, this context overhead matters. I’ve hit conversation limits mid-feature more times than I’d like to admit.
The Anthropic post suggested a different approach: progressive disclosure. Instead of loading all tool definitions upfront, let the AI discover them when relevant. This resonated with how I already work—I don’t need Claude to know about route analysis when I’m focused on model validations.
Progressive Tool Discovery
The most significant change in this release is the reduction from 12 registered tools to just 4. The other tools didn’t disappear—they became internal analyzers that Claude discovers on-demand.
The four registered tools:
| Tool | Purpose |
|---|---|
switch_project |
Select which Rails project to analyze |
search_tools |
Discover available analyzers by keyword or category |
execute_tool |
Run any analyzer by name |
execute_ruby |
Sandboxed Ruby execution for complex queries |
The previous tools like analyze_models, get_routes, and get_schema are now internal analyzers. Claude finds them through search_tools and invokes them through execute_tool. This pattern reduces the initial context footprint by roughly 67%.
When I start a session now, Claude only knows about these four capabilities. If I ask about database structure, Claude can search for relevant tools:
search\_tools(query: "database schema")
This returns information about get_schema without having loaded all nine analyzers upfront. Claude then invokes it:
execute\_tool(tool\_name: "get\_schema", params: { table\_name: "users" })
The workflow feels natural—Claude discovers what it needs when it needs it.
Rails Introspection: Beyond Regex Parsing
While refactoring the tool architecture, I realized something uncomfortable: the previous implementation relied heavily on regex parsing. Patterns like has_many\s+:(\w+) worked most of the time, but missed edge cases—associations with options, multi-line declarations, dynamic associations.
This release replaces regex parsing with proper Rails introspection. When you analyze a model now, the server actually asks Rails about it:
Model.reflect_on_all_associations # All associations with options
Model.validators # All validations with conditions
Model.defined_enums # Enum definitions and values
Model.columns_hash # Column details from the database
For routes, instead of parsing the text output of rails routes, the server accesses route objects directly:
Rails.application.routes.routes.map do |route|
{
verb: route.verb,
path: route.path.spec.to_s,
controller: route.defaults[:controller],
action: route.defaults[:action],
constraints: route.constraints
}
end
For controllers, I now use action_methods instead of scanning for def statements, and _process_action_callbacks to find before/after actions accurately.
The difference in accuracy is significant. Associations with through:, class_name:, or foreign_key: options are now captured correctly. Validations show their conditions. Callbacks include their only: and except: filters.
Prism Static Analysis
Rails introspection tells you what exists at runtime, but sometimes you need to understand the code structure itself. For this, I added Prism static analysis.
Prism is Ruby’s new parser that ships with Ruby 3.3+. It provides a clean AST that I can traverse to extract:
- Callbacks and their method references
- Scope definitions
- Included concerns and modules
- Method definitions with line numbers
- Instance variables assigned per action
The analysis_type parameter lets you choose what you need:
execute_tool(
tool_name: "analyze_models",
params: {
model_name: "User",
analysis_type: "full" # "introspection", "static", or "full"
}
)
With analysis_type: "full", Claude gets both the runtime reflection data and the static code analysis. With analysis_type: "static", it gets just the AST-derived information—useful when you want to understand code structure without loading the Rails environment.
Detail Levels and Filtering
Another insight from the Anthropic post: intermediate results consume tokens too. When you ask about routes, do you really need the complete output of all 200 routes with constraints and defaults?
Every analyzer now supports a detail_level parameter:
names— Minimal output, just identifierssummary— Names plus brief descriptionsfull— Complete information (the previous default)
For routes specifically, I added filtering:
execute_tool(
tool_name: "get_routes",
params: {
controller: "api/v1/users",
verb: "GET",
detail_level: "summary"
}
)
Instead of 200 routes, Claude might get 5. The token savings compound across a conversation.
For models and schemas, batch operations reduce round-trips:
execute_tool(
tool_name: "analyze_models",
params: {
model_names: ["User", "Post", "Comment"],
detail_level: "associations"
}
)
One call, three models, associations only. No source code, no validations, no columns—just the relationships Claude needs for the current task.
Sandboxed Ruby Execution
The most powerful addition is execute_ruby. Sometimes the predefined analyzers aren’t enough—you need a custom query that doesn’t fit the standard patterns.
execute_ruby(code: <<~RUBY)
User.joins(:posts)
.group("users.id")
.having("COUNT(posts.id) > 10")
.pluck(:email)
RUBY
This runs in a sandboxed environment with strict security controls:
- No file writes — Cannot create, modify, or delete files
- No system calls —
system(), backticks, andexecare blocked - No network access — Cannot make HTTP requests or open sockets
- No sensitive file reads —
.env, credentials, and.gitignore‘d files are protected - Project-scoped — Cannot access files outside the project directory
- Timeout protected — Maximum 60 seconds execution
The sandbox provides helper methods for common operations:
read_file("app/models/user.rb") # Safe file reading
file_exists?("config/database.yml") # Existence check (false for sensitive files)
list_files("app/models/**/*.rb") # Glob with filtering
project_root # Project path constant
I use this for complex queries that would otherwise require multiple tool calls or for analysis patterns specific to my projects.
Improved Discovery UX
After testing the new architecture with real conversations, I noticed AI agents sometimes struggled with the initial learning curve. Which tool reads files? What helpers are available in execute_ruby? The progressive discovery approach works well once you know the patterns, but getting started needed to be smoother.
Quick Start Guide
Now when you switch projects, you get an immediate orientation:
Switched to project: my_finances at path: /Users/mario/projects/my_finances
Quick Start:
• Get project overview: execute_tool("project_info")
• Read a file: execute_ruby("puts read_file('config/routes.rb')")
• Find files: execute_ruby("puts Dir.glob('app/models/*.rb').join('\n')")
• Analyze models: execute_tool("analyze_models", { model_name: "User" })
• Get routes: execute_tool("get_routes")
• Get schema: execute_tool("get_schema", { table_name: "users" })
• Search available tools: search_tools()
Helpers in execute_ruby: read_file(path), file_exists?(path), list_files(pattern), project_root
Note: Always use `puts` in execute_ruby to see output.
This eliminates the cold-start problem. Claude immediately knows the most common patterns without searching for them.
The puts Problem
One subtle issue kept appearing: execute_ruby would return “Code executed successfully (no output)” when users forgot to wrap expressions in puts. Now the no-output message includes helpful hints:
Code executed successfully (no output).
Hint: Use `puts` to see results, e.g.:
puts read_file('config/routes.rb')
puts User.count
puts Dir.glob('app/models/*.rb')
Small friction points like this matter more than I initially realized. Every confused moment is context wasted on troubleshooting instead of actual work.
AI Agent Guide
For teams using this with custom AI setups, I created a comprehensive guide at docs/AGENT.md covering:
- Tool selection decision trees
- Common pitfalls and how to avoid them
- Error handling and fallback strategies
- Integration patterns with other MCP servers (like Neovim MCP)
The guide is written for AI consumption—structured, explicit, with clear examples for each scenario.
Interactive Configuration Tool
Managing MCP server configuration used to involve editing YAML files and JSON configs manually. With multiple Rails projects, documentation guides, and Claude Desktop integration, this became tedious. So I built rails-mcp-config—an interactive terminal UI for all configuration tasks.
Getting Started
rails-mcp-config
The tool presents a clean menu organized by frequency of use:
┌──────────────────────────────────────────────────┐
│ Rails MCP Server - Configuration │
╰──────────────────────────────────────────────────╯
Projects
List projects
Add project
Add current directory (my-rails-app)
Edit project
Remove project
Validate all projects
────────────────────
Guides
Download guides
Import custom guides
Manage custom guides
────────────────────
Setup
Claude Desktop integration
Open config file
────────────────────
Exit
Project Management
Adding a project is now a guided process. If you run the tool from within a Rails project directory, it offers to add that directory with one confirmation:
Directory: ~/projects/my-rails-app
Project name: [my-rails-app]
✓ Added project 'my-rails-app' -> ~/projects/my-rails-app
The tool validates paths, checks for Gemfiles, and uses home-relative paths (~/projects/...) for portability.
Documentation Guides
Downloading Rails, Turbo, Stimulus, and Kamal guides is now visual:
Select guides to download:
[x] rails (✓ downloaded)
[x] turbo (not downloaded)
[ ] stimulus (not downloaded)
[ ] kamal (not downloaded)
⠸ Fetching files...
✓ rails: 2 downloaded, 45 skipped
✓ turbo: 12 downloaded, 0 skipped
You can also import your own markdown documentation and manage custom guides through the same interface.
Claude Desktop Integration
The most useful feature for new users is automatic Claude Desktop configuration. The tool:
- Detects your existing Claude Desktop config
- Shows current MCP server settings if configured
- Offers to add or update the Rails MCP Server configuration
- Automatically finds the correct Ruby and server executable paths
- Creates timestamped backups before any changes
- Supports both STDIO (recommended) and HTTP modes
✓ Claude Desktop config file found
✓ Rails MCP Server is configured
Current configuration:
command: /Users/mario/.rubies/ruby-3.3.0/bin/ruby
args: /Users/mario/.gem/ruby/3.3.0/bin/rails-mcp-server
What would you like to do?
View full config
Update Rails MCP Server config
Back to menu
For HTTP mode with mcp-remote, the tool detects npx and guides you through the URL configuration.
Enhanced Terminal UI
The tool uses Gum when available for a polished experience with styled prompts, spinners, and tables. Without Gum, it falls back to a functional basic terminal interface—no dependencies required.
# Optional: Install Gum for enhanced UI
brew install gum # macOS
sudo apt install gum # Debian/Ubuntu
The legacy command-line tools (rails-mcp-setup-claude, rails-mcp-server-download-resources) still work for scripting and backward compatibility, but for interactive use, rails-mcp-config is now the recommended approach.
How I Use the New Architecture
My workflow has evolved with these changes. When I start a session:
Switch to project my_finances.
I immediately see the Quick Start guide, so Claude knows the patterns without any additional discovery.
When I need to explore unfamiliar territory:
Search for tools related to controllers and views.
Claude finds analyze_controller_views and explains what it can do. Then:
Analyze the UsersController with full introspection and static analysis.
For focused work where I know what I need:
Get the schema for the transactions table, just the columns and indexes.
The detail_level: "summary" is implicit in “just the columns”—Claude understands the intent and uses minimal output.
For complex questions that span multiple concerns:
execute_ruby(code: <<~RUBY)
# Find models with callbacks that touch the database
Dir.glob("app/models/**/*.rb").select do |file|
content = File.read(file)
content.match?(/after_save|after_create|after_update/) &&
content.match?(/\.save|\.update|\.create/)
end
RUBY
This kind of query would be tedious with the standard tools but trivial with direct Ruby execution.
Upgrading to the New Version
The upgrade is straightforward:
gem install rails-mcp-server
After installation, run the interactive configuration tool to set up or verify your configuration:
rails-mcp-config
This will help you:
- Add your Rails projects
- Download documentation guides
- Configure Claude Desktop integration (with automatic path detection)
The new architecture is backward compatible in terms of functionality—everything the previous version could do, this version can do. The interface changed from direct tool calls to discovery and execution, but Claude adapts naturally.
If you prefer manual configuration or scripting, the legacy commands still work:
rails-mcp-setup-claude # Configure Claude Desktop
rails-mcp-server-download-resources rails # Download guides
For Prism static analysis to work, your Rails projects need Ruby 3.3+ or the Prism gem installed. If Prism isn’t available, the server gracefully falls back to introspection-only analysis.
Looking Forward
While version 1.4.0 focused on context optimization, my current exploration for 1.4.1 is focused on agent portability.
I am working on a --single-project flag to support ephemeral environments like the GitHub Copilot Agent. These agents run in temporary GitHub Actions environments where no projects.yml exists and no global configuration can be persisted. The new flag will allow the server to skip loading projects.yml, treat the current directory as the sole project, and auto-switch to it immediately on startup.
This same feature unlocks support for Claude Code. Because Claude Code uses worktrees where each session is a separate working directory. By leveraging STDIO mode with the --single-project flag, each worktree can spawn its own isolated MCP server instance without port conflicts. This also allows the .mcp.json configuration to be committed directly to version control, making the setup reproducible for the entire team.
The combination of reduced tool registration, Rails introspection, Prism analysis, and these upcoming compatibility features makes the Rails MCP Server not just smarter, but more adaptable to the rapidly evolving ecosystem of AI agents.
Source code is available at https://github.com/maquina-app/rails-mcp-server