ruby_llm-mcp 0.3.1 → 0.4.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 +121 -2
- data/lib/ruby_llm/mcp/capabilities.rb +22 -2
- data/lib/ruby_llm/mcp/client.rb +106 -18
- data/lib/ruby_llm/mcp/configuration.rb +66 -0
- data/lib/ruby_llm/mcp/coordinator.rb +197 -33
- data/lib/ruby_llm/mcp/error.rb +34 -0
- data/lib/ruby_llm/mcp/errors.rb +37 -4
- data/lib/ruby_llm/mcp/logging.rb +16 -0
- data/lib/ruby_llm/mcp/parameter.rb +2 -0
- data/lib/ruby_llm/mcp/progress.rb +33 -0
- data/lib/ruby_llm/mcp/prompt.rb +12 -5
- data/lib/ruby_llm/mcp/providers/anthropic/complex_parameter_support.rb +5 -2
- data/lib/ruby_llm/mcp/providers/gemini/complex_parameter_support.rb +6 -3
- data/lib/ruby_llm/mcp/providers/openai/complex_parameter_support.rb +6 -3
- data/lib/ruby_llm/mcp/requests/base.rb +3 -3
- data/lib/ruby_llm/mcp/requests/cancelled_notification.rb +32 -0
- data/lib/ruby_llm/mcp/requests/completion_prompt.rb +3 -3
- data/lib/ruby_llm/mcp/requests/completion_resource.rb +3 -3
- data/lib/ruby_llm/mcp/requests/initialization.rb +24 -18
- data/lib/ruby_llm/mcp/requests/initialize_notification.rb +15 -9
- data/lib/ruby_llm/mcp/requests/logging_set_level.rb +28 -0
- data/lib/ruby_llm/mcp/requests/meta.rb +30 -0
- data/lib/ruby_llm/mcp/requests/ping.rb +20 -0
- data/lib/ruby_llm/mcp/requests/ping_response.rb +28 -0
- data/lib/ruby_llm/mcp/requests/prompt_call.rb +3 -3
- data/lib/ruby_llm/mcp/requests/prompt_list.rb +1 -1
- data/lib/ruby_llm/mcp/requests/resource_list.rb +1 -1
- data/lib/ruby_llm/mcp/requests/resource_read.rb +4 -4
- data/lib/ruby_llm/mcp/requests/resource_template_list.rb +1 -1
- data/lib/ruby_llm/mcp/requests/resources_subscribe.rb +30 -0
- data/lib/ruby_llm/mcp/requests/tool_call.rb +6 -3
- data/lib/ruby_llm/mcp/requests/tool_list.rb +17 -11
- data/lib/ruby_llm/mcp/resource.rb +26 -5
- data/lib/ruby_llm/mcp/resource_template.rb +11 -6
- data/lib/ruby_llm/mcp/result.rb +90 -0
- data/lib/ruby_llm/mcp/tool.rb +28 -3
- data/lib/ruby_llm/mcp/transport/sse.rb +81 -75
- data/lib/ruby_llm/mcp/transport/stdio.rb +33 -17
- data/lib/ruby_llm/mcp/transport/streamable_http.rb +647 -0
- data/lib/ruby_llm/mcp/version.rb +1 -1
- data/lib/ruby_llm/mcp.rb +18 -0
- data/lib/tasks/release.rake +23 -0
- metadata +20 -50
- data/lib/ruby_llm/mcp/transport/streamable.rb +0 -299
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "logger"
|
4
|
+
|
3
5
|
module RubyLLM
|
4
6
|
module MCP
|
5
7
|
class Coordinator
|
@@ -23,30 +25,29 @@ module RubyLLM
|
|
23
25
|
|
24
26
|
def request(body, **options)
|
25
27
|
@transport.request(body, **options)
|
28
|
+
rescue RubyLLM::MCP::Errors::TimeoutError => e
|
29
|
+
if @transport.alive?
|
30
|
+
cancelled_notification(reason: "Request timed out", request_id: e.request_id)
|
31
|
+
end
|
32
|
+
raise e
|
26
33
|
end
|
27
34
|
|
28
35
|
def start_transport
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@transport
|
41
|
-
request_timeout: @config[:request_timeout],
|
42
|
-
headers: @headers)
|
43
|
-
else
|
44
|
-
message = "Invalid transport type: :#{transport_type}. Supported types are :sse, :stdio, :streamable"
|
45
|
-
raise Errors::InvalidTransportType.new(message: message)
|
36
|
+
build_transport
|
37
|
+
|
38
|
+
initialize_response = initialize_request
|
39
|
+
initialize_response.raise_error! if initialize_response.error?
|
40
|
+
|
41
|
+
# Extract and store the negotiated protocol version
|
42
|
+
negotiated_version = initialize_response.value["protocolVersion"]
|
43
|
+
@protocol_version = negotiated_version if negotiated_version
|
44
|
+
|
45
|
+
# Set the protocol version on the transport for subsequent requests
|
46
|
+
if @transport.respond_to?(:set_protocol_version)
|
47
|
+
@transport.set_protocol_version(@protocol_version)
|
46
48
|
end
|
47
49
|
|
48
|
-
@
|
49
|
-
@capabilities = RubyLLM::MCP::Capabilities.new(@initialize_response["result"]["capabilities"])
|
50
|
+
@capabilities = RubyLLM::MCP::Capabilities.new(initialize_response.value["capabilities"])
|
50
51
|
initialize_notification
|
51
52
|
end
|
52
53
|
|
@@ -64,48 +65,211 @@ module RubyLLM
|
|
64
65
|
!!@transport&.alive?
|
65
66
|
end
|
66
67
|
|
68
|
+
def ping
|
69
|
+
ping_request = RubyLLM::MCP::Requests::Ping.new(self)
|
70
|
+
if alive?
|
71
|
+
result = ping_request.call
|
72
|
+
else
|
73
|
+
build_transport
|
74
|
+
|
75
|
+
result = ping_request.call
|
76
|
+
@transport = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
result.value == {}
|
80
|
+
rescue RubyLLM::MCP::Errors::TimeoutError, RubyLLM::MCP::Errors::TransportError
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
def process_notification(result)
|
85
|
+
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
|
108
|
+
end
|
109
|
+
|
110
|
+
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}")
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize_request
|
122
|
+
RubyLLM::MCP::Requests::Initialization.new(self).call
|
123
|
+
end
|
124
|
+
|
125
|
+
def tool_list
|
126
|
+
result = RubyLLM::MCP::Requests::ToolList.new(self).call
|
127
|
+
result.raise_error! if result.error?
|
128
|
+
|
129
|
+
result.value["tools"]
|
130
|
+
end
|
131
|
+
|
67
132
|
def execute_tool(**args)
|
133
|
+
if client.human_in_the_loop?
|
134
|
+
name = args[:name]
|
135
|
+
params = args[:parameters]
|
136
|
+
unless client.on[:human_in_the_loop].call(name, params)
|
137
|
+
result = Result.new(
|
138
|
+
{
|
139
|
+
"result" => {
|
140
|
+
"isError" => true,
|
141
|
+
"content" => [{ "type" => "text", "text" => "Tool call was cancelled by the client" }]
|
142
|
+
}
|
143
|
+
}
|
144
|
+
)
|
145
|
+
return result
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
68
149
|
RubyLLM::MCP::Requests::ToolCall.new(self, **args).call
|
69
150
|
end
|
70
151
|
|
152
|
+
def resource_list
|
153
|
+
result = RubyLLM::MCP::Requests::ResourceList.new(self).call
|
154
|
+
result.raise_error! if result.error?
|
155
|
+
|
156
|
+
result.value["resources"]
|
157
|
+
end
|
158
|
+
|
71
159
|
def resource_read(**args)
|
72
160
|
RubyLLM::MCP::Requests::ResourceRead.new(self, **args).call
|
73
161
|
end
|
74
162
|
|
75
|
-
def
|
76
|
-
RubyLLM::MCP::Requests::
|
163
|
+
def resource_template_list
|
164
|
+
result = RubyLLM::MCP::Requests::ResourceTemplateList.new(self).call
|
165
|
+
result.raise_error! if result.error?
|
166
|
+
|
167
|
+
result.value["resourceTemplates"]
|
77
168
|
end
|
78
169
|
|
79
|
-
def
|
80
|
-
RubyLLM::MCP::Requests::
|
170
|
+
def resources_subscribe(**args)
|
171
|
+
RubyLLM::MCP::Requests::ResourcesSubscribe.new(self, **args).call
|
172
|
+
end
|
173
|
+
|
174
|
+
def prompt_list
|
175
|
+
result = RubyLLM::MCP::Requests::PromptList.new(self).call
|
176
|
+
result.raise_error! if result.error?
|
177
|
+
|
178
|
+
result.value["prompts"]
|
81
179
|
end
|
82
180
|
|
83
181
|
def execute_prompt(**args)
|
84
182
|
RubyLLM::MCP::Requests::PromptCall.new(self, **args).call
|
85
183
|
end
|
86
184
|
|
87
|
-
def
|
88
|
-
RubyLLM::MCP::Requests::
|
185
|
+
def completion_resource(**args)
|
186
|
+
RubyLLM::MCP::Requests::CompletionResource.new(self, **args).call
|
187
|
+
end
|
188
|
+
|
189
|
+
def completion_prompt(**args)
|
190
|
+
RubyLLM::MCP::Requests::CompletionPrompt.new(self, **args).call
|
89
191
|
end
|
90
192
|
|
91
193
|
def initialize_notification
|
92
194
|
RubyLLM::MCP::Requests::InitializeNotification.new(self).call
|
93
195
|
end
|
94
196
|
|
95
|
-
def
|
96
|
-
RubyLLM::MCP::Requests::
|
197
|
+
def cancelled_notification(**args)
|
198
|
+
RubyLLM::MCP::Requests::CancelledNotification.new(self, **args).call
|
97
199
|
end
|
98
200
|
|
99
|
-
def
|
100
|
-
RubyLLM::MCP::Requests::
|
201
|
+
def ping_response(id: nil)
|
202
|
+
RubyLLM::MCP::Requests::PingResponse.new(self, id: id).call
|
101
203
|
end
|
102
204
|
|
103
|
-
def
|
104
|
-
RubyLLM::MCP::Requests::
|
205
|
+
def set_logging(level:)
|
206
|
+
RubyLLM::MCP::Requests::LoggingSetLevel.new(self, level: level).call
|
105
207
|
end
|
106
208
|
|
107
|
-
def
|
108
|
-
|
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
|
231
|
+
end
|
232
|
+
|
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
|
239
|
+
end
|
240
|
+
|
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"])
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def name
|
263
|
+
client.name
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
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
|
109
273
|
end
|
110
274
|
end
|
111
275
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module MCP
|
5
|
+
class Error
|
6
|
+
def initialize(error_data)
|
7
|
+
@code = error_data["code"]
|
8
|
+
@message = error_data["message"]
|
9
|
+
@data = error_data["data"]
|
10
|
+
end
|
11
|
+
|
12
|
+
def type
|
13
|
+
case @code
|
14
|
+
when -32_700
|
15
|
+
:parse_error
|
16
|
+
when -32_600
|
17
|
+
:invalid_request
|
18
|
+
when -32_601
|
19
|
+
:method_not_found
|
20
|
+
when -32_602
|
21
|
+
:invalid_params
|
22
|
+
when -32_603
|
23
|
+
:internal_error
|
24
|
+
else
|
25
|
+
:custom_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"Error: code: #{@code} (#{type}), message: #{@message}, data: #{@data}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/ruby_llm/mcp/errors.rb
CHANGED
@@ -12,17 +12,50 @@ module RubyLLM
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
module Capabilities
|
16
|
+
class CompletionNotAvailable < BaseError; end
|
17
|
+
class ResourceSubscribeNotAvailable < BaseError; end
|
18
|
+
end
|
19
|
+
|
20
|
+
class InvalidProtocolVersionError < BaseError; end
|
21
|
+
|
22
|
+
class InvalidTransportType < BaseError; end
|
23
|
+
|
24
|
+
class ProgressHandlerNotAvailable < BaseError; end
|
16
25
|
|
17
26
|
class PromptArgumentError < BaseError; end
|
18
27
|
|
19
|
-
class
|
28
|
+
class ResponseError < BaseError
|
29
|
+
attr_reader :error
|
30
|
+
|
31
|
+
def initialize(message:, error:)
|
32
|
+
@error = error
|
33
|
+
super(message: message)
|
34
|
+
end
|
35
|
+
end
|
20
36
|
|
21
37
|
class SessionExpiredError < BaseError; end
|
22
38
|
|
23
|
-
class TimeoutError < BaseError
|
39
|
+
class TimeoutError < BaseError
|
40
|
+
attr_reader :request_id
|
24
41
|
|
25
|
-
|
42
|
+
def initialize(message:, request_id:)
|
43
|
+
@request_id = request_id
|
44
|
+
super(message: message)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class TransportError < BaseError
|
49
|
+
attr_reader :code, :error
|
50
|
+
|
51
|
+
def initialize(message:, code: nil, error: nil)
|
52
|
+
@code = code
|
53
|
+
@error = error
|
54
|
+
super(message: message)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class UnknownRequest < BaseError; end
|
26
59
|
end
|
27
60
|
end
|
28
61
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module MCP
|
5
|
+
module Logging
|
6
|
+
DEBUG = "debug"
|
7
|
+
INFO = "info"
|
8
|
+
NOTICE = "notice"
|
9
|
+
WARNING = "warning"
|
10
|
+
ERROR = "error"
|
11
|
+
CRITICAL = "critical"
|
12
|
+
ALERT = "alert"
|
13
|
+
EMERGENCY = "emergency"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module MCP
|
5
|
+
class Progress
|
6
|
+
attr_reader :progress_token, :progress, :total, :message, :client
|
7
|
+
|
8
|
+
def initialize(coordinator, progress_handler, progress_data)
|
9
|
+
@coordinator = coordinator
|
10
|
+
@client = coordinator.client
|
11
|
+
@progress_handler = progress_handler
|
12
|
+
|
13
|
+
@progress_token = progress_data["progressToken"]
|
14
|
+
@progress = progress_data["progress"]
|
15
|
+
@total = progress_data["total"]
|
16
|
+
@message = progress_data["message"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute_progress_handler
|
20
|
+
@progress_handler&.call(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
progress_token: @progress_token,
|
26
|
+
progress: @progress,
|
27
|
+
total: @total,
|
28
|
+
message: @message
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/ruby_llm/mcp/prompt.rb
CHANGED
@@ -44,24 +44,31 @@ module RubyLLM
|
|
44
44
|
|
45
45
|
def complete(argument, value)
|
46
46
|
if @coordinator.capabilities.completion?
|
47
|
-
|
48
|
-
|
47
|
+
result = @coordinator.completion_prompt(name: @name, argument: argument, value: value)
|
48
|
+
if result.error?
|
49
|
+
return result.to_error
|
50
|
+
end
|
51
|
+
|
52
|
+
response = result.value["completion"]
|
49
53
|
|
50
54
|
Completion.new(values: response["values"], total: response["total"], has_more: response["hasMore"])
|
51
55
|
else
|
52
|
-
|
56
|
+
message = "Completion is not available for this MCP server"
|
57
|
+
raise Errors::Capabilities::CompletionNotAvailable.new(message: message)
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
56
61
|
private
|
57
62
|
|
58
63
|
def fetch_prompt_messages(arguments)
|
59
|
-
|
64
|
+
result = @coordinator.execute_prompt(
|
60
65
|
name: @name,
|
61
66
|
arguments: arguments
|
62
67
|
)
|
63
68
|
|
64
|
-
|
69
|
+
result.raise_error! if result.error?
|
70
|
+
|
71
|
+
result.value["messages"].map do |message|
|
65
72
|
content = create_content_for_message(message["content"])
|
66
73
|
|
67
74
|
RubyLLM::Message.new(
|
@@ -23,11 +23,13 @@ module RubyLLM
|
|
23
23
|
if param.item_type == :object
|
24
24
|
{
|
25
25
|
type: param.type,
|
26
|
+
description: param.description,
|
26
27
|
items: { type: param.item_type, properties: clean_parameters(param.properties) }
|
27
|
-
}
|
28
|
+
}.compact
|
28
29
|
else
|
29
30
|
{
|
30
31
|
type: param.type,
|
32
|
+
description: param.description,
|
31
33
|
default: param.default,
|
32
34
|
items: { type: param.item_type, enum: param.enum }.compact
|
33
35
|
}.compact
|
@@ -35,9 +37,10 @@ module RubyLLM
|
|
35
37
|
when :object
|
36
38
|
{
|
37
39
|
type: param.type,
|
40
|
+
description: param.description,
|
38
41
|
properties: clean_parameters(param.properties),
|
39
42
|
required: required_parameters(param.properties)
|
40
|
-
}
|
43
|
+
}.compact
|
41
44
|
when :union
|
42
45
|
{
|
43
46
|
param.union_type => param.properties.map { |property| build_properties(property) }
|
@@ -16,20 +16,22 @@ module RubyLLM
|
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
|
-
def build_properties(param)
|
19
|
+
def build_properties(param) # rubocop:disable Metrics/MethodLength
|
20
20
|
properties = case param.type
|
21
21
|
when :array
|
22
22
|
if param.item_type == :object
|
23
23
|
{
|
24
24
|
type: param_type_for_gemini(param.type),
|
25
|
+
description: param.description,
|
25
26
|
items: {
|
26
27
|
type: param_type_for_gemini(param.item_type),
|
27
28
|
properties: param.properties.transform_values { |value| build_properties(value) }
|
28
29
|
}
|
29
|
-
}
|
30
|
+
}.compact
|
30
31
|
else
|
31
32
|
{
|
32
33
|
type: param_type_for_gemini(param.type),
|
34
|
+
description: param.description,
|
33
35
|
default: param.default,
|
34
36
|
items: { type: param_type_for_gemini(param.item_type), enum: param.enum }.compact
|
35
37
|
}.compact
|
@@ -37,9 +39,10 @@ module RubyLLM
|
|
37
39
|
when :object
|
38
40
|
{
|
39
41
|
type: param_type_for_gemini(param.type),
|
42
|
+
description: param.description,
|
40
43
|
properties: param.properties.transform_values { |value| build_properties(value) },
|
41
44
|
required: param.properties.select { |_, p| p.required }.keys
|
42
|
-
}
|
45
|
+
}.compact
|
43
46
|
when :union
|
44
47
|
{
|
45
48
|
param.union_type => param.properties.map { |properties| build_properties(properties) }
|
@@ -7,20 +7,22 @@ module RubyLLM
|
|
7
7
|
module ComplexParameterSupport
|
8
8
|
module_function
|
9
9
|
|
10
|
-
def param_schema(param)
|
10
|
+
def param_schema(param) # rubocop:disable Metrics/MethodLength
|
11
11
|
properties = case param.type
|
12
12
|
when :array
|
13
13
|
if param.item_type == :object
|
14
14
|
{
|
15
15
|
type: param.type,
|
16
|
+
description: param.description,
|
16
17
|
items: {
|
17
18
|
type: param.item_type,
|
18
19
|
properties: param.properties.transform_values { |value| param_schema(value) }
|
19
20
|
}
|
20
|
-
}
|
21
|
+
}.compact
|
21
22
|
else
|
22
23
|
{
|
23
24
|
type: param.type,
|
25
|
+
description: param.description,
|
24
26
|
default: param.default,
|
25
27
|
items: { type: param.item_type, enum: param.enum }.compact
|
26
28
|
}.compact
|
@@ -28,9 +30,10 @@ module RubyLLM
|
|
28
30
|
when :object
|
29
31
|
{
|
30
32
|
type: param.type,
|
33
|
+
description: param.description,
|
31
34
|
properties: param.properties.transform_values { |value| param_schema(value) },
|
32
35
|
required: param.properties.select { |_, p| p.required }.keys
|
33
|
-
}
|
36
|
+
}.compact
|
34
37
|
when :union
|
35
38
|
{
|
36
39
|
param.union_type => param.properties.map { |property| param_schema(property) }
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module MCP
|
5
|
+
module Requests
|
6
|
+
class CancelledNotification
|
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
|
@@ -4,15 +4,15 @@ module RubyLLM
|
|
4
4
|
module MCP
|
5
5
|
module Requests
|
6
6
|
class CompletionPrompt
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(coordinator, name:, argument:, value:)
|
8
|
+
@coordinator = coordinator
|
9
9
|
@name = name
|
10
10
|
@argument = argument
|
11
11
|
@value = value
|
12
12
|
end
|
13
13
|
|
14
14
|
def call
|
15
|
-
@
|
15
|
+
@coordinator.request(request_body)
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
@@ -4,15 +4,15 @@ module RubyLLM
|
|
4
4
|
module MCP
|
5
5
|
module Requests
|
6
6
|
class CompletionResource
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(coordinator, uri:, argument:, value:)
|
8
|
+
@coordinator = coordinator
|
9
9
|
@uri = uri
|
10
10
|
@argument = argument
|
11
11
|
@value = value
|
12
12
|
end
|
13
13
|
|
14
14
|
def call
|
15
|
-
@
|
15
|
+
@coordinator.request(request_body)
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|