Class: MCPClient::ServerSSE Deprecated

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

Overview

Deprecated.

The HTTP+SSE transport has been deprecated since MCP 2025-03-26 and is listed in the 2026-07-28 deprecated features registry (SEP-2596); earliest removal is three months after SEP-2596 reaches Final. Use ServerStreamableHTTP.

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, OriginPolicy, 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
MAX_SSE_BUFFER_BYTES =

Maximum bytes the SSE parse buffer may hold while waiting for an event terminator. The stream is peer-controlled: without a cap, a hostile server could withhold the blank-line delimiter forever and grow the buffer until the host runs out of memory. Generous enough for any legitimate JSON-RPC response event.

32 * 1024 * 1024

Constants included from RequestAuthorization

RequestAuthorization::ANONYMOUS_AUTHORIZATION, RequestAuthorization::UNRECORDED_AUTHORIZATION

Constants included from JsonRpcTransport

JsonRpcTransport::NO_RESULT

Constants included from JsonRpcCommon

JsonRpcCommon::CORE_RESULT_TYPES, JsonRpcCommon::EXTENSION_ID_PATTERN, JsonRpcCommon::INPUT_RETRY_DELAY, JsonRpcCommon::INPUT_RETRY_MAX_DELAY, JsonRpcCommon::LEGACY_RESULT_TYPES, JsonRpcCommon::LOG_LEVELS, JsonRpcCommon::MAX_INPUT_ROUND_TRIPS, JsonRpcCommon::MAX_PEER_LOG_TEXT_LENGTH, JsonRpcCommon::MRTR_METHODS, JsonRpcCommon::NAME_HEADER_SOURCES, JsonRpcCommon::NON_IDEMPOTENT_METHODS, JsonRpcCommon::REMOVED_MODERN_NOTIFICATIONS, JsonRpcCommon::RESULT_TYPE_EXTENSIONS, JsonRpcCommon::TASKS_EXTENSION, JsonRpcCommon::TASK_METHODS

Constants included from SessionPin

MCPClient::SessionPin::SESSION_PINS, MCPClient::SessionPin::WRITE_GUARDS

Constants included from RequestMetadata

RequestMetadata::CACHE_NEUTRAL_META_KEYS, RequestMetadata::META_CLIENT_CAPABILITIES, RequestMetadata::META_CLIENT_INFO, RequestMetadata::META_LOG_LEVEL, RequestMetadata::META_PROTOCOL_VERSION, RequestMetadata::META_SERVER_INFO, RequestMetadata::META_SUBSCRIPTION_ID, RequestMetadata::OPAQUE_PARAMS, RequestMetadata::PROTECTED_META_KEYS, RequestMetadata::UNRECORDED_PARAMS

Constants included from ResultCaching

ResultCaching::CACHE_INIT_LOCK, ResultCaching::LEGACY_ENTRY, ResultCaching::LIST_CHANGE_NOTIFICATIONS, ResultCaching::LIST_METHOD_KINDS, ResultCaching::LIST_VALUE_KINDS, ResultCaching::MAX_CACHED_READS, ResultCaching::MAX_READ_GENERATIONS, ResultCaching::PLACEHOLDER_KINDS

Constants included from InputRoundTrips

InputRoundTrips::INPUT_REQUEST_HANDLERS

Constants included from SubscriptionSupport

MCPClient::SubscriptionSupport::CONTROL_NOTIFICATIONS, MCPClient::SubscriptionSupport::DEFAULT_ACK_TIMEOUT

Constants included from JsonRpcCommon::ErrorBodies

JsonRpcCommon::ErrorBodies::MAX_ERROR_BODY_BYTES

Constants included from RequestMetaScope

RequestMetaScope::HOST_CALLBACKS, RequestMetaScope::SCOPED_OPERATIONS

Constants inherited from ServerBase

MCPClient::ServerBase::MAX_LIST_PAGES, MCPClient::ServerBase::RELATED_TASK_META_KEY

Instance Attribute Summary collapse

Attributes included from JsonRpcCommon

#request_meta, #send_client_info

Attributes inherited from ServerBase

#instructions, #logger, #name, #read_timeout

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 OriginPolicy

#origin_of, #reject_cross_origin_redirect!, #same_origin?

Methods included from JsonRpcTransport

#rpc_notify, #rpc_request, #send_cancellation_notification, #send_one_request

Methods included from JsonRpcCommon

