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.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MCPClient
4
+ # Representation of an MCP prompt
5
+ class Prompt
6
+ # @!attribute [r] name
7
+ # @return [String] the name of the prompt
8
+ # @!attribute [r] description
9
+ # @return [String] the description of the prompt
10
+ # @!attribute [r] arguments
11
+ # @return [Hash] the JSON arguments for the prompt
12
+ # @!attribute [r] server
13
+ # @return [MCPClient::ServerBase, nil] the server this prompt belongs to
14
+ attr_reader :name, :description, :arguments, :server
15
+
16
+ # Initialize a new prompt
17
+ # @param name [String] the name of the prompt
18
+ # @param description [String] the description of the prompt
19
+ # @param arguments [Hash] the JSON arguments for the prompt
20
+ # @param server [MCPClient::ServerBase, nil] the server this prompt belongs to
21
+ def initialize(name:, description:, arguments: {}, server: nil)
22
+ @name = name
23
+ @description = description
24
+ @arguments = arguments
25
+ @server = server
26
+ end
27
+
28
+ # Create a Prompt instance from JSON data
29
+ # @param data [Hash] JSON data from MCP server
30
+ # @param server [MCPClient::ServerBase, nil] the server this prompt belongs to
31
+ # @return [MCPClient::Prompt] prompt instance
32
+ def self.from_json(data, server: nil)
33
+ new(
34
+ name: data['name'],
35
+ description: data['description'],
36
+ arguments: data['arguments'] || {},
37
+ server: server
38
+ )
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MCPClient
4
+ # Representation of an MCP resource
5
+ class Resource
6
+ # @!attribute [r] uri
7
+ # @return [String] unique identifier for the resource
8
+ # @!attribute [r] name
9
+ # @return [String] the name of the resource
10
+ # @!attribute [r] title
11
+ # @return [String, nil] optional human-readable name of the resource for display purposes
12
+ # @!attribute [r] description
13
+ # @return [String, nil] optional description
14
+ # @!attribute [r] mime_type
15
+ # @return [String, nil] optional MIME type
16
+ # @!attribute [r] size
17
+ # @return [Integer, nil] optional size in bytes
18
+ # @!attribute [r] annotations
19
+ # @return [Hash, nil] optional annotations that provide hints to clients
20
+ # @!attribute [r] server
21
+ # @return [MCPClient::ServerBase, nil] the server this resource belongs to
22
+ attr_reader :uri, :name, :title, :description, :mime_type, :size, :annotations, :server
23
+
24
+ # Initialize a new resource
25
+ # @param uri [String] unique identifier for the resource
26
+ # @param name [String] the name of the resource
27
+ # @param title [String, nil] optional human-readable name of the resource for display purposes
28
+ # @param description [String, nil] optional description
29
+ # @param mime_type [String, nil] optional MIME type
30
+ # @param size [Integer, nil] optional size in bytes
31
+ # @param annotations [Hash, nil] optional annotations that provide hints to clients
32
+ # @param server [MCPClient::ServerBase, nil] the server this resource belongs to
33
+ def initialize(uri:, name:, title: nil, description: nil, mime_type: nil, size: nil, annotations: nil, server: nil)
34
+ @uri = uri
35
+ @name = name
36
+ @title = title
37
+ @description = description
38
+ @mime_type = mime_type
39
+ @size = size
40
+ @annotations = annotations
41
+ @server = server
42
+ end
43
+
44
+ # Create a Resource instance from JSON data
45
+ # @param data [Hash] JSON data from MCP server
46
+ # @param server [MCPClient::ServerBase, nil] the server this resource belongs to
47
+ # @return [MCPClient::Resource] resource instance
48
+ def self.from_json(data, server: nil)
49
+ new(
50
+ uri: data['uri'],
51
+ name: data['name'],
52
+ title: data['title'],
53
+ description: data['description'],
54
+ mime_type: data['mimeType'],
55
+ size: data['size'],
56
+ annotations: data['annotations'],
57
+ server: server
58
+ )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MCPClient
4
+ # Representation of MCP resource content
5
+ # Resources can contain either text or binary data
6
+ class ResourceContent
7
+ # @!attribute [r] uri
8
+ # @return [String] unique identifier for the resource
9
+ # @!attribute [r] name
10
+ # @return [String] the name of the resource
11
+ # @!attribute [r] title
12
+ # @return [String, nil] optional human-readable name for display purposes
13
+ # @!attribute [r] mime_type
14
+ # @return [String, nil] optional MIME type
15
+ # @!attribute [r] text
16
+ # @return [String, nil] text content (mutually exclusive with blob)
17
+ # @!attribute [r] blob
18
+ # @return [String, nil] base64-encoded binary content (mutually exclusive with text)
19
+ # @!attribute [r] annotations
20
+ # @return [Hash, nil] optional annotations that provide hints to clients
21
+ attr_reader :uri, :name, :title, :mime_type, :text, :blob, :annotations
22
+
23
+ # Initialize resource content
24
+ # @param uri [String] unique identifier for the resource
25
+ # @param name [String] the name of the resource
26
+ # @param title [String, nil] optional human-readable name for display purposes
27
+ # @param mime_type [String, nil] optional MIME type
28
+ # @param text [String, nil] text content (mutually exclusive with blob)
29
+ # @param blob [String, nil] base64-encoded binary content (mutually exclusive with text)
30
+ # @param annotations [Hash, nil] optional annotations that provide hints to clients
31
+ def initialize(uri:, name:, title: nil, mime_type: nil, text: nil, blob: nil, annotations: nil)
32
+ raise ArgumentError, 'ResourceContent cannot have both text and blob' if text && blob
33
+ raise ArgumentError, 'ResourceContent must have either text or blob' if !text && !blob
34
+
35
+ @uri = uri
36
+ @name = name
37
+ @title = title
38
+ @mime_type = mime_type
39
+ @text = text
40
+ @blob = blob
41
+ @annotations = annotations
42
+ end
43
+
44
+ # Create a ResourceContent instance from JSON data
45
+ # @param data [Hash] JSON data from MCP server
46
+ # @return [MCPClient::ResourceContent] resource content instance
47
+ def self.from_json(data)
48
+ new(
49
+ uri: data['uri'],
50
+ name: data['name'],
51
+ title: data['title'],
52
+ mime_type: data['mimeType'],
53
+ text: data['text'],
54
+ blob: data['blob'],
55
+ annotations: data['annotations']
56
+ )
57
+ end
58
+
59
+ # Check if content is text
60
+ # @return [Boolean] true if content is text
61
+ def text?
62
+ !@text.nil?
63
+ end
64
+
65
+ # Check if content is binary
66
+ # @return [Boolean] true if content is binary
67
+ def binary?
68
+ !@blob.nil?
69
+ end
70
+
71
+ # Get the content (text or decoded blob)
72
+ # @return [String] the content
73
+ def content
74
+ return @text if text?
75
+
76
+ require 'base64'
77
+ Base64.decode64(@blob)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MCPClient
4
+ # Representation of an MCP resource template
5
+ # Resource templates allow servers to expose parameterized resources using URI templates
6
+ class ResourceTemplate
7
+ # @!attribute [r] uri_template
8
+ # @return [String] URI template following RFC 6570
9
+ # @!attribute [r] name
10
+ # @return [String] the name of the resource template
11
+ # @!attribute [r] title
12
+ # @return [String, nil] optional human-readable name for display purposes
13
+ # @!attribute [r] description
14
+ # @return [String, nil] optional description
15
+ # @!attribute [r] mime_type
16
+ # @return [String, nil] optional MIME type for resources created from this template
17
+ # @!attribute [r] annotations
18
+ # @return [Hash, nil] optional annotations that provide hints to clients
19
+ # @!attribute [r] server
20
+ # @return [MCPClient::ServerBase, nil] the server this resource template belongs to
21
+ attr_reader :uri_template, :name, :title, :description, :mime_type, :annotations, :server
22
+
23
+ # Initialize a new resource template
24
+ # @param uri_template [String] URI template following RFC 6570
25
+ # @param name [String] the name of the resource template
26
+ # @param title [String, nil] optional human-readable name for display purposes
27
+ # @param description [String, nil] optional description
28
+ # @param mime_type [String, nil] optional MIME type
29
+ # @param annotations [Hash, nil] optional annotations that provide hints to clients
30
+ # @param server [MCPClient::ServerBase, nil] the server this resource template belongs to
31
+ def initialize(uri_template:, name:, title: nil, description: nil, mime_type: nil, annotations: nil, server: nil)
32
+ @uri_template = uri_template
33
+ @name = name
34
+ @title = title
35
+ @description = description
36
+ @mime_type = mime_type
37
+ @annotations = annotations
38
+ @server = server
39
+ end
40
+
41
+ # Create a ResourceTemplate instance from JSON data
42
+ # @param data [Hash] JSON data from MCP server
43
+ # @param server [MCPClient::ServerBase, nil] the server this resource template belongs to
44
+ # @return [MCPClient::ResourceTemplate] resource template instance
45
+ def self.from_json(data, server: nil)
46
+ new(
47
+ uri_template: data['uriTemplate'],
48
+ name: data['name'],
49
+ title: data['title'],
50
+ description: data['description'],
51
+ mime_type: data['mimeType'],
52
+ annotations: data['annotations'],
53
+ server: server
54
+ )
55
+ end
56
+ end
57
+ end
@@ -33,6 +33,61 @@ module MCPClient
33
33
  raise NotImplementedError, 'Subclasses must implement call_tool'
