rails-mcp-server 1.0.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/LICENSE.txt +21 -0
- data/README.md +482 -0
- data/exe/rails-mcp-server +40 -0
- data/exe/rails-mcp-setup-claude +146 -0
- data/lib/rails-mcp-server/version.rb +3 -0
- data/lib/rails_mcp_server.rb +509 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7d6b13d9872252c1545e274913eb23db3d29d9e9df89e8943d6bc81b36bc530c
|
4
|
+
data.tar.gz: 888854cbf2bd61251c0908462deee4ed2579bade5ccb4fb28cf6e050fcd47bdd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7181f3eb02a5cc1d85bb0503df37d5c6985a95a26f3634d3c677e4be51204ed761a5e50041c66817fa341f460d083e036510699b380b119d0506bcadc6ee95fd
|
7
|
+
data.tar.gz: 7ed062c493c6b9071086a96161dc7f1c63f54f8227a48ad3aa5492c6fd81904abab548e1e4bc9438599ab9a69a068f2ebebe1b9c21a19a8195d3f20d08020748
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 [Mario Alberto Chávez Cárdenas]
|
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,482 @@
|
|
1
|
+
# Rails MCP Server
|
2
|
+
|
3
|
+
A Ruby implementation of a Model Context Protocol (MCP) server for Rails projects. This server allows LLMs (Large Language Models) to interact with Rails projects through the Model Context Protocol.
|
4
|
+
|
5
|
+
## What is MCP?
|
6
|
+
|
7
|
+
The Model Context Protocol (MCP) is a standardized way for AI models to interact with their environment. It defines a structured method for models to request and use tools, access resources, and maintain context during interactions.
|
8
|
+
|
9
|
+
This Rails MCP Server implements the MCP specification to give AI models access to Rails projects for code analysis, exploration, and assistance.
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
- Manage multiple Rails projects
|
14
|
+
- Browse project files and structures
|
15
|
+
- View Rails routes
|
16
|
+
- Inspect model information
|
17
|
+
- Get database schema information
|
18
|
+
- Follow the Model Context Protocol standard
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Install the gem:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
gem install rails-mcp-server
|
26
|
+
```
|
27
|
+
|
28
|
+
After installation, the `rails-mcp-server` and `rails-mcp-setup-claude` executables will be available in your PATH.
|
29
|
+
|
30
|
+
## Configuration
|
31
|
+
|
32
|
+
The Rails MCP Server follows the XDG Base Directory Specification for configuration files:
|
33
|
+
|
34
|
+
- On macOS: `$XDG_CONFIG_HOME/rails-mcp` or `~/.config/rails-mcp` if XDG_CONFIG_HOME is not set
|
35
|
+
- On Windows: `%APPDATA%\rails-mcp`
|
36
|
+
|
37
|
+
The server will automatically create these directories and an empty `projects.yml` file the first time it runs.
|
38
|
+
|
39
|
+
To configure your projects:
|
40
|
+
|
41
|
+
1. Edit the `projects.yml` file in your config directory to include your Rails projects:
|
42
|
+
|
43
|
+
```yaml
|
44
|
+
store: "~/projects/store"
|
45
|
+
blog: "~/projects/rails-blog"
|
46
|
+
```
|
47
|
+
|
48
|
+
Each key in the YAML file is a project name (which will be used with the `switch_project` tool), and each value is the path to the project directory.
|
49
|
+
|
50
|
+
## Claude Desktop Integration
|
51
|
+
|
52
|
+
The Rails MCP Server can be used with Claude Desktop. There are two options to set this up:
|
53
|
+
|
54
|
+
### Option 1: Use the setup script (recommended)
|
55
|
+
|
56
|
+
Run the setup script which will automatically configure Claude Desktop and set up the proper XDG-compliant directory structure:
|
57
|
+
|
58
|
+
```bash
|
59
|
+
rails-mcp-setup-claude
|
60
|
+
```
|
61
|
+
|
62
|
+
The script will:
|
63
|
+
|
64
|
+
- Create the appropriate config directory for your platform
|
65
|
+
- Create an empty `projects.yml` file if it doesn't exist
|
66
|
+
- Update the Claude Desktop configuration
|
67
|
+
|
68
|
+
After running the script, restart Claude Desktop to apply the changes.
|
69
|
+
|
70
|
+
### Option 2: Manual configuration
|
71
|
+
|
72
|
+
1. Create the appropriate config directory for your platform:
|
73
|
+
- macOS: `$XDG_CONFIG_HOME/rails-mcp` or `~/.config/rails-mcp`
|
74
|
+
- Windows: `%APPDATA%\rails-mcp`
|
75
|
+
|
76
|
+
2. Create a `projects.yml` file in that directory with your Rails projects.
|
77
|
+
|
78
|
+
3. Find or create the Claude Desktop configuration file:
|
79
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
80
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
81
|
+
|
82
|
+
4. Add or update the MCP server configuration:
|
83
|
+
|
84
|
+
```json
|
85
|
+
{
|
86
|
+
"mcpServers": {
|
87
|
+
"railsMcpServer": {
|
88
|
+
"command": "ruby",
|
89
|
+
"args": ["/full/path/to/rails-mcp-server/exe/rails-mcp-server"]
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
```
|
94
|
+
|
95
|
+
5. Restart Claude Desktop to apply the changes.
|
96
|
+
|
97
|
+
### Ruby Version Manager Users
|
98
|
+
|
99
|
+
Claude Desktop launches the MCP server using your system's default Ruby environment, bypassing version manager initialization (e.g., rbenv, RVM). The MCP server needs to use the same Ruby version where it was installed, as MCP server startup failures can occur when using an incompatible Ruby version.
|
100
|
+
|
101
|
+
If you are using a Ruby version manager such as rbenv, you can create a symbolic link to your Ruby shim to ensure the correct version is used:
|
102
|
+
|
103
|
+
```
|
104
|
+
|
105
|
+
sudo ln -s /home/your_user/.rbenv/shims/ruby /usr/local/bin/ruby
|
106
|
+
|
107
|
+
```
|
108
|
+
|
109
|
+
Replace "/home/your_user/.rbenv/shims/ruby" with your actual path for the Ruby shim.
|
110
|
+
|
111
|
+
## Usage
|
112
|
+
|
113
|
+
Start the server:
|
114
|
+
|
115
|
+
```bash
|
116
|
+
rails-mcp-server
|
117
|
+
```
|
118
|
+
|
119
|
+
### Logging Options
|
120
|
+
|
121
|
+
The server logs to a file in the `./log` directory by default. You can customize logging with these options:
|
122
|
+
|
123
|
+
```bash
|
124
|
+
# Set the log level (debug, info, warn, error, fatal)
|
125
|
+
rails-mcp-server --log-level debug
|
126
|
+
```
|
127
|
+
|
128
|
+
## How the Server Works
|
129
|
+
|
130
|
+
The Rails MCP Server implements the Model Context Protocol over standard input/output (stdio). It:
|
131
|
+
|
132
|
+
1. Reads JSON-RPC 2.0 requests from standard input
|
133
|
+
2. Processes the requests using the appropriate tools
|
134
|
+
3. Returns JSON-RPC 2.0 responses to standard output
|
135
|
+
|
136
|
+
Each request includes a sequence number to match requests with responses, as defined in the MCP specification.
|
137
|
+
|
138
|
+
## Available Tools
|
139
|
+
|
140
|
+
The server provides the following tools for interacting with Rails projects:
|
141
|
+
|
142
|
+
### 1. `switch_project`
|
143
|
+
|
144
|
+
Switch the active Rails project.
|
145
|
+
|
146
|
+
**Parameters:**
|
147
|
+
|
148
|
+
- `project_name`: (String, required) Name of the project to switch to, as defined in the projects.yml file
|
149
|
+
|
150
|
+
**Example:**
|
151
|
+
|
152
|
+
```json
|
153
|
+
{
|
154
|
+
"jsonrpc": "2.0",
|
155
|
+
"id": "123",
|
156
|
+
"method": "tools/call",
|
157
|
+
"params": {
|
158
|
+
"name": "switch_project",
|
159
|
+
"arguments": {
|
160
|
+
"project_name": "blog"
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}
|
164
|
+
```
|
165
|
+
|
166
|
+
**Description:** Change the active Rails project to interact with a different codebase. Must be called before using other tools. Available projects are defined in the projects.yml configuration file.
|
167
|
+
|
168
|
+
Examples:
|
169
|
+
|
170
|
+
```
|
171
|
+
Can you switch to the "store" project so we can explore it?
|
172
|
+
```
|
173
|
+
|
174
|
+
```
|
175
|
+
I'd like to analyze my "blog" application. Please switch to that project first.
|
176
|
+
```
|
177
|
+
|
178
|
+
```
|
179
|
+
Switch to the "ecommerce" project and give me a summary of the codebase.
|
180
|
+
```
|
181
|
+
|
182
|
+
### 2. `get_project_info`
|
183
|
+
|
184
|
+
Get information about the current Rails project, including version, directory structure, and configuration.
|
185
|
+
|
186
|
+
**Parameters:** None
|
187
|
+
|
188
|
+
**Example:**
|
189
|
+
|
190
|
+
```json
|
191
|
+
{
|
192
|
+
"jsonrpc": "2.0",
|
193
|
+
"id": "124",
|
194
|
+
"method": "tools/call",
|
195
|
+
"params": {
|
196
|
+
"name": "get_project_info",
|
197
|
+
"arguments": {}
|
198
|
+
}
|
199
|
+
}
|
200
|
+
```
|
201
|
+
|
202
|
+
**Description:** Retrieve comprehensive information about the current Rails project, including Rails version, directory structure, API-only status, and overall project organization.
|
203
|
+
|
204
|
+
Examples:
|
205
|
+
|
206
|
+
```
|
207
|
+
Now that we're in the blog project, can you give me an overview of the project structure and Rails version?
|
208
|
+
```
|
209
|
+
|
210
|
+
```
|
211
|
+
Tell me about this Rails application. What version is it running and how is it organized?
|
212
|
+
```
|
213
|
+
|
214
|
+
```
|
215
|
+
I'd like to understand the high-level architecture of this project. Can you provide the project information?
|
216
|
+
```
|
217
|
+
|
218
|
+
### 3. `list_files`
|
219
|
+
|
220
|
+
List files in the Rails project, with optional directory path and pattern filtering.
|
221
|
+
|
222
|
+
**Parameters:**
|
223
|
+
|
224
|
+
- `directory`: (String, optional) Directory path relative to the project root
|
225
|
+
- `pattern`: (String, optional) File pattern to match (e.g., "*.rb")
|
226
|
+
|
227
|
+
**Example:**
|
228
|
+
|
229
|
+
```json
|
230
|
+
{
|
231
|
+
"jsonrpc": "2.0",
|
232
|
+
"id": "125",
|
233
|
+
"method": "tools/call",
|
234
|
+
"params": {
|
235
|
+
"name": "list_files",
|
236
|
+
"arguments": {
|
237
|
+
"directory": "app/models",
|
238
|
+
"pattern": "*.rb"
|
239
|
+
}
|
240
|
+
}
|
241
|
+
}
|
242
|
+
```
|
243
|
+
|
244
|
+
**Description:** List files in the Rails project matching specific criteria. Use this to explore project directories or locate specific file types.
|
245
|
+
|
246
|
+
Examples:
|
247
|
+
|
248
|
+
```
|
249
|
+
Can you list all the model files in this project?
|
250
|
+
```
|
251
|
+
|
252
|
+
```
|
253
|
+
Show me all the controller files in the app/controllers directory.
|
254
|
+
```
|
255
|
+
|
256
|
+
```
|
257
|
+
I need to see all the view templates in the users section. Can you list the files in app/views/users?
|
258
|
+
```
|
259
|
+
|
260
|
+
```
|
261
|
+
List all the JavaScript files in the app/javascript directory.
|
262
|
+
```
|
263
|
+
|
264
|
+
### 4. `get_file`
|
265
|
+
|
266
|
+
Get the content of a file in the Rails project with syntax highlighting.
|
267
|
+
|
268
|
+
**Parameters:**
|
269
|
+
|
270
|
+
- `path`: (String, required) File path relative to the project root
|
271
|
+
|
272
|
+
**Example:**
|
273
|
+
|
274
|
+
```json
|
275
|
+
{
|
276
|
+
"jsonrpc": "2.0",
|
277
|
+
"id": "126",
|
278
|
+
"method": "tools/call",
|
279
|
+
"params": {
|
280
|
+
"name": "get_file",
|
281
|
+
"arguments": {
|
282
|
+
"path": "app/models/user.rb"
|
283
|
+
}
|
284
|
+
}
|
285
|
+
}
|
286
|
+
```
|
287
|
+
|
288
|
+
**Description:** Retrieve the complete content of a specific file with syntax highlighting.
|
289
|
+
|
290
|
+
Examples:
|
291
|
+
|
292
|
+
```
|
293
|
+
Can you show me the content of the User model file?
|
294
|
+
```
|
295
|
+
|
296
|
+
```
|
297
|
+
I need to see what's in app/controllers/products_controller.rb. Can you retrieve that file?
|
298
|
+
```
|
299
|
+
|
300
|
+
```
|
301
|
+
Please show me the application.rb file so I can check the configuration settings.
|
302
|
+
```
|
303
|
+
|
304
|
+
```
|
305
|
+
I'd like to examine the routes file. Can you display the content of config/routes.rb?
|
306
|
+
```
|
307
|
+
|
308
|
+
### 5. `get_routes`
|
309
|
+
|
310
|
+
Get the routes defined in the Rails project.
|
311
|
+
|
312
|
+
**Parameters:** None
|
313
|
+
|
314
|
+
**Example:**
|
315
|
+
|
316
|
+
```json
|
317
|
+
{
|
318
|
+
"jsonrpc": "2.0",
|
319
|
+
"id": "127",
|
320
|
+
"method": "tools/call",
|
321
|
+
"params": {
|
322
|
+
"name": "get_routes",
|
323
|
+
"arguments": {}
|
324
|
+
}
|
325
|
+
}
|
326
|
+
```
|
327
|
+
|
328
|
+
**Description:** Retrieve all HTTP routes defined in the Rails application with their associated controllers and actions.
|
329
|
+
|
330
|
+
Examples:
|
331
|
+
|
332
|
+
```
|
333
|
+
Can you show me all the routes defined in this application?
|
334
|
+
```
|
335
|
+
|
336
|
+
```
|
337
|
+
I need to understand the API endpoints available in this project. Can you list the routes?
|
338
|
+
```
|
339
|
+
|
340
|
+
```
|
341
|
+
Show me the routing configuration for this Rails app so I can see how the URLs are structured.
|
342
|
+
```
|
343
|
+
|
344
|
+
### 6. `get_models`
|
345
|
+
|
346
|
+
Get information about the models in the Rails project, including schema, associations, and definitions.
|
347
|
+
|
348
|
+
**Parameters:**
|
349
|
+
|
350
|
+
- `model_name`: (String, optional) Name of a specific model to get information for
|
351
|
+
|
352
|
+
**Example:**
|
353
|
+
|
354
|
+
```json
|
355
|
+
{
|
356
|
+
"jsonrpc": "2.0",
|
357
|
+
"id": "128",
|
358
|
+
"method": "tools/call",
|
359
|
+
"params": {
|
360
|
+
"name": "get_models",
|
361
|
+
"arguments": {
|
362
|
+
"model_name": "User"
|
363
|
+
}
|
364
|
+
}
|
365
|
+
}
|
366
|
+
```
|
367
|
+
|
368
|
+
**Description:** Retrieve detailed information about Active Record models in the project.
|
369
|
+
|
370
|
+
Examples:
|
371
|
+
|
372
|
+
```
|
373
|
+
Can you list all the models in this Rails project?
|
374
|
+
```
|
375
|
+
|
376
|
+
```
|
377
|
+
I'd like to understand the User model in detail. Can you show me its schema, associations, and code?
|
378
|
+
```
|
379
|
+
|
380
|
+
```
|
381
|
+
Show me the Product model's definition, including its relationships with other models.
|
382
|
+
```
|
383
|
+
|
384
|
+
```
|
385
|
+
What are all the models in this application, and can you then show me details for the Order model specifically?
|
386
|
+
```
|
387
|
+
|
388
|
+
### 7. `get_schema`
|
389
|
+
|
390
|
+
Get the database schema for the Rails project or for a specific table.
|
391
|
+
|
392
|
+
**Parameters:**
|
393
|
+
|
394
|
+
- `table_name`: (String, optional) Name of a specific table to get schema for
|
395
|
+
|
396
|
+
**Example:**
|
397
|
+
|
398
|
+
```json
|
399
|
+
{
|
400
|
+
"jsonrpc": "2.0",
|
401
|
+
"id": "129",
|
402
|
+
"method": "tools/call",
|
403
|
+
"params": {
|
404
|
+
"name": "get_schema",
|
405
|
+
"arguments": {
|
406
|
+
"table_name": "users"
|
407
|
+
}
|
408
|
+
}
|
409
|
+
}
|
410
|
+
```
|
411
|
+
|
412
|
+
**Description:** Retrieve database schema information for the Rails application.
|
413
|
+
|
414
|
+
Examples:
|
415
|
+
|
416
|
+
```
|
417
|
+
Can you show me the complete database schema for this Rails application?
|
418
|
+
```
|
419
|
+
|
420
|
+
```
|
421
|
+
I'd like to see the structure of the users table. Can you retrieve that schema information?
|
422
|
+
```
|
423
|
+
|
424
|
+
```
|
425
|
+
Show me the columns and their data types in the products table.
|
426
|
+
```
|
427
|
+
|
428
|
+
```
|
429
|
+
I need to understand the database design. Can you first list all tables and then show me details for the orders table?
|
430
|
+
```
|
431
|
+
|
432
|
+
## Integration with LLM Clients
|
433
|
+
|
434
|
+
This server is designed to be integrated with LLM clients that support the Model Context Protocol, such as Claude Desktop or other MCP-compatible applications.
|
435
|
+
|
436
|
+
To use with an MCP client:
|
437
|
+
|
438
|
+
1. Start the Rails MCP Server
|
439
|
+
2. Connect your MCP-compatible client to the server
|
440
|
+
3. The client will be able to use the available tools to interact with your Rails projects
|
441
|
+
|
442
|
+
## Manual Testing
|
443
|
+
|
444
|
+
You can manually test the server by sending JSON-RPC requests to its standard input:
|
445
|
+
|
446
|
+
```bash
|
447
|
+
echo '0 {"jsonrpc":"2.0","id":"test-123","method":"ping"}' | rails-mcp-server
|
448
|
+
```
|
449
|
+
|
450
|
+
Expected response:
|
451
|
+
|
452
|
+
```
|
453
|
+
0 {"jsonrpc":"2.0","id":"test-123","result":{"version":"1.0.0"}}
|
454
|
+
```
|
455
|
+
|
456
|
+
Or test multiple commands in sequence:
|
457
|
+
|
458
|
+
```bash
|
459
|
+
(echo '0 {"jsonrpc":"2.0","id":"test-123","method":"tools/list"}'; sleep 1; echo '1 {"jsonrpc":"2.0","id":"test-456","method":"tools/call","params":{"name":"switch_project","arguments":{"project_name":"blog"}}}') | rails-mcp-server
|
460
|
+
```
|
461
|
+
|
462
|
+
You can also use `jq` to parse the output and format it nicely:
|
463
|
+
|
464
|
+
```bash
|
465
|
+
echo '0 {"jsonrpc":"2.0","id":"list-tools","method":"tools/list"}' | rails-mcp-server | sed 's/^[0-9]* //' | jq '.result.tools'
|
466
|
+
```
|
467
|
+
|
468
|
+
## License
|
469
|
+
|
470
|
+
This Rails MCP server is released under the MIT License, a permissive open-source license that allows for free use, modification, distribution, and private use.
|
471
|
+
|
472
|
+
Copyright (c) 2025 Mario Alberto Chávez Cárdenas
|
473
|
+
|
474
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
475
|
+
|
476
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
477
|
+
|
478
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
479
|
+
|
480
|
+
## Contributing
|
481
|
+
|
482
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/maquina-app/rails-mcp-server>.
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
# Get script directory and project root
|
6
|
+
bin_dir = Pathname.new(__FILE__).dirname
|
7
|
+
root_dir = bin_dir.parent
|
8
|
+
|
9
|
+
# Check if server script exists
|
10
|
+
server_script = root_dir.join("lib", "rails_mcp_server.rb")
|
11
|
+
unless server_script.exist?
|
12
|
+
puts "Error: rails_mcp_server.rb not found in lib directory (#{root_dir}/lib)"
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
# Show version if requested
|
17
|
+
if ARGV[0] == "version"
|
18
|
+
puts "Rails MCP Server version 1.0.0"
|
19
|
+
exit 0
|
20
|
+
end
|
21
|
+
|
22
|
+
# Help message
|
23
|
+
if ARGV[0] == "--help" || ARGV[0] == "-h"
|
24
|
+
puts "Rails MCP Server - MCP protocol server for Rails projects"
|
25
|
+
puts ""
|
26
|
+
puts "Usage: #{File.basename($0)} [options]"
|
27
|
+
puts ""
|
28
|
+
puts "Options:"
|
29
|
+
puts " --log-level LEVEL Log level: debug, info, warn, error (default: info)"
|
30
|
+
puts " version Display version information"
|
31
|
+
puts " --help, -h Display this help message"
|
32
|
+
puts ""
|
33
|
+
puts "Example:"
|
34
|
+
puts " #{File.basename($0)} --log-level debug"
|
35
|
+
exit 0
|
36
|
+
end
|
37
|
+
|
38
|
+
# Change to root directory and execute server script
|
39
|
+
Dir.chdir(root_dir)
|
40
|
+
load server_script
|
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "fileutils"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
# ANSI color codes
|
8
|
+
class String
|
9
|
+
def colorize(color_code)
|
10
|
+
"\e[#{color_code}m#{self}\e[0m"
|
11
|
+
end
|
12
|
+
|
13
|
+
def green
|
14
|
+
colorize(32)
|
15
|
+
end
|
16
|
+
|
17
|
+
def blue
|
18
|
+
colorize(34)
|
19
|
+
end
|
20
|
+
|
21
|
+
def yellow
|
22
|
+
colorize(33)
|
23
|
+
end
|
24
|
+
|
25
|
+
def red
|
26
|
+
colorize(31)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get the absolute path to the project directory
|
31
|
+
project_dir = Pathname.new(__FILE__).dirname.parent.expand_path
|
32
|
+
|
33
|
+
# Print banner
|
34
|
+
puts "============================================".blue
|
35
|
+
puts " Rails MCP Server Setup for Claude ".blue
|
36
|
+
puts "============================================".blue
|
37
|
+
puts
|
38
|
+
|
39
|
+
# Determine the Rails MCP config directory based on platform
|
40
|
+
def get_rails_mcp_config_dir
|
41
|
+
if RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
|
42
|
+
# Windows
|
43
|
+
File.join(ENV["APPDATA"], "rails-mcp")
|
44
|
+
else
|
45
|
+
# Linux/Unix - honor XDG_CONFIG_HOME
|
46
|
+
xdg_config_home = ENV["XDG_CONFIG_HOME"]
|
47
|
+
if xdg_config_home && !xdg_config_home.empty?
|
48
|
+
File.join(xdg_config_home, "rails-mcp")
|
49
|
+
else
|
50
|
+
File.join(Dir.home, ".config", "rails-mcp")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
rails_mcp_config_dir = get_rails_mcp_config_dir
|
56
|
+
rails_mcp_log_dir = File.join(rails_mcp_config_dir, "log")
|
57
|
+
|
58
|
+
# Create config and log directories
|
59
|
+
FileUtils.mkdir_p(rails_mcp_log_dir)
|
60
|
+
puts "✓".green + " Created Rails MCP config directory: #{rails_mcp_config_dir}"
|
61
|
+
puts "✓".green + " Created Rails MCP log directory: #{rails_mcp_log_dir}"
|
62
|
+
|
63
|
+
# Create projects.yml in the config dir if it doesn't exist
|
64
|
+
projects_yml = File.join(rails_mcp_config_dir, "projects.yml")
|
65
|
+
|
66
|
+
if !File.exist?(projects_yml)
|
67
|
+
# Create a new projects.yml with explanatory comments
|
68
|
+
File.write(projects_yml, "# Rails MCP Projects\n# Format: project_name: /path/to/project\n")
|
69
|
+
puts "✓".green + " Created projects.yml file"
|
70
|
+
puts "!".yellow + " Please edit #{projects_yml} to add your Rails projects"
|
71
|
+
else
|
72
|
+
puts "✓".green + " projects.yml already exists at #{projects_yml}"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Detect OS for Claude Desktop config
|
76
|
+
claude_config_dir = if RUBY_PLATFORM.match?(/mswin|mingw|cygwin/)
|
77
|
+
File.join(ENV["APPDATA"], "Claude")
|
78
|
+
else
|
79
|
+
File.expand_path("~/Library/Application Support/Claude")
|
80
|
+
end
|
81
|
+
claude_config_file = File.join(claude_config_dir, "claude_desktop_config.json")
|
82
|
+
|
83
|
+
puts
|
84
|
+
puts "This script will configure Claude Desktop to use your Rails MCP Server.".blue
|
85
|
+
puts "This will modify: #{claude_config_file}".blue
|
86
|
+
puts "Rails MCP config directory: #{rails_mcp_config_dir}".blue
|
87
|
+
puts
|
88
|
+
|
89
|
+
# Ask for confirmation
|
90
|
+
print "Do you want to continue? (y/n) "
|
91
|
+
unless gets.chomp.casecmp("y").zero?
|
92
|
+
puts "Setup canceled.".yellow
|
93
|
+
exit(0)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Create the config directory if it doesn't exist
|
97
|
+
FileUtils.mkdir_p(claude_config_dir)
|
98
|
+
|
99
|
+
# Full path to the rails-mcp-server executable
|
100
|
+
server_path = File.join(project_dir, "exe", "rails-mcp-server")
|
101
|
+
|
102
|
+
# Ensure the server is executable
|
103
|
+
FileUtils.chmod("+x", server_path)
|
104
|
+
puts "✓".green + " Made server executable"
|
105
|
+
|
106
|
+
# Create or update the Claude Desktop config file
|
107
|
+
config = {}
|
108
|
+
|
109
|
+
if File.exist?(claude_config_file)
|
110
|
+
begin
|
111
|
+
config = JSON.parse(File.read(claude_config_file))
|
112
|
+
rescue JSON::ParserError
|
113
|
+
puts "Error:".red + " Existing config file is not valid JSON. Creating backup and new config."
|
114
|
+
FileUtils.cp(claude_config_file, "#{claude_config_file}.bak")
|
115
|
+
config = {}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Update configuration
|
120
|
+
config["mcpServers"] ||= {}
|
121
|
+
config["mcpServers"]["railsMcpServer"] = {"command" => "ruby", "args" => [server_path]}
|
122
|
+
|
123
|
+
# Write configuration back to file
|
124
|
+
File.write(claude_config_file, JSON.pretty_generate(config))
|
125
|
+
|
126
|
+
puts "✓".green + " Updated Claude Desktop configuration"
|
127
|
+
puts
|
128
|
+
puts "Setup completed!".blue
|
129
|
+
puts "!".yellow + " Please restart Claude Desktop to apply the changes."
|
130
|
+
puts
|
131
|
+
puts "Your Rails MCP Server is now configured at:".blue
|
132
|
+
puts " #{server_path}"
|
133
|
+
puts
|
134
|
+
puts "Using config:".blue
|
135
|
+
puts " Projects file: #{projects_yml}"
|
136
|
+
puts " Log directory: #{rails_mcp_log_dir}"
|
137
|
+
puts
|
138
|
+
puts "To use it in Claude Desktop:".blue
|
139
|
+
puts "1. Restart Claude Desktop"
|
140
|
+
puts "2. Click on the plugin icon in the chat interface"
|
141
|
+
puts "3. Select 'Rails MCP Server' from the list"
|
142
|
+
puts
|
143
|
+
puts "!".yellow + " Don't forget to edit your projects.yml file to add your Rails projects:"
|
144
|
+
puts " #{projects_yml}"
|
145
|
+
puts
|
146
|
+
puts "============================================".blue
|
@@ -0,0 +1,509 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "mcp"
|
4
|
+
require "yaml"
|
5
|
+
require "logger"
|
6
|
+
require "json"
|
7
|
+
require "fileutils"
|
8
|
+
require_relative "rails-mcp-server/version"
|
9
|
+
|
10
|
+
module RailsMcpServer
|
11
|
+
class Error < StandardError; end
|
12
|
+
end
|
13
|
+
|
14
|
+
# rubocop:disable Style/GlobalVars
|
15
|
+
# Initialize configuration
|
16
|
+
def get_config_dir
|
17
|
+
# Use XDG_CONFIG_HOME if set, otherwise use ~/.config
|
18
|
+
xdg_config_home = ENV["XDG_CONFIG_HOME"]
|
19
|
+
if xdg_config_home && !xdg_config_home.empty?
|
20
|
+
File.join(xdg_config_home, "rails-mcp")
|
21
|
+
else
|
22
|
+
File.join(Dir.home, ".config", "rails-mcp")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create config directory if it doesn't exist
|
27
|
+
config_dir = get_config_dir
|
28
|
+
FileUtils.mkdir_p(File.join(config_dir, "log"))
|
29
|
+
|
30
|
+
# Default paths
|
31
|
+
projects_file = File.join(config_dir, "projects.yml")
|
32
|
+
log_file = File.join(config_dir, "log", "rails_mcp_server.log")
|
33
|
+
log_level = :info
|
34
|
+
|
35
|
+
# Parse command-line arguments
|
36
|
+
i = 0
|
37
|
+
while i < ARGV.length
|
38
|
+
case ARGV[i]
|
39
|
+
when "--log-level"
|
40
|
+
log_level = ARGV[i + 1].to_sym
|
41
|
+
i += 2
|
42
|
+
else
|
43
|
+
i += 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Initialize logger
|
48
|
+
$logger = Logger.new(log_file)
|
49
|
+
$logger.level = Logger.const_get(log_level.to_s.upcase)
|
50
|
+
|
51
|
+
# Set a nicer formatter
|
52
|
+
$logger.formatter = proc do |severity, datetime, progname, msg|
|
53
|
+
"[#{datetime.strftime("%Y-%m-%d %H:%M:%S")}] #{severity}: #{msg}\n"
|
54
|
+
end
|
55
|
+
|
56
|
+
def log(level, message)
|
57
|
+
levels = {debug: Logger::DEBUG, info: Logger::INFO, warn: Logger::WARN, error: Logger::ERROR, fatal: Logger::FATAL}
|
58
|
+
log_level = levels[level] || Logger::INFO
|
59
|
+
$logger.add(log_level, message)
|
60
|
+
end
|
61
|
+
|
62
|
+
log(:info, "Starting Rails MCP Server...")
|
63
|
+
log(:info, "Using config directory: #{config_dir}")
|
64
|
+
|
65
|
+
# Create empty projects file if it doesn't exist
|
66
|
+
unless File.exist?(projects_file)
|
67
|
+
log(:info, "Creating empty projects file: #{projects_file}")
|
68
|
+
FileUtils.mkdir_p(File.dirname(projects_file))
|
69
|
+
File.write(projects_file, "# Rails MCP Projects\n# Format: project_name: /path/to/project\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
# Load projects
|
73
|
+
projects_file = File.expand_path(projects_file)
|
74
|
+
projects = {}
|
75
|
+
|
76
|
+
if File.exist?(projects_file)
|
77
|
+
log(:info, "Loading projects from: #{projects_file}")
|
78
|
+
projects = YAML.load_file(projects_file) || {}
|
79
|
+
log(:info, "Loaded #{projects.size} projects: #{projects.keys.join(", ")}")
|
80
|
+
else
|
81
|
+
log(:warn, "Projects file not found: #{projects_file}")
|
82
|
+
end
|
83
|
+
|
84
|
+
# Initialize state
|
85
|
+
$active_project = nil
|
86
|
+
$active_project_path = nil
|
87
|
+
|
88
|
+
# Define MCP server using the mcp-rb DSL
|
89
|
+
name "rails-mcp-server"
|
90
|
+
version RailsMcpServer::VERSION
|
91
|
+
|
92
|
+
# Utility functions for Rails operations
|
93
|
+
def get_directory_structure(path, max_depth: 3, current_depth: 0, prefix: "")
|
94
|
+
return "" if current_depth > max_depth || !File.directory?(path)
|
95
|
+
|
96
|
+
output = ""
|
97
|
+
directories = []
|
98
|
+
files = []
|
99
|
+
|
100
|
+
Dir.foreach(path) do |entry|
|
101
|
+
next if entry == "." || entry == ".."
|
102
|
+
full_path = File.join(path, entry)
|
103
|
+
|
104
|
+
if File.directory?(full_path)
|
105
|
+
directories << entry
|
106
|
+
else
|
107
|
+
files << entry
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
directories.sort.each do |dir|
|
112
|
+
output << "#{prefix}└── #{dir}/\n"
|
113
|
+
full_path = File.join(path, dir)
|
114
|
+
output << get_directory_structure(full_path, max_depth: max_depth,
|
115
|
+
current_depth: current_depth + 1,
|
116
|
+
prefix: "#{prefix} ")
|
117
|
+
end
|
118
|
+
|
119
|
+
files.sort.each do |file|
|
120
|
+
output << "#{prefix}└── #{file}\n"
|
121
|
+
end
|
122
|
+
|
123
|
+
output
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_file_extension(path)
|
127
|
+
case File.extname(path).downcase
|
128
|
+
when ".rb"
|
129
|
+
"ruby"
|
130
|
+
when ".js"
|
131
|
+
"javascript"
|
132
|
+
when ".html", ".erb"
|
133
|
+
"html"
|
134
|
+
when ".css"
|
135
|
+
"css"
|
136
|
+
when ".json"
|
137
|
+
"json"
|
138
|
+
when ".yml", ".yaml"
|
139
|
+
"yaml"
|
140
|
+
else
|
141
|
+
""
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def execute_rails_command(project_path, command)
|
146
|
+
full_command = "cd #{project_path} && bin/rails #{command}"
|
147
|
+
`#{full_command}`
|
148
|
+
end
|
149
|
+
|
150
|
+
def underscore(string)
|
151
|
+
string.gsub("::", "/")
|
152
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
153
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
154
|
+
.tr("-", "_")
|
155
|
+
.downcase
|
156
|
+
end
|
157
|
+
|
158
|
+
# Define tools using the mcp-rb DSL
|
159
|
+
tool "switch_project" do
|
160
|
+
description "Change the active Rails project to interact with a different codebase. Must be called before using other tools. Available projects are defined in the projects.yml configuration file."
|
161
|
+
|
162
|
+
argument :project_name, String, required: true,
|
163
|
+
description: "Name of the project as defined in the projects.yml file (case-sensitive)"
|
164
|
+
|
165
|
+
call do |args|
|
166
|
+
project_name = args[:project_name]
|
167
|
+
|
168
|
+
if projects.key?(project_name)
|
169
|
+
$active_project = project_name
|
170
|
+
$active_project_path = File.expand_path(projects[project_name])
|
171
|
+
log(:info, "Switched to project: #{project_name} at path: #{$active_project_path}")
|
172
|
+
"Switched to project: #{project_name} at path: #{$active_project_path}"
|
173
|
+
else
|
174
|
+
log(:warn, "Project not found: #{project_name}")
|
175
|
+
raise "Project '#{project_name}' not found. Available projects: #{projects.keys.join(", ")}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
tool "get_project_info" do
|
181
|
+
description "Retrieve comprehensive information about the current Rails project, including Rails version, directory structure, API-only status, and overall project organization. Useful for initial project exploration and understanding the codebase structure."
|
182
|
+
|
183
|
+
call do |args|
|
184
|
+
unless $active_project
|
185
|
+
raise "No active project. Please switch to a project first."
|
186
|
+
end
|
187
|
+
|
188
|
+
# Get additional project information
|
189
|
+
gemfile_path = File.join($active_project_path, "Gemfile")
|
190
|
+
gemfile_content = File.exist?(gemfile_path) ? File.read(gemfile_path) : "Gemfile not found"
|
191
|
+
|
192
|
+
# Get Rails version
|
193
|
+
rails_version = gemfile_content.match(/gem ['"]rails['"],\s*['"](.+?)['"]/)&.captures&.first || "Unknown"
|
194
|
+
|
195
|
+
# Check if it's an API-only app
|
196
|
+
config_application_path = File.join($active_project_path, "config", "application.rb")
|
197
|
+
is_api_only = File.exist?(config_application_path) &&
|
198
|
+
File.read(config_application_path).include?("config.api_only = true")
|
199
|
+
|
200
|
+
log(:info, "Project info: Rails v#{rails_version}, API-only: #{is_api_only}")
|
201
|
+
|
202
|
+
<<~INFO
|
203
|
+
Current project: #{$active_project}
|
204
|
+
Path: #{$active_project_path}
|
205
|
+
Rails version: #{rails_version}
|
206
|
+
API only: #{is_api_only ? "Yes" : "No"}
|
207
|
+
|
208
|
+
Project structure:
|
209
|
+
#{get_directory_structure($active_project_path, max_depth: 2)}
|
210
|
+
INFO
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
tool "list_files" do
|
215
|
+
description "List files in the Rails project matching specific criteria. Use this to explore project directories or locate specific file types. If no parameters are provided, lists files in the project root."
|
216
|
+
|
217
|
+
argument :directory, String, required: false,
|
218
|
+
description: "Directory path relative to the project root (e.g., 'app/models', 'config'). Leave empty to list files at the root."
|
219
|
+
|
220
|
+
argument :pattern, String, required: false,
|
221
|
+
description: "File pattern using glob syntax (e.g., '*.rb' for Ruby files, '*.erb' for ERB templates, '*_controller.rb' for controllers)"
|
222
|
+
|
223
|
+
call do |args|
|
224
|
+
unless $active_project
|
225
|
+
raise "No active project. Please switch to a project first."
|
226
|
+
end
|
227
|
+
|
228
|
+
directory = args[:directory] || ""
|
229
|
+
pattern = args[:pattern] || "*"
|
230
|
+
|
231
|
+
full_path = File.join($active_project_path, directory)
|
232
|
+
|
233
|
+
unless File.directory?(full_path)
|
234
|
+
raise "Directory '#{directory}' not found in the project."
|
235
|
+
end
|
236
|
+
|
237
|
+
# Use Dir.glob to get matching files
|
238
|
+
files = Dir.glob(File.join(full_path, pattern))
|
239
|
+
.map { |f| f.sub("#{$active_project_path}/", "") }
|
240
|
+
.sort # rubocop:disable Performance/ChainArrayAllocation
|
241
|
+
|
242
|
+
log(:debug, "Found #{files.size} files matching pattern")
|
243
|
+
|
244
|
+
"Files in #{directory.empty? ? "project root" : directory} matching '#{pattern}':\n\n#{files.join("\n")}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
tool "get_file" do
|
249
|
+
description "Retrieve the complete content of a specific file with syntax highlighting. Use this to examine implementation details, configurations, or any text file in the project."
|
250
|
+
|
251
|
+
argument :path, String, required: true,
|
252
|
+
description: "File path relative to the project root (e.g., 'app/models/user.rb', 'config/routes.rb'). Use list_files first if you're not sure about the exact path."
|
253
|
+
|
254
|
+
call do |args|
|
255
|
+
unless $active_project
|
256
|
+
raise "No active project. Please switch to a project first."
|
257
|
+
end
|
258
|
+
|
259
|
+
path = args[:path]
|
260
|
+
full_path = File.join($active_project_path, path)
|
261
|
+
|
262
|
+
unless File.exist?(full_path)
|
263
|
+
raise "File '#{path}' not found in the project."
|
264
|
+
end
|
265
|
+
|
266
|
+
content = File.read(full_path)
|
267
|
+
log(:debug, "Read file: #{path} (#{content.size} bytes)")
|
268
|
+
|
269
|
+
"File: #{path}\n\n```#{get_file_extension(path)}\n#{content}\n```"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
tool "get_routes" do
|
274
|
+
description "Retrieve all HTTP routes defined in the Rails application with their associated controllers and actions. Equivalent to running 'rails routes' command. This helps understand the API endpoints or page URLs available in the application."
|
275
|
+
|
276
|
+
call do |args|
|
277
|
+
unless $active_project
|
278
|
+
raise "No active project. Please switch to a project first."
|
279
|
+
end
|
280
|
+
|
281
|
+
# Execute the Rails routes command
|
282
|
+
routes_output = execute_rails_command($active_project_path, "routes")
|
283
|
+
log(:debug, "Routes command completed, output size: #{routes_output.size} bytes")
|
284
|
+
|
285
|
+
"Rails Routes:\n\n```\n#{routes_output}\n```"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
tool "get_models" do
|
290
|
+
description "Retrieve detailed information about Active Record models in the project. When called without parameters, lists all model files. When a specific model is specified, returns its schema, associations (has_many, belongs_to, has_one), and complete source code."
|
291
|
+
|
292
|
+
argument :model_name, String, required: false,
|
293
|
+
description: "Class name of a specific model to get detailed information for (e.g., 'User', 'Product'). Use CamelCase, not snake_case. If omitted, returns a list of all models."
|
294
|
+
|
295
|
+
call do |args|
|
296
|
+
unless $active_project
|
297
|
+
raise "No active project. Please switch to a project first."
|
298
|
+
end
|
299
|
+
|
300
|
+
model_name = args[:model_name]
|
301
|
+
|
302
|
+
if model_name
|
303
|
+
log(:info, "Getting info for specific model: #{model_name}")
|
304
|
+
|
305
|
+
# Check if the model file exists
|
306
|
+
model_file = File.join($active_project_path, "app", "models", "#{underscore(model_name)}.rb")
|
307
|
+
unless File.exist?(model_file)
|
308
|
+
log(:warn, "Model file not found: #{model_name}")
|
309
|
+
raise "Model '#{model_name}' not found."
|
310
|
+
end
|
311
|
+
|
312
|
+
log(:debug, "Reading model file: #{model_file}")
|
313
|
+
|
314
|
+
# Get the model file content
|
315
|
+
model_content = File.read(model_file)
|
316
|
+
|
317
|
+
# Try to get schema information
|
318
|
+
log(:debug, "Executing Rails runner to get schema information")
|
319
|
+
schema_info = execute_rails_command(
|
320
|
+
$active_project_path,
|
321
|
+
"runner \"puts #{model_name}.column_names\""
|
322
|
+
)
|
323
|
+
|
324
|
+
# Try to get associations
|
325
|
+
associations = []
|
326
|
+
if model_content.include?("has_many")
|
327
|
+
has_many = model_content.scan(/has_many\s+:(\w+)/).flatten
|
328
|
+
associations << "Has many: #{has_many.join(", ")}" unless has_many.empty?
|
329
|
+
end
|
330
|
+
|
331
|
+
if model_content.include?("belongs_to")
|
332
|
+
belongs_to = model_content.scan(/belongs_to\s+:(\w+)/).flatten
|
333
|
+
associations << "Belongs to: #{belongs_to.join(", ")}" unless belongs_to.empty?
|
334
|
+
end
|
335
|
+
|
336
|
+
if model_content.include?("has_one")
|
337
|
+
has_one = model_content.scan(/has_one\s+:(\w+)/).flatten
|
338
|
+
associations << "Has one: #{has_one.join(", ")}" unless has_one.empty?
|
339
|
+
end
|
340
|
+
|
341
|
+
log(:debug, "Found #{associations.size} associations for model: #{model_name}")
|
342
|
+
|
343
|
+
# Format the output
|
344
|
+
<<~INFO
|
345
|
+
Model: #{model_name}
|
346
|
+
|
347
|
+
Schema:
|
348
|
+
#{schema_info}
|
349
|
+
|
350
|
+
Associations:
|
351
|
+
#{associations.empty? ? "None found" : associations.join("\n")}
|
352
|
+
|
353
|
+
Model Definition:
|
354
|
+
```ruby
|
355
|
+
#{model_content}
|
356
|
+
```
|
357
|
+
INFO
|
358
|
+
else
|
359
|
+
log(:info, "Listing all models")
|
360
|
+
|
361
|
+
# List all models
|
362
|
+
models_dir = File.join($active_project_path, "app", "models")
|
363
|
+
unless File.directory?(models_dir)
|
364
|
+
raise "Models directory not found."
|
365
|
+
end
|
366
|
+
|
367
|
+
# Get all .rb files in the models directory and its subdirectories
|
368
|
+
model_files = Dir.glob(File.join(models_dir, "**", "*.rb"))
|
369
|
+
.map { |f| f.sub("#{models_dir}/", "").sub(/\.rb$/, "") }
|
370
|
+
.sort # rubocop:disable Performance/ChainArrayAllocation
|
371
|
+
|
372
|
+
log(:debug, "Found #{model_files.size} model files")
|
373
|
+
|
374
|
+
"Models in the project:\n\n#{model_files.join("\n")}"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
tool "get_schema" do
|
380
|
+
description "Retrieve database schema information for the Rails application. Without parameters, returns all tables and the complete schema.rb. With a table name, returns detailed column information including data types, constraints, and foreign keys for that specific table."
|
381
|
+
|
382
|
+
argument :table_name, String, required: false,
|
383
|
+
description: "Database table name to get detailed schema information for (e.g., 'users', 'products'). Use snake_case, plural form. If omitted, returns complete database schema."
|
384
|
+
|
385
|
+
call do |args|
|
386
|
+
unless $active_project
|
387
|
+
raise "No active project. Please switch to a project first."
|
388
|
+
end
|
389
|
+
|
390
|
+
table_name = args[:table_name]
|
391
|
+
|
392
|
+
if table_name
|
393
|
+
log(:info, "Getting schema for table: #{table_name}")
|
394
|
+
|
395
|
+
# Execute the Rails schema command for a specific table
|
396
|
+
schema_output = execute_rails_command(
|
397
|
+
$active_project_path,
|
398
|
+
"runner \"require 'active_record'; puts ActiveRecord::Base.connection.columns('#{table_name}').map{|c| [c.name, c.type, c.null, c.default].inspect}.join('\n')\""
|
399
|
+
)
|
400
|
+
|
401
|
+
if schema_output.strip.empty?
|
402
|
+
raise "Table '#{table_name}' not found or has no columns."
|
403
|
+
end
|
404
|
+
|
405
|
+
# Parse the column information
|
406
|
+
columns = schema_output.strip.split("\n").map do |column_info|
|
407
|
+
eval(column_info) # This is safe because we're generating the string ourselves # rubocop:disable Security/Eval
|
408
|
+
end
|
409
|
+
|
410
|
+
# Format the output
|
411
|
+
formatted_columns = columns.map do |name, type, nullable, default|
|
412
|
+
"#{name} (#{type})#{nullable ? ", nullable" : ""}#{default ? ", default: #{default}" : ""}"
|
413
|
+
end
|
414
|
+
|
415
|
+
output = <<~SCHEMA
|
416
|
+
Table: #{table_name}
|
417
|
+
|
418
|
+
Columns:
|
419
|
+
#{formatted_columns.join("\n")}
|
420
|
+
SCHEMA
|
421
|
+
|
422
|
+
# Try to get foreign keys
|
423
|
+
begin
|
424
|
+
fk_output = execute_rails_command(
|
425
|
+
$active_project_path,
|
426
|
+
"runner \"require 'active_record'; puts ActiveRecord::Base.connection.foreign_keys('#{table_name}').map{|fk| [fk.from_table, fk.to_table, fk.column, fk.primary_key].inspect}.join('\n')\""
|
427
|
+
)
|
428
|
+
|
429
|
+
unless fk_output.strip.empty?
|
430
|
+
foreign_keys = fk_output.strip.split("\n").map do |fk_info|
|
431
|
+
eval(fk_info) # This is safe because we're generating the string ourselves # rubocop:disable Security/Eval
|
432
|
+
end
|
433
|
+
|
434
|
+
formatted_fks = foreign_keys.map do |from_table, to_table, column, primary_key|
|
435
|
+
"#{column} -> #{to_table}.#{primary_key}"
|
436
|
+
end
|
437
|
+
|
438
|
+
output += <<~FK
|
439
|
+
|
440
|
+
Foreign Keys:
|
441
|
+
#{formatted_fks.join("\n")}
|
442
|
+
FK
|
443
|
+
end
|
444
|
+
rescue => e
|
445
|
+
log(:warn, "Error fetching foreign keys: #{e.message}")
|
446
|
+
end
|
447
|
+
|
448
|
+
output
|
449
|
+
else
|
450
|
+
log(:info, "Getting full schema")
|
451
|
+
|
452
|
+
# Execute the Rails schema:dump command
|
453
|
+
# First, check if we need to create the schema file
|
454
|
+
schema_file = File.join($active_project_path, "db", "schema.rb")
|
455
|
+
unless File.exist?(schema_file)
|
456
|
+
log(:info, "Schema file not found, attempting to generate it")
|
457
|
+
execute_rails_command($active_project_path, "db:schema:dump")
|
458
|
+
end
|
459
|
+
|
460
|
+
if File.exist?(schema_file)
|
461
|
+
# Read the schema file
|
462
|
+
schema_content = File.read(schema_file)
|
463
|
+
|
464
|
+
# Try to get table list
|
465
|
+
tables_output = execute_rails_command(
|
466
|
+
$active_project_path,
|
467
|
+
"runner \"require 'active_record'; puts ActiveRecord::Base.connection.tables.sort.join('\n')\""
|
468
|
+
)
|
469
|
+
|
470
|
+
tables = tables_output.strip.split("\n")
|
471
|
+
|
472
|
+
<<~SCHEMA
|
473
|
+
Database Schema
|
474
|
+
|
475
|
+
Tables:
|
476
|
+
#{tables.join("\n")}
|
477
|
+
|
478
|
+
Schema Definition:
|
479
|
+
```ruby
|
480
|
+
#{schema_content}
|
481
|
+
```
|
482
|
+
SCHEMA
|
483
|
+
|
484
|
+
else
|
485
|
+
# If we can't get the schema file, try to get the table list
|
486
|
+
tables_output = execute_rails_command(
|
487
|
+
$active_project_path,
|
488
|
+
"runner \"require 'active_record'; puts ActiveRecord::Base.connection.tables.sort.join('\n')\""
|
489
|
+
)
|
490
|
+
|
491
|
+
if tables_output.strip.empty?
|
492
|
+
raise "Could not retrieve schema information. Try running 'rails db:schema:dump' in your project first."
|
493
|
+
end
|
494
|
+
|
495
|
+
tables = tables_output.strip.split("\n")
|
496
|
+
|
497
|
+
<<~SCHEMA
|
498
|
+
Database Schema
|
499
|
+
|
500
|
+
Tables:
|
501
|
+
#{tables.join("\n")}
|
502
|
+
|
503
|
+
Note: Full schema definition is not available. Run 'rails db:schema:dump' to generate the schema.rb file.
|
504
|
+
SCHEMA
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
# rubocop:enable Style/GlobalVars
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails-mcp-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mario Alberto Chávez Cárdenas
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-03-20 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: mcp-rb
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: logger
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: standard
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
description: A Ruby implementation of Model Context Protocol server for Rails projects
|
55
|
+
email:
|
56
|
+
- mario.chavez@gmail.com
|
57
|
+
executables:
|
58
|
+
- rails-mcp-server
|
59
|
+
- rails-mcp-setup-claude
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- LICENSE.txt
|
64
|
+
- README.md
|
65
|
+
- exe/rails-mcp-server
|
66
|
+
- exe/rails-mcp-setup-claude
|
67
|
+
- lib/rails-mcp-server/version.rb
|
68
|
+
- lib/rails_mcp_server.rb
|
69
|
+
homepage: https://github.com/maquina-app/rails-mcp-server
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata:
|
73
|
+
allowed_push_host: https://rubygems.org
|
74
|
+
homepage_uri: https://github.com/maquina-app/rails-mcp-server
|
75
|
+
source_code_uri: https://github.com/maquina-app/rails-mcp-server
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 2.5.0
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubygems_version: 3.6.2
|
91
|
+
specification_version: 4
|
92
|
+
summary: MCP server for Rails projects
|
93
|
+
test_files: []
|