ruby_llm-mcp 0.7.1 → 0.8.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/lib/generators/ruby_llm/mcp/{install_generator.rb → install/install_generator.rb} +4 -2
- 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/auth/browser/callback_handler.rb +71 -0
- data/lib/ruby_llm/mcp/auth/browser/callback_server.rb +30 -0
- data/lib/ruby_llm/mcp/auth/browser/http_server.rb +115 -0
- data/lib/ruby_llm/mcp/auth/browser/opener.rb +41 -0
- data/lib/ruby_llm/mcp/auth/browser/pages.rb +539 -0
- data/lib/ruby_llm/mcp/auth/browser_oauth_provider.rb +254 -0
- data/lib/ruby_llm/mcp/auth/client_registrar.rb +170 -0
- data/lib/ruby_llm/mcp/auth/discoverer.rb +124 -0
- data/lib/ruby_llm/mcp/auth/flows/authorization_code_flow.rb +105 -0
- data/lib/ruby_llm/mcp/auth/flows/client_credentials_flow.rb +66 -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 +65 -0
- data/lib/ruby_llm/mcp/auth/memory_storage.rb +72 -0
- data/lib/ruby_llm/mcp/auth/oauth_provider.rb +226 -0
- data/lib/ruby_llm/mcp/auth/security.rb +44 -0
- data/lib/ruby_llm/mcp/auth/session_manager.rb +56 -0
- data/lib/ruby_llm/mcp/auth/token_manager.rb +236 -0
- data/lib/ruby_llm/mcp/auth/url_builder.rb +78 -0
- data/lib/ruby_llm/mcp/auth.rb +359 -0
- data/lib/ruby_llm/mcp/client.rb +49 -0
- data/lib/ruby_llm/mcp/configuration.rb +39 -13
- data/lib/ruby_llm/mcp/coordinator.rb +11 -0
- data/lib/ruby_llm/mcp/errors.rb +11 -0
- data/lib/ruby_llm/mcp/railtie.rb +2 -10
- data/lib/ruby_llm/mcp/tool.rb +1 -1
- data/lib/ruby_llm/mcp/transport.rb +94 -1
- data/lib/ruby_llm/mcp/transports/sse.rb +116 -22
- data/lib/ruby_llm/mcp/transports/stdio.rb +4 -3
- data/lib/ruby_llm/mcp/transports/streamable_http.rb +81 -79
- data/lib/ruby_llm/mcp/version.rb +1 -1
- data/lib/ruby_llm/mcp.rb +10 -4
- metadata +40 -5
- /data/lib/generators/ruby_llm/mcp/{templates → install/templates}/initializer.rb +0 -0
- /data/lib/generators/ruby_llm/mcp/{templates → install/templates}/mcps.yml +0 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
<div class="mcp-connections-container">
|
|
2
|
+
|
|
3
|
+
<%= link_to "← Back to Connections", mcp_connections_path, class: "mcp-btn mcp-btn-secondary" %>
|
|
4
|
+
<div class="mcp-header">
|
|
5
|
+
<h1><%= @credential.name %></h1>
|
|
6
|
+
<div class="mcp-header-meta">
|
|
7
|
+
<code class="mcp-code"><%= @credential.server_url %></code>
|
|
8
|
+
<% if @credential.expired? %>
|
|
9
|
+
<span class="mcp-badge mcp-badge-danger">Expired</span>
|
|
10
|
+
<% elsif @credential.expires_soon? %>
|
|
11
|
+
<span class="mcp-badge mcp-badge-warning">Expiring Soon</span>
|
|
12
|
+
<% else %>
|
|
13
|
+
<span class="mcp-badge mcp-badge-success">Active</span>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<% if flash[:notice] %>
|
|
19
|
+
<div class="mcp-alert mcp-alert-success">
|
|
20
|
+
<%= flash[:notice] %>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
|
|
24
|
+
<% if flash[:alert] %>
|
|
25
|
+
<div class="mcp-alert mcp-alert-warning">
|
|
26
|
+
<%= flash[:alert] %>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
29
|
+
|
|
30
|
+
<div class="mcp-tools-section">
|
|
31
|
+
<h2>Available Tools (<%= @tools.count %>)</h2>
|
|
32
|
+
<% if @tools.any? %>
|
|
33
|
+
<div class="mcp-tools-list">
|
|
34
|
+
<% @tools.each do |tool| %>
|
|
35
|
+
<div class="mcp-tool-card">
|
|
36
|
+
<div class="mcp-tool-header">
|
|
37
|
+
<h3 class="mcp-tool-name"><%= tool.name %></h3>
|
|
38
|
+
<div class="mcp-tool-badges">
|
|
39
|
+
<% if tool.annotations&.read_only_hint %>
|
|
40
|
+
<span class="mcp-badge mcp-badge-info">Read Only</span>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% if tool.annotations&.destructive_hint %>
|
|
43
|
+
<span class="mcp-badge mcp-badge-warning">Destructive</span>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% if tool.annotations&.idempotent_hint %>
|
|
46
|
+
<span class="mcp-badge mcp-badge-success">Idempotent</span>
|
|
47
|
+
<% end %>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<% if tool.description.present? %>
|
|
52
|
+
<p class="mcp-tool-description"><%= tool.description %></p>
|
|
53
|
+
<% else %>
|
|
54
|
+
<p class="mcp-tool-description mcp-text-muted">No description available</p>
|
|
55
|
+
<% end %>
|
|
56
|
+
|
|
57
|
+
<% if tool.params_schema.present? && tool.params_schema["properties"]&.any? %>
|
|
58
|
+
<div class="mcp-tool-schema">
|
|
59
|
+
<h4>Input Parameters</h4>
|
|
60
|
+
<div class="mcp-parameters">
|
|
61
|
+
<% tool.params_schema["properties"].each do |param_name, param_info| %>
|
|
62
|
+
<div class="mcp-parameter">
|
|
63
|
+
<div class="mcp-parameter-header">
|
|
64
|
+
<code class="mcp-param-name"><%= param_name %></code>
|
|
65
|
+
<span class="mcp-param-type"><%= param_info["type"] %></span>
|
|
66
|
+
<% if tool.params_schema["required"]&.include?(param_name) %>
|
|
67
|
+
<span class="mcp-badge mcp-badge-required">Required</span>
|
|
68
|
+
<% end %>
|
|
69
|
+
</div>
|
|
70
|
+
<% if param_info["description"].present? %>
|
|
71
|
+
<p class="mcp-parameter-description"><%= param_info["description"] %></p>
|
|
72
|
+
<% end %>
|
|
73
|
+
<% if param_info["items"].present? %>
|
|
74
|
+
<div class="mcp-parameter-items">
|
|
75
|
+
<small class="mcp-text-muted">Items: <%= param_info["items"]["type"] %></small>
|
|
76
|
+
</div>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
<% end %>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<details class="mcp-schema-details">
|
|
83
|
+
<summary>View Full Schema</summary>
|
|
84
|
+
<pre class="mcp-schema-code"><%= JSON.pretty_generate(tool.params_schema) %></pre>
|
|
85
|
+
</details>
|
|
86
|
+
</div>
|
|
87
|
+
<% elsif tool.params_schema.present? %>
|
|
88
|
+
<div class="mcp-tool-schema">
|
|
89
|
+
<p class="mcp-text-muted"><em>No input parameters required</em></p>
|
|
90
|
+
</div>
|
|
91
|
+
<% end %>
|
|
92
|
+
</div>
|
|
93
|
+
<% end %>
|
|
94
|
+
</div>
|
|
95
|
+
<% else %>
|
|
96
|
+
<div class="mcp-alert mcp-alert-info">
|
|
97
|
+
<p>No tools available from this MCP server.</p>
|
|
98
|
+
</div>
|
|
99
|
+
<% end %>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<style>
|
|
104
|
+
/* MCP Tools Show Page - Self-contained styles */
|
|
105
|
+
.mcp-connections-container {
|
|
106
|
+
max-width: 1200px;
|
|
107
|
+
margin: 2rem auto;
|
|
108
|
+
padding: 0 1rem;
|
|
109
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
110
|
+
line-height: 1.6;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.mcp-header {
|
|
114
|
+
margin: 2rem 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.mcp-header h1 {
|
|
118
|
+
font-size: 2rem;
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
margin-bottom: 0.5rem;
|
|
121
|
+
color: #1a1a1a;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.mcp-header-meta {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: 1rem;
|
|
128
|
+
flex-wrap: wrap;
|
|
129
|
+
margin-top: 0.5rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.mcp-tools-section {
|
|
133
|
+
margin-top: 2rem;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.mcp-tools-section h2 {
|
|
137
|
+
font-size: 1.5rem;
|
|
138
|
+
font-weight: 600;
|
|
139
|
+
margin-bottom: 1.5rem;
|
|
140
|
+
color: #2a2a2a;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.mcp-tools-list {
|
|
144
|
+
display: flex;
|
|
145
|
+
flex-direction: column;
|
|
146
|
+
gap: 1.5rem;
|
|
147
|
+
margin-top: 1.5rem;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.mcp-tool-card {
|
|
151
|
+
background: white;
|
|
152
|
+
border: 1px solid #dee2e6;
|
|
153
|
+
border-radius: 8px;
|
|
154
|
+
padding: 1.5rem;
|
|
155
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
156
|
+
transition: box-shadow 0.2s ease;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.mcp-tool-card:hover {
|
|
160
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.mcp-tool-header {
|
|
164
|
+
margin-bottom: 1rem;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.mcp-tool-name {
|
|
168
|
+
font-size: 1.25rem;
|
|
169
|
+
font-weight: 600;
|
|
170
|
+
color: #007bff;
|
|
171
|
+
margin: 0 0 0.5rem 0;
|
|
172
|
+
word-break: break-word;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.mcp-tool-badges {
|
|
176
|
+
display: flex;
|
|
177
|
+
gap: 0.5rem;
|
|
178
|
+
flex-wrap: wrap;
|
|
179
|
+
margin-top: 0.5rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.mcp-tool-description {
|
|
183
|
+
font-size: 0.95rem;
|
|
184
|
+
color: #495057;
|
|
185
|
+
margin-bottom: 1rem;
|
|
186
|
+
line-height: 1.5;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.mcp-tool-schema {
|
|
190
|
+
margin-top: 1rem;
|
|
191
|
+
padding-top: 1rem;
|
|
192
|
+
border-top: 1px solid #dee2e6;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.mcp-tool-schema h4 {
|
|
196
|
+
font-size: 0.875rem;
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
color: #6c757d;
|
|
199
|
+
text-transform: uppercase;
|
|
200
|
+
letter-spacing: 0.05em;
|
|
201
|
+
margin-bottom: 0.5rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.mcp-schema-details {
|
|
205
|
+
margin-top: 0.5rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.mcp-schema-details summary {
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
font-size: 0.875rem;
|
|
211
|
+
color: #007bff;
|
|
212
|
+
padding: 0.5rem;
|
|
213
|
+
background-color: #f8f9fa;
|
|
214
|
+
border-radius: 4px;
|
|
215
|
+
user-select: none;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.mcp-schema-details summary:hover {
|
|
219
|
+
background-color: #e9ecef;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.mcp-schema-code {
|
|
223
|
+
margin-top: 0.5rem;
|
|
224
|
+
padding: 1rem;
|
|
225
|
+
background-color: #f5f5f5;
|
|
226
|
+
border: 1px solid #dee2e6;
|
|
227
|
+
border-radius: 4px;
|
|
228
|
+
overflow-x: auto;
|
|
229
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
230
|
+
font-size: 0.875rem;
|
|
231
|
+
line-height: 1.5;
|
|
232
|
+
color: #212529;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.mcp-parameters {
|
|
236
|
+
display: flex;
|
|
237
|
+
flex-direction: column;
|
|
238
|
+
gap: 1rem;
|
|
239
|
+
margin-bottom: 1rem;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.mcp-parameter {
|
|
243
|
+
padding: 0.75rem;
|
|
244
|
+
background-color: #f8f9fa;
|
|
245
|
+
border-radius: 4px;
|
|
246
|
+
border-left: 3px solid #007bff;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.mcp-parameter-header {
|
|
250
|
+
display: flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
gap: 0.5rem;
|
|
253
|
+
margin-bottom: 0.5rem;
|
|
254
|
+
flex-wrap: wrap;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.mcp-param-name {
|
|
258
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
259
|
+
font-size: 0.9rem;
|
|
260
|
+
font-weight: 600;
|
|
261
|
+
color: #007bff;
|
|
262
|
+
background-color: #e7f3ff;
|
|
263
|
+
padding: 0.25rem 0.5rem;
|
|
264
|
+
border-radius: 3px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.mcp-param-type {
|
|
268
|
+
font-size: 0.75rem;
|
|
269
|
+
color: #6c757d;
|
|
270
|
+
background-color: #e9ecef;
|
|
271
|
+
padding: 0.25rem 0.5rem;
|
|
272
|
+
border-radius: 3px;
|
|
273
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.mcp-parameter-description {
|
|
277
|
+
font-size: 0.875rem;
|
|
278
|
+
color: #495057;
|
|
279
|
+
margin: 0;
|
|
280
|
+
line-height: 1.5;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.mcp-parameter-items {
|
|
284
|
+
margin-top: 0.25rem;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* Badge styles */
|
|
288
|
+
.mcp-badge {
|
|
289
|
+
display: inline-block;
|
|
290
|
+
padding: 0.25rem 0.5rem;
|
|
291
|
+
font-size: 0.75rem;
|
|
292
|
+
font-weight: 600;
|
|
293
|
+
line-height: 1;
|
|
294
|
+
text-align: center;
|
|
295
|
+
white-space: nowrap;
|
|
296
|
+
border-radius: 4px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.mcp-badge-success {
|
|
300
|
+
background-color: #d4edda;
|
|
301
|
+
color: #155724;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.mcp-badge-warning {
|
|
305
|
+
background-color: #fff3cd;
|
|
306
|
+
color: #856404;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.mcp-badge-danger {
|
|
310
|
+
background-color: #f8d7da;
|
|
311
|
+
color: #721c24;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.mcp-badge-info {
|
|
315
|
+
background-color: #d1ecf1;
|
|
316
|
+
color: #0c5460;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.mcp-badge-required {
|
|
320
|
+
background-color: #f8d7da;
|
|
321
|
+
color: #721c24;
|
|
322
|
+
font-size: 0.7rem;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Button styles */
|
|
326
|
+
.mcp-btn {
|
|
327
|
+
display: inline-block;
|
|
328
|
+
padding: 0.5rem 1rem;
|
|
329
|
+
font-size: 0.875rem;
|
|
330
|
+
font-weight: 500;
|
|
331
|
+
line-height: 1.5;
|
|
332
|
+
text-align: center;
|
|
333
|
+
text-decoration: none;
|
|
334
|
+
border: 1px solid transparent;
|
|
335
|
+
border-radius: 4px;
|
|
336
|
+
cursor: pointer;
|
|
337
|
+
transition: all 0.15s ease-in-out;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.mcp-btn:hover {
|
|
341
|
+
opacity: 0.9;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.mcp-btn-secondary {
|
|
345
|
+
color: white;
|
|
346
|
+
background-color: #6c757d;
|
|
347
|
+
border-color: #6c757d;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.mcp-btn-secondary:hover {
|
|
351
|
+
background-color: #5a6268;
|
|
352
|
+
border-color: #5a6268;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Code styles */
|
|
356
|
+
.mcp-code {
|
|
357
|
+
background-color: #f5f5f5;
|
|
358
|
+
padding: 0.25rem 0.5rem;
|
|
359
|
+
border-radius: 3px;
|
|
360
|
+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
361
|
+
font-size: 0.875em;
|
|
362
|
+
color: #212529;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* Text utilities */
|
|
366
|
+
.mcp-text-muted {
|
|
367
|
+
color: #6c757d;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* Alert styles */
|
|
371
|
+
.mcp-alert {
|
|
372
|
+
padding: 1rem;
|
|
373
|
+
margin-bottom: 1rem;
|
|
374
|
+
border: 1px solid transparent;
|
|
375
|
+
border-radius: 4px;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.mcp-alert-info {
|
|
379
|
+
background-color: #d1ecf1;
|
|
380
|
+
border-color: #bee5eb;
|
|
381
|
+
color: #0c5460;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.mcp-alert-warning {
|
|
385
|
+
background-color: #fff3cd;
|
|
386
|
+
border-color: #ffeaa7;
|
|
387
|
+
color: #856404;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.mcp-alert-success {
|
|
391
|
+
background-color: #d4edda;
|
|
392
|
+
border-color: #c3e6cb;
|
|
393
|
+
color: #155724;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* Responsive */
|
|
397
|
+
@media (max-width: 768px) {
|
|
398
|
+
.mcp-connections-container {
|
|
399
|
+
padding: 0 0.5rem;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.mcp-header h1 {
|
|
403
|
+
font-size: 1.5rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.mcp-tool-card {
|
|
407
|
+
padding: 1rem;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/* Dark Mode Support */
|
|
412
|
+
@media (prefers-color-scheme: dark) {
|
|
413
|
+
body {
|
|
414
|
+
background-color: #1a1a1a;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.mcp-connections-container {
|
|
418
|
+
color: #e0e0e0;
|
|
419
|
+
background-color: #1a1a1a;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.mcp-header h1 {
|
|
423
|
+
color: #f0f0f0;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.mcp-tools-section h2 {
|
|
427
|
+
color: #f0f0f0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* Tool card dark mode */
|
|
431
|
+
.mcp-tool-card {
|
|
432
|
+
background: #2a2a2a;
|
|
433
|
+
border-color: #3a3a3a;
|
|
434
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.mcp-tool-card:hover {
|
|
438
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.mcp-tool-name {
|
|
442
|
+
color: #4dabf7;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.mcp-tool-description {
|
|
446
|
+
color: #b0b0b0;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* Tool schema dark mode */
|
|
450
|
+
.mcp-tool-schema {
|
|
451
|
+
border-top-color: #3a3a3a;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.mcp-tool-schema h4 {
|
|
455
|
+
color: #8a8a8a;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.mcp-schema-details summary {
|
|
459
|
+
background-color: #1a1a1a;
|
|
460
|
+
color: #4dabf7;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.mcp-schema-details summary:hover {
|
|
464
|
+
background-color: #333333;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.mcp-schema-code {
|
|
468
|
+
background-color: #1a1a1a;
|
|
469
|
+
border-color: #3a3a3a;
|
|
470
|
+
color: #e0e0e0;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* Parameter dark mode */
|
|
474
|
+
.mcp-parameter {
|
|
475
|
+
background-color: #1a1a1a;
|
|
476
|
+
border-left-color: #4dabf7;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.mcp-param-name {
|
|
480
|
+
color: #4dabf7;
|
|
481
|
+
background-color: #0d3a5c;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.mcp-param-type {
|
|
485
|
+
color: #8a8a8a;
|
|
486
|
+
background-color: #2a2a2a;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.mcp-parameter-description {
|
|
490
|
+
color: #b0b0b0;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* Badge dark mode */
|
|
494
|
+
.mcp-badge-success {
|
|
495
|
+
background-color: #1e4620;
|
|
496
|
+
color: #8fd98f;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.mcp-badge-warning {
|
|
500
|
+
background-color: #4a3a00;
|
|
501
|
+
color: #ffd966;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.mcp-badge-danger {
|
|
505
|
+
background-color: #4a1a1f;
|
|
506
|
+
color: #ff8a95;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.mcp-badge-info {
|
|
510
|
+
background-color: #0c3d47;
|
|
511
|
+
color: #9fe5f0;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.mcp-badge-required {
|
|
515
|
+
background-color: #4a1a1f;
|
|
516
|
+
color: #ff8a95;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* Button dark mode */
|
|
520
|
+
.mcp-btn-secondary {
|
|
521
|
+
background-color: #5a6268;
|
|
522
|
+
border-color: #5a6268;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.mcp-btn-secondary:hover {
|
|
526
|
+
background-color: #4e555b;
|
|
527
|
+
border-color: #4e555b;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/* Code dark mode */
|
|
531
|
+
.mcp-code {
|
|
532
|
+
background-color: #1a1a1a;
|
|
533
|
+
color: #e0e0e0;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/* Text utilities dark mode */
|
|
537
|
+
.mcp-text-muted {
|
|
538
|
+
color: #8a8a8a;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/* Alert dark mode */
|
|
542
|
+
.mcp-alert-info {
|
|
543
|
+
background-color: #0c3d47;
|
|
544
|
+
border-color: #0f5460;
|
|
545
|
+
color: #9fe5f0;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.mcp-alert-warning {
|
|
549
|
+
background-color: #4a3a00;
|
|
550
|
+
border-color: #665000;
|
|
551
|
+
color: #ffd966;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.mcp-alert-success {
|
|
555
|
+
background-color: #1e4620;
|
|
556
|
+
border-color: #2a5e2c;
|
|
557
|
+
color: #8fd98f;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
</style>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module MCP
|
|
5
|
+
module Auth
|
|
6
|
+
module Browser
|
|
7
|
+
# Handles OAuth callback request processing
|
|
8
|
+
# Extracts and validates OAuth parameters from callback requests
|
|
9
|
+
class CallbackHandler
|
|
10
|
+
attr_reader :callback_path, :logger
|
|
11
|
+
|
|
12
|
+
def initialize(callback_path:, logger: nil)
|
|
13
|
+
@callback_path = callback_path
|
|
14
|
+
@logger = logger || MCP.logger
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Validate that the request path matches the expected callback path
|
|
18
|
+
# @param path [String] request path
|
|
19
|
+
# @return [Boolean] true if path is valid
|
|
20
|
+
def valid_callback_path?(path)
|
|
21
|
+
uri_path, = path.split("?", 2)
|
|
22
|
+
uri_path == @callback_path
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Parse callback parameters from path
|
|
26
|
+
# @param path [String] full request path with query string
|
|
27
|
+
# @param http_server [HttpServer] HTTP server instance for parsing
|
|
28
|
+
# @return [Hash] parsed parameters
|
|
29
|
+
def parse_callback_params(path, http_server)
|
|
30
|
+
_, query_string = path.split("?", 2)
|
|
31
|
+
params = http_server.parse_query_params(query_string || "")
|
|
32
|
+
@logger.debug("Callback params: #{params.keys.join(', ')}")
|
|
33
|
+
params
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Extract OAuth parameters from parsed params
|
|
37
|
+
# @param params [Hash] parsed query parameters
|
|
38
|
+
# @return [Hash] OAuth parameters (code, state, error, error_description)
|
|
39
|
+
def extract_oauth_params(params)
|
|
40
|
+
{
|
|
41
|
+
code: params["code"],
|
|
42
|
+
state: params["state"],
|
|
43
|
+
error: params["error"],
|
|
44
|
+
error_description: params["error_description"]
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Update result hash with OAuth parameters (thread-safe)
|
|
49
|
+
# @param oauth_params [Hash] OAuth parameters
|
|
50
|
+
# @param result [Hash] result container
|
|
51
|
+
# @param mutex [Mutex] synchronization mutex
|
|
52
|
+
# @param condition [ConditionVariable] wait condition
|
|
53
|
+
def update_result_with_oauth_params(oauth_params, result, mutex, condition)
|
|
54
|
+
mutex.synchronize do
|
|
55
|
+
if oauth_params[:error]
|
|
56
|
+
result[:error] = oauth_params[:error_description] || oauth_params[:error]
|
|
57
|
+
elsif oauth_params[:code] && oauth_params[:state]
|
|
58
|
+
result[:code] = oauth_params[:code]
|
|
59
|
+
result[:state] = oauth_params[:state]
|
|
60
|
+
else
|
|
61
|
+
result[:error] = "Invalid callback: missing code or state parameter"
|
|
62
|
+
end
|
|
63
|
+
result[:completed] = true
|
|
64
|
+
condition.signal
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module MCP
|
|
5
|
+
module Auth
|
|
6
|
+
module Browser
|
|
7
|
+
# Callback server wrapper for clean shutdown
|
|
8
|
+
# Manages server lifecycle and thread coordination
|
|
9
|
+
class CallbackServer
|
|
10
|
+
def initialize(server, thread, stop_proc)
|
|
11
|
+
@server = server
|
|
12
|
+
@thread = thread
|
|
13
|
+
@stop_proc = stop_proc
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Shutdown server and cleanup resources
|
|
17
|
+
# @return [nil] always returns nil
|
|
18
|
+
def shutdown
|
|
19
|
+
@stop_proc.call
|
|
20
|
+
@server.close unless @server.closed?
|
|
21
|
+
@thread.join(5) # Wait max 5 seconds for thread to finish
|
|
22
|
+
rescue StandardError
|
|
23
|
+
# Ignore shutdown errors
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|