ruby_llm-mcp 0.6.4 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bec47480f606a8d28f8a79032e392e331dcdcef565cd708e6dcc855f0a31445
4
- data.tar.gz: ac65a564ce2bd4d39e4e4fdcff02da19cbd7573186924e938fadb2d9c2f523db
3
+ metadata.gz: 9c8d0ab416033fcafeed6caa5146ab4c5599d0576eb3a17b2e0c04bb4bbb2234
4
+ data.tar.gz: 207026831043126a1e0dfac10fbbe9e8a396edbe8ab874c8af04d1230d57de5c
5
5
  SHA512:
6
- metadata.gz: 7e9394538e772a3169fe424854cbf57aee643953be4e44f805c82f0f81c61189a06d755e8a14b4e530183e414e9a4b3000912a577d3eca95da236c124b9a54dc
7
- data.tar.gz: a6eb704e2aaac9386e54cb178dd136bc19510a2159adc63125d50f5a96c9473de9416e33755059f9578ab609d34fe3fbe8c8d7f8e970900380b93770793376a3
6
+ metadata.gz: 4fbae5a8e05f3df2ee2ef81469a7ca8159b03cde998c5881e6e32ab0353cdb22a3b03a3cf63e3464f95e05f71ed87f861d793e944683c72c90660162fb6263e5
7
+ data.tar.gz: 9888713bcb543421c389954a36d235f024e271163096a82473c4bf1b1c2b16737602c0b608ff28d653cf1ab636909f0b9c6120e228b74ecdc794f2b0ebfc3dc4
@@ -11,9 +11,6 @@ RubyLLM::MCP.configure do |config|
11
11
  # Pool timeout in seconds
12
12
  config.pool_timeout = 5
13
13
 
14
- # Enable complex parameter support for various providers
15
- config.support_complex_parameters!
16
-
17
14
  # Path to MCPs configuration file
18
15
  config.config_path = Rails.root.join("config", "mcps.yml")
19
16
 
@@ -31,7 +28,7 @@ RubyLLM::MCP.configure do |config|
31
28
  # Set preferred model for sampling
32
29
  # config.sampling.preferred_model do
33
30
  # # Return the preferred model name
34
- # "claude-3-5-sonnet-20240620"
31
+ # "claude-sonnet-4"
35
32
  # end
36
33
 
37
34
  # Set a guard for sampling
@@ -111,6 +111,7 @@ module RubyLLM
111
111
  end
112
112
 
113
113
  def support_complex_parameters!
114
+ warn "[DEPRECATION] config.support_complex_parameters! is no longer needed and will be removed in version 0.8.0"
114
115
  return if @has_support_complex_parameters
115
116
 
116
117
  @has_support_complex_parameters = true
@@ -7,13 +7,21 @@ module RubyLLM
7
7
 
8
8
  def initialize(text: nil, attachments: nil) # rubocop:disable Lint/MissingSuper
9
9
  @text = text
10
- @attachments = attachments || []
10
+ @attachments = []
11
+
12
+ # Handle MCP::Attachment objects directly without processing
13
+ if attachments.is_a?(Array) && attachments.all? { |a| a.is_a?(MCP::Attachment) }
14
+ @attachments = attachments
15
+ elsif attachments
16
+ # Let parent class process other types of attachments
17
+ process_attachments(attachments)
18
+ end
11
19
  end
12
20
 
13
21
  # This is a workaround to allow the content object to be passed as the tool call
14
22
  # to return audio or image attachments.
15
23
  def to_s
16
- attachments.empty? ? text : self
24
+ text.to_s
17
25
  end
18
26
  end
19
27
  end
@@ -51,7 +51,7 @@ module RubyLLM
51
51
  end
52
52
 
53
53
  def include(chat, **args)