34
34
  end
35
35
 
36
+ # List all prompts available from the MCP server
37
+ # @return [Array<MCPClient::Prompt>] list of available prompts
38
+ def list_prompts
39
+ raise NotImplementedError, 'Subclasses must implement list_prompts'
40
+ end
41
+
42
+ # Get a prompt with the given parameters
43
+ # @param prompt_name [String] the name of the prompt to get
44
+ # @param parameters [Hash] the parameters to pass to the prompt
45
+ # @return [Object] the result of the prompt interpolation
46
+ def get_prompt(prompt_name, parameters)
47
+ raise NotImplementedError, 'Subclasses must implement get_prompt'
48
+ end
49
+
50
+ # List all resources available from the MCP server
51
+ # @param cursor [String, nil] optional cursor for pagination
52
+ # @return [Hash] result containing resources array and optional nextCursor
53
+ def list_resources(cursor: nil)
54
+ raise NotImplementedError, 'Subclasses must implement list_resources'
55
+ end
56
+
57
+ # Read a resource by its URI
58
+ # @param uri [String] the URI of the resource to read
59
+ # @return [Array<MCPClient::ResourceContent>] array of resource contents
60
+ def read_resource(uri)
61
+ raise NotImplementedError, 'Subclasses must implement read_resource'
62
+ end
63
+
64
+ # List all resource templates available from the MCP server
65
+ # @param cursor [String, nil] optional cursor for pagination
66
+ # @return [Hash] result containing resourceTemplates array and optional nextCursor
67
+ def list_resource_templates(cursor: nil)
68
+ raise NotImplementedError, 'Subclasses must implement list_resource_templates'
69
+ end
70
+
71
+ # Subscribe to resource updates
72
+ # @param uri [String] the URI of the resource to subscribe to
73
+ # @return [Boolean] true if subscription successful
74
+ def subscribe_resource(uri)
75
+ raise NotImplementedError, 'Subclasses must implement subscribe_resource'
76
+ end
77
+
78
+ # Unsubscribe from resource updates
79
+ # @param uri [String] the URI of the resource to unsubscribe from
80
+ # @return [Boolean] true if unsubscription successful
81
+ def unsubscribe_resource(uri)
82
+ raise NotImplementedError, 'Subclasses must implement unsubscribe_resource'
83
+ end
84
+
85
+ # Get server capabilities
86
+ # @return [Hash, nil] server capabilities
87
+ def capabilities
88
+ raise NotImplementedError, 'Subclasses must implement capabilities'
89
+ end
90
+
36
91
  # Clean up the server connection
