actionmcp 0.1.2 → 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 (51) 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 +249 -0
  7. data/lib/action_mcp/configuration.rb +55 -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 +8 -1
  12. data/lib/action_mcp/content.rb +13 -3
  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 +17 -0
  16. data/lib/action_mcp/json_rpc/json_rpc_error.rb +22 -1
  17. data/lib/action_mcp/json_rpc/notification.rb +13 -6
  18. data/lib/action_mcp/json_rpc/request.rb +26 -2
  19. data/lib/action_mcp/json_rpc/response.rb +42 -31
  20. data/lib/action_mcp/json_rpc.rb +1 -7
  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 +33 -45
  24. data/lib/action_mcp/prompts_registry.rb +32 -1
  25. data/lib/action_mcp/registry_base.rb +72 -40
  26. data/lib/action_mcp/renderable.rb +54 -0
  27. data/lib/action_mcp/resource.rb +5 -3
  28. data/lib/action_mcp/server.rb +10 -0
  29. data/lib/action_mcp/string_array.rb +14 -0
  30. data/lib/action_mcp/tool.rb +112 -102
  31. data/lib/action_mcp/tools_registry.rb +28 -3
  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 -238
  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 +40 -27
  43. data/lib/generators/action_mcp/install/install_generator.rb +2 -0
  44. data/lib/generators/action_mcp/prompt/templates/prompt.rb.erb +3 -1
  45. data/lib/generators/action_mcp/tool/templates/tool.rb.erb +5 -1
  46. data/lib/tasks/action_mcp_tasks.rake +28 -5
  47. metadata +68 -10
  48. data/exe/action_mcp_stdio +0 -0
  49. data/lib/action_mcp/json_rpc/base.rb +0 -12
  50. data/lib/action_mcp/railtie.rb +0 -27
  51. data/lib/action_mcp/resources_bank.rb +0 -96
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ module ActionMCP
5
+ module Transport
6
+ class StdioClient < TransportBase
7
+ attr_reader :received_server_message
8
+
9
+ def initialize(command, **options)
10
+ super(**options)
11
+ @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(command)
12
+ @threads_started = false
13
+ @received_server_message = false
14
+ @capabilities_sent = false
15
+ end
16
+
17
+ def start
18
+ start_output_threads
19
+
20
+ # Just log that connection is established but don't send capabilities yet
21
+ if @threads_started && @wait_thr.alive?
22
+ log_info("STDIO connection established")
23
+ else
24
+ log_error("Failed to start STDIO threads or process is not alive")
25
+ end
26
+ end
27
+
28
+ def send_message(json)
29
+ log_debug("\e[34m--> #{json}\e[0m")
30
+ @stdin.puts("#{json}\n\n")
31
+ end
32
+
33
+ def stop
34
+ cleanup_resources
35
+ end
36
+
37
+ def ready?
38
+ true
39
+ end
40
+
41
+ # Check if we've received any message from the server
42
+ def received_server_message?
43
+ @received_server_message
44
+ end
45
+
46
+ # Mark the client as ready and send initial capabilities if not already sent
47
+ def mark_ready_and_send_capabilities
48
+ unless @received_server_message
49
+ @received_server_message = true
50
+ log_info("Received first server message")
51
+
52
+ # Send initial capabilities if not already sent
53
+ unless @capabilities_sent
54
+ log_info("Server is ready, sending initial capabilities...")
55
+ send_initial_capabilities
56
+ @capabilities_sent = true
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def start_output_threads
64
+ @stdout_thread = Thread.new do
65
+ @stdout.each_line do |line|
66
+ line = line.chomp
67
+ # Mark ready and send capabilities when we get any stdout
68
+ mark_ready_and_send_capabilities
69
+
70
+ # Continue with normal message handling
71
+ handle_raw_message(line)
72
+ end
73
+ end
74
+
75
+ @stderr_thread = Thread.new do
76
+ @stderr.each_line do |line|
77
+ line = line.chomp
78
+ log_info(line)
79
+
80
+ # Check stderr for server messages
81
+ if line.include?("MCP Server") || line.include?("running on stdio")
82
+ mark_ready_and_send_capabilities
83
+ end
84
+ end
85
+ end
86
+
87
+ @threads_started = true
88
+ end
89
+
90
+ def cleanup_resources
91
+ @stdin.close
92
+ wait_for_server_exit
93
+ cleanup_threads
94
+ end
95
+
96
+ def wait_for_server_exit
97
+ @wait_thr.join(0.5)
98
+ kill_server if @wait_thr.alive?
99
+ end
100
+
101
+ def kill_server
102
+ Process.kill("TERM", @wait_thr.pid)
103
+ rescue StandardError => e
104
+ log_error("Failed to kill server process: #{e}")
105
+ end
106
+
107
+ def cleanup_threads
108
+ @stdout_thread&.kill
109
+ @stderr_thread&.kill
110
+ end
111
+
112
+ def user_agent
113
+ "ActionMCP-stdio-client"
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,20 @@
1
+ module ActionMCP
2
+ module Transport
3
+ module Tools
4
+ def send_tools_list(request_id)
5
+ tools = format_registry_items(ToolsRegistry.non_abstract)
6
+ send_jsonrpc_response(request_id, result: { tools: tools })
7
+ end
8
+
9
+ def send_tools_call(request_id, tool_name, arguments, _meta = {})
10
+ result = ToolsRegistry.tool_call(tool_name, arguments, _meta)
11
+ send_jsonrpc_response(request_id, result: result)
12
+ rescue RegistryBase::NotFound
13
+ send_jsonrpc_response(request_id, error: JsonRpc::JsonRpcError.new(
14
+ :method_not_found,
15
+ message: "Tool not found: #{tool_name}"
16
+ ).as_json)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,125 @@
1
+ module ActionMCP
2
+ module Transport
3
+ class TransportBase
4
+ attr_reader :logger, :client_capabilities, :server_capabilities
5
+
6
+ def initialize(logger: Logger.new(STDOUT))
7
+ @logger = logger
8
+ @on_message = nil
9
+ @on_error = nil
10
+ @client_capabilities = default_capabilities
11
+ @server_capabilities = nil
12
+ @initialize_request_id = SecureRandom.hex(6)
13
+ @initialization_sent = false
14
+ end
15
+
16
+ def on_message(&block)
17
+ @on_message = block
18
+ end
19
+
20
+ def on_error(&block)
21
+ @on_error = block
22
+ end
23
+
24
+ def send_initial_capabilities
25
+ return if @initialization_sent
26
+
27
+ log_info("Sending client capabilities: #{@client_capabilities}")
28
+
29
+ request = JsonRpc::Request.new(
30
+ id: @initialize_request_id,
31
+ method: "initialize",
32
+ params: {
33
+ protocolVersion: PROTOCOL_VERSION,
34
+ capabilities: @client_capabilities,
35
+ clientInfo: {
36
+ name: user_agent,
37
+ version: ActionMCP.gem_version.to_s
38
+ }
39
+ }
40
+ )
41
+ @initialization_sent = true
42
+ send_message(request.to_json)
43
+ end
44
+
45
+ def handle_initialize_response(response)
46
+ unless @server_capabilities
47
+
48
+ if response.result
49
+ @server_capabilities = response.result["capabilities"]
50
+ send_initialized_notification
51
+ else
52
+ log_error("Server initialization failed: #{response.error}")
53
+ end
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def handle_raw_message(raw)
60
+ # Debug - log all raw messages
61
+ log_debug("\e[31m<-- #{raw}\e[0m")
62
+
63
+ begin
64
+ msg_hash = MultiJson.load(raw)
65
+ response = nil
66
+
67
+ if msg_hash.key?("jsonrpc")
68
+ if msg_hash.key?("id")
69
+ response = JsonRpc::Response.new(**msg_hash.slice("id", "result", "error").symbolize_keys)
70
+ else
71
+ response = JsonRpc::Notification.new(**msg_hash.slice("method", "params").symbolize_keys)
72
+ end
73
+ end
74
+ # Check if this is a response to our initialize request
75
+ if response && @initialize_request_id && response.id == @initialize_request_id
76
+ handle_initialize_response(response)
77
+ else
78
+ @on_message&.call(response) if response
79
+ end
80
+ rescue MultiJson::ParseError => e
81
+ log_error("JSON parse error: #{e} (raw: #{raw})")
82
+ @on_error&.call(e) if @on_error
83
+ rescue StandardError => e
84
+ log_error("Error handling message: #{e} (raw: #{raw})")
85
+ @on_error&.call(e) if @on_error
86
+ end
87
+ end
88
+
89
+ # Send the initialized notification to the server
90
+ def send_initialized_notification
91
+ notification = JsonRpc::Notification.new(
92
+ method: "initialized"
93
+ )
94
+
95
+ logger.info("Sent initialized notification to server")
96
+ send_message(notification)
97
+ end
98
+
99
+ def default_capabilities
100
+ {
101
+ # Base client capabilities
102
+ # roots: {}, # Remove from now.
103
+ }
104
+ end
105
+
106
+ def log_debug(message)
107
+ @logger.debug("[#{log_prefix}] #{message}")
108
+ end
109
+
110
+ def log_info(message)
111
+ @logger.info("[#{log_prefix}] #{message}")
112
+ end
113
+
114
+ def log_error(message)
115
+ @logger.error("[#{log_prefix}] #{message}")
116
+ end
117
+
118
+ private
119
+
120
+ def log_prefix
121
+ self.class.name.split("::").last
122
+ end
123
+ end
124
+ end
125
+ end
@@ -1,241 +1,4 @@
1
- # frozen_string_literal: true
2
-
3
1
  module ActionMCP
