vector_mcp 0.3.3 → 0.4.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -0
  3. data/README.md +132 -342
  4. data/lib/vector_mcp/handlers/core.rb +82 -27
  5. data/lib/vector_mcp/image_util.rb +53 -5
  6. data/lib/vector_mcp/log_filter.rb +48 -0
  7. data/lib/vector_mcp/middleware/base.rb +1 -5
  8. data/lib/vector_mcp/middleware/context.rb +11 -1
  9. data/lib/vector_mcp/rails/tool.rb +85 -0
  10. data/lib/vector_mcp/request_context.rb +1 -1
  11. data/lib/vector_mcp/security/middleware.rb +2 -2
  12. data/lib/vector_mcp/security/strategies/api_key.rb +27 -4
  13. data/lib/vector_mcp/security/strategies/jwt_token.rb +10 -5
  14. data/lib/vector_mcp/server/capabilities.rb +4 -10
  15. data/lib/vector_mcp/server/message_handling.rb +2 -2
  16. data/lib/vector_mcp/server/registry.rb +36 -4
  17. data/lib/vector_mcp/server.rb +49 -41
  18. data/lib/vector_mcp/session.rb +5 -3
  19. data/lib/vector_mcp/tool.rb +221 -0
  20. data/lib/vector_mcp/transport/base_session_manager.rb +1 -17
  21. data/lib/vector_mcp/transport/http_stream/event_store.rb +33 -13
  22. data/lib/vector_mcp/transport/http_stream/session_manager.rb +39 -14
  23. data/lib/vector_mcp/transport/http_stream/stream_handler.rb +133 -47
  24. data/lib/vector_mcp/transport/http_stream.rb +294 -33
  25. data/lib/vector_mcp/version.rb +1 -1
  26. data/lib/vector_mcp.rb +7 -8
  27. metadata +5 -10
  28. data/lib/vector_mcp/transport/sse/client_connection.rb +0 -113
  29. data/lib/vector_mcp/transport/sse/message_handler.rb +0 -166
  30. data/lib/vector_mcp/transport/sse/puma_config.rb +0 -77
  31. data/lib/vector_mcp/transport/sse/stream_manager.rb +0 -92
  32. data/lib/vector_mcp/transport/sse.rb +0 -377
  33. data/lib/vector_mcp/transport/sse_session_manager.rb +0 -188
  34. data/lib/vector_mcp/transport/stdio.rb +0 -473
  35. data/lib/vector_mcp/transport/stdio_session_manager.rb +0 -181