#accepted_result_types, #apply_discover_result, #begin_era_probe, #build_jsonrpc_notification, #build_jsonrpc_request, #build_named_request_params, #cancellable_request?, #client_capabilities, #client_info_payload, #declare_extension, #declare_sampling_tools, #declared_extensions, #describe_body_size, #describe_jsonrpc_message, #describe_parse_error, #discovery_cache_scope, #discovery_clock, #discovery_fresh?, #encode_header_value, #era_probe_in_flight?, #host_request_meta, #implemented_extension_result_types, #initialization_params, #mcp_name_header_value, #merge_meta_spellings, #modern?, #modern_request_headers, #notify_cache_invalidation, #ping, #process_jsonrpc_response, #protocol_era, #protocol_version, #record_discovery_freshness, #record_server_info, #refused_undeclared_sampling_tools?, #registered_callback?, #reject_task_result_discover!, #reject_task_result_on_unsupported_method!, #request_meta_claim, #required_request_meta, #reserved_meta_supplied?, #resolve_input_round_trips, restore_wire_keys, result_type, #sampling_tools_supported?, #sanitize_log_text, #select_protocol_version, #send_client_info?, #settle_era_probe, #spend_held_request_meta, #split_request_meta, #supported_versions, #suppressed_modern_notification?, #tasks_extension_declared?, #validate_log_level!, #validate_protocol_version!, #validate_result_type!, #with_request_meta, #with_retry

Methods included from SessionPin

#check_session_pin!, #guarded_writes, #pinned_to_session, #unpinned_session

Methods included from RequestMetadata

#adoptable_request_meta_hold, #claimable_request_meta_hold, #close_request_meta_hold, #current_params_fingerprint, #deep_sort_keys, #held_request_meta, #held_request_meta_key, #holding_request_meta, #note_request_params, #note_request_params_pending, #offer_request_meta_hold, #offered_request_meta_key, #open_request_meta_hold, #outside_request_meta_hold, #params_fingerprint_of, #recorded_request_params, #release_held_request_meta, #request_params_fingerprint, #request_params_key, #restore_request_params, #take_offered_request_meta_hold, #withdraw_request_meta_hold

Methods included from ResultCaching

#assume_zero_ttl?, #attach_list_value, #authorization_fingerprint, authorization_header_value, #authorization_header_value, #bind_authorization_context, #bump_cache_epoch, #bump_cache_generation, #cache_entries, #cache_entries_mutex, #cache_entry_for, #cache_entry_fresh?, #cache_entry_hinted?, #cache_entry_token, #cache_epoch, #cache_fresh?, #cache_generation, #cache_info, #cached_list_value, #clear_response_received_at, #clear_result_cache, #discard_paginated_list, #empty_list_copy?, #entry_for_current_params?, #entry_in_current_context?, #entry_matches_authorization?, faraday_headers, #faraday_headers, #fetching_list_page, #forget_served_entries, #forget_transport_thread_state, #fresh_list_value, #hinted_list_value, #invalid_cursor_error?, #invalidate_cache, #invalidate_cache_for_notification, #invalidate_read_cache, #list_cache_epoch, #list_kind_for, #mixed_pages_placeholder, #monotonic_now, #note_legacy_served, #note_response_received_at, #note_served_entry, #on_cache_invalidation, #private_entry_for_current_context, #prune_read_entries, #read_cache_key, #read_resource_with_cache, #record_cache_hint, #record_list_cache_hint, #record_paginated_cache_hint, #recorded_entries_key, #release_serving_request_meta, #remember_recorded_entry, #response_received_at, #response_received_key, #sent_authorization_known?, #served_entries_key, #stale_fallback_for, #stale_list_entry, #stale_list_value, #store_read_entry, #take_served_entry, #transport_thread_local_keys

Methods included from InputRoundTrips

#fulfil_input_request, #fulfil_input_requests, #undeclared_sampling_tool_use?

Methods included from SubscriptionSupport