4
- class Transport
5
- HEARTBEAT_INTERVAL = 15 # seconds
6
-
7
- def initialize(output_io)
8
- # output_io can be any IO-like object where we write events.
9
- @output = output_io
10
- @output.sync = true
11
- end
12
-
13
- # Sends the capabilities JSON-RPC notification.
14
- #
15
- # @param request_id [String, Integer] The request identifier.
16
- def send_capabilities(request_id)
17
- capabilities = {}
18
-
19
- # Only include each capability if the corresponding registry is non-empty.
20
- capabilities[:tools] = { listChanged: true } if ActionMCP::ToolsRegistry.available_tools.any?
21
-
22
- capabilities[:prompts] = { listChanged: true } if ActionMCP::PromptsRegistry.available_prompts.any?
23
-
24
- capabilities[:resources] = { listChanged: true } if ActionMCP::ResourcesBank.all_resources.any?
25
-
26
- # Add logging capability only if enabled by configuration.
27
- capabilities[:logging] = {} if ActionMCP.configuration.logging_enabled
28
-
29
- payload = {
30
- protocolVersion: "2024-11-05",
31
- capabilities: capabilities,
32
- serverInfo: {
33
- name: ActionMCP.configuration.name,
34
- version: ActionMCP.configuration.version
35
- }
36
- }
37
- send_jsonrpc_response(request_id, result: payload)
38
- end
39
-
40
- # Sends the tools list JSON-RPC notification.
41
- #
42
- # @param request_id [String, Integer] The request identifier.
43
- def send_tools_list(request_id)
44
- tools = format_registry_items(ActionMCP::ToolsRegistry.available_tools)
45
- send_jsonrpc_response(request_id, result: { tools: tools })
46
- end
47
-
48
- # Sends the resources list JSON-RPC response.
49
- #
50
- # @param request_id [String, Integer] The request identifier.
51
- def send_resources_list(request_id)
52
- resources = ActionMCP::ResourcesBank.all_resources # fetch all resources
53
- result_data = { "resources" => resources }
54
- send_jsonrpc_response(request_id, result: result_data)
55
- Rails.logger.info("resources/list: Returned #{resources.size} resources.")
56
- rescue StandardError => e
57
- Rails.logger.error("resources/list failed: #{e.message}")
58
- error_obj = JsonRpcError.new(
59
- :internal_error,
60
- message: "Failed to list resources: #{e.message}"
61
- ).as_json
62
- send_jsonrpc_response(request_id, error: error_obj)
63
- end
64
-
65
- # Sends the resource templates list JSON-RPC response.
66
- #
67
- # @param request_id [String, Integer] The request identifier.
68
- def send_resource_templates_list(request_id)
69
- templates = ActionMCP::ResourcesBank.all_templates # get all resource templates
70
- result_data = { "resourceTemplates" => templates }
71
- send_jsonrpc_response(request_id, result: result_data)
72
- Rails.logger.info("resources/templates/list: Returned #{templates.size} resource templates.")
73
- rescue StandardError => e
74
- Rails.logger.error("resources/templates/list failed: #{e.message}")
75
- error_obj = JsonRpcError.new(
76
- :internal_error,
77
- message: "Failed to list resource templates: #{e.message}"
78
- ).as_json
79
- send_jsonrpc_response(request_id, error: error_obj)
80
- end
81
-
82
- # Sends the resource read JSON-RPC response.
83
- #
84
- # @param request_id [String, Integer] The request identifier.
85
- # @param params [Hash] The parameters including the 'uri' for the resource.
86
- def send_resource_read(request_id, params)
87
- uri = params&.fetch("uri", nil)
88
- if uri.nil? || uri.empty?
89
- Rails.logger.error("resources/read: 'uri' parameter is missing")
90
- error_obj = JsonRpcError.new(
91
- :invalid_params,
92
- message: "Missing 'uri' parameter for resources/read"
93
- ).as_json
94
- return send_jsonrpc_response(request_id, error: error_obj)
95
- end
96
-
97
- begin
98
- content = ActionMCP::ResourcesBank.read(uri) # Expecting an instance of an ActionMCP::Content subclass
99
- if content.nil?
100
- Rails.logger.error("resources/read: Resource not found for URI #{uri}")
101
- error_obj = JsonRpcError.new(
102
- :invalid_params,
103
- message: "Resource not found: #{uri}"
104
- ).as_json
105
- return send_jsonrpc_response(request_id, error: error_obj)
106
- end
107
-
108
- # Use the content object's `to_h` to build the JSON-RPC result.
109
- result_data = { "contents" => [ content.to_h ] }
110
- send_jsonrpc_response(request_id, result: result_data)
111
-
112
- log_msg = "resources/read: Successfully read content of #{uri}"
113
- log_msg += " (#{content.text.size} bytes)" if content.respond_to?(:text) && content.text
114
- Rails.logger.info(log_msg)
115
- rescue StandardError => e
116
- Rails.logger.error("resources/read: Error reading #{uri} - #{e.message}")
117
- error_obj = JsonRpcError.new(
118
- :internal_error,
119
- message: "Failed to read resource: #{e.message}"
120
- ).as_json
121
- send_jsonrpc_response(request_id, error: error_obj)
122
- end
123
- end
124
-
125
- # Sends a call to a tool. Currently logs the call details.
126
- #
127
- # @param request_id [String, Integer] The request identifier.
128
- # @param tool_name [String] The name of the tool.
129
- # @param params [Hash] The parameters for the tool.
130
- def send_tools_call(request_id, tool_name, params)
131
- ActionMCP::ToolsRegistry.fetch_available_tool(tool_name.to_s)
132
- Rails.logger.info("Sending tool call: #{tool_name} with params: #{params}")
133
- # TODO: Implement tool call handling and response if needed.
134
- rescue StandardError => e
135
- Rails.logger.error("tools/call: Failed to call tool #{tool_name} - #{e.message}")
136
- error_obj = JsonRpcError.new(
137
- :internal_error,
138
- message: "Failed to call tool #{tool_name}: #{e.message}"
139
- ).as_json
140
- send_jsonrpc_response(request_id, error: error_obj)
141
- end
142
-
143
- # Sends the prompts list JSON-RPC notification.
144
- #
145
- # @param request_id [String, Integer] The request identifier.
146
- def send_prompts_list(request_id)
147
- prompts = format_registry_items(ActionMCP::PromptsRegistry.available_prompts)
148
- send_jsonrpc_response(request_id, result: { prompts: prompts })
149
- rescue StandardError => e
150
- Rails.logger.error("prompts/list failed: #{e.message}")
151
- error_obj = JsonRpcError.new(
152
- :internal_error,
153
- message: "Failed to list prompts: #{e.message}"
154
- ).as_json
155
- send_jsonrpc_response(request_id, error: error_obj)
156
- end
157
-
158
- def send_prompts_get(request_id, params)
159
- prompt_name = params&.fetch("name", nil)
160
- if prompt_name.nil? || prompt_name.strip.empty?
161
- Rails.logger.error("prompts/get: 'name' parameter is missing")
162
- error_obj = JsonRpcError.new(
163
- :invalid_params,
164
- message: "Missing 'name' parameter for prompts/get"
165
- ).as_json
166
- return send_jsonrpc_response(request_id, error: error_obj)
167
- end
168
-
169
- begin
170
- # Assume a method similar to fetch_available_tool exists for prompts.
171
- prompt = ActionMCP::PromptsRegistry.fetch_available_prompt(prompt_name.to_s)
172
- if prompt.nil?
173
- Rails.logger.error("prompts/get: Prompt not found for name #{prompt_name}")
174
- error_obj = JsonRpcError.new(
175
- :invalid_params,
176
- message: "Prompt not found: #{prompt_name}"
177
- ).as_json
178
- return send_jsonrpc_response(request_id, error: error_obj)
179
- end
180
-
181
- result_data = { "prompt" => prompt.to_h }
182
- send_jsonrpc_response(request_id, result: result_data)
183
- Rails.logger.info("prompts/get: Returned prompt #{prompt_name}")
184
- rescue StandardError => e
185
- Rails.logger.error("prompts/get: Error retrieving prompt #{prompt_name} - #{e.message}")
186
- error_obj = JsonRpcError.new(
187
- :internal_error,
188
- message: "Failed to get prompt: #{e.message}"
189
- ).as_json
190
- send_jsonrpc_response(request_id, error: error_obj)
191
- end
192
- end
193
-
194
- # Sends a JSON-RPC pong response.
195
- # We don't actually to send any data back because the spec are not fun anymore.
196
- #
197
- # @param request_id [String, Integer] The request identifier.
198
- def send_pong(request_id)
199
- send_jsonrpc_response(request_id, result: {})
200
- end
201
-
202
- # Sends a JSON-RPC response.
203
- #
204
- # @param request_id [String, Integer] The request identifier.
205
- # @param result [Object] The result data.
206
- # @param error [Object, nil] The error data, if any.
207
- def send_jsonrpc_response(request_id, result: nil, error: nil)
208
- response = JsonRpc::Response.new(id: request_id, result: result, error: error)
209
- write_message(response.to_json)
210
- end
211
-
212
- # Sends a generic JSON-RPC notification (no response expected).
213
- #
214
- # @param method [String] The JSON-RPC method.
215
- # @param params [Hash] The parameters for the method.
216
- def send_jsonrpc_notification(method, params = {})
217
- notification = JsonRpc::Notification.new(method: method, params: params)
218
- write_message(notification.to_json)
219
- end
220
-
221
- private
222
-
223
- # Formats registry items to a hash representation.
224
- #
225
- # @param registry [Hash] The registry containing tool or prompt definitions.
226
- # @return [Array<Hash>] The formatted registry items.
227
- def format_registry_items(registry)
228
- registry.map { |_, item| item[:class].to_h }
229
- end
230
-
231
- # Writes a message to the output IO.
232
- #
233
- # @param data [String] The data to write.
234
- def write_message(data)
235
- Rails.logger.debug("Response Sent: #{data}")
236
- @output.write("#{data}\n")
237
- rescue IOError => e
238
- Rails.logger.error("Failed to write message: #{e.message}")
239
- end
2
+ module Transport
240
3
  end
