actionmcp 0.2.0 → 0.2.3

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +133 -30
  3. data/Rakefile +0 -2
  4. data/exe/actionmcp_cli +221 -0
  5. data/lib/action_mcp/capability.rb +52 -0
  6. data/lib/action_mcp/client.rb +243 -1
  7. data/lib/action_mcp/configuration.rb +50 -1
  8. data/lib/action_mcp/content/audio.rb +9 -0
  9. data/lib/action_mcp/content/image.rb +9 -0
  10. data/lib/action_mcp/content/resource.rb +13 -0
  11. data/lib/action_mcp/content/text.rb +7 -0
  12. data/lib/action_mcp/content.rb +11 -6
  13. data/lib/action_mcp/engine.rb +34 -0
  14. data/lib/action_mcp/gem_version.rb +2 -2
  15. data/lib/action_mcp/integer_array.rb +6 -0
  16. data/lib/action_mcp/json_rpc/json_rpc_error.rb +21 -0
  17. data/lib/action_mcp/json_rpc/notification.rb +8 -0
  18. data/lib/action_mcp/json_rpc/request.rb +14 -0
  19. data/lib/action_mcp/json_rpc/response.rb +32 -1
  20. data/lib/action_mcp/json_rpc.rb +1 -6
  21. data/lib/action_mcp/json_rpc_handler.rb +106 -0
  22. data/lib/action_mcp/logging.rb +19 -0
  23. data/lib/action_mcp/prompt.rb +30 -46
  24. data/lib/action_mcp/prompts_registry.rb +13 -1
  25. data/lib/action_mcp/registry_base.rb +47 -28
  26. data/lib/action_mcp/renderable.rb +26 -0
  27. data/lib/action_mcp/resource.rb +3 -1
  28. data/lib/action_mcp/server.rb +4 -1
  29. data/lib/action_mcp/string_array.rb +5 -0
  30. data/lib/action_mcp/tool.rb +16 -53
  31. data/lib/action_mcp/tools_registry.rb +14 -1
  32. data/lib/action_mcp/transport/capabilities.rb +21 -0
  33. data/lib/action_mcp/transport/messaging.rb +20 -0
  34. data/lib/action_mcp/transport/prompts.rb +19 -0
  35. data/lib/action_mcp/transport/sse_client.rb +309 -0
  36. data/lib/action_mcp/transport/stdio_client.rb +117 -0
  37. data/lib/action_mcp/transport/tools.rb +20 -0
  38. data/lib/action_mcp/transport/transport_base.rb +125 -0
  39. data/lib/action_mcp/transport.rb +1 -235
  40. data/lib/action_mcp/transport_handler.rb +54 -0
  41. data/lib/action_mcp/version.rb +4 -5
  42. data/lib/action_mcp.rb +36 -33
  43. data/lib/generators/action_mcp/prompt/templates/prompt.rb.erb +3 -1
  44. data/lib/generators/action_mcp/tool/templates/tool.rb.erb +5 -1
  45. data/lib/tasks/action_mcp_tasks.rake +28 -5
  46. metadata +62 -9
  47. data/exe/action_mcp_stdio +0 -0
  48. data/lib/action_mcp/railtie.rb +0 -27
  49. data/lib/action_mcp/resources_bank.rb +0 -94
@@ -1,7 +1,249 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ## TODO: Adding this so i don't forget
4
3
  module ActionMCP
4
+ # Create a client appropriate for the given endpoint
5
+ # @param endpoint [String] The endpoint to connect to (URL or command)
6
+ # @param logger [Logger] The logger to use
7
+ # @return [Client] An SSEClient or StdioClient depending on the endpoint
8
+ def self.create_client(endpoint, logger: Logger.new(STDOUT))
9
+ if endpoint =~ /\Ahttps?:\/\//
10
+ logger.info("Creating SSE client for endpoint: #{endpoint}")
11
+ SSEClient.new(endpoint, logger: logger)
12
+ else
13
+ logger.info("Creating STDIO client for command: #{endpoint}")
14
+ StdioClient.new(endpoint, logger: logger)
15
+ end
16
+ end
17
+
18
+ # Base client class for MCP protocol
5
19
  class Client
