model-context-protocol-rb 0.5.0 → 0.6.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.
@@ -24,8 +24,8 @@ module ModelContextProtocol
24
24
  executing_thread = Concurrent::AtomicReference.new(nil)
25
25
 
26
26
  timer_task = Concurrent::TimerTask.new(execution_interval: interval) do
27
- if context && context[:request_store] && context[:request_id]
28
- if context[:request_store].cancelled?(context[:request_id])
27
+ if context && context[:request_store] && context[:jsonrpc_request_id]
28
+ if context[:request_store].cancelled?(context[:jsonrpc_request_id])
29
29
  thread = executing_thread.get
30
30
  thread&.raise(CancellationError, "Request was cancelled") if thread&.alive?
31
31
  end
@@ -35,9 +35,9 @@ module ModelContextProtocol
35
35
  begin
36
36
  executing_thread.set(Thread.current)
37
37
 
38
- if context && context[:request_store] && context[:request_id]
39
- if context[:request_store].cancelled?(context[:request_id])
40
- raise CancellationError, "Request #{context[:request_id]} was cancelled"
38
+ if context && context[:request_store] && context[:jsonrpc_request_id]
39
+ if context[:request_store].cancelled?(context[:jsonrpc_request_id])
40
+ raise CancellationError, "Request #{context[:jsonrpc_request_id]} was cancelled"
41
41
  end
42
42
  end
43
43
 
@@ -3,11 +3,13 @@ require "forwardable"
3
3
  require "json"
4
4
 
5
5
  module ModelContextProtocol
6
- class Server::MCPLogger
6
+ class Server::ClientLogger
7
7
  extend Forwardable
8
8
 
9
9
  def_delegators :@internal_logger, :datetime_format=, :formatter=, :progname, :progname=
10
10
 
11
+ VALID_LOG_LEVELS = %w[debug info notice warning error critical alert emergency].freeze
12
+
11
13
  LEVEL_MAP = {
12
14
  "debug" => Logger::DEBUG,
13
15
  "info" => Logger::INFO,
@@ -29,11 +31,10 @@ module ModelContextProtocol
29
31
  }.freeze
30
32
 
31
33
  attr_accessor :transport
32
- attr_reader :logger_name, :enabled
34
+ attr_reader :logger_name
33
35
 
34
- def initialize(logger_name: "server", level: "info", enabled: true)
36
+ def initialize(logger_name: "server", level: "info")
35
37
  @logger_name = logger_name
36
- @enabled = enabled
37
38
  @internal_logger = Logger.new(nil)
38
39
  @internal_logger.level = LEVEL_MAP[level] || Logger::INFO
39
40
  @transport = nil
@@ -42,13 +43,11 @@ module ModelContextProtocol
42
43
 
43
44
  %i[debug info warn error fatal unknown].each do |severity|
44
45
  define_method(severity) do |message = nil, **data, &block|
45
- return true unless @enabled
46
46
  add(Logger.const_get(severity.to_s.upcase), message, data, &block)
47
47
  end
48
48
  end
49
49
 
50
50
  def add(severity, message = nil, data = {}, &block)
51
- return true unless @enabled
52
51
  return true if severity < @internal_logger.level
53
52
 
54
53
  message = block.call if message.nil? && block_given?
@@ -70,14 +69,12 @@ module ModelContextProtocol
70
69
 
71
70
  def connect_transport(transport)
72
71
  @transport = transport
73
- flush_queued_messages if @enabled
72
+ flush_queued_messages
74
73
  end
75
74
 
76
75
  private
77
76
 
78
77
  def send_notification(severity, message, data)