241
4
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionMCP
4
+ class TransportHandler
5
+ include Logging
6
+
7
+ include Transport::Capabilities
8
+ include Transport::Tools
9
+ include Transport::Prompts
10
+ include Transport::Messaging
11
+
12
+ HEARTBEAT_INTERVAL = 15 # seconds
13
+ attr_reader :initialized
14
+
15
+ def initialize(output_io)
16
+ @output = output_io
17
+ @output.sync = true if @output.respond_to?(:sync=)
18
+ @initialized = false
19
+ @client_capabilities = {}
20
+ @client_info = {}
21
+ @protocol_version = ""
22
+ end
23
+
24
+ def send_ping
25
+ send_jsonrpc_request("ping")
26
+ end
27
+
28
+ def send_pong(request_id)
29
+ send_jsonrpc_response(request_id, result: {})
30
+ end
31
+
32
+ def initialized?
33
+ @initialized
34
+ end
35
+
36
+ def initialized!
37
+ @initialized = true
38
+ end
39
+
40
+ private
41
+
42
+ def write_message(data)
43
+ Timeout.timeout(5) do
44
+ @output.write("#{data}\n")
45
+ end
46
+ rescue Timeout::Error
47
+ # ActionMCP.logger.error("Write operation timed out")
48
+ end
49
+
50
+ def format_registry_items(registry)
51
+ registry.map { |item| item.klass.to_h }
52
+ end
53
+ end
54
+ end
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "gem_version"
4
-
5
4
  module ActionMCP
