actionmcp 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +133 -30
  3. data/Rakefile +0 -2
  4. data/exe/actionmcp_cli +221 -0
  5. data/lib/action_mcp/capability.rb +52 -0
  6. data/lib/action_mcp/client.rb +243 -1
  7. data/lib/action_mcp/configuration.rb +50 -1
  8. data/lib/action_mcp/content/audio.rb +9 -0
  9. data/lib/action_mcp/content/image.rb +9 -0
  10. data/lib/action_mcp/content/resource.rb +13 -0
  11. data/lib/action_mcp/content/text.rb +7 -0
  12. data/lib/action_mcp/content.rb +11 -6
  13. data/lib/action_mcp/engine.rb +34 -0
  14. data/lib/action_mcp/gem_version.rb +2 -2
  15. data/lib/action_mcp/integer_array.rb +6 -0
  16. data/lib/action_mcp/json_rpc/json_rpc_error.rb +21 -0
  17. data/lib/action_mcp/json_rpc/notification.rb +8 -0
  18. data/lib/action_mcp/json_rpc/request.rb +14 -0
  19. data/lib/action_mcp/json_rpc/response.rb +32 -1
  20. data/lib/action_mcp/json_rpc.rb +1 -6
  21. data/lib/action_mcp/json_rpc_handler.rb +106 -0
  22. data/lib/action_mcp/logging.rb +19 -0
  23. data/lib/action_mcp/prompt.rb +30 -46
  24. data/lib/action_mcp/prompts_registry.rb +13 -1
  25. data/lib/action_mcp/registry_base.rb +47 -28
  26. data/lib/action_mcp/renderable.rb +26 -0
  27. data/lib/action_mcp/resource.rb +3 -1
  28. data/lib/action_mcp/server.rb +4 -1
  29. data/lib/action_mcp/string_array.rb +5 -0
  30. data/lib/action_mcp/tool.rb +16 -53
  31. data/lib/action_mcp/tools_registry.rb +14 -1
  32. data/lib/action_mcp/transport/capabilities.rb +21 -0
  33. data/lib/action_mcp/transport/messaging.rb +20 -0
  34. data/lib/action_mcp/transport/prompts.rb +19 -0
  35. data/lib/action_mcp/transport/sse_client.rb +309 -0
  36. data/lib/action_mcp/transport/stdio_client.rb +117 -0
  37. data/lib/action_mcp/transport/tools.rb +20 -0
  38. data/lib/action_mcp/transport/transport_base.rb +125 -0
  39. data/lib/action_mcp/transport.rb +1 -235
  40. data/lib/action_mcp/transport_handler.rb +54 -0
  41. data/lib/action_mcp/version.rb +4 -5
  42. data/lib/action_mcp.rb +36 -33
  43. data/lib/generators/action_mcp/prompt/templates/prompt.rb.erb +3 -1
  44. data/lib/generators/action_mcp/tool/templates/tool.rb.erb +5 -1
  45. data/lib/tasks/action_mcp_tasks.rake +28 -5
  46. metadata +62 -9
  47. data/exe/action_mcp_stdio +0 -0
  48. data/lib/action_mcp/railtie.rb +0 -27
  49. data/lib/action_mcp/resources_bank.rb +0 -94
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e01fe9b15e57ab4450f9712dd2a38dbf355cbdc50d196a8026cbb205d3d8a07
4
- data.tar.gz: 8ce8ffd799f5b9487c0bb3b78c7d7242cff31742e8cc1a79a76116e50f6cb896
3
+ metadata.gz: 51df9fa26245d233eebbbf193729618ee8c0f8cc7aa6c680d1a2daac97b9a19e
4
+ data.tar.gz: b3b898d71781a538ee1826c64294018494e4824a0971768adadcb516be2dd2df
5
5
  SHA512:
6
- metadata.gz: b044250b38fc6680fc5afa90f33dfc1082cb4ca7ec5916339e8e2a1f0b5ff64a32229968c24eeec19ec706a4341c413cc0d3546f7edee8dd9dd4ffd807427056
7
- data.tar.gz: 8b98f315be3dd31d80f77d85eeddd66ab434af8077d04358928c7be3793e7ad0565d37c9042666a93810694688a45e7147b9a540ae0505b061b34b77a58cf991
6
+ metadata.gz: a5304f933ec4c0b4e97ca263c2c6833a380dd99ae3beca60e00709c71dbe921024e9572bf1a2ba38e69729d8c8d7728c6d1ab2d3e854cdbfc720c8c494fb59fc
7
+ data.tar.gz: c79eac91747d34080ae55326d41005e0a71e2ab940abbb858f35e6601797e6ea923f0e4a21bf718456a0a3a6688a05c833ec41e5717d03bd6fcf4eaff2aa554b
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ActionMCP
2
2
 
