Module: MCPClient::HttpTransportBase
- Includes:
- JsonRpcCommon
- Defined in:
- lib/mcp_client/http_transport_base.rb
Overview
Base module for HTTP-based JSON-RPC transports Contains common functionality shared between HTTP and Streamable HTTP transports
Defined Under Namespace
Classes: NormalizedResponse
Constant Summary collapse
- AUTH_PARAM =
One auth-param (name = token / quoted-string) as it appears in a WWW-Authenticate challenge (RFC 7235 §2.1, optional whitespace around '=').
/[A-Za-z0-9._~+-]+\s*=\s*(?:"(?:[^"\\]|\\.)*"|[^,\s]*)/- AUTH_PARAMS_RUN =
A run of comma/space separated auth-params anchored at the start of a string. The run ends before a token that is NOT followed by '=' — the auth-scheme introducing the next challenge — while commas inside quoted values are consumed by the quoted-string branch, not treated as boundaries.
/\A(?:[\s,]*#{AUTH_PARAM})*/
Instance Method Summary collapse
-
#rpc_notify(method, params = {}) ⇒ void
Send a JSON-RPC notification (no response expected).
-
#rpc_request(method, params = {}, timeout: nil) ⇒ Object
Generic JSON-RPC request: send method with params and return result.
-
#send_cancellation_notification(request_id) ⇒ void
Best-effort notifications/cancelled for a request the client stopped waiting on.
-
#terminate_session ⇒ Boolean
Terminate the current session with the server Sends an HTTP DELETE request with the session ID to properly close the session.
-
#valid_server_url?(url) ⇒ Boolean
Validate the server's base URL for security.
-
#valid_session_id?(session_id) ⇒ Boolean
Validate session ID format Per MCP 2025-11-25, the server-assigned session ID "MUST only contain visible ASCII characters (ranging from 0x21 to 0x7E)" — e.g.
Methods included from JsonRpcCommon
#build_jsonrpc_notification, #build_jsonrpc_request, #build_named_request_params, #cancellable_request?, #client_capabilities, #client_info_payload, #declare_sampling_tools, #initialization_params, #ping, #process_jsonrpc_response, #registered_callback?, #sampling_tools_supported?, #split_request_meta, #validate_protocol_version!, #with_retry
Instance Method Details
#rpc_notify(method, params = {}) ⇒ void
This method returns an undefined value.
Send a JSON-RPC notification (no response expected)
66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/mcp_client/http_transport_base.rb', line 66 def rpc_notify(method, params = {}) ensure_connected notif = build_jsonrpc_notification(method, params) begin send_http_request(notif) rescue MCPClient::Errors::ServerError, MCPClient::Errors::ConnectionError, Faraday::ConnectionFailed => e raise MCPClient::Errors::TransportError, "Failed to send notification: #{e.}" end end |
#rpc_request(method, params = {}, timeout: nil) ⇒ Object
Generic JSON-RPC request: send method with params and return result
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/mcp_client/http_transport_base.rb', line 33 def rpc_request(method, params = {}, timeout: nil) ensure_connected with_retry do request_id = @mutex.synchronize { @request_id += 1 } request = build_jsonrpc_request(method, params, request_id) begin send_jsonrpc_request(request, timeout: timeout) rescue MCPClient::Errors::RequestTimeoutError # MCP lifecycle: on timeout the sender SHOULD issue a cancellation # notification for the abandoned request and stop waiting. send_cancellation_notification(request_id) if cancellable_request?(method, params) raise end end end |
#send_cancellation_notification(request_id) ⇒ void
This method returns an undefined value.
Best-effort notifications/cancelled for a request the client stopped waiting on. Failures are swallowed.
54 55 56 57 58 59 60 |
# File 'lib/mcp_client/http_transport_base.rb', line 54 def send_cancellation_notification(request_id) notif = build_jsonrpc_notification('notifications/cancelled', { 'requestId' => request_id, 'reason' => 'Request timed out' }) send_http_request(notif) rescue StandardError => e @logger.debug("Failed to send cancellation notification: #{e.}") end |
#terminate_session ⇒ Boolean
Terminate the current session with the server Sends an HTTP DELETE request with the session ID to properly close the session
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 111 112 113 |
# File 'lib/mcp_client/http_transport_base.rb', line 82 def terminate_session return true unless @session_id conn = http_connection begin @logger.debug("Terminating session: #{@session_id}") response = conn.delete(@endpoint) do |req| # Apply base headers but prioritize session termination headers @headers.each { |k, v| req.headers[k] = v } req.headers['Mcp-Session-Id'] = @session_id req.headers['Mcp-Protocol-Version'] = @protocol_version if @protocol_version # MCP: authorization MUST be included in every HTTP request @oauth_provider&.(req) end if response.success? @logger.debug("Session terminated successfully: #{@session_id}") @session_id = nil true else @logger.warn("Session termination failed with HTTP #{response.status}") @session_id = nil # Clear session ID even on HTTP error false end rescue Faraday::Error => e @logger.warn("Session termination request failed: #{e.}") # Clear session ID even if termination request failed @session_id = nil false end end |
#valid_server_url?(url) ⇒ Boolean
Validate the server's base URL for security
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/mcp_client/http_transport_base.rb', line 133 def valid_server_url?(url) return false unless url.is_a?(String) uri = URI.parse(url) # Only allow HTTP and HTTPS protocols return false unless %w[http https].include?(uri.scheme) # Must have a host return false if uri.host.nil? || uri.host.empty? # Don't allow localhost binding to all interfaces in production if uri.host == '0.0.0.0' @logger.warn('Server URL uses 0.0.0.0 which may be insecure. Consider using 127.0.0.1 for localhost.') end true rescue URI::InvalidURIError false end |
#valid_session_id?(session_id) ⇒ Boolean
Validate session ID format Per MCP 2025-11-25, the server-assigned session ID "MUST only contain visible ASCII characters (ranging from 0x21 to 0x7E)" — e.g. a UUID, a JWT, or a cryptographic hash — and the client MUST echo whatever the server assigned. A generous length cap guards against abuse.
122 123 124 125 126 127 128 |
# File 'lib/mcp_client/http_transport_base.rb', line 122 def valid_session_id?(session_id) return false unless session_id.is_a?(String) # The 4096-char cap is header-size hygiene, not MCP grammar — the spec # imposes no length limit on session IDs. session_id.match?(/\A[\x21-\x7E]{1,4096}\z/) end |