20
+ attr_reader :logger, :capabilities, :type, :connection_error
21
+
22
+ def initialize(logger: Logger.new(STDOUT))
23
+ @logger = logger
24
+ @connected = false
25
+ @initialize_request_id = SecureRandom.uuid_v7
26
+ @server_capabilities = nil
27
+ @message_callback = nil
28
+ @error_callback = nil
29
+ @connection_error = nil
30
+ end
31
+
32
+ def connect
33
+ return true if @connected
34
+
35
+ begin
36
+ logger.info("Connecting to MCP server...")
37
+ @connection_error = nil
38
+
39
+ # Start transport with proper error handling
40
+ success = start_transport
41
+
42
+ unless success
43
+ logger.error("Failed to establish connection to MCP server")
44
+ return false
45
+ end
46
+
47
+ @connected = true
48
+ logger.info("Connected to MCP server")
49
+ true
50
+ rescue => e
51
+ @connection_error = e.message
52
+ logger.error("Failed to connect to MCP server: #{e.message}")
53
+ false
54
+ end
55
+ end
56
+
57
+ # Disconnect from the MCP server
58
+ # @return [Boolean] true if disconnection was successful
59
+ def disconnect
60
+ return true unless @connected
61
+
62
+ begin
63
+ stop_transport
64
+ @connected = false
65
+ logger.info("Disconnected from MCP server")
66
+ true
67
+ rescue => e
68
+ logger.error("Error disconnecting from MCP server: #{e.message}")
69
+ false
70
+ end
71
+ end
72
+
73
+ # Send a request to the MCP server
74
+ # @param payload [Hash, String] The request payload
75
+ # @return [Boolean] true if the request was sent successfully
76
+ def send_request(payload)
77
+ unless @connected
78
+ logger.error("Cannot send request - not connected")
79
+ return false
80
+ end
81
+
82
+ begin
83
+ json = prepare_payload(payload)
84
+ send_message(json)
85
+ true
86
+ rescue => e
87
+ logger.error("Failed to send request: #{e.message}")
88
+ false
89
+ end
90
+ end
91
+
92
+ # Check if the client is ready to send requests
93
+ # @return [Boolean] true if the client is connected and ready
94
+ def ready?
95
+ @connected && transport_ready?
96
+ end
97
+
98
+ # Set a callback for incoming messages
99
+ # @yield [message] Called when a message is received
100
+ # @yieldparam message The received message
101
+ def on_message(&block)
102
+ @message_callback = block
103
+ end
104
+
105
+ # Set a callback for errors
106
+ # @yield [error] Called when an error occurs
107
+ # @yieldparam error The error that occurred
108
+ def on_error(&block)
109
+ @error_callback = block
110
+ end
111
+
112
+ # Get the server capabilities
113
+ # @return [Hash, nil] The server capabilities, or nil if not connected
114
+ def server_capabilities
115
+ @server_capabilities
116
+ end
117
+
118
+ protected
119
+
120
+ # Start the transport - implemented by subclasses
121
+ def start_transport
122
+ raise NotImplementedError, "Subclasses must implement start_transport"
123
+ end
124
+
125
+ # Stop the transport
126
+ def stop_transport
127
+ @transport.stop
128
+ end
129
+
130
+ # Send a message through the transport
131
+ def send_message(json)
132
+ @transport.send_message(json)
133
+ end
134
+
135
+ # Check if the transport is ready
136
+ def transport_ready?
137
+ @transport.ready?
138
+ end
139
+
140
+ private
141
+
142
+ # Prepare a payload for sending
143
+ # @param payload [Hash, String] The payload to prepare
144
+ # @return [String] The JSON-encoded payload
145
+ def prepare_payload(payload)
146
+ case payload
147
+ when String
148
+ # Assume it's already JSON
149
+ payload
150
+ else
151
+ # Try to convert to JSON
152
+ MultiJson.dump(payload)
153
+ end
154
+ end
155
+ end
156
+
157
+ # MCP client using Server-Sent Events (SSE) transport
158
+ class SSEClient < Client
159
+ # Initialize an SSE client
160
+ # @param endpoint [String] The SSE endpoint URL
161
+ # @param logger [Logger] The logger to use
162
+ def initialize(endpoint, logger: Logger.new(STDOUT))
163
+ super(logger: logger)
164
+ @endpoint = endpoint
165
+ @transport = Transport::SSEClient.new(endpoint, logger: logger)
166
+ @type = :sse
167
+
168
+ # Set up callbacks after transport is initialized
169
+ setup_callbacks
170
+ end
171
+
172
+ protected
173
+
174
+ def start_transport
175
+ begin
176
+ @transport.start(@initialize_request_id)
177
+ true
178
+ rescue Transport::SSEClient::ConnectionError => e
179
+ @connection_error = e.message
180
+ @error_callback&.call(e)
181
+ false
182
+ rescue => e
183
+ @connection_error = e.message
184
+ @error_callback&.call(e)
185
+ false
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ def setup_callbacks
192
+ @transport.on_message do |message|
193
+ # Check if this is a response to our initialize request
194
+ puts @initialize_request_id
195
+ if message&.id == @initialize_request_id
196
+ @transport.handle_initialize_response(message)
197
+ else
198
+ puts "\e[32mCalling message callback\e[0m"
199
+ @message_callback&.call(message)
200
+ end
201
+ end
202
+
203
+ @transport.on_error do |error|
204
+ @error_callback&.call(error)
205
+ end
206
+ end
207
+ end
208
+
209
+ # MCP client using Standard I/O (STDIO) transport
210
+ class StdioClient < Client
211
+ # Initialize a STDIO client
212
+ # @param command [String] The command to execute
213
+ # @param logger [Logger] The logger to use
214
+ def initialize(command, logger: Logger.new(STDOUT))
215
+ super(logger: logger)
216
+ @command = command
217
+ @transport = Transport::StdioClient.new(command, logger: logger)
218
+ @type = :stdio
219
+
220
+ # Set up callbacks after transport is initialized
221
+ setup_callbacks
222
+ end
223
+
224
+ protected
225
+
226
+ def start_transport
227
+ @transport.start
228
+ # For STDIO, we'll send the capabilities from the connect method
229
+ # after this method completes and @connected is set to true
230
+ end
231
+
232
+ private
233
+
234
+ def setup_callbacks
235
+ @transport.on_message do |message|
236
+ # Check if this is a response to our initialize request
237
+ if message && message.id && message.id == @initialize_request_id
238
+ @transport.handle_initialize_response(message)
239
+ end
240
+
241
+ @message_callback&.call(message)
242
+ end
243
+
244
+ @transport.on_error do |error|
245
+ @error_callback&.call(error)
246
+ end
247
+ end
6
248
  end