6
- VERSION = "0.1.2"
7
- # Returns the currently loaded version of Active MCP as a +Gem::Version+.
8
- def self.version
9
- gem_version
5
+ VERSION = "0.2.3"
6
+
7
+ class << self
8
+ alias version gem_version
10
9
  end
11
10
  end
data/lib/action_mcp.rb CHANGED
@@ -2,52 +2,65 @@
2
2
 
3
3
  require "rails"
4
4
  require "active_support"
5
- require "active_model"
6
- require "action_mcp/version"
7
- require "action_mcp/railtie" if defined?(Rails)
5
+ require "active_support/rails"
6
+ require "multi_json"
7
+ require "concurrent"
8
+ require "active_record/railtie"
9
+ require "action_controller/railtie"
10
+ require "action_cable/engine"
11
+ require "action_mcp/engine"
12
+ require "zeitwerk"
8
13
 
9
- ActiveSupport::Inflector.inflections(:en) do |inflect|
10
- inflect.acronym "MCP"
11
- end
12
- module ActionMCP
13
- extend ActiveSupport::Autoload
14
-
15
- autoload :RegistryBase
16
- autoload :Resource
17
- autoload :ToolsRegistry
18
- autoload :PromptsRegistry
19
- autoload :ResourcesBank
20
- autoload :Tool
21
- autoload :Prompt
22
- autoload :JsonRpc
23
- eager_autoload do
24
- autoload :Configuration
25
- end
14
+ lib = File.dirname(__FILE__)
26
15
 
