ruby_llm-mcp 0.6.2 → 0.6.4
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/lib/ruby_llm/mcp/parameter.rb +8 -0
- data/lib/ruby_llm/mcp/providers/anthropic/complex_parameter_support.rb +21 -7
- data/lib/ruby_llm/mcp/providers/gemini/complex_parameter_support.rb +23 -6
- data/lib/ruby_llm/mcp/providers/openai/complex_parameter_support.rb +13 -1
- data/lib/ruby_llm/mcp/transports/sse.rb +67 -27
- data/lib/ruby_llm/mcp/transports/streamable_http.rb +5 -0
- data/lib/ruby_llm/mcp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5bec47480f606a8d28f8a79032e392e331dcdcef565cd708e6dcc855f0a31445
|
|
4
|
+
data.tar.gz: ac65a564ce2bd4d39e4e4fdcff02da19cbd7573186924e938fadb2d9c2f523db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e9394538e772a3169fe424854cbf57aee643953be4e44f805c82f0f81c61189a06d755e8a14b4e530183e414e9a4b3000912a577d3eca95da236c124b9a54dc
|
|
7
|
+
data.tar.gz: a6eb704e2aaac9386e54cb178dd136bc19510a2159adc63125d50f5a96c9473de9416e33755059f9578ab609d34fe3fbe8c8d7f8e970900380b93770793376a3
|
|
@@ -7,6 +7,14 @@ module RubyLLM
|
|
|
7
7
|
class Parameter < RubyLLM::Parameter
|
|
8
8
|
attr_accessor :items, :properties, :enum, :union_type, :default, :title
|
|
9
9
|
|
|
10
|
+
class << self
|
|
11
|
+
def all_mcp_parameters?(parameters)
|
|
12
|
+
parameters.is_a?(Hash) &&
|
|
13
|
+
parameters.any? &&
|
|
14
|
+
parameters.values.all? { |p| p.is_a?(RubyLLM::MCP::Parameter) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
10
18
|
def initialize(name, type: "string", title: nil, desc: nil, required: true, default: nil, union_type: nil) # rubocop:disable Metrics/ParameterLists
|
|
11
19
|
super(name, type: type.to_sym, desc: desc, required: required)
|
|
12
20
|
@title = title
|
|
@@ -9,7 +9,7 @@ module RubyLLM
|
|
|
9
9
|
|
|
10
10
|
def clean_parameters(parameters)
|
|
11
11
|
parameters.transform_values do |param|
|
|
12
|
-
|
|
12
|
+
mcp_build_properties(param).compact
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ module RubyLLM
|
|
|
17
17
|
parameters.select { |_, param| param.required }.keys
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def
|
|
20
|
+
def mcp_build_properties(param) # rubocop:disable Metrics/MethodLength
|
|
21
21
|
case param.type
|
|
22
22
|
when :array
|
|
23
23
|
if param.item_type == :object
|
|
@@ -46,7 +46,7 @@ module RubyLLM
|
|
|
46
46
|
}.compact
|
|
47
47
|
when :union
|
|
48
48
|
{
|
|
49
|
-
param.union_type => param.properties.map { |property|
|
|
49
|
+
param.union_type => param.properties.map { |property| mcp_build_properties(property) }
|
|
50
50
|
}
|
|
51
51
|
else
|
|
52
52
|
{
|
|
@@ -63,11 +63,25 @@ module RubyLLM
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
module RubyLLM::Providers::Anthropic::Tools
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
alias original_clean_parameters clean_parameters
|
|
67
|
+
alias original_required_parameters required_parameters
|
|
68
|
+
module_function :original_clean_parameters, :original_required_parameters
|
|
69
|
+
|
|
70
|
+
def clean_parameters(parameters)
|
|
71
|
+
if RubyLLM::MCP::Parameter.all_mcp_parameters?(parameters)
|
|
72
|
+
return RubyLLM::MCP::Providers::Anthropic::ComplexParameterSupport.clean_parameters(parameters)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
original_clean_parameters(parameters)
|
|
68
76
|
end
|
|
77
|
+
module_function :clean_parameters
|
|
78
|
+
|
|
79
|
+
def required_parameters(parameters)
|
|
80
|
+
if RubyLLM::MCP::Parameter.all_mcp_parameters?(parameters)
|
|
81
|
+
return RubyLLM::MCP::Providers::Anthropic::ComplexParameterSupport.required_parameters(parameters)
|
|
82
|
+
end
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
RubyLLM::MCP::Providers::Anthropic::ComplexParameterSupport.required_parameters(parameters)
|
|
84
|
+
original_required_parameters(parameters)
|
|
72
85
|
end
|
|
86
|
+
module_function :required_parameters
|
|
73
87
|
end
|
|
@@ -11,12 +11,12 @@ module RubyLLM
|
|
|
11
11
|
def format_parameters(parameters)
|
|
12
12
|
{
|
|
13
13
|
type: "OBJECT",
|
|
14
|
-
properties: parameters.transform_values { |param|
|
|
14
|
+
properties: parameters.transform_values { |param| mcp_build_properties(param) },
|
|
15
15
|
required: parameters.select { |_, p| p.required }.keys.map(&:to_s)
|
|
16
16
|
}
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def
|
|
19
|
+
def mcp_build_properties(param) # rubocop:disable Metrics/MethodLength
|
|
20
20
|
properties = case param.type
|
|
21
21
|
when :array
|
|
22
22
|
if param.item_type == :object
|
|
@@ -26,7 +26,7 @@ module RubyLLM
|
|
|
26
26
|
description: param.description,
|
|
27
27
|
items: {
|
|
28
28
|
type: param_type_for_gemini(param.item_type),
|
|
29
|
-
properties: param.properties.transform_values { |value|
|
|
29
|
+
properties: param.properties.transform_values { |value| mcp_build_properties(value) }
|
|
30
30
|
}
|
|
31
31
|
}.compact
|
|
32
32
|
else
|
|
@@ -43,12 +43,12 @@ module RubyLLM
|
|
|
43
43
|
type: param_type_for_gemini(param.type),
|
|
44
44
|
title: param.title,
|
|
45
45
|
description: param.description,
|
|
46
|
-
properties: param.properties.transform_values { |value|
|
|
46
|
+
properties: param.properties.transform_values { |value| mcp_build_properties(value) },
|
|
47
47
|
required: param.properties.select { |_, p| p.required }.keys
|
|
48
48
|
}.compact
|
|
49
49
|
when :union
|
|
50
50
|
{
|
|
51
|
-
param.union_type => param.properties.map { |properties|
|
|
51
|
+
param.union_type => param.properties.map { |properties| mcp_build_properties(properties) }
|
|
52
52
|
}
|
|
53
53
|
else
|
|
54
54
|
{
|
|
@@ -60,10 +60,27 @@ module RubyLLM
|
|
|
60
60
|
|
|
61
61
|
properties.compact
|
|
62
62
|
end
|
|
63
|
+
|
|
64
|
+
def param_type_for_gemini(type)
|
|
65
|
+
RubyLLM::Providers::Gemini::Tools.param_type_for_gemini(type)
|
|
66
|
+
end
|
|
63
67
|
end
|
|
64
68
|
end
|
|
65
69
|
end
|
|
66
70
|
end
|
|
67
71
|
end
|
|
68
72
|
|
|
69
|
-
RubyLLM::Providers::Gemini
|
|
73
|
+
module RubyLLM::Providers::Gemini::Tools
|
|
74
|
+
alias original_format_parameters format_parameters
|
|
75
|
+
module_function :original_format_parameters
|
|
76
|
+
module_function :param_type_for_gemini
|
|
77
|
+
|
|
78
|
+
def format_parameters(parameters)
|
|
79
|
+
if RubyLLM::MCP::Parameter.all_mcp_parameters?(parameters)
|
|
80
|
+
return RubyLLM::MCP::Providers::Gemini::ComplexParameterSupport.format_parameters(parameters)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
original_format_parameters(parameters)
|
|
84
|
+
end
|
|
85
|
+
module_function :format_parameters
|
|
86
|
+
end
|
|
@@ -57,4 +57,16 @@ module RubyLLM
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
RubyLLM::Providers::OpenAI
|
|
60
|
+
module RubyLLM::Providers::OpenAI::Tools
|
|
61
|
+
alias original_param_schema param_schema
|
|
62
|
+
module_function :original_param_schema
|
|
63
|
+
|
|
64
|
+
def param_schema(param)
|
|
65
|
+
if param.is_a?(RubyLLM::MCP::Parameter)
|
|
66
|
+
return RubyLLM::MCP::Providers::OpenAI::ComplexParameterSupport.param_schema(param)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
original_param_schema(param)
|
|
70
|
+
end
|
|
71
|
+
module_function :param_schema
|
|
72
|
+
end
|
|
@@ -30,7 +30,6 @@ module RubyLLM
|
|
|
30
30
|
"Accept" => "text/event-stream",
|
|
31
31
|
"Content-Type" => "application/json",
|
|
32
32
|
"Cache-Control" => "no-cache",
|
|
33
|
-
"Connection" => "keep-alive",
|
|
34
33
|
"X-CLIENT-ID" => @client_id
|
|
35
34
|
})
|
|
36
35
|
|
|
@@ -167,41 +166,82 @@ module RubyLLM
|
|
|
167
166
|
end
|
|
168
167
|
|
|
169
168
|
def stream_events_from_server
|
|
170
|
-
sse_client =
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
169
|
+
sse_client = create_sse_client
|
|
170
|
+
response = sse_client.get(@event_url, stream: true)
|
|
171
|
+
validate_sse_response!(response)
|
|
172
|
+
process_event_stream(response)
|
|
173
|
+
end
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
)
|
|
179
|
-
end
|
|
175
|
+
def create_sse_client
|
|
176
|
+
sse_client = HTTPX.plugin(:stream).with(headers: @headers)
|
|
177
|
+
return sse_client unless @version == :http1
|
|
180
178
|
|
|
181
|
-
|
|
179
|
+
sse_client.with(ssl: { alpn_protocols: ["http/1.1"] })
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def validate_sse_response!(response)
|
|
183
|
+
return unless response.status >= 400
|
|
184
|
+
|
|
185
|
+
error_body = read_error_body(response)
|
|
186
|
+
error_message = "HTTP #{response.status} error from SSE endpoint: #{error_body}"
|
|
187
|
+
RubyLLM::MCP.logger.error error_message
|
|
188
|
+
|
|
189
|
+
handle_client_error!(error_message, response.status) if response.status < 500
|
|
190
|
+
|
|
191
|
+
raise StandardError, error_message
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def handle_client_error!(error_message, status_code)
|
|
195
|
+
@running = false
|
|
196
|
+
raise Errors::TransportError.new(
|
|
197
|
+
message: error_message,
|
|
198
|
+
code: status_code
|
|
199
|
+
)
|
|
200
|
+
end
|
|
182
201
|
|
|
202
|
+
def process_event_stream(response)
|
|
183
203
|
event_buffer = []
|
|
184
204
|
response.each_line do |event_line|
|
|
185
|
-
unless
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
end
|
|
205
|
+
break unless handle_event_line?(event_line, event_buffer, response)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
189
208
|
|
|
190
|
-
|
|
191
|
-
|
|
209
|
+
def handle_event_line?(event_line, event_buffer, response)
|
|
210
|
+
unless @running
|
|
211
|
+
response.body.close
|
|
212
|
+
return false
|
|
213
|
+
end
|
|
192
214
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
215
|
+
line = event_line.strip
|
|
216
|
+
|
|
217
|
+
if line.empty?
|
|
218
|
+
process_buffered_event(event_buffer)
|
|
219
|
+
else
|
|
220
|
+
event_buffer << line
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
true
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def process_buffered_event(event_buffer)
|
|
227
|
+
return unless event_buffer.any?
|
|
228
|
+
|
|
229
|
+
events = parse_event(event_buffer.join("\n"))
|
|
230
|
+
events.each { |event| process_event(event) }
|
|
231
|
+
event_buffer.clear
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def read_error_body(response)
|
|
235
|
+
# Try to read the error body from the response
|
|
236
|
+
body = ""
|
|
237
|
+
begin
|
|
238
|
+
response.each do |chunk|
|
|
239
|
+
body << chunk
|
|
203
240
|
end
|
|
241
|
+
rescue StandardError
|
|
242
|
+
# If we can't read the body, just use what we have
|
|
204
243
|
end
|
|
244
|
+
body.strip.empty? ? "No error details provided" : body.strip
|
|
205
245
|
end
|
|
206
246
|
|
|
207
247
|
def handle_connection_error(message, error)
|
|
@@ -496,6 +496,11 @@ module RubyLLM
|
|
|
496
496
|
# Server doesn't support SSE - this is acceptable
|
|
497
497
|
RubyLLM::MCP.logger.info "Server does not support SSE streaming"
|
|
498
498
|
nil
|
|
499
|
+
when 409
|
|
500
|
+
# Conflict - SSE connection already exists for this session
|
|
501
|
+
# This is expected when reusing sessions and is acceptable
|
|
502
|
+
RubyLLM::MCP.logger.debug "SSE stream already exists for this session"
|
|
503
|
+
nil
|
|
499
504
|
else
|
|
500
505
|
reason_phrase = response.respond_to?(:reason_phrase) ? response.reason_phrase : nil
|
|
501
506
|
raise Errors::TransportError.new(
|
data/lib/ruby_llm/mcp/version.rb
CHANGED