ruby_llm-mcp 0.4.1 → 0.5.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +313 -25
  3. data/lib/generators/ruby_llm/mcp/install_generator.rb +27 -0
  4. data/lib/generators/ruby_llm/mcp/templates/README.txt +32 -0
  5. data/lib/generators/ruby_llm/mcp/templates/initializer.rb +42 -0
  6. data/lib/generators/ruby_llm/mcp/templates/mcps.yml +9 -0
  7. data/lib/ruby_llm/chat.rb +2 -1
  8. data/lib/ruby_llm/mcp/client.rb +32 -13
  9. data/lib/ruby_llm/mcp/configuration.rb +123 -3
  10. data/lib/ruby_llm/mcp/coordinator.rb +108 -115
  11. data/lib/ruby_llm/mcp/errors.rb +3 -1
  12. data/lib/ruby_llm/mcp/notification_handler.rb +84 -0
  13. data/lib/ruby_llm/mcp/{requests/cancelled_notification.rb → notifications/cancelled.rb} +2 -2
  14. data/lib/ruby_llm/mcp/{requests/initialize_notification.rb → notifications/initialize.rb} +7 -3
  15. data/lib/ruby_llm/mcp/notifications/roots_list_change.rb +26 -0
  16. data/lib/ruby_llm/mcp/parameter.rb +19 -1
  17. data/lib/ruby_llm/mcp/progress.rb +3 -1
  18. data/lib/ruby_llm/mcp/prompt.rb +18 -0
  19. data/lib/ruby_llm/mcp/railtie.rb +20 -0
  20. data/lib/ruby_llm/mcp/requests/initialization.rb +8 -4
  21. data/lib/ruby_llm/mcp/requests/ping.rb +6 -2
  22. data/lib/ruby_llm/mcp/requests/prompt_list.rb +10 -2
  23. data/lib/ruby_llm/mcp/requests/resource_list.rb +12 -2
  24. data/lib/ruby_llm/mcp/requests/resource_template_list.rb +12 -2
  25. data/lib/ruby_llm/mcp/requests/shared/meta.rb +32 -0
  26. data/lib/ruby_llm/mcp/requests/shared/pagination.rb +17 -0
  27. data/lib/ruby_llm/mcp/requests/tool_call.rb +1 -1
  28. data/lib/ruby_llm/mcp/requests/tool_list.rb +10 -2
  29. data/lib/ruby_llm/mcp/resource.rb +17 -0
  30. data/lib/ruby_llm/mcp/response_handler.rb +58 -0
  31. data/lib/ruby_llm/mcp/responses/error.rb +33 -0
  32. data/lib/ruby_llm/mcp/{requests/ping_response.rb → responses/ping.rb} +2 -2
  33. data/lib/ruby_llm/mcp/responses/roots_list.rb +31 -0
  34. data/lib/ruby_llm/mcp/responses/sampling_create_message.rb +50 -0
  35. data/lib/ruby_llm/mcp/result.rb +21 -8
  36. data/lib/ruby_llm/mcp/roots.rb +45 -0
  37. data/lib/ruby_llm/mcp/sample.rb +148 -0
  38. data/lib/ruby_llm/mcp/{capabilities.rb → server_capabilities.rb} +1 -1
  39. data/lib/ruby_llm/mcp/tool.rb +35 -4
  40. data/lib/ruby_llm/mcp/transport.rb +58 -0
  41. data/lib/ruby_llm/mcp/transports/http_client.rb +26 -0
  42. data/lib/ruby_llm/mcp/{transport → transports}/sse.rb +25 -24
  43. data/lib/ruby_llm/mcp/{transport → transports}/stdio.rb +28 -26
  44. data/lib/ruby_llm/mcp/{transport → transports}/streamable_http.rb +25 -29
  45. data/lib/ruby_llm/mcp/transports/timeout.rb +32 -0
  46. data/lib/ruby_llm/mcp/version.rb +1 -1
  47. data/lib/ruby_llm/mcp.rb +60 -9
  48. metadata +27 -11
  49. data/lib/ruby_llm/mcp/requests/base.rb +0 -31
  50. data/lib/ruby_llm/mcp/requests/meta.rb +0 -30
data/lib/ruby_llm/mcp.rb CHANGED
@@ -4,19 +4,59 @@ require "ruby_llm"
4
4
  require "zeitwerk"
5
5
  require_relative "chat"
6
6
 
7
- loader = Zeitwerk::Loader.for_gem_extension(RubyLLM)
8
- loader.inflector.inflect("mcp" => "MCP")
9
- loader.inflector.inflect("sse" => "SSE")
10
- loader.inflector.inflect("openai" => "OpenAI")
11
- loader.inflector.inflect("streamable_http" => "StreamableHTTP")
12
- loader.setup
13
-
14
7
  module RubyLLM
15
8
  module MCP
16
9
  module_function
17
10
 
18
- def client(*args, **kwargs)
19
- @client ||= Client.new(*args, **kwargs)
11
+ def clients(config = RubyLLM::MCP.config.mcp_configuration)
12
+ @clients ||= {}
13
+ config.map do |options|
14
+ @clients[options[:name]] ||= Client.new(**options)
15
+ end
16
+ end
17
+
18
+ def add_client(options)
19
+ @clients ||= {}
20
+ @clients[options[:name]] ||= Client.new(**options)
21
+ end
22
+
23
+ def remove_client(name)
24
+ @clients ||= {}
25
+ client = @clients.delete(name)
26
+ client&.stop
27
+ client
28
+ end
29
+
30
+ def client(...)
31
+ Client.new(...)
32
+ end
33
+
34
+ def establish_connection(&)
35
+ clients.each(&:start)
36
+ if block_given?
37
+ begin
38
+ yield clients
39
+ ensure
40
+ close_connection
41
+ end
42
+ else
43
+ clients
44
+ end
45
+ end
46
+
47
+ def close_connection
48
+ clients.each do |client|
49
+ client.stop if client.alive?
50
+ end
51
+ end
52
+
53
+ def tools(blacklist: [], whitelist: [])
54
+ tools = @clients.values.map(&:tools)
55
+ .flatten
56
+ .reject { |tool| blacklist.include?(tool.name) }
57
+
58
+ tools = tools.select { |tool| whitelist.include?(tool.name) } if whitelist.any?
59
+ tools.uniq(&:name)
20
60
  end
21
61
 
22
62
  def support_complex_parameters!
@@ -41,3 +81,14 @@ module RubyLLM
41
81
  end
42
82
  end
43
83
  end
84
+
85
+ require_relative "mcp/railtie" if defined?(Rails::Railtie)
86
+
87
+ loader = Zeitwerk::Loader.for_gem_extension(RubyLLM)
88
+ loader.inflector.inflect("mcp" => "MCP")
89
+ loader.inflector.inflect("sse" => "SSE")
90
+ loader.inflector.inflect("openai" => "OpenAI")
91
+ loader.inflector.inflect("streamable_http" => "StreamableHTTP")
92
+ loader.inflector.inflect("http_client" => "HTTPClient")
93
+
94
+ loader.setup
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Vice
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-02 00:00:00.000000000 Z
11
+ date: 2025-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpx
@@ -65,10 +65,13 @@ extra_rdoc_files: []
65
65
  files:
66
66
  - LICENSE
67
67
  - README.md
68
+ - lib/generators/ruby_llm/mcp/install_generator.rb
69
+ - lib/generators/ruby_llm/mcp/templates/README.txt
70
+ - lib/generators/ruby_llm/mcp/templates/initializer.rb
71
+ - lib/generators/ruby_llm/mcp/templates/mcps.yml
68
72
  - lib/ruby_llm/chat.rb
69
73
  - lib/ruby_llm/mcp.rb
70
74
  - lib/ruby_llm/mcp/attachment.rb
71
- - lib/ruby_llm/mcp/capabilities.rb
72
75
  - lib/ruby_llm/mcp/client.rb
73
76
  - lib/ruby_llm/mcp/completion.rb
74
77
  - lib/ruby_llm/mcp/configuration.rb
@@ -77,37 +80,50 @@ files:
77
80
  - lib/ruby_llm/mcp/error.rb
78
81
  - lib/ruby_llm/mcp/errors.rb
79
82
  - lib/ruby_llm/mcp/logging.rb
83
+ - lib/ruby_llm/mcp/notification_handler.rb
84
+ - lib/ruby_llm/mcp/notifications/cancelled.rb
85
+ - lib/ruby_llm/mcp/notifications/initialize.rb
86
+ - lib/ruby_llm/mcp/notifications/roots_list_change.rb
80
87
  - lib/ruby_llm/mcp/parameter.rb
81
88
  - lib/ruby_llm/mcp/progress.rb
82
89
  - lib/ruby_llm/mcp/prompt.rb
83
90
  - lib/ruby_llm/mcp/providers/anthropic/complex_parameter_support.rb
84
91
  - lib/ruby_llm/mcp/providers/gemini/complex_parameter_support.rb