27
- # Accessor for the configuration instance.
28
- def self.configuration
29
- @configuration ||= Configuration.new
30
- end
16
+ Zeitwerk::Loader.for_gem.tap do |loader|
17
+ loader.ignore(
18
+ "#{lib}/generators",
19
+ "#{lib}/action_mcp/version.rb",
20
+ "#{lib}/action_mcp/gem_version.rb",
21
+ "#{lib}/actionmcp.rb"
22
+ )
31
23
 
32
- def self.configure
33
- yield(configuration)
34
- end
24
+ loader.inflector.inflect("action_mcp" => "ActionMCP")
25
+ loader.inflector.inflect("sse_client" => "SSEClient")
26
+ loader.inflector.inflect("sse_server" => "SSEServer")
27
+ end.setup
28
+
29
+ module ActionMCP
30
+ require_relative "action_mcp/version"
31
+ require_relative "action_mcp/configuration"
32
+ PROTOCOL_VERSION = "2024-11-05"
35
33
 
36
34
  module_function
37
35
 
36
+ # Returns the tools registry.
37
+ #
38
+ # @return [Hash] the tools registry
38
39
  def tools
39
40
  ToolsRegistry.tools
40
41
  end
41
42
 
43
+ # Returns the prompts registry.
44
+ #
45
+ # @return [Hash] the prompts registry
42
46
  def prompts
