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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5ee1e3f459d8555cc547963bd6470dfa16e14c0c57e788064e604084dc6909b
4
- data.tar.gz: 7b423a5ec9acd5a9554c3a6903bfde6536e30e9c5d5ab9c01aa484cab7bd0fa7
3
+ metadata.gz: 86b08066123fbc46a3f965206011a4cbec8f140a43434e665d70c2a01157c091
4
+ data.tar.gz: 8b7a44c842bdbcbdd48f8fade4f32b8fac318d65a74d11d4fe9702a6a264412f
5
5
  SHA512:
6
- metadata.gz: c144bc4598c9aaff5155f84e188a5b8adb8543caaea84c3d55b244eca0f98f3ed90de465233b2f0887e1231c65e32375b0ea6832fc8ee734693f602e610beada
7
- data.tar.gz: 5b7d3e98e3126921e49e34a9b9569dbabf21fa5125e6e5342bcaab25b367da38fb13abd751bc9c267f591501fbbdd0d712e07e1d6d393682d3a6cbdc409c725a
6
+ metadata.gz: 43457242405497bba19d364ce57ee2110062a09ba60e3613c3165febd345de02fec7143ea62f1227c4ef93bf07b991f2d8b266a317f3bb4f9ede03c9662ff5d3
7
+ data.tar.gz: 6dba674bd7b82b96e872cab2b11a453e5111973ac1c99f1c5b9a31d1d3330ba768b08ee9a5b804a5c5e335f1e071feae2b58048c2939afb700719fcc4c2d21e4
data/README.md CHANGED
@@ -15,13 +15,12 @@ Currently full support for MCP protocol version up to `2025-06-18`.
15
15
 
16
16
  ## RubyLLM::MCP Features
17
17
 
18
- - 🎛️ **Dual SDK Support** _(v0.8+)_: Choose between native full-featured implementation or official MCP SDK
19
- - 🔌 **Multiple Transport Types**: Streamable HTTP, STDIO, and SSE transports
18
+ - 🔌 **Multiple Transport Types**: Streamable HTTP, and STDIO and legacy SSE transports
20
19
  - 🛠️ **Tool Integration**: Automatically converts MCP tools into RubyLLM-compatible tools
21
20
  - 📄 **Resource Management**: Access and include MCP resources (files, data) and resource templates in conversations
22
21
  - 🎯 **Prompt Integration**: Use predefined MCP prompts with arguments for consistent interactions
23
- - 🎨 **Client Features**: Support for sampling, roots, progress tracking, human-in-the-loop, and elicitation
24
- - 🔧 **Enhanced Chat Interface**: Extended RubyLLM chat methods for seamless MCP integration
22
+ - 🎛️ **Client Features**: Support for sampling, roots, and elicitation
23
+ - 🎨 **Enhanced Chat Interface**: Extended RubyLLM chat methods for seamless MCP integration
25
24
  - 🔄 **Multiple Client Management**: Create and manage multiple MCP clients simultaneously for different servers and purposes
26
25
  - 📚 **Simple API**: Easy-to-use interface that integrates seamlessly with RubyLLM
27
26
 
@@ -49,46 +48,6 @@ Or install it yourself as:
49
48
  gem install ruby_llm-mcp
