ruby_llm-mcp 0.6.4 → 0.7.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 +4 -4
- data/lib/generators/ruby_llm/mcp/templates/initializer.rb +1 -4
- data/lib/ruby_llm/mcp/configuration.rb +1 -0
- data/lib/ruby_llm/mcp/content.rb +10 -2
- data/lib/ruby_llm/mcp/resource.rb +1 -1
- data/lib/ruby_llm/mcp/tool.rb +89 -85
- data/lib/ruby_llm/mcp/transports/stdio.rb +78 -76
- data/lib/ruby_llm/mcp/version.rb +1 -1
- data/lib/ruby_llm/mcp.rb +3 -3
- metadata +3 -8
- data/lib/ruby_llm/mcp/parameter.rb +0 -49
- data/lib/ruby_llm/mcp/providers/anthropic/complex_parameter_support.rb +0 -87
- data/lib/ruby_llm/mcp/providers/gemini/complex_parameter_support.rb +0 -86
- data/lib/ruby_llm/mcp/providers/openai/complex_parameter_support.rb +0 -72
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b40509c28a60e1ae01ed83d47503d76f30d5f3bf9852ef4c30ca033d6ea149bc
|
|
4
|
+
data.tar.gz: f3393dc6423c6bcda143cb6b22e7042434ca1d7d385cc2adf97ba925efff6912
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86e6dd3009df055ad4fa6d088b6b1d9fb87cc8c6244b1436e4ef96ef1cbd822c321c779bd518557544a6c457d19270bf1a57c874cb6d80543141ecac7c370754
|
|
7
|
+
data.tar.gz: 5a76f9b2aaf2d3db38d97c9d66341458e25f3cdd89fdaf1768095a1bba20257154e128c9b60245f1f09f3e6f09b967daacd70748008c4edf90c3e836546bd6d6
|
|
@@ -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-
|
|
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
|
data/lib/ruby_llm/mcp/content.rb
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
24
|
+
text.to_s
|
|
17
25
|
end
|
|
18
26
|
end
|
|
19
27
|
end
|
data/lib/ruby_llm/mcp/tool.rb
CHANGED
|
@@ -25,7 +25,7 @@ module RubyLLM
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
class Tool < RubyLLM::Tool
|
|
28
|
-
attr_reader :name, :title, :description, :
|
|
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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
|
@@ -78,51 +78,23 @@ module RubyLLM
|
|
|
78
78
|
@running = true
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
def close
|
|
81
|
+
def close
|
|
82
82
|
@running = false
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
rescue
|
|
87
|
-
nil
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
begin
|
|
91
|
-
@wait_thread&.join(1)
|
|
92
|
-
rescue StandardError
|
|
84
|
+
[@stdin, @stdout, @stderr].each do |stream|
|
|
85
|
+
stream&.close
|
|
86
|
+
rescue IOError, Errno::EBADF
|
|
93
87
|
nil
|
|
94
88
|
end
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
rescue StandardError
|
|
99
|
-
nil
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
begin
|
|
103
|
-
@stderr&.close
|
|
90
|
+
[@wait_thread, @reader_thread, @stderr_thread].each do |thread|
|
|
91
|
+
thread&.join(1)
|
|
104
92
|
rescue StandardError
|
|
105
93
|
nil
|
|
106
94
|
end
|
|
107
95
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
rescue StandardError
|
|
111
|
-
nil
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
begin
|
|
115
|
-
@stderr_thread&.join(1)
|
|
116
|
-
rescue StandardError
|
|
117
|
-
nil
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
@stdin = nil
|
|
121
|
-
@stdout = nil
|
|
122
|
-
@stderr = nil
|
|
123
|
-
@wait_thread = nil
|
|
124
|
-
@reader_thread = nil
|
|
125
|
-
@stderr_thread = nil
|
|
96
|
+
@stdin = @stdout = @stderr = nil
|
|
97
|
+
@wait_thread = @reader_thread = @stderr_thread = nil
|
|
126
98
|
end
|
|
127
99
|
|
|
128
100
|
def set_protocol_version(version)
|
|
@@ -151,58 +123,88 @@ module RubyLLM
|
|
|
151
123
|
|
|
152
124
|
def start_reader_thread
|
|
153
125
|
@reader_thread = Thread.new do
|
|
154
|
-
|
|
155
|
-
begin
|
|
156
|
-
if @stdout.closed? || @wait_thread.nil? || !@wait_thread.alive?
|
|
157
|
-
sleep 1
|
|
158
|
-
restart_process if @running
|
|
159
|
-
next
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
line = @stdout.gets
|
|
163
|
-
next unless line && !line.strip.empty?
|
|
164
|
-
|
|
165
|
-
process_response(line.strip)
|
|
166
|
-
rescue IOError, Errno::EPIPE => e
|
|
167
|
-
RubyLLM::MCP.logger.error "Reader error: #{e.message}. Restarting in 1 second..."
|
|
168
|
-
sleep 1
|
|
169
|
-
restart_process if @running
|
|
170
|
-
rescue StandardError => e
|
|
171
|
-
RubyLLM::MCP.logger.error "Error in reader thread: #{e.message}, #{e.backtrace.join("\n")}"
|
|
172
|
-
sleep 1
|
|
173
|
-
end
|
|
174
|
-
end
|
|
126
|
+
read_stdout_loop
|
|
175
127
|
end
|
|
176
128
|
|
|
177
129
|
@reader_thread.abort_on_exception = true
|
|
178
130
|
end
|
|
179
131
|
|
|
132
|
+
def read_stdout_loop
|
|
133
|
+
while @running
|
|
134
|
+
begin
|
|
135
|
+
handle_stdout_read
|
|
136
|
+
rescue IOError, Errno::EPIPE => e
|
|
137
|
+
handle_stream_error(e, "Reader")
|
|
138
|
+
break unless @running
|
|
139
|
+
rescue StandardError => e
|
|
140
|
+
RubyLLM::MCP.logger.error "Error in reader thread: #{e.message}, #{e.backtrace.join("\n")}"
|
|
141
|
+
sleep 1
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def handle_stdout_read
|
|
147
|
+
if @stdout.closed? || @wait_thread.nil? || !@wait_thread.alive?
|
|
148
|
+
if @running
|
|
149
|
+
sleep 1
|
|
150
|
+
restart_process
|
|
151
|
+
end
|
|
152
|
+
return
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
line = @stdout.gets
|
|
156
|
+
return unless line && !line.strip.empty?
|
|
157
|
+
|
|
158
|
+
process_response(line.strip)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def handle_stream_error(error, stream_name)
|
|
162
|
+
# Check @running to distinguish graceful shutdown from unexpected errors.
|
|
163
|
+
# During shutdown, streams are closed intentionally and shouldn't trigger restarts.
|
|
164
|
+
if @running
|
|
165
|
+
RubyLLM::MCP.logger.error "#{stream_name} error: #{error.message}. Restarting in 1 second..."
|
|
166
|
+
sleep 1
|
|
167
|
+
restart_process
|
|
168
|
+
else
|
|
169
|
+
# Graceful shutdown in progress
|
|
170
|
+
RubyLLM::MCP.logger.debug "#{stream_name} thread exiting during shutdown"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
180
174
|
def start_stderr_thread
|
|
181
175
|
@stderr_thread = Thread.new do
|
|
182
|
-
|
|
183
|
-
begin
|
|
184
|
-
if @stderr.closed? || @wait_thread.nil? || !@wait_thread.alive?
|
|
185
|
-
sleep 1
|
|
186
|
-
next
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
line = @stderr.gets
|
|
190
|
-
next unless line && !line.strip.empty?
|
|
191
|
-
|
|
192
|
-
RubyLLM::MCP.logger.info(line.strip)
|
|
193
|
-
rescue IOError, Errno::EPIPE => e
|
|
194
|
-
RubyLLM::MCP.logger.error "Stderr reader error: #{e.message}"
|
|
195
|
-
sleep 1
|
|
196
|
-
rescue StandardError => e
|
|
197
|
-
RubyLLM::MCP.logger.error "Error in stderr thread: #{e.message}"
|
|
198
|
-
sleep 1
|
|
199
|
-
end
|
|
200
|
-
end
|
|
176
|
+
read_stderr_loop
|
|
201
177
|
end
|
|
202
178
|
|
|
203
179
|
@stderr_thread.abort_on_exception = true
|
|
204
180
|
end
|
|
205
181
|
|
|
182
|
+
def read_stderr_loop
|
|
183
|
+
while @running
|
|
184
|
+
begin
|
|
185
|
+
handle_stderr_read
|
|
186
|
+
rescue IOError, Errno::EPIPE => e
|
|
187
|
+
handle_stream_error(e, "Stderr reader")
|
|
188
|
+
break unless @running
|
|
189
|
+
rescue StandardError => e
|
|
190
|
+
RubyLLM::MCP.logger.error "Error in stderr thread: #{e.message}"
|
|
191
|
+
sleep 1
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def handle_stderr_read
|
|
197
|
+
if @stderr.closed? || @wait_thread.nil? || !@wait_thread.alive?
|
|
198
|
+
sleep 1
|
|
199
|
+
return
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
line = @stderr.gets
|
|
203
|
+
return unless line && !line.strip.empty?
|
|
204
|
+
|
|
205
|
+
RubyLLM::MCP.logger.info(line.strip)
|
|
206
|
+
end
|
|
207
|
+
|
|
206
208
|
def process_response(line)
|
|
207
209
|
response = JSON.parse(line)
|
|
208
210
|
request_id = response["id"]&.to_s
|
data/lib/ruby_llm/mcp/version.rb
CHANGED
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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.
|
|
4
|
+
version: 0.7.1
|
|
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.
|
|
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.
|
|
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
|
|
@@ -149,7 +145,6 @@ metadata:
|
|
|
149
145
|
homepage_uri: https://www.rubyllm-mcp.com
|
|
150
146
|
source_code_uri: https://github.com/patvice/ruby_llm-mcp
|
|
151
147
|
changelog_uri: https://github.com/patvice/ruby_llm-mcp/commits/main
|
|
152
|
-
documentation_uri: https://www.rubyllm-mcp.com
|
|
153
148
|
bug_tracker_uri: https://github.com/patvice/ruby_llm-mcp/issues
|
|
154
149
|
rubygems_mfa_required: 'true'
|
|
155
150
|
allowed_push_host: https://rubygems.org
|
|
@@ -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
|