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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6b3a7fc52258ae0e1a8e5635b7c7347165b2c78da97ba1cfa1bc3633390eb39
4
- data.tar.gz: d2220a770be8038c8fa794196742f8c35d390e4020b838a9d7279c449b5894b0
3
+ metadata.gz: 5bec47480f606a8d28f8a79032e392e331dcdcef565cd708e6dcc855f0a31445
4
+ data.tar.gz: ac65a564ce2bd4d39e4e4fdcff02da19cbd7573186924e938fadb2d9c2f523db
5
5
  SHA512:
6
- metadata.gz: 4d6d145ab20653923ce4a557f6cc6d44b10f395a7c54dde228d69fe9f7e175e6d56ae6b6382ceae6dd32878305757271843e09a81ea07aaca2a1741a0f8f29a3
7
- data.tar.gz: 50872333e5206d74d30dd612d8e09db65665957cca705e6f20cf3f72460896332e0c5ea271ab2fa88659421dc4d1a07b0fe217ed9b925d237993b59baf4f94a2
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
- build_properties(param).compact
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 build_properties(param) # rubocop:disable Metrics/MethodLength
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| build_properties(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
- def self.clean_parameters(parameters)
67
- RubyLLM::MCP::Providers::Anthropic::ComplexParameterSupport.clean_parameters(parameters)
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
- def self.required_parameters(parameters)
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| build_properties(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 build_properties(param) # rubocop:disable Metrics/MethodLength
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| build_properties(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| build_properties(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| build_properties(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.extend(RubyLLM::MCP::Providers::Gemini::ComplexParameterSupport)
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.extend(RubyLLM::MCP::Providers::OpenAI::ComplexParameterSupport)
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 = HTTPX.plugin(:stream)
171
- sse_client = sse_client.with(
172
- headers: @headers
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
- if @version == :http1
176
- sse_client = sse_client.with(
177
- ssl: { alpn_protocols: ["http/1.1"] }
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
- response = sse_client.get(@event_url, stream: true)
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 @running
186
- response.body.close
187
- next
188
- end
205
+ break unless handle_event_line?(event_line, event_buffer, response)
206
+ end
207
+ end
189
208
 
190
- # Strip the line and check if it's empty (indicates end of event)
191
- line = event_line.strip
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
- if line.empty?
194
- # End of event - process the accumulated buffer
195
- if event_buffer.any?
196
- events = parse_event(event_buffer.join("\n"))
197
- events.each { |event| process_event(event) }
198
- event_buffer.clear
199
- end
200
- else
201
- # Accumulate the line for the current event
202
- event_buffer << line
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(
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLLM
4
4
  module MCP
5
- VERSION = "0.6.2"
5
+ VERSION = "0.6.4"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Vice