ruby-mcp-client 0.7.3 → 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 +257 -13
- data/lib/mcp_client/client.rb +225 -6
- data/lib/mcp_client/errors.rb +18 -0
- data/lib/mcp_client/prompt.rb +41 -0
- data/lib/mcp_client/resource.rb +61 -0
- data/lib/mcp_client/resource_content.rb +80 -0
- data/lib/mcp_client/resource_template.rb +57 -0
- data/lib/mcp_client/server_base.rb +55 -0
- data/lib/mcp_client/server_http.rb +142 -0
- data/lib/mcp_client/server_sse/json_rpc_transport.rb +1 -0
- data/lib/mcp_client/server_sse.rb +217 -1
- data/lib/mcp_client/server_stdio/json_rpc_transport.rb +6 -0
- data/lib/mcp_client/server_stdio.rb +182 -0
- data/lib/mcp_client/server_streamable_http.rb +201 -0
- data/lib/mcp_client/version.rb +1 -1
- data/lib/mcp_client.rb +4 -0
- metadata +6 -2
@@ -99,6 +99,10 @@ module MCPClient
|
|
99
99
|
@read_timeout = opts[:read_timeout]
|
100
100
|
@tools = nil
|
101
101
|
@tools_data = nil
|
102
|
+
@prompts = nil
|
103
|
+
@prompts_data = nil
|
104
|
+
@resources = nil
|
105
|
+
@resources_data = nil
|
102
106
|
@request_id = 0
|
103
107
|
@mutex = Monitor.new
|
104
108
|
@connection_established = false
|
@@ -211,6 +215,149 @@ module MCPClient
|
|
211
215
|
end
|
212
216
|
end
|
213
217
|
|
218
|
+
# List all prompts available from the MCP server
|
219
|
+
# @return [Array<MCPClient::Prompt>] list of available prompts
|
220
|
+
# @raise [MCPClient::Errors::PromptGetError] if prompts list retrieval fails
|
221
|
+
def list_prompts
|
222
|
+
@mutex.synchronize do
|
223
|
+
return @prompts if @prompts
|
224
|
+
end
|
225
|
+
|
226
|
+
begin
|
227
|
+
ensure_connected
|
228
|
+
|
229
|
+
prompts_data = request_prompts_list
|
230
|
+
@mutex.synchronize do
|
231
|
+
@prompts = prompts_data.map do |prompt_data|
|
232
|
+
MCPClient::Prompt.from_json(prompt_data, server: self)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
@mutex.synchronize { @prompts }
|
237
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
|
238
|
+
# Re-raise these errors directly
|
239
|
+
raise
|
240
|
+
rescue StandardError => e
|
241
|
+
raise MCPClient::Errors::PromptGetError, "Error listing prompts: #{e.message}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Get a prompt with the given parameters
|
246
|
+
# @param prompt_name [String] the name of the prompt to get
|
247
|
+
# @param parameters [Hash] the parameters to pass to the prompt
|
248
|
+
# @return [Object] the result of the prompt (with string keys for backward compatibility)
|
249
|
+
# @raise [MCPClient::Errors::PromptGetError] if prompt retrieval fails
|
250
|
+
def get_prompt(prompt_name, parameters)
|
251
|
+
rpc_request('prompts/get', {
|
252
|
+
name: prompt_name,
|
253
|
+
arguments: parameters.except(:_meta),
|
254
|
+
**parameters.slice(:_meta)
|
255
|
+
})
|
256
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
|
257
|
+
# Re-raise connection/transport errors directly
|
258
|
+
raise
|
259
|
+
rescue StandardError => e
|
260
|
+
# For all other errors, wrap in PromptGetError
|
261
|
+
raise MCPClient::Errors::PromptGetError, "Error getting prompt '#{prompt_name}': #{e.message}"
|
262
|
+
end
|
263
|
+
|
264
|
+
# List all resources available from the MCP server
|
265
|
+
# @param cursor [String, nil] optional cursor for pagination
|
266
|
+
# @return [Hash] result containing resources array and optional nextCursor
|
267
|
+
# @raise [MCPClient::Errors::ResourceReadError] if resources list retrieval fails
|
268
|
+
def list_resources(cursor: nil)
|
269
|
+
@mutex.synchronize do
|
270
|
+
return @resources_result if @resources_result && !cursor
|
271
|
+
end
|
272
|
+
|
273
|
+
begin
|
274
|
+
ensure_connected
|
275
|
+
|
276
|
+
params = {}
|
277
|
+
params['cursor'] = cursor if cursor
|
278
|
+
result = rpc_request('resources/list', params)
|
279
|
+
|
280
|
+
resources = (result['resources'] || []).map do |resource_data|
|
281
|
+
MCPClient::Resource.from_json(resource_data, server: self)
|
282
|
+
end
|
283
|
+
|
284
|
+
resources_result = { 'resources' => resources, 'nextCursor' => result['nextCursor'] }
|
285
|
+
|
286
|
+
@mutex.synchronize do
|
287
|
+
@resources_result = resources_result unless cursor
|
288
|
+
end
|
289
|
+
|
290
|
+
resources_result
|
291
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
|
292
|
+
# Re-raise these errors directly
|
293
|
+
raise
|
294
|
+
rescue StandardError => e
|
295
|
+
raise MCPClient::Errors::ResourceReadError, "Error listing resources: #{e.message}"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# Read a resource by its URI
|
300
|
+
# @param uri [String] the URI of the resource to read
|
301
|
+
# @return [Array<MCPClient::ResourceContent>] array of resource contents
|
302
|
+
# @raise [MCPClient::Errors::ResourceReadError] if resource reading fails
|
303
|
+
def read_resource(uri)
|
304
|
+
result = rpc_request('resources/read', { uri: uri })
|
305
|
+
contents = result['contents'] || []
|
306
|
+
contents.map { |content| MCPClient::ResourceContent.from_json(content) }
|
307
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
|
308
|
+
# Re-raise connection/transport errors directly
|
309
|
+
raise
|
310
|
+
rescue StandardError => e
|
311
|
+
# For all other errors, wrap in ResourceReadError
|
312
|
+
raise MCPClient::Errors::ResourceReadError, "Error reading resource '#{uri}': #{e.message}"
|
313
|
+
end
|
314
|
+
|
315
|
+
# List all resource templates available from the MCP server
|
316
|
+
# @param cursor [String, nil] optional cursor for pagination
|
317
|
+
# @return [Hash] result containing resourceTemplates array and optional nextCursor
|
318
|
+
# @raise [MCPClient::Errors::ResourceReadError] for other errors during resource template listing
|
319
|
+
def list_resource_templates(cursor: nil)
|
320
|
+
params = {}
|
321
|
+
params['cursor'] = cursor if cursor
|
322
|
+
result = rpc_request('resources/templates/list', params)
|
323
|
+
|
324
|
+
templates = (result['resourceTemplates'] || []).map do |template_data|
|
325
|
+
MCPClient::ResourceTemplate.from_json(template_data, server: self)
|
326
|
+
end
|
327
|
+
|
328
|
+
{ 'resourceTemplates' => templates, 'nextCursor' => result['nextCursor'] }
|
329
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
|
330
|
+
raise
|
331
|
+
rescue StandardError => e
|
332
|
+
raise MCPClient::Errors::ResourceReadError, "Error listing resource templates: #{e.message}"
|
333
|
+
end
|
334
|
+
|
335
|
+
# Subscribe to resource updates
|
336
|
+
# @param uri [String] the URI of the resource to subscribe to
|
337
|
+
# @return [Boolean] true if subscription successful
|
338
|
+
# @raise [MCPClient::Errors::ResourceReadError] for other errors during subscription
|
339
|
+
def subscribe_resource(uri)
|
340
|
+
rpc_request('resources/subscribe', { uri: uri })
|
341
|
+
true
|
342
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
|
343
|
+
raise
|
344
|
+
rescue StandardError => e
|
345
|
+
raise MCPClient::Errors::ResourceReadError, "Error subscribing to resource '#{uri}': #{e.message}"
|
346
|
+
end
|
347
|
+
|
348
|
+
# Unsubscribe from resource updates
|
349
|
+
# @param uri [String] the URI of the resource to unsubscribe from
|
350
|
+
# @return [Boolean] true if unsubscription successful
|
351
|
+
# @raise [MCPClient::Errors::ResourceReadError] for other errors during unsubscription
|
352
|
+
def unsubscribe_resource(uri)
|
353
|
+
rpc_request('resources/unsubscribe', { uri: uri })
|
354
|
+
true
|
355
|
+
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
|
356
|
+
raise
|
357
|
+
rescue StandardError => e
|
358
|
+
raise MCPClient::Errors::ResourceReadError, "Error unsubscribing from resource '#{uri}': #{e.message}"
|
359
|
+
end
|
360
|
+
|
214
361
|
# Override apply_request_headers to add session and SSE headers for MCP protocol
|
215
362
|
def apply_request_headers(req, request)
|
216
363
|
super
|
@@ -294,6 +441,10 @@ module MCPClient
|
|
294
441
|
# Clear cached data
|
295
442
|
@tools = nil
|
296
443
|
@tools_data = nil
|
444
|
+
@prompts = nil
|
445
|
+
@prompts_data = nil
|
446
|
+
@resources = nil
|
447
|
+
@resources_data = nil
|
297
448
|
@buffer = ''
|
298
449
|
|
299
450
|
@logger.info('Cleanup completed')
|
@@ -381,6 +532,56 @@ module MCPClient
|
|
381
532
|
raise MCPClient::Errors::ToolCallError, 'Failed to get tools list from JSON-RPC request'
|
382
533
|
end
|
383
534
|
|
535
|
+
# Request the prompts list using JSON-RPC
|
536
|
+
# @return [Array<Hash>] the prompts data
|
537
|
+
# @raise [MCPClient::Errors::PromptGetError] if prompts list retrieval fails
|
538
|
+
def request_prompts_list
|
539
|
+
@mutex.synchronize do
|
540
|
+
return @prompts_data if @prompts_data
|
541
|
+
end
|
542
|
+
|
543
|
+
result = rpc_request('prompts/list')
|
544
|
+
|
545
|
+
if result.is_a?(Hash) && result['prompts']
|
546
|
+
@mutex.synchronize do
|
547
|
+
@prompts_data = result['prompts']
|
548
|
+
end
|
549
|
+
return @mutex.synchronize { @prompts_data.dup }
|
550
|
+
elsif result.is_a?(Array) || result
|
551
|
+
@mutex.synchronize do
|
552
|
+
@prompts_data = result
|
553
|
+
end
|
554
|
+
return @mutex.synchronize { @prompts_data.dup }
|
555
|
+
end
|
556
|
+
|
557
|
+
raise MCPClient::Errors::PromptGetError, 'Failed to get prompts list from JSON-RPC request'
|
558
|
+
end
|
559
|
+
|
560
|
+
# Request the resources list using JSON-RPC
|
561
|
+
# @return [Array<Hash>] the resources data
|
562
|
+
# @raise [MCPClient::Errors::ResourceReadError] if resources list retrieval fails
|
563
|
+
def request_resources_list
|
564
|
+
@mutex.synchronize do
|
565
|
+
return @resources_data if @resources_data
|
566
|
+
end
|
567
|
+
|
568
|
+
result = rpc_request('resources/list')
|
569
|
+
|
570
|
+
if result.is_a?(Hash) && result['resources']
|
571
|
+
@mutex.synchronize do
|
572
|
+
@resources_data = result['resources']
|
573
|
+
end
|
574
|
+
return @mutex.synchronize { @resources_data.dup }
|
575
|
+
elsif result.is_a?(Array) || result
|
576
|
+
@mutex.synchronize do
|
577
|
+
@resources_data = result
|
578
|
+
end
|
579
|
+
return @mutex.synchronize { @resources_data.dup }
|
580
|
+
end
|
581
|
+
|
582
|
+
raise MCPClient::Errors::ResourceReadError, 'Failed to get resources list from JSON-RPC request'
|
583
|
+
end
|
584
|
+
|
384
585
|
# Start the long-lived GET connection for server events
|
385
586
|
# Creates a separate thread to maintain SSE connection for server notifications
|
386
587
|
# @return [void]
|
data/lib/mcp_client/version.rb
CHANGED
data/lib/mcp_client.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
# Load all MCPClient components
|
4
4
|
require_relative 'mcp_client/errors'
|
5
5
|
require_relative 'mcp_client/tool'
|
6
|
+
require_relative 'mcp_client/prompt'
|
7
|
+
require_relative 'mcp_client/resource'
|
8
|
+
require_relative 'mcp_client/resource_template'
|
9
|
+
require_relative 'mcp_client/resource_content'
|
6
10
|
require_relative 'mcp_client/server_base'
|
7
11
|
require_relative 'mcp_client/server_stdio'
|
8
12
|
require_relative 'mcp_client/server_sse'
|
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: 0.
|
4
|
+
version: 0.8.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: 2025-09-
|
11
|
+
date: 2025-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -127,6 +127,10 @@ files:
|
|
127
127
|
- lib/mcp_client/http_transport_base.rb
|
128
128
|
- lib/mcp_client/json_rpc_common.rb
|
129
129
|
- lib/mcp_client/oauth_client.rb
|
130
|
+
- lib/mcp_client/prompt.rb
|
131
|
+
- lib/mcp_client/resource.rb
|
132
|
+
- lib/mcp_client/resource_content.rb
|
133
|
+
- lib/mcp_client/resource_template.rb
|
130
134
|
- lib/mcp_client/server_base.rb
|
131
135
|
- lib/mcp_client/server_factory.rb
|
132
136
|
- lib/mcp_client/server_http.rb
|