Class: MCPClient::ServerSSE

Inherits:
ServerBase show all
Includes:
JsonRpcTransport, ReconnectMonitor, SseParser
Defined in:
lib/mcp_client/server_sse.rb,
lib/mcp_client/server_sse/sse_parser.rb,
lib/mcp_client/server_sse/reconnect_monitor.rb,
lib/mcp_client/server_sse/json_rpc_transport.rb

Overview

Note:

Elicitation Support (MCP 2025-06-18) This transport FULLY supports server-initiated elicitation requests via bidirectional JSON-RPC. The server sends elicitation/create requests via the SSE stream, and the client responds via HTTP POST to the RPC endpoint. This provides full elicitation capability for remote servers.

Implementation of MCP server that communicates via Server-Sent Events (SSE) Useful for communicating with remote MCP servers over HTTP

Defined Under Namespace

Modules: JsonRpcTransport, ReconnectMonitor, SseParser

Constant Summary collapse

CLOSE_AFTER_PING_RATIO =

Ratio of close_after timeout to ping interval

2.5
DEFAULT_MAX_PING_FAILURES =

Default values for connection monitoring

3
DEFAULT_MAX_RECONNECT_ATTEMPTS =
5
BASE_RECONNECT_DELAY =

Reconnection backoff constants

0.5
MAX_RECONNECT_DELAY =
30
JITTER_FACTOR =
0.25

Instance Attribute Summary collapse

Attributes inherited from ServerBase

#name

Instance Method Summary collapse

Methods included from ReconnectMonitor

#activity_monitor_loop, #attempt_ping, #attempt_reconnection, #connection_active?, #handle_ping_failure, #handle_sse_auth_error, #record_activity, #reset_connection_state, #setup_sse_connection, #start_activity_monitor, #wait_for_connection

Methods included from JsonRpcTransport

#rpc_notify, #rpc_request

Methods included from JsonRpcCommon

#build_jsonrpc_notification, #build_jsonrpc_request, #initialization_params, #ping, #process_jsonrpc_response, #with_retry

Methods included from SseParser

#handle_endpoint_event, #handle_message_event, #parse_and_handle_sse_event, #parse_sse_event, #process_error_in_message, #process_notification?, #process_response?, #process_server_request?

Methods inherited from ServerBase

#on_notification, #ping, #rpc_notify, #rpc_request

Constructor Details

#initialize(base_url:, headers: {}, read_timeout: 30, ping: 10, retries: 0, retry_backoff: 1, name: nil, logger: nil) ⇒ ServerSSE

Returns a new instance of ServerSSE.

Parameters:

  • base_url (String)

    The base URL of the MCP server

  • headers (Hash) (defaults to: {})

    Additional headers to include in requests

  • read_timeout (Integer) (defaults to: 30)

    Read timeout in seconds (default: 30)

  • ping (Integer) (defaults to: 10)

    Time in seconds after which to send ping if no activity (default: 10)

  • retries (Integer) (defaults to: 0)

    number of retry attempts on transient errors

  • retry_backoff (Numeric) (defaults to: 1)

    base delay in seconds for exponential backoff

  • name (String, nil) (defaults to: nil)

    optional name for this server

  • logger (Logger, nil) (defaults to: nil)

    optional logger



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/mcp_client/server_sse.rb', line 69

def initialize(base_url:, headers: {}, read_timeout: 30, ping: 10,
               retries: 0, retry_backoff: 1, name: nil, logger: nil)
  super(name: name)
  initialize_logger(logger)
  @max_retries = retries
  @retry_backoff = retry_backoff
  # Normalize base_url: preserve trailing slash if explicitly provided for SSE endpoints
  @base_url = base_url
  @headers = headers.merge({
                             'Accept' => 'text/event-stream',
                             'Cache-Control' => 'no-cache',
                             'Connection' => 'keep-alive'
                           })
  # HTTP client is managed via Faraday
  @tools = nil
  @read_timeout = read_timeout
  @ping_interval = ping
  # Set close_after to a multiple of the ping interval
  @close_after = (ping * CLOSE_AFTER_PING_RATIO).to_i

  # SSE-provided JSON-RPC endpoint path for POST requests
  @rpc_endpoint = nil
  @tools_data = nil
  @request_id = 0
  @sse_results = {}
  @mutex = Monitor.new
  @buffer = ''
  @sse_connected = false
  @connection_established = false
  @connection_cv = @mutex.new_cond
  @initialized = false
  @auth_error = nil
  # Whether to use SSE transport; may disable if handshake fails
  @use_sse = true

  # Time of last activity
  @last_activity_time = Time.now
  @activity_timer_thread = nil
  @elicitation_request_callback = nil # MCP 2025-06-18
  @roots_list_request_callback = nil # MCP 2025-06-18
  @sampling_request_callback = nil # MCP 2025-06-18
