ruby_llm-mcp 0.7.0 → 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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/ruby_llm/mcp/{install_generator.rb → install/install_generator.rb} +4 -2
  3. data/lib/generators/ruby_llm/mcp/oauth/install_generator.rb +354 -0
  4. data/lib/generators/ruby_llm/mcp/oauth/templates/concerns/mcp_token_storage.rb.tt +114 -0
  5. data/lib/generators/ruby_llm/mcp/oauth/templates/concerns/user_mcp_oauth_concern.rb.tt +90 -0
  6. data/lib/generators/ruby_llm/mcp/oauth/templates/controllers/mcp_connections_controller.rb.tt +239 -0
  7. data/lib/generators/ruby_llm/mcp/oauth/templates/jobs/cleanup_expired_oauth_states_job.rb.tt +27 -0
  8. data/lib/generators/ruby_llm/mcp/oauth/templates/jobs/example_job.rb.tt +78 -0
  9. data/lib/generators/ruby_llm/mcp/oauth/templates/lib/mcp_client.rb.tt +68 -0
  10. data/lib/generators/ruby_llm/mcp/oauth/templates/migrations/create_mcp_oauth_credentials.rb.tt +19 -0
  11. data/lib/generators/ruby_llm/mcp/oauth/templates/migrations/create_mcp_oauth_states.rb.tt +21 -0
  12. data/lib/generators/ruby_llm/mcp/oauth/templates/models/mcp_oauth_credential.rb.tt +54 -0
  13. data/lib/generators/ruby_llm/mcp/oauth/templates/models/mcp_oauth_state.rb.tt +30 -0
  14. data/lib/generators/ruby_llm/mcp/oauth/templates/views/index.html.erb +646 -0
  15. data/lib/generators/ruby_llm/mcp/oauth/templates/views/show.html.erb +560 -0
  16. data/lib/ruby_llm/mcp/auth/browser/callback_handler.rb +71 -0
  17. data/lib/ruby_llm/mcp/auth/browser/callback_server.rb +30 -0
  18. data/lib/ruby_llm/mcp/auth/browser/http_server.rb +115 -0
  19. data/lib/ruby_llm/mcp/auth/browser/opener.rb +41 -0
  20. data/lib/ruby_llm/mcp/auth/browser/pages.rb +539 -0
  21. data/lib/ruby_llm/mcp/auth/browser_oauth_provider.rb +254 -0
  22. data/lib/ruby_llm/mcp/auth/client_registrar.rb +170 -0
  23. data/lib/ruby_llm/mcp/auth/discoverer.rb +124 -0
  24. data/lib/ruby_llm/mcp/auth/flows/authorization_code_flow.rb +105 -0
  25. data/lib/ruby_llm/mcp/auth/flows/client_credentials_flow.rb +66 -0
  26. data/lib/ruby_llm/mcp/auth/grant_strategies/authorization_code.rb +31 -0
  27. data/lib/ruby_llm/mcp/auth/grant_strategies/base.rb +31 -0
  28. data/lib/ruby_llm/mcp/auth/grant_strategies/client_credentials.rb +31 -0
  29. data/lib/ruby_llm/mcp/auth/http_response_handler.rb +65 -0
  30. data/lib/ruby_llm/mcp/auth/memory_storage.rb +72 -0
  31. data/lib/ruby_llm/mcp/auth/oauth_provider.rb +226 -0
  32. data/lib/ruby_llm/mcp/auth/security.rb +44 -0
  33. data/lib/ruby_llm/mcp/auth/session_manager.rb +56 -0
  34. data/lib/ruby_llm/mcp/auth/token_manager.rb +236 -0
  35. data/lib/ruby_llm/mcp/auth/url_builder.rb +78 -0
  36. data/lib/ruby_llm/mcp/auth.rb +359 -0
  37. data/lib/ruby_llm/mcp/client.rb +49 -0
  38. data/lib/ruby_llm/mcp/configuration.rb +39 -13
  39. data/lib/ruby_llm/mcp/coordinator.rb +11 -0
  40. data/lib/ruby_llm/mcp/errors.rb +11 -0
  41. data/lib/ruby_llm/mcp/railtie.rb +2 -10
  42. data/lib/ruby_llm/mcp/tool.rb +1 -1
  43. data/lib/ruby_llm/mcp/transport.rb +94 -1
  44. data/lib/ruby_llm/mcp/transports/sse.rb +116 -22
  45. data/lib/ruby_llm/mcp/transports/stdio.rb +82 -79
  46. data/lib/ruby_llm/mcp/transports/streamable_http.rb +81 -79
  47. data/lib/ruby_llm/mcp/version.rb +1 -1
  48. data/lib/ruby_llm/mcp.rb +10 -4
  49. metadata +40 -6
  50. /data/lib/generators/ruby_llm/mcp/{templates → install/templates}/initializer.rb +0 -0
  51. /data/lib/generators/ruby_llm/mcp/{templates → install/templates}/mcps.yml +0 -0