7
249
  end
@@ -3,18 +3,67 @@
3
3
  module ActionMCP
4
4
  # Configuration class to hold settings for the ActionMCP server.
5
5
  class Configuration
6
+ # @!attribute name
7
+ # @return [String] The name of the MCP Server.
8
+ # @!attribute version
9
+ # @return [String] The version of the MCP Server.
10
+ # @!attribute logging_enabled
11
+ # @return [Boolean] Whether logging is enabled.
12
+ # @!attribute list_changed
13
+ # @return [Boolean] Whether to send a listChanged notification for tools, prompts, and resources.
14
+ # @!attribute resources_subscribe
15
+ # @return [Boolean] Whether to subscribe to resources.
16
+ # @!attribute logging_level
17
+ # @return [Symbol] The logging level.
6
18
  attr_accessor :name, :version, :logging_enabled,
7
19
  # Right now, if enabled, the server will send a listChanged notification for tools, prompts, and resources.
8
20
  # We can make it more granular in the future, but for now, it's a simple boolean.
9
21
  :list_changed,
10
- :resources_subscribe
22
+ :resources_subscribe,
23
+ :logging_level
11
24
 
25
+ # Initializes a new Configuration instance.
26
+ #
27
+ # @return [void]
12
28
  def initialize
13
29
  # Use Rails.application values if available, or fallback to defaults.
14
30
  @name = defined?(Rails) && Rails.respond_to?(:application) && Rails.application.respond_to?(:name) ? Rails.application.name : "ActionMCP"
15
31
  @version = defined?(Rails) && Rails.respond_to?(:application) && Rails.application.respond_to?(:version) ? Rails.application.version.to_s.presence : "0.0.1"
16
32
  @logging_enabled = true
17
33
  @list_changed = false