end

Instance Attribute Details

#base_urlString (readonly)

Returns The base URL of the MCP server.

Returns:

  • (String)

    The base URL of the MCP server



51
52
53
# File 'lib/mcp_client/server_sse.rb', line 51

def base_url
  @base_url
end

#capabilitiesHash? (readonly)

Server capabilities from initialize response

Returns:

  • (Hash, nil)

    Server capabilities



59
60
61
# File 'lib/mcp_client/server_sse.rb', line 59

def capabilities
  @capabilities
end

#promptsArray<MCPClient::Prompt>? (readonly)

Returns List of available prompts (nil if not fetched yet).

Returns:

  • (Array<MCPClient::Prompt>, nil)

    List of available prompts (nil if not fetched yet)



51
# File 'lib/mcp_client/server_sse.rb', line 51

attr_reader :base_url, :tools, :prompts, :resources

#resourcesObject (readonly)

Returns the value of attribute resources.



51
# File 'lib/mcp_client/server_sse.rb', line 51

attr_reader :base_url, :tools, :prompts, :resources

#server_infoHash? (readonly)

Server information from initialize response

Returns:

  • (Hash, nil)

    Server information



55
56
57
# File 'lib/mcp_client/server_sse.rb', line 55

def server_info
  @server_info
end

#toolsArray<MCPClient::Tool>? (readonly)

Returns List of available tools (nil if not fetched yet).

Returns:

  • (Array<MCPClient::Tool>, nil)

    List of available tools (nil if not fetched yet)



51
# File 'lib/mcp_client/server_sse.rb', line 51

attr_reader :base_url, :tools, :prompts, :resources

Instance Method Details

#call_tool(tool_name, parameters) ⇒ Object

Call a tool with the given parameters

Parameters:

  • tool_name (String)

    the name of the tool to call

  • parameters (Hash)

    the parameters to pass to the tool

Returns:

  • (Object)

    the result of the tool invocation (with string keys for backward compatibility)

Raises:



317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/mcp_client/server_sse.rb', line 317

def call_tool(tool_name, parameters)
  rpc_request('tools/call', {
                name: tool_name,
                arguments: parameters
              })
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
  # Re-raise connection/transport errors directly to match test expectations
  raise
rescue StandardError => e
  # For all other errors, wrap in ToolCallError
  raise MCPClient::Errors::ToolCallError, "Error calling tool '#{tool_name}': #{e.message}"
end

#call_tool_streaming(tool_name, parameters) ⇒ Enumerator

Stream tool call fallback for SSE transport (yields single result)

Parameters:

  • tool_name (String)
  • parameters (Hash)

Returns:

  • (Enumerator)


116
117
118
119
120
# File 'lib/mcp_client/server_sse.rb', line 116

def call_tool_streaming(tool_name, parameters)
  Enumerator.new do |yielder|
    yielder << call_tool(tool_name, parameters)
  end
end

#cleanupObject

Note:

This method preserves ping failure and reconnection metrics between reconnection attempts, allowing the client to track failures across multiple connection attempts. This is essential for proper reconnection logic and exponential backoff.

Clean up the server connection Properly closes HTTP connections and clears cached state



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/mcp_client/server_sse.rb', line 397

