actionmcp 0.60.0 → 0.60.1
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/README.md +40 -3
- data/app/controllers/action_mcp/application_controller.rb +49 -18
- data/app/models/action_mcp/session.rb +2 -2
- data/lib/action_mcp/client/base.rb +1 -1
- data/lib/action_mcp/client/json_rpc_handler.rb +1 -1
- data/lib/action_mcp/configuration.rb +1 -1
- data/lib/action_mcp/server/capabilities.rb +2 -2
- data/lib/action_mcp/server/memory_session.rb +2 -2
- data/lib/action_mcp/version.rb +1 -1
- data/lib/action_mcp.rb +9 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2384a0cea84e17ca34e3c46e4029e2dc008561ebeeb8e52f15cb05c3fb5f5eb4
|
4
|
+
data.tar.gz: ccba3d9432ae0be54a4950e2f5d357c55fdd39cec2766253e2b3093dba9eedd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7ed0c8b71d4211dc48388a7544c17ecb9ff674faad9274cd1225cb3e3a65f76bcb546c2c2353882a1ade9016670659fed38aa1c90aa94e17479149012e1be01
|
7
|
+
data.tar.gz: add51e3d310ec89d96bef2767008bc4be9f9bddfeda60ca826d6e9168485771ebf61c03e1011c889d0a57fb4ff3d7bb0e0f5c5046cb35ca69a5285d647fe71d2
|
data/README.md
CHANGED
@@ -48,7 +48,16 @@ To start using ActionMCP, add it to your project:
|
|
48
48
|
$ bundle add actionmcp
|
49
49
|
```
|
50
50
|
|
51
|
-
|
51
|
+
After adding the gem, run the install generator to set up the basic ActionMCP structure:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
bundle install
|
55
|
+
bin/rails action_mcp:install:migrations
|
56
|
+
bin/rails db:migrate
|
57
|
+
bin/rails generate action_mcp:install
|
58
|
+
```
|
59
|
+
|
60
|
+
This will create the base application classes, configuration file, and necessary database tables for ActionMCP to function properly.
|
52
61
|
|
53
62
|
## Core Components
|
54
63
|
|
@@ -309,7 +318,8 @@ Then install it:
|
|
309
318
|
|
310
319
|
```bash
|
311
320
|
bundle install
|
312
|
-
bin/rails solid_mcp:install
|
321
|
+
bin/rails solid_mcp:install:migrations
|
322
|
+
bin/rails db:migrate
|
313
323
|
```
|
314
324
|
|
315
325
|
The installer will create the necessary database migration for message storage. Configure it in your `config/mcp.yml`.
|
@@ -623,6 +633,33 @@ run ActionMCP::Engine
|
|
623
633
|
bin/rails s -c mcp.ru -p 62770 -P tmp/pids/mcps0.pid
|
624
634
|
```
|
625
635
|
|
636
|
+
### Dealing with Middleware Conflicts
|
637
|
+
|
638
|
+
If your Rails application uses middleware that interferes with MCP server operation (like Devise, Warden, Ahoy, Rack::Cors, etc.), use `mcp_vanilla.ru` instead:
|
639
|
+
|
640
|
+
```ruby
|
641
|
+
# mcp_vanilla.ru - A minimal Rack app with only essential middleware
|
642
|
+
# This avoids conflicts with authentication, tracking, and other web-specific middleware
|
643
|
+
# See the file for detailed documentation on when and why to use it
|
644
|
+
|
645
|
+
bundle exec rails s -c mcp_vanilla.ru -p 62770
|
646
|
+
# Or with Falcon:
|
647
|
+
bundle exec falcon serve --bind http://0.0.0.0:62770 --config mcp_vanilla.ru
|
648
|
+
```
|
649
|
+
|
650
|
+
Common middleware that can cause issues:
|
651
|
+
- **Devise/Warden** - Expects cookies and sessions, throws `Devise::MissingWarden` errors
|
652
|
+
- **Ahoy** - Analytics tracking that intercepts requests
|
653
|
+
- **Rack::Attack** - Rate limiting designed for web traffic
|
654
|
+
- **Rack::Cors** - CORS headers meant for browsers
|
655
|
+
- Any middleware assuming HTML responses or cookie-based authentication
|
656
|
+
|
657
|
+
An example of a minimal `mcp_vanilla.ru` file is located in the dummy app : test/dummy/mcp_vanilla.ru.
|
658
|
+
This file is a minimal Rack application that only includes the essential middleware needed for MCP server operation, avoiding conflicts with web-specific middleware.
|
659
|
+
But remember to add any instrumentation or logging middleware you need, as the minimal setup will not include them by default.
|
660
|
+
|
661
|
+
```ruby
|
662
|
+
|
626
663
|
## Production Deployment of MCPS0
|
627
664
|
|
628
665
|
In production, **MCPS0** (the MCP server) is a standard Rack application. You can run it using any Rack-compatible server (such as Puma, Unicorn, or Passenger).
|
@@ -637,7 +674,7 @@ Run MCPS0 on its own TCP port (commonly `62770`):
|
|
637
674
|
|
638
675
|
**With Falcon:**
|
639
676
|
```bash
|
640
|
-
bundle exec falcon serve --bind http://0.0.0.0:62770 mcp.ru
|
677
|
+
bundle exec falcon serve --bind http://0.0.0.0:62770 --config mcp.ru
|
641
678
|
```
|
642
679
|
|
643
680
|
**With Puma:**
|
@@ -5,7 +5,6 @@ module ActionMCP
|
|
5
5
|
# Supports GET for server-initiated SSE streams, POST for client messages
|
6
6
|
# (responding with JSON or SSE), and optionally DELETE for session termination.
|
7
7
|
class ApplicationController < ActionController::API
|
8
|
-
REQUIRED_PROTOCOL_VERSION = "2025-03-26"
|
9
8
|
MCP_SESSION_ID_HEADER = "Mcp-Session-Id"
|
10
9
|
|
11
10
|
include Engine.routes.url_helpers
|
@@ -143,11 +142,14 @@ module ActionMCP
|
|
143
142
|
# Handles POST requests containing client JSON-RPC messages according to 2025-03-26 spec.
|
144
143
|
# @route POST /mcp
|
145
144
|
def create
|
146
|
-
|
145
|
+
unless post_accept_headers_valid?
|
146
|
+
id = extract_jsonrpc_id_from_params
|
147
|
+
return render_not_acceptable(post_accept_headers_error_message, id)
|
148
|
+
end
|
147
149
|
|
148
150
|
# Reject JSON-RPC batch requests as per MCP 2025-06-18 spec
|
149
151
|
if jsonrpc_params_batch?
|
150
|
-
return render_bad_request("JSON-RPC batch requests are not supported")
|
152
|
+
return render_bad_request("JSON-RPC batch requests are not supported", nil)
|
151
153
|
end
|
152
154
|
|
153
155
|
is_initialize_request = check_if_initialize_request(jsonrpc_params)
|
@@ -160,11 +162,14 @@ module ActionMCP
|
|
160
162
|
|
161
163
|
unless is_initialize_request
|
162
164
|
if session_initially_missing
|
163
|
-
|
165
|
+
id = jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil
|
166
|
+
return render_bad_request("Mcp-Session-Id header is required for this request.", id)
|
164
167
|
elsif session.nil? || session.new_record?
|
165
|
-
|
168
|
+
id = jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil
|
169
|
+
return render_not_found("Session not found.", id)
|
166
170
|
elsif session.status == "closed"
|
167
|
-
|
171
|
+
id = jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil
|
172
|
+
return render_not_found("Session has been terminated.", id)
|
168
173
|
end
|
169
174
|
end
|
170
175
|
|
@@ -188,7 +193,8 @@ module ActionMCP
|
|
188
193
|
end
|
189
194
|
rescue StandardError => e
|
190
195
|
Rails.logger.error "Unified POST Error: #{e.class} - #{e.message}\n#{e.backtrace.join("\n")}"
|
191
|
-
|
196
|
+
id = jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil rescue nil
|
197
|
+
render_internal_server_error("An unexpected error occurred.", id) unless performed?
|
192
198
|
end
|
193
199
|
|
194
200
|
# Handles DELETE requests for session termination (2025-03-26 spec).
|
@@ -259,7 +265,7 @@ module ActionMCP
|
|
259
265
|
# Session protocol version is set during initialization and should not be overridden
|
260
266
|
session
|
261
267
|
else
|
262
|
-
Server.session_store.create_session(nil, protocol_version:
|
268
|
+
Server.session_store.create_session(nil, protocol_version: ActionMCP::DEFAULT_PROTOCOL_VERSION)
|
263
269
|
end
|
264
270
|
end
|
265
271
|
|
@@ -404,33 +410,58 @@ module ActionMCP
|
|
404
410
|
# --- Error Rendering Methods ---
|
405
411
|
|
406
412
|
# Renders a 400 Bad Request response with a JSON-RPC-like error structure.
|
407
|
-
def render_bad_request(message = "Bad Request")
|
408
|
-
|
413
|
+
def render_bad_request(message = "Bad Request", id = nil)
|
414
|
+
id ||= extract_jsonrpc_id_from_request
|
415
|
+
render json: { jsonrpc: "2.0", id: id, error: { code: -32_600, message: message } }
|
409
416
|
end
|
410
417
|
|
411
418
|
# Renders a 404 Not Found response with a JSON-RPC-like error structure.
|
412
|
-
def render_not_found(message = "Not Found")
|
413
|
-
|
419
|
+
def render_not_found(message = "Not Found", id = nil)
|
420
|
+
id ||= extract_jsonrpc_id_from_request
|
421
|
+
render json: { jsonrpc: "2.0", id: id, error: { code: -32_001, message: message } }
|
414
422
|
end
|
415
423
|
|
416
424
|
# Renders a 405 Method Not Allowed response.
|
417
|
-
def render_method_not_allowed(message = "Method Not Allowed")
|
418
|
-
|
425
|
+
def render_method_not_allowed(message = "Method Not Allowed", id = nil)
|
426
|
+
id ||= extract_jsonrpc_id_from_request
|
427
|
+
render json: { jsonrpc: "2.0", id: id, error: { code: -32_601, message: message } }
|
419
428
|
end
|
420
429
|
|
421
430
|
# Renders a 406 Not Acceptable response.
|
422
|
-
def render_not_acceptable(message = "Not Acceptable")
|
423
|
-
|
431
|
+
def render_not_acceptable(message = "Not Acceptable", id = nil)
|
432
|
+
id ||= extract_jsonrpc_id_from_request
|
433
|
+
render json: { jsonrpc: "2.0", id: id, error: { code: -32_002, message: message } }
|
424
434
|
end
|
425
435
|
|
426
436
|
# Renders a 501 Not Implemented response.
|
427
|
-
def render_not_implemented(message = "Not Implemented")
|
428
|
-
|
437
|
+
def render_not_implemented(message = "Not Implemented", id = nil)
|
438
|
+
id ||= extract_jsonrpc_id_from_request
|
439
|
+
render json: { jsonrpc: "2.0", id: id, error: { code: -32_003, message: message } }
|
429
440
|
end
|
430
441
|
|
431
442
|
# Renders a 500 Internal Server Error response.
|
432
443
|
def render_internal_server_error(message = "Internal Server Error", id = nil)
|
433
444
|
render json: { jsonrpc: "2.0", id: id, error: { code: -32_000, message: message } }
|
434
445
|
end
|
446
|
+
|
447
|
+
# Extract JSON-RPC ID from request
|
448
|
+
def extract_jsonrpc_id_from_request
|
449
|
+
# Try to get from already parsed jsonrpc_params first
|
450
|
+
if defined?(jsonrpc_params) && jsonrpc_params
|
451
|
+
return jsonrpc_params.respond_to?(:id) ? jsonrpc_params.id : nil
|
452
|
+
end
|
453
|
+
|
454
|
+
# Otherwise try to parse from raw body, this need refactoring
|
455
|
+
return nil unless request.post? && request.content_type&.include?("application/json")
|
456
|
+
|
457
|
+
begin
|
458
|
+
body = request.body.read
|
459
|
+
request.body.rewind # Reset for subsequent reads
|
460
|
+
json = JSON.parse(body)
|
461
|
+
json["id"]
|
462
|
+
rescue JSON::ParserError, StandardError
|
463
|
+
nil
|
464
|
+
end
|
465
|
+
end
|
435
466
|
end
|
436
467
|
end
|
@@ -75,7 +75,7 @@ module ActionMCP
|
|
75
75
|
before_create :set_server_info, if: -> { role == "server" }
|
76
76
|
before_create :set_server_capabilities, if: -> { role == "server" }
|
77
77
|
|
78
|
-
validates :protocol_version, inclusion: { in: SUPPORTED_VERSIONS }, allow_nil: true, unless: lambda {
|
78
|
+
validates :protocol_version, inclusion: { in: ActionMCP::SUPPORTED_VERSIONS }, allow_nil: true, unless: lambda {
|
79
79
|
ActionMCP.configuration.vibed_ignore_version
|
80
80
|
}
|
81
81
|
|
@@ -130,7 +130,7 @@ module ActionMCP
|
|
130
130
|
|
131
131
|
def server_capabilities_payload
|
132
132
|
{
|
133
|
-
protocolVersion:
|
133
|
+
protocolVersion: protocol_version || ActionMCP::DEFAULT_PROTOCOL_VERSION,
|
134
134
|
serverInfo: server_info,
|
135
135
|
capabilities: server_capabilities
|
136
136
|
}
|
@@ -180,7 +180,7 @@ module ActionMCP
|
|
180
180
|
# Create a new session with the server-provided ID
|
181
181
|
client.instance_variable_set(:@session, ActionMCP::Session.from_client.new(
|
182
182
|
id: session_id,
|
183
|
-
protocol_version: result["protocolVersion"] ||
|
183
|
+
protocol_version: result["protocolVersion"] || ActionMCP::DEFAULT_PROTOCOL_VERSION,
|
184
184
|
client_info: client.client_info,
|
185
185
|
client_capabilities: client.client_capabilities,
|
186
186
|
server_info: result["serverInfo"],
|
@@ -67,7 +67,7 @@ module ActionMCP
|
|
67
67
|
|
68
68
|
@sse_heartbeat_interval = 30
|
69
69
|
@post_response_preference = :json
|
70
|
-
@protocol_version = "2025-03-26"
|
70
|
+
@protocol_version = "2025-03-26" # Default to legacy for backwards compatibility
|
71
71
|
@vibed_ignore_version = false
|
72
72
|
|
73
73
|
# Resumability defaults
|
@@ -45,7 +45,7 @@ module ActionMCP
|
|
45
45
|
# Return existing session info
|
46
46
|
capabilities_payload = existing_session.server_capabilities_payload
|
47
47
|
capabilities_payload[:protocolVersion] = if ActionMCP.configuration.vibed_ignore_version
|
48
|
-
|
48
|
+
ActionMCP::LATEST_VERSION
|
49
49
|
else
|
50
50
|
client_protocol_version
|
51
51
|
end
|
@@ -66,7 +66,7 @@ module ActionMCP
|
|
66
66
|
|
67
67
|
capabilities_payload = session.server_capabilities_payload
|
68
68
|
capabilities_payload[:protocolVersion] = if ActionMCP.configuration.vibed_ignore_version
|
69
|
-
|
69
|
+
ActionMCP::LATEST_VERSION
|
70
70
|
else
|
71
71
|
client_protocol_version
|
72
72
|
end
|
@@ -175,14 +175,14 @@ module ActionMCP
|
|
175
175
|
# Capability methods
|
176
176
|
def server_capabilities_payload
|
177
177
|
{
|
178
|
-
protocolVersion: ActionMCP::
|
178
|
+
protocolVersion: ActionMCP::LATEST_VERSION,
|
179
179
|
serverInfo: server_info,
|
180
180
|
capabilities: server_capabilities
|
181
181
|
}
|
182
182
|
end
|
183
183
|
|
184
184
|
def set_protocol_version(version)
|
185
|
-
version = ActionMCP::
|
185
|
+
version = ActionMCP::LATEST_VERSION if ActionMCP.configuration.vibed_ignore_version
|
186
186
|
self.protocol_version = version
|
187
187
|
save
|
188
188
|
end
|
data/lib/action_mcp/version.rb
CHANGED
data/lib/action_mcp.rb
CHANGED
@@ -37,9 +37,15 @@ module ActionMCP
|
|
37
37
|
require_relative "action_mcp/version"
|
38
38
|
require_relative "action_mcp/client"
|
39
39
|
include Logging
|
40
|
-
|
41
|
-
|
42
|
-
SUPPORTED_VERSIONS =
|
40
|
+
|
41
|
+
# Protocol version constants
|
42
|
+
SUPPORTED_VERSIONS = [
|
43
|
+
"2025-06-18", # Dr. Identity McBouncer - OAuth 2.1, elicitation, structured output, resource links
|
44
|
+
"2025-03-26" # The Persistent Negotiator - StreamableHTTP, resumability, audio support
|
45
|
+
].freeze
|
46
|
+
|
47
|
+
LATEST_VERSION = SUPPORTED_VERSIONS.first.freeze
|
48
|
+
DEFAULT_PROTOCOL_VERSION = "2025-03-26".freeze # Default to initial stable version for backwards compatibility
|
43
49
|
class << self
|
44
50
|
# Returns a Rack-compatible application for serving MCP requests
|
45
51
|
# This makes ActionMCP.server work similar to ActionCable.server
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionmcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.60.
|
4
|
+
version: 0.60.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -43,14 +43,14 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 0.5.
|
46
|
+
version: 0.5.3
|
47
47
|
type: :runtime
|
48
48
|
prerelease: false
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.5.
|
53
|
+
version: 0.5.3
|
54
54
|
- !ruby/object:Gem::Dependency
|
55
55
|
name: multi_json
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|