ruby-mcp-client 1.0.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7e861ecba26ec407886fbb6d169b85c78ecba0768552b512cbe6c9c1f6216ad
4
- data.tar.gz: d71bab971376849e2bf72e2598116087cdb4f072c556368471d1d9adfe1bd7b5
3
+ metadata.gz: 8e56ef84106ab19bf3028b31dd7ce362cfb58f244743057271f14a83409153f0
4
+ data.tar.gz: 96f0e42875d80032c24056c1dbd9c6417b3db994538838736fa52f303e0d4f85
5
5
  SHA512:
6
- metadata.gz: 20d61bad3b57193dbc1a8019b87ec85758c55f61edc7c37808e7f1bc3a4fb60f35532f694aa68334273da31245ef6541cddbf2c4d0dc720d122bfae09488b415
7
- data.tar.gz: e3e3ec6840cf41e0b18d6112a7ff602a798cdcb6108b1c75afd5ecaf68724adee2109dca6782e884e7b2db51d8dd8ef09fd414e20d3d7ec0c1bc072225e33427
6
+ metadata.gz: 48b7cd77ab406967bc4cd5fe79c5c1511a8eb2c9e4c9ebc27b31b8386e175a435d1a620ffcfa7871044188061b3f0718875d009f2a4ef7d030786ad11bcbde35
7
+ data.tar.gz: 11f845c21895b67d0a308267972936fcf07df042e33de6417b69ee486f1739a8768f25b698bbbad92fdbef3acf0e6ec42eaffa525896b9e77e50e86960c7daf8
data/README.md CHANGED
@@ -303,10 +303,36 @@ client = Anthropic::Client.new(access_token: ENV['ANTHROPIC_API_KEY'])
303
303
  # Use tools with Claude API