def cleanup
  @mutex.synchronize do
    # Set flags first before killing threads to prevent race conditions
    # where threads might check flags after they're set but before they're killed
    @connection_established = false
    @sse_connected = false
    @initialized = false # Reset initialization state for reconnection

    # Log cleanup for debugging
    @logger.debug('Cleaning up SSE connection')

    # Store threads locally to avoid race conditions
    sse_thread = @sse_thread
    activity_thread = @activity_timer_thread

    # Clear thread references first
    @sse_thread = nil
    @activity_timer_thread = nil

    # Kill threads outside the critical section
    begin
      sse_thread&.kill
    rescue StandardError => e
      @logger.debug("Error killing SSE thread: #{e.message}")
    end

    begin
      activity_thread&.kill
    rescue StandardError => e
      @logger.debug("Error killing activity thread: #{e.message}")
    end

    if @http_client
      @http_client.finish if @http_client.started?
      @http_client = nil
    end

    # Close Faraday connections if they exist
    @rpc_conn = nil
    @sse_conn = nil

    @tools = nil
    # Don't clear auth error as we need it for reporting the correct error
    # Don't reset @consecutive_ping_failures or @reconnect_attempts as they're tracked across reconnections
  end
end

#complete(ref:, argument:) ⇒ Hash

Request completion suggestions from the server (MCP 2025-06-18)

Parameters:

  • ref (Hash)

    reference object (e.g., { ‘type’ => ‘ref/prompt’, ‘name’ => ‘prompt_name’ })

  • argument (Hash)

    the argument being completed (e.g., { ‘name’ => ‘arg_name’, ‘value’ => ‘partial’ })

Returns:

  • (Hash)

    completion result with ‘values’, optional ‘total’, and ‘hasMore’ fields

Raises:



335
336
337
338
339
340
341
342
# File 'lib/mcp_client/server_sse.rb', line 335

def complete(ref:, argument:)
  result = rpc_request('completion/complete', { ref: ref, argument: argument })
  result['completion'] || { 'values' => [] }
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
  raise
rescue StandardError => e
  raise MCPClient::Errors::ServerError, "Error requesting completion: #{e.message}"
end

#connectBoolean

Connect to the MCP server over HTTP/HTTPS with SSE

Returns:

  • (Boolean)

    true if connection was successful

Raises:



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/mcp_client/server_sse.rb', line 360

def connect
  return true if @mutex.synchronize { @connection_established }

  # Check for pre-existing auth error (needed for tests)
  pre_existing_auth_error = @mutex.synchronize { @auth_error }

  begin
    # Don't reset auth error if it's pre-existing
    @mutex.synchronize { @auth_error = nil } unless pre_existing_auth_error

    start_sse_thread
    effective_timeout = [@read_timeout || 30, 30].min
    wait_for_connection(timeout: effective_timeout)
    start_activity_monitor
    true
  rescue MCPClient::Errors::ConnectionError => e
    cleanup
    # Simply pass through any ConnectionError without wrapping it again
    # This prevents duplicate error messages in the stack
    raise e
  rescue StandardError => e
    cleanup
    # Check for stored auth error first as it's more specific
    auth_error = @mutex.synchronize { @auth_error }
    raise MCPClient::Errors::ConnectionError, auth_error if auth_error

    raise MCPClient::Errors::ConnectionError, "Failed to connect to MCP server at #{@base_url}: #{e.message}"
  end
end

#get_prompt(prompt_name, parameters) ⇒ Object

Get a prompt with the given parameters

Parameters:

  • prompt_name (String)

    the name of the prompt to get

  • parameters (Hash)

    the parameters to pass to the prompt

Returns:

  • (Object)

    the result of the prompt interpolation

Raises:



159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/mcp_client/server_sse.rb', line 159

def get_prompt(prompt_name, parameters)
  rpc_request('prompts/get', {
                name: prompt_name,
                arguments: parameters
              })
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
  # Re-raise connection/transport errors directly to match test expectations
  raise
rescue StandardError => e
  # For all other errors, wrap in PromptGetError
  raise MCPClient::Errors::PromptGetError, "Error get prompt '#{prompt_name}': #{e.message}"
end

#handle_elicitation_create(request_id, params) ⇒ void

This method returns an undefined value.

Handle elicitation/create request from server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • params (Hash)

    the elicitation parameters



495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/mcp_client/server_sse.rb', line 495

def handle_elicitation_create(request_id, params)
  # If no callback is registered, decline the request
  unless @elicitation_request_callback
    @logger.warn('Received elicitation request but no callback registered, declining')
    send_elicitation_response(request_id, { 'action' => 'decline' })
    return
  end

  # Call the registered callback
  result = @elicitation_request_callback.call(request_id, params)

  # Send the response back to the server
  send_elicitation_response(request_id, result)
end

#handle_roots_list(request_id, params) ⇒ void

This method returns an undefined value.

