actionmcp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []