mcp_on_ruby 2.0.1 → 2.0.2
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +36 -5
- data/lib/generators/mcp_on_ruby/expose/expose_generator.rb +8 -1
- data/lib/mcp_on_ruby/result.rb +15 -2
- data/lib/mcp_on_ruby/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a99b9f1fff3f7a9639c85c82826690264ca0a2032321c227a3424719d73fbdd5
|
|
4
|
+
data.tar.gz: a977652f5dd0e15d31142a1561f9ab9302a1f34abcfc21a759d5b4ce3600b460
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b8dda36e0e1a44f7b67cbb5f1bcbfe91065ff6304c60682a289f524187532a625e1e691c99ef5bd71424ee19f35a2c9151561ffdfe53b34c1aa56919956e25e
|
|
7
|
+
data.tar.gz: e484ae0cee070df784e3621e63d78b982b5bd70bcd02d566f3a74127781c026a0bf683a271814936118ce67416879fd0d4bbf596646d8952d5e61c5ec3e5eea8
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Entries from 2.0.0 onward
|
|
6
6
|
are generated by [release-please](https://github.com/googleapis/release-please) from Conventional Commit PR titles.
|
|
7
7
|
|
|
8
|
+
## [2.0.2](https://github.com/nagstler/mcp_on_ruby/compare/v2.0.1...v2.0.2) (2026-10-31)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* leave belongs_to foreign keys out of scaffolded params ([#37](https://github.com/nagstler/mcp_on_ruby/issues/37)) ([7b7ec5b](https://github.com/nagstler/mcp_on_ruby/commit/7b7ec5bbe40f0dfeec88da94c9deef6de90167bc))
|
|
14
|
+
* report redirects to a sign-in page as not authenticated ([#38](https://github.com/nagstler/mcp_on_ruby/issues/38)) ([949a340](https://github.com/nagstler/mcp_on_ruby/commit/949a34059e925bdad17586cf5bfdab0a68aca031))
|
|
15
|
+
|
|
8
16
|
## [2.0.1](https://github.com/nagstler/mcp_on_ruby/compare/v2.0.0...v2.0.1) (2026-10-31)
|
|
9
17
|
|
|
10
18
|
|
data/README.md
CHANGED
|
@@ -172,7 +172,30 @@ McpOnRuby.configure do |config|
|
|
|
172
172
|
end
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
For Devise and Warden, the user is signed in with the `:user` scope (change it with `config.warden_scope`).
|
|
175
|
+
For Devise and Warden, the user is signed in with the `:user` scope (change it with `config.warden_scope`). Other apps read the stdio user with `mcp_user`.
|
|
176
|
+
|
|
177
|
+
**Rails 8 authentication** (`bin/rails generate authentication`) uses neither Warden nor tokens. Teach its concern about API tokens for HTTP clients and about `mcp_user` for stdio:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
# app/controllers/concerns/authentication.rb
|
|
181
|
+
def resume_session
|
|
182
|
+
Current.session ||= find_session_by_cookie || find_session_by_token || find_session_for_mcp
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# HTTP clients send `Authorization: Bearer <api_token>` (add `has_secure_token :api_token` to User).
|
|
186
|
+
def find_session_by_token
|
|
187
|
+
authenticate_with_http_token do |token, _options|
|
|
188
|
+
User.find_by(api_token: token)&.sessions&.new(user_agent: request.user_agent, ip_address: request.remote_ip)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# `bin/rails mcp:stdio` sessions act as config.stdio_user.
|
|
193
|
+
def find_session_for_mcp
|
|
194
|
+
mcp_user&.sessions&.new
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The sessions are built but never saved, so tool calls don't add rows to `sessions` or set cookies.
|
|
176
199
|
|
|
177
200
|
If authentication fails, the agent gets a clear "Not authenticated (401)" tool error and nothing runs.
|
|
178
201
|
|
|
@@ -209,7 +232,8 @@ See [SECURITY.md](SECURITY.md) for the full threat model.
|
|
|
209
232
|
| `401` / `403` / `404` | Tool error: not authenticated / not permitted / not found |
|
|
210
233
|
| `422` with JSON | Tool error including your validation messages, so the agent can correct itself |
|
|
211
234
|
| Other `4xx` | Tool error with the status, plus the body when it is JSON |
|
|
212
|
-
| `3xx` | Tool error
|
|
235
|
+
| `3xx` to a sign-in page | Tool error: not authenticated (Rails 8 authentication and Devise redirect unauthenticated requests) |
|
|
236
|
+
| Other `3xx` | Tool error naming the redirect target (redirects are not followed) |
|
|
213
237
|
| HTML | Tool error asking for JSON. Use `respond_to` with `format.json`. |
|
|
214
238
|
| `5xx` or an exception | Generic tool error. Details are reported to `Rails.error`, never sent. |
|
|
215
239
|
|
|
@@ -265,19 +289,26 @@ claude mcp add my-app -e MCP_USER=alice@example.com -- bin/rails mcp:stdio
|
|
|
265
289
|
<details>
|
|
266
290
|
<summary><b>Claude Desktop</b></summary>
|
|
267
291
|
|
|
268
|
-
Claude Desktop
|
|
292
|
+
Claude Desktop starts servers without your shell environment, so rbenv, asdf, and mise aren't loaded. Point it at your app's Ruby directly, and use `-C` to run from the app's directory:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# In your app directory, print the Ruby to use:
|
|
296
|
+
ruby -e 'puts RbConfig.ruby'
|
|
297
|
+
```
|
|
269
298
|
|
|
270
299
|
```json
|
|
271
300
|
{
|
|
272
301
|
"mcpServers": {
|
|
273
302
|
"my-app": {
|
|
274
|
-
"command": "
|
|
275
|
-
"args": ["-
|
|
303
|
+
"command": "/Users/you/.rbenv/versions/3.4.7/bin/ruby",
|
|
304
|
+
"args": ["-C", "/path/to/my-app", "bin/rails", "mcp:stdio"],
|
|
276
305
|
"env": { "MCP_USER": "alice@example.com" }
|
|
277
306
|
}
|
|
278
307
|
}
|
|
279
308
|
}
|
|
280
309
|
```
|
|
310
|
+
|
|
311
|
+
Update `command` when you upgrade Ruby.
|
|
281
312
|
</details>
|
|
282
313
|
|
|
283
314
|
<details>
|
|
@@ -90,12 +90,19 @@ module McpOnRuby
|
|
|
90
90
|
}.fetch(action) { "TODO: describe what #{action} does for the agent" }
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# Foreign keys of belongs_to associations usually come from the signed-in user or the URL, not the agent.
|
|
94
|
+
def owner_keys(model)
|
|
95
|
+
return [] unless model.respond_to?(:reflect_on_all_associations)
|
|
96
|
+
|
|
97
|
+
model.reflect_on_all_associations(:belongs_to).map { |association| association.foreign_key.to_s }
|
|
98
|
+
end
|
|
99
|
+
|
|
93
100
|
# A params block built from the model's columns, when the controller has an ActiveRecord model.
|
|
94
101
|
def model_params
|
|
95
102
|
model = controller.split("/").last.classify.safe_constantize
|
|
96
103
|
return unless model.respond_to?(:columns_hash)
|
|
97
104
|
|
|
98
|
-
columns = model.columns_hash.except(model.primary_key, *SKIPPED_COLUMNS)
|
|
105
|
+
columns = model.columns_hash.except(model.primary_key, *SKIPPED_COLUMNS, *owner_keys(model))
|
|
99
106
|
return if columns.empty?
|
|
100
107
|
|
|
101
108
|
lines = columns.map do |name, column|
|
data/lib/mcp_on_ruby/result.rb
CHANGED
|
@@ -6,6 +6,10 @@ module McpOnRuby
|
|
|
6
6
|
JSON_TYPE = %r{\Aapplication/(?:[\w.+-]+\+)?json\b}i
|
|
7
7
|
HTML_TYPE = %r{\Atext/html\b}i
|
|
8
8
|
|
|
9
|
+
# Sign-in pages apps redirect unauthenticated requests to: Rails 8 authentication (/session/new),
|
|
10
|
+
# Devise (/users/sign_in), and common /login and /signin routes.
|
|
11
|
+
SIGN_IN_PATH = %r{/(?:session/new|sign_?in|log_?in)/?\z}i
|
|
12
|
+
|
|
9
13
|
module_function
|
|
10
14
|
|
|
11
15
|
def from_rack(status, headers, body)
|
|
@@ -15,8 +19,7 @@ module McpOnRuby
|
|
|
15
19
|
|
|
16
20
|
case status
|
|
17
21
|
when 200..299 then success(status, text, json, content_type)
|
|
18
|
-
when 300..399 then
|
|
19
|
-
"redirects are not followed")
|
|
22
|
+
when 300..399 then redirect(status, header(headers, "location"))
|
|
20
23
|
when 400..499 then client_error(status, text, json)
|
|
21
24
|
else error(status, "Internal server error (#{status})")
|
|
22
25
|
end
|
|
@@ -36,6 +39,16 @@ module McpOnRuby
|
|
|
36
39
|
text_response(text)
|
|
37
40
|
end
|
|
38
41
|
|
|
42
|
+
def redirect(status, location)
|
|
43
|
+
if location && URI(location).path.to_s.match?(SIGN_IN_PATH)
|
|
44
|
+
error(status, "Not authenticated: the app redirected to its sign-in page (#{location})")
|
|
45
|
+
else
|
|
46
|
+
error(status, "Redirected to #{location || "another page"}; redirects are not followed")
|
|
47
|
+
end
|
|
48
|
+
rescue URI::InvalidURIError
|
|
49
|
+
error(status, "Redirected to #{location}; redirects are not followed")
|
|
50
|
+
end
|
|
51
|
+
|
|
39
52
|
def client_error(status, text, json)
|
|
40
53
|
message = {
|
|
41
54
|
401 => "Not authenticated (401)",
|
data/lib/mcp_on_ruby/version.rb
CHANGED