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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -44
  3. data/lib/generators/ruby_llm/mcp/install/templates/initializer.rb +4 -21
  4. data/lib/generators/ruby_llm/mcp/install/templates/mcps.yml +0 -20
  5. data/lib/ruby_llm/mcp/auth/browser/http_server.rb +3 -0
  6. data/lib/ruby_llm/mcp/auth/browser/opener.rb +2 -0
  7. data/lib/ruby_llm/mcp/auth/browser/pages.rb +32 -100
  8. data/lib/ruby_llm/mcp/auth/browser_oauth_provider.rb +6 -32
  9. data/lib/ruby_llm/mcp/auth/http_response_handler.rb +2 -0
  10. data/lib/ruby_llm/mcp/auth/memory_storage.rb +0 -18
  11. data/lib/ruby_llm/mcp/auth/oauth_provider.rb +3 -82
  12. data/lib/ruby_llm/mcp/auth/session_manager.rb +2 -0
  13. data/lib/ruby_llm/mcp/auth/url_builder.rb +2 -0
  14. data/lib/ruby_llm/mcp/client.rb +32 -119
  15. data/lib/ruby_llm/mcp/configuration.rb +6 -74
  16. data/lib/ruby_llm/mcp/coordinator.rb +304 -0
  17. data/lib/ruby_llm/mcp/elicitation.rb +6 -8
  18. data/lib/ruby_llm/mcp/errors.rb +0 -15
  19. data/lib/ruby_llm/mcp/notification_handler.rb +5 -21
  20. data/lib/ruby_llm/mcp/notifications/cancelled.rb +32 -0
  21. data/lib/ruby_llm/mcp/notifications/initialize.rb +24 -0
  22. data/lib/ruby_llm/mcp/notifications/roots_list_change.rb +26 -0
  23. data/lib/ruby_llm/mcp/prompt.rb +7 -7
  24. data/lib/ruby_llm/mcp/protocol.rb +34 -0
  25. data/lib/ruby_llm/mcp/railtie.rb +6 -8
  26. data/lib/ruby_llm/mcp/requests/completion_prompt.rb +50 -0
  27. data/lib/ruby_llm/mcp/requests/completion_resource.rb +50 -0
  28. data/lib/ruby_llm/mcp/requests/initialization.rb +34 -0
  29. data/lib/ruby_llm/mcp/requests/logging_set_level.rb +28 -0
  30. data/lib/ruby_llm/mcp/requests/ping.rb +24 -0
  31. data/lib/ruby_llm/mcp/requests/prompt_call.rb +32 -0
  32. data/lib/ruby_llm/mcp/requests/prompt_list.rb +31 -0
  33. data/lib/ruby_llm/mcp/requests/resource_list.rb +31 -0
  34. data/lib/ruby_llm/mcp/requests/resource_read.rb +30 -0
  35. data/lib/ruby_llm/mcp/requests/resource_template_list.rb +31 -0
  36. data/lib/ruby_llm/mcp/requests/resources_subscribe.rb +30 -0
  37. data/lib/ruby_llm/mcp/requests/shared/meta.rb +32 -0
  38. data/lib/ruby_llm/mcp/requests/shared/pagination.rb +17 -0
  39. data/lib/ruby_llm/mcp/requests/tool_call.rb +35 -0
  40. data/lib/ruby_llm/mcp/requests/tool_list.rb +31 -0
  41. data/lib/ruby_llm/mcp/resource.rb +8 -6
  42. data/lib/ruby_llm/mcp/resource_template.rb +7 -7
  43. data/lib/ruby_llm/mcp/response_handler.rb +67 -0
  44. data/lib/ruby_llm/mcp/responses/elicitation.rb +33 -0
  45. data/lib/ruby_llm/mcp/responses/error.rb +33 -0
  46. data/lib/ruby_llm/mcp/responses/ping.rb +28 -0
  47. data/lib/ruby_llm/mcp/responses/roots_list.rb +31 -0
  48. data/lib/ruby_llm/mcp/responses/sampling_create_message.rb +50 -0
  49. data/lib/ruby_llm/mcp/result.rb +4 -8
  50. data/lib/ruby_llm/mcp/roots.rb +4 -4
  51. data/lib/ruby_llm/mcp/sample.rb +2 -6
  52. data/lib/ruby_llm/mcp/tool.rb +9 -9
  53. data/lib/ruby_llm/mcp/transport.rb +151 -0
  54. data/lib/ruby_llm/mcp/transports/sse.rb +435 -0
  55. data/lib/ruby_llm/mcp/transports/stdio.rb +231 -0
  56. data/lib/ruby_llm/mcp/transports/streamable_http.rb +725 -0
  57. data/lib/ruby_llm/mcp/transports/support/http_client.rb +28 -0
  58. data/lib/ruby_llm/mcp/transports/support/rate_limit.rb +47 -0
  59. data/lib/ruby_llm/mcp/transports/support/timeout.rb +34 -0
  60. data/lib/ruby_llm/mcp/version.rb +1 -1
  61. data/lib/ruby_llm/mcp.rb +7 -30
  62. metadata +38 -33
  63. data/lib/ruby_llm/mcp/adapters/base_adapter.rb +0 -179
  64. data/lib/ruby_llm/mcp/adapters/mcp_sdk_adapter.rb +0 -292
  65. data/lib/ruby_llm/mcp/adapters/mcp_transports/coordinator_stub.rb +0 -33
  66. data/lib/ruby_llm/mcp/adapters/mcp_transports/sse.rb +0 -52
  67. data/lib/ruby_llm/mcp/adapters/mcp_transports/stdio.rb +0 -52
  68. data/lib/ruby_llm/mcp/adapters/mcp_transports/streamable_http.rb +0 -86
  69. data/lib/ruby_llm/mcp/adapters/ruby_llm_adapter.rb +0 -92
  70. data/lib/ruby_llm/mcp/auth/transport_oauth_helper.rb +0 -107
  71. data/lib/ruby_llm/mcp/native/cancellable_operation.rb +0 -57
  72. data/lib/ruby_llm/mcp/native/client.rb +0 -387
  73. data/lib/ruby_llm/mcp/native/json_rpc.rb +0 -170
  74. data/lib/ruby_llm/mcp/native/messages/helpers.rb +0 -39
  75. data/lib/ruby_llm/mcp/native/messages/notifications.rb +0 -42
  76. data/lib/ruby_llm/mcp/native/messages/requests.rb +0 -206
  77. data/lib/ruby_llm/mcp/native/messages/responses.rb +0 -106
  78. data/lib/ruby_llm/mcp/native/messages.rb +0 -36
  79. data/lib/ruby_llm/mcp/native/notification.rb +0 -16
  80. data/lib/ruby_llm/mcp/native/protocol.rb +0 -36
  81. data/lib/ruby_llm/mcp/native/response_handler.rb +0 -110
  82. data/lib/ruby_llm/mcp/native/transport.rb +0 -88
  83. data/lib/ruby_llm/mcp/native/transports/sse.rb +0 -607
  84. data/lib/ruby_llm/mcp/native/transports/stdio.rb +0 -356
  85. data/lib/ruby_llm/mcp/native/transports/streamable_http.rb +0 -926
  86. data/lib/ruby_llm/mcp/native/transports/support/http_client.rb +0 -28
  87. data/lib/ruby_llm/mcp/native/transports/support/rate_limit.rb +0 -49
  88. data/lib/ruby_llm/mcp/native/transports/support/timeout.rb +0 -36
  89. data/lib/ruby_llm/mcp/native.rb +0 -12