79
- return unless @enabled
80
-
81
78
  notification_params = {
82
79
  level: REVERSE_LEVEL_MAP[severity] || "info",
83
80
  logger: @logger_name,
@@ -99,7 +96,7 @@ module ModelContextProtocol
99
96
  end
100
97
 
101
98
  def flush_queued_messages
102
- return unless @transport && @enabled
99
+ return unless @transport
103
100
  @queued_messages.each do |params|
104
101
  @transport.send_notification("notifications/message", params)
105
102
  end
@@ -1,5 +1,3 @@
1
- require_relative "mcp_logger"
2
-
3
1
  module ModelContextProtocol
4
2
  class Server::Configuration
5
3
  # Raised when configured with invalid name.
@@ -23,48 +21,25 @@ module ModelContextProtocol
23
21
  # Raised when transport configuration is invalid
24
22
  class InvalidTransportError < StandardError; end
25
23
 
26
- # Raised when an invalid log level is provided
27
- class InvalidLogLevelError < StandardError; end
28
-
29
24
  # Raised when pagination configuration is invalid
30
25
  class InvalidPaginationError < StandardError; end
31
26
 
32
- # Valid MCP log levels per the specification
33
- VALID_LOG_LEVELS = %w[debug info notice warning error critical alert emergency].freeze
34
-
35
27
  attr_accessor :name, :registry, :version, :transport, :pagination, :title, :instructions
36
- attr_reader :logger
28
+ attr_reader :client_logger, :server_logger
37
29
 
38
30
  def initialize
39
- @logging_enabled = true
40
- @default_log_level = "info"
41
- @logger = ModelContextProtocol::Server::MCPLogger.new(
31
+ @client_logger = ModelContextProtocol::Server::ClientLogger.new(
42
32
  logger_name: "server",
43
- level: @default_log_level,
44
- enabled: @logging_enabled
33
+ level: "info"
45
34
  )
46
- end
47
35
 
48
- def logging_enabled?
49
- @logging_enabled
50
- end
51
-
52
- def logging_enabled=(value)
53
- @logging_enabled = value
54
- @logger = ModelContextProtocol::Server::MCPLogger.new(
55
- logger_name: "server",
56
- level: @default_log_level,
57
- enabled: value
58
- )
59
- end
60
-
61
- def default_log_level=(level)
62
- unless VALID_LOG_LEVELS.include?(level.to_s)
63
- raise InvalidLogLevelError, "Invalid log level: #{level}. Valid levels are: #{VALID_LOG_LEVELS.join(", ")}"
36
+ server_logger_params = if ModelContextProtocol::Server::GlobalConfig::ServerLogging.configured?
37
+ ModelContextProtocol::Server::GlobalConfig::ServerLogging.logger_params
38
+ else
39
+ {}
64
40
  end
65
41
 
66
- @default_log_level = level.to_s
67
- @logger.set_mcp_level(@default_log_level)
42
+ @server_logger = ModelContextProtocol::Server::ServerLogger.new(**server_logger_params)
68
43
  end
69
44
 
70
45
  def transport_type
@@ -123,12 +98,13 @@ module ModelContextProtocol
123
98
  raise InvalidServerNameError unless valid_name?
124
99
  raise InvalidRegistryError unless valid_registry?
125
100
  raise InvalidServerVersionError unless valid_version?
101
+
126
102
  validate_transport!
127
103
  validate_pagination!
128
104
  validate_title!
129
105
  validate_instructions!
130
-
131
106
  validate_environment_variables!
107
+ validate_server_logging_transport_constraints!
132
108
  end
133
109
 
134
110
  def environment_variables
@@ -176,6 +152,13 @@ module ModelContextProtocol
176
152
  end
177
153
  end
178
154
 
155
+ def validate_server_logging_transport_constraints!
156
+ return unless transport_type == :stdio && server_logger.logdev == $stdout
157
+
158
+ raise ModelContextProtocol::Server::ServerLogger::StdoutNotAllowedError,
159
+ "StdioTransport cannot log to stdout. Use stderr or a file instead."
160
+ end
161
+
179
162
  def valid_name?
180
163
  name&.is_a?(String)
181
164
  end
@@ -0,0 +1,78 @@
1
+ require "singleton"
2
+
3
+ module ModelContextProtocol
4
+ module Server::GlobalConfig
5
+ class ServerLogging
6
+ include Singleton
7
+
8
+ class NotConfiguredError < StandardError
9
+ def initialize
10
+ super("Server logging not configured. Call ModelContextProtocol::Server.configure_server_logging first")
11
+ end
12
+ end
13
+
14
+ class LoggerConfig
15
+ attr_accessor :logdev, :level, :formatter, :progname
16
+
17
+ def initialize
18
+ @level = Logger::INFO
19
+ @progname = "MCP-Server"
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ logdev: @logdev,
25
+ level: @level,
26
+ formatter: @formatter,
27
+ progname: @progname
28
+ }.compact
29
+ end
30
+ end
31
+
32
+ def self.configure(&block)
33
+ instance.configure(&block)
34
+ end
35
+
36
+ def self.configured?
37
+ instance.configured?
38
+ end
39
+
40
+ def self.logger_params
41
+ instance.logger_params
42
+ end
43
+
44
+ def self.reset!
45
+ instance.reset!
46
+ end
47
+
48
+ def configure(&block)
49
+ raise ArgumentError, "Configuration block required" unless block_given?
50
+
51
+ @config = LoggerConfig.new
52
+ yield(@config)
53
+ @configured = true
54
+ end
55
+
56
+ def configured?
57
+ @configured == true
58
+ end
59
+
60
+ def logger_params
61
+ raise NotConfiguredError unless configured?
62
+
63
+ @config.to_h
64
+ end
65
+
66
+ def reset!
67
+ @configured = false
68
+ @config = nil
69
+ end
70
+
71
+ private
72
+
73
+ def initialize
74
+ reset!
75
+ end
76
+ end
77
+ end
78
+ end
@@ -16,56 +16,78 @@ module ModelContextProtocol
16
16
  # end
17
17
  def progressable(max_duration:, message: nil, &block)
18
18
  context = Thread.current[:mcp_context]
19
-
20
19
  return yield unless context && context[:progress_token] && context[:transport]
21
20
 
22
21
  progress_token = context[:progress_token]
23
22
  transport = context[:transport]
23
+ jsonrpc_request_id = context[:jsonrpc_request_id]
24
+ request_store = context[:request_store]
25
+ stream_id = context[:stream_id]
26
+
24
27
  start_time = Time.now
25
28
  update_interval = [1.0, max_duration * 0.05].max
26
29
 
27
30
  timer_task = Concurrent::TimerTask.new(execution_interval: update_interval) do
28
- elapsed_seconds = Time.now - start_time
29
- progress_pct = [(elapsed_seconds / max_duration) * 100, 99].min
30
-
31
- progress_message = if message
32
- "#{message} (#{elapsed_seconds.round(1)}s / ~#{max_duration}s)"
33
- else
34
- "Processing... (#{elapsed_seconds.round(1)}s / ~#{max_duration}s)"
35
- end
31
+ Thread.current[:mcp_context] = {jsonrpc_request_id:}
36
32
 
37
33
  begin
38
- transport.send_notification("notifications/progress", {
39
- progressToken: progress_token,
40
- progress: progress_pct.round(1),
41
- total: 100,
42
- message: progress_message
43
- })
44
- rescue
45
- nil
46
- end
34
+ if request_store && jsonrpc_request_id
35
+ break if request_store.cancelled?(jsonrpc_request_id)
36
+ end
37
+
38
+ elapsed_seconds = Time.now - start_time
39
+ progress_pct = [(elapsed_seconds / max_duration) * 100, 99].min
40
+
41
+ progress_message = if message
42
+ "#{message} (#{elapsed_seconds.round(1)}s / ~#{max_duration}s)"
43
+ else
44
+ "Processing... (#{elapsed_seconds.round(1)}s / ~#{max_duration}s)"
45
+ end
46
+
47
+ begin
48
+ transport.send_notification("notifications/progress", {
49
+ progressToken: progress_token,
50
+ progress: progress_pct.round(1),
51
+ total: 100,
52
+ message: progress_message
53
+ }, session_id: stream_id)
54
+ rescue
55
+ break
56
+ end
47
57
 
48
- timer_task.shutdown if elapsed_seconds >= max_duration
58
+ timer_task.shutdown if elapsed_seconds >= max_duration
59
+ ensure
60
+ Thread.current[:mcp_context] = nil
61
+ end
49
62
  end
50
63
 
51
64
  begin
52
65
  timer_task.execute
66
+
53
67
  result = yield
54
68
 
69
+ original_context = Thread.current[:mcp_context]
70
+ Thread.current[:mcp_context] = {jsonrpc_request_id:}
71
+
55
72
  begin
56
73
  transport.send_notification("notifications/progress", {
57
74
  progressToken: progress_token,
58
75
  progress: 100,
59
76
  total: 100,
60
77
  message: "Completed"
61
- })
78
+ }, session_id: stream_id)
62
79
  rescue
