actionmcp 0.2.0 → 0.2.4

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +133 -30
  3. data/Rakefile +0 -2
  4. data/app/controllers/action_mcp/application_controller.rb +13 -0
  5. data/app/controllers/action_mcp/messages_controller.rb +51 -0
  6. data/app/controllers/action_mcp/sse_controller.rb +151 -0
  7. data/config/routes.rb +4 -0
  8. data/exe/actionmcp_cli +221 -0
  9. data/lib/action_mcp/capability.rb +52 -0
  10. data/lib/action_mcp/client.rb +243 -1
  11. data/lib/action_mcp/configuration.rb +50 -1
  12. data/lib/action_mcp/content/audio.rb +9 -0
  13. data/lib/action_mcp/content/image.rb +9 -0
  14. data/lib/action_mcp/content/resource.rb +13 -0
  15. data/lib/action_mcp/content/text.rb +7 -0
  16. data/lib/action_mcp/content.rb +11 -6
  17. data/lib/action_mcp/engine.rb +34 -0
  18. data/lib/action_mcp/gem_version.rb +2 -2
  19. data/lib/action_mcp/integer_array.rb +6 -0
  20. data/lib/action_mcp/json_rpc/json_rpc_error.rb +21 -0
  21. data/lib/action_mcp/json_rpc/notification.rb +8 -0
  22. data/lib/action_mcp/json_rpc/request.rb +14 -0
  23. data/lib/action_mcp/json_rpc/response.rb +32 -1
  24. data/lib/action_mcp/json_rpc.rb +1 -6
  25. data/lib/action_mcp/json_rpc_handler.rb +106 -0
  26. data/lib/action_mcp/logging.rb +19 -0
  27. data/lib/action_mcp/prompt.rb +30 -46
  28. data/lib/action_mcp/prompts_registry.rb +13 -1
  29. data/lib/action_mcp/registry_base.rb +47 -28
  30. data/lib/action_mcp/renderable.rb +26 -0
  31. data/lib/action_mcp/resource.rb +3 -1
  32. data/lib/action_mcp/server.rb +4 -1
  33. data/lib/action_mcp/string_array.rb +5 -0
  34. data/lib/action_mcp/tool.rb +16 -53
  35. data/lib/action_mcp/tools_registry.rb +14 -1
  36. data/lib/action_mcp/transport/capabilities.rb +21 -0
  37. data/lib/action_mcp/transport/messaging.rb +20 -0
  38. data/lib/action_mcp/transport/prompts.rb +19 -0
  39. data/lib/action_mcp/transport/sse_client.rb +309 -0
  40. data/lib/action_mcp/transport/stdio_client.rb +117 -0
  41. data/lib/action_mcp/transport/tools.rb +20 -0
  42. data/lib/action_mcp/transport/transport_base.rb +125 -0
  43. data/lib/action_mcp/transport.rb +1 -235
  44. data/lib/action_mcp/transport_handler.rb +54 -0
  45. data/lib/action_mcp/version.rb +4 -5
  46. data/lib/action_mcp.rb +36 -33
  47. data/lib/generators/action_mcp/prompt/templates/prompt.rb.erb +3 -1
  48. data/lib/generators/action_mcp/tool/templates/tool.rb.erb +5 -1
  49. data/lib/tasks/action_mcp_tasks.rake +28 -5
  50. metadata +66 -9
  51. data/exe/action_mcp_stdio +0 -0
  52. data/lib/action_mcp/railtie.rb +0 -27
  53. data/lib/action_mcp/resources_bank.rb +0 -94