@@ -13,13 +13,14 @@ module RubyLLM
13
13
 
14
14
  attr_reader :command, :stdin, :stdout, :stderr, :id, :coordinator
15
15
 
16
- def initialize(command:, coordinator:, request_timeout:, args: [], env: {})
16
+ def initialize(command:, coordinator:, request_timeout:, options: {})
17
17
  @request_timeout = request_timeout
18
18
  @command = command
19
19
  @coordinator = coordinator
20
- @args = args
21
- @env = env || {}
20
+ @args = options[:args] || options["args"] || []
21
+ @env = options[:env] || options["env"] || {}
22
22
  @client_id = SecureRandom.uuid
23
+ # NOTE: Stdio transport doesn't use OAuth (local process communication)
23
24
 
24
25
  @id_counter = 0
25
26
  @id_mutex = Mutex.new
@@ -78,51 +79,23 @@ module RubyLLM
78
79
  @running = true
79
80
  end
80
81
 
81
- def close # rubocop:disable Metrics/MethodLength
82
+ def close
82
83
  @running = false
83
84
 
84
- begin
85
- @stdin&.close
86
- rescue StandardError
87
- nil
88
- end
89
-
90
- begin
91
- @wait_thread&.join(1)
92
- rescue StandardError
85
+ [@stdin, @stdout, @stderr].each do |stream|
86
+ stream&.close
87
+ rescue IOError, Errno::EBADF
93
88
  nil
94
89
  end
95
90
 
96
- begin
97
- @stdout&.close
98
- rescue StandardError
99
- nil
100
- end
101
-
102
- begin
103
- @stderr&.close
91
+ [@wait_thread, @reader_thread, @stderr_thread].each do |thread|
92
+ thread&.join(1)
104
93
  rescue StandardError
105
94
  nil
106
95
  end
107
96
 
108
- begin
109
- @reader_thread&.join(1)
110
- rescue StandardError
111
- nil
112
- end
113
-
114
- begin
115
- @stderr_thread&.join(1)
116
- rescue StandardError
117
- nil
118
- end
119
-
120
- @stdin = nil
121
- @stdout = nil
122
- @stderr = nil
123
- @wait_thread = nil
124
- @reader_thread = nil
125
- @stderr_thread = nil
97
+ @stdin = @stdout = @stderr = nil
98
+ @wait_thread = @reader_thread = @stderr_thread = nil
126
99
  end
127
100
 
128
101
  def set_protocol_version(version)
@@ -151,58 +124,88 @@ module RubyLLM
151
124
 
152
125
  def start_reader_thread
153
126
  @reader_thread = Thread.new do
154
- while @running
155
- begin
156
- if @stdout.closed? || @wait_thread.nil? || !@wait_thread.alive?
157
- sleep 1
158
- restart_process if @running
159
- next
160
- end
161
-
162
- line = @stdout.gets
163
- next unless line && !line.strip.empty?
164
-
165
- process_response(line.strip)
166
- rescue IOError, Errno::EPIPE => e
167
- RubyLLM::MCP.logger.error "Reader error: #{e.message}. Restarting in 1 second..."
168
- sleep 1
169
- restart_process if @running
170
- rescue StandardError => e
171
- RubyLLM::MCP.logger.error "Error in reader thread: #{e.message}, #{e.backtrace.join("\n")}"
172
- sleep 1
173
- end
174
- end
127
+ read_stdout_loop
175
128
  end