50
49
  ```
51
50
 
52
- ## Choosing an Adapter
53
-
54
- Starting with version 0.8.0, RubyLLM MCP supports multiple SDK adapters:
55
-
56
- ### RubyLLM Adapter (Default)
57
-
58
- The native implementation with full MCP protocol support:
59
-
60
- ```ruby
61
- client = RubyLLM::MCP.client(
62
- name: "server",
63
- adapter: :ruby_llm, # Default, can be omitted
64
- transport_type: :stdio,
65
- config: { command: "mcp-server" }
66
- )
67
- ```
68
-
69
- **Features**: All MCP features including SSE transport, sampling, roots, progress tracking, etc.
70
-
71
- ### MCP SDK Adapter
72
-
73
- The official Anthropic-maintained SDK:
74
-
75
- ```ruby
76
- # Add to Gemfile
77
- gem 'mcp', '~> 0.4'
78
-
79
- # Use in code
80
- client = RubyLLM::MCP.client(
81
- name: "server",
82
- adapter: :mcp_sdk,
83
- transport_type: :stdio,
84
- config: { command: "mcp-server" }
85
- )
86
- ```
87
-
88
- **Features**: Core MCP features (tools, resources, prompts). No SSE, sampling, or advanced features.
89
-
90
- See the [Adapters Guide](https://rubyllm-mcp.com/guides/adapters.html) for detailed comparison.
91
-
92
51
  ## Usage
93
52
 
94
53
  ### Basic Setup
@@ -2,11 +2,6 @@
2
2
 
3
3
  # Configure RubyLLM MCP
4
4
  RubyLLM::MCP.configure do |config|
5
- # Default SDK adapter to use (:ruby_llm or :mcp_sdk)
6
- # - :ruby_llm: Full-featured, supports all MCP features + extensions
7
- # - :mcp_sdk: Official SDK, limited features but maintained by Anthropic
8
- config.default_adapter = :ruby_llm
9
-
10
5
  # Request timeout in milliseconds
11
6
  config.request_timeout = 8000
12
7
 
@@ -22,35 +17,23 @@ RubyLLM::MCP.configure do |config|
22
17
  # Launch MCPs (:automatic, :manual)
23
18
  config.launch_control = :automatic
24
19
 
25
- # Configure roots for file system access (RubyLLM adapter only)
20
+ # Configure roots for file system access
26
21
  # config.roots = [
27
22
  # Rails.root.to_s
28
23
  # ]
29
24
 
30
- # Configure sampling (RubyLLM adapter only)
25
+ # Configure sampling (optional)
31
26
  config.sampling.enabled = false
32
27
 
33
28
  # Set preferred model for sampling
34
29
  # config.sampling.preferred_model do
30
+ # # Return the preferred model name
35
31
  # "claude-sonnet-4"
36
32
  # end
37
33
 
38
34
  # Set a guard for sampling
39
35
  # config.sampling.guard do
36
+ # # Return true to enable sampling, false to disable
40
37
  # Rails.env.development?
41
38
  # end
42
-
43
- # Event handlers (RubyLLM adapter only)
44
- # config.on_progress do |progress_token, progress, total|
45
- # # Handle progress updates
46
- # end
47
-
48
- # config.on_human_in_the_loop do |tool_name, arguments|
49
- # # Return true to allow, false to deny
50
- # true
51
- # end
52
-
53
- # config.on_logging do |level, logger_name, data|
54
- # # Handle logging from MCP servers
55
- # end
56
39
  end
@@ -1,9 +1,5 @@
1
1
  mcp_servers:
2
2
  filesystem:
3
- # SDK adapter to use (optional, defaults to config.default_adapter)
4
- # Options: ruby_llm, mcp_sdk
5
- # adapter: ruby_llm
6
-
7
3
  transport_type: stdio
8
4
  command: npx
9
5
  args:
@@ -11,19 +7,3 @@ mcp_servers:
11
7
  - "<%%= Rails.root %>"
12
8
  env: {}
13
9
  with_prefix: true
14
-
15
- # Example with MCP SDK (official)
16
- # weather:
17
- # adapter: mcp_sdk
18
- # transport_type: http
19
- # url: "https://api.example.com/mcp"
20
- # headers:
21
- # Authorization: "Bearer <%%= ENV['WEATHER_API_KEY'] %>"
22
-
23
- # Example with SSE (RubyLLM adapter only)
24
- # notifications:
25
- # adapter: ruby_llm
26
- # transport_type: sse
27
- # url: "https://api.example.com/mcp/sse"
28
- # headers:
29
- # Authorization: "Bearer <%%= ENV['API_KEY'] %>"
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi"
4
+ require "socket"
5
+
3
6
  module RubyLLM
4
7
  module MCP
5
8
  module Auth
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rbconfig"
4
+
3
5
  module RubyLLM
4
6
  module MCP
5
7
  module Auth
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi"
4
+
3
5
  module RubyLLM
4
6
  module MCP
5
7
  module Auth
@@ -86,37 +88,7 @@ module RubyLLM
86
88
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
87
89
  }
88
90
 
89
- /* Logo card in top right */
90
- .logo-card {
91
- position: fixed;
92
- top: 1rem;
93
- right: 1rem;
94
- background: var(--card-bg);
95
- border-radius: 0.75rem;
96
- padding: 0.625rem;
97
- box-shadow: 0 10px 25px rgba(0,0,0,0.2);
98
- display: flex;
99
- align-items: center;
100
- gap: 0.5rem;
101
- opacity: 0;
102
- transform: translateY(-20px);
103
- animation: slide-in 500ms ease-out 200ms forwards;
104
- }
105
- .logo {
106
- width: 32px;
107
- height: 32px;
108
- border-radius: 0.375rem;
109
- border: 1.5px solid var(--logo-border);
110
- flex-shrink: 0;
111
- }
112
- .logo-text {
113
- font-weight: 600;
114
- font-size: 0.875rem;
115
- color: var(--text-900);
116
- white-space: nowrap;
117
- }
118
-
119
- /* Main Card */
91
+ /* Card */
120
92
  .card {
121
93
  width: 100%;
122
94
  max-width: 32rem;
@@ -131,6 +103,17 @@ module RubyLLM
131
103
  animation: pop-in 500ms ease-out forwards;
132
104
  }
133
105
 
106
+ /* MCP logo + border */
107
+ .logo {
108
+ width: 120px;
109
+ height: 120px;
110
+ display: block;
111
+ margin: 0 auto 1.5rem;
112
+ border-radius: 1rem;
113
+ border: 2px solid var(--logo-border);
114
+ background: transparent;
115
+ }
116
+
134
117
  /* SVG check animation */
135
118
  .checkwrap {
136
119
  display: flex;
@@ -208,7 +191,6 @@ module RubyLLM
208
191
 
209
192
  /* Animations */
210
193
  @keyframes pop-in { to { opacity: 1; transform: scale(1); } }
211
- @keyframes slide-in { to { opacity: 1; transform: translateY(0); } }
212
194
  @keyframes draw-circle {
213
195
  0% { opacity: 0; stroke-dashoffset: 339.292; }
214
196
  20% { opacity: 1; }
@@ -223,23 +205,11 @@ module RubyLLM
223
205
 
224
206
  /* Motion-reduction respect */
225
207
  @media (prefers-reduced-motion: reduce) {
226
- .card, .logo-card, .circle, .tick, .content { animation: none !important; }
227
- .card, .logo-card { opacity: 1; transform: none; }
208
+ .card, .circle, .tick, .content { animation: none !important; }
209
+ .card { opacity: 1; transform: none; }
228
210
  .content { opacity: 1; transform: none; }
229
211
  }
230
212
 
231
- /* Responsive adjustments */
232
- @media (max-width: 640px) {
233
- .logo-card {
234
- top: 0.75rem;
235
- right: 0.75rem;
236
- padding: 0.5rem;
237
- gap: 0.375rem;
238
- }
239
- .logo { width: 28px; height: 28px; }
240
- .logo-text { font-size: 0.75rem; }
241
- }
242
-
243
213
  /* =========================
244
214
  Dark Mode (automatic)
245
215
  ========================= */
@@ -279,7 +249,7 @@ module RubyLLM
279
249
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#151417" />
280
250
  </head>
281
251
  <body>
282
- <div class="logo-card">
252
+ <main class="card" role="dialog" aria-labelledby="title" aria-describedby="desc">
283
253
  <img
284
254
  class="logo"
285
255
  src="https://www.rubyllm-mcp.com/assets/images/rubyllm-mcp-logo.svg"
@@ -287,10 +257,7 @@ module RubyLLM
287
257
  decoding="async"
288
258
  fetchpriority="high"
289
259
  />
290
- <span class="logo-text">RubyLLM::MCP</span>
291
- </div>
292
260
 
293
- <main class="card" role="dialog" aria-labelledby="title" aria-describedby="desc">
294
261
  <div class="checkwrap">
295
262
  <svg class="checkmark" viewBox="0 0 120 120" aria-hidden="true">
296
263
  <circle class="circle" cx="60" cy="60" r="54"></circle>
@@ -357,37 +324,7 @@ module RubyLLM
357
324
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
358
325
  }
359
326
 
360
- /* Logo card in top right */
361
- .logo-card {
362
- position: fixed;
363
- top: 1rem;
364
- right: 1rem;
365
- background: var(--card-bg);
366
- border-radius: 0.75rem;
367
- padding: 0.625rem;
368
- box-shadow: 0 10px 25px rgba(0,0,0,0.2);
369
- display: flex;
370
- align-items: center;
371
- gap: 0.5rem;
372
- opacity: 0;
373
- transform: translateY(-20px);
374
- animation: slide-in 500ms ease-out 200ms forwards;
375
- }
376
- .logo {
377
- width: 32px;
378
- height: 32px;
379
- border-radius: 0.375rem;
380
- border: 1.5px solid var(--logo-border);
381
- flex-shrink: 0;
382
- }
383
- .logo-text {
384
- font-weight: 600;
385
- font-size: 0.875rem;
386
- color: var(--text-900);
387
- white-space: nowrap;
388
- }
389
-
390
- /* Main Card */
327
+ /* Card */
391
328
  .card {
392
329
  width: 100%;
393
330
  max-width: 32rem;
@@ -402,6 +339,16 @@ module RubyLLM
402
339
  animation: pop-in 500ms ease-out forwards;
403
340
  }
404
341
 
342
+ /* MCP logo */
343
+ .logo {
344
+ width: 120px;
345
+ height: 120px;
346
+ display: block;
347
+ margin: 0 auto 1.5rem;
348
+ border-radius: 1rem;
349
+ border: 2px solid var(--logo-border);
350
+ }
351
+
405
352
  /* Animated error icon (circle + X) */
406
353
  .iconwrap {
407
354
  display: flex;
@@ -498,7 +445,6 @@ module RubyLLM
498
445
 
499
446
  /* Animations */
500
447
  @keyframes pop-in { to { opacity: 1; transform: scale(1); } }
501
- @keyframes slide-in { to { opacity: 1; transform: translateY(0); } }
502
448
  @keyframes draw-circle {
503
449
  0% { opacity: 0; stroke-dashoffset: 339.292; }
504
450
  20% { opacity: 1; }
@@ -512,23 +458,11 @@ module RubyLLM
512
458
  @keyframes rise-in { to { opacity: 1; transform: translateY(0); } }
513
459
 
514
460
  @media (prefers-reduced-motion: reduce) {
515
- .card, .logo-card, .circle, .x1, .x2, .content { animation: none !important; }
516
- .card, .logo-card { opacity: 1; transform: none; }
461
+ .card, .circle, .x1, .x2, .content { animation: none !important; }
462
+ .card { opacity: 1; transform: none; }
517
463
  .content { opacity: 1; transform: none; }
518
464
  }
519
465
 
520
- /* Responsive adjustments */
521
- @media (max-width: 640px) {
522
- .logo-card {
523
- top: 0.75rem;
524
- right: 0.75rem;
525
- padding: 0.5rem;
526
- gap: 0.375rem;
527
- }
528
- .logo { width: 28px; height: 28px; }
529
- .logo-text { font-size: 0.75rem; }
530
- }
531
-
532
466
  /* =========================
533
467
  Dark Mode (automatic)
534
468
  ========================= */
@@ -567,7 +501,8 @@ module RubyLLM
567
501
  </style>
568
502
  </head>
569
503
  <body>
570
- <div class="logo-card">
504
+ <main class="card" role="dialog" aria-labelledby="title" aria-describedby="desc">
505
+ <!-- MCP Logo -->
571
506
  <img
572
507
  class="logo"
573
508
  src="https://www.rubyllm-mcp.com/assets/images/rubyllm-mcp-logo.svg"
@@ -575,10 +510,7 @@ module RubyLLM
575
510
  decoding="async"
576
511
  fetchpriority="high"
577
512
  />
578
- <span class="logo-text">RubyLLM::MCP</span>
579
- </div>
580
513
 
581
- <main class="card" role="dialog" aria-labelledby="title" aria-describedby="desc">
582
514
  <!-- Animated Error Icon -->
583
515
  <div class="iconwrap" aria-hidden="true">
584
516
  <svg class="erroricon" viewBox="0 0 120 120">
@@ -1,5 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "browser/http_server"
4
+ require_relative "browser/callback_handler"
5
+ require_relative "browser/pages"
6
+ require_relative "browser/opener"
7
+ require_relative "browser/callback_server"
8
+
3
9
  module RubyLLM
4
10
  module MCP
5
11
  module Auth
@@ -155,38 +161,6 @@ module RubyLLM
155
161
  @oauth_provider.complete_authorization_flow(code, state)
156
162
  end
157
163
 
158
- # Handle authentication challenge with browser-based auth
159
- # @param www_authenticate [String, nil] WWW-Authenticate header value
160
- # @param resource_metadata_url [String, nil] Resource metadata URL from response
161
- # @param requested_scope [String, nil] Scope from WWW-Authenticate challenge
162
- # @return [Boolean] true if authentication was completed successfully
163
- def handle_authentication_challenge(www_authenticate: nil, resource_metadata_url: nil, requested_scope: nil)
164
- @logger.debug("BrowserOAuthProvider handling authentication challenge")
165
-
166
- # Try standard provider's automatic handling first (token refresh, client credentials)
167
- begin
168
- return @oauth_provider.handle_authentication_challenge(
169
- www_authenticate: www_authenticate,
170
- resource_metadata_url: resource_metadata_url,
171
- requested_scope: requested_scope
172
- )
173
- rescue Errors::AuthenticationRequiredError
174
- # Standard provider couldn't handle it - need interactive auth
175
- @logger.info("Automatic authentication failed, starting browser-based OAuth flow")
176
- end
177
-
178
- # Perform full browser-based authentication
179
- authenticate(auto_open_browser: true)
180
- true
181
- end
182
-
183
- # Parse WWW-Authenticate header (delegate to oauth_provider)
184
- # @param header [String] WWW-Authenticate header value
185
- # @return [Hash] parsed challenge information
186
- def parse_www_authenticate(header)
187
- @oauth_provider.parse_www_authenticate(header)
188
- end
189
-
190
164
  private
191
165
 
192
166
  # Validate and synchronize redirect_uri between this provider and oauth_provider
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module RubyLLM
4
6
  module MCP
5
7
  module Auth
@@ -12,7 +12,6 @@ module RubyLLM
12
12
  @server_metadata = {}
13
13
  @pkce_data = {}
14
14
  @state_data = {}
15
- @resource_metadata = {}
16
15
  end
17
16
 
18
17
  # Token storage
@@ -24,10 +23,6 @@ module RubyLLM
24
23
  @tokens[server_url] = token
25
24
  end
26
25
 
27
- def delete_token(server_url)
28
- @tokens.delete(server_url)
29
- end
30
-
31
26
  # Client registration storage
32
27
  def get_client_info(server_url)
33
28
  @client_infos[server_url]
@@ -71,19 +66,6 @@ module RubyLLM
71
66
  def delete_state(server_url)
72
67
  @state_data.delete(server_url)
73
68
  end
74
-
75
- # Resource metadata management
76
- def get_resource_metadata(server_url)
77
- @resource_metadata[server_url]
78
- end
79
-
80
- def set_resource_metadata(server_url, metadata)
81
- @resource_metadata[server_url] = metadata
82
- end
83
-
84
- def delete_resource_metadata(server_url)
85
- @resource_metadata.delete(server_url)
86
- end
87
69
  end
88
70
  end
89
71
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "httpx"
4
+ require "uri"
5
+
3
6
  module RubyLLM
4
7
  module MCP
5
8
  module Auth
@@ -150,88 +153,6 @@ module RubyLLM
150
153
  request.headers["Authorization"] = token.to_header
151
154
  end
152
155
 
153
- # Handle authentication challenge from server (401 response)
154
- # Attempts to refresh token or raises error if interactive auth required
155
- # @param www_authenticate [String, nil] WWW-Authenticate header value
156
- # @param resource_metadata_url [String, nil] Resource metadata URL from response
157
- # @param requested_scope [String, nil] Scope from WWW-Authenticate challenge
158
- # @return [Boolean] true if authentication was refreshed successfully
159
- # @raise [Errors::AuthenticationRequiredError] if interactive auth is required
160
- def handle_authentication_challenge(www_authenticate: nil, resource_metadata_url: nil, requested_scope: nil)
161
- logger.debug("Handling authentication challenge")
162
- logger.debug(" WWW-Authenticate: #{www_authenticate}") if www_authenticate
163
- logger.debug(" Resource metadata URL: #{resource_metadata_url}") if resource_metadata_url
164
- logger.debug(" Requested scope: #{requested_scope}") if requested_scope
165
-
166
- # Parse WWW-Authenticate header if provided
167
- final_requested_scope = requested_scope
168
- if www_authenticate
169
- challenge_info = parse_www_authenticate(www_authenticate)
170
- final_requested_scope ||= challenge_info[:scope]
171
- # NOTE: resource_metadata_url from challenge_info could be used for future discovery
172
- end
173
-
174
- # Update scope if server requested different scope
175
- if final_requested_scope && final_requested_scope != scope
176
- logger.debug("Updating scope from '#{scope}' to '#{final_requested_scope}'")
177
- self.scope = final_requested_scope
178
- end
179
-
180
- # Try to refresh existing token
181
- token = storage.get_token(server_url)
182
- if token&.refresh_token
183
- logger.debug("Attempting token refresh with existing refresh token")
184
- refreshed_token = refresh_token(token)
185
- return true if refreshed_token
186
- end
187
-
188
- # If we have client credentials, try that flow
189
- if grant_type == :client_credentials
190
- logger.debug("Attempting client credentials flow")
191
- begin
192
- new_token = client_credentials_flow(scope: requested_scope)
193
- return true if new_token
194
- rescue StandardError => e
195
- logger.warn("Client credentials flow failed: #{e.message}")
196
- end
197
- end
198
-
199
- # Cannot automatically authenticate - interactive auth required
200
- logger.warn("Cannot automatically authenticate - interactive authorization required")
201
- raise Errors::AuthenticationRequiredError.new(
202
- message: "OAuth authentication required. Token refresh failed and interactive authorization is needed."
203
- )
204
- end
205
-
206
- # Parse WWW-Authenticate header to extract challenge parameters
207
- # @param header [String] WWW-Authenticate header value
208
- # @return [Hash] parsed challenge information
209
- def parse_www_authenticate(header)
210
- result = {}
211
-
212
- # Example: Bearer realm="example", scope="mcp:read mcp:write", resource_metadata_url="https://..."
213
- if header =~ /Bearer\s+(.+)/i
214
- params = ::Regexp.last_match(1)
215
-
216
- # Extract scope
217
- if params =~ /scope="([^"]+)"/
218
- result[:scope] = ::Regexp.last_match(1)
219
- end
220
-
221
- # Extract resource metadata URL
222
- if params =~ /resource_metadata_url="([^"]+)"/
223
- result[:resource_metadata_url] = ::Regexp.last_match(1)
224
- end
225
-
226
- # Extract realm
227
- if params =~ /realm="([^"]+)"/
228
- result[:realm] = ::Regexp.last_match(1)
229
- end
230
- end
231
-
232
- result
233
- end
234
-
235
156
  private
236
157
 
237
158
  # Create HTTP client for OAuth requests
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom"
4
+
3
5
  module RubyLLM
4
6
  module MCP
5
7
  module Auth
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
4
+
3
5
  module RubyLLM
4
6
  module MCP
5
7
  module Auth