Handle roots/list request from server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • params (Hash)

    the request parameters



514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/mcp_client/server_sse.rb', line 514

def handle_roots_list(request_id, params)
  # If no callback is registered, return empty roots list
  unless @roots_list_request_callback
    @logger.debug('Received roots/list request but no callback registered, returning empty list')
    send_roots_list_response(request_id, { 'roots' => [] })
    return
  end

  # Call the registered callback
  result = @roots_list_request_callback.call(request_id, params)

  # Send the response back to the server
  send_roots_list_response(request_id, result)
end

#handle_sampling_create_message(request_id, params) ⇒ void

This method returns an undefined value.

Handle sampling/createMessage request from server (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • params (Hash)

    the sampling parameters



552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/mcp_client/server_sse.rb', line 552

def handle_sampling_create_message(request_id, params)
  # If no callback is registered, return error
  unless @sampling_request_callback
    @logger.warn('Received sampling request but no callback registered, returning error')
    send_error_response(request_id, -1, 'Sampling not supported')
    return
  end

  # Call the registered callback
  result = @sampling_request_callback.call(request_id, params)

  # Send the response back to the server
  send_sampling_response(request_id, result)
end

#handle_server_request(msg) ⇒ void

This method returns an undefined value.

Handle incoming JSON-RPC request from server (MCP 2025-06-18)

Parameters:

  • msg (Hash)

    the JSON-RPC request message



468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/mcp_client/server_sse.rb', line 468

def handle_server_request(msg)
  request_id = msg['id']
  method = msg['method']
  params = msg['params'] || {}

  @logger.debug("Received server request: #{method} (id: #{request_id})")

  case method
  when 'elicitation/create'
    handle_elicitation_create(request_id, params)
  when 'roots/list'
    handle_roots_list(request_id, params)
  when 'sampling/createMessage'
    handle_sampling_create_message(request_id, params)
  else
    # Unknown request method, send error response
    send_error_response(request_id, -32_601, "Method not found: #{method}")
  end
rescue StandardError => e
  @logger.error("Error handling server request: #{e.message}")
  send_error_response(request_id, -32_603, "Internal error: #{e.message}")
end

#list_promptsArray<MCPClient::Prompt>

List all prompts available from the MCP server

Returns:

Raises:



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/mcp_client/server_sse.rb', line 127

def list_prompts
  @mutex.synchronize do
    return @prompts if @prompts
  end

  begin
    ensure_initialized

    prompts_data = request_prompts_list
    @mutex.synchronize do
      @prompts = prompts_data.map do |prompt_data|
        MCPClient::Prompt.from_json(prompt_data, server: self)
      end
    end

    @mutex.synchronize { @prompts }
  rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
    # Re-raise these errors directly
    raise
  rescue StandardError => e
    raise MCPClient::Errors::PromptGetError, "Error listing prompts: #{e.message}"
  end
end

#list_resource_templates(cursor: nil) ⇒ Hash

List all resource templates available from the MCP server

Parameters:

  • cursor (String, nil) (defaults to: nil)

    optional cursor for pagination

Returns:

  • (Hash)

    result containing resourceTemplates array and optional nextCursor

Raises:



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/mcp_client/server_sse.rb', line 233

def list_resource_templates(cursor: nil)
  ensure_initialized
  params = {}
  params['cursor'] = cursor if cursor
  result = rpc_request('resources/templates/list', params)

  templates = (result['resourceTemplates'] || []).map do |template_data|
    MCPClient::ResourceTemplate.from_json(template_data, server: self)
  end

  { 'resourceTemplates' => templates, 'nextCursor' => result['nextCursor'] }
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
  raise
rescue StandardError => e
  raise MCPClient::Errors::ResourceReadError, "Error listing resource templates: #{e.message}"
end

#list_resources(cursor: nil) ⇒ Hash

List all resources available from the MCP server

Parameters:

  • cursor (String, nil) (defaults to: nil)

    optional cursor for pagination

Returns:

  • (Hash)

    result containing resources array and optional nextCursor

Raises:



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/mcp_client/server_sse.rb', line 178

def list_resources(cursor: nil)
  @mutex.synchronize do
    return @resources_result if @resources_result && !cursor
  end

  begin
    ensure_initialized

    params = {}
    params['cursor'] = cursor if cursor
    result = rpc_request('resources/list', params)

    resources = (result['resources'] || []).map do |resource_data|
      MCPClient::Resource.from_json(resource_data, server: self)
    end

    resources_result = { 'resources' => resources, 'nextCursor' => result['nextCursor'] }

    @mutex.synchronize do
      @resources_result = resources_result unless cursor
    end

    resources_result
  rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
    # Re-raise these errors directly
    raise
  rescue StandardError => e
    raise MCPClient::Errors::ResourceReadError, "Error listing resources: #{e.message}"
  end
end

#list_toolsArray<MCPClient::Tool>

List all tools available from the MCP server

Returns:

Raises:



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/mcp_client/server_sse.rb', line 285

def list_tools
  @mutex.synchronize do
    return @tools if @tools
  end

  begin
    ensure_initialized

    tools_data = request_tools_list
    @mutex.synchronize do
      @tools = tools_data.map do |tool_data|
        MCPClient::Tool.from_json(tool_data, server: self)
      end
    end

    @mutex.synchronize { @tools }
  rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
    # Re-raise these errors directly
    raise
  rescue StandardError => e
    raise MCPClient::Errors::ToolCallError, "Error listing tools: #{e.message}"
  end
end

#log_level=(level) ⇒ Hash

Set the logging level on the server (MCP 2025-06-18)

Parameters:

  • level (String)

    the log level (‘debug’, ‘info’, ‘notice’, ‘warning’, ‘error’, ‘critical’, ‘alert’, ‘emergency’)

Returns:

  • (Hash)

    empty result on success

Raises:



349
350
351
352
353
354
355
# File 'lib/mcp_client/server_sse.rb', line 349

def log_level=(level)
  rpc_request('logging/setLevel', { level: level })
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
  raise
rescue StandardError => e
  raise MCPClient::Errors::ServerError, "Error setting log level: #{e.message}"
end

#on_elicitation_request(&block) ⇒ void

This method returns an undefined value.

Register a callback for elicitation requests (MCP 2025-06-18)

Parameters:

  • block (Proc)

    callback that receives (request_id, params) and returns response hash



447
448
449
# File 'lib/mcp_client/server_sse.rb', line 447

def on_elicitation_request(&block)
  @elicitation_request_callback = block
end

#on_roots_list_request(&block) ⇒ void

This method returns an undefined value.

Register a callback for roots/list requests (MCP 2025-06-18)

Parameters:

  • block (Proc)

    callback that receives (request_id, params) and returns response hash



454
455
456
# File 'lib/mcp_client/server_sse.rb', line 454

def on_roots_list_request(&block)
  @roots_list_request_callback = block
end

#on_sampling_request(&block) ⇒ void

This method returns an undefined value.

Register a callback for sampling requests (MCP 2025-06-18)

Parameters:

  • block (Proc)

    callback that receives (request_id, params) and returns response hash



461
462
463
# File 'lib/mcp_client/server_sse.rb', line 461

def on_sampling_request(&block)
  @sampling_request_callback = block
end

#post_jsonrpc_response(response) ⇒ void

This method returns an undefined value.

Post a JSON-RPC response message to the server via HTTP

Parameters:

  • response (Hash)

    the JSON-RPC response



638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/mcp_client/server_sse.rb', line 638

def post_jsonrpc_response(response)
  unless @rpc_endpoint
    @logger.error('Cannot send response: RPC endpoint not available')
    return
  end

  # Use the same connection pattern as post_json_rpc_request
  uri = URI.parse(@base_url)
  base = "#{uri.scheme}://#{uri.host}:#{uri.port}"
  @rpc_conn ||= create_json_rpc_connection(base)

  json_body = JSON.generate(response)

  @rpc_conn.post do |req|
    req.url @rpc_endpoint
    req.headers['Content-Type'] = 'application/json'
    @headers.each { |k, v| req.headers[k] = v }
    req.body = json_body
  end

  @logger.debug("Sent response via HTTP POST: #{json_body}")
rescue StandardError => e
  @logger.error("Failed to send response via HTTP POST: #{e.message}")
end

#read_resource(uri) ⇒ Array<MCPClient::ResourceContent>

Read a resource by its URI

Parameters:

  • uri (String)

    the URI of the resource to read

Returns:

Raises:



216
217
218
219
220
221
222
223
224
225
226
# File 'lib/mcp_client/server_sse.rb', line 216

def read_resource(uri)
  result = rpc_request('resources/read', { uri: uri })
  contents = result['contents'] || []
  contents.map { |content| MCPClient::ResourceContent.from_json(content) }
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
  # Re-raise connection/transport errors directly to match test expectations
  raise
rescue StandardError => e
  # For all other errors, wrap in ResourceReadError
  raise MCPClient::Errors::ResourceReadError, "Error reading resource '#{uri}': #{e.message}"
end

#send_elicitation_response(request_id, result) ⇒ void

This method returns an undefined value.

Send elicitation response back to server via HTTP POST (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • result (Hash)

    the elicitation result (action and optional content)



596
597
598
599
600
601
602
603
604
605
606
607
608
609
# File 'lib/mcp_client/server_sse.rb', line 596

def send_elicitation_response(request_id, result)
  ensure_initialized

  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'result' => result
  }

  # Send response via HTTP POST to the RPC endpoint
  post_jsonrpc_response(response)
rescue StandardError => e
  @logger.error("Error sending elicitation response: #{e.message}")
end

#send_error_response(request_id, code, message) ⇒ void

This method returns an undefined value.

Send error response back to server via HTTP POST (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • code (Integer)

    the error code

  • message (String)

    the error message



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
# File 'lib/mcp_client/server_sse.rb', line 616

def send_error_response(request_id, code, message)
  ensure_initialized

  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'error' => {
      'code' => code,
      'message' => message
    }
  }

  # Send response via HTTP POST to the RPC endpoint
  post_jsonrpc_response(response)
rescue StandardError => e
  @logger.error("Error sending error response: #{e.message}")
end

#send_roots_list_response(request_id, result) ⇒ void

This method returns an undefined value.

Send roots/list response back to server via HTTP POST (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • result (Hash)

    the roots list result



533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/mcp_client/server_sse.rb', line 533

def send_roots_list_response(request_id, result)
  ensure_initialized

  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'result' => result
  }

  # Send response via HTTP POST to the RPC endpoint
  post_jsonrpc_response(response)
rescue StandardError => e
  @logger.error("Error sending roots/list response: #{e.message}")
end

#send_sampling_response(request_id, result) ⇒ void

This method returns an undefined value.

Send sampling response back to server via HTTP POST (MCP 2025-06-18)

Parameters:

  • request_id (String, Integer)

    the JSON-RPC request ID

  • result (Hash)

    the sampling result (role, content, model, stopReason)



571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
# File 'lib/mcp_client/server_sse.rb', line 571

def send_sampling_response(request_id, result)
  ensure_initialized

  # Check if result contains an error
  if result.is_a?(Hash) && result['error']
    send_error_response(request_id, result['error']['code'] || -1, result['error']['message'] || 'Sampling error')
    return
  end

  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'result' => result
  }

  # Send response via HTTP POST to the RPC endpoint
  post_jsonrpc_response(response)
