actionmcp 0.32.1 → 0.50.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 +4 -4
- data/README.md +138 -4
- data/app/controllers/action_mcp/unified_controller.rb +1 -1
- data/config/routes.rb +4 -9
- data/db/migrate/20250512154359_consolidated_migration.rb +146 -0
- data/exe/actionmcp_cli +8 -1
- data/lib/action_mcp/client.rb +3 -9
- data/lib/action_mcp/configuration.rb +2 -4
- data/lib/action_mcp/engine.rb +1 -1
- data/lib/action_mcp/server/configuration.rb +63 -0
- data/lib/action_mcp/server/simple_pub_sub.rb +145 -0
- data/lib/action_mcp/server/solid_cable_adapter.rb +222 -0
- data/lib/action_mcp/server.rb +84 -2
- data/lib/action_mcp/sse_listener.rb +3 -3
- data/lib/action_mcp/tool.rb +3 -7
- data/lib/action_mcp/version.rb +1 -1
- data/lib/action_mcp.rb +3 -3
- data/lib/generators/action_mcp/config/config_generator.rb +29 -0
- data/lib/generators/action_mcp/config/templates/mcp.yml +36 -0
- metadata +21 -26
- data/app/controllers/action_mcp/messages_controller.rb +0 -44
- data/app/controllers/action_mcp/sse_controller.rb +0 -179
- data/db/migrate/20250308122801_create_action_mcp_sessions.rb +0 -32
- data/db/migrate/20250314230152_add_is_ping_to_session_message.rb +0 -8
- data/db/migrate/20250316005021_create_action_mcp_session_subscriptions.rb +0 -16
- data/db/migrate/20250316005649_create_action_mcp_session_resources.rb +0 -25
- data/db/migrate/20250324203409_remove_session_message_text.rb +0 -7
- data/db/migrate/20250327124131_add_sse_event_counter_to_action_mcp_sessions.rb +0 -7
- data/db/migrate/20250329120300_add_registries_to_sessions.rb +0 -9
- data/db/migrate/20250329150312_create_action_mcp_sse_events.rb +0 -16
- data/lib/action_mcp/client/stdio_client.rb +0 -115
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require "concurrent/map"
|
5
|
+
require "concurrent/array"
|
6
|
+
require "concurrent/executor/thread_pool_executor"
|
7
|
+
|
8
|
+
module ActionMCP
|
9
|
+
module Server
|
10
|
+
# Mock SolidCable::PubSub for testing
|
11
|
+
class MockSolidCablePubSub
|
12
|
+
attr_reader :subscriptions, :messages
|
13
|
+
|
14
|
+
def initialize(options = {})
|
15
|
+
@options = options
|
16
|
+
@subscriptions = Concurrent::Map.new
|
17
|
+
@messages = Concurrent::Array.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def subscribe(channel, &block)
|
21
|
+
@subscriptions[channel] ||= Concurrent::Array.new
|
22
|
+
@subscriptions[channel] << block
|
23
|
+
end
|
24
|
+
|
25
|
+
def unsubscribe(channel)
|
26
|
+
@subscriptions.delete(channel)
|
27
|
+
end
|
28
|
+
|
29
|
+
def broadcast(channel, message)
|
30
|
+
@messages << { channel: channel, message: message }
|
31
|
+
callbacks = @subscriptions[channel] || []
|
32
|
+
callbacks.each { |callback| callback.call(message) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Adapter for SolidCable PubSub
|
37
|
+
class SolidCableAdapter
|
38
|
+
# Thread pool configuration
|
39
|
+
DEFAULT_MIN_THREADS = 5
|
40
|
+
DEFAULT_MAX_THREADS = 10
|
41
|
+
DEFAULT_MAX_QUEUE = 100
|
42
|
+
DEFAULT_THREAD_TIMEOUT = 60 # seconds
|
43
|
+
|
44
|
+
def initialize(options = {})
|
45
|
+
@options = options
|
46
|
+
@subscriptions = Concurrent::Map.new
|
47
|
+
@channels = Concurrent::Map.new
|
48
|
+
@channel_subscribed = Concurrent::Map.new # Track channel subscription status
|
49
|
+
|
50
|
+
# Initialize thread pool for callbacks
|
51
|
+
pool_options = {
|
52
|
+
min_threads: options["min_threads"] || DEFAULT_MIN_THREADS,
|
53
|
+
max_threads: options["max_threads"] || DEFAULT_MAX_THREADS,
|
54
|
+
max_queue: options["max_queue"] || DEFAULT_MAX_QUEUE,
|
55
|
+
fallback_policy: :caller_runs, # Execute in the caller's thread if queue is full
|
56
|
+
idletime: DEFAULT_THREAD_TIMEOUT
|
57
|
+
}
|
58
|
+
@thread_pool = Concurrent::ThreadPoolExecutor.new(pool_options)
|
59
|
+
|
60
|
+
# Configure SolidCable with options from mcp.yml
|
61
|
+
# The main option we care about is polling_interval
|
62
|
+
pubsub_options = {}
|
63
|
+
|
64
|
+
if @options["polling_interval"]
|
65
|
+
# Convert from ActiveSupport::Duration if needed (e.g., "0.1.seconds")
|
66
|
+
interval = @options["polling_interval"]
|
67
|
+
interval = interval.to_f if interval.respond_to?(:to_f)
|
68
|
+
pubsub_options[:polling_interval] = interval
|
69
|
+
end
|
70
|
+
|
71
|
+
# If there's a connects_to option, pass it along
|
72
|
+
if @options["connects_to"]
|
73
|
+
pubsub_options[:connects_to] = @options["connects_to"]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Use mock version for testing or real version in production
|
77
|
+
if defined?(SolidCable) && !testing?
|
78
|
+
@solid_cable_pubsub = SolidCable::PubSub.new(pubsub_options)
|
79
|
+
else
|
80
|
+
@solid_cable_pubsub = MockSolidCablePubSub.new(pubsub_options)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Subscribe to a channel
|
85
|
+
# @param channel [String] The channel name
|
86
|
+
# @param message_callback [Proc] Callback for received messages
|
87
|
+
# @param success_callback [Proc] Callback for successful subscription
|
88
|
+
# @return [String] Subscription ID
|
89
|
+
def subscribe(channel, message_callback, success_callback = nil)
|
90
|
+
subscription_id = SecureRandom.uuid
|
91
|
+
|
92
|
+
@subscriptions[subscription_id] = {
|
93
|
+
channel: channel,
|
94
|
+
message_callback: message_callback
|
95
|
+
}
|
96
|
+
|
97
|
+
@channels[channel] ||= Concurrent::Array.new
|
98
|
+
@channels[channel] << subscription_id
|
99
|
+
|
100
|
+
# Subscribe to SolidCable only if we haven't already subscribed to this channel
|
101
|
+
unless subscribed_to_solid_cable?(channel)
|
102
|
+
@solid_cable_pubsub.subscribe(channel) do |message|
|
103
|
+
dispatch_message(channel, message)
|
104
|
+
end
|
105
|
+
@channel_subscribed[channel] = true
|
106
|
+
end
|
107
|
+
|
108
|
+
log_subscription_event(channel, "Subscribed", subscription_id)
|
109
|
+
success_callback&.call
|
110
|
+
|
111
|
+
subscription_id
|
112
|
+
end
|
113
|
+
|
114
|
+
# Unsubscribe from a channel
|
115
|
+
# @param channel [String] The channel name
|
116
|
+
# @param callback [Proc] Optional callback for unsubscribe completion
|
117
|
+
def unsubscribe(channel, callback = nil)
|
118
|
+
# Remove our subscriptions
|
119
|
+
subscription_ids = @channels[channel] || []
|
120
|
+
subscription_ids.each do |subscription_id|
|
121
|
+
@subscriptions.delete(subscription_id)
|
122
|
+
end
|
123
|
+
|
124
|
+
@channels.delete(channel)
|
125
|
+
|
126
|
+
# Only unsubscribe from SolidCable if we're actually subscribed
|
127
|
+
if subscribed_to_solid_cable?(channel)
|
128
|
+
@solid_cable_pubsub.unsubscribe(channel)
|
129
|
+
@channel_subscribed.delete(channel)
|
130
|
+
end
|
131
|
+
|
132
|
+
log_subscription_event(channel, "Unsubscribed")
|
133
|
+
callback&.call
|
134
|
+
end
|
135
|
+
|
136
|
+
# Broadcast a message to a channel
|
137
|
+
# @param channel [String] The channel name
|
138
|
+
# @param message [String] The message to broadcast
|
139
|
+
def broadcast(channel, message)
|
140
|
+
@solid_cable_pubsub.broadcast(channel, message)
|
141
|
+
log_broadcast_event(channel, message)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Check if a channel has subscribers
|
145
|
+
# @param channel [String] The channel name
|
146
|
+
# @return [Boolean] True if channel has subscribers
|
147
|
+
def has_subscribers?(channel)
|
148
|
+
subscribers = @channels[channel]
|
149
|
+
return false unless subscribers
|
150
|
+
!subscribers.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
# Check if we're already subscribed to a channel
|
154
|
+
# @param channel [String] The channel name
|
155
|
+
# @return [Boolean] True if we're already subscribed
|
156
|
+
def subscribed_to?(channel)
|
157
|
+
channel_subs = @channels[channel]
|
158
|
+
return false if channel_subs.nil?
|
159
|
+
!channel_subs.empty?
|
160
|
+
end
|
161
|
+
|
162
|
+
# Shut down the thread pool gracefully
|
163
|
+
def shutdown
|
164
|
+
@thread_pool.shutdown
|
165
|
+
@thread_pool.wait_for_termination(5) # Wait up to 5 seconds for tasks to complete
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
# Check if we're in a testing environment
|
171
|
+
def testing?
|
172
|
+
defined?(Minitest) || ENV["RAILS_ENV"] == "test"
|
173
|
+
end
|
174
|
+
|
175
|
+
# Check if we're already subscribed to this channel in SolidCable
|
176
|
+
def subscribed_to_solid_cable?(channel)
|
177
|
+
@channel_subscribed[channel] == true
|
178
|
+
end
|
179
|
+
|
180
|
+
def dispatch_message(channel, message)
|
181
|
+
subscription_ids = @channels[channel] || []
|
182
|
+
|
183
|
+
subscription_ids.each do |subscription_id|
|
184
|
+
subscription = @subscriptions[subscription_id]
|
185
|
+
next unless subscription && subscription[:message_callback]
|
186
|
+
|
187
|
+
@thread_pool.post do
|
188
|
+
begin
|
189
|
+
subscription[:message_callback].call(message)
|
190
|
+
rescue StandardError => e
|
191
|
+
log_error("Error in message callback: #{e.message}\n#{e.backtrace.join("\n")}")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def log_subscription_event(channel, action, subscription_id = nil)
|
198
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger)
|
199
|
+
|
200
|
+
message = "SolidCableAdapter: #{action} channel=#{channel}"
|
201
|
+
message += " subscription_id=#{subscription_id}" if subscription_id
|
202
|
+
|
203
|
+
Rails.logger.debug(message)
|
204
|
+
end
|
205
|
+
|
206
|
+
def log_broadcast_event(channel, message)
|
207
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger)
|
208
|
+
|
209
|
+
# Truncate the message for logging
|
210
|
+
truncated_message = message.to_s[0..100]
|
211
|
+
truncated_message += "..." if message.to_s.length > 100
|
212
|
+
|
213
|
+
Rails.logger.debug("SolidCableAdapter: Broadcasting to channel=#{channel} message=#{truncated_message}")
|
214
|
+
end
|
215
|
+
|
216
|
+
def log_error(message)
|
217
|
+
return unless defined?(Rails) && Rails.respond_to?(:logger)
|
218
|
+
Rails.logger.error("SolidCableAdapter: #{message}")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
data/lib/action_mcp/server.rb
CHANGED
@@ -1,13 +1,95 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "server/simple_pub_sub"
|
4
|
+
require_relative "server/configuration"
|
5
|
+
|
6
|
+
# Conditionally load adapters based on available gems
|
7
|
+
begin
|
8
|
+
require "solid_cable/pubsub"
|
9
|
+
require_relative "server/solid_cable_adapter"
|
10
|
+
rescue LoadError
|
11
|
+
# SolidCable not available
|
12
|
+
end
|
13
|
+
|
4
14
|
module ActionMCP
|
5
15
|
# Module for server-related functionality.
|
6
16
|
module Server
|
7
17
|
module_function
|
8
18
|
|
9
19
|
def server
|
10
|
-
@server ||=
|
20
|
+
@server ||= ServerBase.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Shut down the server and clean up resources
|
24
|
+
def shutdown
|
25
|
+
return unless @server
|
26
|
+
@server.shutdown
|
27
|
+
@server = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Available pubsub adapter types
|
31
|
+
ADAPTERS = {
|
32
|
+
"test" => "SimplePubSub",
|
33
|
+
"simple" => "SimplePubSub",
|
34
|
+
"solid_cable" => "SolidCableAdapter" # Will use mock version in tests
|
35
|
+
}.compact.freeze
|
36
|
+
|
37
|
+
# Custom server base class for PubSub functionality
|
38
|
+
class ServerBase
|
39
|
+
def initialize(config_path = nil)
|
40
|
+
@configuration = Configuration.new(config_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def pubsub
|
44
|
+
@pubsub ||= create_pubsub
|
45
|
+
end
|
46
|
+
|
47
|
+
# Allow manual override of the configuration
|
48
|
+
# @param config_path [String] Path to a cable.yml configuration file
|
49
|
+
def configure(config_path)
|
50
|
+
shutdown_pubsub if @pubsub
|
51
|
+
@configuration = Configuration.new(config_path)
|
52
|
+
@pubsub = nil # Reset pubsub so it will be recreated with new config
|
53
|
+
end
|
54
|
+
|
55
|
+
# Gracefully shut down the server and its resources
|
56
|
+
def shutdown
|
57
|
+
shutdown_pubsub
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Shut down the pubsub adapter gracefully
|
63
|
+
def shutdown_pubsub
|
64
|
+
return unless @pubsub && @pubsub.respond_to?(:shutdown)
|
65
|
+
|
66
|
+
begin
|
67
|
+
@pubsub.shutdown
|
68
|
+
rescue => e
|
69
|
+
message = "Error shutting down pubsub adapter: #{e.message}"
|
70
|
+
Rails.logger.error(message) if defined?(Rails) && Rails.respond_to?(:logger)
|
71
|
+
ensure
|
72
|
+
@pubsub = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_pubsub
|
77
|
+
adapter_name = @configuration.adapter_name
|
78
|
+
adapter_options = @configuration.adapter_options
|
79
|
+
|
80
|
+
# Default to simple if adapter not found
|
81
|
+
adapter_class_name = ADAPTERS[adapter_name] || "SimplePubSub"
|
82
|
+
|
83
|
+
begin
|
84
|
+
# Create an instance of the adapter class with the configuration options
|
85
|
+
adapter_class = ActionMCP::Server.const_get(adapter_class_name)
|
86
|
+
adapter_class.new(adapter_options)
|
87
|
+
rescue NameError, LoadError => e
|
88
|
+
message = "Error creating adapter #{adapter_name}: #{e.message}"
|
89
|
+
Rails.logger.error(message) if defined?(Rails) && Rails.respond_to?(:logger)
|
90
|
+
SimplePubSub.new # Fallback to simple pubsub
|
91
|
+
end
|
92
|
+
end
|
11
93
|
end
|
12
94
|
end
|
13
95
|
end
|
@@ -4,7 +4,7 @@ require "concurrent/atomic/atomic_boolean"
|
|
4
4
|
require "concurrent/promise"
|
5
5
|
|
6
6
|
module ActionMCP
|
7
|
-
# Listener class to subscribe to session messages via
|
7
|
+
# Listener class to subscribe to session messages via PubSub adapter.
|
8
8
|
class SSEListener
|
9
9
|
delegate :session_key, :adapter, to: :@session
|
10
10
|
|
@@ -15,7 +15,7 @@ module ActionMCP
|
|
15
15
|
@subscription_active = Concurrent::AtomicBoolean.new
|
16
16
|
end
|
17
17
|
|
18
|
-
# Start listening using
|
18
|
+
# Start listening using PubSub adapter
|
19
19
|
# @yield [Hash] Yields parsed message received from the pub/sub channel
|
20
20
|
# @return [Boolean] True if subscription was successful within timeout, false otherwise.
|
21
21
|
def start(&callback)
|
@@ -30,7 +30,7 @@ module ActionMCP
|
|
30
30
|
process_message(raw_message, callback)
|
31
31
|
}
|
32
32
|
|
33
|
-
# Subscribe using the
|
33
|
+
# Subscribe using the PubSub adapter
|
34
34
|
adapter.subscribe(session_key, message_callback, success_callback)
|
35
35
|
|
36
36
|
wait_for_subscription
|
data/lib/action_mcp/tool.rb
CHANGED
@@ -60,14 +60,10 @@ module ActionMCP
|
|
60
60
|
annotate(:readOnly, enabled)
|
61
61
|
end
|
62
62
|
|
63
|
-
# Return annotations
|
63
|
+
# Return annotations for the tool
|
64
64
|
def annotations_for_protocol(protocol_version = nil)
|
65
|
-
#
|
66
|
-
|
67
|
-
{}
|
68
|
-
else
|
69
|
-
_annotations
|
70
|
-
end
|
65
|
+
# Always include annotations now that we only support 2025+
|
66
|
+
_annotations
|
71
67
|
end
|
72
68
|
end
|
73
69
|
|
data/lib/action_mcp/version.rb
CHANGED
data/lib/action_mcp.rb
CHANGED
@@ -34,9 +34,9 @@ module ActionMCP
|
|
34
34
|
require_relative "action_mcp/version"
|
35
35
|
require_relative "action_mcp/client"
|
36
36
|
include Logging
|
37
|
-
PROTOCOL_VERSION = "
|
38
|
-
CURRENT_VERSION = "2025-03-26" # Current version
|
39
|
-
SUPPORTED_VERSIONS = %w[
|
37
|
+
PROTOCOL_VERSION = "2025-03-26" # Default version
|
38
|
+
CURRENT_VERSION = "2025-03-26" # Current version
|
39
|
+
SUPPORTED_VERSIONS = %w[2025-03-26].freeze
|
40
40
|
class << self
|
41
41
|
delegate :server, to: "ActionMCP::Server"
|
42
42
|
# Returns the configuration instance.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMCP
|
4
|
+
module Generators
|
5
|
+
class ConfigGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
|
8
|
+
desc "Creates ActionMCP configuration file (config/mcp.yml)"
|
9
|
+
|
10
|
+
def create_mcp_yml
|
11
|
+
template "mcp.yml", "config/mcp.yml"
|
12
|
+
end
|
13
|
+
|
14
|
+
def show_instructions
|
15
|
+
say "ActionMCP configuration file created at config/mcp.yml"
|
16
|
+
say "You can customize your PubSub adapters and other settings in this file."
|
17
|
+
say ""
|
18
|
+
say "Available adapters:"
|
19
|
+
say " - simple : In-memory adapter for development"
|
20
|
+
say " - test : Test adapter"
|
21
|
+
say " - solid_cable : Database-backed adapter (requires solid_cable gem)"
|
22
|
+
say " - redis : Redis-backed adapter (requires redis gem)"
|
23
|
+
say ""
|
24
|
+
say "Example usage:"
|
25
|
+
say " rails g action_mcp:install # Main generator"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# ActionMCP Configuration
|
2
|
+
# This file contains configuration for the ActionMCP pub/sub system.
|
3
|
+
# Different environments can use different adapters.
|
4
|
+
|
5
|
+
development:
|
6
|
+
# In-memory adapter for development
|
7
|
+
adapter: simple
|
8
|
+
# Thread pool configuration (optional)
|
9
|
+
# min_threads: 5 # Minimum number of threads in the pool
|
10
|
+
# max_threads: 10 # Maximum number of threads in the pool
|
11
|
+
# max_queue: 100 # Maximum number of tasks that can be queued
|
12
|
+
|
13
|
+
test:
|
14
|
+
# Test adapter for testing
|
15
|
+
adapter: test
|
16
|
+
|
17
|
+
production:
|
18
|
+
# Choose one of the following adapters:
|
19
|
+
|
20
|
+
# 1. Database-backed adapter (recommended)
|
21
|
+
adapter: solid_cable
|
22
|
+
polling_interval: 0.5.seconds
|
23
|
+
# connects_to: cable # Optional: specify a different database connection
|
24
|
+
|
25
|
+
# Thread pool configuration (optional)
|
26
|
+
min_threads: 10 # Minimum number of threads in the pool
|
27
|
+
max_threads: 20 # Maximum number of threads in the pool
|
28
|
+
max_queue: 500 # Maximum number of tasks that can be queued
|
29
|
+
|
30
|
+
# 2. Redis-backed adapter (alternative)
|
31
|
+
# adapter: redis
|
32
|
+
# url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
33
|
+
# channel_prefix: <%= Rails.application.class.module_parent_name.underscore %>_production
|
34
|
+
# min_threads: 10 # Minimum number of threads in the pool
|
35
|
+
# max_threads: 20 # Maximum number of threads in the pool
|
36
|
+
# max_queue: 500 # Maximum number of tasks that can be queued
|
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.
|
4
|
+
version: 0.50.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
@@ -9,20 +9,6 @@ bindir: exe
|
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
|
-
- !ruby/object:Gem::Dependency
|
13
|
-
name: actioncable
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
15
|
-
requirements:
|
16
|
-
- - ">="
|
17
|
-
- !ruby/object:Gem::Version
|
18
|
-
version: 8.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: 8.0.1
|
26
12
|
- !ruby/object:Gem::Dependency
|
27
13
|
name: activerecord
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -93,6 +79,20 @@ dependencies:
|
|
93
79
|
- - "~>"
|
94
80
|
- !ruby/object:Gem::Version
|
95
81
|
version: '2.6'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: concurrent-ruby
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 1.3.1
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 1.3.1
|
96
96
|
description: It offers base classes and helpers for creating MCP applications, making
|
97
97
|
it easier to integrate your Ruby/Rails application with the MCP standard
|
98
98
|
email:
|
@@ -106,8 +106,6 @@ files:
|
|
106
106
|
- README.md
|
107
107
|
- Rakefile
|
108
108
|
- app/controllers/action_mcp/mcp_controller.rb
|
109
|
-
- app/controllers/action_mcp/messages_controller.rb
|
110
|
-
- app/controllers/action_mcp/sse_controller.rb
|
111
109
|
- app/controllers/action_mcp/unified_controller.rb
|
112
110
|
- app/models/action_mcp.rb
|
113
111
|
- app/models/action_mcp/application_record.rb
|
@@ -119,14 +117,7 @@ files:
|
|
119
117
|
- app/models/concerns/mcp_console_helpers.rb
|
120
118
|
- app/models/concerns/mcp_message_inspect.rb
|
121
119
|
- config/routes.rb
|
122
|
-
- db/migrate/
|
123
|
-
- db/migrate/20250314230152_add_is_ping_to_session_message.rb
|
124
|
-
- db/migrate/20250316005021_create_action_mcp_session_subscriptions.rb
|
125
|
-
- db/migrate/20250316005649_create_action_mcp_session_resources.rb
|
126
|
-
- db/migrate/20250324203409_remove_session_message_text.rb
|
127
|
-
- db/migrate/20250327124131_add_sse_event_counter_to_action_mcp_sessions.rb
|
128
|
-
- db/migrate/20250329120300_add_registries_to_sessions.rb
|
129
|
-
- db/migrate/20250329150312_create_action_mcp_sse_events.rb
|
120
|
+
- db/migrate/20250512154359_consolidated_migration.rb
|
130
121
|
- exe/actionmcp_cli
|
131
122
|
- lib/action_mcp.rb
|
132
123
|
- lib/action_mcp/base_response.rb
|
@@ -147,7 +138,6 @@ files:
|
|
147
138
|
- lib/action_mcp/client/roots.rb
|
148
139
|
- lib/action_mcp/client/server.rb
|
149
140
|
- lib/action_mcp/client/sse_client.rb
|
150
|
-
- lib/action_mcp/client/stdio_client.rb
|
151
141
|
- lib/action_mcp/client/toolbox.rb
|
152
142
|
- lib/action_mcp/client/tools.rb
|
153
143
|
- lib/action_mcp/configuration.rb
|
@@ -177,6 +167,7 @@ files:
|
|
177
167
|
- lib/action_mcp/resource_templates_registry.rb
|
178
168
|
- lib/action_mcp/server.rb
|
179
169
|
- lib/action_mcp/server/capabilities.rb
|
170
|
+
- lib/action_mcp/server/configuration.rb
|
180
171
|
- lib/action_mcp/server/json_rpc_handler.rb
|
181
172
|
- lib/action_mcp/server/messaging.rb
|
182
173
|
- lib/action_mcp/server/notifications.rb
|
@@ -186,6 +177,8 @@ files:
|
|
186
177
|
- lib/action_mcp/server/roots.rb
|
187
178
|
- lib/action_mcp/server/sampling.rb
|
188
179
|
- lib/action_mcp/server/sampling_request.rb
|
180
|
+
- lib/action_mcp/server/simple_pub_sub.rb
|
181
|
+
- lib/action_mcp/server/solid_cable_adapter.rb
|
189
182
|
- lib/action_mcp/server/tools.rb
|
190
183
|
- lib/action_mcp/server/transport_handler.rb
|
191
184
|
- lib/action_mcp/sse_listener.rb
|
@@ -199,6 +192,8 @@ files:
|
|
199
192
|
- lib/action_mcp/uri_ambiguity_checker.rb
|
200
193
|
- lib/action_mcp/version.rb
|
201
194
|
- lib/actionmcp.rb
|
195
|
+
- lib/generators/action_mcp/config/config_generator.rb
|
196
|
+
- lib/generators/action_mcp/config/templates/mcp.yml
|
202
197
|
- lib/generators/action_mcp/install/install_generator.rb
|
203
198
|
- lib/generators/action_mcp/install/templates/application_mcp_prompt.rb
|
204
199
|
- lib/generators/action_mcp/install/templates/application_mcp_res_template.rb
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ActionMCP
|
4
|
-
class MessagesController < MCPController
|
5
|
-
REQUIRED_PROTOCOL_VERSION = "2024-11-05"
|
6
|
-
|
7
|
-
include Instrumentation::ControllerRuntime
|
8
|
-
|
9
|
-
# @route POST / (sse_in)
|
10
|
-
def create
|
11
|
-
handle_post_message(params, response)
|
12
|
-
head response.status
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def transport_handler
|
18
|
-
Server::TransportHandler.new(mcp_session)
|
19
|
-
end
|
20
|
-
|
21
|
-
def json_rpc_handler
|
22
|
-
@json_rpc_handler ||= Server::JsonRpcHandler.new(transport_handler)
|
23
|
-
end
|
24
|
-
|
25
|
-
def handle_post_message(params, response)
|
26
|
-
filtered_params = filter_jsonrpc_params(params)
|
27
|
-
json_rpc_handler.call(filtered_params)
|
28
|
-
response.status = :accepted
|
29
|
-
rescue StandardError => _e
|
30
|
-
response.status = :bad_request
|
31
|
-
end
|
32
|
-
|
33
|
-
def mcp_session
|
34
|
-
@mcp_session ||= Session.find_or_create_by(id: params[:session_id])
|
35
|
-
end
|
36
|
-
|
37
|
-
def filter_jsonrpc_params(params)
|
38
|
-
# Valid JSON-RPC keys (both request and response)
|
39
|
-
valid_keys = %w[jsonrpc method params id result error]
|
40
|
-
|
41
|
-
params.to_h.slice(*valid_keys)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|