raix 0.9.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed80bdf079ec738beb42df145184b5b23b5ad6c2f507233afd9aed47a1ac61f9
4
- data.tar.gz: b38006ad88398d9584d29fad763b7361cd22f7d6d6fb3d51e70b6ac80a5c39aa
3
+ metadata.gz: '0868c4163ea58511e3c0dfc66b2b22a5cb2dbb16a7575746b36cd70af4777137'
4
+ data.tar.gz: 4e396262603a787dc58817e9fbb536264f25cdb9462bcbe035d675e59cb179ad
5
5
  SHA512:
6
- metadata.gz: c896ee3e447b7d45e009cc0c18b6d5527f4fc2620773a27a94743c591e9f535be0f1692e4e4d2b1a60b2059b813e66c5c5c32464f7ea194a95d6b31e553fad59
7
- data.tar.gz: 822e63a7375693fec475f6056db092c5e1a75398cd513e7650b8bfeec6535fe42c85681b98d4867869bca7e83a156cae5ac8101573c6ebd25f899d86c9b1fe46
6
+ metadata.gz: 8ae8a80e0e45dfba22290aefe27c39fd914f9046228dac33af1bc631e55a9eff73e38500c12c9eec6ba7bff67a5a16d51438ccf80cbcf423b070125dcd5710c4
7
+ data.tar.gz: ffb45352168291e28041b787fa8c2bf61c032d5bd5c59fb5a5653a602d6b1bb5ef282475ce7d828722113df797369127a1a4cdeec5da1c53ad887f1665b0385a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.9.1] - 2025-05-30
2
+ ### Added
3
+ - **MCP Type Coercion** - Automatic type conversion for MCP tool arguments based on JSON schema
4
+ - Supports integer, number, boolean, array, and object types
5
+ - Handles nested objects and arrays of objects with proper coercion
6
+ - Gracefully handles invalid JSON and type mismatches
7
+ - **MCP Image Support** - MCP tools can now return image content as structured JSON
8
+
9
+ ### Fixed
10
+ - Fixed handling of nil values in MCP argument coercion
11
+
1
12
  ## [0.9.0] - 2025-05-30
2
13
  ### Added
3
14
  - **MCP (Model Context Protocol) Support**
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- raix (0.9.0)
4
+ raix (0.9.1)
5
5
  activesupport (>= 6.0)
6
6
  faraday-retry (~> 2.0)
7
7
  open_router (~> 0.2)
@@ -42,7 +42,8 @@ module Raix
42
42
  end
43
43
  end
44
44
 
45
- # Executes a tool with given arguments, returns text content.
45
+ # Executes a tool with given arguments.
46
+ # Returns text content directly, or JSON-encoded data for other content types.
46
47
  def call_tool(name, **arguments)
47
48
  request_id = SecureRandom.uuid
48
49
  send_json_rpc(request_id, "tools/call", name:, arguments:)
@@ -56,9 +57,18 @@ module Raix
56
57
  first_item = content.first
57
58
  case first_item
58
59
  when Hash
59
- if first_item[:type] == "text"
60
+ case first_item[:type]
61
+ when "text"
60
62
  first_item[:text]
63
+ when "image"
64
+ # Return a structured response for images
65
+ {
66
+ type: "image",
67
+ data: first_item[:data],
68
+ mime_type: first_item[:mimeType] || "image/png"
69
+ }.to_json
61
70
  else
71
+ # For any other type, return the item as JSON
62
72
  first_item.to_json
63
73
  end
64
74
  else
@@ -21,14 +21,34 @@ module Raix
21
21
  end
22
22
  end
23
23
 
24
- # Executes a tool with given arguments, returns text content.
24
+ # Executes a tool with given arguments.
25
+ # Returns text content directly, or JSON-encoded data for other content types.
25
26
  def call_tool(name, **arguments)
26
27
  result = call("tools/call", name:, arguments:)
27
- unless result.dig("content", 0, "type") == "text"
28
- raise NotImplementedError, "Only text is supported"
29
- end
28
+ content = result["content"]
29
+ return "" if content.nil? || content.empty?
30
30
 
31
- result.dig("content", 0, "text")
31
+ # Handle different content formats
32
+ first_item = content.first
33
+ case first_item
34
+ when Hash
35
+ case first_item["type"]
36
+ when "text"
37
+ first_item["text"]
38
+ when "image"
39
+ # Return a structured response for images
40
+ {
41
+ type: "image",
42
+ data: first_item["data"],
43
+ mime_type: first_item["mimeType"] || "image/png"
44
+ }.to_json
45
+ else
46
+ # For any other type, return the item as JSON
47
+ first_item.to_json
48
+ end
49
+ else
50
+ first_item.to_s
51
+ end
32
52
  end
