model-context-protocol-rb 0.3.2 → 0.3.4

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: 99ee11ebae0c9c984a15954b386e89489f6715a1d371b288ece0438d066436d7
4
- data.tar.gz: cdf41e8742941e9a0a41c714127d02f5a189d901a95a35e6f61a16f1f7b8a704
3
+ metadata.gz: e004a2e471748b63aa9f5742300fd2b648aabb84d02e75949f9741f1c78aa963
4
+ data.tar.gz: ecf369882f10f2cb19099d47d991ba70e6d33e0841ebda48e6917fb98780f2fb
5
5
  SHA512:
6
- metadata.gz: aad64bd6e42b2ecea2b742bb23b902ea1bfae3e77cffc5009b0a83712df7efac417b680bb460649bb73fff31d2d554d33a17d6fe96adb35f3cc9d0addbbcf9be
7
- data.tar.gz: 6fa834a1c2bf4494748474dc26a29bede650b1a1913c7c543a7c195fd24fe99eda5f1610c40a63067f454b50f669b1fb47030e106fa6ba80cbde522bccfb9518
6
+ metadata.gz: 9fa10a6fe78390f0d8c6303efcc6135751b50a2c4dd3cc2b0ed0d07e0647e9555a3317a7b68b2794d52f9d6ac3e8e38a569bda96e20bc2e0193dadefab346dd6
7
+ data.tar.gz: fda6f707e7814911a4e9175cd40fbab41f45dc775db644185e4bc2008fe71bddd83c89ed321cfeee67f93cbf7c29bc6e69b46596893e3ae2242fa757fd9f4f75
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.4] - 2025-09-02
4
+
5
+ - (Fix) Fixes broken arguments usage in prompts and tools.
6
+
7
+ ## [0.3.3] - 2025-09-02
8
+
9
+ - (Breaking) Added logging support.
10
+ - Requires updating the `enable_log` configuration option to `logging_enabled`.
11
+ - Added experimental Streamable HTTP transport.
12
+ - (Breaking) Renamed params to arguments in prompts, resources, and tools.
13
+ - Requires updating all references to `params` in prompts, resources, and tools to `arguments` with symbolized keys.
14
+ - Improved ergonomics of completions and resource templates.
15
+ - Added support for providing context to prompts, resources, and tools.
16
+
3
17
  ## [0.3.2] - 2025-05-10
4
18
 
5
19
  - Added resource template support.
@@ -37,8 +51,10 @@
37
51
 
38
52
  - Initial release
39
53
 
