actionmcp 0.50.4 → 0.50.5
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.
- checksums.yaml +4 -4
- data/README.md +77 -6
- data/app/controllers/action_mcp/application_controller.rb +6 -2
- data/app/models/action_mcp/session.rb +3 -1
- data/lib/action_mcp/client/blueprint.rb +5 -3
- data/lib/action_mcp/client/catalog.rb +4 -2
- data/lib/action_mcp/client/collection.rb +80 -4
- data/lib/action_mcp/client/json_rpc_handler.rb +8 -0
- data/lib/action_mcp/client/prompts.rb +12 -3
- data/lib/action_mcp/client/resources.rb +24 -6
- data/lib/action_mcp/client/toolbox.rb +9 -4
- data/lib/action_mcp/client/tools.rb +12 -3
- data/lib/action_mcp/configuration.rb +3 -1
- data/lib/action_mcp/content/audio.rb +3 -2
- data/lib/action_mcp/content/base.rb +28 -0
- data/lib/action_mcp/content/image.rb +5 -3
- data/lib/action_mcp/content/resource.rb +6 -5
- data/lib/action_mcp/content/text.rb +3 -2
- data/lib/action_mcp/content.rb +8 -3
- data/lib/action_mcp/renderable.rb +4 -4
- data/lib/action_mcp/server/capabilities.rb +9 -3
- data/lib/action_mcp/server/sampling_request.rb +1 -1
- data/lib/action_mcp/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 334dbf522501eda848a1ab5d66378792b71acfd4177366de980e868be405d4dc
|
4
|
+
data.tar.gz: 1d29dd025817770f2389e99e9e11978e3ca813af5326d1c89ffef867267a82be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c7ef8680677a244d909f482440aeed23033cfb9fbe4d109a14b474eefc4eec061436c22c39bd9b161f84c30cda9e707b9b79acf96bf54582e4fadef219ce5bd
|
7
|
+
data.tar.gz: b8f01db29cac834c91d75353dbeaa9d96c2cc5646c04eb8d17f625b63432e1304d0ae85a7a41a9f892020ab1d64a6c2fafe2cc2d28e017bc3184886da677d9f6
|
data/README.md
CHANGED
@@ -222,12 +222,31 @@ module Tron
|
|
222
222
|
config.action_mcp.version = "1.2.3" # defaults to "0.0.1"
|
223
223
|
config.action_mcp.logging_enabled = true # defaults to true
|
224
224
|
config.action_mcp.logging_level = :info # defaults to :info, can be :debug, :info, :warn, :error, :fatal
|
225
|
+
config.action_mcp.vibed_ignore_version = false # defaults to false, set to true to ignore client protocol version mismatches
|
225
226
|
end
|
226
227
|
end
|
227
228
|
```
|
228
229
|
|
229
230
|
For dynamic versioning, consider adding the `rails_app_version` gem.
|
230
231
|
|
232
|
+
### Protocol Version Compatibility
|
233
|
+
|
234
|
+
By default, ActionMCP requires clients to use the exact protocol version supported by the server (currently "2025-03-26"). If the client specifies a different version during initialization, the request will be rejected with an error.
|
235
|
+
|
236
|
+
To support clients with incompatible protocol versions, you can enable the `vibed_ignore_version` option:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
# In config/application.rb or an initializer
|
240
|
+
Rails.application.config.action_mcp.vibed_ignore_version = true
|
241
|
+
```
|
242
|
+
|
243
|
+
When enabled, the server will ignore protocol version mismatches from clients and always use the latest supported version. This is useful for:
|
244
|
+
- Development environments with older client libraries
|
245
|
+
- Supporting clients that cannot be easily updated
|
246
|
+
- Situations where protocol differences are minor and known to be compatible
|
247
|
+
|
248
|
+
> **Note:** Using `vibed_ignore_version = true` in production is not recommended as it may lead to unexpected behavior if clients rely on specific protocol features that differ between versions.
|
249
|
+
|
231
250
|
### PubSub Configuration
|
232
251
|
|
233
252
|
ActionMCP uses a pub/sub system for real-time communication. You can choose between several adapters:
|
@@ -346,7 +365,7 @@ This ensures all thread pools are properly terminated and tasks are completed.
|
|
346
365
|
|
347
366
|
## Engine and Mounting
|
348
367
|
|
349
|
-
**ActionMCP** runs as a standalone Rack application.
|
368
|
+
**ActionMCP** runs as a standalone Rack application. **Do not attempt to mount it in your application's `routes.rb`**—it is not designed to be mounted as an engine at a custom path. When you use `run ActionMCP::Engine` in your `mcp.ru`, the MCP endpoint is always available at the root path (`/`).
|
350
369
|
|
351
370
|
### Installing the Configuration Generator
|
352
371
|
|
@@ -367,17 +386,69 @@ This will create `config/mcp.yml` with example configurations for all environmen
|
|
367
386
|
# Load the full Rails environment to access models, DB, Redis, etc.
|
368
387
|
require_relative "config/environment"
|
369
388
|
|
370
|
-
|
371
|
-
|
372
|
-
end
|
373
|
-
|
389
|
+
# No need to set a custom endpoint path. The MCP endpoint is always served at root ("/")
|
390
|
+
# when using ActionMCP::Engine directly.
|
374
391
|
run ActionMCP::Engine
|
375
392
|
```
|
376
393
|
### 2. Start the server
|
377
394
|
```bash
|
378
|
-
bin/rails s -c mcp.ru -p
|
395
|
+
bin/rails s -c mcp.ru -p 62770 -P tmp/pids/mcps0.pid
|
379
396
|
```
|
380
397
|
|
398
|
+
## Production Deployment of MCPS0
|
399
|
+
|
400
|
+
In production, **MCPS0** (the MCP server) is a standard Rack application. You can run it using any Rack-compatible server (such as Puma, Unicorn, or Passenger).
|
401
|
+
|
402
|
+
> **For best performance and concurrency, it is highly recommended to use a modern, synchronous server like [Falcon](https://github.com/socketry/falcon)**. Falcon is optimized for streaming and concurrent workloads, making it ideal for MCP servers. You can still use Puma, Unicorn, or Passenger, but Falcon will generally provide superior throughput and responsiveness for real-time and streaming use cases.
|
403
|
+
|
404
|
+
You have two main options for exposing the server:
|
405
|
+
|
406
|
+
### 1. Dedicated Port
|
407
|
+
|
408
|
+
Run MCPS0 on its own TCP port (commonly `62770`):
|
409
|
+
|
410
|
+
**With Falcon:**
|
411
|
+
```bash
|
412
|
+
bundle exec falcon serve --bind http://0.0.0.0:62770 mcp.ru
|
413
|
+
```
|
414
|
+
|
415
|
+
**With Puma:**
|
416
|
+
```bash
|
417
|
+
bundle exec rails s -c mcp.ru -p 62770
|
418
|
+
```
|
419
|
+
|
420
|
+
Then, use your web server (Nginx, Apache, etc.) to reverse proxy requests to this port.
|
421
|
+
|
422
|
+
### 2. Unix Socket
|
423
|
+
|
424
|
+
Alternatively, you can run MCPS0 on a Unix socket for improved performance and security (especially when the web server and app server are on the same machine):
|
425
|
+
|
426
|
+
**With Falcon:**
|
427
|
+
```bash
|
428
|
+
bundle exec falcon serve --bind unix:/tmp/mcps0.sock mcp.ru
|
429
|
+
```
|
430
|
+
|
431
|
+
**With Puma:**
|
432
|
+
```bash
|
433
|
+
bundle exec puma -C config/puma.rb -b unix:///tmp/mcps0.sock -c mcp.ru
|
434
|
+
```
|
435
|
+
|
436
|
+
And configure your web server to proxy to the socket:
|
437
|
+
|
438
|
+
```nginx
|
439
|
+
location /mcp/ {
|
440
|
+
proxy_pass http://unix:/tmp/mcps0.sock:;
|
441
|
+
proxy_set_header Host $host;
|
442
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
443
|
+
}
|
444
|
+
```
|
445
|
+
|
446
|
+
**Key Points:**
|
447
|
+
- MCPS0 is a standalone Rack app—run it separately from your main Rails server.
|
448
|
+
- You can expose it via a TCP port (e.g., 62770) or a Unix socket.
|
449
|
+
- Use a reverse proxy (Nginx, Apache, etc.) to route requests to MCPS0 as needed.
|
450
|
+
- This separation ensures reliability and scalability for both your main app and MCP services.
|
451
|
+
|
381
452
|
## Generators
|
382
453
|
|
383
454
|
ActionMCP includes Rails generators to help you quickly set up your MCP server components.
|
@@ -207,8 +207,12 @@ module ActionMCP
|
|
207
207
|
session_id = extract_session_id
|
208
208
|
if session_id
|
209
209
|
session = Session.find_by(id: session_id)
|
210
|
-
if session
|
211
|
-
|
210
|
+
if session
|
211
|
+
if ActionMCP.configuration.vibed_ignore_version
|
212
|
+
session.update!(protocol_version: self.class::REQUIRED_PROTOCOL_VERSION) if session.protocol_version != self.class::REQUIRED_PROTOCOL_VERSION
|
213
|
+
elsif session.protocol_version != self.class::REQUIRED_PROTOCOL_VERSION
|
214
|
+
session.update!(protocol_version: self.class::REQUIRED_PROTOCOL_VERSION)
|
215
|
+
end
|
212
216
|
end
|
213
217
|
session
|
214
218
|
else
|
@@ -64,7 +64,7 @@ module ActionMCP
|
|
64
64
|
before_create :set_server_info, if: -> { role == "server" }
|
65
65
|
before_create :set_server_capabilities, if: -> { role == "server" }
|
66
66
|
|
67
|
-
validates :protocol_version, inclusion: { in: SUPPORTED_VERSIONS }, allow_nil: true
|
67
|
+
validates :protocol_version, inclusion: { in: SUPPORTED_VERSIONS }, allow_nil: true, unless: -> { ActionMCP.configuration.vibed_ignore_version }
|
68
68
|
|
69
69
|
def close!
|
70
70
|
dummy_callback = ->(*) { } # this callback seem broken
|
@@ -102,6 +102,8 @@ module ActionMCP
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def set_protocol_version(version)
|
105
|
+
# If vibed_ignore_version is true, always use the latest supported version
|
106
|
+
version = PROTOCOL_VERSION if ActionMCP.configuration.vibed_ignore_version
|
105
107
|
update(protocol_version: version)
|
106
108
|
end
|
107
109
|
|
@@ -89,18 +89,19 @@ module ActionMCP
|
|
89
89
|
|
90
90
|
# Internal Blueprint class to represent individual URI templates
|
91
91
|
class ResourceTemplate
|
92
|
-
attr_reader :pattern, :name, :description, :mime_type
|
92
|
+
attr_reader :pattern, :name, :description, :mime_type, :annotations
|
93
93
|
|
94
94
|
# Initialize a new ResourceTemplate instance
|
95
95
|
#
|
96
96
|
# @param data [Hash] ResourceTemplate definition hash containing uriTemplate, name, description,
|
97
|
-
# and optionally mimeType
|
97
|
+
# and optionally mimeType, and annotations
|
98
98
|
def initialize(data)
|
99
99
|
@pattern = data["uriTemplate"]
|
100
100
|
@name = data["name"]
|
101
101
|
@description = data["description"]
|
102
102
|
@mime_type = data["mimeType"]
|
103
103
|
@variable_pattern = /{([^}]+)}/
|
104
|
+
@annotations = data["annotations"] || {}
|
104
105
|
end
|
105
106
|
|
106
107
|
# Extract variable names from the template pattern
|
@@ -152,7 +153,8 @@ module ActionMCP
|
|
152
153
|
"uriTemplate" => @pattern,
|
153
154
|
"name" => @name,
|
154
155
|
"description" => @description,
|
155
|
-
"mimeType" => @mime_type
|
156
|
+
"mimeType" => @mime_type,
|
157
|
+
"annotations" => @annotations
|
156
158
|
}
|
157
159
|
end
|
158
160
|
end
|
@@ -103,7 +103,7 @@ module ActionMCP
|
|
103
103
|
|
104
104
|
# Internal Resource class to represent individual resources
|
105
105
|
class Resource
|
106
|
-
attr_reader :uri, :name, :description, :mime_type
|
106
|
+
attr_reader :uri, :name, :description, :mime_type, :annotations
|
107
107
|
|
108
108
|
# Initialize a new Resource instance
|
109
109
|
#
|
@@ -113,6 +113,7 @@ module ActionMCP
|
|
113
113
|
@name = data["name"]
|
114
114
|
@description = data["description"]
|
115
115
|
@mime_type = data["mimeType"]
|
116
|
+
@annotations = data["annotations"] || {}
|
116
117
|
end
|
117
118
|
|
118
119
|
# Get the file extension from the resource name
|
@@ -153,7 +154,8 @@ module ActionMCP
|
|
153
154
|
"uri" => @uri,
|
154
155
|
"name" => @name,
|
155
156
|
"description" => @description,
|
156
|
-
"mimeType" => @mime_type
|
157
|
+
"mimeType" => @mime_type,
|
158
|
+
"annotations" => @annotations
|
157
159
|
}
|
158
160
|
end
|
159
161
|
end
|
@@ -6,18 +6,28 @@ module ActionMCP
|
|
6
6
|
class Collection
|
7
7
|
include RequestTimeouts
|
8
8
|
|
9
|
-
attr_reader :client, :loaded
|
9
|
+
attr_reader :client, :loaded, :next_cursor, :total
|
10
10
|
|
11
11
|
def initialize(items, client, silence_sql: true)
|
12
12
|
@collection_data = items || []
|
13
13
|
@client = client
|
14
14
|
@loaded = !@collection_data.empty?
|
15
15
|
@silence_sql = silence_sql
|
16
|
+
@next_cursor = nil
|
17
|
+
@total = items&.size || 0
|
16
18
|
end
|
17
19
|
|
18
|
-
def all
|
19
|
-
|
20
|
-
|
20
|
+
def all(limit: nil)
|
21
|
+
if limit
|
22
|
+
# If a limit is provided, use pagination
|
23
|
+
result = []
|
24
|
+
each_page(limit: limit) { |page| result.concat(page) }
|
25
|
+
result
|
26
|
+
else
|
27
|
+
# Otherwise, maintain the old behavior
|
28
|
+
silence_logs { load_items unless @loaded }
|
29
|
+
@collection_data
|
30
|
+
end
|
21
31
|
end
|
22
32
|
|
23
33
|
def all!(timeout: DEFAULT_TIMEOUT)
|
@@ -25,6 +35,49 @@ module ActionMCP
|
|
25
35
|
@collection_data
|
26
36
|
end
|
27
37
|
|
38
|
+
# Fetch a single page of results
|
39
|
+
#
|
40
|
+
# @param cursor [String, nil] Optional cursor for pagination
|
41
|
+
# @param limit [Integer, nil] Optional limit for page size
|
42
|
+
# @return [Array<Object>] The page of items
|
43
|
+
def page(cursor: nil, limit: nil)
|
44
|
+
silence_logs { load_page(cursor: cursor, limit: limit) }
|
45
|
+
@collection_data
|
46
|
+
end
|
47
|
+
|
48
|
+
# Check if there are more pages available
|
49
|
+
#
|
50
|
+
# @return [Boolean] true if there are more pages to fetch
|
51
|
+
def has_more_pages?
|
52
|
+
!@next_cursor.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Fetch the next page of results
|
56
|
+
#
|
57
|
+
# @param limit [Integer, nil] Optional limit for page size
|
58
|
+
# @return [Array<Object>] The next page of items, or empty array if no more pages
|
59
|
+
def next_page(limit: nil)
|
60
|
+
return [] unless has_more_pages?
|
61
|
+
page(cursor: @next_cursor, limit: limit)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Iterate through all pages of results
|
65
|
+
#
|
66
|
+
# @param limit [Integer, nil] Optional limit for page size
|
67
|
+
# @yield [page] Block to process each page
|
68
|
+
# @yieldparam page [Array<Object>] A page of items
|
69
|
+
def each_page(limit: nil, &block)
|
70
|
+
return unless block_given?
|
71
|
+
|
72
|
+
current_page = page(limit: limit)
|
73
|
+
yield current_page
|
74
|
+
|
75
|
+
while has_more_pages?
|
76
|
+
current_page = next_page(limit: limit)
|
77
|
+
yield current_page unless current_page.empty?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
28
81
|
# Filter items based on a given block
|
29
82
|
#
|
30
83
|
# @yield [item] Block that determines whether to include an item
|
@@ -63,6 +116,29 @@ module ActionMCP
|
|
63
116
|
load_with_timeout(@load_method, force: force, timeout: timeout)
|
64
117
|
end
|
65
118
|
|
119
|
+
def load_page(cursor: nil, limit: nil, timeout: DEFAULT_TIMEOUT)
|
120
|
+
# Make sure @load_method is defined in the subclass
|
121
|
+
raise NotImplementedError, "Subclass must define @load_method" unless defined?(@load_method)
|
122
|
+
|
123
|
+
# Use the RequestTimeouts module to handle the request with pagination params
|
124
|
+
params = {}
|
125
|
+
params[:cursor] = cursor if cursor
|
126
|
+
params[:limit] = limit if limit
|
127
|
+
|
128
|
+
request_id = client.send(@load_method, params)
|
129
|
+
|
130
|
+
start_time = Time.now
|
131
|
+
while !@loaded && (Time.now - start_time) < timeout
|
132
|
+
sleep(0.1)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Update @loaded status even if we timed out
|
136
|
+
@loaded = true
|
137
|
+
|
138
|
+
# Return the loaded data
|
139
|
+
@collection_data
|
140
|
+
end
|
141
|
+
|
66
142
|
private
|
67
143
|
|
68
144
|
def silence_logs
|
@@ -85,15 +85,23 @@ module ActionMCP
|
|
85
85
|
case request.rpc_method
|
86
86
|
when "tools/list"
|
87
87
|
client.toolbox.tools = result["tools"]
|
88
|
+
client.toolbox.instance_variable_set(:@next_cursor, result["nextCursor"])
|
89
|
+
client.toolbox.instance_variable_set(:@total, result["tools"]&.size || 0)
|
88
90
|
return true
|
89
91
|
when "prompts/list"
|
90
92
|
client.prompt_book.prompts = result["prompts"]
|
93
|
+
client.prompt_book.instance_variable_set(:@next_cursor, result["nextCursor"])
|
94
|
+
client.prompt_book.instance_variable_set(:@total, result["prompts"]&.size || 0)
|
91
95
|
return true
|
92
96
|
when "resources/list"
|
93
97
|
client.catalog.resources = result["resources"]
|
98
|
+
client.catalog.instance_variable_set(:@next_cursor, result["nextCursor"])
|
99
|
+
client.catalog.instance_variable_set(:@total, result["resources"]&.size || 0)
|
94
100
|
return true
|
95
101
|
when "resources/templates/list"
|
96
102
|
client.blueprint.templates = result["resourceTemplates"]
|
103
|
+
client.blueprint.instance_variable_set(:@next_cursor, result["nextCursor"])
|
104
|
+
client.blueprint.instance_variable_set(:@total, result["resourceTemplates"]&.size || 0)
|
97
105
|
return true
|
98
106
|
end
|
99
107
|
|
@@ -4,12 +4,21 @@ module ActionMCP
|
|
4
4
|
module Client
|
5
5
|
module Prompts
|
6
6
|
# List all available prompts from the server
|
7
|
+
# @param params [Hash] Optional parameters for pagination
|
8
|
+
# @option params [String] :cursor Pagination cursor for fetching next page
|
9
|
+
# @option params [Integer] :limit Maximum number of items to return
|
7
10
|
# @return [String] Request ID for tracking the request
|
8
|
-
def list_prompts
|
11
|
+
def list_prompts(params = {})
|
9
12
|
request_id = SecureRandom.uuid_v7
|
10
13
|
|
11
|
-
# Send request
|
12
|
-
|
14
|
+
# Send request with pagination parameters if provided
|
15
|
+
request_params = {}
|
16
|
+
request_params[:cursor] = params[:cursor] if params[:cursor]
|
17
|
+
request_params[:limit] = params[:limit] if params[:limit]
|
18
|
+
|
19
|
+
send_jsonrpc_request("prompts/list",
|
20
|
+
params: request_params.empty? ? nil : request_params,
|
21
|
+
id: request_id)
|
13
22
|
|
14
23
|
# Return request ID for tracking the request
|
15
24
|
request_id
|
@@ -4,24 +4,42 @@ module ActionMCP
|
|
4
4
|
module Client
|
5
5
|
module Resources
|
6
6
|
# List all available resources from the server
|
7
|
+
# @param params [Hash] Optional parameters for pagination
|
8
|
+
# @option params [String] :cursor Pagination cursor for fetching next page
|
9
|
+
# @option params [Integer] :limit Maximum number of items to return
|
7
10
|
# @return [String] Request ID for tracking the request
|
8
|
-
def list_resources
|
11
|
+
def list_resources(params = {})
|
9
12
|
request_id = SecureRandom.uuid_v7
|
10
13
|
|
11
|
-
# Send request
|
12
|
-
|
14
|
+
# Send request with pagination parameters if provided
|
15
|
+
request_params = {}
|
16
|
+
request_params[:cursor] = params[:cursor] if params[:cursor]
|
17
|
+
request_params[:limit] = params[:limit] if params[:limit]
|
18
|
+
|
19
|
+
send_jsonrpc_request("resources/list",
|
20
|
+
params: request_params.empty? ? nil : request_params,
|
21
|
+
id: request_id)
|
13
22
|
|
14
23
|
# Return request ID for tracking the request
|
15
24
|
request_id
|
16
25
|
end
|
17
26
|
|
18
27
|
# List resource templates from the server
|
28
|
+
# @param params [Hash] Optional parameters for pagination
|
29
|
+
# @option params [String] :cursor Pagination cursor for fetching next page
|
30
|
+
# @option params [Integer] :limit Maximum number of items to return
|
19
31
|
# @return [String] Request ID for tracking the request
|
20
|
-
def list_resource_templates
|
32
|
+
def list_resource_templates(params = {})
|
21
33
|
request_id = SecureRandom.uuid_v7
|
22
34
|
|
23
|
-
# Send request
|
24
|
-
|
35
|
+
# Send request with pagination parameters if provided
|
36
|
+
request_params = {}
|
37
|
+
request_params[:cursor] = params[:cursor] if params[:cursor]
|
38
|
+
request_params[:limit] = params[:limit] if params[:limit]
|
39
|
+
|
40
|
+
send_jsonrpc_request("resources/templates/list",
|
41
|
+
params: request_params.empty? ? nil : request_params,
|
42
|
+
id: request_id)
|
25
43
|
|
26
44
|
# Return request ID for tracking the request
|
27
45
|
request_id
|
@@ -104,15 +104,17 @@ module ActionMCP
|
|
104
104
|
|
105
105
|
# Internal Tool class to represent individual tools
|
106
106
|
class Tool
|
107
|
-
attr_reader :name, :description, :input_schema
|
107
|
+
attr_reader :name, :description, :input_schema, :annotations
|
108
108
|
|
109
109
|
# Initialize a new Tool instance
|
110
110
|
#
|
111
111
|
# @param data [Hash] Tool definition hash containing name, description, and inputSchema
|
112
|
+
# and optionally annotations
|
112
113
|
def initialize(data)
|
113
114
|
@name = data["name"]
|
114
115
|
@description = data["description"]
|
115
116
|
@input_schema = data["inputSchema"] || {}
|
117
|
+
@annotations = data["annotations"] || {}
|
116
118
|
end
|
117
119
|
|
118
120
|
# Get all required properties for this tool
|
@@ -160,7 +162,8 @@ module ActionMCP
|
|
160
162
|
{
|
161
163
|
"name" => @name,
|
162
164
|
"description" => @description,
|
163
|
-
"inputSchema" => @input_schema
|
165
|
+
"inputSchema" => @input_schema,
|
166
|
+
"annotations" => @annotations
|
164
167
|
}
|
165
168
|
end
|
166
169
|
|
@@ -171,7 +174,8 @@ module ActionMCP
|
|
171
174
|
{
|
172
175
|
"name" => @name,
|
173
176
|
"description" => @description,
|
174
|
-
"input_schema" => @input_schema.transform_keys { |k| k == "inputSchema" ? "input_schema" : k }
|
177
|
+
"input_schema" => @input_schema.transform_keys { |k| k == "inputSchema" ? "input_schema" : k },
|
178
|
+
"annotations" => @annotations
|
175
179
|
}
|
176
180
|
end
|
177
181
|
|
@@ -184,7 +188,8 @@ module ActionMCP
|
|
184
188
|
"function" => {
|
185
189
|
"name" => @name,
|
186
190
|
"description" => @description,
|
187
|
-
"parameters" => @input_schema
|
191
|
+
"parameters" => @input_schema,
|
192
|
+
"annotations" => @annotations
|
188
193
|
}
|
189
194
|
}
|
190
195
|
end
|
@@ -4,12 +4,21 @@ module ActionMCP
|
|
4
4
|
module Client
|
5
5
|
module Tools
|
6
6
|
# List all available tools from the server
|
7
|
+
# @param params [Hash] Optional parameters for pagination
|
8
|
+
# @option params [String] :cursor Pagination cursor for fetching next page
|
9
|
+
# @option params [Integer] :limit Maximum number of items to return
|
7
10
|
# @return [String] Request ID for tracking the request
|
8
|
-
def list_tools
|
11
|
+
def list_tools(params = {})
|
9
12
|
request_id = SecureRandom.uuid_v7
|
10
13
|
|
11
|
-
# Send request
|
12
|
-
|
14
|
+
# Send request with pagination parameters if provided
|
15
|
+
request_params = {}
|
16
|
+
request_params[:cursor] = params[:cursor] if params[:cursor]
|
17
|
+
request_params[:limit] = params[:limit] if params[:limit]
|
18
|
+
|
19
|
+
send_jsonrpc_request("tools/list",
|
20
|
+
params: request_params.empty? ? nil : request_params,
|
21
|
+
id: request_id)
|
13
22
|
|
14
23
|
# Return request ID for timeout tracking
|
15
24
|
request_id
|
@@ -26,6 +26,8 @@ module ActionMCP
|
|
26
26
|
:sse_heartbeat_interval,
|
27
27
|
:post_response_preference, # :json or :sse
|
28
28
|
:protocol_version,
|
29
|
+
# --- VibedIgnoreVersion Option ---
|
30
|
+
:vibed_ignore_version,
|
29
31
|
# --- SSE Resumability Options ---
|
30
32
|
:enable_sse_resumability,
|
31
33
|
:sse_event_retention_period,
|
@@ -38,10 +40,10 @@ module ActionMCP
|
|
38
40
|
@resources_subscribe = false
|
39
41
|
@active_profile = :primary
|
40
42
|
@profiles = default_profiles
|
41
|
-
|
42
43
|
@sse_heartbeat_interval = 30
|
43
44
|
@post_response_preference = :json
|
44
45
|
@protocol_version = "2025-03-26"
|
46
|
+
@vibed_ignore_version = false
|
45
47
|
|
46
48
|
# Resumability defaults
|
47
49
|
@enable_sse_resumability = true
|
@@ -12,8 +12,9 @@ module ActionMCP
|
|
12
12
|
#
|
13
13
|
# @param data [String] The base64-encoded audio data.
|
14
14
|
# @param mime_type [String] The MIME type of the audio data.
|
15
|
-
|
16
|
-
|
15
|
+
# @param annotations [Hash, nil] Optional annotations for the audio content.
|
16
|
+
def initialize(data, mime_type, annotations: nil)
|
17
|
+
super("audio", annotations: annotations)
|
17
18
|
@data = data
|
18
19
|
@mime_type = mime_type
|
19
20
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionMCP
|
4
|
+
module Content
|
5
|
+
# Base class for all content types, supporting annotations.
|
6
|
+
class Base
|
7
|
+
attr_reader :type, :annotations
|
8
|
+
|
9
|
+
# Initializes a new content base.
|
10
|
+
#
|
11
|
+
# @param type [String] The type of the content (e.g., "text", "image", etc.)
|
12
|
+
# @param annotations [Hash, nil] Optional annotations for the content.
|
13
|
+
def initialize(type, annotations: nil)
|
14
|
+
@type = type
|
15
|
+
@annotations = annotations
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns a hash representation of the base content.
|
19
|
+
#
|
20
|
+
# @return [Hash] The hash representation of the base content.
|
21
|
+
def to_h
|
22
|
+
h = { type: @type }
|
23
|
+
h[:annotations] = @annotations if @annotations
|
24
|
+
h
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -12,8 +12,9 @@ module ActionMCP
|
|
12
12
|
#
|
13
13
|
# @param data [String] The base64-encoded image data.
|
14
14
|
# @param mime_type [String] The MIME type of the image data.
|
15
|
-
|
16
|
-
|
15
|
+
# @param annotations [Hash, nil] Optional annotations for the image content.
|
16
|
+
def initialize(data, mime_type, annotations: nil)
|
17
|
+
super("image", annotations: annotations)
|
17
18
|
@data = data
|
18
19
|
@mime_type = mime_type
|
19
20
|
end
|
@@ -22,7 +23,8 @@ module ActionMCP
|
|
22
23
|
#
|
23
24
|
# @return [Hash] The hash representation of the image content.
|
24
25
|
def to_h
|
25
|
-
super.merge(data: @data, mimeType: @mime_type)
|
26
|
+
h = super.merge(data: @data, mimeType: @mime_type)
|
27
|
+
h
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -9,7 +9,7 @@ module ActionMCP
|
|
9
9
|
# @return [String] The MIME type of the resource.
|
10
10
|
# @return [String, nil] The text content of the resource (optional).
|
11
11
|
# @return [String, nil] The base64-encoded blob of the resource (optional).
|
12
|
-
attr_reader :uri, :mime_type, :text, :blob
|
12
|
+
attr_reader :uri, :mime_type, :text, :blob, :annotations
|
13
13
|
|
14
14
|
# Initializes a new Resource content.
|
15
15
|
#
|
@@ -17,22 +17,23 @@ module ActionMCP
|
|
17
17
|
# @param mime_type [String] The MIME type of the resource.
|
18
18
|
# @param text [String, nil] The text content of the resource (optional).
|
19
19
|
# @param blob [String, nil] The base64-encoded blob of the resource (optional).
|
20
|
-
|
21
|
-
|
20
|
+
# @param annotations [Hash, nil] Optional annotations for the resource.
|
21
|
+
def initialize(uri, mime_type = "text/plain", text: nil, blob: nil, annotations: nil)
|
22
|
+
super("resource", annotations: annotations)
|
22
23
|
@uri = uri
|
23
24
|
@mime_type = mime_type
|
24
25
|
@text = text
|
25
26
|
@blob = blob
|
27
|
+
@annotations = annotations
|
26
28
|
end
|
27
29
|
|
28
30
|
# Returns a hash representation of the resource content.
|
29
31
|
#
|
30
32
|
# @return [Hash] The hash representation of the resource content.
|
31
33
|
def to_h
|
32
|
-
resource_data =
|
34
|
+
resource_data = super.merge(uri: @uri, mimeType: @mime_type)
|
33
35
|
resource_data[:text] = @text if @text
|
34
36
|
resource_data[:blob] = @blob if @blob
|
35
|
-
|
36
37
|
resource_data
|
37
38
|
end
|
38
39
|
end
|
@@ -10,8 +10,9 @@ module ActionMCP
|
|
10
10
|
# Initializes a new Text content.
|
11
11
|
#
|
12
12
|
# @param text [String] The text content.
|
13
|
-
|
14
|
-
|
13
|
+
# @param annotations [Hash, nil] Optional annotations for the content.
|
14
|
+
def initialize(text, annotations: nil)
|
15
|
+
super("text", annotations: annotations)
|
15
16
|
@text = text.to_s
|
16
17
|
end
|
17
18
|
|
data/lib/action_mcp/content.rb
CHANGED
@@ -6,20 +6,25 @@ module ActionMCP
|
|
6
6
|
# Base class for MCP content items.
|
7
7
|
class Base
|
8
8
|
# @return [Symbol] The type of content.
|
9
|
-
|
9
|
+
# @return [Hash, nil] Optional annotations for the content.
|
10
|
+
attr_reader :type, :annotations
|
10
11
|
|
11
12
|
# Initializes a new content item.
|
12
13
|
#
|
13
14
|
# @param type [Symbol] The type of content.
|
14
|
-
|
15
|
+
# @param annotations [Hash, nil] Optional annotations for the content.
|
16
|
+
def initialize(type, annotations: nil)
|
15
17
|
@type = type
|
18
|
+
@annotations = annotations
|
16
19
|
end
|
17
20
|
|
18
21
|
# Returns a hash representation of the content.
|
19
22
|
#
|
20
23
|
# @return [Hash] The hash representation.
|
21
24
|
def to_h
|
22
|
-
{ type: @type }
|
25
|
+
h = { type: @type }
|
26
|
+
h[:annotations] = @annotations if @annotations
|
27
|
+
h
|
23
28
|
end
|
24
29
|
|
25
30
|
# Returns a JSON representation of the content.
|
@@ -22,13 +22,13 @@ module ActionMCP
|
|
22
22
|
#
|
23
23
|
def render(text: nil, audio: nil, image: nil, resource: nil, mime_type: nil, blob: nil)
|
24
24
|
if text
|
25
|
-
Content::Text.new(text)
|
25
|
+
Content::Text.new(text, annotations: nil)
|
26
26
|
elsif audio && mime_type
|
27
|
-
Content::Audio.new(audio, mime_type)
|
27
|
+
Content::Audio.new(audio, mime_type, annotations: nil)
|
28
28
|
elsif image && mime_type
|
29
|
-
Content::Image.new(image, mime_type)
|
29
|
+
Content::Image.new(image, mime_type, annotations: nil)
|
30
30
|
elsif resource && mime_type
|
31
|
-
Content::Resource.new(resource, mime_type, text: text, blob: blob)
|
31
|
+
Content::Resource.new(resource, mime_type, text: text, blob: blob, annotations: nil)
|
32
32
|
else
|
33
33
|
raise ArgumentError, "No content to render"
|
34
34
|
end
|
@@ -20,7 +20,7 @@ module ActionMCP
|
|
20
20
|
payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_602, message: "Missing or invalid 'protocolVersion'" } } }
|
21
21
|
end
|
22
22
|
# Check if the protocol version is supported
|
23
|
-
unless ActionMCP::SUPPORTED_VERSIONS.include?(client_protocol_version)
|
23
|
+
unless ActionMCP.configuration.vibed_ignore_version || ActionMCP::SUPPORTED_VERSIONS.include?(client_protocol_version)
|
24
24
|
error_data = {
|
25
25
|
supported: ActionMCP::SUPPORTED_VERSIONS,
|
26
26
|
requested: client_protocol_version
|
@@ -53,9 +53,15 @@ module ActionMCP
|
|
53
53
|
payload: { jsonrpc: "2.0", id: request_id, error: { code: -32_603, message: "Failed to initialize session" } } }
|
54
54
|
end
|
55
55
|
|
56
|
-
# Send the successful response with the protocol version
|
56
|
+
# Send the successful response with the correct protocol version
|
57
57
|
capabilities_payload = session.server_capabilities_payload
|
58
|
-
|
58
|
+
# If vibed_ignore_version is true, always use the latest supported version in the response
|
59
|
+
# Otherwise, use the client's requested version
|
60
|
+
if ActionMCP.configuration.vibed_ignore_version
|
61
|
+
capabilities_payload[:protocolVersion] = PROTOCOL_VERSION
|
62
|
+
else
|
63
|
+
capabilities_payload[:protocolVersion] = client_protocol_version
|
64
|
+
end
|
59
65
|
|
60
66
|
send_jsonrpc_response(request_id, result: capabilities_payload)
|
61
67
|
{ type: :responses, id: request_id, payload: { jsonrpc: "2.0", id: request_id, result: capabilities_payload } }
|
@@ -92,7 +92,7 @@ module ActionMCP
|
|
92
92
|
if content.is_a?(Content::Base) || (content.respond_to?(:to_h) && !content.is_a?(Hash))
|
93
93
|
@messages << { role: role, content: content.to_h }
|
94
94
|
else
|
95
|
-
content = Content::Text.new(content).to_h if content.is_a?(String)
|
95
|
+
content = Content::Text.new(content, annotations: nil).to_h if content.is_a?(String)
|
96
96
|
@messages << { role: role, content: content }
|
97
97
|
end
|
98
98
|
end
|
data/lib/action_mcp/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionmcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.50.
|
4
|
+
version: 0.50.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abdelkader Boudih
|
8
|
+
autorequire:
|
8
9
|
bindir: exe
|
9
10
|
cert_chain: []
|
10
|
-
date:
|
11
|
+
date: 2025-05-14 00:00:00.000000000 Z
|
11
12
|
dependencies:
|
12
13
|
- !ruby/object:Gem::Dependency
|
13
14
|
name: activerecord
|
@@ -143,6 +144,7 @@ files:
|
|
143
144
|
- lib/action_mcp/console_detector.rb
|
144
145
|
- lib/action_mcp/content.rb
|
145
146
|
- lib/action_mcp/content/audio.rb
|
147
|
+
- lib/action_mcp/content/base.rb
|
146
148
|
- lib/action_mcp/content/image.rb
|
147
149
|
- lib/action_mcp/content/resource.rb
|
148
150
|
- lib/action_mcp/content/text.rb
|
@@ -213,6 +215,7 @@ metadata:
|
|
213
215
|
source_code_uri: https://github.com/seuros/action_mcp
|
214
216
|
changelog_uri: https://github.com/seuros/action_mcp/blob/master/CHANGELOG.md
|
215
217
|
rubygems_mfa_required: 'true'
|
218
|
+
post_install_message:
|
216
219
|
rdoc_options: []
|
217
220
|
require_paths:
|
218
221
|
- lib
|
@@ -227,7 +230,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
227
230
|
- !ruby/object:Gem::Version
|
228
231
|
version: '0'
|
229
232
|
requirements: []
|
230
|
-
rubygems_version: 3.
|
233
|
+
rubygems_version: 3.5.22
|
234
|
+
signing_key:
|
231
235
|
specification_version: 4
|
232
236
|
summary: Provides essential tooling for building Model Context Protocol (MCP) capable
|
233
237
|
servers
|