ruby_llm-mcp 0.6.1 → 0.6.2
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/install_generator.rb +14 -1
- data/lib/ruby_llm/mcp/notifications/initialize.rb +1 -1
- data/lib/ruby_llm/mcp/parameter.rb +3 -2
- data/lib/ruby_llm/mcp/providers/anthropic/complex_parameter_support.rb +5 -1
- data/lib/ruby_llm/mcp/providers/gemini/complex_parameter_support.rb +4 -0
- data/lib/ruby_llm/mcp/providers/openai/complex_parameter_support.rb +4 -0
- data/lib/ruby_llm/mcp/railtie.rb +2 -2
- data/lib/ruby_llm/mcp/tool.rb +27 -3
- data/lib/ruby_llm/mcp/transports/sse.rb +19 -13
- data/lib/ruby_llm/mcp/transports/streamable_http.rb +15 -8
- data/lib/ruby_llm/mcp/version.rb +1 -1
- data/lib/tasks/release.rake +2 -2
- metadata +1 -16
- data/lib/generators/ruby_llm/mcp/templates/README.txt +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6b3a7fc52258ae0e1a8e5635b7c7347165b2c78da97ba1cfa1bc3633390eb39
|
|
4
|
+
data.tar.gz: d2220a770be8038c8fa794196742f8c35d390e4020b838a9d7279c449b5894b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d6d145ab20653923ce4a557f6cc6d44b10f395a7c54dde228d69fe9f7e175e6d56ae6b6382ceae6dd32878305757271843e09a81ea07aaca2a1741a0f8f29a3
|
|
7
|
+
data.tar.gz: 50872333e5206d74d30dd612d8e09db65665957cca705e6f20cf3f72460896332e0c5ea271ab2fa88659421dc4d1a07b0fe217ed9b925d237993b59baf4f94a2
|
|
@@ -19,7 +19,20 @@ module RubyLlm
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def display_readme
|
|
22
|
-
|
|
22
|
+
return unless behavior == :invoke
|
|
23
|
+
|
|
24
|
+
say "✅ RubyLLM MCP installed!", :green
|
|
25
|
+
say ""
|
|
26
|
+
say "Next steps:", :blue
|
|
27
|
+
say " 1. Configure config/initializers/ruby_llm_mcp.rb (main settings)"
|
|
28
|
+
say " 2. Define servers in config/mcps.yml"
|
|
29
|
+
say " 3. Install MCP servers (e.g., npm install @modelcontextprotocol/server-filesystem)"
|
|
30
|
+
say " 4. Set environment variables for authentication"
|
|
31
|
+
say ""
|
|
32
|
+
say "📚 Full docs: https://www.rubyllm-mcp.com/", :cyan
|
|
33
|
+
say ""
|
|
34
|
+
say "⭐ Help us improve!", :magenta
|
|
35
|
+
say " Report issues or show your support with a GitHub star: https://github.com/patvice/ruby_llm-mcp"
|
|
23
36
|
end
|
|
24
37
|
end
|
|
25
38
|
end
|
|
@@ -5,10 +5,11 @@ require "ruby_llm/tool"
|
|
|
5
5
|
module RubyLLM
|
|
6
6
|
module MCP
|
|
7
7
|
class Parameter < RubyLLM::Parameter
|
|
8
|
-
attr_accessor :items, :properties, :enum, :union_type, :default
|
|
8
|
+
attr_accessor :items, :properties, :enum, :union_type, :default, :title
|
|
9
9
|
|
|
10
|
-
def initialize(name, type: "string", desc: nil, required: true, default: nil, union_type: nil) # rubocop:disable Metrics/ParameterLists
|
|
10
|
+
def initialize(name, type: "string", title: nil, desc: nil, required: true, default: nil, union_type: nil) # rubocop:disable Metrics/ParameterLists
|
|
11
11
|
super(name, type: type.to_sym, desc: desc, required: required)
|
|
12
|
+
@title = title
|
|
12
13
|
@properties = {}
|
|
13
14
|
@union_type = union_type
|
|
14
15
|
@default = default
|
|
@@ -17,18 +17,20 @@ module RubyLLM
|
|
|
17
17
|
parameters.select { |_, param| param.required }.keys
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def build_properties(param)
|
|
20
|
+
def build_properties(param) # rubocop:disable Metrics/MethodLength
|
|
21
21
|
case param.type
|
|
22
22
|
when :array
|
|
23
23
|
if param.item_type == :object
|
|
24
24
|
{
|
|
25
25
|
type: param.type,
|
|
26
|
+
title: param.title,
|
|
26
27
|
description: param.description,
|
|
27
28
|
items: { type: param.item_type, properties: clean_parameters(param.properties) }
|
|
28
29
|
}.compact
|
|
29
30
|
else
|
|
30
31
|
{
|
|
31
32
|
type: param.type,
|
|
33
|
+
title: param.title,
|
|
32
34
|
description: param.description,
|
|
33
35
|
default: param.default,
|
|
34
36
|
items: { type: param.item_type, enum: param.enum }.compact
|
|
@@ -37,6 +39,7 @@ module RubyLLM
|
|
|
37
39
|
when :object
|
|
38
40
|
{
|
|
39
41
|
type: param.type,
|
|
42
|
+
title: param.title,
|
|
40
43
|
description: param.description,
|
|
41
44
|
properties: clean_parameters(param.properties),
|
|
42
45
|
required: required_parameters(param.properties)
|
|
@@ -48,6 +51,7 @@ module RubyLLM
|
|
|
48
51
|
else
|
|
49
52
|
{
|
|
50
53
|
type: param.type,
|
|
54
|
+
title: param.title,
|
|
51
55
|
description: param.description
|
|
52
56
|
}.compact
|
|
53
57
|
end
|
|
@@ -22,6 +22,7 @@ module RubyLLM
|
|
|
22
22
|
if param.item_type == :object
|
|
23
23
|
{
|
|
24
24
|
type: param_type_for_gemini(param.type),
|
|
25
|
+
title: param.title,
|
|
25
26
|
description: param.description,
|
|
26
27
|
items: {
|
|
27
28
|
type: param_type_for_gemini(param.item_type),
|
|
@@ -31,6 +32,7 @@ module RubyLLM
|
|
|
31
32
|
else
|
|
32
33
|
{
|
|
33
34
|
type: param_type_for_gemini(param.type),
|
|
35
|
+
title: param.title,
|
|
34
36
|
description: param.description,
|
|
35
37
|
default: param.default,
|
|
36
38
|
items: { type: param_type_for_gemini(param.item_type), enum: param.enum }.compact
|
|
@@ -39,6 +41,7 @@ module RubyLLM
|
|
|
39
41
|
when :object
|
|
40
42
|
{
|
|
41
43
|
type: param_type_for_gemini(param.type),
|
|
44
|
+
title: param.title,
|
|
42
45
|
description: param.description,
|
|
43
46
|
properties: param.properties.transform_values { |value| build_properties(value) },
|
|
44
47
|
required: param.properties.select { |_, p| p.required }.keys
|
|
@@ -50,6 +53,7 @@ module RubyLLM
|
|
|
50
53
|
else
|
|
51
54
|
{
|
|
52
55
|
type: param_type_for_gemini(param.type),
|
|
56
|
+
title: param.title,
|
|
53
57
|
description: param.description
|
|
54
58
|
}
|
|
55
59
|
end
|
|
@@ -13,6 +13,7 @@ module RubyLLM
|
|
|
13
13
|
if param.item_type == :object
|
|
14
14
|
{
|
|
15
15
|
type: param.type,
|
|
16
|
+
title: param.title,
|
|
16
17
|
description: param.description,
|
|
17
18
|
items: {
|
|
18
19
|
type: param.item_type,
|
|
@@ -22,6 +23,7 @@ module RubyLLM
|
|
|
22
23
|
else
|
|
23
24
|
{
|
|
24
25
|
type: param.type,
|
|
26
|
+
title: param.title,
|
|
25
27
|
description: param.description,
|
|
26
28
|
default: param.default,
|
|
27
29
|
items: { type: param.item_type, enum: param.enum }.compact
|
|
@@ -30,6 +32,7 @@ module RubyLLM
|
|
|
30
32
|
when :object
|
|
31
33
|
{
|
|
32
34
|
type: param.type,
|
|
35
|
+
title: param.title,
|
|
33
36
|
description: param.description,
|
|
34
37
|
properties: param.properties.transform_values { |value| param_schema(value) },
|
|
35
38
|
required: param.properties.select { |_, p| p.required }.keys
|
|
@@ -41,6 +44,7 @@ module RubyLLM
|
|
|
41
44
|
else
|
|
42
45
|
{
|
|
43
46
|
type: param.type,
|
|
47
|
+
title: param.title,
|
|
44
48
|
description: param.description
|
|
45
49
|
}.compact
|
|
46
50
|
end
|
data/lib/ruby_llm/mcp/railtie.rb
CHANGED
|
@@ -5,9 +5,9 @@ module RubyLLM
|
|
|
5
5
|
class Railtie < Rails::Railtie
|
|
6
6
|
config.after_initialize do
|
|
7
7
|
if RubyLLM::MCP.config.launch_control == :automatic
|
|
8
|
-
RubyLLM::MCP.clients.map(&:start)
|
|
8
|
+
RubyLLM::MCP.clients.each_value.map(&:start)
|
|
9
9
|
at_exit do
|
|
10
|
-
RubyLLM::MCP.clients.map(&:stop)
|
|
10
|
+
RubyLLM::MCP.clients.each_value.map(&:stop)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
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, :description, :parameters, :coordinator, :tool_response, :with_prefix
|
|
28
|
+
attr_reader :name, :title, :description, :parameters, :coordinator, :tool_response, :with_prefix
|
|
29
29
|
|
|
30
30
|
def initialize(coordinator, tool_response, with_prefix: false)
|
|
31
31
|
super()
|
|
@@ -98,6 +98,7 @@ module RubyLLM
|
|
|
98
98
|
|
|
99
99
|
schema["properties"].each_key do |key|
|
|
100
100
|
param_data = schema.dig("properties", key)
|
|
101
|
+
param_data = expand_shorthand_type_to_anyof(param_data)
|
|
101
102
|
|
|
102
103
|
param = if param_data.key?("oneOf") || param_data.key?("anyOf") || param_data.key?("allOf")
|
|
103
104
|
process_union_parameter(key, param_data)
|
|
@@ -116,11 +117,18 @@ module RubyLLM
|
|
|
116
117
|
param = RubyLLM::MCP::Parameter.new(
|
|
117
118
|
key,
|
|
118
119
|
type: :union,
|
|
120
|
+
title: param_data["title"],
|
|
121
|
+
desc: param_data["description"],
|
|
119
122
|
union_type: union_type
|
|
120
123
|
)
|
|
121
124
|
|
|
122
125
|
param.properties = param_data[union_type].map do |value|
|
|
123
|
-
|
|
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
|
|
124
132
|
end.compact
|
|
125
133
|
|
|
126
134
|
param
|
|
@@ -129,7 +137,8 @@ module RubyLLM
|
|
|
129
137
|
def process_parameter(key, param_data, lifted_type: nil)
|
|
130
138
|
param = RubyLLM::MCP::Parameter.new(
|
|
131
139
|
key,
|
|
132
|
-
type: param_data["type"] || lifted_type,
|
|
140
|
+
type: param_data["type"] || lifted_type || "string",
|
|
141
|
+
title: param_data["title"],
|
|
133
142
|
desc: param_data["description"],
|
|
134
143
|
required: param_data["required"],
|
|
135
144
|
default: param_data["default"]
|
|
@@ -195,6 +204,21 @@ module RubyLLM
|
|
|
195
204
|
name
|
|
196
205
|
end
|
|
197
206
|
end
|
|
207
|
+
|
|
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)
|
|
217
|
+
|
|
218
|
+
{
|
|
219
|
+
"anyOf" => type.map { |t| { "type" => t } }
|
|
220
|
+
}.merge(param_data.except("type"))
|
|
221
|
+
end
|
|
198
222
|
end
|
|
199
223
|
end
|
|
200
224
|
end
|
|
@@ -193,8 +193,8 @@ module RubyLLM
|
|
|
193
193
|
if line.empty?
|
|
194
194
|
# End of event - process the accumulated buffer
|
|
195
195
|
if event_buffer.any?
|
|
196
|
-
|
|
197
|
-
process_event(event)
|
|
196
|
+
events = parse_event(event_buffer.join("\n"))
|
|
197
|
+
events.each { |event| process_event(event) }
|
|
198
198
|
event_buffer.clear
|
|
199
199
|
end
|
|
200
200
|
else
|
|
@@ -275,19 +275,25 @@ module RubyLLM
|
|
|
275
275
|
end
|
|
276
276
|
|
|
277
277
|
def parse_event(raw)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
event
|
|
278
|
+
event_blocks = raw.split(/\n\s*\n/)
|
|
279
|
+
|
|
280
|
+
events = event_blocks.map do |event_block|
|
|
281
|
+
event = {}
|
|
282
|
+
event_block.each_line do |line|
|
|
283
|
+
case line
|
|
284
|
+
when /^data:\s*(.*)/
|
|
285
|
+
(event[:data] ||= []) << ::Regexp.last_match(1)
|
|
286
|
+
when /^event:\s*(.*)/
|
|
287
|
+
event[:event] = ::Regexp.last_match(1)
|
|
288
|
+
when /^id:\s*(.*)/
|
|
289
|
+
event[:id] = ::Regexp.last_match(1)
|
|
290
|
+
end
|
|
287
291
|
end
|
|
292
|
+
event[:data] = event[:data]&.join("\n")
|
|
293
|
+
event
|
|
288
294
|
end
|
|
289
|
-
|
|
290
|
-
event
|
|
295
|
+
|
|
296
|
+
events.reject { |event| event.empty? || event[:data].nil? }
|
|
291
297
|
end
|
|
292
298
|
end
|
|
293
299
|
end
|
|
@@ -122,7 +122,6 @@ module RubyLLM
|
|
|
122
122
|
|
|
123
123
|
response_queue = setup_response_queue(request_id, wait_for_response)
|
|
124
124
|
result = send_http_request(body, request_id, is_initialization: is_initialization)
|
|
125
|
-
|
|
126
125
|
return result if result.is_a?(RubyLLM::MCP::Result)
|
|
127
126
|
|
|
128
127
|
if wait_for_response && request_id
|
|
@@ -365,16 +364,17 @@ module RubyLLM
|
|
|
365
364
|
end
|
|
366
365
|
|
|
367
366
|
def handle_success_response(response, request_id, _original_message)
|
|
368
|
-
# Safely access content type
|
|
369
367
|
content_type = response.respond_to?(:headers) ? response.headers["content-type"] : nil
|
|
370
368
|
|
|
371
369
|
if content_type&.include?("text/event-stream")
|
|
372
|
-
# SSE response - let the streaming handler process it
|
|
373
370
|
start_sse_stream
|
|
374
371
|
nil
|
|
375
372
|
elsif content_type&.include?("application/json")
|
|
376
|
-
# Direct JSON response
|
|
377
373
|
response_body = response.respond_to?(:body) ? response.body.to_s : "{}"
|
|
374
|
+
if response_body == "null" # Fix related to official MCP Ruby SDK implementation
|
|
375
|
+
response_body = "{}"
|
|
376
|
+
end
|
|
377
|
+
|
|
378
378
|
json_response = JSON.parse(response_body)
|
|
379
379
|
result = RubyLLM::MCP::Result.new(json_response, session_id: @session_id)
|
|
380
380
|
|
|
@@ -594,10 +594,17 @@ module RubyLLM
|
|
|
594
594
|
end
|
|
595
595
|
|
|
596
596
|
def extract_sse_event(buffer)
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
597
|
+
# Support both Unix (\n\n) and Windows (\r\n\r\n) line endings
|
|
598
|
+
separator = if buffer.include?("\r\n\r\n")
|
|
599
|
+
"\r\n\r\n"
|
|
600
|
+
elsif buffer.include?("\n\n")
|
|
601
|
+
"\n\n"
|
|
602
|
+
else
|
|
603
|
+
return nil
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
raw, rest = buffer.split(separator, 2)
|
|
607
|
+
[parse_sse_event(raw), rest || ""]
|
|
601
608
|
end
|
|
602
609
|
|
|
603
610
|
def parse_sse_event(raw)
|
data/lib/ruby_llm/mcp/version.rb
CHANGED
data/lib/tasks/release.rake
CHANGED
|
@@ -4,8 +4,8 @@ namespace :release do
|
|
|
4
4
|
desc "Release a new version of the gem"
|
|
5
5
|
task :version do
|
|
6
6
|
# Load the current version from version.rb
|
|
7
|
-
require_relative "../../lib/ruby_llm/
|
|
8
|
-
version =
|
|
7
|
+
require_relative "../../lib/ruby_llm/mcp/version"
|
|
8
|
+
version = RubyLLM::MCP::VERSION
|
|
9
9
|
|
|
10
10
|
puts "Releasing version #{version}..."
|
|
11
11
|
|
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
|
+
version: 0.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick Vice
|
|
@@ -51,20 +51,6 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '1.3'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: thor
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '1.3'
|
|
61
|
-
type: :runtime
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - "~>"
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '1.3'
|
|
68
54
|
- !ruby/object:Gem::Dependency
|
|
69
55
|
name: zeitwerk
|
|
70
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -93,7 +79,6 @@ files:
|
|
|
93
79
|
- LICENSE
|
|
94
80
|
- README.md
|
|
95
81
|
- lib/generators/ruby_llm/mcp/install_generator.rb
|
|
96
|
-
- lib/generators/ruby_llm/mcp/templates/README.txt
|
|
97
82
|
- lib/generators/ruby_llm/mcp/templates/initializer.rb
|
|
98
83
|
- lib/generators/ruby_llm/mcp/templates/mcps.yml
|
|
99
84
|
- lib/ruby_llm/chat.rb
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
RubyLLM MCP has been successfully installed!
|
|
2
|
-
|
|
3
|
-
The following files have been created:
|
|
4
|
-
|
|
5
|
-
config/initializers/ruby_llm_mcp.rb - Main configuration file
|
|
6
|
-
config/mcps.json - MCP servers configuration
|
|
7
|
-
|
|
8
|
-
Next steps:
|
|
9
|
-
|
|
10
|
-
1. Edit config/initializers/ruby_llm_mcp.rb to configure your MCP settings
|
|
11
|
-
2. Edit config/mcps.json to define your MCP servers
|
|
12
|
-
3. Install any MCP servers you want to use (e.g., npm install @modelcontextprotocol/server-filesystem) or use remote MCPs
|
|
13
|
-
4. Update environment variables for any MCP servers that require authentication
|
|
14
|
-
|
|
15
|
-
Example usage in your Rails application:
|
|
16
|
-
|
|
17
|
-
# With Ruby::MCP installed in a controller or service
|
|
18
|
-
clients = RubyLLM::MCP.clients
|
|
19
|
-
|
|
20
|
-
# Get all tools use the configured client
|
|
21
|
-
tools = RubyLLM::MCP.tools
|
|
22
|
-
|
|
23
|
-
# Or use the configured client
|
|
24
|
-
client = RubyLLM::MCP.clients["file-system"]
|
|
25
|
-
|
|
26
|
-
# Or use the configured client
|
|
27
|
-
tools = client.tools
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
For more information, visit: https://github.com/patvice/ruby_llm-mcp
|
|
31
|
-
|
|
32
|
-
===============================================================================
|