ruby_llm-mcp 0.7.1 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +144 -162
- data/lib/generators/ruby_llm/mcp/{install_generator.rb → install/install_generator.rb} +4 -2
- data/lib/generators/ruby_llm/mcp/{templates → install/templates}/initializer.rb +21 -4
- data/lib/generators/ruby_llm/mcp/install/templates/mcps.yml +29 -0
- data/lib/generators/ruby_llm/mcp/oauth/install_generator.rb +354 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/concerns/mcp_token_storage.rb.tt +114 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/concerns/user_mcp_oauth_concern.rb.tt +90 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/controllers/mcp_connections_controller.rb.tt +239 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/jobs/cleanup_expired_oauth_states_job.rb.tt +27 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/jobs/example_job.rb.tt +78 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/lib/mcp_client.rb.tt +68 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/migrations/create_mcp_oauth_credentials.rb.tt +19 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/migrations/create_mcp_oauth_states.rb.tt +21 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/models/mcp_oauth_credential.rb.tt +54 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/models/mcp_oauth_state.rb.tt +30 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/views/index.html.erb +646 -0
- data/lib/generators/ruby_llm/mcp/oauth/templates/views/show.html.erb +560 -0
- data/lib/ruby_llm/mcp/adapters/base_adapter.rb +215 -0
- data/lib/ruby_llm/mcp/adapters/mcp_sdk_adapter.rb +413 -0
- data/lib/ruby_llm/mcp/adapters/mcp_transports/coordinator_stub.rb +41 -0
- data/lib/ruby_llm/mcp/adapters/mcp_transports/sse.rb +56 -0
- data/lib/ruby_llm/mcp/adapters/mcp_transports/stdio.rb +56 -0
- data/lib/ruby_llm/mcp/adapters/mcp_transports/streamable_http.rb +90 -0
- data/lib/ruby_llm/mcp/adapters/ruby_llm_adapter.rb +216 -0
- data/lib/ruby_llm/mcp/auth/browser/callback_handler.rb +71 -0
- data/lib/ruby_llm/mcp/auth/browser/callback_server.rb +36 -0
- data/lib/ruby_llm/mcp/auth/browser/http_server.rb +112 -0
- data/lib/ruby_llm/mcp/auth/browser/opener.rb +39 -0
- data/lib/ruby_llm/mcp/auth/browser/pages.rb +607 -0
- data/lib/ruby_llm/mcp/auth/browser_oauth_provider.rb +427 -0
- data/lib/ruby_llm/mcp/auth/client_registrar.rb +170 -0
- data/lib/ruby_llm/mcp/auth/discoverer.rb +255 -0
- data/lib/ruby_llm/mcp/auth/flows/authorization_code_flow.rb +122 -0
- data/lib/ruby_llm/mcp/auth/flows/client_credentials_flow.rb +67 -0
- data/lib/ruby_llm/mcp/auth/grant_strategies/authorization_code.rb +31 -0
- data/lib/ruby_llm/mcp/auth/grant_strategies/base.rb +31 -0
- data/lib/ruby_llm/mcp/auth/grant_strategies/client_credentials.rb +31 -0
- data/lib/ruby_llm/mcp/auth/http_response_handler.rb +63 -0
- data/lib/ruby_llm/mcp/auth/memory_storage.rb +91 -0
- data/lib/ruby_llm/mcp/auth/oauth_provider.rb +341 -0
- data/lib/ruby_llm/mcp/auth/security.rb +44 -0
- data/lib/ruby_llm/mcp/auth/session_manager.rb +54 -0
- data/lib/ruby_llm/mcp/auth/token_manager.rb +307 -0
- data/lib/ruby_llm/mcp/auth/transport_oauth_helper.rb +107 -0
- data/lib/ruby_llm/mcp/auth/url_builder.rb +135 -0
- data/lib/ruby_llm/mcp/auth.rb +371 -0
- data/lib/ruby_llm/mcp/client.rb +312 -35
- data/lib/ruby_llm/mcp/configuration.rb +199 -24
- data/lib/ruby_llm/mcp/elicitation.rb +261 -14
- data/lib/ruby_llm/mcp/errors.rb +29 -0
- data/lib/ruby_llm/mcp/extensions/apps/constants.rb +28 -0
- data/lib/ruby_llm/mcp/extensions/apps/resource_metadata.rb +24 -0
- data/lib/ruby_llm/mcp/extensions/apps/tool_metadata.rb +45 -0
- data/lib/ruby_llm/mcp/extensions/configuration.rb +72 -0
- data/lib/ruby_llm/mcp/extensions/constants.rb +16 -0
- data/lib/ruby_llm/mcp/extensions/registry.rb +85 -0
- data/lib/ruby_llm/mcp/handlers/approval_decision.rb +90 -0
- data/lib/ruby_llm/mcp/handlers/async_response.rb +181 -0
- data/lib/ruby_llm/mcp/handlers/concerns/approval_actions.rb +42 -0
- data/lib/ruby_llm/mcp/handlers/concerns/async_execution.rb +80 -0
- data/lib/ruby_llm/mcp/handlers/concerns/elicitation_actions.rb +42 -0
- data/lib/ruby_llm/mcp/handlers/concerns/error_handling.rb +29 -0
- data/lib/ruby_llm/mcp/handlers/concerns/guard_checks.rb +72 -0
- data/lib/ruby_llm/mcp/handlers/concerns/lifecycle.rb +84 -0
- data/lib/ruby_llm/mcp/handlers/concerns/logging.rb +19 -0
- data/lib/ruby_llm/mcp/handlers/concerns/model_filtering.rb +36 -0
- data/lib/ruby_llm/mcp/handlers/concerns/options.rb +83 -0
- data/lib/ruby_llm/mcp/handlers/concerns/registry_integration.rb +54 -0
- data/lib/ruby_llm/mcp/handlers/concerns/sampling_actions.rb +84 -0
- data/lib/ruby_llm/mcp/handlers/concerns/timeouts.rb +52 -0
- data/lib/ruby_llm/mcp/handlers/concerns/tool_filtering.rb +50 -0
- data/lib/ruby_llm/mcp/handlers/elicitation_handler.rb +58 -0
- data/lib/ruby_llm/mcp/handlers/elicitation_registry.rb +203 -0
- data/lib/ruby_llm/mcp/handlers/human_in_the_loop_handler.rb +93 -0
- data/lib/ruby_llm/mcp/handlers/human_in_the_loop_registry.rb +271 -0
- data/lib/ruby_llm/mcp/handlers/promise.rb +192 -0
- data/lib/ruby_llm/mcp/handlers/sampling_handler.rb +64 -0
- data/lib/ruby_llm/mcp/handlers.rb +14 -0
- data/lib/ruby_llm/mcp/native/cancellable_operation.rb +94 -0
- data/lib/ruby_llm/mcp/native/client.rb +551 -0
- data/lib/ruby_llm/mcp/native/json_rpc.rb +170 -0
- data/lib/ruby_llm/mcp/native/messages/helpers.rb +39 -0
- data/lib/ruby_llm/mcp/native/messages/notifications.rb +60 -0
- data/lib/ruby_llm/mcp/native/messages/requests.rb +267 -0
- data/lib/ruby_llm/mcp/native/messages/responses.rb +114 -0
- data/lib/ruby_llm/mcp/native/messages.rb +43 -0
- data/lib/ruby_llm/mcp/native/notification.rb +16 -0
- data/lib/ruby_llm/mcp/native/protocol.rb +79 -0
- data/lib/ruby_llm/mcp/native/response_handler.rb +220 -0
- data/lib/ruby_llm/mcp/native/task_registry.rb +62 -0
- data/lib/ruby_llm/mcp/native/transport.rb +88 -0
- data/lib/ruby_llm/mcp/native/transports/sse.rb +655 -0
- data/lib/ruby_llm/mcp/native/transports/stdio.rb +367 -0
- data/lib/ruby_llm/mcp/native/transports/streamable_http.rb +1024 -0
- data/lib/ruby_llm/mcp/native/transports/support/http_client.rb +28 -0
- data/lib/ruby_llm/mcp/native/transports/support/rate_limiter.rb +49 -0
- data/lib/ruby_llm/mcp/native/transports/support/timeout.rb +36 -0
- data/lib/ruby_llm/mcp/native.rb +12 -0
- data/lib/ruby_llm/mcp/notification_handler.rb +43 -5
- data/lib/ruby_llm/mcp/prompt.rb +7 -7
- data/lib/ruby_llm/mcp/railtie.rb +7 -13
- data/lib/ruby_llm/mcp/resource.rb +17 -8
- data/lib/ruby_llm/mcp/resource_template.rb +8 -7
- data/lib/ruby_llm/mcp/result.rb +8 -4
- data/lib/ruby_llm/mcp/roots.rb +4 -4
- data/lib/ruby_llm/mcp/sample.rb +83 -13
- data/lib/ruby_llm/mcp/schema_validator.rb +33 -0
- data/lib/ruby_llm/mcp/server_capabilities.rb +41 -0
- data/lib/ruby_llm/mcp/task.rb +65 -0
- data/lib/ruby_llm/mcp/tool.rb +33 -27
- data/lib/ruby_llm/mcp/version.rb +1 -1
- data/lib/ruby_llm/mcp.rb +37 -7
- data/lib/tasks/smoke.rake +66 -0
- metadata +115 -39
- data/lib/generators/ruby_llm/mcp/templates/mcps.yml +0 -9
- data/lib/ruby_llm/mcp/coordinator.rb +0 -293
- data/lib/ruby_llm/mcp/notifications/cancelled.rb +0 -32
- data/lib/ruby_llm/mcp/notifications/initialize.rb +0 -24
- data/lib/ruby_llm/mcp/notifications/roots_list_change.rb +0 -26
- data/lib/ruby_llm/mcp/protocol.rb +0 -34
- data/lib/ruby_llm/mcp/requests/completion_prompt.rb +0 -50
- data/lib/ruby_llm/mcp/requests/completion_resource.rb +0 -50
- data/lib/ruby_llm/mcp/requests/initialization.rb +0 -34
- data/lib/ruby_llm/mcp/requests/logging_set_level.rb +0 -28
- data/lib/ruby_llm/mcp/requests/ping.rb +0 -24
- data/lib/ruby_llm/mcp/requests/prompt_call.rb +0 -32
- data/lib/ruby_llm/mcp/requests/prompt_list.rb +0 -31
- data/lib/ruby_llm/mcp/requests/resource_list.rb +0 -31
- data/lib/ruby_llm/mcp/requests/resource_read.rb +0 -30
- data/lib/ruby_llm/mcp/requests/resource_template_list.rb +0 -31
- data/lib/ruby_llm/mcp/requests/resources_subscribe.rb +0 -30
- data/lib/ruby_llm/mcp/requests/shared/meta.rb +0 -32
- data/lib/ruby_llm/mcp/requests/shared/pagination.rb +0 -17
- data/lib/ruby_llm/mcp/requests/tool_call.rb +0 -35
- data/lib/ruby_llm/mcp/requests/tool_list.rb +0 -31
- data/lib/ruby_llm/mcp/response_handler.rb +0 -67
- data/lib/ruby_llm/mcp/responses/elicitation.rb +0 -33
- data/lib/ruby_llm/mcp/responses/error.rb +0 -33
- data/lib/ruby_llm/mcp/responses/ping.rb +0 -28
- data/lib/ruby_llm/mcp/responses/roots_list.rb +0 -31
- data/lib/ruby_llm/mcp/responses/sampling_create_message.rb +0 -50
- data/lib/ruby_llm/mcp/transport.rb +0 -58
- data/lib/ruby_llm/mcp/transports/sse.rb +0 -341
- data/lib/ruby_llm/mcp/transports/stdio.rb +0 -230
- data/lib/ruby_llm/mcp/transports/streamable_http.rb +0 -723
- data/lib/ruby_llm/mcp/transports/support/http_client.rb +0 -28
- data/lib/ruby_llm/mcp/transports/support/rate_limit.rb +0 -47
- data/lib/ruby_llm/mcp/transports/support/timeout.rb +0 -34
data/lib/ruby_llm/mcp/client.rb
CHANGED
|
@@ -7,17 +7,42 @@ module RubyLLM
|
|
|
7
7
|
class Client
|
|
8
8
|
extend Forwardable
|
|
9
9
|
|
|
10
|
-
attr_reader :name, :config, :transport_type, :request_timeout, :log_level, :on, :roots
|
|
10
|
+
attr_reader :name, :config, :transport_type, :request_timeout, :log_level, :on, :roots, :adapter,
|
|
11
|
+
:on_logging_level
|
|
11
12
|
attr_accessor :linked_resources
|
|
12
13
|
|
|
13
|
-
def initialize(name:, transport_type:, start: true,
|
|
14
|
+
def initialize(name:, transport_type:, sdk: nil, adapter: nil, start: true, # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
|
15
|
+
request_timeout: MCP.config.request_timeout, config: {})
|
|
14
16
|
@name = name
|
|
15
|
-
@with_prefix = config.delete(:with_prefix) || false
|
|
16
|
-
@config = config.merge(request_timeout: request_timeout)
|
|
17
17
|
@transport_type = transport_type.to_sym
|
|
18
|
+
@adapter_type = adapter || sdk || MCP.config.default_adapter
|
|
19
|
+
|
|
20
|
+
# Validate early
|
|
21
|
+
MCP.config.adapter_config.validate!(
|
|
22
|
+
adapter: @adapter_type,
|
|
23
|
+
transport: @transport_type
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
@with_prefix = config.delete(:with_prefix) || false
|
|
27
|
+
@resolved_protocol_version = config[:protocol_version] ||
|
|
28
|
+
config["protocol_version"] ||
|
|
29
|
+
MCP.config.protocol_version
|
|
30
|
+
@resolved_extensions = Extensions::Registry.merge(
|
|
31
|
+
MCP.config.extensions.to_h,
|
|
32
|
+
config[:extensions] || config["extensions"]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
@config = config.merge(
|
|
36
|
+
request_timeout: request_timeout,
|
|
37
|
+
protocol_version: @resolved_protocol_version,
|
|
38
|
+
extensions: @resolved_extensions
|
|
39
|
+
)
|
|
18
40
|
@request_timeout = request_timeout
|
|
19
41
|
|
|
20
|
-
|
|
42
|
+
# Store OAuth config for later use
|
|
43
|
+
@oauth_config = config[:oauth] || config["oauth"]
|
|
44
|
+
@oauth_provider = nil
|
|
45
|
+
@oauth_storage = nil
|
|
21
46
|
|
|
22
47
|
@on = {}
|
|
23
48
|
@tools = {}
|
|
@@ -29,31 +54,73 @@ module RubyLLM
|
|
|
29
54
|
|
|
30
55
|
@linked_resources = []
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
|
|
57
|
+
# Build adapter based on configuration
|
|
58
|
+
@adapter = build_adapter
|
|
59
|
+
|
|
60
|
+
setup_roots if @adapter.supports?(:roots)
|
|
61
|
+
setup_sampling if @adapter.supports?(:sampling)
|
|
62
|
+
setup_event_handlers
|
|
63
|
+
sync_elicitation_handler_state
|
|
34
64
|
|
|
35
|
-
@
|
|
65
|
+
@adapter.start if start
|
|
36
66
|
end
|
|
37
67
|
|
|
38
|
-
def_delegators :@
|
|
68
|
+
def_delegators :@adapter, :alive?, :capabilities, :ping, :client_capabilities,
|
|
69
|
+
:register_in_flight_request, :unregister_in_flight_request,
|
|
70
|
+
:cancel_in_flight_request
|
|
39
71
|
|
|
40
72
|
def start
|
|
41
|
-
@
|
|
73
|
+
@adapter.start
|
|
42
74
|
end
|
|
43
75
|
|
|
44
76
|
def stop
|
|
45
|
-
@
|
|
77
|
+
@adapter.stop
|
|
46
78
|
end
|
|
47
79
|
|
|
48
80
|
def restart!
|
|
49
|
-
@
|
|
81
|
+
@adapter.restart!
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get or create OAuth provider for this client
|
|
85
|
+
# @param type [Symbol] OAuth provider type (:standard or :browser, defaults to :standard)
|
|
86
|
+
# @param options [Hash] additional options passed to provider
|
|
87
|
+
# @return [OAuthProvider, BrowserOAuthProvider] OAuth provider instance
|
|
88
|
+
def oauth(type: :standard, **options)
|
|
89
|
+
# Return existing provider if already created
|
|
90
|
+
return @oauth_provider if @oauth_provider
|
|
91
|
+
|
|
92
|
+
# Get provider from transport if it already exists
|
|
93
|
+
transport_oauth = transport_oauth_provider
|
|
94
|
+
return transport_oauth if transport_oauth
|
|
95
|
+
|
|
96
|
+
# Create new provider lazily
|
|
97
|
+
server_url = @config[:url] || @config["url"]
|
|
98
|
+
unless server_url
|
|
99
|
+
raise Errors::ConfigurationError.new(
|
|
100
|
+
message: "Cannot create OAuth provider without server URL in config"
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
oauth_options = {
|
|
105
|
+
server_url: server_url,
|
|
106
|
+
scope: @oauth_config&.dig(:scope) || @oauth_config&.dig("scope"),
|
|
107
|
+
storage: oauth_storage,
|
|
108
|
+
**options
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@oauth_provider = Auth.create_oauth(
|
|
112
|
+
server_url,
|
|
113
|
+
type: type,
|
|
114
|
+
**oauth_options
|
|
115
|
+
)
|
|
50
116
|
end
|
|
51
117
|
|
|
52
118
|
def tools(refresh: false)
|
|
119
|
+
require_feature!(:tools)
|
|
53
120
|
return [] unless capabilities.tools_list?
|
|
54
121
|
|
|
55
122
|
fetch(:tools, refresh) do
|
|
56
|
-
tools = @
|
|
123
|
+
tools = @adapter.tool_list
|
|
57
124
|
build_map(tools, MCP::Tool, with_prefix: @with_prefix)
|
|
58
125
|
end
|
|
59
126
|
|
|
@@ -71,10 +138,11 @@ module RubyLLM
|
|
|
71
138
|
end
|
|
72
139
|
|
|
73
140
|
def resources(refresh: false)
|
|
141
|
+
require_feature!(:resources)
|
|
74
142
|
return [] unless capabilities.resources_list?
|
|
75
143
|
|
|
76
144
|
fetch(:resources, refresh) do
|
|
77
|
-
resources = @
|
|
145
|
+
resources = @adapter.resource_list
|
|
78
146
|
resources = build_map(resources, MCP::Resource)
|
|
79
147
|
include_linked_resources(resources)
|
|
80
148
|
end
|
|
@@ -88,15 +156,30 @@ module RubyLLM
|
|
|
88
156
|
@resources[name]
|
|
89
157
|
end
|
|
90
158
|
|
|
159
|
+
def unsubscribe_from_resource(resource_or_uri) # rubocop:disable Naming/PredicateMethod
|
|
160
|
+
require_feature!(:subscriptions)
|
|
161
|
+
|
|
162
|
+
uri = if resource_or_uri.respond_to?(:uri)
|
|
163
|
+
resource_or_uri.uri
|
|
164
|
+
else
|
|
165
|
+
resource_or_uri.to_s
|
|
166
|
+
end
|
|
167
|
+
@adapter.resources_unsubscribe(uri: uri)
|
|
168
|
+
resource = @resources.values.find { |existing| existing.uri == uri }
|
|
169
|
+
resource&.instance_variable_set(:@subscribed, false)
|
|
170
|
+
true
|
|
171
|
+
end
|
|
172
|
+
|
|
91
173
|
def reset_resources!
|
|
92
174
|
@resources = {}
|
|
93
175
|
end
|
|
94
176
|
|
|
95
177
|
def resource_templates(refresh: false)
|
|
178
|
+
require_feature!(:resource_templates)
|
|
96
179
|
return [] unless capabilities.resources_list?
|
|
97
180
|
|
|
98
181
|
fetch(:resource_templates, refresh) do
|
|
99
|
-
resource_templates = @
|
|
182
|
+
resource_templates = @adapter.resource_template_list
|
|
100
183
|
build_map(resource_templates, MCP::ResourceTemplate)
|
|
101
184
|
end
|
|
102
185
|
|
|
@@ -114,10 +197,11 @@ module RubyLLM
|
|
|
114
197
|
end
|
|
115
198
|
|
|
116
199
|
def prompts(refresh: false)
|
|
200
|
+
require_feature!(:prompts)
|
|
117
201
|
return [] unless capabilities.prompt_list?
|
|
118
202
|
|
|
119
203
|
fetch(:prompts, refresh) do
|
|
120
|
-
prompts = @
|
|
204
|
+
prompts = @adapter.prompt_list
|
|
121
205
|
build_map(prompts, MCP::Prompt)
|
|
122
206
|
end
|
|
123
207
|
|
|
@@ -134,11 +218,46 @@ module RubyLLM
|
|
|
134
218
|
@prompts = {}
|
|
135
219
|
end
|
|
136
220
|
|
|
221
|
+
def tasks_list
|
|
222
|
+
require_feature!(:tasks)
|
|
223
|
+
return [] unless capabilities.tasks? && capabilities.tasks_list?
|
|
224
|
+
|
|
225
|
+
@adapter.tasks_list.map { |task| MCP::Task.new(@adapter, task) }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def task_get(task_id)
|
|
229
|
+
require_feature!(:tasks)
|
|
230
|
+
result = @adapter.task_get(task_id: task_id)
|
|
231
|
+
MCP::Task.new(@adapter, result.value)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def task_result(task_id)
|
|
235
|
+
require_feature!(:tasks)
|
|
236
|
+
result = @adapter.task_result(task_id: task_id)
|
|
237
|
+
result.value
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def task_cancel(task_id)
|
|
241
|
+
require_feature!(:tasks)
|
|
242
|
+
unless capabilities.tasks_cancel?
|
|
243
|
+
message = "Task cancellation is not available for this MCP server"
|
|
244
|
+
raise Errors::Capabilities::TaskCancelNotAvailable.new(message: message)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
result = @adapter.task_cancel(task_id: task_id)
|
|
248
|
+
MCP::Task.new(@adapter, result.value)
|
|
249
|
+
end
|
|
250
|
+
|
|
137
251
|
def tracking_progress?
|
|
138
252
|
@on.key?(:progress) && !@on[:progress].nil?
|
|
139
253
|
end
|
|
140
254
|
|
|
141
255
|
def on_progress(&block)
|
|
256
|
+
require_feature!(:progress_tracking)
|
|
257
|
+
if alive?
|
|
258
|
+
@adapter.set_progress_tracking(enabled: true)
|
|
259
|
+
end
|
|
260
|
+
|
|
142
261
|
@on[:progress] = block
|
|
143
262
|
self
|
|
144
263
|
end
|
|
@@ -147,8 +266,23 @@ module RubyLLM
|
|
|
147
266
|
@on.key?(:human_in_the_loop) && !@on[:human_in_the_loop].nil?
|
|
148
267
|
end
|
|
149
268
|
|
|
150
|
-
def on_human_in_the_loop(
|
|
151
|
-
|
|
269
|
+
def on_human_in_the_loop(handler_class = nil, **options)
|
|
270
|
+
require_feature!(:human_in_the_loop)
|
|
271
|
+
|
|
272
|
+
if block_given?
|
|
273
|
+
raise ArgumentError, "Block-based human-in-the-loop callbacks are no longer supported. Use a handler class."
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
if handler_class
|
|
277
|
+
# Validate handler class
|
|
278
|
+
validate_handler_class!(handler_class, :execute)
|
|
279
|
+
|
|
280
|
+
@on[:human_in_the_loop] = { class: handler_class, options: options }
|
|
281
|
+
else
|
|
282
|
+
# Clear handler when called without arguments
|
|
283
|
+
@on[:human_in_the_loop] = nil
|
|
284
|
+
end
|
|
285
|
+
|
|
152
286
|
self
|
|
153
287
|
end
|
|
154
288
|
|
|
@@ -161,9 +295,10 @@ module RubyLLM
|
|
|
161
295
|
end
|
|
162
296
|
|
|
163
297
|
def on_logging(level: Logging::WARNING, &block)
|
|
298
|
+
require_feature!(:logging)
|
|
164
299
|
@on_logging_level = level
|
|
165
300
|
if alive?
|
|
166
|
-
@
|
|
301
|
+
@adapter.set_logging(level: level)
|
|
167
302
|
end
|
|
168
303
|
|
|
169
304
|
@on[:logging] = block
|
|
@@ -174,17 +309,69 @@ module RubyLLM
|
|
|
174
309
|
@on.key?(:sampling) && !@on[:sampling].nil?
|
|
175
310
|
end
|
|
176
311
|
|
|
177
|
-
def on_sampling(&block)
|
|
178
|
-
|
|
312
|
+
def on_sampling(handler_class = nil, **options, &block)
|
|
313
|
+
require_feature!(:sampling)
|
|
314
|
+
|
|
315
|
+
if handler_class
|
|
316
|
+
# Validate handler class
|
|
317
|
+
validate_handler_class!(handler_class, :execute)
|
|
318
|
+
|
|
319
|
+
# Handler class provided
|
|
320
|
+
@on[:sampling] = if options.any?
|
|
321
|
+
lambda do |sample|
|
|
322
|
+
handler_class.new(sample: sample, coordinator: @adapter.native_client, **options).call
|
|
323
|
+
end
|
|
324
|
+
else
|
|
325
|
+
handler_class
|
|
326
|
+
end
|
|
327
|
+
elsif block_given?
|
|
328
|
+
# Block provided (backward compatible)
|
|
329
|
+
@on[:sampling] = block
|
|
330
|
+
else
|
|
331
|
+
# Clear handler when called without arguments
|
|
332
|
+
@on[:sampling] = nil
|
|
333
|
+
end
|
|
334
|
+
|
|
179
335
|
self
|
|
180
336
|
end
|
|
181
337
|
|
|
182
338
|
def elicitation_enabled?
|
|
339
|
+
@adapter_type == :ruby_llm && MCP.config.elicitation.enabled?
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def elicitation_callback_enabled?
|
|
183
343
|
@on.key?(:elicitation) && !@on[:elicitation].nil?
|
|
184
344
|
end
|
|
185
345
|
|
|
186
|
-
def on_elicitation(&block)
|
|
187
|
-
|
|
346
|
+
def on_elicitation(handler_class = nil, **options, &block)
|
|
347
|
+
require_feature!(:elicitation)
|
|
348
|
+
|
|
349
|
+
if handler_class
|
|
350
|
+
# Validate handler class
|
|
351
|
+
validate_handler_class!(handler_class, :execute)
|
|
352
|
+
|
|
353
|
+
# Handler class provided
|
|
354
|
+
@on[:elicitation] = if options.any?
|
|
355
|
+
lambda do |elicitation|
|
|
356
|
+
handler_class.new(
|
|
357
|
+
elicitation: elicitation,
|
|
358
|
+
coordinator: @adapter.native_client,
|
|
359
|
+
**options
|
|
360
|
+
).call
|
|
361
|
+
end
|
|
362
|
+
else
|
|
363
|
+
handler_class
|
|
364
|
+
end
|
|
365
|
+
elsif block_given?
|
|
366
|
+
# Block provided (backward compatible)
|
|
367
|
+
@on[:elicitation] = block
|
|
368
|
+
else
|
|
369
|
+
# Clear handler when called without arguments
|
|
370
|
+
@on[:elicitation] = nil
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
sync_elicitation_handler_state
|
|
374
|
+
|
|
188
375
|
self
|
|
189
376
|
end
|
|
190
377
|
|
|
@@ -212,10 +399,55 @@ module RubyLLM
|
|
|
212
399
|
|
|
213
400
|
private
|
|
214
401
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
402
|
+
# Get OAuth provider from adapter's transport if available
|
|
403
|
+
# @return [OAuthProvider, BrowserOAuthProvider, nil] OAuth provider or nil
|
|
404
|
+
def transport_oauth_provider
|
|
405
|
+
return nil unless @adapter
|
|
406
|
+
|
|
407
|
+
# For RubyLLMAdapter
|
|
408
|
+
if @adapter.respond_to?(:native_client)
|
|
409
|
+
transport = @adapter.native_client.transport
|
|
410
|
+
transport_protocol = transport.transport_protocol
|
|
411
|
+
return transport_protocol.oauth_provider if transport_protocol.respond_to?(:oauth_provider)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# For MCPSdkAdapter with wrapped transports
|
|
415
|
+
if @adapter.respond_to?(:mcp_client) && @adapter.instance_variable_get(:@mcp_client)
|
|
416
|
+
mcp_client = @adapter.instance_variable_get(:@mcp_client)
|
|
417
|
+
if mcp_client&.transport.respond_to?(:native_transport)
|
|
418
|
+
return mcp_client.transport.native_transport.oauth_provider
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
nil
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def build_adapter
|
|
426
|
+
case @adapter_type
|
|
427
|
+
when :ruby_llm
|
|
428
|
+
RubyLLM::MCP::Adapters::RubyLLMAdapter.new(self,
|
|
429
|
+
transport_type: @transport_type,
|
|
430
|
+
config: @config)
|
|
431
|
+
when :mcp_sdk
|
|
432
|
+
RubyLLM::MCP::Adapters::MCPSdkAdapter.new(self,
|
|
433
|
+
transport_type: @transport_type,
|
|
434
|
+
config: @config)
|
|
435
|
+
else
|
|
436
|
+
raise ArgumentError, "Unknown adapter type: #{@adapter_type}"
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def require_feature!(feature)
|
|
441
|
+
unless @adapter.supports?(feature)
|
|
442
|
+
raise Errors::UnsupportedFeature.new(
|
|
443
|
+
message: <<~MSG.strip
|
|
444
|
+
Feature '#{feature}' is not supported by the #{@adapter_type} adapter.
|
|
445
|
+
|
|
446
|
+
This feature requires the :ruby_llm adapter.
|
|
447
|
+
Change your configuration to use adapter: :ruby_llm
|
|
448
|
+
MSG
|
|
449
|
+
)
|
|
450
|
+
end
|
|
219
451
|
end
|
|
220
452
|
|
|
221
453
|
def fetch(cache_key, refresh)
|
|
@@ -229,9 +461,9 @@ module RubyLLM
|
|
|
229
461
|
def build_map(raw_data, klass, with_prefix: false)
|
|
230
462
|
raw_data.each_with_object({}) do |item, acc|
|
|
231
463
|
instance = if with_prefix
|
|
232
|
-
klass.new(@
|
|
464
|
+
klass.new(@adapter, item, with_prefix: @with_prefix)
|
|
233
465
|
else
|
|
234
|
-
klass.new(@
|
|
466
|
+
klass.new(@adapter, item)
|
|
235
467
|
end
|
|
236
468
|
acc[instance.name] = instance
|
|
237
469
|
end
|
|
@@ -246,7 +478,7 @@ module RubyLLM
|
|
|
246
478
|
end
|
|
247
479
|
|
|
248
480
|
def setup_roots
|
|
249
|
-
@roots = Roots.new(paths: MCP.config.roots,
|
|
481
|
+
@roots = Roots.new(paths: MCP.config.roots, adapter: @adapter)
|
|
250
482
|
end
|
|
251
483
|
|
|
252
484
|
def setup_sampling
|
|
@@ -254,11 +486,56 @@ module RubyLLM
|
|
|
254
486
|
end
|
|
255
487
|
|
|
256
488
|
def setup_event_handlers
|
|
257
|
-
|
|
258
|
-
@
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
489
|
+
# Only setup handlers that are supported
|
|
490
|
+
if @adapter.supports?(:progress_tracking)
|
|
491
|
+
@on[:progress] = MCP.config.on_progress
|
|
492
|
+
if @on[:progress] && alive?
|
|
493
|
+
@adapter.set_progress_tracking(enabled: true)
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if @adapter.supports?(:human_in_the_loop)
|
|
498
|
+
@on[:human_in_the_loop] = MCP.config.on_human_in_the_loop
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
if @adapter.supports?(:logging)
|
|
502
|
+
@on[:logging] = MCP.config.on_logging
|
|
503
|
+
@on_logging_level = MCP.config.on_logging_level
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
if @adapter.supports?(:elicitation)
|
|
507
|
+
@on[:elicitation] = MCP.config.on_elicitation
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def sync_elicitation_handler_state
|
|
512
|
+
return unless @adapter.supports?(:elicitation)
|
|
513
|
+
|
|
514
|
+
@adapter.set_elicitation_enabled(enabled: elicitation_enabled?)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Get or create OAuth storage shared with transport
|
|
518
|
+
def oauth_storage
|
|
519
|
+
# Try to get storage from transport's OAuth provider
|
|
520
|
+
transport_oauth = transport_oauth_provider
|
|
521
|
+
return transport_oauth.storage if transport_oauth
|
|
522
|
+
|
|
523
|
+
# Create new storage shared with client
|
|
524
|
+
@oauth_storage ||= Auth::MemoryStorage.new
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# Validate that a handler class has required methods
|
|
528
|
+
# @param handler_class [Class] the handler class to validate
|
|
529
|
+
# @param required_method [Symbol] the method that must be defined
|
|
530
|
+
# @raise [ArgumentError] if validation fails
|
|
531
|
+
def validate_handler_class!(handler_class, required_method)
|
|
532
|
+
unless Handlers.handler_class?(handler_class)
|
|
533
|
+
raise ArgumentError, "Handler must be a class, got #{handler_class.class}"
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
unless handler_class.method_defined?(required_method)
|
|
537
|
+
raise ArgumentError, "Handler class #{handler_class} must define ##{required_method} method"
|
|
538
|
+
end
|
|
262
539
|
end
|
|
263
540
|
end
|
|
264
541
|
end
|