rails-mcp-insight 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/LICENSE +21 -0
- data/README.md +308 -0
- data/bin/rails-mcp-insight +100 -0
- data/lib/rails_mcp_insight/analyzers/ast_parser.rb +200 -0
- data/lib/rails_mcp_insight/analyzers/code_searcher.rb +155 -0
- data/lib/rails_mcp_insight/analyzers/controller_analyzer.rb +124 -0
- data/lib/rails_mcp_insight/analyzers/gem_analyzer.rb +113 -0
- data/lib/rails_mcp_insight/analyzers/job_analyzer.rb +126 -0
- data/lib/rails_mcp_insight/analyzers/migration_analyzer.rb +98 -0
- data/lib/rails_mcp_insight/analyzers/model_analyzer.rb +239 -0
- data/lib/rails_mcp_insight/analyzers/route_analyzer.rb +171 -0
- data/lib/rails_mcp_insight/analyzers/security_analyzer.rb +127 -0
- data/lib/rails_mcp_insight/analyzers/test_analyzer.rb +91 -0
- data/lib/rails_mcp_insight/configuration.rb +74 -0
- data/lib/rails_mcp_insight/formatters/mermaid_formatter.rb +68 -0
- data/lib/rails_mcp_insight/project_detector.rb +84 -0
- data/lib/rails_mcp_insight/server.rb +51 -0
- data/lib/rails_mcp_insight/tools/analyze_controller.rb +34 -0
- data/lib/rails_mcp_insight/tools/analyze_job.rb +33 -0
- data/lib/rails_mcp_insight/tools/analyze_migration_history.rb +30 -0
- data/lib/rails_mcp_insight/tools/analyze_model.rb +34 -0
- data/lib/rails_mcp_insight/tools/audit_gems.rb +24 -0
- data/lib/rails_mcp_insight/tools/check_security.rb +34 -0
- data/lib/rails_mcp_insight/tools/explain_association_chain.rb +101 -0
- data/lib/rails_mcp_insight/tools/find_definition.rb +34 -0
- data/lib/rails_mcp_insight/tools/find_tests.rb +32 -0
- data/lib/rails_mcp_insight/tools/generate_erd.rb +38 -0
- data/lib/rails_mcp_insight/tools/list_models.rb +25 -0
- data/lib/rails_mcp_insight/tools/search_code.rb +31 -0
- data/lib/rails_mcp_insight/tools/search_routes.rb +34 -0
- data/lib/rails_mcp_insight/tools/stats_overview.rb +76 -0
- data/lib/rails_mcp_insight/tools/trace_request.rb +162 -0
- data/lib/rails_mcp_insight/version.rb +5 -0
- data/lib/rails_mcp_insight.rb +55 -0
- metadata +155 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5bef39c81f49c39312a2a53416779eaf9051ccd35aae59154fd48eda61fa9ac6
|
|
4
|
+
data.tar.gz: 3c47c8d03e72b32e035b3ec5c834ef3003baac65241988ad8dca4fde1a688798
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fc2fab417e9a718ab06a0b755b8e39ddf310944e11571c4ad3461877689a54f2fc99fb87d138499005180ee0ff2810aefcbbd2e867069d0e6fefe99620145b8e
|
|
7
|
+
data.tar.gz: 735a7950a20ab12dff1daf86e3c00f5c76b3eda133fd129c883bf66ce61697f888addf64952c1dcb3d5604396f51887da6b1a3127a6db135afb1aca7d9bcacdf
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-07-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial release with 15 MCP tools for Rails introspection
|
|
9
|
+
- `stats_overview` — Project health dashboard
|
|
10
|
+
- `search_routes` — Smart route search with filtering
|
|
11
|
+
- `analyze_model` — Deep model analysis (columns, associations, validations, callbacks)
|
|
12
|
+
- `analyze_controller` — Controller analysis (actions, filters, strong params)
|
|
13
|
+
- `search_code` — Regex code search with context
|
|
14
|
+
- `find_definition` — Symbol definition lookup
|
|
15
|
+
- `list_models` — List all models with metadata
|
|
16
|
+
- `trace_request` — Full HTTP request lifecycle tracing
|
|
17
|
+
- `analyze_migration_history` — Schema evolution timeline
|
|
18
|
+
- `audit_gems` — Dependency analysis
|
|
19
|
+
- `check_security` — Static security scanning
|
|
20
|
+
- `find_tests` — Test file mapping
|
|
21
|
+
- `analyze_job` — ActiveJob analysis
|
|
22
|
+
- `explain_association_chain` — Association pathfinding between models
|
|
23
|
+
- `generate_erd` — Mermaid ERD generation
|
|
24
|
+
- CLI with setup instructions for Claude Desktop, Cursor, and VS Code
|
|
25
|
+
- Built on official MCP Ruby SDK
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aditya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">🔍 rails-mcp-insight</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>Deep Rails introspection MCP server for AI assistants</strong>
|
|
5
|
+
</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://rubygems.org/gems/rails-mcp-insight"><img src="https://img.shields.io/gem/v/rails-mcp-insight?color=%23e9573f" alt="Gem Version"></a>
|
|
8
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
9
|
+
<a href="https://rubygems.org/gems/rails-mcp-insight"><img src="https://img.shields.io/gem/dt/rails-mcp-insight?color=green" alt="Downloads"></a>
|
|
10
|
+
</p>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
Give your AI assistant (**Claude Desktop**, **Cursor**, **VS Code / Roo Code / Cline**) **deep, intelligent understanding** of your Ruby on Rails application through the open [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
|
|
16
|
+
|
|
17
|
+
Unlike basic tools that dump raw files or schema definitions, `rails-mcp-insight` provides **cross-referenced, contextual intelligence** — tracing the full request lifecycle from route → controller → model → callbacks → background jobs → mailers.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## ✨ Available Tools (15)
|
|
22
|
+
|
|
23
|
+
| Tool | Description | Example Prompt |
|
|
24
|
+
|------|-------------|----------------|
|
|
25
|
+
| `stats_overview` | Project dashboard: versions, counts, LOC, test coverage | *"Give me an overview of this Rails project."* |
|
|
26
|
+
| `search_routes` | Smart route search with HTTP method & path filtering | *"Find all POST routes in config/routes.rb"* |
|
|
27
|
+
| `analyze_model` | Deep dive: columns, associations, validations, callbacks, scopes | *"Analyze the Order model."* |
|
|
28
|
+
| `analyze_controller` | Actions, filters, strong params, rescue handlers | *"Show details for OrdersController."* |
|
|
29
|
+
| `search_code` | Regex code search with surrounding context lines | *"Search for stripe_customer_id across the codebase."* |
|
|
30
|
+
| `find_definition` | Locate where any class, module, method, or constant is defined | *"Where is OrderService defined?"* |
|
|
31
|
+
| `list_models` | List all models with table names & metadata at a glance | *"List all models in this application."* |
|
|
32
|
+
| **`trace_request`** | **🔥 Full request lifecycle tracing (killer feature!)** | *"Trace what happens when POST /orders is called."* |
|
|
33
|
+
| `analyze_migration_history` | Chronological schema evolution & migration timeline | *"Show migration history for the users table."* |
|
|
34
|
+
| `audit_gems` | Dependency health check from Gemfile / Gemfile.lock | *"Audit our installed gems."* |
|
|
35
|
+
| `check_security` | Static analysis for SQL injection, mass assignment, XSS, etc. | *"Scan this project for security vulnerabilities."* |
|
|
36
|
+
| `find_tests` | Map source files → test/spec files + factories | *"Find test files for app/models/user.rb"* |
|
|
37
|
+
| `analyze_job` | ActiveJob queue, retry/discard config, callbacks, triggers | *"Analyze OrderConfirmationJob."* |
|
|
38
|
+
| `explain_association_chain` | Find all association paths between two models | *"How does User relate to Product?"* |
|
|
39
|
+
| `generate_erd` | Mermaid Entity Relationship Diagrams | *"Generate an ERD for User, Order, Product."* |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🎯 The Killer Feature: `trace_request`
|
|
44
|
+
|
|
45
|
+
Ask your AI: *"What happens when a user submits `POST /orders`?"*
|
|
46
|
+
|
|
47
|
+
`rails-mcp-insight` traces the full request path statically:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"route": { "method": "POST", "path": "/orders", "controller_action": "orders#create" },
|
|
52
|
+
"controller": {
|
|
53
|
+
"name": "OrdersController",
|
|
54
|
+
"action": "create",
|
|
55
|
+
"before_actions": ["authenticate_user!", "set_cart"],
|
|
56
|
+
"strong_params": [{ "resource": "order", "permitted_attributes": ["product_id", "quantity", "notes"] }]
|
|
57
|
+
},
|
|
58
|
+
"model_operations": {
|
|
59
|
+
"Order": {
|
|
60
|
+
"callbacks": [
|
|
61
|
+
{ "type": "before_create", "target": "calculate_total" },
|
|
62
|
+
{ "type": "after_create", "target": "notify_warehouse" },
|
|
63
|
+
{ "type": "after_commit", "target": "update_inventory" }
|
|
64
|
+
],
|
|
65
|
+
"validations": ["validates :quantity, presence: true"]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"side_effects": {
|
|
69
|
+
"jobs": ["OrderConfirmationJob"],
|
|
70
|
+
"mailers": ["OrderMailer"],
|
|
71
|
+
"broadcasts": false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 📦 Installation
|
|
79
|
+
|
|
80
|
+
### Option 1: Global Gem Installation (Recommended)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
gem install rails-mcp-insight
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Verify installation:
|
|
87
|
+
```bash
|
|
88
|
+
rails-mcp-insight --version
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Option 2: Add to Gemfile
|
|
92
|
+
|
|
93
|
+
Add to your Rails application's `Gemfile`:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
group :development do
|
|
97
|
+
gem "rails-mcp-insight", require: false
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Then run:
|
|
102
|
+
```bash
|
|
103
|
+
bundle install
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 📖 Step-by-Step Setup Guide
|
|
109
|
+
|
|
110
|
+
### 1. Claude Desktop Setup
|
|
111
|
+
|
|
112
|
+
#### Step 1: Open the configuration file
|
|
113
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
114
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
115
|
+
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
|
116
|
+
|
|
117
|
+
#### Step 2: Add the server entry
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"rails-insight": {
|
|
122
|
+
"command": "rails-mcp-insight",
|
|
123
|
+
"args": ["--project", "/absolute/path/to/your/rails/app"]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Step 3: Restart Claude Desktop
|
|
130
|
+
Restart Claude Desktop. You will see a hammer icon 🔨 indicating that `rails-mcp-insight` tools are active.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 2. Cursor IDE Setup
|
|
135
|
+
|
|
136
|
+
#### Option A: Project-specific config (Recommended)
|
|
137
|
+
Create a file at `.cursor/mcp.json` in your Rails root directory:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"mcpServers": {
|
|
142
|
+
"rails-insight": {
|
|
143
|
+
"command": "rails-mcp-insight",
|
|
144
|
+
"args": ["--project", "/absolute/path/to/your/rails/app"]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Option B: Cursor Global Settings
|
|
151
|
+
1. Open Cursor Settings (`Ctrl+,` or `Cmd+,`).
|
|
152
|
+
2. Navigate to **Features** → **MCP Servers**.
|
|
153
|
+
3. Click **Add New MCP Server**.
|
|
154
|
+
4. Name: `rails-insight`
|
|
155
|
+
5. Type: `stdio`
|
|
156
|
+
6. Command: `rails-mcp-insight --project /absolute/path/to/your/rails/app`
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### 3. VS Code / Roo Code / Cline Setup
|
|
161
|
+
|
|
162
|
+
Add to your workspace or global `.vscode/mcp.json`:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"servers": {
|
|
167
|
+
"rails-insight": {
|
|
168
|
+
"type": "stdio",
|
|
169
|
+
"command": "rails-mcp-insight",
|
|
170
|
+
"args": ["--project", "/absolute/path/to/your/rails/app"]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 💬 Step-by-Step Usage & Example Prompts
|
|
179
|
+
|
|
180
|
+
Once configured, simply chat naturally with your AI assistant. Here are concrete examples of how to use each tool:
|
|
181
|
+
|
|
182
|
+
### 1. Full Lifecycle Tracing
|
|
183
|
+
> **Prompt:** *"Trace what happens when a user calls POST /orders"*
|
|
184
|
+
> **Tool called:** `trace_request`
|
|
185
|
+
> **Result:** Traces routes → filters → strong params → model validations/callbacks → background jobs & mailers.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### 2. Project Health Overview
|
|
190
|
+
> **Prompt:** *"Give me a health dashboard for this Rails app."*
|
|
191
|
+
> **Tool called:** `stats_overview`
|
|
192
|
+
> **Result:** Displays Ruby/Rails versions, line counts, model/controller totals, and test coverage ratios.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### 3. Deep Model Inspection
|
|
197
|
+
> **Prompt:** *"Analyze the Order model including columns, validations, and callbacks."*
|
|
198
|
+
> **Tool called:** `analyze_model`
|
|
199
|
+
> **Result:** Returns schema columns with types, associations (`belongs_to :user`), scopes, and callback chains.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### 4. Controller Details
|
|
204
|
+
> **Prompt:** *"Show me all before_actions and strong params for OrdersController."*
|
|
205
|
+
> **Tool called:** `analyze_controller`
|
|
206
|
+
> **Result:** Extracts filters, allowed params, rescue handlers, and action methods.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### 5. Relationship Pathfinding
|
|
211
|
+
> **Prompt:** *"How does User relate to Product?"*
|
|
212
|
+
> **Tool called:** `explain_association_chain`
|
|
213
|
+
> **Result:** Finds all association paths (e.g., `User → has_many :orders → Order → belongs_to :product → Product`).
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### 6. Visual ERD Generation
|
|
218
|
+
> **Prompt:** *"Generate a Mermaid ERD diagram for User, Order, and Product."*
|
|
219
|
+
> **Tool called:** `generate_erd`
|
|
220
|
+
> **Result:** Produces a rendered Mermaid `erDiagram` showing tables, attributes, and relationships.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### 7. Security Auditing
|
|
225
|
+
> **Prompt:** *"Scan this project for potential SQL injection or mass assignment issues."*
|
|
226
|
+
> **Tool called:** `check_security`
|
|
227
|
+
> **Result:** Static scan checking 10 security patterns (raw SQL, `html_safe`, `params.permit`, `eval`, system calls).
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### 8. Finding Definitions
|
|
232
|
+
> **Prompt:** *"Where is OrderConfirmationJob defined?"*
|
|
233
|
+
> **Tool called:** `find_definition`
|
|
234
|
+
> **Result:** Locates exact file and line number across the project.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 🛠️ Local Development & Testing Guide
|
|
239
|
+
|
|
240
|
+
If you want to contribute or test `rails-mcp-insight` locally:
|
|
241
|
+
|
|
242
|
+
### 1. Clone the repository
|
|
243
|
+
```bash
|
|
244
|
+
git clone https://github.com/aditya/rails-mcp-insight.git
|
|
245
|
+
cd rails-mcp-insight
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 2. Install dependencies
|
|
249
|
+
```bash
|
|
250
|
+
bundle install
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 3. Run the test suite (26 RSpec tests)
|
|
254
|
+
```bash
|
|
255
|
+
bundle exec rspec
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 4. Run RuboCop (Code style check)
|
|
259
|
+
```bash
|
|
260
|
+
bundle exec rubocop
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 5. Test against the included sample Rails fixture
|
|
264
|
+
```bash
|
|
265
|
+
bundle exec bin/rails-mcp-insight --project spec/fixtures/sample_rails_app
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 🏗️ How It Works Under The Hood
|
|
271
|
+
|
|
272
|
+
`rails-mcp-insight` performs **pure static analysis**:
|
|
273
|
+
- ⚡ **Fast** — No Rails boot time
|
|
274
|
+
- 🔒 **Safe** — Read-only, never modifies your code or database
|
|
275
|
+
- 🔌 **Zero config** — Auto-detects Rails project structure
|
|
276
|
+
- 📦 **Lightweight** — Depends only on the official `mcp` gem
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
AI Assistant ←→ MCP (JSON-RPC / stdio) ←→ rails-mcp-insight ←→ Rails App (Filesystem)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 🛡️ Security & Privacy
|
|
285
|
+
|
|
286
|
+
- **Read-Only**: Every tool is declared with `read_only_hint: true`.
|
|
287
|
+
- **Zero Execution**: Does not execute Ruby code inside your Rails application.
|
|
288
|
+
- **Local & Offline**: All processing happens locally on your computer. No data is sent to external servers by this gem.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 📋 Requirements
|
|
293
|
+
|
|
294
|
+
- **Ruby**: `>= 3.0`
|
|
295
|
+
- **Rails App**: Rails 5.0+, 6.x, 7.x, 8.x
|
|
296
|
+
- **AI Host**: Any MCP-compatible host (Claude Desktop, Cursor, VS Code Roo Code / Cline)
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## 📄 License
|
|
301
|
+
|
|
302
|
+
Distributed under the [MIT License](LICENSE).
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
<p align="center">
|
|
307
|
+
<strong>⭐ Star this repo if it helps your Rails development!</strong>
|
|
308
|
+
</p>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require "bundler/setup"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
# Running from installed gem
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative "../lib/rails_mcp_insight"
|
|
11
|
+
|
|
12
|
+
# Parse command line arguments
|
|
13
|
+
project_path = Dir.pwd
|
|
14
|
+
show_version = false
|
|
15
|
+
|
|
16
|
+
ARGV.each_with_index do |arg, idx|
|
|
17
|
+
case arg
|
|
18
|
+
when "--version", "-v"
|
|
19
|
+
show_version = true
|
|
20
|
+
when "--project", "-p"
|
|
21
|
+
project_path = ARGV[idx + 1] if ARGV[idx + 1]
|
|
22
|
+
when "--help", "-h"
|
|
23
|
+
$stderr.puts <<~HELP
|
|
24
|
+
rails-mcp-insight v#{RailsMcpInsight::VERSION}
|
|
25
|
+
|
|
26
|
+
Deep Rails introspection MCP server for AI assistants.
|
|
27
|
+
Provides 15 tools for analyzing routes, models, controllers,
|
|
28
|
+
security, testing, and full request lifecycle tracing.
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
rails-mcp-insight Start MCP server (stdio transport)
|
|
32
|
+
rails-mcp-insight --project /path Specify Rails project directory
|
|
33
|
+
rails-mcp-insight --version Show version
|
|
34
|
+
rails-mcp-insight --help Show this help
|
|
35
|
+
|
|
36
|
+
Configuration for AI clients:
|
|
37
|
+
|
|
38
|
+
Claude Desktop (~/.claude/claude_desktop_config.json):
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"rails-insight": {
|
|
42
|
+
"command": "rails-mcp-insight",
|
|
43
|
+
"args": ["--project", "/path/to/your/rails/app"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Cursor (.cursor/mcp.json):
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"rails-insight": {
|
|
52
|
+
"command": "rails-mcp-insight",
|
|
53
|
+
"args": ["--project", "/path/to/your/rails/app"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
VS Code (.vscode/mcp.json):
|
|
59
|
+
{
|
|
60
|
+
"servers": {
|
|
61
|
+
"rails-insight": {
|
|
62
|
+
"type": "stdio",
|
|
63
|
+
"command": "rails-mcp-insight",
|
|
64
|
+
"args": ["--project", "/path/to/your/rails/app"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
HELP
|
|
70
|
+
exit 0
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if show_version
|
|
75
|
+
puts "rails-mcp-insight v#{RailsMcpInsight::VERSION}"
|
|
76
|
+
exit 0
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Log to stderr (stdout is reserved for MCP JSON-RPC)
|
|
80
|
+
$stderr.puts "rails-mcp-insight v#{RailsMcpInsight::VERSION}"
|
|
81
|
+
$stderr.puts "Project: #{project_path}"
|
|
82
|
+
|
|
83
|
+
# Validate the project
|
|
84
|
+
config = RailsMcpInsight::Configuration.new(project_path: project_path)
|
|
85
|
+
detector = RailsMcpInsight::ProjectDetector.new(config)
|
|
86
|
+
|
|
87
|
+
begin
|
|
88
|
+
detector.validate!
|
|
89
|
+
rescue RailsMcpInsight::Error => e
|
|
90
|
+
$stderr.puts "Error: #{e.message}"
|
|
91
|
+
exit 1
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
info = detector.detect
|
|
95
|
+
$stderr.puts "Detected: Rails #{info[:rails_version] || 'unknown'}, " \
|
|
96
|
+
"#{info[:model_count]} models, #{info[:controller_count]} controllers"
|
|
97
|
+
$stderr.puts "Starting MCP server on stdio..."
|
|
98
|
+
|
|
99
|
+
# Start the MCP server
|
|
100
|
+
RailsMcpInsight.start(project_path: project_path)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module RailsMcpInsight
|
|
6
|
+
module Analyzers
|
|
7
|
+
# Lightweight Ruby AST parser using regex-based analysis.
|
|
8
|
+
# Extracts class definitions, method definitions, module inclusions,
|
|
9
|
+
# DSL calls (has_many, belongs_to, validates, etc.) from Ruby source files.
|
|
10
|
+
#
|
|
11
|
+
# This avoids requiring Prism as a hard dependency while still providing
|
|
12
|
+
# useful structural information. Can be upgraded to Prism later for
|
|
13
|
+
# more accurate parsing.
|
|
14
|
+
class AstParser
|
|
15
|
+
# Represents a parsed Ruby file's structural information
|
|
16
|
+
ParseResult = Struct.new(
|
|
17
|
+
:file_path,
|
|
18
|
+
:classes,
|
|
19
|
+
:modules,
|
|
20
|
+
:methods,
|
|
21
|
+
:includes,
|
|
22
|
+
:extends,
|
|
23
|
+
:dsl_calls,
|
|
24
|
+
:constants,
|
|
25
|
+
:comments,
|
|
26
|
+
keyword_init: true
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
ClassInfo = Struct.new(:name, :parent, :line, :body_lines, keyword_init: true)
|
|
30
|
+
MethodInfo = Struct.new(:name, :line, :visibility, :args, keyword_init: true)
|
|
31
|
+
DslCall = Struct.new(:method_name, :args, :line, :options, keyword_init: true)
|
|
32
|
+
|
|
33
|
+
def initialize
|
|
34
|
+
@cache = {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Parse a Ruby file and return structured information
|
|
38
|
+
def parse_file(file_path)
|
|
39
|
+
return nil unless File.exist?(file_path)
|
|
40
|
+
|
|
41
|
+
@cache[file_path] ||= begin
|
|
42
|
+
content = File.read(file_path)
|
|
43
|
+
parse_content(content, file_path)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Clear the parse cache
|
|
48
|
+
def clear_cache!
|
|
49
|
+
@cache.clear
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Parse Ruby source content
|
|
53
|
+
def parse_content(content, file_path = "(string)")
|
|
54
|
+
lines = content.lines
|
|
55
|
+
|
|
56
|
+
ParseResult.new(
|
|
57
|
+
file_path: file_path,
|
|
58
|
+
classes: extract_classes(lines),
|
|
59
|
+
modules: extract_modules(lines),
|
|
60
|
+
methods: extract_methods(lines),
|
|
61
|
+
includes: extract_includes(lines),
|
|
62
|
+
extends: extract_extends(lines),
|
|
63
|
+
dsl_calls: extract_dsl_calls(lines),
|
|
64
|
+
constants: extract_constants(lines),
|
|
65
|
+
comments: extract_comments(lines)
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def extract_classes(lines)
|
|
72
|
+
classes = []
|
|
73
|
+
lines.each_with_index do |line, idx|
|
|
74
|
+
next unless (match = line.match(/^\s*class\s+([A-Z][\w:]*)\s*(?:<\s*([A-Z][\w:]*))?/))
|
|
75
|
+
|
|
76
|
+
classes << ClassInfo.new(
|
|
77
|
+
name: match[1],
|
|
78
|
+
parent: match[2],
|
|
79
|
+
line: idx + 1,
|
|
80
|
+
body_lines: []
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
classes
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def extract_modules(lines)
|
|
87
|
+
modules = []
|
|
88
|
+
lines.each_with_index do |line, idx|
|
|
89
|
+
if (match = line.match(/^\s*module\s+([A-Z][\w:]*)/))
|
|
90
|
+
modules << { name: match[1], line: idx + 1 }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
modules
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def extract_methods(lines)
|
|
97
|
+
methods = []
|
|
98
|
+
current_visibility = :public
|
|
99
|
+
|
|
100
|
+
lines.each_with_index do |line, idx|
|
|
101
|
+
stripped = line.strip
|
|
102
|
+
|
|
103
|
+
# Track visibility changes
|
|
104
|
+
case stripped
|
|
105
|
+
when "private" then current_visibility = :private
|
|
106
|
+
when "protected" then current_visibility = :protected
|
|
107
|
+
when "public" then current_visibility = :public
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Match method definitions
|
|
111
|
+
next unless (match = stripped.match(/^def\s+(self\.)?(\w+[?!=]?)(?:\((.*?)\))?/))
|
|
112
|
+
|
|
113
|
+
is_class_method = !match[1].nil?
|
|
114
|
+
method_name = match[2]
|
|
115
|
+
args = match[3]&.strip
|
|
116
|
+
|
|
117
|
+
methods << MethodInfo.new(
|
|
118
|
+
name: is_class_method ? "self.#{method_name}" : method_name,
|
|
119
|
+
line: idx + 1,
|
|
120
|
+
visibility: is_class_method ? :public : current_visibility,
|
|
121
|
+
args: args
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
methods
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def extract_includes(lines)
|
|
128
|
+
results = []
|
|
129
|
+
lines.each_with_index do |line, idx|
|
|
130
|
+
if (match = line.match(/^\s*include\s+(.+)/))
|
|
131
|
+
results << { module: match[1].strip, line: idx + 1 }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
results
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def extract_extends(lines)
|
|
138
|
+
results = []
|
|
139
|
+
lines.each_with_index do |line, idx|
|
|
140
|
+
if (match = line.match(/^\s*extend\s+(.+)/))
|
|
141
|
+
results << { module: match[1].strip, line: idx + 1 }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
results
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Extract Rails DSL calls: associations, validations, callbacks, scopes, etc.
|
|
148
|
+
def extract_dsl_calls(lines)
|
|
149
|
+
dsl_methods = [
|
|
150
|
+
"has_many", "has_one", "belongs_to", "has_and_belongs_to_many", "validates", "validates_presence_of", "validates_uniqueness_of", "validates_format_of", "validate", "validates_with", "validates_each", "validates_length_of", "validates_numericality_of", "before_action", "after_action", "around_action", "skip_before_action", "before_create", "after_create", "before_save", "after_save", "before_update", "after_update", "before_destroy", "after_destroy", "before_validation", "after_validation", "after_commit", "after_create_commit", "after_update_commit", "after_destroy_commit", "scope", "enum", "delegate", "has_secure_password", "has_one_attached", "has_many_attached", "rescue_from", "before_enqueue", "before_perform", "after_perform", "around_perform", "queue_as", "retry_on", "discard_on"
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
pattern = /^\s*(#{dsl_methods.join('|')})\s+(.+)/
|
|
154
|
+
|
|
155
|
+
results = []
|
|
156
|
+
lines.each_with_index do |line, idx|
|
|
157
|
+
next unless (match = line.match(pattern))
|
|
158
|
+
|
|
159
|
+
method_name = match[1]
|
|
160
|
+
raw_args = match[2].strip.chomp(",")
|
|
161
|
+
|
|
162
|
+
results << DslCall.new(
|
|
163
|
+
method_name: method_name,
|
|
164
|
+
args: raw_args,
|
|
165
|
+
line: idx + 1,
|
|
166
|
+
options: extract_options_from_args(raw_args)
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
results
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def extract_constants(lines)
|
|
173
|
+
results = []
|
|
174
|
+
lines.each_with_index do |line, idx|
|
|
175
|
+
if (match = line.match(/^\s*([A-Z][A-Z_0-9]*)\s*=\s*(.+)/))
|
|
176
|
+
results << { name: match[1], value: match[2].strip, line: idx + 1 }
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
results
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def extract_comments(lines)
|
|
183
|
+
results = []
|
|
184
|
+
lines.each_with_index do |line, idx|
|
|
185
|
+
results << { text: line.strip.sub(/^#\s*/, ""), line: idx + 1 } if line.strip.start_with?("#")
|
|
186
|
+
end
|
|
187
|
+
results
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def extract_options_from_args(raw_args)
|
|
191
|
+
# Simple extraction of hash-style options from DSL args
|
|
192
|
+
options = {}
|
|
193
|
+
raw_args.scan(/(\w+):\s*(?::(\w+)|"([^"]*)"|(true|false|nil|\d+))/) do |key, sym, str, lit|
|
|
194
|
+
options[key] = sym || str || lit
|
|
195
|
+
end
|
|
196
|
+
options
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|