@@ -7,31 +7,23 @@ 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, :adapter,
11
- :on_logging_level
10
+ attr_reader :name, :config, :transport_type, :request_timeout, :log_level, :on, :roots
12
11
  attr_accessor :linked_resources
13
12
 
14
- def initialize(name:, transport_type:, sdk: nil, adapter: nil, start: true, # rubocop:disable Metrics/ParameterLists
15
- request_timeout: MCP.config.request_timeout, config: {})
13
+ def initialize(name:, transport_type:, start: true, request_timeout: MCP.config.request_timeout, config: {})
16
14
  @name = name
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
15
  @with_prefix = config.delete(:with_prefix) || false
27
16
  @config = config.merge(request_timeout: request_timeout)
17
+ @transport_type = transport_type.to_sym
28
18
  @request_timeout = request_timeout
29
19
 
30
- # Store OAuth config for later use
20
+ # Store OAuth config before coordinator setup
31
21
  @oauth_config = config[:oauth] || config["oauth"]
32
22
  @oauth_provider = nil
33
23
  @oauth_storage = nil
34
24
 
25
+ @coordinator = setup_coordinator
26
+
35
27
  @on = {}
36
28
  @tools = {}
37
29
  @resources = {}
@@ -42,30 +34,24 @@ module RubyLLM
42
34
 
43
35
  @linked_resources = []
44
36
 
45
- # Build adapter based on configuration
46
- @adapter = build_adapter
47
-
48
- setup_roots if @adapter.supports?(:roots)
49
- setup_sampling if @adapter.supports?(:sampling)
50
- setup_event_handlers
37
+ setup_roots
38
+ setup_sampling
51
39
 