#await_acknowledgment_deadline, #close_subscription_gracefully, #confirm_resource_subscription, #deliver_subscription_notification, #discard_mapped_resource_subscription, #drop_unacknowledged_resource_subscriptions, #ensure_modern_listen!, #expire_unacknowledged_subscription, #handle_server_cancellation, #handle_subscription_acknowledgment, #handle_subscription_control, #handle_subscription_response, #listen, #live_resource_subscription, #notify_host, #open_listen, #open_resource_subscription, #rearm_acknowledgment_deadline, #recheck_mapped_resource_subscription, #register_subscription, #report_declined_subscription_types, #require_resource_watch, #resource_subscription_mutex, #resource_subscription_mutexes, #resource_subscriptions, #route_notification, #settled_resource_subscription, #subscribe_resource_via_listen, #subscription_ack_timeout, #subscription_by_id, #subscription_delivery_target, #subscription_for_notification, #subscriptions, #subscriptions_mutex, #unmap_resource_subscription, #unregister_subscription, #unregister_subscription_id, #unsubscribe_resource_via_listen

Methods included from DeprecationNotices

#warn_input_request_answer_deprecated, #warn_input_request_deprecated, #warn_logging_deprecated, #warn_request_log_level_deprecated, #warn_roots_deprecated, #warn_sampling_deprecated

Methods included from JsonRpcCommon::InputWaits

#on_input_required_wait, #reject_input_required_discover!, #resume_input_required

Methods included from JsonRpcCommon::ErrorBodies

#decoded_error_body, #gunzip_bounded, #jsonrpc_error_from_http_response, #jsonrpc_error_in_body, #oversized_error_body?

Methods included from SseParser

#fail_endpoint_handshake!, #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?, #resolve_endpoint_uri

Methods inherited from ServerBase

#cancel_subscription, #capability?, #client_info=, #discovery_refresh_needed?, #listen, #merge_related_task_meta, #on_cache_invalidation, #on_notification, #ping, #require_capability!, #resource_not_found_error, #resource_not_found_response?, #rpc_notify, #rpc_request, #session_epoch

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



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/mcp_client/server_sse.rb', line 92

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 = {}
  # Ids of requests a caller is actively waiting on. Only responses for
  # these ids are stored in @sse_results — everything else on the peer
  # controlled stream is unsolicited and discarded.
  @pending_request_ids = Set.new
  @mutex = Monitor.new
  @buffer = +''
  # How much of @buffer has already been searched for an event terminator
  @buffer_scanned = 0
  @sse_connected = false
  @connection_established = false
  @connection_cv = @mutex.new_cond
  @initialized = false
  # Negotiated protocol version captured from the initialize result
  # (sent as the MCP-Protocol-Version header on post-initialize requests)
  @protocol_version = nil
  @auth_error = nil
  # Non-auth connection failure cause (e.g. invalid endpoint event URI)
  # recorded by the SSE worker for wait_for_connection to surface
  @connection_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-11-25
end

Instance Attribute Details

#base_url ⇒ String (readonly)

Returns The base URL of the MCP server.

Returns:

  • (String) —

    The base URL of the MCP server



74
75
76
# File 'lib/mcp_client/server_sse.rb', line 74

def base_url
  @base_url
end

#capabilities ⇒ Hash? (readonly)

Server capabilities from initialize response

Returns:

  • (Hash, nil) —

    Server capabilities



82
83
84
# File 'lib/mcp_client/server_sse.rb', line 82

def capabilities
  @capabilities
end

#prompts ⇒ Array<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)



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

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

#resources ⇒ Object (readonly)

Returns the value of attribute resources.



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

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

#server_info ⇒ Hash? (readonly)

Server information from initialize response

Returns:

  • (Hash, nil) —

    Server information



78
79
80
# File 'lib/mcp_client/server_sse.rb', line 78

def server_info
  @server_info
end

#tools ⇒ Array<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)



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

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:



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/mcp_client/server_sse.rb', line 393

def call_tool(tool_name, parameters)
  rpc_request('tools/call', build_named_request_params(tool_name, parameters))
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
  # Re-raise connection/transport errors directly to match test expectations
  raise
rescue MCPClient::Errors::ServerError => e
  # 2026-07-28 protocol errors (typed -3202x, invalid result) carry
  # actionable data such as requiredCapabilities; keep them intact.
  raise if e.protocol_error?

  raise MCPClient::Errors::ToolCallError, "Error calling tool '#{tool_name}': #{e.message}"
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)


151
152
153
154
155
# File 'lib/mcp_client/server_sse.rb', line 151

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

#cleanup ⇒ Object

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



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
# File 'lib/mcp_client/server_sse.rb', line 516