data/exe/actionmcp_cli ADDED
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup' # Ensure correct gem dependencies
5
+ require 'optparse'
6
+ require 'multi_json'
7
+ require 'actionmcp'
8
+ require 'action_mcp/client'
9
+ require 'securerandom'
10
+ require 'logger'
11
+
12
+ # Default options
13
+ options = {
14
+ logging_level: "INFO",
15
+ auto_initialize: true
16
+ }
17
+
18
+ # Set up logger
19
+ logger = Logger.new(STDOUT)
20
+ logger.formatter = proc do |severity, _, _, msg|
21
+ "#{severity}: #{msg}\n"
22
+ end
23
+
24
+ # Parse command-line arguments
25
+ parser = OptionParser.new do |opts|
26
+ opts.banner = "Usage: mcp_client ENDPOINT [options]"
27
+ opts.on("-l", "--log-level LEVEL", "Set log level (DEBUG, INFO, WARN, ERROR)") do |l|
28
+ options[:logging_level] = l.upcase
29
+ logger.level = Logger.const_get(l.upcase) rescue Logger::INFO
30
+ end
31
+ opts.on("--no-auto-init", "Don't automatically initialize the connection") do
32
+ options[:auto_initialize] = false
33
+ end
34
+ opts.on("-h", "--help", "Show this help message") do
35
+ puts opts
36
+ exit
37
+ end
38
+ end
39
+
40
+ # Extract first argument as endpoint
41
+ endpoint = ARGV.shift
42
+
43
+ # Parse remaining options
44
+ parser.parse!(ARGV)
45
+
46
+ if endpoint.nil?
47
+ puts "Error: You must provide an MCP endpoint."
48
+ puts parser
49
+ exit 1
50
+ end
51
+
52
+ # Function to generate a unique request ID
53
+ def generate_request_id
54
+ SecureRandom.uuid
55
+ end
56
+
57
+ # Function to parse command shortcuts and return a Request object
58
+ def parse_command(input)
59
+ parts = input.strip.split(/\s+/)
60
+ command = parts.shift
61
+
62
+ case command
63
+ when "call_tool"
64
+ tool_name = parts.shift
65
+ return nil unless tool_name
66
+
67
+ arguments = {}
68
+ parts.each do |arg|
69
+ key, value = arg.split(":", 2)
70
+ next unless value
71
+
72
+ # Try to convert the value to appropriate type
73
+ parsed_value = case value
74
+ when /^\d+$/
75
+ value.to_i
76
+ when /^\d+\.\d+$/
77
+ value.to_f
78
+ when "true"
79
+ true
80
+ when "false"
81
+ false
82
+ when "null"
83
+ nil
84
+ else
85
+ value
86
+ end
87
+
88
+ arguments[key] = parsed_value
89
+ end
90
+
91
+ ActionMCP::JsonRpc::Request.new(
92
+ id: generate_request_id,
93
+ method: "tools/get",
94
+ params: {
95
+ "name" => tool_name,
96
+ "arguments" => arguments
97
+ }
98
+ )
99
+ when "list_tools"
100
+ ActionMCP::JsonRpc::Request.new(
101
+ id: generate_request_id,
102
+ method: "tools/list"
103
+ )
104
+ when "list_prompts"
105
+ ActionMCP::JsonRpc::Request.new(
106
+ id: generate_request_id,
107
+ method: "prompts/list"
108
+ )
109
+ else
110
+ nil
111
+ end
112
+ end
113
+
114
+ # Help message for shortcuts
115
+ def print_help
116
+ puts "Available shortcuts:"
117
+ puts " list_tools"
118
+ puts " - Get a list of available tools"
119
+ puts " call_tool TOOL_NAME PARAM1:VALUE1 PARAM2:VALUE2 ..."
120
+ puts " - Sends a tools/get request with the specified tool and parameters"
121
+ puts " list_prompts"
122
+ puts " - Get a list of available prompts"
123
+ puts " get_prompt PROMPT_NAME PARAM1:VALUE1 PARAM2:VALUE2 ..."
124
+ puts " - Sends a prompts/get request with the specified prompt and arguments"
125
+ puts " help - Show this help message"
126
+ puts " exit - Quit the client"
127
+ puts "Otherwise, enter a raw JSON-RPC request to send directly"
128
+ end
129
+
130
+ # Initialize and start the client
131
+ client = ActionMCP.create_client(endpoint, logger: logger)
132
+
133
+ # Start the transport
134
+ unless client.connect
135
+ error_msg = client.connection_error || "Unknown connection error"
136
+ puts "\nERROR: Failed to connect to MCP server at #{endpoint}"
137
+ puts "Reason: #{error_msg}"
138
+ puts "\nPlease check that:"
139
+ puts " 1. The server is running"
140
+ puts " 2. The endpoint URL/address is correct"
141
+ puts " 3. Any required firewall ports are open"
142
+
143
+ if endpoint =~ /\Ahttps?:\/\//
144
+ puts " 4. The URL includes the correct protocol, host, and port"
145
+ puts " For example: http://localhost:3000/action_mcp"
146
+ end
147
+
148
+ exit 1
149
+ end
150
+
151
+ Signal.trap("INT") do
152
+ puts "\nReceived Ctrl+C. Disconnecting..."
153
+ client.disconnect
154
+ puts "MCP Client stopped."
155
+ exit 0
156
+ end
157
+
158
+ # Main REPL loop
159
+ loop do
160
+ print "mcp> "
161
+ input = gets&.chomp
162
+ break unless input # Handle EOF
163
+ next if input.empty?
164
+
165
+ case input.downcase
166
+ when "exit"
167
+ break
168
+ when "help"
169
+ print_help
170
+ next
171
+ else
172
+ begin
173
+ # Check if input is a command shortcut
174
+ if input.start_with?("call_tool")
175
+ request = parse_command(input)
176
+ logger.debug("Parsed shortcut to: #{request.to_h}") if request
177
+ elsif input.start_with?("connect") || input.start_with?("initialize")
178
+ request = parse_command(input)
179
+ logger.debug("Initializing connection with: #{request.to_h}") if request
180
+ elsif input.start_with?("list_tools") || input.start_with?("list_prompts")
181
+ request = parse_command(input)
182
+ logger.debug("Requesting tool list: #{request.to_h}") if request
183
+ else
184
+ # Try parsing as JSON and creating a Request object
185
+ begin
186
+ json = MultiJson.load(input)
187
+ # Validate that the parsed JSON has the required fields
188
+ if json["method"]
189
+ request = ActionMCP::JsonRpc::Request.new(
190
+ id: json["id"] || generate_request_id,
191
+ method: json["method"],
192
+ params: json["params"]
193
+ )
194
+ else
195
+ puts "Invalid JSON-RPC request: missing 'method' field"
196
+ next
197
+ end
198
+ rescue MultiJson::ParseError => e
199
+ puts "Invalid input: not a valid command or JSON. #{e.message}"
200
+ next
201
+ rescue ActionMCP::JsonRpc::JsonRpcError => e
202
+ puts "Invalid JSON-RPC request: #{e.message}"
203
+ next
204
+ end
205
+ end
206
+
207
+ if request
208
+ client.send_request(request.to_h)
209
+ else
210
+ puts "Invalid command format. Type 'help' for available commands."
211
+ end
212
+ rescue StandardError => e
213
+ puts "Error: #{e.message}"
214
+ puts e.backtrace.first(5) if logger.level == Logger::DEBUG
215
+ end
216
+ end
217
+ end
218
+
219
+ puts "Disconnecting..."
220
+ client.disconnect
221
+ puts "MCP Client stopped."
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "renderable"
4
+
5
+ module ActionMCP
6
+ class Capability
7
+ include ActiveModel::Model
8
+ include ActiveModel::Attributes
9
+ include Renderable
10
+
11
+ class_attribute :_capability_name, instance_accessor: false
12
+ class_attribute :_description, instance_accessor: false, default: ""
13
+ class_attribute :abstract_tool, instance_accessor: false, default: false
14
+
15
+ # use _capability_name or default_capability_name
16
+ def self.capability_name
17
+ _capability_name || default_capability_name
18
+ end
19
+
20
+ def self.abstract_capability
21
+ @abstract_tool ||= false # Default to false, unique to each class
22
+ end
23
+
24
+ def self.abstract_capability=(value)
25
+ @abstract_tool = value
26
+ end
27
+
28
+ # Marks this tool as abstract so that it won’t be available for use.
29
+ # If the tool is registered in ToolsRegistry, it is unregistered.
30
+ #
31
+ # @return [void]
32
+ def self.abstract!
33
+ self.abstract_capability = true
34
+ end
35
+
36
+ # Returns whether this tool is abstract.
37
+ #
38
+ # @return [Boolean] true if abstract, false otherwise.
39
+ def self.abstract?
40
+ abstract_capability
41
+ end
42
+
43
+
44
+ def self.description(text = nil)
45
+ if text
46
+ self._description = text
47
+ else
48
+ _description
49
+ end
50
+ end
51
+ end
52
+ end
@@ -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