ruby_llm-mcp 0.4.1 → 0.5.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +296 -25
  3. data/lib/ruby_llm/chat.rb +2 -1
  4. data/lib/ruby_llm/mcp/client.rb +32 -13
  5. data/lib/ruby_llm/mcp/configuration.rb +123 -3
  6. data/lib/ruby_llm/mcp/coordinator.rb +108 -115
  7. data/lib/ruby_llm/mcp/errors.rb +3 -1
  8. data/lib/ruby_llm/mcp/notification_handler.rb +84 -0
  9. data/lib/ruby_llm/mcp/{requests/cancelled_notification.rb → notifications/cancelled.rb} +2 -2
  10. data/lib/ruby_llm/mcp/{requests/initialize_notification.rb → notifications/initialize.rb} +7 -3
  11. data/lib/ruby_llm/mcp/notifications/roots_list_change.rb +26 -0
  12. data/lib/ruby_llm/mcp/parameter.rb +19 -1
  13. data/lib/ruby_llm/mcp/progress.rb +3 -1
  14. data/lib/ruby_llm/mcp/prompt.rb +18 -0
  15. data/lib/ruby_llm/mcp/railtie.rb +20 -0
  16. data/lib/ruby_llm/mcp/requests/initialization.rb +8 -4
  17. data/lib/ruby_llm/mcp/requests/ping.rb +6 -2
  18. data/lib/ruby_llm/mcp/requests/prompt_list.rb +10 -2
  19. data/lib/ruby_llm/mcp/requests/resource_list.rb +12 -2
  20. data/lib/ruby_llm/mcp/requests/resource_template_list.rb +12 -2
  21. data/lib/ruby_llm/mcp/requests/shared/meta.rb +32 -0
  22. data/lib/ruby_llm/mcp/requests/shared/pagination.rb +17 -0
  23. data/lib/ruby_llm/mcp/requests/tool_call.rb +1 -1
  24. data/lib/ruby_llm/mcp/requests/tool_list.rb +10 -2
  25. data/lib/ruby_llm/mcp/resource.rb +17 -0
  26. data/lib/ruby_llm/mcp/response_handler.rb +58 -0
  27. data/lib/ruby_llm/mcp/responses/error.rb +33 -0
  28. data/lib/ruby_llm/mcp/{requests/ping_response.rb → responses/ping.rb} +2 -2
  29. data/lib/ruby_llm/mcp/responses/roots_list.rb +31 -0
  30. data/lib/ruby_llm/mcp/responses/sampling_create_message.rb +50 -0
  31. data/lib/ruby_llm/mcp/result.rb +21 -8
  32. data/lib/ruby_llm/mcp/roots.rb +45 -0
  33. data/lib/ruby_llm/mcp/sample.rb +148 -0
  34. data/lib/ruby_llm/mcp/{capabilities.rb → server_capabilities.rb} +1 -1
  35. data/lib/ruby_llm/mcp/tool.rb +35 -4
  36. data/lib/ruby_llm/mcp/transport.rb +58 -0
  37. data/lib/ruby_llm/mcp/transports/http_client.rb +26 -0
  38. data/lib/ruby_llm/mcp/{transport → transports}/sse.rb +25 -24
  39. data/lib/ruby_llm/mcp/{transport → transports}/stdio.rb +28 -26
  40. data/lib/ruby_llm/mcp/{transport → transports}/streamable_http.rb +25 -29
  41. data/lib/ruby_llm/mcp/transports/timeout.rb +32 -0
  42. data/lib/ruby_llm/mcp/version.rb +1 -1
  43. data/lib/ruby_llm/mcp.rb +50 -9
  44. metadata +23 -12
  45. data/lib/ruby_llm/mcp/requests/base.rb +0 -31
  46. data/lib/ruby_llm/mcp/requests/meta.rb +0 -30
  47. data/lib/tasks/release.rake +0 -23
@@ -8,8 +8,7 @@ module RubyLLM
8
8
  PROTOCOL_VERSION = "2025-03-26"
9
9
  PV_2024_11_05 = "2024-11-05"
10
10
 
11
- attr_reader :client, :transport_type, :config, :request_timeout, :headers, :transport, :initialize_response,
12
- :capabilities, :protocol_version
11
+ attr_reader :client, :transport_type, :config, :capabilities, :protocol_version
13
12
 
14
13
  def initialize(client, transport_type:, config: {})
15
14
  @client = client
@@ -17,23 +16,46 @@ module RubyLLM
17
16
  @config = config
18
17
 
19
18
  @protocol_version = PROTOCOL_VERSION
20
- @headers = config[:headers] || {}
21
19
 
22
20
  @transport = nil
23
21
  @capabilities = nil
24
22
  end
25
23
 
24
+ def name
25
+ client.name
26
+ end
27
+
26
28
  def request(body, **options)
27
- @transport.request(body, **options)
29
+ transport.request(body, **options)
28
30
  rescue RubyLLM::MCP::Errors::TimeoutError => e
29
- if @transport.alive?
31
+ if transport&.alive?
30
32
  cancelled_notification(reason: "Request timed out", request_id: e.request_id)
31
33
  end
32
34
  raise e
33
35
  end
34
36
 
37
+ def process_result(result)
38
+ if result.notification?
39
+ process_notification(result)
40
+ return nil
41
+ end
42
+
43
+ if result.request?
44
+ process_request(result) if alive?
45
+ return nil
46
+ end
47
+
48
+ if result.response?
49
+ return result
50
+ end
51
+
52
+ nil
53
+ end
54
+
35
55
  def start_transport
36
- build_transport
56
+ return unless capabilities.nil?
57
+
58
+ transport.start
37
59
 
38
60
  initialize_response = initialize_request
39
61
  initialize_response.raise_error! if initialize_response.error?
@@ -47,16 +69,19 @@ module RubyLLM
47
69
  @transport.set_protocol_version(@protocol_version)
48
70
  end
49
71
 
50
- @capabilities = RubyLLM::MCP::Capabilities.new(initialize_response.value["capabilities"])
72
+ @capabilities = RubyLLM::MCP::ServerCapabilities.new(initialize_response.value["capabilities"])
51
73
  initialize_notification
52
74
  end
53
75
 
54
76
  def stop_transport
55
77
  @transport&.close
78
+ @capabilities = nil
56
79
  @transport = nil
80
+ @protocol_version = PROTOCOL_VERSION
57
81
  end
58
82
 
59
83
  def restart_transport
84
+ @initialize_response = nil
60
85
  stop_transport
61
86
  start_transport
62
87
  end
@@ -70,7 +95,7 @@ module RubyLLM
70
95
  if alive?
71
96
  result = ping_request.call
72
97
  else
73
- build_transport
98
+ transport.start
74
99
 
75
100
  result = ping_request.call
76
101
  @transport = nil
@@ -83,50 +108,26 @@ module RubyLLM
83
108
 
84
109
  def process_notification(result)
85
110
  notification = result.notification
86
-
87
- case notification.type
88
- when "notifications/tools/list_changed"
89
- client.reset_tools!
90
- when "notifications/resources/list_changed"
91
- client.reset_resources!
92
- when "notifications/resources/updated"
93
- uri = notification.params["uri"]
94
- resource = client.resources.find { |r| r.uri == uri }
95
- resource&.reset_content!
96
- when "notifications/prompts/list_changed"
97
- client.reset_prompts!
98
- when "notifications/message"
99
- process_logging_message(notification)
100
- when "notifications/progress"
101
- process_progress_message(notification)
102
- when "notifications/cancelled"
103
- # TODO: - do nothing at the moment until we support client operations
104
- else
105
- message = "Unknown notification type: #{notification.type} params:#{notification.params.to_h}"
106
- raise Errors::UnknownNotification.new(message: message)
107
- end
111
+ NotificationHandler.new(self).execute(notification)
108
112
  end
109
113
 
110
114
  def process_request(result)
111
- if result.ping?
112
- ping_response(id: result.id)
113
- return
114
- end
115
-
116
- # Handle server-initiated requests
117
- # Currently, we do not support any client operations but will
118
- raise RubyLLM::MCP::Errors::UnknownRequest.new(message: "Unknown request type: #{result.inspect}")
115
+ ResponseHandler.new(self).execute(result)
119
116
  end
120
117
 
121
118
  def initialize_request
122
119
  RubyLLM::MCP::Requests::Initialization.new(self).call
123
120
  end
124
121
 
125
- def tool_list
126
- result = RubyLLM::MCP::Requests::ToolList.new(self).call
122
+ def tool_list(cursor: nil)
123
+ result = RubyLLM::MCP::Requests::ToolList.new(self, cursor: cursor).call
127
124
  result.raise_error! if result.error?
128
125
 
129
- result.value["tools"]
126
+ if result.next_cursor?
127
+ result.value["tools"] + tool_list(cursor: result.next_cursor)
128
+ else
129
+ result.value["tools"]
130
+ end
130
131
  end
131
132
 
132
133
  def execute_tool(**args)
@@ -149,33 +150,45 @@ module RubyLLM
149
150
  RubyLLM::MCP::Requests::ToolCall.new(self, **args).call
150
151
  end
151
152
 
152
- def resource_list
153
- result = RubyLLM::MCP::Requests::ResourceList.new(self).call
153
+ def resource_list(cursor: nil)
154
+ result = RubyLLM::MCP::Requests::ResourceList.new(self, cursor: cursor).call
154
155
  result.raise_error! if result.error?
155
156
 
156
- result.value["resources"]
157
+ if result.next_cursor?
158
+ result.value["resources"] + resource_list(cursor: result.next_cursor)
159
+ else
160
+ result.value["resources"]
161
+ end
157
162
  end
158
163
 
159
164
  def resource_read(**args)
160
165
  RubyLLM::MCP::Requests::ResourceRead.new(self, **args).call
161
166
  end
162
167
 
163
- def resource_template_list
164
- result = RubyLLM::MCP::Requests::ResourceTemplateList.new(self).call
168
+ def resource_template_list(cursor: nil)
169
+ result = RubyLLM::MCP::Requests::ResourceTemplateList.new(self, cursor: cursor).call
165
170
  result.raise_error! if result.error?
166
171
 
167
- result.value["resourceTemplates"]
172
+ if result.next_cursor?
173
+ result.value["resourceTemplates"] + resource_template_list(cursor: result.next_cursor)
174
+ else
175
+ result.value["resourceTemplates"]
176
+ end
168
177
  end
169
178
 
170
179
  def resources_subscribe(**args)
171
180
  RubyLLM::MCP::Requests::ResourcesSubscribe.new(self, **args).call
172
181
  end
173
182
 
174
- def prompt_list
175
- result = RubyLLM::MCP::Requests::PromptList.new(self).call
183
+ def prompt_list(cursor: nil)
184
+ result = RubyLLM::MCP::Requests::PromptList.new(self, cursor: cursor).call
176
185
  result.raise_error! if result.error?
177
186
 
178
- result.value["prompts"]
187
+ if result.next_cursor?
188
+ result.value["prompts"] + prompt_list(cursor: result.next_cursor)
189
+ else
190
+ result.value["prompts"]
191
+ end
179
192
  end
180
193
 
181
194
  def execute_prompt(**args)
@@ -190,86 +203,66 @@ module RubyLLM
190
203
  RubyLLM::MCP::Requests::CompletionPrompt.new(self, **args).call
191
204
  end
192
205
 
206
+ def set_logging(**args)
207
+ RubyLLM::MCP::Requests::LoggingSetLevel.new(self, **args).call
208
+ end
209
+
210
+ ## Notifications
211
+ #
193
212
  def initialize_notification
194
- RubyLLM::MCP::Requests::InitializeNotification.new(self).call
213
+ RubyLLM::MCP::Notifications::Initialize.new(self).call
195
214
  end
196
215
 
197
216
  def cancelled_notification(**args)
198
- RubyLLM::MCP::Requests::CancelledNotification.new(self, **args).call
199
- end
200
-
201
- def ping_response(id: nil)
202
- RubyLLM::MCP::Requests::PingResponse.new(self, id: id).call
203
- end
204
-
205
- def set_logging(level:)
206
- RubyLLM::MCP::Requests::LoggingSetLevel.new(self, level: level).call
207
- end
208
-
209
- def build_transport
210
- case @transport_type
211
- when :sse
212
- @transport = RubyLLM::MCP::Transport::SSE.new(@config[:url],
213
- request_timeout: @config[:request_timeout],
214
- headers: @headers,
215
- coordinator: self)
216
- when :stdio
217
- @transport = RubyLLM::MCP::Transport::Stdio.new(@config[:command],
218
- request_timeout: @config[:request_timeout],
219
- args: @config[:args],
220
- env: @config[:env],
221
- coordinator: self)
222
- when :streamable
223
- @transport = RubyLLM::MCP::Transport::StreamableHTTP.new(@config[:url],
224
- request_timeout: @config[:request_timeout],
225
- headers: @headers,
226
- coordinator: self)
227
- else
228
- message = "Invalid transport type: :#{transport_type}. Supported types are :sse, :stdio, :streamable"
229
- raise Errors::InvalidTransportType.new(message: message)
230
- end
217
+ RubyLLM::MCP::Notifications::Cancelled.new(self, **args).call
231
218
  end
232
219
 
233
- def process_logging_message(notification)
234
- if client.logging_handler_enabled?
235
- client.on[:logging].call(notification)
236
- else
237
- default_process_logging_message(notification)
238
- end
220
+ def roots_list_change_notification
221
+ RubyLLM::MCP::Notifications::RootsListChange.new(self).call
222
+ end
223
+
224
+ ## Responses
225
+ #
226
+ def ping_response(**args)
227
+ RubyLLM::MCP::Responses::Ping.new(self, **args).call
228
+ end
229
+
230
+ def roots_list_response(**args)
231
+ RubyLLM::MCP::Responses::RootsList.new(self, **args).call
232
+ end
233
+
234
+ def sampling_create_message_response(**args)
235
+ RubyLLM::MCP::Responses::SamplingCreateMessage.new(self, **args).call
239
236
  end
240
237
 
241
- def default_process_logging_message(notification, logger: RubyLLM::MCP.logger)
242
- level = notification.params["level"]
243
- logger_message = notification.params["logger"]
244
- message = notification.params["data"]
245
-
246
- message = "#{logger_message}: #{message}"
247
-
248
- case level
249
- when "debug"
250
- logger.debug(message["message"])
251
- when "info", "notice"
252
- logger.info(message["message"])
253
- when "warning"
254
- logger.warn(message["message"])
255
- when "error", "critical"
256
- logger.error(message["message"])
257
- when "alert", "emergency"
258
- logger.fatal(message["message"])
238
+ def error_response(**args)
239
+ RubyLLM::MCP::Responses::Error.new(self, **args).call
240
+ end
241
+
242
+ def client_capabilities
243
+ capabilities = {}
244
+
245
+ if client.roots.active?
246
+ capabilities[:roots] = {
247
+ listChanged: true
248
+ }
259
249
  end
250
+
251
+ if sampling_enabled?
252
+ capabilities[:sampling] = {}
253
+ end
254
+
255
+ capabilities
260
256
  end
261
257
 
262
- def name
263
- client.name
258
+ def transport
259
+ @transport ||= RubyLLM::MCP::Transport.new(@transport_type, self, config: @config)
264
260
  end
265
261
 
266
262
  private
267
263
 
268
- def process_progress_message(notification)
269
- progress_obj = RubyLLM::MCP::Progress.new(self, client.on[:progress], notification.params)
270
- if client.tracking_progress?
271
- progress_obj.execute_progress_handler
272
- end
264
+ def sampling_enabled?
265
+ MCP.config.sampling.enabled?
273
266
  end
274
267
  end
275
268
  end
@@ -17,6 +17,8 @@ module RubyLLM
17
17
  class ResourceSubscribeNotAvailable < BaseError; end
18
18
  end
19
19
 
20
+ class InvalidFormatError < BaseError; end
21
+
20
22
  class InvalidProtocolVersionError < BaseError; end
21
23
 
22
24
  class InvalidTransportType < BaseError; end
@@ -39,7 +41,7 @@ module RubyLLM
39
41
  class TimeoutError < BaseError
40
42
  attr_reader :request_id
41
43
 
42
- def initialize(message:, request_id:)
44
+ def initialize(message:, request_id: nil)
43
45
  @request_id = request_id
44
46
  super(message: message)
45
47
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ class NotificationHandler
6
+ attr_reader :coordinator, :client
7
+
8
+ def initialize(coordinator)
9
+ @coordinator = coordinator
10
+ @client = coordinator.client
11
+ end
12
+
13
+ def execute(notification)
14
+ case notification.type
15
+ when "notifications/tools/list_changed"
16
+ client.reset_tools!
17
+ when "notifications/resources/list_changed"
18
+ client.reset_resources!
19
+ when "notifications/resources/updated"
20
+ process_resource_updated(notification)
21
+ when "notifications/prompts/list_changed"
22
+ client.reset_prompts!
23
+ when "notifications/message"
24
+ process_logging_message(notification)
25
+ when "notifications/progress"
26
+ process_progress_message(notification)
27
+ when "notifications/cancelled"
28
+ # TODO: - do nothing at the moment until we support client operations
29
+ else
30
+ process_unknown_notification(notification)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def process_resource_updated(notification)
37
+ uri = notification.params["uri"]
38
+ resource = client.resources.find { |r| r.uri == uri }
39
+ resource&.reset_content!
40
+ end
41
+
42
+ def process_logging_message(notification)
43
+ if client.logging_handler_enabled?
44
+ client.on[:logging].call(notification)
45
+ else
46
+ default_process_logging_message(notification)
47
+ end
48
+ end
49
+
50
+ def process_progress_message(notification)
51
+ if client.tracking_progress?
52
+ progress_obj = RubyLLM::MCP::Progress.new(self, client.on[:progress], notification.params)
53
+ progress_obj.execute_progress_handler
54
+ end
55
+ end
56
+
57
+ def default_process_logging_message(notification, logger: RubyLLM::MCP.logger)
58
+ level = notification.params["level"]
59
+ logger_message = notification.params["logger"]
60
+ message = notification.params["data"]
61
+
62
+ message = "#{logger_message}: #{message}"
63
+
64
+ case level
65
+ when "debug"
66
+ logger.debug(message["message"])
67
+ when "info", "notice"
68
+ logger.info(message["message"])
69
+ when "warning"
70
+ logger.warn(message["message"])
71
+ when "error", "critical"
72
+ logger.error(message["message"])
73
+ when "alert", "emergency"
74
+ logger.fatal(message["message"])
75
+ end
76
+ end
77
+
78
+ def process_unknown_notification(notification)
79
+ message = "Unknown notification type: #{notification.type} params: #{notification.params.to_h}"
80
+ RubyLLM::MCP.logger.error(message)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module RubyLLM
4
4
  module MCP
5
- module Requests
6
- class CancelledNotification
5
+ module Notifications
6
+ class Cancelled
7
7
  def initialize(coordinator, request_id:, reason:)
8
8
  @coordinator = coordinator
9
9
  @request_id = request_id
@@ -2,10 +2,14 @@
2
2
 
3
3
  module RubyLLM
4
4
  module MCP
5
- module Requests
6
- class InitializeNotification < RubyLLM::MCP::Requests::Base
5
+ module Notifications
6
+ class Initialize
7
+ def initialize(coordinator)
8
+ @coordinator = coordinator
9
+ end
10
+
7
11
  def call
8
- coordinator.request(notification_body, add_id: false, wait_for_response: false)
12
+ @coordinator.request(notification_body, add_id: false, wait_for_response: false)
9
13
  end
10
14
 
11
15
  def notification_body
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ module Notifications
6
+ class RootsListChange
7
+ def initialize(coordinator)
8
+ @coordinator = coordinator
9
+ end
10
+
11
+ def call
12
+ @coordinator.request(roots_list_change_notification_body, add_id: false, wait_for_response: false)
13
+ end
14
+
15
+ private
16
+
17
+ def roots_list_change_notification_body
18
+ {
19
+ jsonrpc: "2.0",
20
+ method: "notifications/roots/list_changed"
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -15,7 +15,25 @@ module RubyLLM
15
15
  end
16
16
 
17
17
  def item_type
18
- @items["type"].to_sym
18
+ @items&.dig("type")&.to_sym
19
+ end
20
+
21
+ def as_json(*_args)
22
+ to_h
23
+ end
24
+
25
+ def to_h
26
+ {
27
+ name: @name,
28
+ type: @type,
29
+ description: @desc,
30
+ required: @required,
31
+ default: @default,
32
+ union_type: @union_type,
33
+ items: @items&.to_h,
34
+ properties: @properties&.values,
35
+ enum: @enum
36
+ }
19
37
  end
20
38
  end
21
39
  end
@@ -17,7 +17,7 @@ module RubyLLM
17
17
  end
18
18
 
19
19
  def execute_progress_handler
20
- @progress_handler&.call(self)
20
+ @progress_handler.call(self)
21
21
  end
22
22
 
23
23
  def to_h
@@ -28,6 +28,8 @@ module RubyLLM
28
28
  message: @message
29
29
  }
30
30
  end
31
+
32
+ alias to_json to_h
31
33
  end
32
34
  end
33
35
  end
@@ -11,6 +11,14 @@ module RubyLLM
11
11
  @description = description
12
12
  @required = required
13
13
  end
14
+
15
+ def to_h
16
+ {
17
+ name: @name,
18
+ description: @description,
19
+ required: @required
20
+ }
21
+ end
14
22
  end
15
23
 
16
24
  attr_reader :name, :description, :arguments, :coordinator
@@ -58,6 +66,16 @@ module RubyLLM
58
66
  end
59
67
  end
60
68
 
69
+ def to_h
70
+ {
71
+ name: @name,
72
+ description: @description,
73
+ arguments: @arguments.map(&:to_h)
74
+ }
75
+ end
76
+
77
+ alias to_json to_h
78
+
61
79
  private
62
80
 
63
81
  def fetch_prompt_messages(arguments)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ class Railtie < Rails::Railtie
6
+ config.after_initialize do
7
+ if RubyLLM::MCP.config.launch_control == :automatic
8
+ RubyLLM::MCP.clients.map(&:start)
9
+ at_exit do
10
+ RubyLLM::MCP.clients.map(&:stop)
11
+ end
12
+ end
13
+ end
14
+
15
+ generators do
16
+ require_relative "../../generators/ruby_llm/mcp/install_generator"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -3,9 +3,13 @@
3
3
  module RubyLLM
4
4
  module MCP
5
5
  module Requests
6
- class Initialization < RubyLLM::MCP::Requests::Base
6
+ class Initialization
7
+ def initialize(coordinator)
8
+ @coordinator = coordinator
9
+ end
10
+
7
11
  def call
8
- coordinator.request(initialize_body)
12
+ @coordinator.request(initialize_body)
9
13
  end
10
14
 
11
15
  private
@@ -15,8 +19,8 @@ module RubyLLM
15
19
  jsonrpc: "2.0",
16
20
  method: "initialize",
17
21
  params: {
18
- protocolVersion: coordinator.protocol_version,
19
- capabilities: {},
22
+ protocolVersion: @coordinator.protocol_version,
23
+ capabilities: @coordinator.client_capabilities,
20
24
  clientInfo: {
21
25
  name: "RubyLLM-MCP Client",
22
26
  version: RubyLLM::MCP::VERSION
@@ -3,9 +3,13 @@
3
3
  module RubyLLM
4
4
  module MCP
5
5
  module Requests
6
- class Ping < Base
6
+ class Ping
7
+ def initialize(coordinator)
8
+ @coordinator = coordinator
9
+ end
10
+
7
11
  def call
8
- coordinator.request(ping_body)
12
+ @coordinator.request(ping_body)
9
13
  end
10
14
 
11
15
  def ping_body
@@ -3,9 +3,17 @@
3
3
  module RubyLLM
4
4
  module MCP
5
5
  module Requests
6
- class PromptList < Base
6
+ class PromptList
7
+ include Shared::Pagination
8
+
9
+ def initialize(coordinator, cursor: nil)
10
+ @coordinator = coordinator
11
+ @cursor = cursor
12
+ end
13
+
7
14
  def call
8
- coordinator.request(request_body)
15
+ body = merge_pagination(request_body)
16
+ @coordinator.request(body)
9
17
  end
10
18
 
11
19
  private
@@ -3,11 +3,21 @@
3
3
  module RubyLLM
4
4
  module MCP
5
5
  module Requests
6
- class ResourceList < Base
6
+ class ResourceList
7
+ include Shared::Pagination
8
+
9
+ def initialize(coordinator, cursor: nil)
10
+ @coordinator = coordinator
11
+ @cursor = cursor
12
+ end
13
+
7
14
  def call
8
- coordinator.request(resource_list_body)
15
+ body = merge_pagination(resource_list_body)
16
+ @coordinator.request(body)
9
17
  end
10
18
 
19
+ private
20
+
11
21
  def resource_list_body
12
22
  {
13
23
  jsonrpc: "2.0",