176
129
 
177
130
  @reader_thread.abort_on_exception = true
178
131
  end
179
132
 
133
+ def read_stdout_loop
134
+ while @running
135
+ begin
136
+ handle_stdout_read
137
+ rescue IOError, Errno::EPIPE => e
138
+ handle_stream_error(e, "Reader")
139
+ break unless @running
140
+ rescue StandardError => e
141
+ RubyLLM::MCP.logger.error "Error in reader thread: #{e.message}, #{e.backtrace.join("\n")}"
142
+ sleep 1
143
+ end
144
+ end
145
+ end
146
+
147
+ def handle_stdout_read
148
+ if @stdout.closed? || @wait_thread.nil? || !@wait_thread.alive?
149
+ if @running
150
+ sleep 1
151
+ restart_process
152
+ end
153
+ return
154
+ end
155
+
156
+ line = @stdout.gets
157
+ return unless line && !line.strip.empty?
158
+
159
+ process_response(line.strip)
160
+ end
161
+
162
+ def handle_stream_error(error, stream_name)
163
+ # Check @running to distinguish graceful shutdown from unexpected errors.
164
+ # During shutdown, streams are closed intentionally and shouldn't trigger restarts.
165
+ if @running
166
+ RubyLLM::MCP.logger.error "#{stream_name} error: #{error.message}. Restarting in 1 second..."
167
+ sleep 1
168
+ restart_process
169
+ else
170
+ # Graceful shutdown in progress
171
+ RubyLLM::MCP.logger.debug "#{stream_name} thread exiting during shutdown"
172
+ end
173
+ end
174
+
180
175
  def start_stderr_thread
181
176
  @stderr_thread = Thread.new do
182
- while @running
183
- begin
184
- if @stderr.closed? || @wait_thread.nil? || !@wait_thread.alive?
185
- sleep 1
186
- next
187
- end
188
-
189
- line = @stderr.gets
190
- next unless line && !line.strip.empty?
191
-
192
- RubyLLM::MCP.logger.info(line.strip)
193
- rescue IOError, Errno::EPIPE => e
194
- RubyLLM::MCP.logger.error "Stderr reader error: #{e.message}"
195
- sleep 1
196
- rescue StandardError => e
197
- RubyLLM::MCP.logger.error "Error in stderr thread: #{e.message}"
198
- sleep 1
199
- end
200
- end
177
+ read_stderr_loop
201
178
  end
202
179
 
203
180
  @stderr_thread.abort_on_exception = true
204
181
  end
205
182
 
183
+ def read_stderr_loop
184
+ while @running
185
+ begin
186
+ handle_stderr_read
187
+ rescue IOError, Errno::EPIPE => e
188
+ handle_stream_error(e, "Stderr reader")
189
+ break unless @running
190
+ rescue StandardError => e
191
+ RubyLLM::MCP.logger.error "Error in stderr thread: #{e.message}"
192
+ sleep 1
193
+ end
194
+ end
195
+ end
196
+
197
+ def handle_stderr_read
198
+ if @stderr.closed? || @wait_thread.nil? || !@wait_thread.alive?
199
+ sleep 1
200
+ return
201
+ end
202
+
203
+ line = @stderr.gets
204
+ return unless line && !line.strip.empty?
205
+
206
+ RubyLLM::MCP.logger.info(line.strip)
207
+ end
208
+
206
209
  def process_response(line)
207
210
  response = JSON.parse(line)
208
211
  request_id = response["id"]&.to_s
@@ -27,21 +27,6 @@ module RubyLLM
27
27
  end
28
28
  end
29
29
 
30
- class OAuthOptions
31
- attr_reader :issuer, :client_id, :client_secret, :scope
32
-
33
- def initialize(issuer:, client_id:, client_secret:, scopes:)
34
- @issuer = issuer
35
- @client_id = client_id
36
- @client_secret = client_secret
37
- @scope = scopes
38
- end
39
-
40
- def enabled?
41
- @issuer && @client_id && @client_secret && @scope
42
- end
43
- end
44
-
45
30
  # Options for starting SSE connections
