actionmcp 0.1.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.
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module ActionMCP
6
+ module ResourcesBank
7
+ @resources = {} # { uri => content_object }
8
+ @templates = {} # { uri => template_object }
9
+ @watchers = {} # { source_uri => watcher }
10
+
11
+ class << self
12
+ # Basic resource registration.
13
+ def register_resource(uri, content)
14
+ @resources[uri] = content
15
+ end
16
+
17
+ def all_resources
18
+ @resources.values
19
+ end
20
+
21
+ def read(uri)
22
+ @resources[uri]
23
+ end
24
+
25
+ def register_template(uri, template)
26
+ @templates[uri] = template
27
+ end
28
+
29
+ def all_templates
30
+ @templates.values
31
+ end
32
+
33
+ # Registers a source (file or directory) for resources.
34
+ #
35
+ # @param source_uri [String] An identifier for this source.
36
+ # @param path [String] Filesystem path to the source.
37
+ # @param watch [Boolean] Whether to watch the source for changes.
38
+ def register_source(source_uri, path, watch: false)
39
+ reload_source(source_uri, path) # Initial load
40
+
41
+ if watch
42
+ require "active_support/evented_file_update_checker"
43
+ # Watch all files under the given path (recursive)
44
+ file_paths = Dir.glob("#{path}/**/*")
45
+ watcher = ActiveSupport::EventedFileUpdateChecker.new(file_paths) do |modified, added, removed|
46
+ Rails.logger.info("Files changed in #{path} - Modified: #{modified.inspect}, Added: #{added.inspect}, Removed: #{removed.inspect}")
47
+ # Reload resources for this source when changes occur.
48
+ reload_source(source_uri, path)
49
+ end
50
+ @watchers[source_uri] = { path: path, watcher: watcher }
51
+ end
52
+ end
53
+
54
+ # Unregisters a source and stops watching it.
55
+ #
56
+ # @param source_uri [String] The identifier for the source.
57
+ def unregister_source(source_uri)
58
+ @watchers.delete(source_uri)
59
+ # Optionally, remove any resources associated with this source.
60
+ @resources.reject! { |uri, _| uri.start_with?("#{source_uri}://") }
61
+ end
62
+
63
+ # Reloads (or loads) all resources from the given directory.
64
+ #
65
+ # @param source_uri [String] The identifier for the source.
66
+ # @param path [String] Filesystem path to the source.
67
+ def reload_source(source_uri, path)
68
+ Rails.logger.info("Reloading resources from #{path} for #{source_uri}")
69
+ Dir.glob("#{path}/**/*").each do |file|
70
+ next unless File.file?(file)
71
+ # Create a resource URI from the source and file path.
72
+ relative_path = file.sub(%r{\A#{Regexp.escape(path)}/?}, "")
73
+ resource_uri = "#{source_uri}://#{relative_path}"
74
+ # For this example, we assume text files.
75
+ begin
76
+ text = File.read(file)
77
+ content = ActionMCP::Content::Text.new(text)
78
+ register_resource(resource_uri, content)
79
+ Rails.logger.info("Registered resource: #{resource_uri}")
80
+ rescue StandardError => e
81
+ Rails.logger.error("Error reading file #{file}: #{e.message}")
82
+ end
83
+ end
84
+ end
85
+
86
+ # This method should be called periodically (e.g. via a background thread)
87
+ # to check if any watched files have changed.
88
+ def run_watchers
89
+ @watchers.each_value do |data|
90
+ data[:watcher].execute_if_updated
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,147 @@
1
+ # lib/action_mcp/tool.rb
2
+ # frozen_string_literal: true
3
+
4
+ module ActionMCP
5
+ class Tool
6
+ include ActiveModel::Model
7
+ include ActiveModel::Attributes
8
+
9
+ class_attribute :_tool_name, instance_accessor: false
10
+ class_attribute :_description, instance_accessor: false, default: ""
11
+ class_attribute :_schema_properties, instance_accessor: false, default: {}
12
+ class_attribute :_required_properties, instance_accessor: false, default: []
13
+ class_attribute :abstract_tool, instance_accessor: false, default: false
14
+
15
+ # Register each non-abstract subclass in ToolsRegistry
16
+ def self.inherited(subclass)
17
+ super
18
+ return if subclass == Tool
19
+ subclass.abstract_tool = false
20
+ return if "ApplicationTool" == subclass.name
21
+
22
+ ToolsRegistry.register(subclass.tool_name, subclass)
23
+ end
24
+
25
+ # Mark this tool as abstract so it won’t be available for use.
26
+ def self.abstract!
27
+ self.abstract_tool = true
28
+ ToolsRegistry.unregister(self.tool_name) if ToolsRegistry.items.key?(self.tool_name)
29
+ end
30
+
31
+ def self.abstract?
32
+ self.abstract_tool
33
+ end
34
+
35
+ # ---------------------------------------------------
36
+ # Tool Name & Description
37
+ # ---------------------------------------------------
38
+ def self.tool_name(name = nil)
39
+ if name
40
+ self._tool_name = name
41
+ else
42
+ _tool_name || default_tool_name
43
+ end
44
+ end
45
+
46
+ def self.default_tool_name
47
+ name.demodulize.underscore.dasherize.sub(/-tool$/, "")
48
+ end
49
+
50
+ def self.description(text = nil)
51
+ if text
52
+ self._description = text
53
+ else
54
+ _description
55
+ end
56
+ end
57
+
58
+ # ---------------------------------------------------
59
+ # Property DSL (Direct Declaration)
60
+ # ---------------------------------------------------
61
+ def self.property(prop_name, type: "string", description: nil, required: false, default: nil, **opts)
62
+ # Build JSON Schema definition for the property.
63
+ prop_definition = { type: type }
64
+ prop_definition[:description] = description if description && !description.empty?
65
+ prop_definition.merge!(opts) if opts.any?
66
+
67
+ self._schema_properties = _schema_properties.merge(prop_name.to_s => prop_definition)
68
+ self._required_properties = _required_properties.dup
69
+ self._required_properties << prop_name.to_s if required
70
+
71
+ # Map our DSL type to an ActiveModel attribute type.
72
+ am_type = case type.to_s
73
+ when "number" then :float
74
+ when "integer" then :integer
75
+ when "array" then :string
76
+ else
77
+ :string
78
+ end
79
+ attribute prop_name, am_type, default: default
80
+ end
81
+
82
+ # ---------------------------------------------------
83
+ # Collection DSL
84
+ # ---------------------------------------------------
85
+ # Supports two forms:
86
+ #
87
+ # 1. Without a block:
88
+ # collection :args, type: "string", description: "Command arguments"
89
+ #
90
+ # 2. With a block (defining a nested object):
91
+ # collection :files, description: "List of Files" do
92
+ # property :file, required: true, description: 'file uri'
93
+ # property :checksum, required: true, description: 'checksum to verify'
94
+ # end
95
+ def self.collection(prop_name, type: nil, description: nil, required: false, default: nil, **opts, &block)
96
+ if block_given?
97
+ # Build nested schema for an object.
98
+ nested_schema = { type: "object", properties: {}, required: [] }
99
+ dsl = CollectionDSL.new(nested_schema)
100
+ dsl.instance_eval(&block)
101
+ collection_definition = { type: "array", description: description, items: nested_schema }
102
+ else
103
+ raise ArgumentError, "Type is required for a collection without a block" if type.nil?
104
+ collection_definition = { type: "array", description: description, items: { type: type } }
105
+ end
106
+
107
+ self._schema_properties = _schema_properties.merge(prop_name.to_s => collection_definition)
108
+ self._required_properties = _required_properties.dup
109
+ self._required_properties << prop_name.to_s if required
110
+
111
+ # Register the property as an attribute.
112
+ # (Mapping for a collection can be customized; here we use :string to mimic previous behavior.)
113
+ attribute prop_name, :string, default: default
114
+ end
115
+
116
+ # DSL for building a nested schema within a collection block.
117
+ class CollectionDSL
118
+ attr_reader :schema
119
+
120
+ def initialize(schema)
121
+ @schema = schema
122
+ end
123
+
124
+ def property(prop_name, type: "string", description: nil, required: false, default: nil, **opts)
125
+ prop_definition = { type: type }
126
+ prop_definition[:description] = description if description && !description.empty?
127
+ prop_definition.merge!(opts) if opts.any?
128
+
129
+ @schema[:properties][prop_name.to_s] = prop_definition
130
+ @schema[:required] << prop_name.to_s if required
131
+ end
132
+ end
133
+
134
+ # ---------------------------------------------------
135
+ # Convert Tool Definition to Hash
136
+ # ---------------------------------------------------
137
+ def self.to_h
138
+ schema = { type: "object", properties: self._schema_properties }
139
+ schema[:required] = self._required_properties if self._required_properties.any?
140
+ {
141
+ name: self.tool_name,
142
+ description: self.description.presence,
143
+ inputSchema: schema
144
+ }.compact
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module ActionMCP
6
+ class ToolsRegistry < RegistryBase
7
+ class << self
8
+ alias_method :tools, :items
9
+ alias_method :available_tools, :enabled
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ 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
+ payload = {
18
+ protocolVersion: "2024-11-05",
19
+ capabilities: {
20
+ tools: { listChanged: true },
21
+ prompts: { listChanged: true },
22
+ resources: { listChanged: true },
23
+ logging: {}
24
+ },
25
+ serverInfo: {
26
+ name: Rails.application.name,
27
+ version: Rails.application.version.to_s
28
+ }
29
+ }
30
+ send_jsonrpc_response(request_id, result: payload)
31
+ end
32
+
33
+ # Sends the tools list JSON-RPC notification.
34
+ #
35
+ # @param request_id [String, Integer] The request identifier.
36
+ def send_tools_list(request_id)
37
+ tools = format_registry_items(ActionMCP::ToolsRegistry.available_tools)
38
+ send_jsonrpc_response(request_id, result: { tools: tools })
39
+ end
40
+
41
+ # Sends the resources list JSON-RPC response.
42
+ #
43
+ # @param request_id [String, Integer] The request identifier.
44
+ def send_resources_list(request_id)
45
+ begin
46
+ resources = ActionMCP::ResourcesRegistry.all_resources # fetch all resources
47
+ result_data = { "resources" => resources }
48
+ send_jsonrpc_response(request_id, result: result_data)
49
+ Rails.logger.info("resources/list: Returned #{resources.size} resources.")
50
+ rescue StandardError => e
51
+ Rails.logger.error("resources/list failed: #{e.message}")
52
+ error_obj = JsonRpcError.new(
53
+ :internal_error,
54
+ message: "Failed to list resources: #{e.message}"
55
+ ).as_json
56
+ send_jsonrpc_response(request_id, error: error_obj)
57
+ end
58
+ end
59
+
60
+ # Sends the resource templates list JSON-RPC response.
61
+ #
62
+ # @param request_id [String, Integer] The request identifier.
63
+ def send_resource_templates_list(request_id)
64
+ begin
65
+ templates = ActionMCP::ResourcesRegistry.all_templates # get all resource templates
66
+ result_data = { "resourceTemplates" => templates }
67
+ send_jsonrpc_response(request_id, result: result_data)
68
+ Rails.logger.info("resources/templates/list: Returned #{templates.size} resource templates.")
69
+ rescue StandardError => e
70
+ Rails.logger.error("resources/templates/list failed: #{e.message}")
71
+ error_obj = JsonRpcError.new(
72
+ :internal_error,
73
+ message: "Failed to list resource templates: #{e.message}"
74
+ ).as_json
75
+ send_jsonrpc_response(request_id, error: error_obj)
76
+ end
77
+ end
78
+
79
+ # Sends the resource read JSON-RPC response.
80
+ #
81
+ # @param request_id [String, Integer] The request identifier.
82
+ # @param params [Hash] The parameters including the 'uri' for the resource.
83
+ def send_resource_read(request_id, params)
84
+ uri = params&.fetch("uri", nil)
85
+ if uri.nil? || uri.empty?
86
+ Rails.logger.error("resources/read: 'uri' parameter is missing")
87
+ error_obj = JsonRpcError.new(
88
+ :invalid_params,
89
+ message: "Missing 'uri' parameter for resources/read"
90
+ ).as_json
91
+ return send_jsonrpc_response(request_id, error: error_obj)
92
+ end
93
+
94
+ begin
95
+ content = ActionMCP::ResourcesRegistry.read(uri) # Expecting an instance of an ActionMCP::Content subclass
96
+ if content.nil?
97
+ Rails.logger.error("resources/read: Resource not found for URI #{uri}")
98
+ error_obj = JsonRpcError.new(
99
+ :invalid_params,
100
+ message: "Resource not found: #{uri}"
101
+ ).as_json
102
+ return send_jsonrpc_response(request_id, error: error_obj)
103
+ end
104
+
105
+ # Use the content object's `to_h` to build the JSON-RPC result.
106
+ result_data = { "contents" => [ content.to_h ] }
107
+ send_jsonrpc_response(request_id, result: result_data)
108
+
109
+ log_msg = "resources/read: Successfully read content of #{uri}"
110
+ log_msg += " (#{content.text.size} bytes)" if content.respond_to?(:text) && content.text
111
+ Rails.logger.info(log_msg)
112
+ rescue StandardError => e
113
+ Rails.logger.error("resources/read: Error reading #{uri} - #{e.message}")
114
+ error_obj = JsonRpcError.new(
115
+ :internal_error,
116
+ message: "Failed to read resource: #{e.message}"
117
+ ).as_json
118
+ send_jsonrpc_response(request_id, error: error_obj)
119
+ end
120
+ end
121
+
122
+
123
+ # Sends a call to a tool. Currently logs the call details.
124
+ #
125
+ # @param request_id [String, Integer] The request identifier.
126
+ # @param tool_name [String] The name of the tool.
127
+ # @param params [Hash] The parameters for the tool.
128
+ def send_tools_call(request_id, tool_name, params)
129
+ begin
130
+ tool = ActionMCP::ToolsRegistry.fetch_available_tool(tool_name.to_s)
131
+ Rails.logger.info("Sending tool call: #{tool_name} with params: #{params}")
132
+ # TODO: Implement tool call handling and response if needed.
133
+ rescue StandardError => e
134
+ Rails.logger.error("tools/call: Failed to call tool #{tool_name} - #{e.message}")
135
+ error_obj = JsonRpcError.new(
136
+ :internal_error,
137
+ message: "Failed to call tool #{tool_name}: #{e.message}"
138
+ ).as_json
139
+ send_jsonrpc_response(request_id, error: error_obj)
140
+ end
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
+ begin
148
+ prompts = format_registry_items(ActionMCP::PromptsRegistry.available_prompts)
149
+ send_jsonrpc_response(request_id, result: {prompts: prompts} )
150
+ rescue StandardError => e
151
+ Rails.logger.error("prompts/list failed: #{e.message}")
152
+ error_obj = JsonRpcError.new(
153
+ :internal_error,
154
+ message: "Failed to list prompts: #{e.message}"
155
+ ).as_json
156
+ send_jsonrpc_response(request_id, error: error_obj)
157
+ end
158
+ end
159
+
160
+ def send_prompts_get(request_id, params)
161
+ prompt_name = params&.fetch("name", nil)
162
+ if prompt_name.nil? || prompt_name.strip.empty?
163
+ Rails.logger.error("prompts/get: 'name' parameter is missing")
164
+ error_obj = JsonRpcError.new(
165
+ :invalid_params,
166
+ message: "Missing 'name' parameter for prompts/get"
167
+ ).as_json
168
+ return send_jsonrpc_response(request_id, error: error_obj)
169
+ end
170
+
171
+ begin
172
+ # Assume a method similar to fetch_available_tool exists for prompts.
173
+ prompt = ActionMCP::PromptsRegistry.fetch_available_prompt(prompt_name.to_s)
174
+ if prompt.nil?
175
+ Rails.logger.error("prompts/get: Prompt not found for name #{prompt_name}")
176
+ error_obj = JsonRpcError.new(
177
+ :invalid_params,
178
+ message: "Prompt not found: #{prompt_name}"
179
+ ).as_json
180
+ return send_jsonrpc_response(request_id, error: error_obj)
181
+ end
182
+
183
+ result_data = { "prompt" => prompt.to_h }
184
+ send_jsonrpc_response(request_id, result: result_data)
185
+ Rails.logger.info("prompts/get: Returned prompt #{prompt_name}")
186
+ rescue StandardError => e
187
+ Rails.logger.error("prompts/get: Error retrieving prompt #{prompt_name} - #{e.message}")
188
+ error_obj = JsonRpcError.new(
189
+ :internal_error,
190
+ message: "Failed to get prompt: #{e.message}"
191
+ ).as_json
192
+ send_jsonrpc_response(request_id, error: error_obj)
193
+ end
194
+ end
195
+
196
+
197
+ # Sends a JSON-RPC pong response.
198
+ # We don't actually to send any data back because the spec are not fun anymore.
199
+ #
200
+ # @param request_id [String, Integer] The request identifier.
201
+ def send_pong(request_id)
202
+ send_jsonrpc_response(request_id, result: {})
203
+ end
204
+
205
+ # Sends a JSON-RPC response.
206
+ #
207
+ # @param request_id [String, Integer] The request identifier.
208
+ # @param result [Object] The result data.
209
+ # @param error [Object, nil] The error data, if any.
210
+ def send_jsonrpc_response(request_id, result: nil, error: nil)
211
+ response = JsonRpc::Response.new(id: request_id, result: result, error: error)
212
+ write_message(response.to_json)
213
+ end
214
+
215
+ # Sends a generic JSON-RPC notification (no response expected).
216
+ #
217
+ # @param method [String] The JSON-RPC method.
218
+ # @param params [Hash] The parameters for the method.
219
+ def send_jsonrpc_notification(method, params = {})
220
+ notification = JsonRpc::Notification.new(method: method, params: params)
221
+ write_message(notification.to_json)
222
+ end
223
+
224
+ private
225
+
226
+ # Formats registry items to a hash representation.
227
+ #
228
+ # @param registry [Hash] The registry containing tool or prompt definitions.
229
+ # @return [Array<Hash>] The formatted registry items.
230
+ def format_registry_items(registry)
231
+ registry.map { |_, item| item[:class].to_h }
232
+ end
233
+
234
+ # Writes a message to the output IO.
235
+ #
236
+ # @param data [String] The data to write.
237
+ def write_message(data)
238
+ Rails.logger.debug("Response Sent: #{data}")
239
+ @output.write("#{data}\n")
240
+ rescue IOError => e
241
+ Rails.logger.error("Failed to write message: #{e.message}")
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gem_version"
4
+
5
+ module ActionMCP
6
+ VERSION = "0.1.0"
7
+ # Returns the currently loaded version of Active MCP as a +Gem::Version+.
8
+ def self.version
9
+ gem_version
10
+ end
11
+ end
data/lib/action_mcp.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "active_support"
5
+ require "active_model"
6
+ require "action_mcp/version"
7
+ require "action_mcp/railtie" if defined?(Rails)
8
+
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 :Tool
20
+ autoload :Prompt
21
+ autoload :JsonRpc
22
+
23
+ module_function
24
+ def tools
25
+ ToolsRegistry.tools
26
+ end
27
+
28
+ def prompts
29
+ PromptsRegistry.prompts
30
+ end
31
+
32
+ def available_tools
33
+ ToolsRegistry.available_tools
34
+ end
35
+
36
+ def available_prompts
37
+ PromptsRegistry.available_prompts
38
+ end
39
+ end
data/lib/actionmcp.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "action_mcp"
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :action_mcp do
5
+ # # Task goes here
6
+ # end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: actionmcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Abdelkader Boudih
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-02-14 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activemodel
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
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 8.0.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 8.0.1
40
+ - !ruby/object:Gem::Dependency
41
+ name: multi_json
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ description: It offers base classes and helpers for creating MCP applications, making
55
+ it easier to integrate your Ruby/Rails application with the MCP standard
56
+ email:
57
+ - terminale@gmail.com
58
+ executables:
59
+ - action_mcp_stdio
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - MIT-LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - exe/action_mcp_stdio
67
+ - lib/action_mcp.rb
68
+ - lib/action_mcp/content.rb
69
+ - lib/action_mcp/content/audio.rb
70
+ - lib/action_mcp/content/image.rb
71
+ - lib/action_mcp/content/resource.rb
72
+ - lib/action_mcp/content/text.rb
73
+ - lib/action_mcp/gem_version.rb
74
+ - lib/action_mcp/json_rpc.rb
75
+ - lib/action_mcp/json_rpc/base.rb
76
+ - lib/action_mcp/json_rpc/json_rpc_error.rb
77
+ - lib/action_mcp/json_rpc/notification.rb
78
+ - lib/action_mcp/json_rpc/request.rb
79
+ - lib/action_mcp/json_rpc/response.rb
80
+ - lib/action_mcp/prompt.rb
81
+ - lib/action_mcp/prompts_registry.rb
82
+ - lib/action_mcp/railtie.rb
83
+ - lib/action_mcp/registry_base.rb
84
+ - lib/action_mcp/resource.rb
85
+ - lib/action_mcp/resources_bank.rb
86
+ - lib/action_mcp/tool.rb
87
+ - lib/action_mcp/tools_registry.rb
88
+ - lib/action_mcp/transport.rb
89
+ - lib/action_mcp/version.rb
90
+ - lib/actionmcp.rb
91
+ - lib/tasks/action_mcp_tasks.rake
92
+ homepage: https://github.com/seuros/action_mcp
93
+ licenses:
94
+ - MIT
95
+ metadata:
96
+ homepage_uri: https://github.com/seuros/action_mcp
97
+ source_code_uri: https://github.com/seuros/action_mcp
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubygems_version: 3.6.2
113
+ specification_version: 4
114
+ summary: Provides essential tooling for building Model Context Protocol (MCP) capable
115
+ servers
116
+ test_files: []