52
- @adapter.start if start
40
+ @coordinator.start_transport if start
53
41
  end
54
42
 
55
- def_delegators :@adapter, :alive?, :capabilities, :ping, :client_capabilities,
56
- :register_in_flight_request, :unregister_in_flight_request,
57
- :cancel_in_flight_request
43
+ def_delegators :@coordinator, :alive?, :capabilities, :ping, :client_capabilities
58
44
 
59
45
  def start
60
- @adapter.start
46
+ @coordinator.start_transport
61
47
  end
62
48
 
63
49
  def stop
64
- @adapter.stop
50
+ @coordinator.stop_transport
65
51
  end
66
52
 
67
53
  def restart!
68
- @adapter.restart!
54
+ @coordinator.restart_transport
69
55
  end
70
56
 
71
57
  # Get or create OAuth provider for this client
@@ -77,7 +63,7 @@ module RubyLLM
77
63
  return @oauth_provider if @oauth_provider
78
64
 
79
65
  # Get provider from transport if it already exists
80
- transport_oauth = transport_oauth_provider
66
+ transport_oauth = @coordinator.transport_oauth_provider
81
67
  return transport_oauth if transport_oauth
82
68
 
83
69
  # Create new provider lazily
@@ -103,11 +89,10 @@ module RubyLLM
103
89
  end
104
90
 
105
91
  def tools(refresh: false)
106
- require_feature!(:tools)
107
92
  return [] unless capabilities.tools_list?
108
93
 
109
94
  fetch(:tools, refresh) do
110
- tools = @adapter.tool_list
95
+ tools = @coordinator.tool_list
111
96
  build_map(tools, MCP::Tool, with_prefix: @with_prefix)
112
97
  end
113
98
 
@@ -125,11 +110,10 @@ module RubyLLM
125
110
  end
126
111
 
127
112
  def resources(refresh: false)
128
- require_feature!(:resources)
129
113
  return [] unless capabilities.resources_list?
130
114
 
131
115
  fetch(:resources, refresh) do
132
- resources = @adapter.resource_list
116
+ resources = @coordinator.resource_list
133
117
  resources = build_map(resources, MCP::Resource)
134
118
  include_linked_resources(resources)
135
119
  end
@@ -148,11 +132,10 @@ module RubyLLM
148
132
  end
149
133
 
150
134
  def resource_templates(refresh: false)
151
- require_feature!(:resource_templates)
152
135
  return [] unless capabilities.resources_list?
153
136
 
154
137
  fetch(:resource_templates, refresh) do
155
- resource_templates = @adapter.resource_template_list
138
+ resource_templates = @coordinator.resource_template_list
156
139
  build_map(resource_templates, MCP::ResourceTemplate)
157
140
  end
158
141
 
@@ -170,11 +153,10 @@ module RubyLLM
170
153
  end
171
154
 
172
155
  def prompts(refresh: false)
173
- require_feature!(:prompts)
174
156
  return [] unless capabilities.prompt_list?
175
157
 
176
158
  fetch(:prompts, refresh) do
177
- prompts = @adapter.prompt_list
159
+ prompts = @coordinator.prompt_list
178
160
  build_map(prompts, MCP::Prompt)
179
161
  end
180
162
 
@@ -196,11 +178,6 @@ module RubyLLM
196
178
  end
197
179
 
198
180
  def on_progress(&block)
199
- require_feature!(:progress_tracking)
200
- if alive?
201
- @adapter.set_progress_tracking(enabled: true)
202
- end
203
-
204
181
  @on[:progress] = block
205
182
  self
206
183
  end
@@ -210,7 +187,6 @@ module RubyLLM
210
187
  end
211
188
 
212
189
  def on_human_in_the_loop(&block)
213
- require_feature!(:human_in_the_loop)
214
190
  @on[:human_in_the_loop] = block
215
191
  self
216
192
  end
@@ -224,10 +200,9 @@ module RubyLLM
224
200
  end
225
201
 
226
202
  def on_logging(level: Logging::WARNING, &block)
227
- require_feature!(:logging)
228
203
  @on_logging_level = level
229
204
  if alive?
230
- @adapter.set_logging(level: level)
205
+ @coordinator.set_logging(level: level)
231
206
  end
232
207
 
233
208
  @on[:logging] = block
@@ -239,7 +214,6 @@ module RubyLLM
239
214
  end
240
215
 
241
216
  def on_sampling(&block)
242
- require_feature!(:sampling)
243
217
  @on[:sampling] = block
244
218
  self
245
219
  end