63
80
  nil
81
+ ensure
82
+ Thread.current[:mcp_context] = original_context
64
83
  end
65
84
 
66
85
  result
67
86
  ensure
68
- timer_task&.shutdown if timer_task&.running?
87
+ if timer_task&.running?
88
+ timer_task.shutdown
89
+ sleep(0.1) if timer_task.running?
90
+ end
69
91
  end
70
92
  end
71
93
  end
@@ -4,13 +4,14 @@ module ModelContextProtocol
4
4
  include ModelContextProtocol::Server::ContentHelpers
5
5
  include ModelContextProtocol::Server::Progressable
6
6
 
7
- attr_reader :arguments, :context, :logger
7
+ attr_reader :arguments, :context, :client_logger, :server_logger
8
8
 
9
- def initialize(arguments, logger, context = {})
9
+ def initialize(arguments, client_logger, server_logger, context = {})
10
10
  validate!(arguments)
11
11
  @arguments = arguments
12
12
  @context = context
13
- @logger = logger
13
+ @client_logger = client_logger
14
+ @server_logger = server_logger
14
15
  end
15
16
 
16
17
  def call
@@ -90,8 +91,8 @@ module ModelContextProtocol
90
91
  subclass.instance_variable_set(:@defined_arguments, @defined_arguments&.dup)