@@ -1,473 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # lib/vector_mcp/transport/stdio.rb
4
- require "json"
5
- require_relative "../errors"
6
- require_relative "../util"
7
- require_relative "stdio_session_manager"
8
- require "securerandom" # For generating unique request IDs
9
- require "timeout" # For request timeouts
10
-
11
- module VectorMCP
12
- module Transport
13
- # Implements the Model Context Protocol transport over standard input/output (stdio).
14
- # This transport reads JSON-RPC messages line-by-line from `$stdin` and writes
15
- # responses/notifications line-by-line to `$stdout`.
16
- #
17
- # It is suitable for inter-process communication on the same machine where a parent
18
- # process spawns an MCP server and communicates with it via its stdio streams.
19
- class Stdio
20
- # @return [VectorMCP::Server] The server instance this transport is bound to.
21
- attr_reader :server
22
- # @return [Logger] The logger instance, shared with the server.
23
- attr_reader :logger
24
- # @return [StdioSessionManager] The session manager for this transport.
25
- attr_reader :session_manager
26
-
27
- # Timeout for waiting for a response to a server-initiated request (in seconds)
28
- DEFAULT_REQUEST_TIMEOUT = 30 # Configurable if needed
29
-
30
- # Initializes a new Stdio transport.
31
- #
32
- # @param server [VectorMCP::Server] The server instance that will handle messages.
33
- # @param options [Hash] Optional configuration options.
34
- # @option options [Boolean] :enable_session_manager (false) Whether to enable the unified session manager.
35
- def initialize(server, options = {})
36
- @server = server
37
- @logger = server.logger
38
- @session_manager = options[:enable_session_manager] ? StdioSessionManager.new(self) : nil
39
- @input_mutex = Mutex.new
40
- @output_mutex = Mutex.new
41
- @running = false
42
- @input_thread = nil
43
- @shutdown_requested = false
44
- @outgoing_request_responses = {} # To store responses for server-initiated requests
45
- @outgoing_request_conditions = {} # ConditionVariables for server-initiated requests
46
- @mutex = Mutex.new # To synchronize access to shared response data
47
- @request_id_generator = Enumerator.new do |y|
48
- i = 0
49
- loop { y << "vecmcp_stdio_#{i += 1}_#{SecureRandom.hex(4)}" }
50
- end
51
- end
52
-
53
- # Starts the stdio transport, listening for input and processing messages.
54
- # This method will block until the input stream is closed or an interrupt is received.
55
- #
56
- # @return [void]
57
- def run
58
- session = create_session
59
- logger.info("Starting stdio transport")
60
- @running = true
61
-
62
- begin
63
- launch_input_thread(session)
64
- @input_thread.join
65
- rescue Interrupt
66
- logger.info("Interrupted. Shutting down...")
67
- ensure
68
- shutdown_transport
69
- end
70
- end
71
-
72
- # Sends a JSON-RPC response message for a given request ID.
73
- #
74
- # @param id [String, Integer, nil] The ID of the request being responded to.
75
- # @param result [Object] The result data for the successful request.
76
- # @return [void]
77
- def send_response(id, result)
78
- response = {
79
- jsonrpc: "2.0",
80
- id: id,
81
- result: result
82
- }
83
- write_message(response)
84
- end
85
-
86
- # Sends a JSON-RPC error response message.
87
- #
88
- # @param id [String, Integer, nil] The ID of the request that caused the error.
89
- # @param code [Integer] The JSON-RPC error code.
90
- # @param message [String] A short description of the error.
91
- # @param data [Object, nil] Additional error data (optional).
92
- # @return [void]
93
- def send_error(id, code, message, data = nil)
94
- error_obj = { code: code, message: message }
95
- error_obj[:data] = data if data
96
- response = {
97
- jsonrpc: "2.0",
98
- id: id,
99
- error: error_obj
100
- }
101
- write_message(response)
102
- end
103
-
104
- # Sends a JSON-RPC notification message (a request without an ID).
105
- #
106
- # @param method [String] The method name of the notification.
107
- # @param params [Hash, Array, nil] The parameters for the notification (optional).
108
- # @return [void]
109
- def send_notification(method, params = nil)
110
- notification = {
111
- jsonrpc: "2.0",
112
- method: method
113
- }
114
- notification[:params] = params if params
115
- write_message(notification)
116
- end
117
-
118
- # Sends a JSON-RPC notification message to a specific session.
119
- # For stdio transport, this behaves the same as send_notification since there's only one session.
120
- #
121
- # @param _session_id [String] The session ID (ignored for stdio transport).
122
- # @param method [String] The method name of the notification.
123
- # @param params [Hash, Array, nil] The parameters for the notification (optional).
124
- # @return [Boolean] True if the notification was sent successfully.
125
- # rubocop:disable Naming/PredicateMethod
126
- def send_notification_to_session(_session_id, method, params = nil)
127
- send_notification(method, params)
128
- true
129
- end
130
- # rubocop:enable Naming/PredicateMethod
131
-
132
- # Sends a JSON-RPC notification message to a specific session.
133
- # For stdio transport, this behaves the same as send_notification since there's only one session.
134
- #
135
- # @param _session_id [String] The session ID (ignored for stdio transport).
136
- # @param method [String] The method name of the notification.
137
- # @param params [Hash, Array, nil] The parameters for the notification (optional).
138
- # @return [Boolean] True if the notification was sent successfully.
139
- def notification_sent_to_session?(_session_id, method, params = nil)
140
- send_notification(method, params)
141
- true
142
- end
143
-
144
- # Broadcasts a JSON-RPC notification message to all sessions.
145
- # For stdio transport, this behaves the same as send_notification since there's only one session.
146
- #
147
- # @param method [String] The method name of the notification.
148
- # @param params [Hash, Array, nil] The parameters for the notification (optional).
149
- # @return [Integer] Number of sessions the notification was sent to (always 1 for stdio).
150
- def broadcast_notification(method, params = nil)
151
- send_notification(method, params)
152
- 1
153
- end
154
-
155
- # Sends a server-initiated JSON-RPC request to the client and waits for a response.
156
- # This is a blocking call.
157
- #
158
- # @param method [String] The request method name.
159
- # @param params [Hash, Array, nil] The request parameters.
160
- # @param timeout [Numeric] How long to wait for a response, in seconds.
161
- # @return [Object] The result part of the client's response.
162
- # @raise [VectorMCP::SamplingError, VectorMCP::SamplingTimeoutError] if the client returns an error or times out.
163
- # @raise [ArgumentError] if method is blank.
164
- def send_request(method, params = nil, timeout: DEFAULT_REQUEST_TIMEOUT)
165
- raise ArgumentError, "Method cannot be blank" if method.to_s.strip.empty?
166
-
167
- request_id = @request_id_generator.next
168
- request_payload = { jsonrpc: "2.0", id: request_id, method: method }
169
- request_payload[:params] = params if params
170
-
171
- setup_request_tracking(request_id)
172
- # Sending request to client
173
- write_message(request_payload)
174
-
175
- response = wait_for_response(request_id, method, timeout)
176
- process_response(response, request_id, method)
177
- end
178
-
179
- # Initiates an immediate shutdown of the transport.
180
- # Sets the running flag to false and attempts to kill the input reading thread.
181
- #
182
- # @return [void]
183
- def shutdown
184
- logger.info("Shutdown requested for stdio transport.")
185
- @running = false
186
- @input_thread&.kill if @input_thread&.alive?
187
- end
188
-
189
- private
190
-
191
- # The main loop for reading and processing lines from `$stdin`.
192
- # @api private
193
- # @param session [VectorMCP::Session] The session object for this connection.
194
- # @return [void]
195
- # Constant identifier for stdio sessions
196
- def read_input_loop(session)
197
- while @running
198
- line = read_input_line
199
- if line.nil?
200
- logger.info("End of input ($stdin closed). Shutting down stdio transport.")
201
- break
202
- end
203
- next if line.strip.empty?
204
-
205
- handle_input_line(line, session)
206
- end
207
- end
208
-
209
- # Reads a single line from `$stdin` in a thread-safe manner.
210
- # @api private
211
- # @return [String, nil] The line read from stdin, or nil if EOF is reached.
212
- def read_input_line
213
- @input_mutex.synchronize do
214
- $stdin.gets
215
- end
216
- end
217
-
218
- # Parses a line of input as JSON and dispatches it to the server for handling.
219
- # Sends back any response data or errors.
220
- # @api private
221
- # @param line [String] The line of text read from stdin.
222
- # @param session [VectorMCP::Session] The current session.
223
- # @return [void]
224
- def handle_input_line(line, _session)
225
- message = parse_json(line)
226
- return if message.is_a?(Array) && message.empty? # Error handled in parse_json, indicated by empty array
227
-
228
- return handle_outgoing_response(message) if outgoing_response?(message)
229
-
230
- handle_server_message(message)
231
- end
232
-
233
- # Checks if a message is a response to an outgoing request.
234
- # @api private
235
- # @param message [Hash] The parsed message.
236
- # @return [Boolean] True if this is an outgoing response.
237
- def outgoing_response?(message)
238
- message["id"] && !message["method"] && (message.key?("result") || message.key?("error"))
239
- end
240
-
241
- # Gets the global session for this stdio transport.
242
- # @api private
243
- # @return [VectorMCP::Session] The current session.
244
- def session
245
- # Try session manager first, fallback to old method for backward compatibility
246
- if @session_manager
247
- session_wrapper = @session_manager.global_session
248
- return session_wrapper.context if session_wrapper
249
- end
250
-
251
- # Fallback to old session creation for backward compatibility
252
- ensure_session_exists
253
- end
254
-
255
- # Ensures a global session exists for this stdio transport (legacy method).
256
- # @api private
257
- # @return [VectorMCP::Session] The current session.
258
- def ensure_session_exists
259
- @ensure_session_exists ||= VectorMCP::Session.new(@server, self, id: "stdio_global_session")
260
- end
261
-
262
- # Handles a server message with proper error handling.
263
- # @api private
264
- # @param message [Hash] The parsed message.
265
- # @return [void]
266
- def handle_server_message(message)
267
- current_session = session
268
- session_id = current_session.id
269
-
270
- begin
271
- result = @server.handle_message(message, current_session, session_id)
272
- send_response(message["id"], result) if message["id"] && result
273
- rescue VectorMCP::ProtocolError => e
274
- handle_protocol_error(e, message)
275
- rescue StandardError => e
276
- handle_unexpected_error(e, message)
277
- end
278
- end
279
-
280
- # --- Run helpers (private) ---
281
-
282
- # Gets the session for the stdio connection.
283
- # @api private
284
- # @return [VectorMCP::Session] The session.
285
- def create_session
286
- session
287
- end
288
-
289
- # Launches the input reading loop in a new thread.
290
- # Exits the process on fatal errors within this thread.
291
- # @api private
292
- # @param session [VectorMCP::Session] The session to pass to the input loop.
293
- # @return [void]
294
- def launch_input_thread(session)
295
- @input_thread = Thread.new do
296
- read_input_loop(session)
297
- rescue StandardError => e
298
- logger.error("Fatal error in input thread: #{e.message}")
299
- exit(1) # Critical failure, exit the server process
300
- end
301
- end
302
-
303
- # Cleans up transport resources, ensuring the input thread is stopped.
304
- # @api private
305
- # @return [void]
306
- def shutdown_transport
307
- @running = false
308
- @input_thread&.kill if @input_thread&.alive?
309
- @session_manager&.cleanup_all_sessions
310
- logger.info("Stdio transport shut down")
311
- end
312
-
313
- # --- Input helpers (private) ---
314
-
315
- # Handles responses to outgoing requests (like sampling requests).
316
- # @api private
317
- # @param message [Hash] The parsed response message.
318
- # @return [void]
319
- def handle_outgoing_response(message)
320
- request_id = message["id"]
321
- # Received response for outgoing request
322
-
323
- @mutex.synchronize do
324
- # Store the response (convert keys to symbols for consistency)
325
- response_data = deep_transform_keys(message, &:to_sym)
326
- @outgoing_request_responses[request_id] = response_data
327
-
328
- # Signal any thread waiting for this response
329
- condition = @outgoing_request_conditions[request_id]
330
- if condition
331
- condition.signal
332
- # Signaled condition for request
333
- else
334
- logger.warn "[Stdio Transport] Received response for request ID #{request_id} but no thread is waiting"
335
- end
336
- end
337
- end
338
-
339
- # Parses a line of text as JSON.
340
- # If parsing fails, sends a JSON-RPC ParseError and returns an empty array
341
- # to signal that the error has been handled.
342
- # @api private
343
- # @param line [String] The line to parse.
344
- # @return [Hash, Array] The parsed JSON message as a Hash, or an empty Array if a parse error occurred and was handled.
345
- def parse_json(line)
346
- JSON.parse(line.strip)
347
- rescue JSON::ParserError => e
348
- logger.error("Failed to parse message as JSON: #{line.strip.inspect} - #{e.message}")
349
- id = begin
350
- VectorMCP::Util.extract_id_from_invalid_json(line)
351
- rescue StandardError
352
- nil # Best effort, don't let ID extraction fail fatally
353
- end
354
- send_error(id, -32_700, "Parse error")
355
- [] # Signal that error was handled
356
- end
357
-
358
- # Handles known VectorMCP::ProtocolError exceptions during message processing.
359
- # @api private
360
- # @param error [VectorMCP::ProtocolError] The protocol error instance.
361
- # @param message [Hash, nil] The original parsed message, if available.
362
- # @return [void]
363
- def handle_protocol_error(error, message)
364
- logger.error("Protocol error processing message: #{error.message} (code: #{error.code}), Details: #{error.details.inspect}")
365
- request_id = error.request_id || message&.fetch("id", nil)
366
- send_error(request_id, error.code, error.message, error.details)
367
- end
368
-
369
- # Handles unexpected StandardError exceptions during message processing.
370
- # @api private
371
- # @param error [StandardError] The unexpected error instance.
372
- # @param message [Hash, nil] The original parsed message, if available.
373
- # @return [void]
374
- def handle_unexpected_error(error, message)
375
- logger.error("Unexpected error handling message: #{error.message}\n#{error.backtrace.join("\n")}")
376
- request_id = message&.fetch("id", nil)
377
- send_error(request_id, -32_603, "Internal error", { details: error.message })
378
- end
379
-
380
- # Recursively transforms hash keys using the given block.
381
- # @api private
382
- # @param obj [Object] The object to transform (Hash, Array, or other).
383
- # @return [Object] The transformed object.
384
- def deep_transform_keys(obj, &block)
385
- case obj
386
- when Hash
387
- obj.transform_keys(&block).transform_values { |v| deep_transform_keys(v, &block) }
388
- when Array
389
- obj.map { |v| deep_transform_keys(v, &block) }
390
- else
391
- obj
392
- end
393
- end
394
-
395
- # Writes a message hash to `$stdout` as a JSON string, followed by a newline.
396
- # Ensures the output is flushed. Handles EPIPE errors if stdout closes.
397
- # @api private
398
- # @param message [Hash] The message hash to send.
399
- # @return [void]
400
- def write_message(message)
401
- json_msg = message.to_json
402
- # Sending stdio message
403
-
404
- begin
405
- @output_mutex.synchronize do
406
- $stdout.puts(json_msg)
407
- $stdout.flush
408
- end
409
- rescue Errno::EPIPE
410
- logger.error("Output pipe closed. Cannot send message. Shutting down stdio transport.")
411
- shutdown # Initiate shutdown as we can no longer communicate
412
- end
413
- end
414
-
415
- # Sets up tracking for an outgoing request.
416
- # @api private
417
- # @param request_id [String] The request ID to track.
418
- # @return [void]
419
- def setup_request_tracking(request_id)
420
- condition = ConditionVariable.new
421
- @mutex.synchronize do
422
- @outgoing_request_conditions[request_id] = condition
423
- end
424
- end
425
-
426
- # Waits for a response to an outgoing request.
427
- # @api private
428
- # @param request_id [String] The request ID to wait for.
429
- # @param method [String] The request method name.
430
- # @param timeout [Numeric] How long to wait.
431
- # @return [Hash] The response data.
432
- # @raise [VectorMCP::SamplingTimeoutError] if timeout occurs.
433
- def wait_for_response(request_id, method, timeout)
434
- condition = @outgoing_request_conditions[request_id]
435
-
436
- @mutex.synchronize do
437
- Timeout.timeout(timeout) do
438
- condition.wait(@mutex) until @outgoing_request_responses.key?(request_id)
439
- @outgoing_request_responses.delete(request_id)
440
- end
441
- rescue Timeout::Error
442
- logger.warn "[Stdio Transport] Timeout waiting for response to request ID #{request_id} (#{method}) after #{timeout}s"
443
- @outgoing_request_responses.delete(request_id)
444
- @outgoing_request_conditions.delete(request_id)
445
- raise VectorMCP::SamplingTimeoutError, "Timeout waiting for client response to '#{method}' request (ID: #{request_id})"
446
- ensure
447
- @outgoing_request_conditions.delete(request_id)
448
- end
449
- end
450
-
451
- # Processes the response from an outgoing request.
452
- # @api private
453
- # @param response [Hash, nil] The response data.
454
- # @param request_id [String] The request ID.
455
- # @param method [String] The request method name.
456
- # @return [Object] The result data.
457
- # @raise [VectorMCP::SamplingError] if response contains an error or is nil.
458
- def process_response(response, request_id, method)
459
- if response.nil?
460
- raise VectorMCP::SamplingError, "No response received for '#{method}' request (ID: #{request_id}) - this indicates a logic error."
461
- end
462
-
463
- if response.key?(:error)
464
- err = response[:error]
465
- logger.warn "[Stdio Transport] Client returned error for request ID #{request_id} (#{method}): #{err.inspect}"
466
- raise VectorMCP::SamplingError, "Client returned an error for '#{method}' request (ID: #{request_id}): [#{err[:code]}] #{err[:message]}"
467
- end
468
-
469
- response[:result]
470
- end
471
- end
472
- end
473
- end
@@ -1,181 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_session_manager"
4
-
5
- module VectorMCP
6
- module Transport
7
- # Session manager for Stdio transport with single global session.
8
- # Extends BaseSessionManager with stdio-specific functionality.
9
- #
10
- # The Stdio transport uses a single global session for the entire transport lifetime.
11
- class StdioSessionManager < BaseSessionManager
12
- GLOBAL_SESSION_ID = "stdio_global_session"
13
-
14
- # Initializes a new Stdio session manager.
15
- #
16
- # @param transport [Stdio] The parent transport instance
17
- # @param session_timeout [Integer] Session timeout in seconds (ignored for stdio)
18
- def initialize(transport, session_timeout = 300)
19
- super
20
-
21
- # Create the single global session for stdio transport
22
- @global_session = create_global_session
23
- end
24
-
25
- # Gets the global session for stdio transport.
26
- # Stdio uses a single global session for the entire transport lifetime.
27
- #
28
- # @return [Session] The global session
29
- def global_session
30
- @global_session&.touch!
31
- @global_session
32
- end
33
-
34
- # Gets or creates the global session for stdio transport.
35
- # This is an alias for global_session for stdio transport.
36
- #
37
- # @return [Session] The global session
38
- def global_session_or_create
39
- global_session
40
- end
41
-
42
- # Override: Gets session by ID, but always returns the global session for stdio.
43
- #
44
- # @param session_id [String] The session ID (ignored for stdio)
45
- # @return [Session] The global session
46
- def session(_session_id = nil)
47
- global_session
48
- end
49
-
50
- # Override: Always returns the global session for stdio.
51
- #
52
- # @param session_id [String, nil] The session ID (ignored)
53
- # @return [Session] The global session
54
- def session_or_create(_session_id = nil)
55
- global_session
56
- end
57
-
58
- # Override: Cannot create additional sessions in stdio transport.
59
- #
60
- # @param session_id [String, nil] The session ID (ignored)
61
- # @return [Session] The global session
62
- def create_session(_session_id = nil)
63
- # For stdio, always return the existing global session
64
- global_session
65
- end
66
-
67
- # Override: Cannot terminate the global session while transport is running.
68
- #
69
- # @param session_id [String] The session ID (ignored)
70
- # @return [Boolean] Always false (session cannot be terminated individually)
71
- def session_terminated?(_session_id)
72
- # For stdio, the session is only terminated when the transport shuts down
73
- false
74
- end
75
-
76
- # Override: Always returns 1 for the single global session.
77
- #
78
- # @return [Integer] Always 1
79
- def session_count
80
- 1
81
- end
82
-
83
- # Override: Always returns the global session ID.
84
- #
85
- # @return [Array<String>] Array containing the global session ID
86
- def active_session_ids
87
- [GLOBAL_SESSION_ID]
88
- end
89
-
90
- # Override: Always returns true for the single session.
91
- #
92
- # @return [Boolean] Always true
93
- def sessions?
94
- true
95
- end
96
-
97
- # Gets all sessions for stdio transport (just the one global session).
98
- #
99
- # @return [Array<Session>] Array containing the global session
100
- def all_sessions
101
- [@global_session].compact
102
- end
103
-
104
- # Alias for global_session for compatibility with tests.
105
- #
106
- # @return [Session] The global session
107
- def current_global_session
108
- global_session
109
- end
110
-
111
- # Alias for global_session_or_create for compatibility with tests.
112
- #
113
- # @return [Session] The global session
114
- def global_session_or_create_current
115
- global_session_or_create
116
- end
117
-
118
- # Alias for all_sessions for compatibility with tests.
119
- #
120
- # @return [Array<Session>] Array containing the global session
121
- def current_all_sessions
122
- all_sessions
123
- end
124
-
125
- protected
126
-
127
- # Override: Stdio doesn't need automatic cleanup since it has a single persistent session.
128
- def auto_cleanup_enabled?
129
- false
130
- end
131
-
132
- # Override: Returns metadata for stdio sessions.
133
- def create_session_metadata
134
- { session_type: :stdio_global, created_via: :transport_startup }
135
- end
136
-
137
- # Override: Stdio can always send messages (single session assumption).
138
- def can_send_message_to_session?(_session)
139
- true
140
- end
141
-
142
- # Override: Sends messages via the transport's notification mechanism.
143
- def message_sent_to_session?(_session, message)
144
- # For stdio, we send notifications directly via the transport
145
- @transport.send_notification(message["method"], message["params"])
146
- true
147
- end
148
-
149
- # Override: Stdio broadcasts to the single session (same as regular send).
150
- def broadcast_message(message)
151
- message_sent_to_session?(@global_session, message) ? 1 : 0
152
- end
153
-
154
- private
155
-
156
- # Creates the single global session for stdio transport.
157
- #
158
- # @return [BaseSessionManager::Session] The global session
159
- def create_global_session
160
- now = Time.now
161
-
162
- # Create VectorMCP session context with minimal request context
163
- request_context = VectorMCP::RequestContext.minimal("stdio")
164
- session_context = VectorMCP::Session.new(@transport.server, @transport, id: GLOBAL_SESSION_ID, request_context: request_context)
165
-
166
- # Create internal session record using base session manager struct
167
- session = BaseSessionManager::Session.new(
168
- GLOBAL_SESSION_ID,
169
- session_context,
170
- now,
171
- now,
172
- create_session_metadata
173
- )
174
-
175
- @sessions[GLOBAL_SESSION_ID] = session
176
- logger.info { "Global stdio session created: #{GLOBAL_SESSION_ID}" }
177
- session
178
- end
179
- end
180
- end
181
- end