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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 86b08066123fbc46a3f965206011a4cbec8f140a43434e665d70c2a01157c091
|
|
4
|
+
data.tar.gz: 8b7a44c842bdbcbdd48f8fade4f32b8fac318d65a74d11d4fe9702a6a264412f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
-
|
|
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
|
-
-
|
|
24
|
-
-
|
|
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
|
|
20
|
+
# Configure roots for file system access
|
|
26
21
|
# config.roots = [
|
|
27
22
|
# Rails.root.to_s
|
|
28
23
|
# ]
|
|
29
24
|
|
|
30
|
-
# Configure sampling (
|
|
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,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
|
-
/*
|
|
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, .
|
|
227
|
-
.card
|
|
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
|
-
<
|
|
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
|
-
/*
|
|
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, .
|
|
516
|
-
.card
|
|
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
|
-
<
|
|
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
|
|
@@ -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
|