91
92
  end
92
93
 
93
- def call(arguments, logger, context = {})
94
- new(arguments, logger, context).call
94
+ def call(arguments, client_logger, server_logger, context = {})
95
+ new(arguments, client_logger, server_logger, context).call
95
96
  rescue ArgumentError => error
96
97
  raise ModelContextProtocol::Server::ParameterValidationError, error.message
97
98
  end
@@ -127,8 +128,12 @@ module ModelContextProtocol
127
128
  @prompt_instance.context
128
129
  end
129
130
 
130
- def logger
131
- @prompt_instance.logger
131
+ def client_logger
132
+ @prompt_instance.client_logger
133
+ end
134
+
135
+ def server_logger
136
+ @prompt_instance.server_logger
132
137
  end
133
138
 
134
139
  def user_message(&block)
@@ -48,7 +48,7 @@ module ModelContextProtocol
48
48
  def reap_now
49
49
  return unless @pool
50
50
 
51
- @pool.reap(@reaper_config[:idle_timeout]) do |conn|
51
+ @pool.reap(idle_seconds: @reaper_config[:idle_timeout]) do |conn|
52
52
  conn.close
53
53
  end
54
54
  end
@@ -3,11 +3,14 @@ module ModelContextProtocol
3
3
  include ModelContextProtocol::Server::Cancellable
4
4
  include ModelContextProtocol::Server::Progressable
5
5
 
6
- attr_reader :mime_type, :uri
6
+ attr_reader :mime_type, :uri, :client_logger, :server_logger, :context
7
7
 
8
- def initialize
8
+ def initialize(client_logger, server_logger, context = {})
9
9
  @mime_type = self.class.mime_type
10
10
  @uri = self.class.uri
11
+ @client_logger = client_logger
12
+ @server_logger = server_logger
13
+ @context = context
11
14
  end
12
15
 
13
16
  def call
@@ -71,8 +74,8 @@ module ModelContextProtocol
71
74
  subclass.instance_variable_set(:@annotations, @annotations&.dup)
72
75
  end
73
76
 
74
- def call
75
- new.call
77
+ def call(client_logger, server_logger, context = {})
78
+ new(client_logger, server_logger, context).call
76
79
  end
77
80
 
78
81
  def definition
@@ -20,23 +20,24 @@ module ModelContextProtocol
20
20
  # @param request_store [Object] the request store for tracking cancellation
21
21
  # @param session_id [String, nil] the session ID for HTTP transport
22
22
  # @param transport [Object, nil] the transport for sending notifications
23
+ # @param stream_id [String, nil] the specific stream ID for targeted notifications
23
24
  # @return [Object] the handler result, or nil if cancelled
24
- def route(message, request_store: nil, session_id: nil, transport: nil)
25
+ def route(message, request_store: nil, session_id: nil, transport: nil, stream_id: nil)
25
26
  method = message["method"]
26
27
  handler = @handlers[method]
27
28
  raise MethodNotFoundError, "Method not found: #{method}" unless handler
28
29
 
29
- request_id = message["id"]
30
+ jsonrpc_request_id = message["id"]
30
31
  progress_token = message.dig("params", "_meta", "progressToken")
31
32
 
32
- if request_id && request_store
33
- request_store.register_request(request_id, session_id)
33
+ if jsonrpc_request_id && request_store
34
+ request_store.register_request(jsonrpc_request_id, session_id)
34
35
  end
35
36
 
36
37
  result = nil
37
38
  begin
38
39
  with_environment(@configuration&.environment_variables) do
