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.
- checksums.yaml +4 -4
- data/README.md +296 -25
- data/lib/ruby_llm/chat.rb +2 -1
- data/lib/ruby_llm/mcp/client.rb +32 -13
- data/lib/ruby_llm/mcp/configuration.rb +123 -3
- data/lib/ruby_llm/mcp/coordinator.rb +108 -115
- data/lib/ruby_llm/mcp/errors.rb +3 -1
- data/lib/ruby_llm/mcp/notification_handler.rb +84 -0
- data/lib/ruby_llm/mcp/{requests/cancelled_notification.rb → notifications/cancelled.rb} +2 -2
- data/lib/ruby_llm/mcp/{requests/initialize_notification.rb → notifications/initialize.rb} +7 -3
- data/lib/ruby_llm/mcp/notifications/roots_list_change.rb +26 -0
- data/lib/ruby_llm/mcp/parameter.rb +19 -1
- data/lib/ruby_llm/mcp/progress.rb +3 -1
- data/lib/ruby_llm/mcp/prompt.rb +18 -0
- data/lib/ruby_llm/mcp/railtie.rb +20 -0
- data/lib/ruby_llm/mcp/requests/initialization.rb +8 -4
- data/lib/ruby_llm/mcp/requests/ping.rb +6 -2
- data/lib/ruby_llm/mcp/requests/prompt_list.rb +10 -2
- data/lib/ruby_llm/mcp/requests/resource_list.rb +12 -2
- data/lib/ruby_llm/mcp/requests/resource_template_list.rb +12 -2
- data/lib/ruby_llm/mcp/requests/shared/meta.rb +32 -0
- data/lib/ruby_llm/mcp/requests/shared/pagination.rb +17 -0
- data/lib/ruby_llm/mcp/requests/tool_call.rb +1 -1
- data/lib/ruby_llm/mcp/requests/tool_list.rb +10 -2
- data/lib/ruby_llm/mcp/resource.rb +17 -0
- data/lib/ruby_llm/mcp/response_handler.rb +58 -0
- data/lib/ruby_llm/mcp/responses/error.rb +33 -0
- data/lib/ruby_llm/mcp/{requests/ping_response.rb → responses/ping.rb} +2 -2
- data/lib/ruby_llm/mcp/responses/roots_list.rb +31 -0
- data/lib/ruby_llm/mcp/responses/sampling_create_message.rb +50 -0
- data/lib/ruby_llm/mcp/result.rb +21 -8
- data/lib/ruby_llm/mcp/roots.rb +45 -0
- data/lib/ruby_llm/mcp/sample.rb +148 -0
- data/lib/ruby_llm/mcp/{capabilities.rb → server_capabilities.rb} +1 -1
- data/lib/ruby_llm/mcp/tool.rb +35 -4
- data/lib/ruby_llm/mcp/transport.rb +58 -0
- data/lib/ruby_llm/mcp/transports/http_client.rb +26 -0
- data/lib/ruby_llm/mcp/{transport → transports}/sse.rb +25 -24
- data/lib/ruby_llm/mcp/{transport → transports}/stdio.rb +28 -26
- data/lib/ruby_llm/mcp/{transport → transports}/streamable_http.rb +25 -29
- data/lib/ruby_llm/mcp/transports/timeout.rb +32 -0
- data/lib/ruby_llm/mcp/version.rb +1 -1
- data/lib/ruby_llm/mcp.rb +50 -9
- metadata +23 -12
- data/lib/ruby_llm/mcp/requests/base.rb +0 -31
- data/lib/ruby_llm/mcp/requests/meta.rb +0 -30
- 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, :
|
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
|
-
|
29
|
+
transport.request(body, **options)
|
28
30
|
rescue RubyLLM::MCP::Errors::TimeoutError => e
|
29
|
-
if
|
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
|
-
|
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::
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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::
|
213
|
+
RubyLLM::MCP::Notifications::Initialize.new(self).call
|
195
214
|
end
|
196
215
|
|
197
216
|
def cancelled_notification(**args)
|
198
|
-
RubyLLM::MCP::
|
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
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
263
|
-
|
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
|
269
|
-
|
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
|
data/lib/ruby_llm/mcp/errors.rb
CHANGED
@@ -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,10 +2,14 @@
|
|
2
2
|
|
3
3
|
module RubyLLM
|
4
4
|
module MCP
|
5
|
-
module
|
6
|
-
class
|
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
|
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
|
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
|
data/lib/ruby_llm/mcp/prompt.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
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",
|