37
92
  def cleanup
38
93
  raise NotImplementedError, 'Subclasses must implement cleanup'
@@ -216,6 +216,148 @@ module MCPClient
216
216
  end
217
217
  end
218
218
 
219
+ # List all prompts available from the MCP server
220
+ # @return [Array<MCPClient::Prompt>] list of available prompts
221
+ # @raise [MCPClient::Errors::ServerError] if server returns an error
222
+ # @raise [MCPClient::Errors::TransportError] if response isn't valid JSON
223
+ # @raise [MCPClient::Errors::PromptGetError] for other errors during prompt listing
224
+ def list_prompts
225
+ @mutex.synchronize do
226
+ return @prompts if @prompts
227
+ end
228
+
229
+ begin
230
+ ensure_connected
231
+
232
+ prompts_data = rpc_request('prompts/list')
233
+ prompts = prompts_data['prompts'] || []
234
+
235
+ @mutex.synchronize do
236
+ @prompts = prompts.map do |prompt_data|
237
+ MCPClient::Prompt.from_json(prompt_data, server: self)
238
+ end
239
+ end
240
+
241
+ @mutex.synchronize { @prompts }
242
+ rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
243
+ raise
244
+ rescue StandardError => e
245
+ raise MCPClient::Errors::PromptGetError, "Error listing prompts: #{e.message}"
246
+ end
247
+ end
248
+
249
+ # Get a prompt with the given parameters
250
+ # @param prompt_name [String] the name of the prompt to get
251
+ # @param parameters [Hash] the parameters to pass to the prompt
252
+ # @return [Object] the result of the prompt interpolation
253
+ # @raise [MCPClient::Errors::ServerError] if server returns an error
254
+ # @raise [MCPClient::Errors::TransportError] if response isn't valid JSON
255
+ # @raise [MCPClient::Errors::PromptGetError] for other errors during prompt interpolation
256
+ def get_prompt(prompt_name, parameters)
257
+ rpc_request('prompts/get', {
258
+ name: prompt_name,
259
+ arguments: parameters
260
+ })
261
+ rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
262
+ raise
263
+ rescue StandardError => e
264
+ raise MCPClient::Errors::PromptGetError, "Error getting prompt '#{prompt_name}': #{e.message}"
265
+ end
266
+
267
+ # List all resources available from the MCP server
268
+ # @param cursor [String, nil] optional cursor for pagination
269
+ # @return [Hash] result containing resources array and optional nextCursor
270
+ # @raise [MCPClient::Errors::ResourceReadError] if resources list retrieval fails
271
+ def list_resources(cursor: nil)
272
+ @mutex.synchronize do
273
+ return @resources_result if @resources_result && !cursor
274
+ end
275
+
276
+ begin
277
+ ensure_connected
278
+
279
+ params = {}
280
+ params['cursor'] = cursor if cursor
281
+ result = rpc_request('resources/list', params)
282
+
283
+ resources = (result['resources'] || []).map do |resource_data|
284
+ MCPClient::Resource.from_json(resource_data, server: self)
285
+ end
286
+
287
+ resources_result = { 'resources' => resources, 'nextCursor' => result['nextCursor'] }
288
+
289
+ @mutex.synchronize do
290
+ @resources_result = resources_result unless cursor
291
+ end
292
+
293
+ resources_result
294
+ rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
295
+ raise
296
+ rescue StandardError => e
297
+ raise MCPClient::Errors::ResourceReadError, "Error listing resources: #{e.message}"
298
+ end
299
+ end
300
+
301
+ # Read a resource by its URI
302
+ # @param uri [String] the URI of the resource to read
303
+ # @return [Array<MCPClient::ResourceContent>] array of resource contents
304
+ # @raise [MCPClient::Errors::ResourceReadError] if resource reading fails
305
+ def read_resource(uri)
306
+ result = rpc_request('resources/read', { uri: uri })
307
+ contents = result['contents'] || []
308
+ contents.map { |content| MCPClient::ResourceContent.from_json(content) }
309
+ rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
310
+ raise
311
+ rescue StandardError => e
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
+
219
361
  # Stream tool call (default implementation returns single-value stream)
220
362
  # @param tool_name [String] the name of the tool to call
221
363
  # @param parameters [Hash] the parameters to pass to the tool
@@ -7,6 +7,7 @@ module MCPClient
7
7
  # JSON-RPC request/notification plumbing for SSE transport
8
8
  module JsonRpcTransport
9
9
  include JsonRpcCommon
10
+
10
11
  # Generic JSON-RPC request: send method with params and return result
11
12
  # @param method [String] JSON-RPC method name
12
13
  # @param params [Hash] parameters for the request