39
- context = {request_id:, request_store:, session_id:, progress_token:, transport:}
40
+ context = {jsonrpc_request_id:, request_store:, session_id:, progress_token:, transport:, stream_id:}
40
41
 
41
42
  Thread.current[:mcp_context] = context
42
43
 
@@ -45,8 +46,8 @@ module ModelContextProtocol
45
46
  rescue Server::Cancellable::CancellationError
46
47
  return nil
47
48
  ensure
48
- if request_id && request_store
49
- request_store.unregister_request(request_id)
49
+ if jsonrpc_request_id && request_store
50
+ request_store.unregister_request(jsonrpc_request_id)
50
51
  end
51
52
 
52
53
  Thread.current[:mcp_context] = nil
@@ -0,0 +1,28 @@
1
+ require "logger"
2
+ require "json"
3
+
4
+ module ModelContextProtocol
5
+ class Server::ServerLogger < Logger
6
+ class StdoutNotAllowedError < StandardError; end
7
+
8
+ attr_reader :logdev
9
+
10
+ def initialize(logdev: $stderr, level: Logger::INFO, formatter: nil, progname: "MCP-Server")
11
+ super(logdev)
12
+ @logdev = logdev
13
+
14
+ self.level = level
15
+ self.progname = progname
16
+
17
+ self.formatter = formatter || proc do |severity, datetime, progname, msg|
18
+ timestamp = datetime.strftime("%Y-%m-%d %H:%M:%S.%3N")
19
+ prog_name = progname ? "[#{progname}]" : ""
20
+ mcp_context = Thread.current[:mcp_context]
21
+ request_id = mcp_context&.dig(:jsonrpc_request_id)
22
+ request_id_str = request_id ? " [#{request_id}]" : ""
23
+
24
+ "[#{timestamp}] #{prog_name}#{request_id_str} #{severity}: #{msg}\n"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -10,12 +10,12 @@ module ModelContextProtocol
10
10
 
11
11
  # Register a new request with its associated thread
12
12
  #
13
- # @param request_id [String] the unique request identifier
13
+ # @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
14
14
  # @param thread [Thread] the thread processing this request (defaults to current thread)
15
15
  # @return [void]
16
- def register_request(request_id, thread = Thread.current)
16
+ def register_request(jsonrpc_request_id, thread = Thread.current)
17
17
  @mutex.synchronize do
