ruby_llm_swarm-mcp 0.8.0 → 0.8.1
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 +3 -44
- data/lib/generators/ruby_llm/mcp/install/templates/initializer.rb +4 -21
- data/lib/generators/ruby_llm/mcp/install/templates/mcps.yml +0 -20
- data/lib/ruby_llm/mcp/auth/browser/http_server.rb +3 -0
- data/lib/ruby_llm/mcp/auth/browser/opener.rb +2 -0
- data/lib/ruby_llm/mcp/auth/browser/pages.rb +32 -100
- data/lib/ruby_llm/mcp/auth/browser_oauth_provider.rb +6 -32
- data/lib/ruby_llm/mcp/auth/http_response_handler.rb +2 -0
- data/lib/ruby_llm/mcp/auth/memory_storage.rb +0 -18
- data/lib/ruby_llm/mcp/auth/oauth_provider.rb +3 -82
- data/lib/ruby_llm/mcp/auth/session_manager.rb +2 -0
- data/lib/ruby_llm/mcp/auth/url_builder.rb +2 -0
- data/lib/ruby_llm/mcp/client.rb +32 -119
- data/lib/ruby_llm/mcp/configuration.rb +6 -74
- data/lib/ruby_llm/mcp/coordinator.rb +304 -0
- data/lib/ruby_llm/mcp/elicitation.rb +6 -8
- data/lib/ruby_llm/mcp/errors.rb +0 -15
- data/lib/ruby_llm/mcp/notification_handler.rb +5 -21
- data/lib/ruby_llm/mcp/notifications/cancelled.rb +32 -0
- data/lib/ruby_llm/mcp/notifications/initialize.rb +24 -0
- data/lib/ruby_llm/mcp/notifications/roots_list_change.rb +26 -0
- data/lib/ruby_llm/mcp/prompt.rb +7 -7
- data/lib/ruby_llm/mcp/protocol.rb +34 -0
- data/lib/ruby_llm/mcp/railtie.rb +6 -8
- data/lib/ruby_llm/mcp/requests/completion_prompt.rb +50 -0
- data/lib/ruby_llm/mcp/requests/completion_resource.rb +50 -0
- data/lib/ruby_llm/mcp/requests/initialization.rb +34 -0
- data/lib/ruby_llm/mcp/requests/logging_set_level.rb +28 -0
- data/lib/ruby_llm/mcp/requests/ping.rb +24 -0
- data/lib/ruby_llm/mcp/requests/prompt_call.rb +32 -0
- data/lib/ruby_llm/mcp/requests/prompt_list.rb +31 -0
- data/lib/ruby_llm/mcp/requests/resource_list.rb +31 -0
- data/lib/ruby_llm/mcp/requests/resource_read.rb +30 -0
- data/lib/ruby_llm/mcp/requests/resource_template_list.rb +31 -0
- data/lib/ruby_llm/mcp/requests/resources_subscribe.rb +30 -0
- data/lib/ruby_llm/mcp/requests/shared/meta.rb +32 -0
- data/lib/ruby_llm/mcp/requests/shared/pagination.rb +17 -0
- data/lib/ruby_llm/mcp/requests/tool_call.rb +35 -0
- data/lib/ruby_llm/mcp/requests/tool_list.rb +31 -0
- data/lib/ruby_llm/mcp/resource.rb +8 -6
- data/lib/ruby_llm/mcp/resource_template.rb +7 -7
- data/lib/ruby_llm/mcp/response_handler.rb +67 -0
- data/lib/ruby_llm/mcp/responses/elicitation.rb +33 -0
- data/lib/ruby_llm/mcp/responses/error.rb +33 -0
- data/lib/ruby_llm/mcp/responses/ping.rb +28 -0
- data/lib/ruby_llm/mcp/responses/roots_list.rb +31 -0
- data/lib/ruby_llm/mcp/responses/sampling_create_message.rb +50 -0
- data/lib/ruby_llm/mcp/result.rb +4 -8
- data/lib/ruby_llm/mcp/roots.rb +4 -4
- data/lib/ruby_llm/mcp/sample.rb +2 -6
- data/lib/ruby_llm/mcp/tool.rb +9 -9
- data/lib/ruby_llm/mcp/transport.rb +151 -0
- data/lib/ruby_llm/mcp/transports/sse.rb +435 -0
- data/lib/ruby_llm/mcp/transports/stdio.rb +231 -0
- data/lib/ruby_llm/mcp/transports/streamable_http.rb +725 -0
- data/lib/ruby_llm/mcp/transports/support/http_client.rb +28 -0
- data/lib/ruby_llm/mcp/transports/support/rate_limit.rb +47 -0
- data/lib/ruby_llm/mcp/transports/support/timeout.rb +34 -0
- data/lib/ruby_llm/mcp/version.rb +1 -1
- data/lib/ruby_llm/mcp.rb +7 -30
- metadata +38 -33
- data/lib/ruby_llm/mcp/adapters/base_adapter.rb +0 -179
- data/lib/ruby_llm/mcp/adapters/mcp_sdk_adapter.rb +0 -292
- data/lib/ruby_llm/mcp/adapters/mcp_transports/coordinator_stub.rb +0 -33
- data/lib/ruby_llm/mcp/adapters/mcp_transports/sse.rb +0 -52
- data/lib/ruby_llm/mcp/adapters/mcp_transports/stdio.rb +0 -52
- data/lib/ruby_llm/mcp/adapters/mcp_transports/streamable_http.rb +0 -86
- data/lib/ruby_llm/mcp/adapters/ruby_llm_adapter.rb +0 -92
- data/lib/ruby_llm/mcp/auth/transport_oauth_helper.rb +0 -107
- data/lib/ruby_llm/mcp/native/cancellable_operation.rb +0 -57
- data/lib/ruby_llm/mcp/native/client.rb +0 -387
- data/lib/ruby_llm/mcp/native/json_rpc.rb +0 -170
- data/lib/ruby_llm/mcp/native/messages/helpers.rb +0 -39
- data/lib/ruby_llm/mcp/native/messages/notifications.rb +0 -42
- data/lib/ruby_llm/mcp/native/messages/requests.rb +0 -206
- data/lib/ruby_llm/mcp/native/messages/responses.rb +0 -106
- data/lib/ruby_llm/mcp/native/messages.rb +0 -36
- data/lib/ruby_llm/mcp/native/notification.rb +0 -16
- data/lib/ruby_llm/mcp/native/protocol.rb +0 -36
- data/lib/ruby_llm/mcp/native/response_handler.rb +0 -110
- data/lib/ruby_llm/mcp/native/transport.rb +0 -88
- data/lib/ruby_llm/mcp/native/transports/sse.rb +0 -607
- data/lib/ruby_llm/mcp/native/transports/stdio.rb +0 -356
- data/lib/ruby_llm/mcp/native/transports/streamable_http.rb +0 -926
- data/lib/ruby_llm/mcp/native/transports/support/http_client.rb +0 -28
- data/lib/ruby_llm/mcp/native/transports/support/rate_limit.rb +0 -49
- data/lib/ruby_llm/mcp/native/transports/support/timeout.rb +0 -36
- data/lib/ruby_llm/mcp/native.rb +0 -12
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module MCP
|
|
5
|
-
module Auth
|
|
6
|
-
# Helper module for preparing OAuth providers for transports
|
|
7
|
-
# This keeps OAuth logic out of the Native module while making it reusable
|
|
8
|
-
module TransportOauthHelper
|
|
9
|
-
module_function
|
|
10
|
-
|
|
11
|
-
# Check if OAuth configuration is present
|
|
12
|
-
# @param config [Hash] transport configuration hash
|
|
13
|
-
# @return [Boolean] true if OAuth config is present
|
|
14
|
-
def oauth_config_present?(config)
|
|
15
|
-
oauth_config = config[:oauth] || config["oauth"]
|
|
16
|
-
return false if oauth_config.nil?
|
|
17
|
-
|
|
18
|
-
# If it's an OAuth provider instance, it's present
|
|
19
|
-
return true if oauth_config.respond_to?(:access_token)
|
|
20
|
-
|
|
21
|
-
# If it's a hash, check if it's not empty
|
|
22
|
-
!oauth_config.empty?
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Create OAuth provider from configuration
|
|
26
|
-
# Accepts either a provider instance or a configuration hash
|
|
27
|
-
# @param config [Hash] transport configuration hash (will be modified)
|
|
28
|
-
# @return [OAuthProvider, BrowserOAuthProvider, nil] OAuth provider or nil
|
|
29
|
-
def create_oauth_provider(config)
|
|
30
|
-
oauth_config = config.delete(:oauth) || config.delete("oauth")
|
|
31
|
-
return nil unless oauth_config
|
|
32
|
-
|
|
33
|
-
# If provider key exists with an instance, use it
|
|
34
|
-
if oauth_config.is_a?(Hash) && (oauth_config[:provider] || oauth_config["provider"])
|
|
35
|
-
return oauth_config[:provider] || oauth_config["provider"]
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# If oauth_config itself is a provider instance, use it directly
|
|
39
|
-
if oauth_config.respond_to?(:access_token) && oauth_config.respond_to?(:start_authorization_flow)
|
|
40
|
-
return oauth_config
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Otherwise create new provider from config hash
|
|
44
|
-
server_url = determine_server_url(config)
|
|
45
|
-
return nil unless server_url
|
|
46
|
-
|
|
47
|
-
redirect_uri = oauth_config[:redirect_uri] || oauth_config["redirect_uri"] || "http://localhost:8080/callback"
|
|
48
|
-
scope = oauth_config[:scope] || oauth_config["scope"]
|
|
49
|
-
storage = oauth_config[:storage] || oauth_config["storage"]
|
|
50
|
-
grant_type = oauth_config[:grant_type] || oauth_config["grant_type"] || :authorization_code
|
|
51
|
-
|
|
52
|
-
RubyLLM::MCP::Auth::OAuthProvider.new(
|
|
53
|
-
server_url: server_url,
|
|
54
|
-
redirect_uri: redirect_uri,
|
|
55
|
-
scope: scope,
|
|
56
|
-
logger: MCP.logger,
|
|
57
|
-
storage: storage,
|
|
58
|
-
grant_type: grant_type
|
|
59
|
-
)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Determine server URL from transport config
|
|
63
|
-
# @param config [Hash] transport configuration hash
|
|
64
|
-
# @return [String, nil] server URL or nil
|
|
65
|
-
def determine_server_url(config)
|
|
66
|
-
config[:url] || config["url"]
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Prepare HTTP transport configuration with OAuth provider
|
|
70
|
-
# @param config [Hash] transport configuration hash (will be modified)
|
|
71
|
-
# @param oauth_provider [OAuthProvider, nil] OAuth provider instance
|
|
72
|
-
# @return [Hash] prepared configuration
|
|
73
|
-
def prepare_http_transport_config(config, oauth_provider)
|
|
74
|
-
options = {
|
|
75
|
-
version: config.delete(:version) || config.delete("version"),
|
|
76
|
-
headers: config.delete(:headers) || config.delete("headers"),
|
|
77
|
-
oauth_provider: oauth_provider,
|
|
78
|
-
reconnection: config.delete(:reconnection) || config.delete("reconnection"),
|
|
79
|
-
reconnection_options: config.delete(:reconnection_options) || config.delete("reconnection_options"),
|
|
80
|
-
rate_limit: config.delete(:rate_limit) || config.delete("rate_limit"),
|
|
81
|
-
session_id: config.delete(:session_id) || config.delete("session_id")
|
|
82
|
-
}.compact
|
|
83
|
-
|
|
84
|
-
config[:options] = options
|
|
85
|
-
config
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Prepare stdio transport configuration
|
|
89
|
-
# @param config [Hash] transport configuration hash (will be modified)
|
|
90
|
-
# @return [Hash] prepared configuration
|
|
91
|
-
def prepare_stdio_transport_config(config)
|
|
92
|
-
# Remove OAuth config from stdio transport (not supported)
|
|
93
|
-
config.delete(:oauth)
|
|
94
|
-
config.delete("oauth")
|
|
95
|
-
|
|
96
|
-
options = {
|
|
97
|
-
args: config.delete(:args) || config.delete("args"),
|
|
98
|
-
env: config.delete(:env) || config.delete("env")
|
|
99
|
-
}.compact
|
|
100
|
-
|
|
101
|
-
config[:options] = options unless options.empty?
|
|
102
|
-
config
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module MCP
|
|
5
|
-
module Native
|
|
6
|
-
# Wraps server-initiated requests to support cancellation
|
|
7
|
-
# Executes the request in a separate thread that can be terminated on cancellation
|
|
8
|
-
class CancellableOperation
|
|
9
|
-
attr_reader :request_id, :thread
|
|
10
|
-
|
|
11
|
-
def initialize(request_id)
|
|
12
|
-
@request_id = request_id
|
|
13
|
-
@cancelled = false
|
|
14
|
-
@mutex = Mutex.new
|
|
15
|
-
@thread = nil
|
|
16
|
-
@result = nil
|
|
17
|
-
@error = nil
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def cancelled?
|
|
21
|
-
@mutex.synchronize { @cancelled }
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def cancel
|
|
25
|
-
@mutex.synchronize { @cancelled = true }
|
|
26
|
-
if @thread&.alive?
|
|
27
|
-
@thread.raise(Errors::RequestCancelled.new(
|
|
28
|
-
message: "Request #{@request_id} was cancelled",
|
|
29
|
-
request_id: @request_id
|
|
30
|
-
))
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Execute a block in a separate thread
|
|
35
|
-
# This allows the thread to be terminated if cancellation is requested
|
|
36
|
-
# Returns the result of the block or re-raises any error that occurred
|
|
37
|
-
def execute(&)
|
|
38
|
-
@thread = Thread.new do
|
|
39
|
-
Thread.current.abort_on_exception = false
|
|
40
|
-
begin
|
|
41
|
-
@result = yield
|
|
42
|
-
rescue Errors::RequestCancelled, StandardError => e
|
|
43
|
-
@error = e
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
@thread.join
|
|
48
|
-
raise @error if @error && !@error.is_a?(Errors::RequestCancelled)
|
|
49
|
-
|
|
50
|
-
@result
|
|
51
|
-
ensure
|
|
52
|
-
@thread = nil
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module MCP
|
|
5
|
-
module Native
|
|
6
|
-
# Native MCP protocol client implementation
|
|
7
|
-
# This is the core protocol implementation that handles all MCP operations
|
|
8
|
-
# It's self-contained and could potentially be extracted as a separate gem
|
|
9
|
-
class Client
|
|
10
|
-
attr_reader :name, :transport_type, :config, :capabilities, :protocol_version, :elicitation_callback,
|
|
11
|
-
:sampling_callback
|
|
12
|
-
|
|
13
|
-
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
14
|
-
name:,
|
|
15
|
-
transport_type:,
|
|
16
|
-
transport_config: {},
|
|
17
|
-
human_in_the_loop_callback: nil,
|
|
18
|
-
roots_callback: nil,
|
|
19
|
-
logging_enabled: false,
|
|
20
|
-
logging_level: nil,
|
|
21
|
-
elicitation_enabled: false,
|
|
22
|
-
elicitation_callback: nil,
|
|
23
|
-
progress_tracking_enabled: false,
|
|
24
|
-
sampling_callback: nil,
|
|
25
|
-
notification_callback: nil,
|
|
26
|
-
protocol_version: nil,
|
|
27
|
-
request_timeout: nil
|
|
28
|
-
)
|
|
29
|
-
@name = name
|
|
30
|
-
@transport_type = transport_type
|
|
31
|
-
@config = transport_config.merge(request_timeout: request_timeout || MCP.config.request_timeout)
|
|
32
|
-
@protocol_version = protocol_version || MCP.config.protocol_version || Native::Protocol.default_negotiated_version
|
|
33
|
-
|
|
34
|
-
# Callbacks
|
|
35
|
-
@human_in_the_loop_callback = human_in_the_loop_callback
|
|
36
|
-
@roots_callback = roots_callback
|
|
37
|
-
@logging_enabled = logging_enabled
|
|
38
|
-
@logging_level = logging_level
|
|
39
|
-
@elicitation_enabled = elicitation_enabled
|
|
40
|
-
@elicitation_callback = elicitation_callback
|
|
41
|
-
@progress_tracking_enabled = progress_tracking_enabled
|
|
42
|
-
@sampling_callback = sampling_callback
|
|
43
|
-
@notification_callback = notification_callback
|
|
44
|
-
|
|
45
|
-
@transport = nil
|
|
46
|
-
@capabilities = nil
|
|
47
|
-
|
|
48
|
-
# Track in-flight server-initiated requests for cancellation
|
|
49
|
-
@in_flight_requests = {}
|
|
50
|
-
@in_flight_mutex = Mutex.new
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def request(body, **options)
|
|
54
|
-
transport.request(body, **options)
|
|
55
|
-
rescue RubyLLM::MCP::Errors::TimeoutError => e
|
|
56
|
-
if transport&.alive? && !e.request_id.nil?
|
|
57
|
-
cancelled_notification(reason: "Request timed out", request_id: e.request_id)
|
|
58
|
-
end
|
|
59
|
-
raise e
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def process_result(result)
|
|
63
|
-
if result.notification?
|
|
64
|
-
process_notification(result)
|
|
65
|
-
return nil
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
if result.request?
|
|
69
|
-
process_request(result) if alive?
|
|
70
|
-
return nil
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
if result.response?
|
|
74
|
-
return result
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
nil
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def start
|
|
81
|
-
return unless capabilities.nil?
|
|
82
|
-
|
|
83
|
-
transport.start
|
|
84
|
-
|
|
85
|
-
initialize_response = initialize_request
|
|
86
|
-
initialize_response.raise_error! if initialize_response.error?
|
|
87
|
-
|
|
88
|
-
# Extract and store the negotiated protocol version
|
|
89
|
-
negotiated_version = initialize_response.value["protocolVersion"]
|
|
90
|
-
|
|
91
|
-
if negotiated_version && !Native::Protocol.supported_version?(negotiated_version)
|
|
92
|
-
raise Errors::UnsupportedProtocolVersion.new(
|
|
93
|
-
message: <<~MESSAGE
|
|
94
|
-
Unsupported protocol version, and could not negotiate a supported version: #{negotiated_version}.
|
|
95
|
-
Supported versions: #{Native::Protocol.supported_versions.join(', ')}
|
|
96
|
-
MESSAGE
|
|
97
|
-
)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
@protocol_version = negotiated_version if negotiated_version
|
|
101
|
-
|
|
102
|
-
# Set the protocol version on the transport for subsequent requests
|
|
103
|
-
if @transport.respond_to?(:set_protocol_version)
|
|
104
|
-
@transport.set_protocol_version(@protocol_version)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
@capabilities = RubyLLM::MCP::ServerCapabilities.new(initialize_response.value["capabilities"])
|
|
108
|
-
initialize_notification
|
|
109
|
-
|
|
110
|
-
if @logging_enabled && @logging_level
|
|
111
|
-
set_logging(level: @logging_level)
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def stop
|
|
116
|
-
@transport&.close
|
|
117
|
-
@capabilities = nil
|
|
118
|
-
@transport = nil
|
|
119
|
-
@protocol_version = Native::Protocol.default_negotiated_version
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def restart!
|
|
123
|
-
@initialize_response = nil
|
|
124
|
-
stop
|
|
125
|
-
start
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def alive?
|
|
129
|
-
!!@transport&.alive?
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def ping
|
|
133
|
-
body = Native::Messages::Requests.ping(tracking_progress: tracking_progress?)
|
|
134
|
-
if alive?
|
|
135
|
-
result = request(body)
|
|
136
|
-
else
|
|
137
|
-
transport.start
|
|
138
|
-
|
|
139
|
-
result = request(body)
|
|
140
|
-
@transport = nil
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
result.value == {}
|
|
144
|
-
rescue RubyLLM::MCP::Errors::TimeoutError, RubyLLM::MCP::Errors::TransportError
|
|
145
|
-
false
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def process_notification(result)
|
|
149
|
-
notification = result.notification
|
|
150
|
-
@notification_callback&.call(notification)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def process_request(result)
|
|
154
|
-
Native::ResponseHandler.new(self).execute(result)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def initialize_request
|
|
158
|
-
body = Native::Messages::Requests.initialize(
|
|
159
|
-
protocol_version: protocol_version,
|
|
160
|
-
capabilities: client_capabilities
|
|
161
|
-
)
|
|
162
|
-
request(body)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def tool_list(cursor: nil)
|
|
166
|
-
body = Native::Messages::Requests.tool_list(cursor: cursor, tracking_progress: tracking_progress?)
|
|
167
|
-
result = request(body)
|
|
168
|
-
result.raise_error! if result.error?
|
|
169
|
-
|
|
170
|
-
if result.next_cursor?
|
|
171
|
-
result.value["tools"] + tool_list(cursor: result.next_cursor)
|
|
172
|
-
else
|
|
173
|
-
result.value["tools"]
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def execute_tool(name:, parameters:)
|
|
178
|
-
if @human_in_the_loop_callback && !@human_in_the_loop_callback.call(name, parameters)
|
|
179
|
-
result = Result.new(
|
|
180
|
-
{
|
|
181
|
-
"result" => {
|
|
182
|
-
"isError" => true,
|
|
183
|
-
"content" => [{ "type" => "text", "text" => "Tool call was cancelled by the client" }]
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
)
|
|
187
|
-
return result
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
body = Native::Messages::Requests.tool_call(name: name, parameters: parameters,
|
|
191
|
-
tracking_progress: tracking_progress?)
|
|
192
|
-
request(body)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def resource_list(cursor: nil)
|
|
196
|
-
body = Native::Messages::Requests.resource_list(cursor: cursor, tracking_progress: tracking_progress?)
|
|
197
|
-
result = request(body)
|
|
198
|
-
result.raise_error! if result.error?
|
|
199
|
-
|
|
200
|
-
if result.next_cursor?
|
|
201
|
-
result.value["resources"] + resource_list(cursor: result.next_cursor)
|
|
202
|
-
else
|
|
203
|
-
result.value["resources"]
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def resource_read(uri:)
|
|
208
|
-
body = Native::Messages::Requests.resource_read(uri: uri, tracking_progress: tracking_progress?)
|
|
209
|
-
request(body)
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def resource_template_list(cursor: nil)
|
|
213
|
-
body = Native::Messages::Requests.resource_template_list(cursor: cursor,
|
|
214
|
-
tracking_progress: tracking_progress?)
|
|
215
|
-
result = request(body)
|
|
216
|
-
result.raise_error! if result.error?
|
|
217
|
-
|
|
218
|
-
if result.next_cursor?
|
|
219
|
-
result.value["resourceTemplates"] + resource_template_list(cursor: result.next_cursor)
|
|
220
|
-
else
|
|
221
|
-
result.value["resourceTemplates"]
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def resources_subscribe(uri:)
|
|
226
|
-
body = Native::Messages::Requests.resources_subscribe(uri: uri, tracking_progress: tracking_progress?)
|
|
227
|
-
request(body, wait_for_response: false)
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def prompt_list(cursor: nil)
|
|
231
|
-
body = Native::Messages::Requests.prompt_list(cursor: cursor, tracking_progress: tracking_progress?)
|
|
232
|
-
result = request(body)
|
|
233
|
-
result.raise_error! if result.error?
|
|
234
|
-
|
|
235
|
-
if result.next_cursor?
|
|
236
|
-
result.value["prompts"] + prompt_list(cursor: result.next_cursor)
|
|
237
|
-
else
|
|
238
|
-
result.value["prompts"]
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
def execute_prompt(name:, arguments:)
|
|
243
|
-
body = Native::Messages::Requests.prompt_call(name: name, arguments: arguments,
|
|
244
|
-
tracking_progress: tracking_progress?)
|
|
245
|
-
request(body)
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def completion_resource(uri:, argument:, value:, context: nil)
|
|
249
|
-
body = Native::Messages::Requests.completion_resource(uri: uri, argument: argument, value: value,
|
|
250
|
-
context: context, tracking_progress: tracking_progress?)
|
|
251
|
-
request(body)
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def completion_prompt(name:, argument:, value:, context: nil)
|
|
255
|
-
body = Native::Messages::Requests.completion_prompt(name: name, argument: argument, value: value,
|
|
256
|
-
context: context, tracking_progress: tracking_progress?)
|
|
257
|
-
request(body)
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
def set_logging(level:)
|
|
261
|
-
body = Native::Messages::Requests.logging_set_level(level: level, tracking_progress: tracking_progress?)
|
|
262
|
-
request(body)
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def set_progress_tracking(enabled:)
|
|
266
|
-
@progress_tracking_enabled = enabled
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
## Notifications
|
|
270
|
-
#
|
|
271
|
-
def initialize_notification
|
|
272
|
-
body = Native::Messages::Notifications.initialized
|
|
273
|
-
request(body, wait_for_response: false)
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def cancelled_notification(reason:, request_id:)
|
|
277
|
-
body = Native::Messages::Notifications.cancelled(request_id: request_id, reason: reason)
|
|
278
|
-
request(body, wait_for_response: false)
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
def roots_list_change_notification
|
|
282
|
-
body = Native::Messages::Notifications.roots_list_changed
|
|
283
|
-
request(body, wait_for_response: false)
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
## Responses
|
|
287
|
-
#
|
|
288
|
-
def ping_response(id:)
|
|
289
|
-
body = Native::Messages::Responses.ping(id: id)
|
|
290
|
-
request(body, wait_for_response: false)
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def roots_list_response(id:)
|
|
294
|
-
body = Native::Messages::Responses.roots_list(id: id, roots_paths: roots_paths)
|
|
295
|
-
request(body, wait_for_response: false)
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
def sampling_create_message_response(id:, model:, message:, **_options)
|
|
299
|
-
body = Native::Messages::Responses.sampling_create_message(id: id, model: model, message: message)
|
|
300
|
-
request(body, wait_for_response: false)
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
def error_response(id:, message:, code: Native::JsonRpc::ErrorCodes::SERVER_ERROR, data: nil)
|
|
304
|
-
body = Native::Messages::Responses.error(id: id, message: message, code: code, data: data)
|
|
305
|
-
request(body, wait_for_response: false)
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
def elicitation_response(id:, elicitation:)
|
|
309
|
-
body = Native::Messages::Responses.elicitation(id: id, action: elicitation[:action],
|
|
310
|
-
content: elicitation[:content])
|
|
311
|
-
request(body, wait_for_response: false)
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
def client_capabilities
|
|
315
|
-
capabilities_hash = {}
|
|
316
|
-
|
|
317
|
-
if @roots_callback&.call&.any?
|
|
318
|
-
capabilities_hash[:roots] = {
|
|
319
|
-
listChanged: true
|
|
320
|
-
}
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
if MCP.config.sampling.enabled?
|
|
324
|
-
capabilities_hash[:sampling] = {}
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
if @elicitation_enabled
|
|
328
|
-
capabilities_hash[:elicitation] = {}
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
capabilities_hash
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
def roots_paths
|
|
335
|
-
@roots_callback&.call || []
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
def tracking_progress?
|
|
339
|
-
@progress_tracking_enabled
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
def sampling_callback_enabled?
|
|
343
|
-
!@sampling_callback.nil?
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
def transport
|
|
347
|
-
@transport ||= Native::Transport.new(@transport_type, self, config: @config)
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
# Register a server-initiated request that can be cancelled
|
|
351
|
-
# @param request_id [String] The ID of the request
|
|
352
|
-
# @param cancellable_operation [CancellableOperation, nil] The operation that can be cancelled
|
|
353
|
-
def register_in_flight_request(request_id, cancellable_operation = nil)
|
|
354
|
-
@in_flight_mutex.synchronize do
|
|
355
|
-
@in_flight_requests[request_id.to_s] = cancellable_operation
|
|
356
|
-
end
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
# Unregister a completed or cancelled request
|
|
360
|
-
# @param request_id [String] The ID of the request
|
|
361
|
-
def unregister_in_flight_request(request_id)
|
|
362
|
-
@in_flight_mutex.synchronize do
|
|
363
|
-
@in_flight_requests.delete(request_id.to_s)
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
# Cancel an in-flight server-initiated request
|
|
368
|
-
# @param request_id [String] The ID of the request to cancel
|
|
369
|
-
# @return [Boolean] true if the request was found and cancelled, false otherwise
|
|
370
|
-
def cancel_in_flight_request(request_id) # rubocop:disable Naming/PredicateMethod
|
|
371
|
-
operation = nil
|
|
372
|
-
@in_flight_mutex.synchronize do
|
|
373
|
-
operation = @in_flight_requests[request_id.to_s]
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
if operation.respond_to?(:cancel)
|
|
377
|
-
operation.cancel
|
|
378
|
-
true
|
|
379
|
-
else
|
|
380
|
-
RubyLLM::MCP.logger.warn("Request #{request_id} cannot be cancelled or was already completed")
|
|
381
|
-
false
|
|
382
|
-
end
|
|
383
|
-
end
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
end
|