ask-rails-harness-mcp 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 10bdd0fdd7f2b5ad18f3953a8d87474c5df24a4c21ae3e5bfbd9945c2e21bb67
4
+ data.tar.gz: 50fb399f513255f82626819688d5afc898b6fcab709adf7126a5a93b57c7176a
5
+ SHA512:
6
+ metadata.gz: '0383729111ae526ea38c8a7a12e13310bb4c705c876f55b2a4b0ba2afacc65ff330be644bd5e134dc4fc77568200092f48546e2f8ed81663c697742782634f96'
7
+ data.tar.gz: 5c1791fdc69510e5fd48acf49b764a73129b5bbe37e0cf47877ef147386191bf5df3b7bdcf612760e6c0f7f63e3d782fab859b81358a99446ebe8d274250fc7c
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ ## [0.2.0] — 2026-07-23
2
+
3
+ ### Added
4
+
5
+ - **stdio transport** — `Ask::Rails::MCP.start` boots the Rails app and starts a stdio MCP server. Run `ask-rails-mcp` from your Rails app root.
6
+ - **`bin/ask-rails-mcp`** — CLI entry point for local development. No web server needed.
7
+
8
+ ## [0.1.0] — 2026-07-23
9
+
10
+ ### Added
11
+
12
+ - **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.
13
+ - **JSON-RPC handler** — `Ask::Rails::MCP.handle` processes MCP messages (`initialize`, `tools/list`, `tools/call`, `ping`) and returns proper JSON-RPC responses.
14
+ - **Tool definitions** — All 9 tools are automatically registered with their names, descriptions, and JSON Schema input definitions.
15
+ - **Auth** — The MCP endpoint respects `Ask::Rails::Auth.check` for authentication.
16
+
17
+ ### Usage
18
+
19
+ Mount in `config/routes.rb`:
20
+
21
+ ```ruby
22
+ Rails.application.routes.draw do
23
+ # Protect behind your auth
24
+ authenticate :user, ->(u) { u.admin? } do
25
+ post "ask/mcp", to: "ask/rails/mcp#handle"
26
+ end
27
+ end
28
+ ```
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # ask-rails-harness-mcp
2
+
3
+ MCP server for Rails app introspection. Exposes all [ask-rails-harness](https://github.com/ask-rb/ask-rails-harness) 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-harness uses internally.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ bundle add ask-rails-harness-mcp
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Option 1: stdio (local development — recommended)
16
+
17
+ Run from your Rails app root:
18
+
19
+ ```bash
20
+ cd my-rails-app
21
+ ask-rails-harness-mcp
22
+ ```
23
+
24
+ This boots your Rails app and starts an MCP stdio server. Configure in your agent's MCP config:
25
+
26
+ ```json
27
+ {
28
+ "mcp": {
29
+ "servers": {
30
+ "ask-rails-harness-mcp": {
31
+ "type": "stdio",
32
+ "command": "ask-rails-harness-mcp",
33
+ "args": []
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### Option 2: HTTP endpoint (remote/production)
41
+
42
+ Mount in `config/routes.rb` behind your existing auth:
43
+
44
+ ```ruby
45
+ Rails.application.routes.draw do
46
+ authenticate :user, ->(u) { u.admin? } do
47
+ post "ask/mcp", to: "ask/rails/mcp#handle"
48
+ end
49
+ end
50
+ ```
51
+
52
+ Then configure any MCP-compatible agent:
53
+
54
+ ```json
55
+ {
56
+ "mcp": {
57
+ "servers": {
58
+ "ask-rails-harness-mcp": {
59
+ "type": "http",
60
+ "url": "https://myapp.com/ask/mcp"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ The agent discovers 9 tools automatically:
68
+
69
+ | Tool | What it does |
70
+ |---|---|
71
+ | `schema_graph` | Full schema introspection — all models, tables, columns, associations, validations |
72
+ | `query_database` | Read-only SQL queries with safety guards |
73
+ | `read_model` | Introspect a single ActiveRecord model |
74
+ | `route_inspector` | Parsed route table with filters |
75
+ | `read_log` | Read Rails log files with level/search filtering |
76
+ | `search_codebase` | Full-text grep search |
77
+ | `read_file` | Read any file from `Rails.root` |
78
+ | `run_command` | Run shell commands in the app root |
79
+ | `read_routes` | Read the raw `config/routes.rb` |
80
+
81
+ ### Authentication
82
+
83
+ The MCP endpoint uses the same `Ask::Rails::Harness::Auth` system as the chat UI:
84
+
85
+ ```ruby
86
+ Ask::Rails::Harness::Auth.check = -> {
87
+ redirect_to main_app.login_path unless current_user&.admin?
88
+ }
89
+ ```
90
+
91
+ ### Safety
92
+
93
+ All ask-rails-harness safety features apply automatically:
94
+ - **Permissions** — access modes (`:read_only`, `:ask_before_changes`, `:full_access`)
95
+ - **Command allowlists** — `allowed_commands` / `denied_commands` for `RunCommand`
96
+ - **Write guards** — `INSERT`/`UPDATE`/`DELETE` blocked by `QueryDatabase`
97
+ - **Audit log** — every tool call recorded in `ask_audit_logs`
98
+
99
+ ## Development
100
+
101
+ ```bash
102
+ bundle exec rake test
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "ask-rails-harness-mcp"
5
+
6
+ Ask::Rails::MCP.start
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ask
4
+ module Rails
5
+ module MCP
6
+ VERSION = "0.2.0"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ask/rails/harness"
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-harness tools wrapped as MCP tool instances.
14
+ def tools
15
+ @tools ||= Ask::Rails::Harness::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-harness-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
+ # Start the MCP stdio server, auto-loading the Rails app.
86
+ #
87
+ # $ ask-rails-harness-mcp
88
+ #
89
+ # The server will listen for JSON-RPC messages on stdin and write
90
+ # responses to stdout — the standard MCP stdio transport. Register
91
+ # this executable as an MCP server in your client configuration:
92
+ #
93
+ # "mcp": {
94
+ # "servers": {
95
+ # "ask-rails-harness-mcp": {
96
+ # "type": "stdio",
97
+ # "command": "ask-rails-harness-mcp",
98
+ # "args": []
99
+ # }
100
+ # }
101
+ # }
102
+ def start
103
+ load_rails_app
104
+
105
+ Ask::MCP::Server.start_stdio(
106
+ name: "ask-rails-harness-mcp",
107
+ tools: tools,
108
+ capabilities: { tools: {} },
109
+ debug: ENV["DEBUG"] == "1"
110
+ )
111
+ end
112
+
113
+ # Get or clear the cache (useful in tests).
114
+ def reset!
115
+ @tools = nil
116
+ @tool_server = nil
117
+ @initialized = false
118
+ end
119
+
120
+ private
121
+
122
+ def load_rails_app
123
+ return if defined?(::Rails) && ::Rails.application
124
+ require File.expand_path("config/environment")
125
+ rescue LoadError
126
+ warn "ask-rails-harness-mcp: must be run from your Rails app root (config/environment.rb not found)"
127
+ exit 1
128
+ end
129
+
130
+ def success_response(id, result)
131
+ { "jsonrpc" => "2.0", "id" => id, "result" => deep_stringify_keys(result) }
132
+ end
133
+
134
+ def error_response(id, code, message)
135
+ { "jsonrpc" => "2.0", "id" => id, "error" => { "code" => code, "message" => message } }
136
+ end
137
+
138
+ def deep_stringify_keys(obj)
139
+ case obj
140
+ when Hash then obj.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify_keys(v) }
141
+ when Array then obj.map { |v| deep_stringify_keys(v) }
142
+ else obj
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ask/rails/mcp"
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ask-rails-harness-mcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.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-harness
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0.1'
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-harness 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
+ - ask-rails-harness-mcp
105
+ extensions: []
106
+ extra_rdoc_files: []
107
+ files:
108
+ - CHANGELOG.md
109
+ - README.md
110
+ - bin/ask-rails-harness-mcp
111
+ - lib/ask-rails-harness-mcp.rb
112
+ - lib/ask/rails/mcp.rb
113
+ - lib/ask/rails/mcp/version.rb
114
+ homepage: https://github.com/ask-rb/ask-rails-harness-mcp
115
+ licenses:
116
+ - MIT
117
+ metadata:
118
+ homepage_uri: https://github.com/ask-rb/ask-rails-harness-mcp
119
+ source_code_uri: https://github.com/ask-rb/ask-rails-harness-mcp
120
+ changelog_uri: https://github.com/ask-rb/ask-rails-harness-mcp/blob/master/CHANGELOG.md
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '3.2'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubygems_version: 4.0.3
136
+ specification_version: 4
137
+ summary: MCP server for Rails app introspection — exposes ask-rails-harness tools
138
+ over the Model Context Protocol
139
+ test_files: []