304
304
  ```
305
305
 
306
+ ### RubyLLM
307
+
308
+ ```ruby
309
+ require 'mcp_client'
310
+ require 'ruby_llm'
311
+
312
+ RubyLLM.configure { |c| c.openai_api_key = ENV['OPENAI_API_KEY'] }
313
+ mcp = MCPClient.connect('http://localhost:8931/mcp') # Playwright MCP
314
+
315
+ # Wrap each MCP tool as a RubyLLM tool
316
+ tools = mcp.list_tools.map do |t|
317
+ tool_name = t.name
318
+ Class.new(RubyLLM::Tool) do
319
+ description t.description
320
+ params t.schema
321
+ define_method(:name) { tool_name }
322
+ define_method(:execute) { |**args| mcp.call_tool(tool_name, args) }
323
+ end.new
324
+ end
325
+
326
+ chat = RubyLLM.chat(model: 'gpt-4o-mini')
327
+ tools.each { |tool| chat.with_tool(tool) }
328
+ response = chat.ask('Navigate to google.com and tell me the page title')
329
+ ```
330
+
306
331
  See `examples/` for complete implementations:
307
332
  - `ruby_openai_mcp.rb`, `openai_ruby_mcp.rb` - OpenAI integration
308
333
  - `ruby_anthropic_mcp.rb` - Anthropic integration
309
334
  - `gemini_ai_mcp.rb` - Google Vertex AI integration
335
+ - `ruby_llm_mcp.rb` - RubyLLM integration (OpenAI provider)
310
336
 
311
337
  ## OAuth 2.1 Authentication
312
338
 
@@ -14,7 +14,7 @@ module MCPClient
14
14
  # @!attribute [rw] redirect_uri
15
15
  # @return [String] OAuth redirect URI
16
16
  # @!attribute [rw] scope
17
- # @return [String, nil] OAuth scope
17
+ # @return [String, Symbol, nil] OAuth scope (use :all for all server-supported scopes)
18
18
  # @!attribute [rw] logger
19
19
  # @return [Logger] Logger instance
20
20
  # @!attribute [rw] storage
@@ -27,15 +27,19 @@ module MCPClient
27
27
  # Initialize OAuth provider
28
28
  # @param server_url [String] The MCP server URL (used as OAuth resource parameter)
29
29
  # @param redirect_uri [String] OAuth redirect URI (default: http://localhost:8080/callback)
30
- # @param scope [String, nil] OAuth scope
30
+ # @param scope [String, Symbol, nil] OAuth scope (use :all for all server-supported scopes)
31
31
  # @param logger [Logger, nil] Optional logger
32
32
  # @param storage [Object, nil] Storage backend for tokens and client info
33
- def initialize(server_url:, redirect_uri: 'http://localhost:8080/callback', scope: nil, logger: nil, storage: nil)
33
+ # @param client_metadata [Hash] Extra OIDC client metadata fields for DCR registration.
34
+ # Supported keys: :client_name, :client_uri, :logo_uri, :tos_uri, :policy_uri, :contacts
35
+ def initialize(server_url:, redirect_uri: 'http://localhost:8080/callback', scope: nil, logger: nil, storage: nil,
36
+ client_metadata: {})
34
37
  self.server_url = server_url
35
38
  self.redirect_uri = redirect_uri
36
39
  self.scope = scope
37
40
  self.logger = logger || Logger.new($stdout, level: Logger::WARN)
38
41
  self.storage = storage || MemoryStorage.new
42
+ @extra_client_metadata = client_metadata
39
43
  @http_client = create_http_client
40
44
  end
41
45
 
@@ -58,6 +62,14 @@ module MCPClient
58
62
  refresh_token(token) if token.refresh_token
59
63
  end
60
64
 
65
+ # Return the scopes supported by the authorization server
66
+ # Discovers server metadata and returns the scopes_supported list.
67
+ # @return [Array<String>] supported scopes, or empty array if not advertised
68
+ # @raise [MCPClient::Errors::ConnectionError] if server discovery fails
69
+ def supported_scopes
70
+ @supported_scopes ||= discover_authorization_server.scopes_supported || []
71
+ end
72
+
61
73
  # Start OAuth authorization flow
62
74
  # @return [String] Authorization URL to redirect user to
63
75
  # @raise [MCPClient::Errors::ConnectionError] if server discovery fails
@@ -328,12 +340,15 @@ module MCPClient
328
340
  def register_client(server_metadata)
329
341
  logger.debug("Registering OAuth client at: #{server_metadata.registration_endpoint}")
330
342
 
343
+ resolved_scope = scope == :all ? supported_scopes.join(' ') : scope
344
+
331
345
  metadata = ClientMetadata.new(
332
346
  redirect_uris: [redirect_uri],
333
347
  token_endpoint_auth_method: 'none', # Public client
334
348
  grant_types: %w[authorization_code refresh_token],
335
349
  response_types: ['code'],
336
- scope: scope
350
+ scope: resolved_scope,
351
+ **@extra_client_metadata
337
352
  )
338
353
 
339
354
  response = @http_client.post(server_metadata.registration_endpoint) do |req|
@@ -355,7 +370,13 @@ module MCPClient
355
370
  token_endpoint_auth_method: data['token_endpoint_auth_method'] || 'none',
356
371
  grant_types: data['grant_types'] || %w[authorization_code refresh_token],
357
372
  response_types: data['response_types'] || ['code'],
358
- scope: data['scope']
373
+ scope: data['scope'],
374
+ client_name: data['client_name'],
375
+ client_uri: data['client_uri'],
376
+ logo_uri: data['logo_uri'],
377
+ tos_uri: data['tos_uri'],
378
+ policy_uri: data['policy_uri'],
379
+ contacts: data['contacts']
359
380
  )
360
381
 
361
382
  # Warn if server changed redirect_uri
@@ -396,11 +417,13 @@ module MCPClient
396
417
  # Use the redirect_uri that was actually registered
397
418
  registered_redirect_uri = client_info.metadata.redirect_uris.first
398
419
 
420
+ resolved_scope = scope == :all ? supported_scopes.join(' ') : scope
421
+
399
422
  params = {
400
423
  response_type: 'code',
401
424
  client_id: client_info.client_id,
402
425
  redirect_uri: registered_redirect_uri,
403
- scope: scope,
426
+ scope: resolved_scope,
404
427
  state: state,
405
428
  code_challenge: pkce.code_challenge,
406
429
  code_challenge_method: pkce.code_challenge_method,
@@ -86,21 +86,38 @@ module MCPClient
86
86
 
87
87
  # OAuth client metadata for registration and authorization
88
88
  class ClientMetadata
89
- attr_reader :redirect_uris, :token_endpoint_auth_method, :grant_types, :response_types, :scope
89
+ attr_reader :redirect_uris, :token_endpoint_auth_method, :grant_types, :response_types, :scope,
90
+ :client_name, :client_uri, :logo_uri, :tos_uri, :policy_uri, :contacts
90
91
 
91
92
  # @param redirect_uris [Array<String>] List of valid redirect URIs
92
93
  # @param token_endpoint_auth_method [String] Authentication method for token endpoint
93
94
  # @param grant_types [Array<String>] Supported grant types
94
95
  # @param response_types [Array<String>] Supported response types
95
96
  # @param scope [String, nil] Requested scope
97
+ # @param client_name [String, nil] Human-readable client name
98
+ # @param client_uri [String, nil] URL of the client home page
99
+ # @param logo_uri [String, nil] URL of the client logo
100
+ # @param tos_uri [String, nil] URL of the client terms of service
101
+ # @param policy_uri [String, nil] URL of the client privacy policy
102
+ # @param contacts [Array<String>, nil] List of contact emails for the client
103
+ # rubocop:disable Metrics/ParameterLists
96
104
  def initialize(redirect_uris:, token_endpoint_auth_method: 'none',
97
105
  grant_types: %w[authorization_code refresh_token],
98
- response_types: ['code'], scope: nil)
106
+ response_types: ['code'], scope: nil,
107
+ client_name: nil, client_uri: nil, logo_uri: nil,
108
+ tos_uri: nil, policy_uri: nil, contacts: nil)
109
+ # rubocop:enable Metrics/ParameterLists
99
110
  @redirect_uris = redirect_uris
100
111
  @token_endpoint_auth_method = token_endpoint_auth_method
101
112
  @grant_types = grant_types
102
113
  @response_types = response_types
103
114
  @scope = scope
115
+ @client_name = client_name
116
+ @client_uri = client_uri
117
+ @logo_uri = logo_uri
118
+ @tos_uri = tos_uri
119
+ @policy_uri = policy_uri
120
+ @contacts = contacts
104
121
  end
105
122
 
106
123
  # Convert to hash for HTTP requests
@@ -111,7 +128,13 @@ module MCPClient
111
128
  token_endpoint_auth_method: @token_endpoint_auth_method,
112
129
  grant_types: @grant_types,
113
130
  response_types: @response_types,
114
- scope: @scope
131
+ scope: @scope,
132
+ client_name: @client_name,
133
+ client_uri: @client_uri,
134
+ logo_uri: @logo_uri,
135
+ tos_uri: @tos_uri,
136
+ policy_uri: @policy_uri,
137
+ contacts: @contacts
115
138
  }.compact
116
139
  end
117
140
  end
@@ -180,7 +203,13 @@ module MCPClient
180
203
  grant_types: metadata_data[:grant_types] || metadata_data['grant_types'] ||
181
204
  %w[authorization_code refresh_token],
182
205
  response_types: metadata_data[:response_types] || metadata_data['response_types'] || ['code'],
183
- scope: metadata_data[:scope] || metadata_data['scope']
206
+ scope: metadata_data[:scope] || metadata_data['scope'],
207
+ client_name: metadata_data[:client_name] || metadata_data['client_name'],
208
+ client_uri: metadata_data[:client_uri] || metadata_data['client_uri'],
209
+ logo_uri: metadata_data[:logo_uri] || metadata_data['logo_uri'],
210
+ tos_uri: metadata_data[:tos_uri] || metadata_data['tos_uri'],
211
+ policy_uri: metadata_data[:policy_uri] || metadata_data['policy_uri'],
212
+ contacts: metadata_data[:contacts] || metadata_data['contacts']
184
213
  )
185
214
  end
186
215
 
@@ -288,10 +317,41 @@ module MCPClient
288
317
  attr_reader :code_verifier, :code_challenge, :code_challenge_method
289
318
 
290
319
  # Generate PKCE parameters
291
- def initialize
292
- @code_verifier = generate_code_verifier
293
- @code_challenge = generate_code_challenge(@code_verifier)
294
- @code_challenge_method = 'S256'
320
+ # @param code_verifier [String, nil] Existing code verifier (for deserialization)
321
+ # @param code_challenge [String, nil] Existing code challenge (for deserialization)
322
+ # @param code_challenge_method [String] Challenge method (default: 'S256')
323
+ def initialize(code_verifier: nil, code_challenge: nil, code_challenge_method: nil)
324
+ @code_verifier = code_verifier || generate_code_verifier
325
+ @code_challenge = code_challenge || generate_code_challenge(@code_verifier)
326
+ @code_challenge_method = code_challenge_method || 'S256'
327
+ end
328
+
329
+ # Convert to hash for serialization
330
+ # @return [Hash] Hash representation
331
+ def to_h
332
+ {
333
+ code_verifier: @code_verifier,
334
+ code_challenge: @code_challenge,
335
+ code_challenge_method: @code_challenge_method
336
+ }
337
+ end
338
+
339
+ # Create PKCE instance from hash
340
+ # @param data [Hash] Hash with PKCE parameters (symbol or string keys)
341
+ # @return [PKCE] New PKCE instance
342
+ # @raise [ArgumentError] If required parameters are missing
343
+ # @note code_challenge_method is optional and defaults to 'S256'.
344
+ # The code_challenge is not re-validated against code_verifier;
345
+ # callers are expected to provide values from a prior to_h round-trip.
346
+ def self.from_h(data)
347
+ verifier = data[:code_verifier] || data['code_verifier']
348
+ challenge = data[:code_challenge] || data['code_challenge']
349
+ method = data[:code_challenge_method] || data['code_challenge_method']
350
+
351
+ raise ArgumentError, 'Missing code_verifier' unless verifier
352
+ raise ArgumentError, 'Missing code_challenge' unless challenge
353
+
354
+ new(code_verifier: verifier, code_challenge: challenge, code_challenge_method: method)
295
355
  end
296
356
 
297
357
  private
@@ -2,7 +2,7 @@
2
2
 
3
3
  module MCPClient
4
4
  # Current version of the MCP client gem
5
- VERSION = '1.0.0'
5
+ VERSION = '1.0.1'
6
6
 
7
7
  # MCP protocol version (date-based) - unified across all transports
8
8
  PROTOCOL_VERSION = '2025-11-25'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-mcp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Szymon Kurcab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-15 00:00:00.000000000 Z
11
+ date: 2026-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday