ruby_llm_swarm-mcp 0.8.0 → 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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -44
  3. data/lib/generators/ruby_llm/mcp/install/templates/initializer.rb +4 -21
  4. data/lib/generators/ruby_llm/mcp/install/templates/mcps.yml +0 -20
  5. data/lib/ruby_llm/mcp/auth/browser/http_server.rb +3 -0
  6. data/lib/ruby_llm/mcp/auth/browser/opener.rb +2 -0
  7. data/lib/ruby_llm/mcp/auth/browser/pages.rb +32 -100
  8. data/lib/ruby_llm/mcp/auth/browser_oauth_provider.rb +6 -32
  9. data/lib/ruby_llm/mcp/auth/http_response_handler.rb +2 -0
  10. data/lib/ruby_llm/mcp/auth/memory_storage.rb +0 -18
  11. data/lib/ruby_llm/mcp/auth/oauth_provider.rb +3 -82
  12. data/lib/ruby_llm/mcp/auth/session_manager.rb +2 -0
  13. data/lib/ruby_llm/mcp/auth/url_builder.rb +2 -0
  14. data/lib/ruby_llm/mcp/client.rb +32 -119
  15. data/lib/ruby_llm/mcp/configuration.rb +6 -74
  16. data/lib/ruby_llm/mcp/coordinator.rb +304 -0
  17. data/lib/ruby_llm/mcp/elicitation.rb +6 -8
  18. data/lib/ruby_llm/mcp/errors.rb +0 -15
  19. data/lib/ruby_llm/mcp/notification_handler.rb +5 -21
  20. data/lib/ruby_llm/mcp/notifications/cancelled.rb +32 -0
  21. data/lib/ruby_llm/mcp/notifications/initialize.rb +24 -0
  22. data/lib/ruby_llm/mcp/notifications/roots_list_change.rb +26 -0
  23. data/lib/ruby_llm/mcp/prompt.rb +7 -7
  24. data/lib/ruby_llm/mcp/protocol.rb +34 -0
  25. data/lib/ruby_llm/mcp/railtie.rb +6 -8
  26. data/lib/ruby_llm/mcp/requests/completion_prompt.rb +50 -0
  27. data/lib/ruby_llm/mcp/requests/completion_resource.rb +50 -0
  28. data/lib/ruby_llm/mcp/requests/initialization.rb +34 -0
  29. data/lib/ruby_llm/mcp/requests/logging_set_level.rb +28 -0
  30. data/lib/ruby_llm/mcp/requests/ping.rb +24 -0
  31. data/lib/ruby_llm/mcp/requests/prompt_call.rb +32 -0
  32. data/lib/ruby_llm/mcp/requests/prompt_list.rb +31 -0
  33. data/lib/ruby_llm/mcp/requests/resource_list.rb +31 -0
  34. data/lib/ruby_llm/mcp/requests/resource_read.rb +30 -0
  35. data/lib/ruby_llm/mcp/requests/resource_template_list.rb +31 -0
  36. data/lib/ruby_llm/mcp/requests/resources_subscribe.rb +30 -0
  37. data/lib/ruby_llm/mcp/requests/shared/meta.rb +32 -0
  38. data/lib/ruby_llm/mcp/requests/shared/pagination.rb +17 -0
  39. data/lib/ruby_llm/mcp/requests/tool_call.rb +35 -0
  40. data/lib/ruby_llm/mcp/requests/tool_list.rb +31 -0
  41. data/lib/ruby_llm/mcp/resource.rb +8 -6
  42. data/lib/ruby_llm/mcp/resource_template.rb +7 -7
  43. data/lib/ruby_llm/mcp/response_handler.rb +67 -0
  44. data/lib/ruby_llm/mcp/responses/elicitation.rb +33 -0
  45. data/lib/ruby_llm/mcp/responses/error.rb +33 -0
  46. data/lib/ruby_llm/mcp/responses/ping.rb +28 -0
  47. data/lib/ruby_llm/mcp/responses/roots_list.rb +31 -0
  48. data/lib/ruby_llm/mcp/responses/sampling_create_message.rb +50 -0
  49. data/lib/ruby_llm/mcp/result.rb +4 -8
  50. data/lib/ruby_llm/mcp/roots.rb +4 -4
  51. data/lib/ruby_llm/mcp/sample.rb +2 -6
  52. data/lib/ruby_llm/mcp/tool.rb +9 -9
  53. data/lib/ruby_llm/mcp/transport.rb +151 -0
  54. data/lib/ruby_llm/mcp/transports/sse.rb +435 -0
  55. data/lib/ruby_llm/mcp/transports/stdio.rb +231 -0
  56. data/lib/ruby_llm/mcp/transports/streamable_http.rb +725 -0
  57. data/lib/ruby_llm/mcp/transports/support/http_client.rb +28 -0
  58. data/lib/ruby_llm/mcp/transports/support/rate_limit.rb +47 -0
  59. data/lib/ruby_llm/mcp/transports/support/timeout.rb +34 -0
  60. data/lib/ruby_llm/mcp/version.rb +1 -1
  61. data/lib/ruby_llm/mcp.rb +7 -30
  62. metadata +38 -33
  63. data/lib/ruby_llm/mcp/adapters/base_adapter.rb +0 -179
  64. data/lib/ruby_llm/mcp/adapters/mcp_sdk_adapter.rb +0 -292
  65. data/lib/ruby_llm/mcp/adapters/mcp_transports/coordinator_stub.rb +0 -33
  66. data/lib/ruby_llm/mcp/adapters/mcp_transports/sse.rb +0 -52
  67. data/lib/ruby_llm/mcp/adapters/mcp_transports/stdio.rb +0 -52
  68. data/lib/ruby_llm/mcp/adapters/mcp_transports/streamable_http.rb +0 -86
  69. data/lib/ruby_llm/mcp/adapters/ruby_llm_adapter.rb +0 -92
  70. data/lib/ruby_llm/mcp/auth/transport_oauth_helper.rb +0 -107
  71. data/lib/ruby_llm/mcp/native/cancellable_operation.rb +0 -57
  72. data/lib/ruby_llm/mcp/native/client.rb +0 -387
  73. data/lib/ruby_llm/mcp/native/json_rpc.rb +0 -170
  74. data/lib/ruby_llm/mcp/native/messages/helpers.rb +0 -39
  75. data/lib/ruby_llm/mcp/native/messages/notifications.rb +0 -42
  76. data/lib/ruby_llm/mcp/native/messages/requests.rb +0 -206
  77. data/lib/ruby_llm/mcp/native/messages/responses.rb +0 -106
  78. data/lib/ruby_llm/mcp/native/messages.rb +0 -36
  79. data/lib/ruby_llm/mcp/native/notification.rb +0 -16
  80. data/lib/ruby_llm/mcp/native/protocol.rb +0 -36
  81. data/lib/ruby_llm/mcp/native/response_handler.rb +0 -110
  82. data/lib/ruby_llm/mcp/native/transport.rb +0 -88
  83. data/lib/ruby_llm/mcp/native/transports/sse.rb +0 -607
  84. data/lib/ruby_llm/mcp/native/transports/stdio.rb +0 -356
  85. data/lib/ruby_llm/mcp/native/transports/streamable_http.rb +0 -926
  86. data/lib/ruby_llm/mcp/native/transports/support/http_client.rb +0 -28
  87. data/lib/ruby_llm/mcp/native/transports/support/rate_limit.rb +0 -49
  88. data/lib/ruby_llm/mcp/native/transports/support/timeout.rb +0 -36
  89. data/lib/ruby_llm/mcp/native.rb +0 -12
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module RubyLLM
6
+ module MCP
7
+ class Coordinator
8
+ attr_reader :client, :transport_type, :config, :capabilities, :protocol_version
9
+
10
+ def initialize(client, transport_type:, config: {})
11
+ @client = client
12
+ @transport_type = transport_type
13
+ @config = config
14
+
15
+ @protocol_version = MCP.config.protocol_version || MCP::Protocol.default_negotiated_version
16
+
17
+ @transport = nil
18
+ @capabilities = nil
19
+ end
20
+
21
+ def name
22
+ client.name
23
+ end
24
+
25
+ def request(body, **options)
26
+ transport.request(body, **options)
27
+ rescue RubyLLM::MCP::Errors::TimeoutError => e
28
+ if transport&.alive? && !e.request_id.nil?
29
+ cancelled_notification(reason: "Request timed out", request_id: e.request_id)
30
+ end
31
+ raise e
32
+ end
33
+
34
+ def process_result(result)
35
+ if result.notification?
36
+ process_notification(result)
37
+ return nil
38
+ end
39
+
40
+ if result.request?
41
+ process_request(result) if alive?
42
+ return nil
43
+ end
44
+
45
+ if result.response?
46
+ return result
47
+ end
48
+
49
+ nil
50
+ end
51
+
52
+ def start_transport
53
+ return unless capabilities.nil?
54
+
55
+ transport.start
56
+
57
+ initialize_response = initialize_request
58
+ initialize_response.raise_error! if initialize_response.error?
59
+
60
+ # Extract and store the negotiated protocol version
61
+ negotiated_version = initialize_response.value["protocolVersion"]
62
+
63
+ if negotiated_version && !MCP::Protocol.supported_version?(negotiated_version)
64
+ raise Errors::UnsupportedProtocolVersion.new(
65
+ message: <<~MESSAGE
66
+ Unsupported protocol version, and could not negotiate a supported version: #{negotiated_version}.
67
+ Supported versions: #{MCP::Protocol.supported_versions.join(', ')}
68
+ MESSAGE
69
+ )
70
+ end
71
+
72
+ @protocol_version = negotiated_version if negotiated_version
73
+
74
+ # Set the protocol version on the transport for subsequent requests
75
+ if @transport.respond_to?(:set_protocol_version)
76
+ @transport.set_protocol_version(@protocol_version)
77
+ end
78
+
79
+ @capabilities = RubyLLM::MCP::ServerCapabilities.new(initialize_response.value["capabilities"])
80
+ initialize_notification
81
+
82
+ if client.logging_handler_enabled?
83
+ set_logging(level: client.on_logging_level)
84
+ end
85
+ end
86
+
87
+ def stop_transport
88
+ @transport&.close
89
+ @capabilities = nil
90
+ @transport = nil
91
+ @protocol_version = MCP::Protocol.default_negotiated_version
92
+ end
93
+
94
+ def restart_transport
95
+ @initialize_response = nil
96
+ stop_transport
97
+ start_transport
98
+ end
99
+
100
+ def alive?
101
+ !!@transport&.alive?
102
+ end
103
+
104
+ def ping
105
+ ping_request = RubyLLM::MCP::Requests::Ping.new(self)
106
+ if alive?
107
+ result = ping_request.call
108
+ else
109
+ transport.start
110
+
111
+ result = ping_request.call
112
+ @transport = nil
113
+ end
114
+
115
+ result.value == {}
116
+ rescue RubyLLM::MCP::Errors::TimeoutError, RubyLLM::MCP::Errors::TransportError
117
+ false
118
+ end
119
+
120
+ def process_notification(result)
121
+ notification = result.notification
122
+ NotificationHandler.new(self).execute(notification)
123
+ end
124
+
125
+ def process_request(result)
126
+ ResponseHandler.new(self).execute(result)
127
+ end
128
+
129
+ def initialize_request
130
+ RubyLLM::MCP::Requests::Initialization.new(self).call
131
+ end
132
+
133
+ def tool_list(cursor: nil)
134
+ result = RubyLLM::MCP::Requests::ToolList.new(self, cursor: cursor).call
135
+ result.raise_error! if result.error?
136
+
137
+ if result.next_cursor?
138
+ result.value["tools"] + tool_list(cursor: result.next_cursor)
139
+ else
140
+ result.value["tools"]
141
+ end
142
+ end
143
+
144
+ def execute_tool(**args)
145
+ if client.human_in_the_loop?
146
+ name = args[:name]
147
+ params = args[:parameters]
148
+ unless client.on[:human_in_the_loop].call(name, params)
149
+ result = Result.new(
150
+ {
151
+ "result" => {
152
+ "isError" => true,
153
+ "content" => [{ "type" => "text", "text" => "Tool call was cancelled by the client" }]
154
+ }
155
+ }
156
+ )
157
+ return result
158
+ end
159
+ end
160
+
161
+ RubyLLM::MCP::Requests::ToolCall.new(self, **args).call
162
+ end
163
+
164
+ def register_resource(resource)
165
+ @client.linked_resources << resource
166
+ @client.resources[resource.name] = resource
167
+ end
168
+
169
+ def resource_list(cursor: nil)
170
+ result = RubyLLM::MCP::Requests::ResourceList.new(self, cursor: cursor).call
171
+ result.raise_error! if result.error?
172
+
173
+ if result.next_cursor?
174
+ result.value["resources"] + resource_list(cursor: result.next_cursor)
175
+ else
176
+ result.value["resources"]
177
+ end
178
+ end
179
+
180
+ def resource_read(**args)
181
+ RubyLLM::MCP::Requests::ResourceRead.new(self, **args).call
182
+ end
183
+
184
+ def resource_template_list(cursor: nil)
185
+ result = RubyLLM::MCP::Requests::ResourceTemplateList.new(self, cursor: cursor).call
186
+ result.raise_error! if result.error?
187
+
188
+ if result.next_cursor?
189
+ result.value["resourceTemplates"] + resource_template_list(cursor: result.next_cursor)
190
+ else
191
+ result.value["resourceTemplates"]
192
+ end
193
+ end
194
+
195
+ def resources_subscribe(**args)
196
+ RubyLLM::MCP::Requests::ResourcesSubscribe.new(self, **args).call
197
+ end
198
+
199
+ def prompt_list(cursor: nil)
200
+ result = RubyLLM::MCP::Requests::PromptList.new(self, cursor: cursor).call
201
+ result.raise_error! if result.error?
202
+
203
+ if result.next_cursor?
204
+ result.value["prompts"] + prompt_list(cursor: result.next_cursor)
205
+ else
206
+ result.value["prompts"]
207
+ end
208
+ end
209
+
210
+ def execute_prompt(**args)
211
+ RubyLLM::MCP::Requests::PromptCall.new(self, **args).call
212
+ end
213
+
214
+ def completion_resource(**args)
215
+ RubyLLM::MCP::Requests::CompletionResource.new(self, **args).call
216
+ end
217
+
218
+ def completion_prompt(**args)
219
+ RubyLLM::MCP::Requests::CompletionPrompt.new(self, **args).call
220
+ end
221
+
222
+ def set_logging(**args)
223
+ RubyLLM::MCP::Requests::LoggingSetLevel.new(self, **args).call
224
+ end
225
+
226
+ ## Notifications
227
+ #
228
+ def initialize_notification
229
+ RubyLLM::MCP::Notifications::Initialize.new(self).call
230
+ end
231
+
232
+ def cancelled_notification(**args)
233
+ RubyLLM::MCP::Notifications::Cancelled.new(self, **args).call
234
+ end
235
+
236
+ def roots_list_change_notification
237
+ RubyLLM::MCP::Notifications::RootsListChange.new(self).call
238
+ end
239
+
240
+ ## Responses
241
+ #
242
+ def ping_response(**args)
243
+ RubyLLM::MCP::Responses::Ping.new(self, **args).call
244
+ end
245
+
246
+ def roots_list_response(**args)
247
+ RubyLLM::MCP::Responses::RootsList.new(self, **args).call
248
+ end
249
+
250
+ def sampling_create_message_response(**args)
251
+ RubyLLM::MCP::Responses::SamplingCreateMessage.new(self, **args).call
252
+ end
253
+
254
+ def error_response(**args)
255
+ RubyLLM::MCP::Responses::Error.new(self, **args).call
256
+ end
257
+
258
+ def elicitation_response(**args)
259
+ RubyLLM::MCP::Responses::Elicitation.new(self, **args).call
260
+ end
261
+
262
+ def client_capabilities
263
+ capabilities = {}
264
+
265
+ if client.roots.active?
266
+ capabilities[:roots] = {
267
+ listChanged: true
268
+ }
269
+ end
270
+
271
+ if sampling_enabled?
272
+ capabilities[:sampling] = {}
273
+ end
274
+
275
+ if client.elicitation_enabled?
276
+ capabilities[:elicitation] = {}
277
+ end
278
+
279
+ capabilities
280
+ end
281
+
282
+ def transport
283
+ @transport ||= RubyLLM::MCP::Transport.new(@transport_type, self, config: @config)
284
+ end
285
+
286
+ # Get OAuth provider from transport if available
287
+ # @return [OAuthProvider, BrowserOAuthProvider, nil] OAuth provider or nil
288
+ def transport_oauth_provider
289
+ return nil unless @transport
290
+
291
+ transport_protocol = @transport.transport_protocol
292
+ return nil unless transport_protocol.respond_to?(:oauth_provider)
293
+
294
+ transport_protocol.oauth_provider
295
+ end
296
+
297
+ private
298
+
299
+ def sampling_enabled?
300
+ MCP.config.sampling.enabled?
301
+ end
302
+ end
303
+ end
304
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json-schema"
4
+
3
5
  module RubyLLM
4
6
  module MCP
5
7
  class Elicitation
@@ -19,20 +21,16 @@ module RubyLLM
19
21
  end
20
22
 
21
23
  def execute
22
- success = @coordinator.elicitation_callback&.call(self)
23
-
24
+ success = @coordinator.client.on[:elicitation].call(self)
24
25
  if success
25
26
  valid = validate_response
26
27
  if valid
27
- @coordinator.elicitation_response(id: @id,
28
- elicitation: {
29
- action: ACCEPT_ACTION, content: @structured_response
30
- })
28
+ @coordinator.elicitation_response(id: @id, action: ACCEPT_ACTION, content: @structured_response)
31
29
  else
32
- @coordinator.elicitation_response(id: @id, elicitation: { action: CANCEL_ACTION, content: nil })
30
+ @coordinator.elicitation_response(id: @id, action: CANCEL_ACTION, content: nil)
33
31
  end
34
32
  else
35
- @coordinator.elicitation_response(id: @id, elicitation: { action: REJECT_ACTION, content: nil })
33
+ @coordinator.elicitation_response(id: @id, action: REJECT_ACTION, content: nil)
36
34
  end
37
35
  end
38
36
 
@@ -71,21 +71,6 @@ module RubyLLM
71
71
  class UnknownRequest < BaseError; end
72
72
 
73
73
  class UnsupportedProtocolVersion < BaseError; end
74
-
75
- class UnsupportedFeature < BaseError; end
76
-
77
- class UnsupportedTransport < BaseError; end
78
-
79
- class AdapterConfigurationError < BaseError; end
80
-
81
- class RequestCancelled < BaseError
82
- attr_reader :request_id
83
-
84
- def initialize(message:, request_id:)
85
- @request_id = request_id
86
- super(message: message)
87
- end
88
- end
89
74
  end
90
75
  end
91
76
  end
@@ -3,10 +3,11 @@
3
3
  module RubyLLM
4
4
  module MCP
5
5
  class NotificationHandler
6
- attr_reader :client
6
+ attr_reader :coordinator, :client
7
7
 
8
- def initialize(client)
9
- @client = client
8
+ def initialize(coordinator)
9
+ @coordinator = coordinator
10
+ @client = coordinator.client
10
11
  end
11
12
 
12
13
  def execute(notification)
@@ -24,7 +25,7 @@ module RubyLLM
24
25
  when "notifications/progress"
25
26
  process_progress_message(notification)
26
27
  when "notifications/cancelled"
27
- process_cancelled_notification(notification)
28
+ # TODO: - do nothing at the moment until we support client operations
28
29
  else
29
30
  process_unknown_notification(notification)
30
31
  end
@@ -74,23 +75,6 @@ module RubyLLM
74
75
  end
75
76
  end
76
77
 
77
- def process_cancelled_notification(notification)
78
- request_id = notification.params["requestId"]
79
- reason = notification.params["reason"] || "No reason provided"
80
-
81
- RubyLLM::MCP.logger.info(
82
- "Received cancellation for request #{request_id}: #{reason}"
83
- )
84
-
85
- success = client.cancel_in_flight_request(request_id)
86
-
87
- unless success
88
- RubyLLM::MCP.logger.debug(
89
- "Request #{request_id} was not found or already completed"
90
- )
91
- end
92
- end
93
-
94
78
  def process_unknown_notification(notification)
95
79
  message = "Unknown notification type: #{notification.type} params: #{notification.params.to_h}"
96
80
  RubyLLM::MCP.logger.error(message)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ module Notifications
6
+ class Cancelled
7
+ def initialize(coordinator, request_id:, reason:)
8
+ @coordinator = coordinator
9
+ @request_id = request_id
10
+ @reason = reason
11
+ end
12
+
13
+ def call
14
+ @coordinator.request(cancelled_notification_body, add_id: false, wait_for_response: false)
15
+ end
16
+
17
+ private
18
+
19
+ def cancelled_notification_body
20
+ {
21
+ jsonrpc: "2.0",
22
+ method: "notifications/cancelled",
23
+ params: {
24
+ requestId: @request_id,
25
+ reason: @reason
26
+ }
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ module Notifications
6
+ class Initialize
7
+ def initialize(coordinator)
8
+ @coordinator = coordinator
9
+ end
10
+
11
+ def call
12
+ @coordinator.request(notification_body, add_id: true, wait_for_response: false)
13
+ end
14
+
15
+ def notification_body
16
+ {
17
+ jsonrpc: "2.0",
18
+ method: "notifications/initialized"
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -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
@@ -21,10 +21,10 @@ module RubyLLM
21
21
  end
22
22
  end
23
23
 
24
- attr_reader :name, :description, :arguments, :adapter
24
+ attr_reader :name, :description, :arguments, :coordinator
25
25
 
26
- def initialize(adapter, prompt)
27
- @adapter = adapter
26
+ def initialize(coordinator, prompt)
27
+ @coordinator = coordinator
28
28
  @name = prompt["name"]
29
29
  @description = prompt["description"]
30
30
  @arguments = parse_arguments(prompt["arguments"])
@@ -51,8 +51,8 @@ module RubyLLM
51
51
  alias say ask
52
52
 
53
53
  def complete(argument, value, context: nil)
54
- if @adapter.capabilities.completion?
55
- result = @adapter.completion_prompt(name: @name, argument: argument, value: value, context: context)
54
+ if @coordinator.capabilities.completion?
55
+ result = @coordinator.completion_prompt(name: @name, argument: argument, value: value, context: context)
56
56
  if result.error?
57
57
  return result.to_error
58
58
  end
@@ -80,7 +80,7 @@ module RubyLLM
80
80
  private
81
81
 
82
82
  def fetch_prompt_messages(arguments)
83
- result = @adapter.execute_prompt(
83
+ result = @coordinator.execute_prompt(
84
84
  name: @name,
85
85
  arguments: arguments
86
86
  )
@@ -113,7 +113,7 @@ module RubyLLM
113
113
  attachment = MCP::Attachment.new(content["content"], content["mime_type"])
114
114
  MCP::Content.new(text: nil, attachments: [attachment])
115
115
  when "resource"
116
- resource = Resource.new(adapter, content["resource"])
116
+ resource = Resource.new(coordinator, content["resource"])
117
117
  resource.to_content
118
118
  end
119
119
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ module Protocol
6
+ module_function
7
+
8
+ LATEST_PROTOCOL_VERSION = "2025-06-18"
9
+ DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26"
10
+ SUPPORTED_PROTOCOL_VERSIONS = [
11
+ LATEST_PROTOCOL_VERSION,
12
+ "2025-03-26",
13
+ "2024-11-05",
14
+ "2024-10-07"
15
+ ].freeze
16
+
17
+ def supported_version?(version)
18
+ SUPPORTED_PROTOCOL_VERSIONS.include?(version)
19
+ end
20
+
21
+ def supported_versions
22
+ SUPPORTED_PROTOCOL_VERSIONS
23
+ end
24
+
25
+ def latest_version
26
+ LATEST_PROTOCOL_VERSION
27
+ end
28
+
29
+ def default_negotiated_version
30
+ DEFAULT_NEGOTIATED_PROTOCOL_VERSION
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if defined?(Rails::Railtie)
4
- module RubyLLM
5
- module MCP
6
- class Railtie < Rails::Railtie
7
- generators do
8
- require_relative "../../generators/ruby_llm/mcp/install/install_generator"
9
- require_relative "../../generators/ruby_llm/mcp/oauth/install_generator"
10
- end
3
+ module RubyLLM
4
+ module MCP
5
+ class Railtie < Rails::Railtie
6
+ generators do
7
+ require_relative "../../generators/ruby_llm/mcp/install/install_generator"
8
+ require_relative "../../generators/ruby_llm/mcp/oauth/install_generator"
11
9
  end
12
10
  end
13
11
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ module Requests
6
+ class CompletionPrompt
7
+ def initialize(coordinator, name:, argument:, value:, context: nil)
8
+ @coordinator = coordinator
9
+ @name = name
10
+ @argument = argument
11
+ @value = value
12
+ @context = context
13
+ end
14
+
15
+ def call
16
+ @coordinator.request(request_body)
17
+ end
18
+
19
+ private
20
+
21
+ def request_body
22
+ {
23
+ jsonrpc: "2.0",
24
+ id: 1,
25
+ method: "completion/complete",
26
+ params: {
27
+ ref: {
28
+ type: "ref/prompt",
29
+ name: @name
30
+ },
31
+ argument: {
32
+ name: @argument,
33
+ value: @value
34
+ },
35
+ context: format_context
36
+ }.compact
37
+ }
38
+ end
39
+
40
+ def format_context
41
+ return nil if @context.nil?
42
+
43
+ {
44
+ arguments: @context
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ module Requests
6
+ class CompletionResource
7
+ def initialize(coordinator, uri:, argument:, value:, context: nil)
8
+ @coordinator = coordinator
9
+ @uri = uri
10
+ @argument = argument
11
+ @value = value
12
+ @context = context
13
+ end
14
+
15
+ def call
16
+ @coordinator.request(request_body)
17
+ end
18
+
19
+ private
20
+
21
+ def request_body
22
+ {
23
+ jsonrpc: "2.0",
24
+ id: 1,
25
+ method: "completion/complete",
26
+ params: {
27
+ ref: {
28
+ type: "ref/resource",
29
+ uri: @uri
30
+ },
31
+ argument: {
32
+ name: @argument,
33
+ value: @value
34
+ },
35
+ context: format_context
36
+ }.compact
37
+ }
38
+ end
39
+
40
+ def format_context
41
+ return nil if @context.nil?
42
+
43
+ {
44
+ arguments: @context
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end