def cleanup
  # Everything this transport left on this thread — the notes of the
  # entries it served and recorded, the credentials, parameters and
  # metadata of its requests — describes a slice that will never be
  # tagged and a request that will never be made.
  forget_transport_thread_state
  bump_session_epoch
  @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
    # A fresh session negotiates its own protocol version; keeping the old
    # one would leak the previous session's version into the next
    # initialize POST's MCP-Protocol-Version header.
    @protocol_version = nil

    # Reset the SSE parse buffer so a reconnect never inherits a leftover
    # partial event from the previous connection.
    @buffer = +''
    @buffer_scanned = 0

    # Drop results nobody is waiting for, so peer-supplied state cannot
    # accumulate across reconnects. Results for still-pending requests are
    # KEPT: a response can arrive while its POST is still returning, and
    # the waiter (which reconnects through ensure_sse_connection_active)
    # is about to consume it. Discarding those reported a timeout for a
    # tool call the server had already executed — inviting a duplicate
    # manual retry. unregister_pending_request clears each entry when its
    # request finishes, so nothing lingers.
    @sse_results.select! { |id, _| @pending_request_ids.include?(id) }

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

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

    # The activity monitor drives reconnection, and reconnection calls this
    # method FROM that thread. Killing the current thread here would abort
    # the reconnect before connect() runs (the historical dead-code bug), so
    # never kill/clear the activity thread when we are running on it. Leaving
    # @activity_timer_thread referenced also makes start_activity_monitor
    # short-circuit, preventing a duplicate monitor after reconnect.
    activity_thread = @activity_timer_thread
    kill_activity_thread = activity_thread && activity_thread != Thread.current

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

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

    if kill_activity_thread
      begin
        activity_thread.kill
      rescue StandardError => e
        @logger.debug("Error killing activity thread: #{e.message}")
      end
    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
    @tools_data = nil
    @prompts = nil
    @prompts_data = nil
    @resources_result = nil
    @templates_result = 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
  # Cached results and their hints belong to the connection that was just
  # torn down (MCP 2026-07-28 caching); outside @mutex, as the cache has
  # its own lock.
  clear_result_cache
end

#complete(ref:, argument:, context: nil) ⇒ 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' })

  • context (Hash, nil) (defaults to: nil) —

    optional context for the completion (MCP 2025-11-25)

Returns:

  • (Hash) —

    completion result with 'values', optional 'total', and 'hasMore' fields

Raises:



415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/mcp_client/server_sse.rb', line 415

def complete(ref:, argument:, context: nil)
  ensure_initialized
  require_capability!('completions', method: 'completion/complete')
  params = { ref: ref, argument: argument }
  params[:context] = context if context
  result = require_complete_result!(rpc_request('completion/complete', params), 'completion/complete')
  result['completion'] || { 'values' => [] }
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError,
       MCPClient::Errors::CapabilityError
  raise
rescue MCPClient::Errors::ServerError => e
  # 2026-07-28 protocol errors (typed -3202x, invalid result) carry
  # actionable data such as requiredCapabilities; keep them intact.
  raise if e.protocol_error?

  raise MCPClient::Errors::ServerError, "Error requesting completion: #{e.message}"
rescue StandardError => e
  raise MCPClient::Errors::ServerError, "Error requesting completion: #{e.message}"
end

#connect ⇒ Boolean

Connect to the MCP server over HTTP/HTTPS with SSE

Returns:

  • (Boolean) —

    true if connection was successful

Raises:



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/mcp_client/server_sse.rb', line 465

def connect
  # An already-established connection is another use of the transport,
  # and another chance at the notice: the first connect may have found
  # notices disabled, a logger above WARN or a logger that raised, all of
  # which leave the slot unspent. A long-lived connection would otherwise
  # never retry it.
  if @mutex.synchronize { @connection_established }
    MCPClient::Deprecations.warn(:http_sse_transport, @logger)
    return true
  end

  # 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
    # Clear any stale connection failure cause from a previous attempt
    @mutex.synchronize { @connection_error = nil }

    start_sse_thread
    effective_timeout = [@read_timeout || 30, 30].min
    wait_for_connection(timeout: effective_timeout)
    start_activity_monitor
    # The notice is about using the transport: MCPClient.connect builds
    # (and tries) an SSE server while probing a URL that may end up on
    # another transport, so only an established connection counts.
    MCPClient::Deprecations.warn(:http_sse_transport, @logger)
    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

#current_authorization_context(_kind = nil) ⇒ String?

The authorization context of this transport (MCP 2026-07-28 caching, cacheScope "private"): HTTP+SSE sends the static Authorization header it was configured with, so that header is both what the next request would carry and what the last one carried.

Parameters:

  • _kind (Symbol, String, nil) (defaults to: nil) —

    the cache kind (every request carries the same header)

Returns:

  • (String, nil)


613
614
615
616
617
618
# File 'lib/mcp_client/server_sse.rb', line 613

def current_authorization_context(_kind = nil)
  # Through Faraday's case-insensitive table, so several spellings of
  # the header in the configured hash resolve to the one value a
  # request would actually carry.
  authorization_fingerprint(authorization_header_value(faraday_headers(@headers)))
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:



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/mcp_client/server_sse.rb', line 196

def get_prompt(prompt_name, parameters)
  rpc_request('prompts/get', build_named_request_params(prompt_name, parameters))
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError
  # Re-raise connection/transport errors directly to match test expectations
  raise
rescue MCPClient::Errors::ServerError => e
  # 2026-07-28 protocol errors (typed -3202x, invalid result) carry
  # actionable data such as requiredCapabilities; keep them intact.
  raise if e.protocol_error?

  raise MCPClient::Errors::PromptGetError, "Error get prompt '#{prompt_name}': #{e.message}"
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



720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
# File 'lib/mcp_client/server_sse.rb', line 720

def handle_elicitation_create(request_id, params)
  # Without a callback there is no user to interact with: answer with a
  # JSON-RPC error rather than fabricating a user "decline".
  unless @elicitation_request_callback
    @logger.warn('Received elicitation request but no callback registered')
    send_error_response(request_id, -32_601, 'Elicitation not supported: no handler configured')
    return
  end

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

  # Send the response back to the server (echoing related-task _meta)
  send_elicitation_response(request_id, merge_related_task_meta(result, params))
end

#handle_ping(request_id) ⇒ void

This method returns an undefined value.

Handle a server-initiated ping request (MCP ping utility) The receiver MUST respond promptly with an empty result.

Parameters:

  • request_id (String, Integer) —

    the JSON-RPC request ID



705
706
707
708
709
710
711
712
713
714
# File 'lib/mcp_client/server_sse.rb', line 705

def handle_ping(request_id)
  response = {
    'jsonrpc' => '2.0',
    'id' => request_id,
    'result' => {}
  }
  post_jsonrpc_response(response)
rescue StandardError => e
  @logger.error("Error sending ping response: #{e.message}")
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



740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
# File 'lib/mcp_client/server_sse.rb', line 740

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)
  # Serving a roots/list answer that carries a root means this host
  # declared, and is using, the deprecated Roots capability (SEP-2577) —
  # with or without a Client. An empty answer is not use of it.
  warn_roots_deprecated(result)

  # Send the response back to the server (echoing related-task _meta)
  send_roots_list_response(request_id, merge_related_task_meta(result, params))
end

#handle_sampling_create_message(request_id, params) ⇒ void

This method returns an undefined value.

Handle sampling/createMessage request from server (MCP 2025-11-25)

Parameters:

  • request_id (String, Integer) —

    the JSON-RPC request ID

  • params (Hash) —

    the sampling parameters



782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
# File 'lib/mcp_client/server_sse.rb', line 782

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')
    # sampling.mdx § Error Handling reserves -1 for "User rejected sampling
    # request"; a capability this client never declared is an unsupported
    # method (-32601, Method not found), as Client#handle_sampling_request answers.
    send_error_response(request_id, -32_601, 'Sampling not supported')
    return
  end

  # Sampling, and the includeContext values it may carry, are deprecated
  # (SEP-2577, SEP-2596) — with or without a Client.
  warn_sampling_deprecated(params)
  return if refused_undeclared_sampling_tools?(request_id, params)

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

  # Send the response back to the server (echoing related-task _meta)
  send_sampling_response(request_id, merge_related_task_meta(result, params))
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



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
# File 'lib/mcp_client/server_sse.rb', line 673

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 'ping'
    handle_ping(request_id)
  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
  # The exception message is host-internal (file paths, connection
  # strings, library internals): log it locally, but answer the peer with
  # a constant message so failures cannot be used to probe the host.
  @logger.error("Error handling server request: #{e.message}")
  send_error_response(request_id, -32_603, 'Internal error')
end

#list_prompts ⇒ Array<MCPClient::Prompt>

List all prompts available from the MCP server

Returns:

Raises:



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/mcp_client/server_sse.rb', line 162

def list_prompts
  # MCP 2026-07-28 caching: a list is served only while its hint is
  # fresh, and only from the entry that carries the hint.
  cached = fresh_list_value(:prompts) { @mutex.synchronize { @prompts } }
  return cached if cached

  @mutex.synchronize { @prompts_data = nil }

  begin
    ensure_initialized

    prompts = request_prompts_list.map { |prompt_data| MCPClient::Prompt.from_json(prompt_data, server: self) }
    @mutex.synchronize do
      @prompts = attach_list_value(:prompts, prompts) ? prompts : nil
    end

    # This request's own list, never a re-read of @prompts.
    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:



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/mcp_client/server_sse.rb', line 284

def list_resource_templates(cursor: nil)
  # Only a list the server itself bounded is served from here: a
  # positive ttlMs means no second request, while a list with no hint
  # (a 2025-11-25 server) is asked for again, as it was before this
  # transport cached anything (MCP 2026-07-28 caching).
  cached = cursor ? nil : hinted_list_value(:templates)
  return cached if cached

  ensure_initialized
  params = {}
  params['cursor'] = cursor if cursor
  epoch = cache_epoch(:templates)
  answer = fetching_list_page(:templates, cursor) { rpc_request('resources/templates/list', params) }
  result = require_complete_result!(answer, 'resources/templates/list')
  # MCP 2026-07-28 caching: the first page's hint decides freshness,
  # counted from receipt (the list is attached once converted).
  record_cache_hint(:templates, result, epoch: epoch) unless cursor

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

  @mutex.synchronize do
    unless cursor
      @templates_result = attach_list_value(:templates, templates_result) ? templates_result : nil
    end
  end

  templates_result
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:



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/mcp_client/server_sse.rb', line 218

def list_resources(cursor: nil)
  cached = cursor ? nil : fresh_list_value(:resources) { @mutex.synchronize { @resources_result } }
  return cached if cached

  begin
    ensure_initialized

    params = {}
    params['cursor'] = cursor if cursor
    epoch = cache_epoch(:resources)
    answer = fetching_list_page(:resources, cursor) { rpc_request('resources/list', params) }
    result = require_complete_result!(answer, 'resources/list')
    # MCP 2026-07-28 caching: the first page's hint decides how long the
    # cached list may be served, counted from receipt (the list is
    # attached once converted).
    record_cache_hint(:resources, result, epoch: epoch) unless cursor

    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
      unless cursor
        @resources_result = attach_list_value(:resources, resources_result) ? resources_result : nil
      end
    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_tools ⇒ Array<MCPClient::Tool>

List all tools available from the MCP server

Returns:

Raises:



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

def list_tools
  # MCP 2026-07-28 caching: a list is served only while its hint is
  # fresh, and only from the entry that carries the hint.
  cached = fresh_list_value(:tools) { @mutex.synchronize { @tools } }
  return cached if cached

  @mutex.synchronize { @tools_data = nil }

  begin
    ensure_initialized

    tools = request_tools_list.map { |tool_data| MCPClient::Tool.from_json(tool_data, server: self) }
    @mutex.synchronize do
      @tools = attach_list_value(:tools, tools) ? tools : nil
    end

    # This request's own list, never a re-read of @tools.
    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

Deprecated.

Logging is deprecated since MCP 2026-07-28 (SEP-2577); earliest removal is the first revision released on or after 2027-07-28. Have the server log to stderr (stdio) or use OpenTelemetry instead.

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:



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/mcp_client/server_sse.rb', line 444

def log_level=(level)
  MCPClient::Deprecations.warn(:logging, @logger)
  ensure_initialized
  require_capability!('logging', method: 'logging/setLevel')
  rpc_request('logging/setLevel', { level: level })
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError,
       MCPClient::Errors::CapabilityError
  raise
rescue MCPClient::Errors::ServerError => e
  # 2026-07-28 protocol errors (typed -3202x, invalid result) carry
  # actionable data such as requiredCapabilities; keep them intact.
  raise if e.protocol_error?

  raise MCPClient::Errors::ServerError, "Error setting log level: #{e.message}"
rescue StandardError => e
  raise MCPClient::Errors::ServerError, "Error setting log level: #{e.message}"
end

#note_sent_authorization(response) ⇒ void

This method returns an undefined value.

