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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02af0c9ae4ba24f6e7af288d77530f87e885e1c0eb594b935bae93916a50870b
4
- data.tar.gz: 300b6e91813c6cebe612fe966a84e8dc13beb59ea27f12eb0157624e4eca3655
3
+ metadata.gz: f6b3a7fc52258ae0e1a8e5635b7c7347165b2c78da97ba1cfa1bc3633390eb39
4
+ data.tar.gz: d2220a770be8038c8fa794196742f8c35d390e4020b838a9d7279c449b5894b0
5
5
  SHA512:
6
- metadata.gz: 9e19267562108057d4a3b031c1859c1569184e77554be2c7af306ed58e5e35cf1c1c184ffcf0be950fef90ce1bf84dd0d5544f72f0b83ac8d7a60eacbef43beb
7
- data.tar.gz: abaab7588640608c5bf0df1291c2b05b94794acae6dd5c850f2dd44ce3e259136d330d698cd4455d429ab4641f21b0bf988cec20961362b65392570f964390db
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
- readme "README.txt" if behavior == :invoke
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
@@ -9,7 +9,7 @@ module RubyLLM
9
9
  end
10
10
 
11
11
  def call
12
- @coordinator.request(notification_body, add_id: false, wait_for_response: false)
12
+ @coordinator.request(notification_body, add_id: true, wait_for_response: false)
13
13
  end
14
14
 
15
15
  def notification_body
@@ -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
@@ -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
@@ -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
- process_parameter(key, value, lifted_type: param_data["type"])
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
- event = parse_event(event_buffer.join("\n"))
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
- event = {}
279
- raw.each_line do |line|
280
- case line
281
- when /^data:\s*(.*)/
282
- (event[:data] ||= []) << ::Regexp.last_match(1)
283
- when /^event:\s*(.*)/
284
- event[:event] = ::Regexp.last_match(1)
285
- when /^id:\s*(.*)/
286
- event[:id] = ::Regexp.last_match(1)
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
- event[:data] = event[:data]&.join("\n")
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
- return nil unless buffer.include?("\n\n")
598
-
599
- raw, rest = buffer.split("\n\n", 2)
600
- [parse_sse_event(raw), rest]
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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyLLM
4
4
  module MCP
5
- VERSION = "0.6.1"
5
+ VERSION = "0.6.2"
6
6
  end
7
7
  end
@@ -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/schema/version"
8
- version = RubyLlm::Schema::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.1
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
- ===============================================================================