@@ -249,7 +223,6 @@ module RubyLLM
249
223
  end
250
224
 
251
225
  def on_elicitation(&block)
252
- require_feature!(:elicitation)
253
226
  @on[:elicitation] = block
254
227
  self
255
228
  end
@@ -278,55 +251,10 @@ module RubyLLM
278
251
 
279
252
  private
280
253
 
281
- # Get OAuth provider from adapter's transport if available
282
- # @return [OAuthProvider, BrowserOAuthProvider, nil] OAuth provider or nil
283
- def transport_oauth_provider
284
- return nil unless @adapter
285
-
286
- # For RubyLLMAdapter
287
- if @adapter.respond_to?(:native_client)
288
- transport = @adapter.native_client.transport
289
- transport_protocol = transport.transport_protocol
290
- return transport_protocol.oauth_provider if transport_protocol.respond_to?(:oauth_provider)
291
- end
292
-
293
- # For MCPSdkAdapter with wrapped transports
294
- if @adapter.respond_to?(:mcp_client) && @adapter.instance_variable_get(:@mcp_client)
295
- mcp_client = @adapter.instance_variable_get(:@mcp_client)
296
- if mcp_client&.transport.respond_to?(:native_transport)
297
- return mcp_client.transport.native_transport.oauth_provider
298
- end
299
- end
300
-
301
- nil
302
- end
303
-
304
- def build_adapter
305
- case @adapter_type
306
- when :ruby_llm
307
- RubyLLM::MCP::Adapters::RubyLLMAdapter.new(self,
308
- transport_type: @transport_type,
309
- config: @config)
310
- when :mcp_sdk
311
- RubyLLM::MCP::Adapters::MCPSdkAdapter.new(self,
312
- transport_type: @transport_type,
313
- config: @config)
314
- else
315
- raise ArgumentError, "Unknown adapter type: #{@adapter_type}"
316
- end
317
- end
318
-
319
- def require_feature!(feature)
320
- unless @adapter.supports?(feature)
321
- raise Errors::UnsupportedFeature.new(
322
- message: <<~MSG.strip
323
- Feature '#{feature}' is not supported by the #{@adapter_type} adapter.
324
-
325
- This feature requires the :ruby_llm adapter.
326
- Change your configuration to use adapter: :ruby_llm
327
- MSG
328
- )
329
- end
254
+ def setup_coordinator
255
+ Coordinator.new(self,
256
+ transport_type: @transport_type,
257
+ config: @config)
330
258
  end
331
259
 
332
260
  def fetch(cache_key, refresh)
@@ -340,9 +268,9 @@ module RubyLLM
340
268
  def build_map(raw_data, klass, with_prefix: false)
341
269
  raw_data.each_with_object({}) do |item, acc|
342
270
  instance = if with_prefix
343
- klass.new(@adapter, item, with_prefix: @with_prefix)
271
+ klass.new(@coordinator, item, with_prefix: @with_prefix)
344
272
  else
345
- klass.new(@adapter, item)
273
+ klass.new(@coordinator, item)
346
274
  end
347
275
  acc[instance.name] = instance
348
276
  end
@@ -357,7 +285,7 @@ module RubyLLM
357
285
  end
358
286
 
359
287
  def setup_roots
360
- @roots = Roots.new(paths: MCP.config.roots, adapter: @adapter)
288
+ @roots = Roots.new(paths: MCP.config.roots, coordinator: @coordinator)
361
289
  end
362
290
 
363
291
  def setup_sampling
@@ -365,32 +293,17 @@ module RubyLLM
365
293
  end
366
294
 
367
295
  def setup_event_handlers
368
- # Only setup handlers that are supported
369
- if @adapter.supports?(:progress_tracking)
370
- @on[:progress] = MCP.config.on_progress
371
- if @on[:progress] && alive?
372
- @adapter.set_progress_tracking(enabled: true)
373
- end
374
- end
375
-
376
- if @adapter.supports?(:human_in_the_loop)
377
- @on[:human_in_the_loop] = MCP.config.on_human_in_the_loop
378
- end
379
-
380
- if @adapter.supports?(:logging)
381
- @on[:logging] = MCP.config.on_logging
382
- @on_logging_level = MCP.config.on_logging_level
383
- end
384
-
385
- if @adapter.supports?(:elicitation)
386
- @on[:elicitation] = MCP.config.on_elicitation
387
- end
296
+ @on[:progress] = MCP.config.on_progress
297
+ @on[:human_in_the_loop] = MCP.config.on_human_in_the_loop
298
+ @on[:logging] = MCP.config.on_logging
299
+ @on_logging_level = MCP.config.on_logging_level
300
+ @on[:elicitation] = MCP.config.on_elicitation
388
301
  end
