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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +138 -4
  3. data/app/controllers/action_mcp/unified_controller.rb +1 -1
  4. data/config/routes.rb +4 -9
  5. data/db/migrate/20250512154359_consolidated_migration.rb +146 -0
  6. data/exe/actionmcp_cli +8 -1
  7. data/lib/action_mcp/client.rb +3 -9
  8. data/lib/action_mcp/configuration.rb +2 -4
  9. data/lib/action_mcp/engine.rb +1 -1
  10. data/lib/action_mcp/server/configuration.rb +63 -0
  11. data/lib/action_mcp/server/simple_pub_sub.rb +145 -0
  12. data/lib/action_mcp/server/solid_cable_adapter.rb +222 -0
  13. data/lib/action_mcp/server.rb +84 -2
  14. data/lib/action_mcp/sse_listener.rb +3 -3
  15. data/lib/action_mcp/tool.rb +3 -7
  16. data/lib/action_mcp/version.rb +1 -1
  17. data/lib/action_mcp.rb +3 -3
  18. data/lib/generators/action_mcp/config/config_generator.rb +29 -0
  19. data/lib/generators/action_mcp/config/templates/mcp.yml +36 -0
  20. metadata +21 -26
  21. data/app/controllers/action_mcp/messages_controller.rb +0 -44
  22. data/app/controllers/action_mcp/sse_controller.rb +0 -179
  23. data/db/migrate/20250308122801_create_action_mcp_sessions.rb +0 -32
  24. data/db/migrate/20250314230152_add_is_ping_to_session_message.rb +0 -8
  25. data/db/migrate/20250316005021_create_action_mcp_session_subscriptions.rb +0 -16
  26. data/db/migrate/20250316005649_create_action_mcp_session_resources.rb +0 -25
  27. data/db/migrate/20250324203409_remove_session_message_text.rb +0 -7
  28. data/db/migrate/20250327124131_add_sse_event_counter_to_action_mcp_sessions.rb +0 -7
  29. data/db/migrate/20250329120300_add_registries_to_sessions.rb +0 -9
  30. data/db/migrate/20250329150312_create_action_mcp_sse_events.rb +0 -16
  31. 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
@@ -1,13 +1,95 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # TODO: move all server related code here before version 1.0.0
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 ||= ActionCable::Server::Base.new
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 Action Cable adapter.
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 ActionCable's adapter
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 ActionCable adapter
33
+ # Subscribe using the PubSub adapter
34
34
  adapter.subscribe(session_key, message_callback, success_callback)
35
35
 
36
36
  wait_for_subscription
@@ -60,14 +60,10 @@ module ActionMCP
60
60
  annotate(:readOnly, enabled)
61
61
  end
62
62
 
63
- # Return annotations based on protocol version
63
+ # Return annotations for the tool
64
64
  def annotations_for_protocol(protocol_version = nil)
65
- # Only include annotations for 2025+ protocols
66
- if protocol_version.nil? || protocol_version == "2024-11-05"
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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative "gem_version"
4
4
  module ActionMCP
5
- VERSION = "0.32.1"
5
+ VERSION = "0.50.0"
6
6
 
7
7
  class << self
8
8
  alias version gem_version
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 = "2024-11-05" # Default version
38
- CURRENT_VERSION = "2025-03-26" # Current version for the /mcp endpoint
39
- SUPPORTED_VERSIONS = %w[2024-11-05 2025-03-26].freeze
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.32.1
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/20250308122801_create_action_mcp_sessions.rb
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