actionmcp 0.102.0 → 0.103.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.
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module Client
5
- # PromptBook
6
- #
7
- # A collection that manages and provides access to prompt templates from the MCP server.
8
- # The class stores prompt definitions along with their arguments and provides methods
9
- # for retrieving, filtering, and accessing prompts. It supports lazy loading of prompts
10
- # when initialized with a client.
11
- #
12
- # Example usage:
13
- # # Eager loading
14
- # prompts_data = client.list_prompts # Returns array of prompt definitions
15
- # book = PromptBook.new(prompts_data)
16
- #
17
- # # Lazy loading
18
- # book = PromptBook.new([], client)
19
- # prompts = book.all # Prompts are loaded here
20
- #
21
- # # Access a specific prompt by name
22
- # summary_prompt = book.find("summarize_text")
23
- #
24
- # # Get all prompts matching a criteria
25
- # text_prompts = book.filter { |p| p.name.include?("text") }
26
- #
27
- class PromptBook < Collection
28
- # Initialize a new PromptBook with prompt definitions
29
- #
30
- # @param prompts [Array<Hash>] Array of prompt definition hashes, each containing
31
- # name, description, and arguments keys
32
- # @param client [Object, nil] Optional client for lazy loading of prompts
33
- def initialize(prompts, client)
34
- super(prompts, client)
35
- self.prompts = @collection_data
36
- @load_method = :list_prompts
37
- end
38
-
39
- # Find a prompt by name
40
- #
41
- # @param name [String] Name of the prompt to find
42
- # @return [Prompt, nil] The prompt with the given name, or nil if not found
43
- def find(name)
44
- all.find { |prompt| prompt.name == name }
45
- end
46
-
47
- # Get a list of all prompt names
48
- #
49
- # @return [Array<String>] Names of all prompts in the collection
50
- def names
51
- all.map(&:name)
52
- end
53
-
54
- # Check if the collection contains a prompt with the given name
55
- #
56
- # @param name [String] The prompt name to check for
57
- # @return [Boolean] true if a prompt with the name exists
58
- def contains?(name)
59
- all.any? { |prompt| prompt.name == name }
60
- end
61
-
62
- # Convert raw prompt data into Prompt objects
63
- #
64
- # @param prompts [Array<Hash>] Array of prompt definition hashes
65
- def prompts=(prompts)
66
- @collection_data = prompts.map { |data| Prompt.new(data) }
67
- end
68
-
69
- # Internal Prompt class to represent individual prompts
70
- class Prompt
71
- attr_reader :name, :description, :arguments
72
-
73
- # Initialize a new Prompt instance
74
- #
75
- # @param data [Hash] Prompt definition hash containing name, description, and arguments
76
- def initialize(data)
77
- @name = data["name"]
78
- @description = data["description"]
79
- @arguments = data["arguments"] || []
80
- end
81
-
82
- # Get all required arguments for this prompt
83
- #
84
- # @return [Array<Hash>] Array of argument hashes that are required
85
- def required_arguments
86
- @arguments.select { |arg| arg["required"] }
87
- end
88
-
89
- # Get all optional arguments for this prompt
90
- #
91
- # @return [Array<Hash>] Array of argument hashes that are optional
92
- def optional_arguments
93
- @arguments.reject { |arg| arg["required"] }
94
- end
95
-
96
- # Check if the prompt has a specific argument
97
- #
98
- # @param name [String] Name of the argument to check for
99
- # @return [Boolean] true if the argument exists
100
- def has_argument?(name)
101
- @arguments.any? { |arg| arg["name"] == name }
102
- end
103
-
104
- # Generate a hash representation of the prompt
105
- #
106
- # @return [Hash] Hash containing prompt details
107
- def to_h
108
- {
109
- "name" => @name,
110
- "description" => @description,
111
- "arguments" => @arguments
112
- }
113
- end
114
- end
115
- end
116
- end
117
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module Client
5
- module Prompts
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
10
- # @return [String] Request ID for tracking the request
11
- def list_prompts(params = {})
12
- request_id = SecureRandom.uuid_v7
13
-
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)
22
-
23
- # Return request ID for tracking the request
24
- request_id
25
- end
26
-
27
- # Get a specific prompt with arguments
28
- # @param name [String] Name of the prompt to get
29
- # @param arguments [Hash] Arguments to pass to the prompt
30
- # @return [String] Request ID for tracking the request
31
- def get_prompt(name, arguments = {})
32
- request_id = SecureRandom.uuid_v7
33
-
34
- # Send request
35
- send_jsonrpc_request("prompts/get",
36
- params: {
37
- name: name,
38
- arguments: arguments
39
- },
40
- id: request_id)
41
-
42
- # Return request ID for tracking the request
43
- request_id
44
- end
45
- end
46
- end
47
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module Client
5
- module RequestTimeouts
6
- # Default timeout in seconds
7
- DEFAULT_TIMEOUT = 1.0
8
-
9
- # Load resources with timeout support - blocking until response or timeout
10
- # @param method_name [Symbol] The method to call for loading (e.g., :list_resources)
11
- # @param force [Boolean] Whether to force reload even if already loaded
12
- # @param timeout [Float] Timeout in seconds
13
- # @return [Boolean] Success status
14
- def load_with_timeout(method_name, force: false, timeout: DEFAULT_TIMEOUT)
15
- return true if @loaded && !force
16
-
17
- # Make the request and store its ID
18
- request_id = client.send(method_name)
19
-
20
- start_time = Time.now
21
-
22
- # Wait until either:
23
- # 1. The collection is loaded (@loaded becomes true from JsonRpcHandler)
24
- # 2. The timeout is reached
25
- sleep(0.1) while !@loaded && (Time.now - start_time) < timeout
26
-
27
- # If we timed out
28
- unless @loaded
29
- request = client.session.messages.requests.find_by(jsonrpc_id: request_id)
30
-
31
- if request && !request.request_acknowledged?
32
- # Send cancel notification
33
- client.send_jsonrpc_notification("notifications/cancelled", {
34
- requestId: request_id,
35
- reason: "Request timed out after #{timeout} seconds"
36
- })
37
-
38
- # Mark as cancelled in the database
39
- request.update(request_cancelled: true)
40
-
41
- log_error("Request #{method_name} timed out after #{timeout} seconds")
42
- end
43
-
44
- # Mark as loaded even though we timed out
45
- @loaded = true
46
- return false
47
- end
48
-
49
- # Collection was successfully loaded
50
- true
51
- end
52
-
53
- private
54
-
55
- def handle_timeout(request_id, method_name, timeout)
56
- # Find the request
57
- request = client.session.messages.requests.find_by(jsonrpc_id: request_id)
58
-
59
- return unless request && !request.request_acknowledged?
60
-
61
- # Send cancel notification
62
- client.send_jsonrpc_notification("notifications/cancelled", {
63
- requestId: request_id,
64
- reason: "Request timed out after #{timeout} seconds"
65
- })
66
-
67
- # Mark as cancelled in the database
68
- request.update(request_cancelled: true)
69
-
70
- log_error("Request #{method_name} timed out after #{timeout} seconds")
71
- end
72
- end
73
- end
74
- end
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module Client
5
- module Resources
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
10
- # @return [String] Request ID for tracking the request
11
- def list_resources(params = {})
12
- request_id = SecureRandom.uuid_v7
13
-
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)
22
-
23
- # Return request ID for tracking the request
24
- request_id
25
- end
26
-
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
31
- # @return [String] Request ID for tracking the request
32
- def list_resource_templates(params = {})
33
- request_id = SecureRandom.uuid_v7
34
-
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)
43
-
44
- # Return request ID for tracking the request
45
- request_id
46
- end
47
-
48
- # Read a specific resource
49
- # @param uri [String] URI of the resource to read
50
- # @return [String] Request ID for tracking the request
51
- def read_resource(uri)
52
- request_id = SecureRandom.uuid_v7
53
-
54
- # Send request
55
- send_jsonrpc_request("resources/read",
56
- params: { uri: uri },
57
- id: request_id)
58
-
59
- # Return request ID for tracking the request
60
- request_id
61
- end
62
-
63
- # Subscribe to updates for a specific resource
64
- # @param uri [String] URI of the resource to subscribe to
65
- # @param update_callback [Proc] Callback for resource updates
66
- # @return [String] Request ID for tracking the request
67
- def subscribe_resource(uri, update_callback)
68
- @resource_subscriptions ||= {}
69
- @resource_subscriptions[uri] = update_callback
70
-
71
- request_id = SecureRandom.uuid_v7
72
-
73
- # Send request
74
- send_jsonrpc_request("resources/subscribe",
75
- params: { uri: uri },
76
- id: request_id)
77
-
78
- # Return request ID for tracking the request
79
- request_id
80
- end
81
-
82
- # Unsubscribe from updates for a specific resource
83
- # @param uri [String] URI of the resource to unsubscribe from
84
- # @return [String] Request ID for tracking the request
85
- def unsubscribe_resource(uri)
86
- @resource_subscriptions&.delete(uri)
87
-
88
- request_id = SecureRandom.uuid_v7
89
-
90
- # Send request
91
- send_jsonrpc_request("resources/unsubscribe",
92
- params: { uri: uri },
93
- id: request_id)
94
-
95
- # Return request ID for tracking the request
96
- request_id
97
- end
98
- end
99
- end
100
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module Client
5
- module Roots
6
- # Notify the server that the roots list has changed
7
- def roots_list_changed_notification
8
- send_jsonrpc_notification("notifications/roots/list_changed")
9
- true
10
- end
11
- end
12
- end
13
- end
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module Client
5
- class Server
6
- attr_reader :name, :version, :server_info, :capabilities
7
-
8
- def initialize(data)
9
- # Store protocol version if needed for later use
10
- @protocol_version = data["protocolVersion"]
11
-
12
- # Extract server information
13
- @server_info = data["serverInfo"] || {}
14
- @name = server_info["name"]
15
- @version = server_info["version"]
16
-
17
- # Store capabilities for dynamic checking
18
- @capabilities = data["capabilities"] || {}
19
- end
20
-
21
- # Check if 'tools' capability is present
22
- def tools?
23
- @capabilities.key?("tools")
24
- end
25
-
26
- # Check if 'prompts' capability is present
27
- def prompts?
28
- @capabilities.key?("prompts")
29
- end
30
-
31
- # Check if tools have a dynamic state based on listChanged flag
32
- def dynamic_tools?
33
- tool_cap = @capabilities["tools"] || {}
34
- tool_cap["listChanged"] == true
35
- end
36
-
37
- # Check if logging capability exists
38
- def logging?
39
- @capabilities.key?("logging")
40
- end
41
-
42
- # Check if resources capability exists
43
- def resources?
44
- @capabilities.key?("resources")
45
- end
46
-
47
- # Check if resources have a dynamic state based on listChanged flag
48
- def dynamic_resources?
49
- resources_cap = @capabilities["resources"] || {}
50
- resources_cap["listChanged"] == true
51
- end
52
-
53
- def inspect
54
- "#<#{self.class.name} name: #{name}, version: #{version} with resources: #{resources?}, tools: #{tools?}, prompts: #{prompts?}, logging: #{logging?}>"
55
- end
56
-
57
- alias to_s inspect
58
- end
59
- end
60
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module Client
5
- # Abstract interface for session storage
6
- module SessionStore
7
- # Load session data by ID
8
- def load_session(session_id)
9
- raise NotImplementedError, "#{self.class} must implement #load_session"
10
- end
11
-
12
- # Save session data
13
- def save_session(session_id, session_data)
14
- raise NotImplementedError, "#{self.class} must implement #save_session"
15
- end
16
-
17
- # Delete session
18
- def delete_session(session_id)
19
- raise NotImplementedError, "#{self.class} must implement #delete_session"
20
- end
21
-
22
- # Check if session exists
23
- def session_exists?(session_id)
24
- raise NotImplementedError, "#{self.class} must implement #session_exists?"
25
- end
26
-
27
- # Update specific session attributes
28
- def update_session(session_id, attributes)
29
- session_data = load_session(session_id)
30
- return nil unless session_data
31
-
32
- session_data.merge!(attributes)
33
- save_session(session_id, session_data)
34
- # Return the reloaded session to get the actual saved values
35
- load_session(session_id)
36
- end
37
- end
38
- end
39
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionMCP
4
- module Client
5
- # Factory for creating session stores
6
- class SessionStoreFactory
7
- def self.create(type = nil, **_options)
8
- type ||= default_type
9
-
10
- case type.to_sym
11
- when :volatile, :memory
12
- VolatileSessionStore.new
13
- when :active_record, :persistent
14
- ActiveRecordSessionStore.new
15
- when :test
16
- TestSessionStore.new
17
- else
18
- raise ArgumentError, "Unknown session store type: #{type}"
19
- end
20
- end
21
-
22
- def self.default_type
23
- ActionMCP.configuration.client_session_store_type
24
- end
25
- end
26
- end
27
- end