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 +4 -4
- data/README.md +26 -0
- data/lib/mcp_client/auth/oauth_provider.rb +29 -6
- data/lib/mcp_client/auth.rb +68 -8
- data/lib/mcp_client/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e56ef84106ab19bf3028b31dd7ce362cfb58f244743057271f14a83409153f0
|
|
4
|
+
data.tar.gz: 96f0e42875d80032c24056c1dbd9c6417b3db994538838736fa52f303e0d4f85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
426
|
+
scope: resolved_scope,
|
|
404
427
|
state: state,
|
|
405
428
|
code_challenge: pkce.code_challenge,
|
|
406
429
|
code_challenge_method: pkce.code_challenge_method,
|
data/lib/mcp_client/auth.rb
CHANGED
|
@@ -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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
data/lib/mcp_client/version.rb
CHANGED
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.
|
|
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-
|
|
11
|
+
date: 2026-03-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|