3
- **ActionMCP** is a Ruby gem that provides essential tooling for building Model Context Protocol (MCP) capable servers.
3
+ **ActionMCP** is a Ruby gem that provides essential tooling for building Model Context Protocol (MCP) capable servers.
4
4
 
5
5
  It offers base classes and helpers for creating MCP applications, making it easier to integrate your Ruby/Rails application with the MCP standard.
6
6
 
@@ -12,7 +12,7 @@ With ActionMCP, you can focus on your app's logic while it handles the boilerpla
12
12
 
13
13
  Think of it as a universal interface for connecting AI assistants to external data sources and tools.
14
14
 
15
- MCP allows AI systems to plug into various resources in a consistent, secure way, enabling two-way integration between your data and AI-powered applications ([Introducing the Model Context Protocol \ Anthropic](https://www.anthropic.com/news/model-context-protocol#:~:text=The%20Model%20Context%20Protocol%20is,that%20connect%20to%20these%20servers)).
15
+ MCP allows AI systems to plug into various resources in a consistent, secure way, enabling two-way integration between your data and AI-powered applications ([Introducing the Model Context Protocol \ Anthropic](https://www.anthropic.com/news/model-context-protocol#:~:text=The%20Model%20Context%20Protocol%20is,that%20connect%20to%20these%20servers)).
16
16
 
17
17
  This means an AI (like an LLM) can request information or actions from your application through a well-defined protocol, and your app can provide context or perform tasks for the AI in return.
18
18
 
@@ -30,63 +30,82 @@ In short, ActionMCP helps you build an MCP server (the component that exposes ca
30
30
  To start using ActionMCP, add it to your project:
31
31
 
32
32
  - **Using Bundler (Rails or Ruby projects):** Add the gem to your Gemfile and run bundle install:
33
-
34
- execute:
35
- ```
33
+
34
+ ```bash
36
35
  $ bundle add actionmcp
37
36
  ```
38
37
 
39
- After installing, include the gem in your code by requiring it:
40
-
41
38
  This will load the ActionMCP library so you can start defining MCP prompts, tools, and resources in your application.
42
39
 
43
40
  ## Core Components
44
41
 
45
42
  ActionMCP provides three core abstractions to streamline MCP server development: **Prompt**, **Tool**, and **Resource**.
43
+
46
44
  These correspond to key MCP concepts and let you define what context or capabilities your server exposes to LLMs.
47
- Below is an overview of each component and how you might use it:
45
+
46
+ Note that ActionMCP requires a Rails application; it is not meant for standalone Ruby apps.
48
47
 
49
48
  ### Configuration
50
- ActionMCP is configured via config.action_mcp in your Rails application.
51
- By default, the name is set to your application's name and the version defaults to "0.0.1".
52
- You can override these settings in your configuration (e.g., in config/application.rb):
49
+
50
+ ActionMCP is configured via `config.action_mcp` in your Rails application.
51
+
52
+ By default, the name is set to your application's name and the version defaults to "0.0.1" unless your app has a version file.
53
+
54
+ You can override these settings in your configuration (e.g., in `config/application.rb`):
55
+
53
56
  ```ruby
54
57
  module Tron
55
58
  class Application < Rails::Application
56
59
  config.action_mcp.name = "Friendly MCP (Master Control Program)" # defaults to Rails.application.name
57
- config.action_mcp.version = "1.2.3" # defaults to "0.0.1"
58
- config.action_mcp.logging_enabled = true # defaults to true
60
+ config.action_mcp.version = "1.2.3" # defaults to "0.0.1"
61
+ config.action_mcp.logging_enabled = true # defaults to true
62
+ config.action_mcp.logging_level = :info # defaults to :info, can be :debug, :info, :warn, :error, :fatal
59
63
  end
60
64
  end
61
65
  ```
62
- For dynamic versioning, consider adding the rails_app_version gem.
63
66
 
64
- ## Generators
67
+ For dynamic versioning, consider adding the `rails_app_version` gem.
68
+
69
+ ### Engine
70
+
71
+ ActionMCP is implemented as a Rails engine, which means it can be mounted in your application's routes.
72
+ The engine provides no authentication or authorization by default, so you'll need to handle that in your application for now.
73
+
74
+ To mount the ActionMCP engine in your routes, add the following line to your `config/routes.rb`:
75
+
76
+ ```ruby
77
+ Rails.application.routes.draw do
78
+ mount ActionMCP::Engine => "/action_mcp"
79
+ end
80
+ ```
81
+
82
+ ### Generators
65
83
 
66
- ActionMCP includes Rails generators to help you quickly set up your MCP server components. You can generate the base classes for your MCP Prompt and Tool using the following commands.
84
+ ActionMCP includes Rails generators to help you quickly set up your MCP server components.
67
85
 
68
- To generate both the ApplicationPrompt and ApplicationTool files in your application, run:
86
+ You can generate the base classes for your MCP Prompt and Tool using the following command:
69
87
 
70
88
  ```bash
71
89
  bin/rails generate action_mcp:install
72
90
  ```
73
91
 
74
92
  This command will create:
75
- app/prompts/application_prompt.rb
76
- app/tools/application_tool.rb
93
+ - `app/prompts/application_prompt.rb`
94
+ - `app/tools/application_tool.rb`
77
95
 
78
- ### Generate a New Prompt
96
+ #### Generate a New Prompt
79
97
 
80
98
  Run the following command to generate a new prompt class:
81
99
 
82
100
  ```bash
83
101
  bin/rails generate action_mcp:prompt AnalyzeCode
84
102
  ```
85
- This command will create a file at app/prompts/analyze_code_prompt.rb with content similar to:
103
+
104
+ This command will create a file at `app/prompts/analyze_code_prompt.rb` with content similar to:
86
105
 
87
106
  ```ruby
88
107
  class AnalyzeCodePrompt < ApplicationPrompt
89
- # Override the prompt_name (otherwise we'd get "analyze-code")
108
+ # Override the prompt_name (otherwise we'd get "analyze_code")
90
109
  prompt_name "analyze-code"
91
110
 
92
111
  # Provide a user-facing description for your prompt.
@@ -96,38 +115,122 @@ class AnalyzeCodePrompt < ApplicationPrompt
96
115
  argument :language, description: "Programming language", default: "Ruby"
97
116
  argument :code, description: "Code to explain", required: true
98
117
 
99
- # Add validations (note: "Ruby" is not allowed per the validation)
100
- validates :language, inclusion: { in: %w[C Cobol FORTRAN] }
118
+ # Add validations
119
+ validates :language, inclusion: { in: %w[Ruby C Cobol FORTRAN] }
120
+
121
+ def call
122
+ # Implement your prompt logic here
123
+ render_text("Analyzing #{language} code: #{code}")
124
+ end
101
125
  end
102
126
  ```
103
127
 
104
- ## Generate a New Tool
128
+ #### Generate a New Tool
129
+
105
130
  Similarly, run the following command to generate a new tool class:
106
131
 
107
132
  ```bash
108
133
  bin/rails generate action_mcp:tool CalculateSum
109
134
  ```
110
135
 
111
- This command will create a file at app/tools/calculate_sum_tool.rb with content similar to:
136
+ This command will create a file at `app/tools/calculate_sum_tool.rb` with content similar to:
112
137
 
113
138
  ```ruby
114
139
  class CalculateSumTool < ApplicationTool
115
- tool_name "calculate-sum"
140
+ tool_name "calculate_sum"
116
141
  description "Calculate the sum of two numbers"
117
142
 
118
143
  property :a, type: "number", description: "First number", required: true
119
144
  property :b, type: "number", description: "Second number", required: true
145
+
146
+ def call
147
+ render_text(a + b)
148
+ end
120
149
  end
121
150
  ```
122
151
 
123
152
  ### ActionMCP::Prompt
124
153
 
125
- Make Rails Say Sexy stuff
154
+ A **Prompt** defines a question or request that an LLM can make to your application. It encapsulates the input parameters required for the request and any validations that need to be performed. For example, you might define a prompt called "analyze-code" that takes a code snippet as input and returns an analysis of the code.
126
155
 
127
156
  ### ActionMCP::Tool
128
157
 
129
- Make Rails Do Sexy stuff and serve beer to Clients.
158
+ A **Tool** defines an action that your application can perform on behalf of an LLM. It encapsulates the input parameters required for the action and any logic that needs to be executed. For example, you might define a tool called "execute-command" that takes a shell command as input and executes it on the server, returning the output. This could be used to retrieve system information, run scripts, or perform other administrative tasks.
130
159
 
131
160
  ### ActionMCP::Resource
132
161
 
133
- I dont need this for now
162
+ *I don't need this for now.*
163
+
164
+ ## Usage Example
165
+
166
+ Both Tool and Prompt classes are based on ActiveModel, which means they share the same initialization and validation behavior. You can instantiate them with initial values, update their attributes later if necessary, and then call the `call` method to execute the logic defined in your class.
167
+
168
+ ### Example for a Prompt
169
+
170
+ ```ruby
171
+ # Instantiate the prompt with initial values
172
+ analyze_prompt = AnalyzeCodePrompt.new(language: "Ruby", code: "def hello; puts 'Hello, world!'; end")
173
+
174
+ # Optionally update attributes later:
175
+ analyze_prompt.code = "def goodbye; puts 'Goodbye!'; end"
176
+
177
+ # Validate the prompt before calling it
178
+ if analyze_prompt.valid?
179
+ result = analyze_prompt.call # => #<ActionMCP::Content::Text:0x00000001239398c8 @text="The code you provided is written in Ruby and looks great!", @type="text">
180
+ puts result.to_h # => {type: "text", text: "The code you provided is written in Ruby and looks great!"}
181
+ else
182
+ puts analyze_prompt.errors.full_messages
183
+ end
184
+ ```
185
+
186
+ ### Example for a Tool
187
+
188
+ ```ruby
189
+ # Instantiate the tool with initial values
190
+ sum_tool = CalculateSumTool.new(a: 5, b: 10)
191
+
192
+ # Optionally update attributes later:
193
+ sum_tool.a = 15
194
+ sum_tool.b = 20
195
+
196
+ # Validate the tool before calling it
197
+ if sum_tool.valid?
198
+ result = sum_tool.call # => #<ActionMCP::Content::Text:0x0000000124cfaba0 @text="35.0", @type="text">
199
+ puts result.to_h # => {type: "text", text: "35.0"}
200
+ else
201
+ puts sum_tool.errors.full_messages
202
+ end
203
+ ```
204
+
205
+ These examples show that both prompts and tools follow a consistent pattern for initialization, validation, and execution, making it easy to integrate them into your application logic.
206
+
207
+ ## Examples & Important Notes
208
+
209
+ - **Running the Dummy App:**
210
+ After creating the database with `bin/rails db:prepare`, you can run the dummy application using:
211
+ ```bash
212
+ bin/rails s
213
+ ```
214
+ This allows you to test and interact with the MCP server from the dummy environment.
215
+ - **Inspecting the App:**
216
+ You can use the mcp inspector to test your app ```npx @modelcontextprotocol/inspector```
217
+ the path by default will be http://localhost:3000/action_mcp
218
+
219
+
220
+ - **Postgres on macOS:**
221
+ If you are using Postgres on macOS, you may encounter issues due to a bug in Puma and the `pg` gem. To work around this, set the following environment variables:
222
+ ```bash
223
+ export PGGSSENCMODE=disable
224
+ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
225
+ ```
226
+ More details can be found in [Rails Issue #38560](https://github.com/rails/rails/issues/38560).
227
+
228
+ - **Notifiers:**
229
+ ActionMCP works with the ActiveCable Postgres notifier by default, but its architecture is flexible enough to support other notifier implementations.
230
+
231
+ - **API Stability:**
232
+ The ActionMCP API is stable, though it is acceptable for improvements and changes to be introduced as we move forward. This approach ensures the gem stays modern and adaptable to evolving requirements.
233
+
234
+ ## Conclusion
235
+
236
+ ActionMCP empowers developers to build MCP-compliant servers efficiently by handling the standardization and boilerplate associated with integrating with LLMs. With built-in generators, clear configuration options, robust usage examples, and important deployment considerations, it is designed to accelerate development and integration work while remaining flexible for future enhancements.
data/Rakefile CHANGED
@@ -4,6 +4,4 @@ require 'bundler/setup'
4
4
 
5
5
  APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
6
6
 
7
- load 'rails/tasks/statistics.rake'
8
-
9
7
  require 'bundler/gem_tasks'
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