rescue StandardError => e
  @logger.error("Error sending sampling response: #{e.message}")
end

#subscribe_resource(uri) ⇒ Boolean

Subscribe to resource updates

Parameters:

  • uri (String)

    the URI of the resource to subscribe to

Returns:

  • (Boolean)

    true if subscription successful

Raises:



255
256
257
258
259
260
261
262
263
# File 'lib/mcp_client/server_sse.rb', line 255

def subscribe_resource(uri)
  ensure_initialized
  rpc_request('resources/subscribe', { uri: uri })
  true
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
  raise
rescue StandardError => e
  raise MCPClient::Errors::ResourceReadError, "Error subscribing to resource '#{uri}': #{e.message}"
end

#unsubscribe_resource(uri) ⇒ Boolean

Unsubscribe from resource updates

Parameters:

  • uri (String)

    the URI of the resource to unsubscribe from

Returns:

  • (Boolean)

    true if unsubscription successful

Raises:



270
271
272
273
274
275
276
277
278
# File 'lib/mcp_client/server_sse.rb', line 270

def unsubscribe_resource(uri)
  ensure_initialized
  rpc_request('resources/unsubscribe', { uri: uri })
  true
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError
  raise
rescue StandardError => e
  raise MCPClient::Errors::ResourceReadError, "Error unsubscribing from resource '#{uri}': #{e.message}"
end