ruby_llm_swarm-mcp 0.8.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.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +277 -0
  4. data/lib/generators/ruby_llm/mcp/install/install_generator.rb +42 -0
  5. data/lib/generators/ruby_llm/mcp/install/templates/initializer.rb +56 -0
  6. data/lib/generators/ruby_llm/mcp/install/templates/mcps.yml +29 -0
  7. data/lib/generators/ruby_llm/mcp/oauth/install_generator.rb +354 -0
  8. data/lib/generators/ruby_llm/mcp/oauth/templates/concerns/mcp_token_storage.rb.tt +114 -0
  9. data/lib/generators/ruby_llm/mcp/oauth/templates/concerns/user_mcp_oauth_concern.rb.tt +90 -0
  10. data/lib/generators/ruby_llm/mcp/oauth/templates/controllers/mcp_connections_controller.rb.tt +239 -0
  11. data/lib/generators/ruby_llm/mcp/oauth/templates/jobs/cleanup_expired_oauth_states_job.rb.tt +27 -0
  12. data/lib/generators/ruby_llm/mcp/oauth/templates/jobs/example_job.rb.tt +78 -0
  13. data/lib/generators/ruby_llm/mcp/oauth/templates/lib/mcp_client.rb.tt +68 -0
  14. data/lib/generators/ruby_llm/mcp/oauth/templates/migrations/create_mcp_oauth_credentials.rb.tt +19 -0
  15. data/lib/generators/ruby_llm/mcp/oauth/templates/migrations/create_mcp_oauth_states.rb.tt +21 -0
  16. data/lib/generators/ruby_llm/mcp/oauth/templates/models/mcp_oauth_credential.rb.tt +54 -0
  17. data/lib/generators/ruby_llm/mcp/oauth/templates/models/mcp_oauth_state.rb.tt +30 -0
  18. data/lib/generators/ruby_llm/mcp/oauth/templates/views/index.html.erb +646 -0
  19. data/lib/generators/ruby_llm/mcp/oauth/templates/views/show.html.erb +560 -0
  20. data/lib/ruby_llm/chat.rb +34 -0
  21. data/lib/ruby_llm/mcp/adapters/base_adapter.rb +179 -0
  22. data/lib/ruby_llm/mcp/adapters/mcp_sdk_adapter.rb +292 -0
  23. data/lib/ruby_llm/mcp/adapters/mcp_transports/coordinator_stub.rb +33 -0
  24. data/lib/ruby_llm/mcp/adapters/mcp_transports/sse.rb +52 -0
  25. data/lib/ruby_llm/mcp/adapters/mcp_transports/stdio.rb +52 -0
  26. data/lib/ruby_llm/mcp/adapters/mcp_transports/streamable_http.rb +86 -0
  27. data/lib/ruby_llm/mcp/adapters/ruby_llm_adapter.rb +92 -0
  28. data/lib/ruby_llm/mcp/attachment.rb +18 -0
  29. data/lib/ruby_llm/mcp/auth/browser/callback_handler.rb +71 -0
  30. data/lib/ruby_llm/mcp/auth/browser/callback_server.rb +30 -0
  31. data/lib/ruby_llm/mcp/auth/browser/http_server.rb +112 -0
  32. data/lib/ruby_llm/mcp/auth/browser/opener.rb +39 -0
  33. data/lib/ruby_llm/mcp/auth/browser/pages.rb +607 -0
  34. data/lib/ruby_llm/mcp/auth/browser_oauth_provider.rb +280 -0
  35. data/lib/ruby_llm/mcp/auth/client_registrar.rb +170 -0
  36. data/lib/ruby_llm/mcp/auth/discoverer.rb +124 -0
  37. data/lib/ruby_llm/mcp/auth/flows/authorization_code_flow.rb +105 -0
  38. data/lib/ruby_llm/mcp/auth/flows/client_credentials_flow.rb +66 -0
  39. data/lib/ruby_llm/mcp/auth/grant_strategies/authorization_code.rb +31 -0
  40. data/lib/ruby_llm/mcp/auth/grant_strategies/base.rb +31 -0
  41. data/lib/ruby_llm/mcp/auth/grant_strategies/client_credentials.rb +31 -0
  42. data/lib/ruby_llm/mcp/auth/http_response_handler.rb +63 -0
  43. data/lib/ruby_llm/mcp/auth/memory_storage.rb +90 -0
  44. data/lib/ruby_llm/mcp/auth/oauth_provider.rb +305 -0
  45. data/lib/ruby_llm/mcp/auth/security.rb +44 -0
  46. data/lib/ruby_llm/mcp/auth/session_manager.rb +54 -0
  47. data/lib/ruby_llm/mcp/auth/token_manager.rb +236 -0
  48. data/lib/ruby_llm/mcp/auth/transport_oauth_helper.rb +107 -0
  49. data/lib/ruby_llm/mcp/auth/url_builder.rb +76 -0
  50. data/lib/ruby_llm/mcp/auth.rb +359 -0
  51. data/lib/ruby_llm/mcp/client.rb +401 -0
  52. data/lib/ruby_llm/mcp/completion.rb +16 -0
  53. data/lib/ruby_llm/mcp/configuration.rb +310 -0
  54. data/lib/ruby_llm/mcp/content.rb +28 -0
  55. data/lib/ruby_llm/mcp/elicitation.rb +48 -0
  56. data/lib/ruby_llm/mcp/error.rb +34 -0
  57. data/lib/ruby_llm/mcp/errors.rb +91 -0
  58. data/lib/ruby_llm/mcp/logging.rb +16 -0
  59. data/lib/ruby_llm/mcp/native/cancellable_operation.rb +57 -0
  60. data/lib/ruby_llm/mcp/native/client.rb +387 -0
  61. data/lib/ruby_llm/mcp/native/json_rpc.rb +170 -0
  62. data/lib/ruby_llm/mcp/native/messages/helpers.rb +39 -0
  63. data/lib/ruby_llm/mcp/native/messages/notifications.rb +42 -0
  64. data/lib/ruby_llm/mcp/native/messages/requests.rb +206 -0
  65. data/lib/ruby_llm/mcp/native/messages/responses.rb +106 -0
  66. data/lib/ruby_llm/mcp/native/messages.rb +36 -0
  67. data/lib/ruby_llm/mcp/native/notification.rb +16 -0
  68. data/lib/ruby_llm/mcp/native/protocol.rb +36 -0
  69. data/lib/ruby_llm/mcp/native/response_handler.rb +110 -0
  70. data/lib/ruby_llm/mcp/native/transport.rb +88 -0
  71. data/lib/ruby_llm/mcp/native/transports/sse.rb +607 -0
  72. data/lib/ruby_llm/mcp/native/transports/stdio.rb +356 -0
  73. data/lib/ruby_llm/mcp/native/transports/streamable_http.rb +926 -0
  74. data/lib/ruby_llm/mcp/native/transports/support/http_client.rb +28 -0
  75. data/lib/ruby_llm/mcp/native/transports/support/rate_limit.rb +49 -0
  76. data/lib/ruby_llm/mcp/native/transports/support/timeout.rb +36 -0
  77. data/lib/ruby_llm/mcp/native.rb +12 -0
  78. data/lib/ruby_llm/mcp/notification_handler.rb +100 -0
  79. data/lib/ruby_llm/mcp/progress.rb +35 -0
  80. data/lib/ruby_llm/mcp/prompt.rb +132 -0
  81. data/lib/ruby_llm/mcp/railtie.rb +14 -0
  82. data/lib/ruby_llm/mcp/resource.rb +112 -0
  83. data/lib/ruby_llm/mcp/resource_template.rb +85 -0
  84. data/lib/ruby_llm/mcp/result.rb +108 -0
  85. data/lib/ruby_llm/mcp/roots.rb +45 -0
  86. data/lib/ruby_llm/mcp/sample.rb +152 -0
  87. data/lib/ruby_llm/mcp/server_capabilities.rb +49 -0
  88. data/lib/ruby_llm/mcp/tool.rb +228 -0
  89. data/lib/ruby_llm/mcp/version.rb +7 -0
  90. data/lib/ruby_llm/mcp.rb +125 -0
  91. data/lib/tasks/release.rake +23 -0
  92. metadata +184 -0
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ module Native
6
+ class ResponseHandler
7
+ attr_reader :coordinator
8
+
9
+ def initialize(coordinator)
10
+ @coordinator = coordinator
11
+ end
12
+
13
+ def execute(result)
14
+ operation = CancellableOperation.new(result.id)
15
+ coordinator.register_in_flight_request(result.id, operation)
16
+
17
+ begin
18
+ # Execute in a separate thread that can be terminated on cancellation
19
+ operation.execute do
20
+ if result.ping?
21
+ coordinator.ping_response(id: result.id)
22
+ true
23
+ elsif result.roots?
24
+ handle_roots_response(result)
25
+ true
26
+ elsif result.sampling?
27
+ handle_sampling_response(result)
28
+ true
29
+ elsif result.elicitation?
30
+ handle_elicitation_response(result)
31
+ true
32
+ else
33
+ handle_unknown_request(result)
34
+ RubyLLM::MCP.logger.error("MCP client was sent unknown method type and \
35
+ could not respond: #{result.inspect}.")
36
+ false
37
+ end
38
+ end
39
+ rescue Errors::RequestCancelled => e
40
+ RubyLLM::MCP.logger.info("Request #{result.id} was cancelled: #{e.message}")
41
+ # Don't send response - cancellation means result is unused
42
+ true
43
+ ensure
44
+ coordinator.unregister_in_flight_request(result.id)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def handle_roots_response(result)
51
+ RubyLLM::MCP.logger.info("Roots request: #{result.inspect}")
52
+ roots_paths = coordinator.roots_paths
53
+ if roots_paths&.any?
54
+ coordinator.roots_list_response(id: result.id)
55
+ else
56
+ coordinator.error_response(
57
+ id: result.id,
58
+ message: "Roots are not enabled",
59
+ code: Native::JsonRpc::ErrorCodes::SERVER_ERROR
60
+ )
61
+ end
62
+ rescue StandardError => e
63
+ RubyLLM::MCP.logger.error("Error in roots request: #{e.message}\n#{e.backtrace.join("\n")}")
64
+ coordinator.error_response(
65
+ id: result.id,
66
+ message: "Internal error processing roots request",
67
+ code: Native::JsonRpc::ErrorCodes::INTERNAL_ERROR,
68
+ data: { detail: e.message }
69
+ )
70
+ end
71
+
72
+ def handle_sampling_response(result)
73
+ unless MCP.config.sampling.enabled?
74
+ RubyLLM::MCP.logger.info("Sampling is disabled, yet server requested sampling")
75
+ coordinator.error_response(
76
+ id: result.id,
77
+ message: "Sampling is disabled",
78
+ code: Native::JsonRpc::ErrorCodes::SERVER_ERROR
79
+ )
80
+ return
81
+ end
82
+
83
+ RubyLLM::MCP.logger.info("Sampling request: #{result.inspect}")
84
+ Sample.new(result, coordinator).execute
85
+ rescue StandardError => e
86
+ RubyLLM::MCP.logger.error("Error in sampling request: #{e.message}\n#{e.backtrace.join("\n")}")
87
+ coordinator.error_response(
88
+ id: result.id,
89
+ message: "Internal error processing sampling request",
90
+ code: Native::JsonRpc::ErrorCodes::INTERNAL_ERROR,
91
+ data: { detail: e.message }
92
+ )
93
+ end
94
+
95
+ def handle_elicitation_response(result)
96
+ RubyLLM::MCP.logger.info("Elicitation request: #{result.inspect}")
97
+ Elicitation.new(coordinator, result).execute
98
+ end
99
+
100
+ def handle_unknown_request(result)
101
+ coordinator.error_response(
102
+ id: result.id,
103
+ message: "Method not found: #{result.method}",
104
+ code: Native::JsonRpc::ErrorCodes::METHOD_NOT_FOUND
105
+ )
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module MCP
5
+ module Native
6
+ class Transport
7
+ class << self
8
+ def transports
9
+ @transports ||= {}
10
+ end
11
+
12
+ def register_transport(transport_type, transport_class)
13
+ transports[transport_type] = transport_class
14
+ end
15
+ end
16
+
17
+ extend Forwardable
18
+
19
+ register_transport(:sse, RubyLLM::MCP::Native::Transports::SSE)
20
+ register_transport(:stdio, RubyLLM::MCP::Native::Transports::Stdio)
21
+ register_transport(:streamable, RubyLLM::MCP::Native::Transports::StreamableHTTP)
22
+ register_transport(:streamable_http, RubyLLM::MCP::Native::Transports::StreamableHTTP)
23
+
24
+ attr_reader :transport_type, :coordinator, :config, :pid
25
+
26
+ def initialize(transport_type, coordinator, config:)
27
+ @transport_type = transport_type
28
+ @coordinator = coordinator
29
+ @config = config
30
+ @pid = Process.pid
31
+ end
32
+
33
+ def_delegators :transport_protocol, :request, :alive?, :close, :start, :set_protocol_version
34
+
35
+ def transport_protocol
36
+ if @pid != Process.pid
37
+ @pid = Process.pid
38
+ @transport_protocol = nil
39
+ @transport_protocol = build_transport
40
+ end
41
+
42
+ @transport_protocol ||= build_transport
43
+ end
44
+
45
+ private
46
+
47
+ def build_transport
48
+ unless RubyLLM::MCP::Native::Transport.transports.key?(transport_type)
49
+ supported_types = RubyLLM::MCP::Native::Transport.transports.keys.join(", ")
50
+ message = "Invalid transport type: :#{transport_type}. Supported types are #{supported_types}"
51
+ raise Errors::InvalidTransportType.new(message: message)
52
+ end
53
+
54
+ # Extract and merge options if present (from OAuth helper preparation)
55
+ transport_config = config.dup
56
+ if transport_config[:options]
57
+ options = transport_config.delete(:options)
58
+ transport_config.merge!(options)
59
+ end
60
+
61
+ # Handle SSE transport specially - it uses options hash pattern
62
+ if transport_type == :sse
63
+ url = transport_config.delete(:url) || transport_config.delete("url")
64
+ request_timeout = transport_config.delete(:request_timeout) ||
65
+ transport_config.delete("request_timeout") ||
66
+ MCP.config.request_timeout
67
+ # Everything else goes into options
68
+ options_hash = transport_config.dup
69
+ transport_config.clear
70
+ transport_config[:url] = url
71
+ transport_config[:request_timeout] = request_timeout
72
+ transport_config[:options] = options_hash
73
+ end
74
+
75
+ # Remove OAuth-specific params from transports that don't support them
76
+ # This allows other arbitrary params (like timeout) to pass through for testing
77
+ unless %i[streamable streamable_http sse].include?(transport_type)
78
+ transport_config.delete(:oauth_provider)
79
+ transport_config.delete(:oauth)
80
+ end
81
+
82
+ transport_klass = RubyLLM::MCP::Native::Transport.transports[transport_type]
83
+ transport_klass.new(coordinator: coordinator, **transport_config)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end