46
31
  class StartSSEOptions
47
32
  attr_reader :resumption_token, :on_resumption_token, :replay_message_id
@@ -57,52 +42,52 @@ module RubyLLM
57
42
  class StreamableHTTP
58
43
  include Support::Timeout
59
44
 
60
- attr_reader :session_id, :protocol_version, :coordinator
61
-
62
- def initialize( # rubocop:disable Metrics/ParameterLists
63
- url:,
64
- request_timeout:,
65
- coordinator:,
66
- headers: {},
67
- reconnection: {},
68
- version: :http2,
69
- oauth: nil,
70
- rate_limit: nil,
71
- reconnection_options: nil,
72
- session_id: nil
73
- )
45
+ attr_reader :session_id, :protocol_version, :coordinator, :oauth_provider
46
+
47
+ def initialize(url:, request_timeout:, coordinator:, options: {})
74
48
  @url = URI(url)
75
49
  @coordinator = coordinator
76
50
  @request_timeout = request_timeout
77
- @headers = headers || {}
78
- @session_id = session_id
79
51
 
80
- @version = version
81
- @reconnection_options = reconnection_options || ReconnectionOptions.new
52
+ extract_options(options)
53
+ initialize_state_variables
54
+ initialize_mutexes
55
+
56
+ @connection = create_connection
57
+
58
+ RubyLLM::MCP.logger.debug "OAuth provider: #{@oauth_provider ? 'present' : 'none'}" if @oauth_provider
59
+ end
60
+
61
+ def extract_options(options)
62
+ @headers = options[:headers] || options["headers"] || {}
63
+ @session_id = options[:session_id] || options["session_id"]
64
+ @oauth_provider = options[:oauth_provider] || options["oauth_provider"]
65
+ @version = options[:version] || options["version"] || :http2
82
66
  @protocol_version = nil
83
- @session_id = session_id
84
67
 
85
- @resource_metadata_url = nil
86
- @client_id = SecureRandom.uuid
68
+ reconnection = options[:reconnection] || options["reconnection"] || {}
69
+ @reconnection_options = options[:reconnection_options] || ReconnectionOptions.new(**reconnection)
87
70
 
88
- @reconnection_options = ReconnectionOptions.new(**reconnection)
89
- @oauth_options = OAuthOptions.new(**oauth) unless oauth.nil?
71
+ rate_limit = options[:rate_limit] || options["rate_limit"]
90
72
  @rate_limiter = Support::RateLimiter.new(**rate_limit) if rate_limit
73
+ end
91
74
 
75
+ def initialize_state_variables
76
+ @resource_metadata_url = nil
77
+ @client_id = SecureRandom.uuid
92
78
  @id_counter = 0
93
- @id_mutex = Mutex.new
94
79
  @pending_requests = {}
95
- @pending_mutex = Mutex.new
96
80
  @running = true
97
81
  @abort_controller = nil
98
82
  @sse_thread = nil
99
- @sse_mutex = Mutex.new
100
-
101
- # Thread-safe collection of all HTTPX clients
102
83
  @clients = []
103
- @clients_mutex = Mutex.new
84
+ end
104
85
 
105
- @connection = create_connection
86
+ def initialize_mutexes
87
+ @id_mutex = Mutex.new
88
+ @pending_mutex = Mutex.new
89
+ @sse_mutex = Mutex.new
90
+ @clients_mutex = Mutex.new
106
91
  end
107
92
 
108
93
  def request(body, add_id: true, wait_for_response: true)
@@ -242,17 +227,6 @@ module RubyLLM
242
227
  }
243
228
  )
244
229
 
245
- if @oauth_options&.enabled?
246
- client = client.plugin(:oauth).oauth_auth(
247
- issuer: @oauth_options.issuer,
248
- client_id: @oauth_options.client_id,
249
- client_secret: @oauth_options.client_secret,
250
- scope: @oauth_options.scope
251
- )
252
-
253
- client.with_access_token
254
- end
255
-
256
230
  register_client(client)
257
231
  end