34
+ @logging_level = :info
35
+ end
36
+
37
+ # Returns a hash of capabilities.
38
+ #
39
+ # @return [Hash] A hash containing the resources capabilities.
40
+ def capabilities
41
+ capabilities = {}
42
+ # Only include each capability if the corresponding registry is non-empty.
43
+ capabilities[:tools] = { listChanged: @list_changed } if ToolsRegistry.non_abstract.any?
44
+ capabilities[:prompts] = { listChanged: @list_changed } if PromptsRegistry.non_abstract.any?
45
+ capabilities[:logging] = {} if @logging_enabled
46
+ # capabilities[:resources] = { subscribe: @resources_subscribe,
47
+ # listChanged: @list_changed }.compact
48
+ { capabilities: capabilities }
49
+ end
50
+ end
51
+
52
+ class << self
53
+ attr_accessor :server
54
+ # Returns the configuration instance.
55
+ #
56
+ # @return [Configuration] the configuration instance
57
+ def configuration
58
+ @configuration ||= Configuration.new
59
+ end
60
+
61
+ # Configures the ActionMCP module.
62
+ #
63
+ # @yield [configuration] the configuration instance
64
+ # @return [void]
65
+ def configure
66
+ yield(configuration)
18
67
  end
19
68
  end
20
69
  end
@@ -4,14 +4,23 @@ module ActionMCP
4
4
  module Content
5
5
  # Audio content includes a base64-encoded audio clip and its MIME type.
6
6
  class Audio < Base
7
+ # @return [String] The base64-encoded audio data.
8
+ # @return [String] The MIME type of the audio data.
7
9
  attr_reader :data, :mime_type
8
10
 
11
+ # Initializes a new Audio content.
12
+ #
13
+ # @param data [String] The base64-encoded audio data.
14
+ # @param mime_type [String] The MIME type of the audio data.
9
15
  def initialize(data, mime_type)
10
16
  super("audio")
11
17
  @data = data
12
18
  @mime_type = mime_type
13
19
  end
14
20
 
21
+ # Returns a hash representation of the audio content.
22
+ #
23
+ # @return [Hash] The hash representation of the audio content.
15
24
  def to_h
16
25
  super.merge(data: @data, mimeType: @mime_type)
17
26
  end
@@ -4,14 +4,23 @@ module ActionMCP
4
4
  module Content
5
5
  # Image content includes a base64-encoded image and its MIME type.
6
6
  class Image < Base
7
+ # @return [String] The base64-encoded image data.
8
+ # @return [String] The MIME type of the image data.
7
9
  attr_reader :data, :mime_type
8
10
 
11
+ # Initializes a new Image content.
12
+ #
13
+ # @param data [String] The base64-encoded image data.
14
+ # @param mime_type [String] The MIME type of the image data.
9
15
  def initialize(data, mime_type)
10
16
  super("image")
11
17
  @data = data
12
18
  @mime_type = mime_type
13
19
  end
14
20
 
21
+ # Returns a hash representation of the image content.
22
+ #
23
+ # @return [Hash] The hash representation of the image content.
15
24
  def to_h
16
25
  super.merge(data: @data, mimeType: @mime_type)
17
26
  end
@@ -5,8 +5,18 @@ module ActionMCP
5
5
  # Resource content references a server-managed resource.
6
6
  # It includes a URI, MIME type, and optionally text content or a base64-encoded blob.
7
7
  class Resource < Base
8
+ # @return [String] The URI of the resource.
9
+ # @return [String] The MIME type of the resource.
10
+ # @return [String, nil] The text content of the resource (optional).
11
+ # @return [String, nil] The base64-encoded blob of the resource (optional).
8
12
  attr_reader :uri, :mime_type, :text, :blob
9
13
 
14
+ # Initializes a new Resource content.
15
+ #
16
+ # @param uri [String] The URI of the resource.
17
+ # @param mime_type [String] The MIME type of the resource.
18
+ # @param text [String, nil] The text content of the resource (optional).
19
+ # @param blob [String, nil] The base64-encoded blob of the resource (optional).
10
20
  def initialize(uri, mime_type, text: nil, blob: nil)
11
21
  super("resource")
12
22
  @uri = uri
@@ -15,6 +25,9 @@ module ActionMCP
15
25
  @blob = blob
16
26
  end
17
27
 
28
+ # Returns a hash representation of the resource content.
29
+ #
30
+ # @return [Hash] The hash representation of the resource content.
18
31
  def to_h