43
47
  PromptsRegistry.prompts
44
48
  end
45
49
 
50
+ # Returns the available tools.
51
+ #
52
+ # @return [ActionMCP::RegistryBase::RegistryScope] the available tools
46
53
  def available_tools
47
54
  ToolsRegistry.available_tools
48
55
  end
49
56
 
57
+ # Returns the available prompts.
58
+ #
59
+ # @return [ActionMCP::RegistryBase::RegistryScope] the available prompts
50
60
  def available_prompts
51
61
  PromptsRegistry.available_prompts
52
62
  end
63
+
64
+ ActiveModel::Type.register(:string_array, StringArray)
65
+ ActiveModel::Type.register(:integer_array, IntegerArray)
53
66
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionMCP
2
4
  module Generators
3
5
  class InstallGenerator < Rails::Generators::Base
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Template for generating new prompts.
3
4
  class <%= class_name %> < ApplicationPrompt
5
+ # Set the prompt name.
4
6
  prompt_name "<%= prompt_name %>"
5
7
 
6
8
  # Provide a user-facing description for your prompt.
@@ -13,7 +15,7 @@ class <%= class_name %> < ApplicationPrompt
13
15
  # Add validations (note: "Ruby" is not allowed per the validation)
14
16
  validates :language, inclusion: { in: %w[Ruby C Cobol FORTRAN] }
15
17
 
18
+ # Implement your prompt's behavior here
16
19
  def call
17
- # Implement your prompt's behavior here
18
20
  end
19
21
  end