40
- [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.2...HEAD
41
- [0.3.1]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.1...v0.3.2
54
+ [Unreleased]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.4...HEAD
55
+ [0.3.4]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.3...v0.3.4
56
+ [0.3.3]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.2...v0.3.3
57
+ [0.3.2]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.1...v0.3.2
42
58
  [0.3.1]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.3.0...v0.3.1
43
59
  [0.3.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.2.0...v0.3.0
44
60
  [0.2.0]: https://github.com/dickdavis/model-context-protocol-rb/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -30,7 +30,7 @@ Build a simple MCP server by registering your prompts, resources, resource templ
30
30
  server = ModelContextProtocol::Server.new do |config|
31
31
  config.name = "MCP Development Server"
32
32
  config.version = "1.0.0"
33
- config.enable_log = true
33
+ config.logging_enabled = true
34
34
 
35
35
  # Environment Variables - https://modelcontextprotocol.io/docs/tools/debugging#environment-variables
36
36
  # Require specific environment variables to be set
@@ -39,6 +39,12 @@ server = ModelContextProtocol::Server.new do |config|
39
39
  # Set environment variables programmatically
40
40
  config.set_environment_variable("DEBUG_MODE", "true")
41
41
 
42
+ # Provide prompts, resources, and tools with contextual variables
43
+ config.context = {
44
+ user_id: "123456",
45
+ request_id: SecureRandom.uuid
46
+ }
47
+
42
48
  config.registry = ModelContextProtocol::Server::Registry.new do
43
49
  prompts list_changed: true do
44
50
  register TestPrompt
@@ -61,45 +67,159 @@ end
61
67
  server.start
62
68
  ```
63
69
 
70
+ ### Transport Configuration
71
+
72
+ The MCP server supports different transport mechanisms for communication with clients. By default, it uses stdio (standard input/output), but you can also configure it to use streamable HTTP transport for distributed deployments.
73
+
74
+ #### Stdio Transport (Default)
75
+
76
+ When no transport is specified, the server uses stdio transport, which is suitable for single-process communication:
77
+
78
+ ```ruby
79
+ server = ModelContextProtocol::Server.new do |config|
80
+ config.name = "MCP Development Server"
81
+ config.version = "1.0.0"
82
+ # No transport specified - uses stdio by default
83
+ config.registry = ModelContextProtocol::Server::Registry.new
84
+ end
85
+
86
+ server.start
87
+ ```
88
+
89
+ #### Streamable HTTP Transport
90
+
91
+ For distributed deployments with load balancers and multiple server instances, use the streamable HTTP transport with Redis-backed session management:
92
+
93
+ ```ruby
94
+ require 'redis'
95
+
96
+ server = ModelContextProtocol::Server.new do |config|
97
+ config.name = "MCP Development Server"
98
+ config.version = "1.0.0"
99
+
100
+ # Configure streamable HTTP transport
101
+ config.transport = {
102
+ type: :streamable_http,
103
+ redis_client: Redis.new(url: ENV['REDIS_URL']),
104
+ session_ttl: 3600 # Optional: session timeout in seconds (default: 3600)
105
+ }
106
+
107
+ config.registry = ModelContextProtocol::Server::Registry.new
108
+ end
109
+
110
+ # For HTTP frameworks, handle the request and return the response
111
+ result = server.start
112
+ # result will be a hash like: {json: {...}, status: 200, headers: {...}}
113
+ ```
114
+
115
+ **Key Features:**
116
+ - **Distributed Sessions**: Redis-backed session storage enables multiple server instances
117
+ - **Load Balancer Support**: Sessions persist across different server instances
118
+ - **HTTP Methods**: Supports POST (requests), GET (Server-Sent Events), DELETE (cleanup)
119
+ - **Cross-Server Routing**: Messages are routed between servers via Redis pub/sub
120
+
121
+ **Integration Example (Rails):**
122
+
123
+ ```ruby
124
+ class McpController < ApplicationController
125
+ def handle
126
+ server = ModelContextProtocol::Server.new do |config|
127
+ config.name = "Rails MCP Server"
128
+ config.version = "1.0.0"
129
+ config.transport = {
130
+ type: :streamable_http,
131
+ redis_client: Redis.new(url: ENV['REDIS_URL']),
132
+ request: request,
133
+ response: response
134
+ }
135
+ config.registry = build_registry
136
+ end
137
+
138
+ result = server.start
139
+ render json: result[:json], status: result[:status], headers: result[:headers]
140
+ end
141
+ end
142
+ ```
143
+
64
144
  Messages from the MCP client will be routed to the appropriate custom handler. This SDK provides several classes that should be used to build your handlers.
65
145
 
146
+ ### Server features
147
+
66
148
  #### Prompts
67
149
 
68
150
  The `ModelContextProtocol::Server::Prompt` base class allows subclasses to define a prompt that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/prompts/) in the `with_metadata` block.
69
151
 
70
- Define any arguments using the `with_argument` block. You can mark an argument as required, and you can optionally provide the class name of a service object that provides completions. See [Completions](#completions) for more information.
152
+ Define any arguments using the `with_argument` block. You can mark an argument as required, and you can optionally provide a completion class. See [Completions](#completions) for more information.
153
+
154
+ Then implement the `call` method to build your prompt. Any arguments passed to the tool from the MCP client will be available in the `arguments` hash with symbol keys (e.g., `arguments[:argument_name]`), and any context values provided in the server configuration will be available in the `context` hash. Use the `respond_with` instance method to ensure your prompt responds with appropriately formatted response data.
71
155
 
72
- Then implement the `call` method to build your prompt. Use the `respond_with` instance method to ensure your prompt responds with appropriately formatted response data.
156
+ You can also log from within your prompt by calling a valid logger level method on the `logger` and passing a string message.
73
157
 
74
158
  This is an example prompt that returns a properly formatted response:
75
159
 
76
160
  ```ruby
77
161
  class TestPrompt < ModelContextProtocol::Server::Prompt
162
+ ToneCompletion = ModelContextProtocol::Server::Completion.define do
163
+ hints = ["whiny", "angry", "callous", "desperate", "nervous", "sneaky"]
164
+ values = hints.grep(/#{argument_value}/)
165
+
166
+ respond_with values:
167
+ end
168
+
78
169
  with_metadata do
79
- name "test_prompt"
80
- description "A test prompt"
170
+ name "brainstorm_excuses"
171
+ description "A prompt for brainstorming excuses to get out of something"
81
172
  end
82
173
 
83
174
  with_argument do
84
- name "message"
85
- description "The thing to do"
175
+ name "undesirable_activity"
176
+ description "The thing to get out of"
86
177
  required true
87
- completion TestCompletion
88
178
  end
89
179
 
90
180
  with_argument do
91
- name "other"
92
- description "Another thing to do"
181
+ name "tone"
182
+ description "The general tone to be used in the generated excuses"
93
183
  required false
184
+ completion ToneCompletion
94
185
  end
95
186
 
96
187
  def call
188
+ logger.info("Brainstorming excuses...")
97
189
  messages = [
98
190
  {
99
191
  role: "user",
100
192
  content: {
101
193
  type: "text",
102
- text: "Do this: #{params["message"]}"
194
+ text: "My wife wants me to: #{arguments[:undesirable_activity]}... Can you believe it?"
195
+ }
196
+ },
197
+ {
198
+ role: "assistant",
199
+ content: {
200
+ type: "text",
201
+ text: "Oh, that's just downright awful. What are you going to do?"
202
+ }
203
+ },
204
+ {
205
+ role: "user",
206
+ content: {
207
+ type: "text",
208
+ text: "Well, I'd like to get out of it, but I'm going to need your help."
209
+ }
210
+ },
211
+ {
212
+ role: "assistant",
213
+ content: {
214
+ type: "text",
215
+ text: "Anything for you."
216
+ }
217
+ },
218
+ {
219
+ role: "user",
220
+ content: {
221
+ type: "text",
222
+ text: "Can you generate some excuses for me?" + (arguments[:tone] ? "Make them as #{arguments[:tone]} as possible." : "")
103
223
  }
104
224
  }
105
225
  ]
@@ -113,21 +233,35 @@ end
113
233
 
114
234
  The `ModelContextProtocol::Server::Resource` base class allows subclasses to define a resource that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/) in the `with_metadata` block.
115
235
 
116
- Then, implement the `call` method to build your resource. Use the `respond_with` instance method to ensure your resource responds with appropriately formatted response data.
236
+ Then, implement the `call` method to build your resource. Any context values provided in the server configuration will be available in the `context` hash. Use the `respond_with` instance method to ensure your resource responds with appropriately formatted response data.
237
+
238
+ You can also log from within your resource by calling a valid logger level method on the `logger` and passing a string message.
117
239
 
118
240
  This is an example resource that returns a text response:
119
241
 
120
242
  ```ruby
121
243
  class TestResource < ModelContextProtocol::Server::Resource
122
244
  with_metadata do
123
- name "Test Resource"
124
- description "A test resource"
245
+ name "top-secret-plans.txt"
246
+ description "Top secret plans to do top secret things"
125
247
  mime_type "text/plain"
126
- uri "resource://test-resource"
248
+ uri "file:///top-secret-plans.txt"
127
249
  end
128
250
 
129
251
  def call
130
- respond_with :text, text: "Here's the data"
252
+ unless authorized?(context[:user_id])
253
+ logger.info("This fool thinks he can get my top secret plans...")
254
+ return respond_with :text, text: "Nothing to see here, move along."
255
+ end
256
+
257
+ respond_with :text, text: "I'm finna eat all my wife's leftovers."
258
+ end
259
+
260
+ private
261
+
262
+ def authorized?(user_id)
263
+ authorized_users = ["42", "123456"]
264
+ authorized_users.any?(user_id)
131
265
  end
132
266
  end
133
267
  ```
@@ -137,14 +271,15 @@ This is an example resource that returns binary data:
137
271
  ```ruby
138
272
  class TestBinaryResource < ModelContextProtocol::Server::Resource
139
273
  with_metadata do
140
- name "Project Logo"
274
+ name "project-logo.png"
141
275
  description "The logo for the project"
142
- mime_type "image/jpeg"
143
- uri "resource://project-logo"
276
+ mime_type "image/png"
277
+ uri "file:///project-logo.png"
144
278
  end
145
279
 
146
280
  def call
147
281
  # In a real implementation, we would retrieve the binary resource
282
+ # This is a small valid base64 encoded string (represents "test")
148
283
  data = "dGVzdA=="
149
284
  respond_with :binary, blob: data
150
285
  end
@@ -158,24 +293,22 @@ The `ModelContextProtocol::Server::ResourceTemplate` base class allows subclasse
158
293
  This is an example resource template that provides a completion for a parameter of the URI template:
159
294
 
160
295
  ```ruby
161
- class TestResourceTemplateCompletion < ModelContextProtocol::Server::Completion
162
- def call
296
+ class TestResourceTemplate < ModelContextProtocol::Server::ResourceTemplate
297
+ Completion = ModelContextProtocol::Server::Completion.define do
163
298
  hints = {
164
- "name" => ["test-resource", "project-logo"]
299
+ "name" => ["top-secret-plans.txt"]
165
300
  }
166
301
  values = hints[argument_name].grep(/#{argument_value}/)
167
302
 
168
303
  respond_with values:
169
304
  end
170
- end
171
305
 
172
- class TestResourceTemplate < ModelContextProtocol::Server::ResourceTemplate
173
306
  with_metadata do
174
- name "Test Resource Template"
175
- description "A test resource template"
307
+ name "project-document-resource-template"
308
+ description "A resource template for retrieving project documents"
176
309
  mime_type "text/plain"
177
- uri_template "resource://{name}" do
178
- completion :name, TestResourceTemplateCompletion
310
+ uri_template "file:///{name}" do
311
+ completion :name, Completion
179
312
  end
180
313
  end
181
314
  end
@@ -185,7 +318,9 @@ end
185
318
 
186
319
  The `ModelContextProtocol::Server::Tool` base class allows subclasses to define a tool that the MCP client can use. Define the [appropriate metadata](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/tools/) in the `with_metadata` block.
187
320
 
188
- Then implement the `call` method to build your tool. Use the `respond_with` instance method to ensure your tool responds with appropriately formatted response data.
321
+ Then, implement the `call` method to build your tool. Any arguments passed to the tool from the MCP client will be available in the `arguments` hash with symbol keys (e.g., `arguments[:argument_name]`), and any context values provided in the server configuration will be available in the `context` hash. Use the `respond_with` instance method to ensure your tool responds with appropriately formatted response data.
322
+
323
+ You can also log from within your tool by calling a valid logger level method on the `logger` and passing a string message.
189
324
 
190
325
  This is an example tool that returns a text response:
191
326
 
@@ -208,9 +343,12 @@ class TestToolWithTextResponse < ModelContextProtocol::Server::Tool
208
343
  end
209
344
 
210
345
  def call
211
- number = params["number"].to_i
212
- result = number * 2
213
- respond_with :text, text: "#{number} doubled is #{result}"
346
+ user_id = context[:user_id]
347
+ number = arguments[:number].to_i
348
+ logger.info("Silly user doesn't know how to double a number")
349
+ calculation = number * 2
350
+ salutation = user_id ? "User #{user_id}, " : ""
351
+ respond_with :text, text: salutation << "#{number} doubled is #{calculation}"
214
352
  end
215
353
  end
216
354
  ```
@@ -242,7 +380,7 @@ class TestToolWithImageResponse < ModelContextProtocol::Server::Tool
242
380
 
243
381
  def call
244
382
  # Map format to mime type
245
- mime_type = case params["format"].downcase
383
+ mime_type = case arguments[:format].downcase
246
384
  when "svg"
247
385
  "image/svg+xml"
248
386
  when "jpg", "jpeg"
@@ -311,7 +449,7 @@ class TestToolWithResourceResponse < ModelContextProtocol::Server::Tool
311
449
  end
312
450
 
313
451
  def call
314
- title = params["title"].downcase
452
+ title = arguments[:title].downcase
315
453
  # In a real implementation, we would do a lookup to get the document data
316
454
  document = "richtextdata"
317
455
  respond_with :resource, uri: "resource://document/#{title}", text: document, mime_type: "application/rtf"
@@ -15,6 +15,12 @@ module ModelContextProtocol
15
15
  new(...).call
16
16
  end
17
17
 
18
+ def self.define(&block)
19
+ Class.new(self) do
20
+ define_method(:call, &block)
21
+ end
22
+ end
23
+
18
24
  private
19
25
 
20
26
  Response = Data.define(:values, :total, :hasMore) do
@@ -1,3 +1,5 @@
1
+ require_relative "mcp_logger"
2
+
1
3
  module ModelContextProtocol
2
4
  class Server::Configuration
3
5
  # Raised when configured with invalid name.
@@ -12,16 +14,74 @@ module ModelContextProtocol
12
14
  # Raised when a required environment variable is not set
13
15
  class MissingRequiredEnvironmentVariable < StandardError; end
14
16
 
15
- attr_accessor :enable_log, :name, :registry, :version
17
+ # Raised when transport configuration is invalid
18
+ class InvalidTransportError < StandardError; end
19
+
20
+ # Raised when an invalid log level is provided
21
+ class InvalidLogLevelError < StandardError; end
22
+
23
+ # Valid MCP log levels per the specification
24
+ VALID_LOG_LEVELS = %w[debug info notice warning error critical alert emergency].freeze
25
+
26
+ attr_accessor :name, :registry, :version, :transport
27
+ attr_reader :logger
28
+
29
+ def initialize
30
+ # Always create a logger - enabled by default
31
+ @logging_enabled = true
32
+ @default_log_level = "info"
33
+ @logger = ModelContextProtocol::Server::MCPLogger.new(
34
+ logger_name: "server",
35
+ level: @default_log_level,
36
+ enabled: @logging_enabled
37
+ )
38
+ end
16
39
 
17
40
  def logging_enabled?
18
- enable_log || false
41
+ @logging_enabled
42
+ end
43
+
44
+ def logging_enabled=(value)
45
+ @logging_enabled = value
46
+ @logger = ModelContextProtocol::Server::MCPLogger.new(
47
+ logger_name: "server",
48
+ level: @default_log_level,
49
+ enabled: value
50
+ )
51
+ end
52
+
53
+ def default_log_level=(level)
54
+ unless VALID_LOG_LEVELS.include?(level.to_s)
55
+ raise InvalidLogLevelError, "Invalid log level: #{level}. Valid levels are: #{VALID_LOG_LEVELS.join(", ")}"
56
+ end
57
+
58
+ @default_log_level = level.to_s
59
+ @logger.set_mcp_level(@default_log_level)
60
+ end
61
+
62
+ def transport_type
63
+ case transport
64
+ when Hash
65
+ transport[:type] || transport["type"]
66
+ when Symbol, String
67
+ transport.to_sym
68
+ end
69
+ end
70
+
71
+ def transport_options
72
+ case transport
73
+ when Hash
74
+ transport.except(:type, "type").transform_keys(&:to_sym)
75
+ else
76
+ {}
77
+ end
19
78
  end
20
79
 
21
80
  def validate!
22
81
  raise InvalidServerNameError unless valid_name?
23
82
  raise InvalidRegistryError unless valid_registry?
24
83
  raise InvalidServerVersionError unless valid_version?
84
+ validate_transport!
25
85
 
26
86
  validate_environment_variables!
27
87
  end
@@ -51,6 +111,14 @@ module ModelContextProtocol
51
111
  environment_variables[key.to_s.upcase] = value
52
112
  end
53
113
 
114
+ def context
115
+ @context ||= {}
116
+ end
117
+
118
+ def context=(context_hash = {})
119
+ @context = context_hash
120
+ end
121
+
54
122
  private
55
123
 
56
124
  def required_environment_variables
@@ -74,5 +142,29 @@ module ModelContextProtocol
74
142
  def valid_version?
75
143
  version&.is_a?(String)
76
144
  end
145
+
146
+ def validate_transport!
147
+ case transport_type
148
+ when :streamable_http
149
+ validate_streamable_http_transport!
150
+ when :stdio, nil
151
+ # stdio transport has no required options
152
+ else
153
+ raise InvalidTransportError, "Unknown transport type: #{transport_type}" if transport_type
154
+ end
155
+ end
156
+
157
+ def validate_streamable_http_transport!
158
+ options = transport_options
159
+
160
+ unless options[:redis_client]
161
+ raise InvalidTransportError, "streamable_http transport requires redis_client option"
162
+ end
163
+
164
+ redis_client = options[:redis_client]
165
+ unless redis_client.respond_to?(:hset) && redis_client.respond_to?(:expire)
166
+ raise InvalidTransportError, "redis_client must be a Redis-compatible client"
167
+ end
168
+ end
77
169
  end
78
170
  end
@@ -0,0 +1,109 @@
1
+ require "logger"
2
+ require "forwardable"
3
+ require "json"
4
+
5
+ module ModelContextProtocol
6
+ class Server::MCPLogger
7
+ extend Forwardable
8
+
9
+ def_delegators :@internal_logger, :datetime_format=, :formatter=, :progname, :progname=
10
+
11
+ LEVEL_MAP = {
12
+ "debug" => Logger::DEBUG,
13
+ "info" => Logger::INFO,
14
+ "notice" => Logger::INFO,
15
+ "warning" => Logger::WARN,
16
+ "error" => Logger::ERROR,
17
+ "critical" => Logger::FATAL,
18
+ "alert" => Logger::FATAL,
19
+ "emergency" => Logger::UNKNOWN
20
+ }.freeze
21
+
22
+ REVERSE_LEVEL_MAP = {
23
+ Logger::DEBUG => "debug",
24
+ Logger::INFO => "info",
25
+ Logger::WARN => "warning",
26
+ Logger::ERROR => "error",
27
+ Logger::FATAL => "critical",
28
+ Logger::UNKNOWN => "emergency"
29
+ }.freeze
30
+
31
+ attr_accessor :transport
32
+ attr_reader :logger_name, :enabled
33
+
34
+ def initialize(logger_name: "server", level: "info", enabled: true)
35
+ @logger_name = logger_name
36
+ @enabled = enabled
37
+ @internal_logger = Logger.new(nil)
38
+ @internal_logger.level = LEVEL_MAP[level] || Logger::INFO
39
+ @transport = nil
40
+ @queued_messages = []
41
+ end
42
+
43
+ %i[debug info warn error fatal unknown].each do |severity|
44
+ define_method(severity) do |message = nil, **data, &block|
45
+ return true unless @enabled
46
+ add(Logger.const_get(severity.to_s.upcase), message, data, &block)
47
+ end
48
+ end
49
+
50
+ def add(severity, message = nil, data = {}, &block)
51
+ return true unless @enabled
52
+ return true if severity < @internal_logger.level
53
+
54
+ message = block.call if message.nil? && block_given?
55
+ send_notification(severity, message, data)
56
+ true
57
+ end
58
+
59
+ def level=(value)
60
+ @internal_logger.level = value
61
+ end
62
+
63
+ def level
64
+ @internal_logger.level
65
+ end
66
+
67
+ def set_mcp_level(mcp_level)
68
+ self.level = LEVEL_MAP[mcp_level] || Logger::INFO
69
+ end
70
+
71
+ def connect_transport(transport)
72
+ @transport = transport
73
+ flush_queued_messages if @enabled
74
+ end
75
+
76
+ private
77
+
78
+ def send_notification(severity, message, data)
79
+ return unless @enabled
80
+
81
+ notification_params = {
82
+ level: REVERSE_LEVEL_MAP[severity] || "info",
83
+ logger: @logger_name,
84
+ data: format_data(message, data)
85
+ }
86
+
87
+ if @transport
88
+ @transport.send_notification("notifications/message", notification_params)
89
+ else
90
+ @queued_messages << notification_params
91
+ end
92
+ end
93
+
94
+ def format_data(message, additional_data)
95
+ data = {}
96
+ data[:message] = message.to_s if message
97
+ data.merge!(additional_data) unless additional_data.empty?
98
+ data
99
+ end
100
+
101
+ def flush_queued_messages
102
+ return unless @transport && @enabled
103
+ @queued_messages.each do |params|
104
+ @transport.send_notification("notifications/message", params)
105
+ end
106
+ @queued_messages.clear
107
+ end
108
+ end
109
+ end