19
32
  resource_data = { uri: @uri, mimeType: @mime_type }
20
33
  resource_data[:text] = @text if @text
@@ -4,13 +4,20 @@ module ActionMCP
4
4
  module Content
5
5
  # Text content represents plain text messages.
6
6
  class Text < Base
7
+ # @return [String] The text content.
7
8
  attr_reader :text
8
9
 
10
+ # Initializes a new Text content.
11
+ #
12
+ # @param text [String] The text content.
9
13
  def initialize(text)
10
14
  super("text")
11
15
  @text = text.to_s
12
16
  end
13
17
 
18
+ # Returns a hash representation of the text content.
19
+ #
20
+ # @return [Hash] The hash representation of the text content.
14
21
  def to_h
15
22
  super.merge(text: @text)
16
23
  end
@@ -1,28 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionMCP
4
+ # Module for managing content within ActionMCP.
4
5
  module Content
5
- extend ActiveSupport::Autoload
6
6
  # Base class for MCP content items.
7
7
  class Base
8
+ # @return [Symbol] The type of content.
8
9
  attr_reader :type
9
10
 
11
+ # Initializes a new content item.
12
+ #
13
+ # @param type [Symbol] The type of content.
10
14
  def initialize(type)
11
15
  @type = type
12
16
  end
13
17
 
18
+ # Returns a hash representation of the content.
19
+ #
20
+ # @return [Hash] The hash representation.
14
21
  def to_h
15
22
  { type: @type }
16
23
  end
17
24
 
25
+ # Returns a JSON representation of the content.
26
+ #
27
+ # @return [String] The JSON representation.
18
28
  def to_json(*)
19
29
  MultiJson.dump(to_h, *)
20
30
  end
21
31
  end
22
-
23
- autoload :Image
24
- autoload :Text
25
- autoload :Audio
26
- autoload :Resource
27
32
  end
28
33
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "active_model/railtie"
5
+
6
+ module ActionMCP
7
+ # Engine for integrating ActionMCP with Rails applications.
8
+ class Engine < ::Rails::Engine
9
+ isolate_namespace ActionMCP
10
+
11
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
12
+ inflect.acronym "SSE"
13
+ inflect.acronym "MCP"
14
+ end
15
+ # Provide a configuration namespace for ActionMCP
16
+ config.action_mcp = ActiveSupport::OrderedOptions.new
17
+
18
+ initializer "action_mcp.configure" do |app|
19
+ options = app.config.action_mcp.to_h.symbolize_keys
20
+
21
+ # Override the default configuration if specified in the Rails app.
22
+ ActionMCP.configuration.name = options[:name] if options.key?(:name)
23
+ ActionMCP.configuration.version = options[:version] if options.key?(:version)
24
+ ActionMCP.configuration.logging_enabled = options.fetch(:logging_enabled, true)
25
+ end
26
+
27
+ # Initialize the ActionMCP logger.
28
+ initializer "action_mcp.logger" do
29
+ ActiveSupport.on_load(:action_mcp) do
30
+ self.logger = ::Rails.logger
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,7 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  module ActionMCP
4
2
  # Returns the currently loaded version of Active MCP as a +Gem::Version+.
3
+ #
4
+ # @return [Gem::Version] the currently loaded version of Active MCP
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION
7
7
  end
@@ -3,7 +3,13 @@
3
3
  module ActionMCP
4
4
  # This temporary naming extracted from MCPangea
5
5
  # If there is a better name, please suggest it or part of ActiveModel, open a PR
6
+ #
7
+ # Custom type for handling arrays of integers in ActiveModel.
6
8
  class IntegerArray < ActiveModel::Type::Value
9
+ # Casts the given value to an array of integers.
10
+ #
11
+ # @param value [Object] The value to cast.
12
+ # @return [Array<Integer>] The array of integers.
7
13
  def cast(value)
8
14
  Array(value).map(&:to_i) # Ensure all elements are integers
9
15
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module ActionMCP
4
4
  module JsonRpc
5
+ # Custom exception class for JSON-RPC errors, based on the JSON-RPC 2.0 specification.
5
6
  class JsonRpcError < StandardError
6
7
  # Define the standard JSON-RPC 2.0 error codes
7
8
  ERROR_CODES = {
@@ -31,14 +32,25 @@ module ActionMCP
31
32
  }