258
232
 
@@ -262,7 +236,25 @@ module RubyLLM
262
236
  headers["mcp-session-id"] = @session_id if @session_id
263
237
  headers["mcp-protocol-version"] = @protocol_version if @protocol_version
264
238
  headers["X-CLIENT-ID"] = @client_id
265
- headers["Origin"] = @uri.to_s
239
+ headers["Origin"] = @url.to_s
240
+
241
+ # Apply OAuth authorization if available
242
+ if @oauth_provider
243
+ RubyLLM::MCP.logger.debug "OAuth provider present, attempting to get token..."
244
+ RubyLLM::MCP.logger.debug " Server URL: #{@oauth_provider.server_url}"
245
+
246
+ token = @oauth_provider.access_token
247
+ if token
248
+ headers["Authorization"] = token.to_header
249
+ RubyLLM::MCP.logger.debug "✓ Applied OAuth authorization header: #{token.to_header[0..30]}..."
250
+ else
251
+ RubyLLM::MCP.logger.warn "✗ OAuth provider present but no valid token available!"
252
+ RubyLLM::MCP.logger.warn " This means the token is not in storage or has expired"
253
+ RubyLLM::MCP.logger.warn " Check that authentication completed successfully"
254
+ end
255
+ else
256
+ RubyLLM::MCP.logger.debug "No OAuth provider configured for this transport"
257
+ end
266
258
 
267
259
  headers
268
260
  end
@@ -320,16 +312,6 @@ module RubyLLM
320
312
  }
321
313
  )
322
314
 
323
- if @oauth_options&.enabled?
324
- client = client.plugin(:oauth).oauth_auth(
325
- issuer: @oauth_options.issuer,
326
- client_id: @oauth_options.client_id,
327
- client_secret: @oauth_options.client_secret,
328
- scope: @oauth_options.scope
329
- )
330
-
331
- client.with_access_token
332
- end
333
315
  register_client(client)
334
316
  end
335
317
 
@@ -348,8 +330,12 @@ module RubyLLM
348
330
  handle_accepted_response(original_message)
349
331
  when 404
350
332
  handle_session_expired
351
- when 405, 401
352
- # TODO: Implement 401 handling this once we are adding authorization
333
+ when 401
334
+ raise Errors::AuthenticationRequiredError.new(
335
+ message: "OAuth authentication required. Server returned 401 Unauthorized.",
336
+ code: 401
337
+ )
338
+ when 405
353
339
  # Method not allowed - acceptable for some endpoints
354
340
  nil
355
341
  when 400...500
@@ -405,23 +391,38 @@ module RubyLLM
405
391
  end
406
392
 
407
393
  def handle_client_error(response)
394
+ response_body = response.respond_to?(:body) ? response.body.to_s : "Unknown error"
395
+ status_code = response.respond_to?(:status) ? response.status : "Unknown"
396
+
408
397
  begin
409
- # Safely access response body
410
- response_body = response.respond_to?(:body) ? response.body.to_s : "Unknown error"
411
398
  error_body = JSON.parse(response_body)
412
399
 
413
400
  if error_body.is_a?(Hash) && error_body["error"]
414
- error_message = error_body["error"]["message"] || error_body["error"]["code"]
401
+ error_message = error_body["error"]["message"] || error_body["error"]["code"] || error_body["error"].to_s
402
+
403
+ # If we still don't have a message, include the full error object
404
+ if error_message.to_s.strip.empty?
405
+ error_message = "Empty error (full response: #{response_body})"
406
+ end
415
407
 
416
408
  if error_message.to_s.downcase.include?("session")
417
409
  raise Errors::TransportError.new(
418
- code: response.status,
410
+ code: status_code,
419
411
  message: "Server error: #{error_message} (Current session ID: #{@session_id || 'none'})"
420
412
  )
421
413
  end
422
414
 
415
+ # Special handling for 403 Forbidden with OAuth
416
+ if status_code == 403 && @oauth_provider
417
+ raise Errors::TransportError.new(
418
+ code: status_code,
419
+ message: "Authorization failed (403 Forbidden): #{error_message}. \
420
+ Check token scope and resource permissions at #{@oauth_provider.server_url}."
421
+ )
422
+ end
423
+
423
424
  raise Errors::TransportError.new(
424
- code: response.status,
425
+ code: status_code,
425
426
  message: "Server error: #{error_message}"
426
427
  )
