ask-rails-mcp 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 +21 -0
- data/README.md +80 -0
- data/lib/ask/rails/mcp/version.rb +9 -0
- data/lib/ask/rails/mcp.rb +112 -0
- data/lib/ask-rails-mcp.rb +3 -0
- metadata +137 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0735b5cd8de91ba218011330ce945aaf843cb2b84d1a98e382b54cbe3f7ef51f
|
|
4
|
+
data.tar.gz: 6cec7455ddc917a6b3482fe5cb9e72c2ffefd168f3f1310b93fbf6570ad0ece3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 972281e304211d8939710ef5c6167313fcd0945b7d30204642d5be58f6c8f7c4f41fc3ea1159d4c04cb013059ad7b803fe455d086175e5383327e90709a1f970
|
|
7
|
+
data.tar.gz: 61f31bccee0eb59227266ea2e23637fe4b5f616db9d4a6d40d6b2df56c3251a951e8c31294503ba5b3209e8c59635a78c897852b125c93046371ef1a831ab24e
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## [0.1.0] — 2026-07-23
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **MCP endpoint** — Mount an MCP server in your Rails app that exposes all ask-rails tools (SchemaGraph, QueryDatabase, ReadModel, RouteInspector, ReadLog, SearchCodebase, ReadFile, RunCommand, ReadRoutes) over the Model Context Protocol.
|
|
6
|
+
- **JSON-RPC handler** — `Ask::Rails::MCP.handle` processes MCP messages (`initialize`, `tools/list`, `tools/call`, `ping`) and returns proper JSON-RPC responses.
|
|
7
|
+
- **Tool definitions** — All 9 tools are automatically registered with their names, descriptions, and JSON Schema input definitions.
|
|
8
|
+
- **Auth** — The MCP endpoint respects `Ask::Rails::Auth.check` for authentication.
|
|
9
|
+
|
|
10
|
+
### Usage
|
|
11
|
+
|
|
12
|
+
Mount in `config/routes.rb`:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
Rails.application.routes.draw do
|
|
16
|
+
# Protect behind your auth
|
|
17
|
+
authenticate :user, ->(u) { u.admin? } do
|
|
18
|
+
post "ask/mcp", to: "ask/rails/mcp#handle"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
```
|
data/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# ask-rails-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for Rails app introspection. Exposes all [ask-rails](https://github.com/ask-rb/ask-rails) tools over the [Model Context Protocol](https://modelcontextprotocol.io/).
|
|
4
|
+
|
|
5
|
+
Coding agents like Claude Code, Cursor, or any MCP-compatible client can connect to inspect your Rails schema, query your database, read models, and more — all through the same tools ask-rails uses internally.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bundle add ask-rails-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Mount in `config/routes.rb` behind your existing auth:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
Rails.application.routes.draw do
|
|
19
|
+
authenticate :user, ->(u) { u.admin? } do
|
|
20
|
+
post "ask/mcp", to: "ask/rails/mcp#handle"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then configure any MCP-compatible agent to connect to your app. For example, in Claude Code's `~/.claude/settings.json`:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcp": {
|
|
30
|
+
"servers": {
|
|
31
|
+
"ask-rails-mcp": {
|
|
32
|
+
"type": "url",
|
|
33
|
+
"url": "https://myapp.com/ask/mcp"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The agent discovers 9 tools automatically:
|
|
41
|
+
|
|
42
|
+
| Tool | What it does |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `schema_graph` | Full schema introspection — all models, tables, columns, associations, validations |
|
|
45
|
+
| `query_database` | Read-only SQL queries with safety guards |
|
|
46
|
+
| `read_model` | Introspect a single ActiveRecord model |
|
|
47
|
+
| `route_inspector` | Parsed route table with filters |
|
|
48
|
+
| `read_log` | Read Rails log files with level/search filtering |
|
|
49
|
+
| `search_codebase` | Full-text grep search |
|
|
50
|
+
| `read_file` | Read any file from `Rails.root` |
|
|
51
|
+
| `run_command` | Run shell commands in the app root |
|
|
52
|
+
| `read_routes` | Read the raw `config/routes.rb` |
|
|
53
|
+
|
|
54
|
+
### Authentication
|
|
55
|
+
|
|
56
|
+
The MCP endpoint uses the same `Ask::Rails::Auth` system as the chat UI:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
Ask::Rails::Auth.check = -> {
|
|
60
|
+
redirect_to main_app.login_path unless current_user&.admin?
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Safety
|
|
65
|
+
|
|
66
|
+
All ask-rails safety features apply automatically:
|
|
67
|
+
- **Permissions** — access modes (`:read_only`, `:ask_before_changes`, `:full_access`)
|
|
68
|
+
- **Command allowlists** — `allowed_commands` / `denied_commands` for `RunCommand`
|
|
69
|
+
- **Write guards** — `INSERT`/`UPDATE`/`DELETE` blocked by `QueryDatabase`
|
|
70
|
+
- **Audit log** — every tool call recorded in `ask_audit_logs`
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
bundle exec rake test
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ask/rails"
|
|
4
|
+
require "ask/mcp"
|
|
5
|
+
require_relative "mcp/version"
|
|
6
|
+
|
|
7
|
+
module Ask
|
|
8
|
+
module Rails
|
|
9
|
+
module MCP
|
|
10
|
+
PROTOCOL_VERSION = "2025-06-18"
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# All ask-rails tools wrapped as MCP tool instances.
|
|
14
|
+
def tools
|
|
15
|
+
@tools ||= Ask::Rails::CORE_RAILS_TOOLS.map(&:new)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Tool server adapter that dispatches tools/call requests.
|
|
19
|
+
def tool_server
|
|
20
|
+
@tool_server ||= Ask::MCP::Adapters::ToolServer.new(tools)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Handle a raw JSON-RPC message string.
|
|
24
|
+
# Returns a Hash suitable for JSON serialization.
|
|
25
|
+
def handle(json_string)
|
|
26
|
+
msg = JSON.parse(json_string)
|
|
27
|
+
process_message(msg)
|
|
28
|
+
rescue JSON::ParserError => e
|
|
29
|
+
error_response(nil, -32700, "Parse error: #{e.message}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Handle a parsed JSON-RPC message hash.
|
|
33
|
+
def process_message(msg)
|
|
34
|
+
method_name = msg["method"]
|
|
35
|
+
id = msg["id"]
|
|
36
|
+
params = msg["params"] || {}
|
|
37
|
+
has_id = msg.key?("id")
|
|
38
|
+
|
|
39
|
+
case method_name
|
|
40
|
+
when "initialize"
|
|
41
|
+
handle_initialize(id, params)
|
|
42
|
+
when "notifications/initialized"
|
|
43
|
+
@initialized = true
|
|
44
|
+
nil # Notification — no response
|
|
45
|
+
when "tools/list"
|
|
46
|
+
@initialized ? handle_tools_list(id) : error_response(id, -32000, "Server not initialized")
|
|
47
|
+
when "tools/call"
|
|
48
|
+
@initialized ? handle_tool_call(id, params) : error_response(id, -32000, "Server not initialized")
|
|
49
|
+
when "ping"
|
|
50
|
+
has_id ? success_response(id, {}) : nil
|
|
51
|
+
else
|
|
52
|
+
has_id ? error_response(id, -32601, "Method not found: #{method_name}") : nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Process an initialize request.
|
|
57
|
+
def handle_initialize(id, params)
|
|
58
|
+
@initialized = true
|
|
59
|
+
client_version = params["protocolVersion"] || PROTOCOL_VERSION
|
|
60
|
+
success_response(id, {
|
|
61
|
+
"protocolVersion" => client_version,
|
|
62
|
+
"capabilities" => { "tools" => {} },
|
|
63
|
+
"serverInfo" => {
|
|
64
|
+
"name" => "ask-rails-mcp",
|
|
65
|
+
"version" => Ask::Rails::MCP::VERSION
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Process a tools/list request.
|
|
71
|
+
def handle_tools_list(id)
|
|
72
|
+
defs = tool_server.definitions
|
|
73
|
+
success_response(id, { tools: defs })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Process a tools/call request.
|
|
77
|
+
def handle_tool_call(id, params)
|
|
78
|
+
tool_name = params["name"]
|
|
79
|
+
arguments = params["arguments"] || {}
|
|
80
|
+
|
|
81
|
+
result = tool_server.call(tool_name, arguments)
|
|
82
|
+
success_response(id, result)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Get or clear the cache (useful in tests).
|
|
86
|
+
def reset!
|
|
87
|
+
@tools = nil
|
|
88
|
+
@tool_server = nil
|
|
89
|
+
@initialized = false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def success_response(id, result)
|
|
95
|
+
{ "jsonrpc" => "2.0", "id" => id, "result" => deep_stringify_keys(result) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def error_response(id, code, message)
|
|
99
|
+
{ "jsonrpc" => "2.0", "id" => id, "error" => { "code" => code, "message" => message } }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def deep_stringify_keys(obj)
|
|
103
|
+
case obj
|
|
104
|
+
when Hash then obj.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify_keys(v) }
|
|
105
|
+
when Array then obj.map { |v| deep_stringify_keys(v) }
|
|
106
|
+
else obj
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ask-rails-mcp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kaka Ruto
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ask-rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.10'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.10'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: ask-mcp
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rails
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.25'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.25'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: mocha
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '2.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '2.0'
|
|
96
|
+
description: |
|
|
97
|
+
Mount an MCP endpoint in your Rails app that exposes SchemaGraph, QueryDatabase,
|
|
98
|
+
ReadModel, RouteInspector, and all other ask-rails tools as MCP tools.
|
|
99
|
+
Coding agents like Claude Code, Cursor, or any MCP-compatible client can
|
|
100
|
+
connect to inspect your schema, query your database, read models, and more.
|
|
101
|
+
email:
|
|
102
|
+
- kaka@myrrlabs.com
|
|
103
|
+
executables: []
|
|
104
|
+
extensions: []
|
|
105
|
+
extra_rdoc_files: []
|
|
106
|
+
files:
|
|
107
|
+
- CHANGELOG.md
|
|
108
|
+
- README.md
|
|
109
|
+
- lib/ask-rails-mcp.rb
|
|
110
|
+
- lib/ask/rails/mcp.rb
|
|
111
|
+
- lib/ask/rails/mcp/version.rb
|
|
112
|
+
homepage: https://github.com/ask-rb/ask-rails-mcp
|
|
113
|
+
licenses:
|
|
114
|
+
- MIT
|
|
115
|
+
metadata:
|
|
116
|
+
homepage_uri: https://github.com/ask-rb/ask-rails-mcp
|
|
117
|
+
source_code_uri: https://github.com/ask-rb/ask-rails-mcp
|
|
118
|
+
changelog_uri: https://github.com/ask-rb/ask-rails-mcp/blob/master/CHANGELOG.md
|
|
119
|
+
rdoc_options: []
|
|
120
|
+
require_paths:
|
|
121
|
+
- lib
|
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '3.2'
|
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
requirements: []
|
|
133
|
+
rubygems_version: 4.0.3
|
|
134
|
+
specification_version: 4
|
|
135
|
+
summary: MCP server for Rails app introspection — exposes ask-rails tools over the
|
|
136
|
+
Model Context Protocol
|
|
137
|
+
test_files: []
|