32
33
  }.freeze
33
34
 
35
+ # @return [Integer] The error code.
36
+ # @return [Object] The error data.
34
37
  attr_reader :code, :data
35
38
 
36
39
  # Retrieve error details by symbol.
40
+ #
41
+ # @param symbol [Symbol] The error symbol.
42
+ # @raise [ArgumentError] if the error code is unknown.
43
+ # @return [Hash] The error details.
37
44
  def self.[](symbol)
38
45
  ERROR_CODES[symbol] or raise ArgumentError, "Unknown error code: #{symbol}"
39
46
  end
40
47
 
41
48
  # Build an error hash, allowing custom message or data to override defaults.
49
+ #
50
+ # @param symbol [Symbol] The error symbol.
51
+ # @param message [String, nil] Optional custom message.
52
+ # @param data [Object, nil] Optional custom data.
53
+ # @return [Hash] The error hash.
42
54
  def self.build(symbol, message: nil, data: nil)
43
55
  error = self[symbol].dup
44
56
  error[:message] = message if message
@@ -47,6 +59,10 @@ module ActionMCP
47
59
  end
48
60
 
49
61
  # Initialize the error using a symbol key, with optional custom message and data.
62
+ #
63
+ # @param symbol [Symbol] The error symbol.
64
+ # @param message [String, nil] Optional custom message.
65
+ # @param data [Object, nil] Optional custom data.
50
66
  def initialize(symbol, message: nil, data: nil)
51
67
  error_details = self.class.build(symbol, message: message, data: data)
52
68
  @code = error_details[:code]
@@ -55,6 +71,8 @@ module ActionMCP
55
71
  end
56
72
 
57
73
  # Returns a hash formatted for a JSON-RPC error response.
74
+ #
75
+ # @return [Hash] The error hash.
58
76
  def as_json
59
77
  hash = { code: code, message: message }
60
78
  hash[:data] = data if data
@@ -62,6 +80,9 @@ module ActionMCP
62
80
  end
63
81
 
64
82
  # Converts the error hash to a JSON string.
83
+ #
84
+ # @param _args [Array] Arguments passed to MultiJson.dump.
85
+ # @return [String] The JSON string.
65
86
  def to_json(*_args)
66
87
  MultiJson.dump(as_json, *args)
67
88
  end
@@ -2,11 +2,19 @@
2
2
 
3
3
  module ActionMCP
4
4
  module JsonRpc
5
+ # Represents a JSON-RPC notification.
5
6
  Notification = Data.define(:method, :params) do
7
+ # Initializes a new Notification.
8
+ #
9
+ # @param method [String] The method name.
10
+ # @param params [Hash, nil] The parameters (optional).
6
11
  def initialize(method:, params: nil)
7
12
  super
8
13
  end
9
14
 
15
+ # Returns a hash representation of the notification.
16
+ #
17
+ # @return [Hash] The hash representation.
10
18
  def to_h
11
19
  {
12
20
  jsonrpc: "2.0",
@@ -2,12 +2,22 @@
2
2
 
3
3
  module ActionMCP
4
4
  module JsonRpc
5
+ # Represents a JSON-RPC request.
5
6
  Request = Data.define(:id, :method, :params) do
7
+ # Initializes a new Request.
8
+ #
9
+ # @param id [String, Numeric] The request identifier.
10
+ # @param method [String] The method name.
11
+ # @param params [Hash, nil] The parameters (optional).
12
+ # @raise [JsonRpcError] if the ID is invalid.
6
13
  def initialize(id:, method:, params: nil)
7
14
  validate_id(id)
8
15
  super
9
16
  end
10
17
 
18
+ # Returns a hash representation of the request.
19
+ #
20
+ # @return [Hash] The hash representation.
11
21
  def to_h
12
22
  hash = {
13
23
  jsonrpc: "2.0",
@@ -20,6 +30,10 @@ module ActionMCP
20
30
 
21
31
  private
22
32
 
33
+ # Validates the ID.
34
+ #
35
+ # @param id [Object] The ID to validate.
36
+ # @raise [JsonRpcError] if the ID is invalid.
23
37
  def validate_id(id)
24
38
  unless id.is_a?(String) || id.is_a?(Numeric)
25
39
  raise JsonRpcError.new(:invalid_params,