18
- @requests[request_id] = {
18
+ @requests[jsonrpc_request_id] = {
19
19
  thread:,
20
20
  cancelled: false,
21
21
  started_at: Time.now
@@ -25,11 +25,11 @@ module ModelContextProtocol
25
25
 
26
26
  # Mark a request as cancelled
27
27
  #
28
- # @param request_id [String] the unique request identifier
28
+ # @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
29
29
  # @return [Boolean] true if request was found and marked cancelled, false otherwise
30
- def mark_cancelled(request_id)
30
+ def mark_cancelled(jsonrpc_request_id)
31
31
  @mutex.synchronize do
32
- if (request = @requests[request_id])
32
+ if (request = @requests[jsonrpc_request_id])
33
33
  request[:cancelled] = true
34
34
  return true
35
35
  end
@@ -39,31 +39,31 @@ module ModelContextProtocol
39
39
 
40
40
  # Check if a request has been cancelled
41
41
  #
42
- # @param request_id [String] the unique request identifier
42
+ # @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
43
43
  # @return [Boolean] true if the request is cancelled, false otherwise
44
- def cancelled?(request_id)
44
+ def cancelled?(jsonrpc_request_id)
45
45
  @mutex.synchronize do
46
- @requests[request_id]&.fetch(:cancelled, false) || false
46
+ @requests[jsonrpc_request_id]&.fetch(:cancelled, false) || false
47
47
  end
48
48
  end
49
49
 
50
50
  # Unregister a request (typically called when request completes)
51
51
  #
52
- # @param request_id [String] the unique request identifier
52
+ # @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
53
53
  # @return [Hash, nil] the removed request data, or nil if not found
54
- def unregister_request(request_id)
54
+ def unregister_request(jsonrpc_request_id)
55
55
  @mutex.synchronize do
56
- @requests.delete(request_id)
56
+ @requests.delete(jsonrpc_request_id)
57
57
  end
58
58
  end
59
59
 
60
60
  # Get information about a specific request
61
61
  #
62
- # @param request_id [String] the unique request identifier
62
+ # @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
63
63
  # @return [Hash, nil] request information or nil if not found
64
- def get_request(request_id)
64
+ def get_request(jsonrpc_request_id)
65
65
  @mutex.synchronize do
66
- @requests[request_id]&.dup
66
+ @requests[jsonrpc_request_id]&.dup
67
67
  end
68
68
  end
69
69
 
@@ -85,9 +85,9 @@ module ModelContextProtocol
85
85
  removed_ids = []
86
86
 
87
87
  @mutex.synchronize do
88
- @requests.delete_if do |request_id, data|
88
+ @requests.delete_if do |jsonrpc_request_id, data|
89
89
  if data[:started_at] < cutoff_time
90
- removed_ids << request_id
90
+ removed_ids << jsonrpc_request_id
91
91
  true
92
92
  else
93
93
  false
@@ -1,5 +1,3 @@
1
- require_relative "stdio_transport/request_store"
2
-
3
1
  module ModelContextProtocol
4
2
  class Server::StdioTransport
5
3
  Response = Data.define(:id, :result) do
@@ -14,16 +12,19 @@ module ModelContextProtocol
14
12
  end
15
13
  end
16
14
 
17
- attr_reader :router, :configuration, :request_store
15
+ attr_reader :router, :client_logger, :server_logger, :configuration, :request_store
18
16
 
19
17
  def initialize(router:, configuration:)
20
18
  @router = router
21
19
  @configuration = configuration
22
- @request_store = RequestStore.new
20
+ @client_logger = configuration.client_logger
21
+ @server_logger = configuration.server_logger
22
+ @request_store = ModelContextProtocol::Server::StdioTransport::RequestStore.new
23
23
  end
24
24
 
25
25
  def handle
26
- @configuration.logger.connect_transport(self)
26
+ client_logger.connect_transport(self)
27
+ server_logger.info("Starting stdio transport handler")
27
28
 
28
29
  loop do
29
30
  line = receive_message
@@ -45,17 +46,21 @@ module ModelContextProtocol
45
46
  send_message(Response[id: message["id"], result: result.serialized])
46
47
  end
47
48
  rescue ModelContextProtocol::Server::ParameterValidationError => validation_error
48
- @configuration.logger.error("Validation error", error: validation_error.message)
49
+ client_logger.error("Validation error", error: validation_error.message)
50
+ server_logger.error("Parameter validation failed in stdio transport: #{validation_error.message}")
49
51
  send_message(
50
52
  ErrorResponse[id: message["id"], error: {code: -32602, message: validation_error.message}]
51
53
  )
52
54
  rescue JSON::ParserError => parser_error
53
- @configuration.logger.error("Parser error", error: parser_error.message)
55
+ client_logger.error("Parser error", error: parser_error.message)
56
+ server_logger.error("JSON parsing failed in stdio transport: #{parser_error.message}")
54
57
  send_message(
55
58
  ErrorResponse[id: "", error: {code: -32700, message: parser_error.message}]
56
59
  )
57
60
  rescue => error
58
- @configuration.logger.error("Internal error", error: error.message, backtrace: error.backtrace.first(5))
61
+ client_logger.error("Internal error", error: error.message, backtrace: error.backtrace.first(5))
62
+ server_logger.error("Internal error in stdio transport: #{error.message}")
63
+ server_logger.debug("Backtrace: #{error.backtrace.join("\n")}")
59
64
  send_message(
60
65
  ErrorResponse[id: message["id"], error: {code: -32603, message: error.message}]
61
66
  )
@@ -72,7 +77,8 @@ module ModelContextProtocol
72
77
  $stdout.puts(JSON.generate(notification))
73
78
  $stdout.flush
74
79
  rescue IOError => e
75
- @configuration.logger.debug("Failed to send notification", error: e.message) if @configuration.logging_enabled?
80
+ @configuration.client_logger.debug("Failed to send notification", error: e.message)
81
+ @configuration.server_logger.debug("Failed to send notification via stdio: #{e.message}")
76
82
  end
77
83
 
78
84
  private
@@ -84,10 +90,10 @@ module ModelContextProtocol
84
90
  params = message["params"]
85
91
  return unless params
86
92
 
87
- request_id = params["requestId"]
88
- return unless request_id
93
+ jsonrpc_request_id = params["requestId"]
94
+ return unless jsonrpc_request_id
89
95
 
90
- @request_store.mark_cancelled(request_id)
96
+ @request_store.mark_cancelled(jsonrpc_request_id)
91
97
  rescue
92
98
  nil
93
99
  end