427
428
  end
@@ -429,10 +430,6 @@ module RubyLLM
429
430
  # Fall through to generic error
430
431
  end
431
432
 
432
- # Safely access response attributes
433
- response_body = response.respond_to?(:body) ? response.body.to_s : "Unknown error"
434
- status_code = response.respond_to?(:status) ? response.status : "Unknown"
435
-
436
433
  raise Errors::TransportError.new(
437
434
  code: status_code,
438
435
  message: "HTTP client error: #{status_code} - #{response_body}"
@@ -492,7 +489,12 @@ module RubyLLM
492
489
  # SSE stream established successfully
493
490
  RubyLLM::MCP.logger.debug "SSE stream established"
494
491
  # Response will be processed through callbacks
495
- when 405, 401
492
+ when 401
493
+ raise Errors::AuthenticationRequiredError.new(
494
+ message: "OAuth authentication required. Server returned 401 Unauthorized.",
495
+ code: 401
496
+ )
497
+ when 405
496
498
  # Server doesn't support SSE - this is acceptable
497
499
  RubyLLM::MCP.logger.info "Server does not support SSE streaming"
498
500
  nil
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLLM
4
4
  module MCP
5
- VERSION = "0.7.0"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
data/lib/ruby_llm/mcp.rb CHANGED
@@ -61,10 +61,10 @@ module RubyLLM
61
61
  tools.uniq(&:name)
62
62
  end
63
63
 
64
- def support_complex_parameters!
65
- warn "[DEPRECATION] RubyLLM::MCP.support_complex_parameters! is no longer needed " \
66
- "and will be removed in version 0.8.0"
67
- # No-op: Complex parameters are now supported by default
64
+ def mcp_configurations
65
+ config.mcp_configuration.each_with_object({}) do |config, acc|
66
+ acc[config[:name]] = config
67
+ end
68
68
  end
69
69
 
70
70
  def configure
@@ -92,5 +92,11 @@ loader.inflector.inflect("sse" => "SSE")
92
92
  loader.inflector.inflect("openai" => "OpenAI")
93
93
  loader.inflector.inflect("streamable_http" => "StreamableHTTP")
94
94
  loader.inflector.inflect("http_client" => "HTTPClient")
95
+ loader.inflector.inflect("oauth_provider" => "OAuthProvider")
96
+ loader.inflector.inflect("browser_oauth" => "BrowserOAuth")
97
+ loader.inflector.inflect("browser_oauth_provider" => "BrowserOAuthProvider")
98
+ loader.inflector.inflect("http_server" => "HttpServer")
99
+ loader.inflector.inflect("callback_handler" => "CallbackHandler")
100
+ loader.inflector.inflect("callback_server" => "CallbackServer")
95
101
 
96
102
  loader.setup
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Vice
@@ -78,12 +78,46 @@ extra_rdoc_files: []
78
78
  files:
79
79
  - LICENSE
80
80
  - README.md
81
- - lib/generators/ruby_llm/mcp/install_generator.rb
82
- - lib/generators/ruby_llm/mcp/templates/initializer.rb
83
- - lib/generators/ruby_llm/mcp/templates/mcps.yml
81
+ - lib/generators/ruby_llm/mcp/install/install_generator.rb
82
+ - lib/generators/ruby_llm/mcp/install/templates/initializer.rb
83
+ - lib/generators/ruby_llm/mcp/install/templates/mcps.yml
84
+ - lib/generators/ruby_llm/mcp/oauth/install_generator.rb
85
+ - lib/generators/ruby_llm/mcp/oauth/templates/concerns/mcp_token_storage.rb.tt
86
+ - lib/generators/ruby_llm/mcp/oauth/templates/concerns/user_mcp_oauth_concern.rb.tt
87
+ - lib/generators/ruby_llm/mcp/oauth/templates/controllers/mcp_connections_controller.rb.tt
88
+ - lib/generators/ruby_llm/mcp/oauth/templates/jobs/cleanup_expired_oauth_states_job.rb.tt
89
+ - lib/generators/ruby_llm/mcp/oauth/templates/jobs/example_job.rb.tt
90
+ - lib/generators/ruby_llm/mcp/oauth/templates/lib/mcp_client.rb.tt
91
+ - lib/generators/ruby_llm/mcp/oauth/templates/migrations/create_mcp_oauth_credentials.rb.tt
92
+ - lib/generators/ruby_llm/mcp/oauth/templates/migrations/create_mcp_oauth_states.rb.tt
93
+ - lib/generators/ruby_llm/mcp/oauth/templates/models/mcp_oauth_credential.rb.tt
94
+ - lib/generators/ruby_llm/mcp/oauth/templates/models/mcp_oauth_state.rb.tt
95
+ - lib/generators/ruby_llm/mcp/oauth/templates/views/index.html.erb
96
+ - lib/generators/ruby_llm/mcp/oauth/templates/views/show.html.erb
84
97
  - lib/ruby_llm/chat.rb
85
98
  - lib/ruby_llm/mcp.rb
86
99
  - lib/ruby_llm/mcp/attachment.rb
100
+ - lib/ruby_llm/mcp/auth.rb
101
+ - lib/ruby_llm/mcp/auth/browser/callback_handler.rb
102
+ - lib/ruby_llm/mcp/auth/browser/callback_server.rb
103
+ - lib/ruby_llm/mcp/auth/browser/http_server.rb
104
+ - lib/ruby_llm/mcp/auth/browser/opener.rb
105
+ - lib/ruby_llm/mcp/auth/browser/pages.rb
106
+ - lib/ruby_llm/mcp/auth/browser_oauth_provider.rb
107
+ - lib/ruby_llm/mcp/auth/client_registrar.rb
108
+ - lib/ruby_llm/mcp/auth/discoverer.rb
109
+ - lib/ruby_llm/mcp/auth/flows/authorization_code_flow.rb
110
+ - lib/ruby_llm/mcp/auth/flows/client_credentials_flow.rb
111
+ - lib/ruby_llm/mcp/auth/grant_strategies/authorization_code.rb
112
+ - lib/ruby_llm/mcp/auth/grant_strategies/base.rb
113
+ - lib/ruby_llm/mcp/auth/grant_strategies/client_credentials.rb
114
+ - lib/ruby_llm/mcp/auth/http_response_handler.rb
115
+ - lib/ruby_llm/mcp/auth/memory_storage.rb
116
+ - lib/ruby_llm/mcp/auth/oauth_provider.rb
117
+ - lib/ruby_llm/mcp/auth/security.rb
118
+ - lib/ruby_llm/mcp/auth/session_manager.rb
119
+ - lib/ruby_llm/mcp/auth/token_manager.rb
120
+ - lib/ruby_llm/mcp/auth/url_builder.rb
87
121
  - lib/ruby_llm/mcp/client.rb
88
122
  - lib/ruby_llm/mcp/completion.rb
89
123
  - lib/ruby_llm/mcp/configuration.rb
@@ -145,7 +179,7 @@ metadata:
145
179
  homepage_uri: https://www.rubyllm-mcp.com
146
180
  source_code_uri: https://github.com/patvice/ruby_llm-mcp
147
181
  changelog_uri: https://github.com/patvice/ruby_llm-mcp/commits/main
148
- documentation_uri: https://www.rubyllm-mcp.com
182
+ documentation_uri: https://www.rubyllm-mcp.com/guides/
149
183
  bug_tracker_uri: https://github.com/patvice/ruby_llm-mcp/issues
150
184
  rubygems_mfa_required: 'true'
151
185
  allowed_push_host: https://rubygems.org
@@ -163,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
197
  - !ruby/object:Gem::Version
164
198
  version: '0'
165
199
  requirements: []
166
- rubygems_version: 3.6.7
200
+ rubygems_version: 3.6.9
167
201
  specification_version: 4
168
202
  summary: A RubyLLM MCP Client
169
203
  test_files: []