Remember the Authorization a request actually carried once it was sent, for a request that recorded none of its own. What binds the result is what the request was built with: response.env is mutable and the response phase may rewrite it (a redaction, a redirect that strips the header), which would file an authenticated result under the anonymous context.

Parameters:

  • response (Faraday::Response, nil)


628
629
630
631
632
633
634
635
# File 'lib/mcp_client/server_sse.rb', line 628

def note_sent_authorization(response)
  return if request_authorization_recorded?

  env = response.respond_to?(:env) ? response.env : nil
  return unless env.respond_to?(:request_headers) && env.request_headers

  note_request_authorization(authorization_header_value(env.request_headers))
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



640
641
642
# File 'lib/mcp_client/server_sse.rb', line 640

def on_elicitation_request(&block)
  @elicitation_request_callback = block
end

#on_roots_list_request(&block) ⇒ void

Deprecated.

Roots is deprecated since MCP 2026-07-28 (SEP-2577); earliest removal is the first revision released on or after 2027-07-28. Registering a handler is not itself use of Roots — a handler that answers with no root exposes nothing deprecated — but a handler that answers with a root adopts the deprecated feature and raises the notice. Pass directories or files through tool parameters, resource URIs or server configuration instead.

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



654
655
656
# File 'lib/mcp_client/server_sse.rb', line 654

def on_roots_list_request(&block)
  @roots_list_request_callback = block
end

#on_sampling_request(&block) ⇒ void

Deprecated.

Sampling is deprecated since MCP 2026-07-28 (SEP-2577); earliest removal is the first revision released on or after 2027-07-28. Integrate directly with the LLM provider API instead of serving sampling/createMessage.

This method returns an undefined value.

Register a callback for sampling requests (MCP 2025-11-25)

Parameters:

  • block (Proc) —

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



666
667
668
# File 'lib/mcp_client/server_sse.rb', line 666

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



884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
# File 'lib/mcp_client/server_sse.rb', line 884

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'
    # MCP lifecycle "Version Negotiation": include the MCP-Protocol-Version
    # header on all HTTP requests after the initialize handshake.
    req.headers['Mcp-Protocol-Version'] = @protocol_version if @protocol_version
    @headers.each { |k, v| req.headers[k] = v }
    note_request_authorization(authorization_header_value(req.headers))
    req.body = json_body
  end

  @logger.debug("Sent response via HTTP POST: #{describe_jsonrpc_message(response)}")
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:



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/mcp_client/server_sse.rb', line 263

def read_resource(uri)
  ensure_initialized
  read_resource_with_cache(uri) { |sent| rpc_request('resources/read', { uri: sent }) }
rescue MCPClient::Errors::ServerError => e
  raise if e.protocol_error?
  raise resource_not_found_error(uri, e) if resource_not_found_response?(e)

  raise MCPClient::Errors::ResourceReadError, "Error reading resource '#{uri}': #{e.message}"
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)



834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
# File 'lib/mcp_client/server_sse.rb', line 834

def send_elicitation_response(request_id, result)
  # Error-shaped results become JSON-RPC error responses (e.g. -32602 for
  # an undeclared elicitation mode), mirroring the sampling error path.
  if result.is_a?(Hash) && result['error']
    send_error_response(request_id, result['error']['code'] || -32_603,
                        result['error']['message'] || 'Elicitation error')
    return
  end

  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



862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
# File 'lib/mcp_client/server_sse.rb', line 862

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



763
764
765
766
767
768
769
770
771
772
773
774
775
776
# File 'lib/mcp_client/server_sse.rb', line 763

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-11-25)

Parameters:

  • request_id (String, Integer) —

    the JSON-RPC request ID

  • result (Hash) —

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



809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
# File 'lib/mcp_client/server_sse.rb', line 809

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:



325
326
327
328
329
330
331
332
333
334
335
# File 'lib/mcp_client/server_sse.rb', line 325

def subscribe_resource(uri)
  ensure_initialized
  require_capability!('resources', 'subscribe', method: 'resources/subscribe')
  rpc_request('resources/subscribe', { uri: uri })
  true
rescue MCPClient::Errors::ConnectionError, MCPClient::Errors::TransportError, MCPClient::Errors::ServerError,
       MCPClient::Errors::CapabilityError
  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:



342
343
344
345
346
347
348
349
350
351
352
# File 'lib/mcp_client/server_sse.rb', line 342

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