33
53
 
34
54
  # Closes the connection to the server.
data/lib/raix/mcp.rb CHANGED
@@ -115,11 +115,19 @@ module Raix
115
115
  # Required by OpenAI
116
116
  latest_definition[:parameters][:properties] ||= {}
117
117
 
118
+ # Store the schema for type coercion
119
+ tool_schemas = @tool_schemas ||= {}
120
+ tool_schemas[local_name] = input_schema
121
+
118
122
  # --- define an instance method that proxies to the server
119
123
  define_method(local_name) do |arguments, _cache|
120
124
  arguments ||= {}
121
125
 
122
- content_text = client.call_tool(remote_name, **arguments)
126
+ # Coerce argument types based on the input schema
127
+ stored_schema = self.class.instance_variable_get(:@tool_schemas)&.dig(local_name)
128
+ coerced_arguments = coerce_arguments(arguments, stored_schema)
129
+
130
+ content_text = client.call_tool(remote_name, **coerced_arguments)
123
131
  call_id = SecureRandom.uuid
124
132
 
125
133
  # Mirror FunctionDispatch transcript behaviour
@@ -157,5 +165,92 @@ module Raix
157
165
  @mcp_servers[client.unique_key] = { tools: filtered_tools, client: }
158
166
  end
159
167
  end
168
+
169
+ private
170
+
171
+ # Coerce argument types based on the JSON schema
172
+ def coerce_arguments(arguments, schema)
173
+ return arguments unless schema.is_a?(Hash) && schema["properties"].is_a?(Hash)
174
+
175
+ coerced = {}
176
+ schema["properties"].each do |key, prop_schema|
177
+ value = if arguments.key?(key)
178
+ arguments[key]
179
+ elsif arguments.key?(key.to_sym)
180
+ arguments[key.to_sym]
181
+ end
182
+ next if value.nil?
183
+
184
+ coerced[key] = coerce_value(value, prop_schema)
185
+ end
186
+
187
+ # Include any additional arguments not in the schema
188
+ arguments.each do |key, value|
189
+ key_str = key.to_s
190
+ coerced[key_str] = value unless coerced.key?(key_str)
191
+ end
192
+
193
+ coerced.with_indifferent_access
194
+ end
195
+
196
+ # Coerce a single value based on its schema
197
+ def coerce_value(value, schema)
198
+ return value unless schema.is_a?(Hash)
199
+
200
+ case schema["type"]
201
+ when "number", "integer"
202
+ if value.is_a?(String) && value.match?(/\A-?\d+(\.\d+)?\z/)
203
+ schema["type"] == "integer" ? value.to_i : value.to_f
204
+ else
205
+ value
206
+ end
207
+ when "boolean"
208
+ case value
209
+ when "true", true then true
210
+ when "false", false then false
211
+ else value
212
+ end
213
+ when "array"
214
+ array_value = begin
215
+ value.is_a?(String) ? JSON.parse(value) : value
216
+ rescue JSON::ParserError
217
+ value
218
+ end
219
+
220
+ # If there's an items schema, coerce each element
221
+ if array_value.is_a?(Array) && schema["items"]
222
+ array_value.map { |item| coerce_value(item, schema["items"]) }
223
+ else
224
+ array_value
225
+ end
226
+ when "object"
227
+ object_value = begin
228
+ value.is_a?(String) ? JSON.parse(value) : value
229
+ rescue JSON::ParserError
230
+ value
231
+ end
232
+
233
+ # If there are properties defined, coerce them recursively
234
+ if object_value.is_a?(Hash) && schema["properties"]
235
+ coerced_object = {}
236
+ schema["properties"].each do |prop_key, prop_schema|
237
+ prop_value = object_value[prop_key] || object_value[prop_key.to_sym]
238
+ coerced_object[prop_key] = coerce_value(prop_value, prop_schema) unless prop_value.nil?
239
+ end
240
+
241
+ # Include any additional properties not in the schema
242
+ object_value.each do |obj_key, obj_value|
243
+ obj_key_str = obj_key.to_s
244
+ coerced_object[obj_key_str] = obj_value unless coerced_object.key?(obj_key_str)
245
+ end
246
+
247
+ coerced_object
248
+ else
249
+ object_value
250
+ end
251
+ else
252
+ value
253
+ end
254
+ end
160
255
  end
161
256
  end
data/lib/raix/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Raix
4
- VERSION = "0.9.0"
4
+ VERSION = "0.9.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Obie Fernandez