54
- message = Message.new(
54
+ message = RubyLLM::Message.new(
55
55
  role: "user",
56
56
  content: to_content(**args)
57
57
  )
@@ -25,7 +25,7 @@ module RubyLLM
25
25
  end
26
26
 
27
27
  class Tool < RubyLLM::Tool
28
- attr_reader :name, :title, :description, :parameters, :coordinator, :tool_response, :with_prefix
28
+ attr_reader :name, :title, :description, :coordinator, :tool_response, :with_prefix
29
29
 
30
30
  def initialize(coordinator, tool_response, with_prefix: false)
31
31
  super()
@@ -35,18 +35,23 @@ module RubyLLM
35
35
  @name = format_name(tool_response["name"])
36
36
  @mcp_name = tool_response["name"]
37
37
  @description = tool_response["description"].to_s
38
- @parameters = create_parameters(tool_response["inputSchema"])
39
38
 
40
39
  @input_schema = tool_response["inputSchema"]
41
40
  @output_schema = tool_response["outputSchema"]
42
41
 
43
42
  @annotations = tool_response["annotations"] ? Annotation.new(tool_response["annotations"]) : nil
43
+
44
+ @normalized_input_schema = normalize_if_invalid(@input_schema)
44
45
  end
45
46
 
46
47
  def display_name
47
48
  "#{@coordinator.name}: #{@name}"
48
49
  end
49
50
 
51
+ def params_schema
52
+ @normalized_input_schema
53
+ end
54
+
50
55
  def execute(**params)
51
56
  result = @coordinator.execute_tool(
52
57
  name: @mcp_name,
@@ -83,7 +88,7 @@ module RubyLLM
83
88
  {
84
89
  name: @name,
85
90
  description: @description,
86
- parameters: @parameters.to_h,
91
+ params_schema: @@normalized_input_schema,
87
92
  annotations: @annotations&.to_h
88
93
  }
89
94
  end
@@ -92,76 +97,6 @@ module RubyLLM
92
97
 
93
98
  private
94
99
 
95
- def create_parameters(schema)
96
- params = {}
97
- return params if schema["properties"].nil?
98
-
99
- schema["properties"].each_key do |key|
100
- param_data = schema.dig("properties", key)
101
- param_data = expand_shorthand_type_to_anyof(param_data)
102
-
103
- param = if param_data.key?("oneOf") || param_data.key?("anyOf") || param_data.key?("allOf")
104
- process_union_parameter(key, param_data)
105
- else
106
- process_parameter(key, param_data)
107
- end
108
-
109
- params[key] = param
110
- end
111
-
112
- params
113
- end
114
-
115
- def process_union_parameter(key, param_data)
116
- union_type = param_data.keys.first
117
- param = RubyLLM::MCP::Parameter.new(
118
- key,
119
- type: :union,
120
- title: param_data["title"],
121
- desc: param_data["description"],
122
- union_type: union_type
123
- )
124
-
125
- param.properties = param_data[union_type].map do |value|
126
- expanded_value = expand_shorthand_type_to_anyof(value)
127
- if expanded_value.key?("anyOf")
128
- process_union_parameter(key, expanded_value)
129
- else
130
- process_parameter(key, value, lifted_type: param_data["type"])
131
- end
132
- end.compact
133
-
134
- param
135
- end
136
-
137
- def process_parameter(key, param_data, lifted_type: nil)
138
- param = RubyLLM::MCP::Parameter.new(
139
- key,
140
- type: param_data["type"] || lifted_type || "string",
141
- title: param_data["title"],
142
- desc: param_data["description"],
143
- required: param_data["required"],
144
- default: param_data["default"]
145
- )
146
-
147
- if param.type == :array
148
- items = param_data["items"]
149
- param.items = items
150
- if items.key?("properties")
151
- param.properties = create_parameters(items)
152
- end
153
- if items.key?("enum")
154
- param.enum = items["enum"]
155
- end
156
- elsif param.type == :object
157
- if param_data.key?("properties")
158
- param.properties = create_parameters(param_data)
159
- end
160
- end
161
-
162
- param
163
- end
164
-
165
100
  def create_content_for_message(content)
166
101
  case content["type"]
167
102
  when "text"
@@ -205,19 +140,88 @@ module RubyLLM
205
140
  end
206
141
  end
207
142
 
208
- # Expands shorthand type arrays into explicit anyOf unions
209
- # Converts { "type": ["string", "number"] } into { "anyOf": [{"type": "string"}, {"type": "number"}] }
210
- # This keeps $ref references clean and provides a consistent structure for union types
211
- #
212
- # @param param_data [Hash] The parameter data that may contain a shorthand type array
213
- # @return [Hash] The expanded parameter data with anyOf, or the original if not a shorthand
214
- def expand_shorthand_type_to_anyof(param_data)
215
- type = param_data["type"]
216
- return param_data unless type.is_a?(Array)
143
+ def normalize_schema(schema)
144
+ return schema if schema.nil?
217
145
 
218
- {
219
- "anyOf" => type.map { |t| { "type" => t } }
220
- }.merge(param_data.except("type"))
146
+ case schema
147
+ when Hash
148
+ normalize_hash_schema(schema)
149
+ when Array
150
+ normalize_array_schema(schema)
151
+ else
152
+ schema
153
+ end
154
+ end
155
+
156
+ def normalize_hash_schema(schema)
157
+ normalized = schema.transform_values { |value| normalize_schema_value(value) }
158
+ ensure_object_properties(normalized)
159
+ normalized
160
+ end
161
+
162
+ def normalize_array_schema(schema)
163
+ schema.map { |item| normalize_schema_value(item) }
164
+ end
165
+
166
+ def normalize_schema_value(value)
167
+ case value
168
+ when Hash
169
+ normalize_schema(value)
170
+ when Array
171
+ normalize_array_schema(value)
172
+ else
173
+ value
174
+ end
175
+ end
176
+
177
+ def ensure_object_properties(schema)
178
+ if schema["type"] == "object" && !schema.key?("properties")
179
+ schema["properties"] = {}
180
+ end
181
+ end
182
+
183
+ def normalize_if_invalid(schema)
184
+ return schema if schema.nil?
185
+
186
+ if valid_schema?(schema)
187
+ schema
188
+ else
189
+ normalize_schema(schema)
190
+ end
191
+ end
192
+
193
+ def valid_schema?(schema)
194
+ return true if schema.nil?
195
+
196
+ case schema
197
+ when Hash
198
+ valid_hash_schema?(schema)
199
+ when Array
200
+ schema.all? { |item| valid_schema?(item) }
201
+ else
202
+ true
203
+ end
204
+ end
205
+
206
+ def valid_hash_schema?(schema)
207
+ # Check if this level has missing properties for object type
208
+ if schema["type"] == "object" && !schema.key?("properties")
209
+ return false
210
+ end
211
+
212
+ # Recursively check nested schemas
213
+ schema.each_value do |value|
214
+ return false unless valid_schema?(value)
215
+ end
216
+
217
+ begin
218
+ JSON::Validator.validate!(schema, {})
219
+ true
220
+ rescue JSON::Schema::SchemaError
221
+ false
222
+ rescue JSON::Schema::ValidationError
223
+ true
224
+ end
221
225
  end
222
226
  end
223
227
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLLM
4
4
  module MCP
5
- VERSION = "0.6.4"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
data/lib/ruby_llm/mcp.rb CHANGED
@@ -62,9 +62,9 @@ module RubyLLM
62
62
  end
63
63
 
64
64
  def support_complex_parameters!
65
- require_relative "mcp/providers/openai/complex_parameter_support"
66
- require_relative "mcp/providers/anthropic/complex_parameter_support"
67
- require_relative "mcp/providers/gemini/complex_parameter_support"
65
+ warn "[DEPRECATION] RubyLLM::MCP.support_complex_parameters! is no longer needed " \
66
+ "and will be removed in version 0.8.0"
67
+ # No-op: Complex parameters are now supported by default
68
68
  end
69
69
 
70
70
  def configure
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.4
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Vice
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '1.3'
46
+ version: '1.9'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.3'
53
+ version: '1.9'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: zeitwerk
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -97,13 +97,9 @@ files:
97
97
  - lib/ruby_llm/mcp/notifications/cancelled.rb
98
98
  - lib/ruby_llm/mcp/notifications/initialize.rb
99
99
  - lib/ruby_llm/mcp/notifications/roots_list_change.rb
100
- - lib/ruby_llm/mcp/parameter.rb
101
100
  - lib/ruby_llm/mcp/progress.rb
102
101
  - lib/ruby_llm/mcp/prompt.rb
103
102
  - lib/ruby_llm/mcp/protocol.rb
104
- - lib/ruby_llm/mcp/providers/anthropic/complex_parameter_support.rb
105
- - lib/ruby_llm/mcp/providers/gemini/complex_parameter_support.rb
106
- - lib/ruby_llm/mcp/providers/openai/complex_parameter_support.rb
107
103
  - lib/ruby_llm/mcp/railtie.rb
108
104
  - lib/ruby_llm/mcp/requests/completion_prompt.rb
109
105
  - lib/ruby_llm/mcp/requests/completion_resource.rb
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "ruby_llm/tool"
4
-
5
- module RubyLLM
6
- module MCP
7
- class Parameter < RubyLLM::Parameter
8
- attr_accessor :items, :properties, :enum, :union_type, :default, :title
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
-
18
- def initialize(name, type: "string", title: nil, desc: nil, required: true, default: nil, union_type: nil) # rubocop:disable Metrics/ParameterLists
19
- super(name, type: type.to_sym, desc: desc, required: required)
20
- @title = title
21
- @properties = {}
22
- @union_type = union_type
23
- @default = default
24
- end
25
-
26
- def item_type
27
- @items&.dig("type")&.to_sym
28
- end
29
-
30
- def as_json(*_args)
31
- to_h
32
- end
33
-
34
- def to_h
35
- {
36
- name: @name,
37
- type: @type,
38
- description: @desc,
39
- required: @required,
40
- default: @default,
41
- union_type: @union_type,
42
- items: @items&.to_h,
43
- properties: @properties&.values,
44
- enum: @enum
45
- }
46
- end
47
- end
48
- end
49
- end
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module MCP
5
- module Providers
6
- module Anthropic
7
- module ComplexParameterSupport
8
- module_function
9
-
10
- def clean_parameters(parameters)
11
- parameters.transform_values do |param|
12
- mcp_build_properties(param).compact
13
- end
14
- end
15
-
16
- def required_parameters(parameters)
17
- parameters.select { |_, param| param.required }.keys
18
- end
19
-
20
- def mcp_build_properties(param) # rubocop:disable Metrics/MethodLength
21
- case param.type
22
- when :array
23
- if param.item_type == :object
24
- {
25
- type: param.type,
26
- title: param.title,
27
- description: param.description,
28
- items: { type: param.item_type, properties: clean_parameters(param.properties) }
29
- }.compact
30
- else
31
- {
32
- type: param.type,
33
- title: param.title,
34
- description: param.description,
35
- default: param.default,
36
- items: { type: param.item_type, enum: param.enum }.compact
37
- }.compact
38
- end
39
- when :object
40
- {
41
- type: param.type,
42
- title: param.title,
43
- description: param.description,
44
- properties: clean_parameters(param.properties),
45
- required: required_parameters(param.properties)
46
- }.compact
47
- when :union
48
- {
49
- param.union_type => param.properties.map { |property| mcp_build_properties(property) }
50
- }
51
- else
52
- {
53
- type: param.type,
54
- title: param.title,
55
- description: param.description
56
- }.compact
57
- end
58
- end
59
- end
60
- end
61
- end
62
- end
63
- end
64
-
65
- module RubyLLM::Providers::Anthropic::Tools
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)
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
83
-
84
- original_required_parameters(parameters)
85
- end
86
- module_function :required_parameters
87
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module MCP
5
- module Providers
6
- module Gemini
7
- module ComplexParameterSupport
8
- module_function
9
-
10
- # Format tool parameters for Gemini API
11
- def format_parameters(parameters)
12
- {
13
- type: "OBJECT",
14
- properties: parameters.transform_values { |param| mcp_build_properties(param) },
15
- required: parameters.select { |_, p| p.required }.keys.map(&:to_s)
16
- }
17
- end
18
-
19
- def mcp_build_properties(param) # rubocop:disable Metrics/MethodLength
20
- properties = case param.type
21
- when :array
22
- if param.item_type == :object
23
- {
24
- type: param_type_for_gemini(param.type),
25
- title: param.title,
26
- description: param.description,
27
- items: {
28
- type: param_type_for_gemini(param.item_type),
29
- properties: param.properties.transform_values { |value| mcp_build_properties(value) }
30
- }
31
- }.compact
32
- else
33
- {
34
- type: param_type_for_gemini(param.type),
35
- title: param.title,
36
- description: param.description,
37
- default: param.default,
38
- items: { type: param_type_for_gemini(param.item_type), enum: param.enum }.compact
39
- }.compact
40
- end
41
- when :object
42
- {
43
- type: param_type_for_gemini(param.type),
44
- title: param.title,
45
- description: param.description,
46
- properties: param.properties.transform_values { |value| mcp_build_properties(value) },
47
- required: param.properties.select { |_, p| p.required }.keys
48
- }.compact
49
- when :union
50
- {
51
- param.union_type => param.properties.map { |properties| mcp_build_properties(properties) }
52
- }
53
- else
54
- {
55
- type: param_type_for_gemini(param.type),
56
- title: param.title,
57
- description: param.description
58
- }
59
- end
60
-
61
- properties.compact
62
- end
63
-
64
- def param_type_for_gemini(type)
65
- RubyLLM::Providers::Gemini::Tools.param_type_for_gemini(type)
66
- end
67
- end
68
- end
69
- end
70
- end
71
- end
72
-
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
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module MCP
5
- module Providers
6
- module OpenAI
7
- module ComplexParameterSupport
8
- module_function
9
-
10
- def param_schema(param) # rubocop:disable Metrics/MethodLength
11
- properties = case param.type
12
- when :array
13
- if param.item_type == :object
14
- {
15
- type: param.type,
16
- title: param.title,
17
- description: param.description,
18
- items: {
19
- type: param.item_type,
20
- properties: param.properties.transform_values { |value| param_schema(value) }
21
- }
22
- }.compact
23
- else
24
- {
25
- type: param.type,
26
- title: param.title,
27
- description: param.description,
28
- default: param.default,
29
- items: { type: param.item_type, enum: param.enum }.compact
30
- }.compact
31
- end
32
- when :object
33
- {
34
- type: param.type,
35
- title: param.title,
36
- description: param.description,
37
- properties: param.properties.transform_values { |value| param_schema(value) },
38
- required: param.properties.select { |_, p| p.required }.keys
39
- }.compact
40
- when :union
41
- {
42
- param.union_type => param.properties.map { |property| param_schema(property) }
43
- }
44
- else
45
- {
46
- type: param.type,
47
- title: param.title,
48
- description: param.description
49
- }.compact
50
- end
51
-
52
- properties.compact
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end
59
-
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