389
302
 
390
303
  # Get or create OAuth storage shared with transport
391
304
  def oauth_storage
392
305
  # Try to get storage from transport's OAuth provider
393
- transport_oauth = transport_oauth_provider
306
+ transport_oauth = @coordinator.transport_oauth_provider
394
307
  return transport_oauth.storage if transport_oauth
395
308
 
396
309
  # Create new storage shared with client
@@ -1,52 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+ require "yaml"
5
+ require "erb"
6
+
3
7
  module RubyLLM
4
8
  module MCP
5
9
  class Configuration
6
- class AdapterConfig
7
- VALID_ADAPTERS = %i[ruby_llm mcp_sdk].freeze
8
- VALID_TRANSPORTS = %i[stdio sse streamable streamable_http http].freeze
9
-
10
- attr_accessor :default_adapter
11
-
12
- def initialize
13
- @default_adapter = :ruby_llm
14
- end
15
-
16
- def validate!(adapter:, transport:)
17
- validate_adapter!(adapter)
18
- validate_transport!(transport)
19
- validate_adapter_transport_combination!(adapter, transport)
20
- end
21
-
22
- def adapter_for(config)
23
- config[:sdk] || config[:adapter] || @default_adapter
24
- end
25
-
26
- private
27
-
28
- def validate_adapter!(adapter)
29
- unless VALID_ADAPTERS.include?(adapter)
30
- raise Errors::AdapterConfigurationError.new(
31
- message: "Invalid adapter '#{adapter}'. Valid options: #{VALID_ADAPTERS.join(', ')}"
32
- )
33
- end
34
- end
35
-
36
- def validate_transport!(transport)
37
- unless VALID_TRANSPORTS.include?(transport)
38
- raise Errors::AdapterConfigurationError.new(
39
- message: "Invalid transport '#{transport}'. Valid options: #{VALID_TRANSPORTS.join(', ')}"
40
- )
41
- end
42
- end
43
-
44
- def validate_adapter_transport_combination!(adapter, transport)
45
- # SSE is supported by both ruby_llm and mcp_sdk adapters
46
- # No validation needed at this time
47
- end
48
- end
49
-
50
10
  class Sampling
51
11
  attr_accessor :enabled
52
12
  attr_writer :preferred_model
@@ -165,7 +125,6 @@ module RubyLLM
165
125
  :config_path,
166
126
  :launch_control,
167
127
  :on_logging_level,
168
- :adapter_config,
169
128
  :oauth
170
129
 
171
130
  attr_writer :logger, :mcp_configuration
@@ -174,7 +133,6 @@ module RubyLLM
174
133
 
175
134
  def initialize
176
135
  @sampling = Sampling.new
177
- @adapter_config = AdapterConfig.new
178
136
  @oauth = OAuth.new
179
137
  set_defaults
180
138
  end
@@ -191,20 +149,8 @@ module RubyLLM
191
149
  )
192
150
  end
193
151
 
194
- # Convenience method for setting default adapter
195
- def default_adapter=(adapter)
196
- @adapter_config.default_adapter = adapter
197
- end
198
-
199
- def default_adapter
200
- @adapter_config.default_adapter
201
- end
202
-
203
- # Validate MCP configuration before use
204
152
  def mcp_configuration
205
- configs = @mcp_configuration + load_mcps_config
206
- validate_configurations!(configs)
207
- configs
153
+ @mcp_configuration + load_mcps_config
208
154
  end
209
155
 
210
156
  def on_progress(&block)
@@ -247,20 +193,6 @@ module RubyLLM
247
193
 
248
194
  private
249
195
 
250
- def validate_configurations!(configs)
251
- configs.each do |config|
252
- adapter = @adapter_config.adapter_for(config)
253
- transport = config[:transport_type]
254
- # Convert string to symbol if needed
255
- transport = transport.to_sym if transport.is_a?(String)
256
-
257
- @adapter_config.validate!(
258
- adapter: adapter,
259
- transport: transport
260
- )
261
- end
262
- end
263
-
264
196
  def load_mcps_config
265
197
  @config_file ||= ConfigFile.new(config_path)
266
198
  @config_file.parse
@@ -290,7 +222,7 @@ module RubyLLM
290
222
  @roots = []
291
223
 
292
224
  # Protocol configuration
293
- @protocol_version = Native::Protocol.latest_version
225
+ @protocol_version = Protocol.latest_version
294
226
 
295
227
  # OAuth configuration
296
228
  @oauth = OAuth.new