85
92
  - lib/ruby_llm/mcp/providers/openai/complex_parameter_support.rb
86
- - lib/ruby_llm/mcp/requests/base.rb
87
- - lib/ruby_llm/mcp/requests/cancelled_notification.rb
93
+ - lib/ruby_llm/mcp/railtie.rb
88
94
  - lib/ruby_llm/mcp/requests/completion_prompt.rb
89
95
  - lib/ruby_llm/mcp/requests/completion_resource.rb
90
96
  - lib/ruby_llm/mcp/requests/initialization.rb
91
- - lib/ruby_llm/mcp/requests/initialize_notification.rb
92
97
  - lib/ruby_llm/mcp/requests/logging_set_level.rb
93
- - lib/ruby_llm/mcp/requests/meta.rb
94
98
  - lib/ruby_llm/mcp/requests/ping.rb
95
- - lib/ruby_llm/mcp/requests/ping_response.rb
96
99
  - lib/ruby_llm/mcp/requests/prompt_call.rb
97
100
  - lib/ruby_llm/mcp/requests/prompt_list.rb
98
101
  - lib/ruby_llm/mcp/requests/resource_list.rb
99
102
  - lib/ruby_llm/mcp/requests/resource_read.rb
100
103
  - lib/ruby_llm/mcp/requests/resource_template_list.rb
101
104
  - lib/ruby_llm/mcp/requests/resources_subscribe.rb
105
+ - lib/ruby_llm/mcp/requests/shared/meta.rb
106
+ - lib/ruby_llm/mcp/requests/shared/pagination.rb
102
107
  - lib/ruby_llm/mcp/requests/tool_call.rb
103
108
  - lib/ruby_llm/mcp/requests/tool_list.rb
104
109
  - lib/ruby_llm/mcp/resource.rb
105
110
  - lib/ruby_llm/mcp/resource_template.rb
111
+ - lib/ruby_llm/mcp/response_handler.rb
112
+ - lib/ruby_llm/mcp/responses/error.rb
113
+ - lib/ruby_llm/mcp/responses/ping.rb
114
+ - lib/ruby_llm/mcp/responses/roots_list.rb
115
+ - lib/ruby_llm/mcp/responses/sampling_create_message.rb
106
116
  - lib/ruby_llm/mcp/result.rb
117
+ - lib/ruby_llm/mcp/roots.rb
118
+ - lib/ruby_llm/mcp/sample.rb
119
+ - lib/ruby_llm/mcp/server_capabilities.rb
107
120
  - lib/ruby_llm/mcp/tool.rb
108
- - lib/ruby_llm/mcp/transport/sse.rb
109
- - lib/ruby_llm/mcp/transport/stdio.rb
110
- - lib/ruby_llm/mcp/transport/streamable_http.rb
121
+ - lib/ruby_llm/mcp/transport.rb
122
+ - lib/ruby_llm/mcp/transports/http_client.rb
123
+ - lib/ruby_llm/mcp/transports/sse.rb
124
+ - lib/ruby_llm/mcp/transports/stdio.rb
125
+ - lib/ruby_llm/mcp/transports/streamable_http.rb
126
+ - lib/ruby_llm/mcp/transports/timeout.rb
111
127
  - lib/ruby_llm/mcp/version.rb
112
128
  - lib/tasks/release.rake
113
129
  homepage: https://github.com/patvice/ruby_llm-mcp
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module RubyLLM
6
- module MCP
7
- module Requests
8
- class Base
9
- attr_reader :coordinator
10
-
11
- def initialize(coordinator)
12
- @coordinator = coordinator
13
- end
14
-
15
- def call
16
- raise "Not implemented"
17
- end
18
-
19
- private
20
-
21
- def validate_response!(response, body)
22
- # TODO: Implement response validation
23
- end
24
-
25
- def raise_error(error)
26
- raise "MCP Error: code: #{error['code']} message: #{error['message']} data: #{error['data']}"
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
-
5
- module RubyLLM
6
- module MCP
7
- module Requests
8
- module Meta
9
- def merge_meta(body)
10
- meta = {}
11
- meta.merge!(progress_token) if @coordinator.client.tracking_progress?
12
-
13
- body[:params] ||= {}
14
- body[:params].merge!({ _meta: meta }) unless meta.empty?
15
- body
16
- end
17
-
18
- private
19
-
20
- def progress_token
21
- { progressToken: generate_progress_token }
22
- end
23
-
24
- def generate_progress_token
25
- SecureRandom.uuid
26
- end
27
- end
28
- end
29
- end
30
- end