Class: MCP::ServerSession
- Inherits:
-
Object
- Object
- MCP::ServerSession
- Defined in:
- lib/mcp/server_session.rb
Overview
Holds per-connection state for a single client session. Created by the transport layer; delegates request handling to the shared ‘Server`.
Instance Attribute Summary collapse
-
#client ⇒ Object
readonly
Returns the value of attribute client.
-
#logging_message_notification ⇒ Object
readonly
Returns the value of attribute logging_message_notification.
-
#session_id ⇒ Object
readonly
Returns the value of attribute session_id.
Instance Method Summary collapse
-
#cancel_incoming(request_id:, reason: nil) ⇒ Object
Flips the ‘Cancellation` for a matching in-flight request received from the peer.
-
#cancel_request(request_id:, reason: nil) ⇒ Object
Sends ‘notifications/cancelled` to the peer for a previously-issued request.
-
#client_capabilities ⇒ Object
Returns per-session client capabilities, falling back to global.
-
#configure_logging(logging_message_notification) ⇒ Object
Called by ‘Server#configure_logging_level`.
-
#create_form_elicitation(message:, requested_schema:, related_request_id: nil) ⇒ Object
Sends an ‘elicitation/create` request (form mode) scoped to this session.
-
#create_sampling_message(related_request_id: nil, **kwargs) ⇒ Object
Sends a ‘sampling/createMessage` request scoped to this session.
-
#create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil) ⇒ Object
Sends an ‘elicitation/create` request (URL mode) scoped to this session.
- #handle(request) ⇒ Object
- #handle_json(request_json) ⇒ Object
-
#initialize(server:, transport:, session_id: nil) ⇒ ServerSession
constructor
A new instance of ServerSession.
-
#initialized? ⇒ Boolean
Whether ‘initialize` has already completed for this session.
-
#list_roots(related_request_id: nil) ⇒ Object
Sends a ‘roots/list` request scoped to this session.
- #lookup_in_flight(request_id) ⇒ Object
-
#mark_initialized! ⇒ Object
Called by ‘Server#init` after a successful `initialize` response, so subsequent `initialize` requests on the same session can be rejected per MCP spec (the initialization phase MUST be the first interaction).
-
#notify_elicitation_complete(elicitation_id:) ⇒ Object
Sends an elicitation complete notification scoped to this session.
-
#notify_log_message(data:, level:, logger: nil, related_request_id: nil) ⇒ Object
Sends a log message notification to this session only.
-
#notify_progress(progress_token:, progress:, total: nil, message: nil, related_request_id: nil) ⇒ Object
Sends a progress notification to this session only.
-
#notify_resources_updated(uri:) ⇒ Object
Sends a resource updated notification to this session only.
-
#register_in_flight(request_id) ⇒ Object
Registers a ‘Cancellation` token for an in-flight request.
-
#send_peer_cancellation(nested_request_id:, related_request_id: nil, reason: nil) ⇒ Object
Sends ‘notifications/cancelled` to the peer for a nested server-to-client request that was started inside a now-cancelled parent request.
-
#store_client_info(client:, capabilities: nil) ⇒ Object
Called by ‘Server#init` during the initialization handshake.
- #unregister_in_flight(request_id) ⇒ Object
Constructor Details
#initialize(server:, transport:, session_id: nil) ⇒ ServerSession
Returns a new instance of ServerSession.
12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/mcp/server_session.rb', line 12 def initialize(server:, transport:, session_id: nil) @server = server @transport = transport @session_id = session_id @client = nil @client_capabilities = nil @logging_message_notification = nil @in_flight = {} @in_flight_mutex = Mutex.new @initialized = false end |
Instance Attribute Details
#client ⇒ Object (readonly)
Returns the value of attribute client.
10 11 12 |
# File 'lib/mcp/server_session.rb', line 10 def client @client end |
#logging_message_notification ⇒ Object (readonly)
Returns the value of attribute logging_message_notification.
10 11 12 |
# File 'lib/mcp/server_session.rb', line 10 def @logging_message_notification end |
#session_id ⇒ Object (readonly)
Returns the value of attribute session_id.
10 11 12 |
# File 'lib/mcp/server_session.rb', line 10 def session_id @session_id end |
Instance Method Details
#cancel_incoming(request_id:, reason: nil) ⇒ Object
Flips the ‘Cancellation` for a matching in-flight request received from the peer. Silently ignores unknown IDs per MCP spec (cancellation utilities, item 5).
57 58 59 60 |
# File 'lib/mcp/server_session.rb', line 57 def cancel_incoming(request_id:, reason: nil) cancellation = lookup_in_flight(request_id) cancellation&.cancel(reason: reason) end |
#cancel_request(request_id:, reason: nil) ⇒ Object
Sends ‘notifications/cancelled` to the peer for a previously-issued request. Also unblocks any transport-level `send_request` waiting on a response for `request_id`.
64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/mcp/server_session.rb', line 64 def cancel_request(request_id:, reason: nil) params = { requestId: request_id } params[:reason] = reason if reason send_to_transport(Methods::NOTIFICATIONS_CANCELLED, params) if @transport.respond_to?(:cancel_pending_request) @transport.cancel_pending_request(request_id, reason: reason) end rescue => e MCP.configuration.exception_reporter.call(e, { notification: "cancelled", request_id: request_id }) end |
#client_capabilities ⇒ Object
Returns per-session client capabilities, falling back to global.
96 97 98 |
# File 'lib/mcp/server_session.rb', line 96 def client_capabilities @client_capabilities || @server.client_capabilities end |
#configure_logging(logging_message_notification) ⇒ Object
Called by ‘Server#configure_logging_level`.
91 92 93 |
# File 'lib/mcp/server_session.rb', line 91 def configure_logging() @logging_message_notification = end |
#create_form_elicitation(message:, requested_schema:, related_request_id: nil) ⇒ Object
Sends an ‘elicitation/create` request (form mode) scoped to this session.
116 117 118 119 120 121 122 123 124 |
# File 'lib/mcp/server_session.rb', line 116 def create_form_elicitation(message:, requested_schema:, related_request_id: nil) unless client_capabilities&.dig(:elicitation) raise "Client does not support elicitation. " \ "The client must declare the `elicitation` capability during initialization." end params = { mode: "form", message: , requestedSchema: requested_schema } send_to_transport_request(Methods::ELICITATION_CREATE, params, related_request_id: ) end |
#create_sampling_message(related_request_id: nil, **kwargs) ⇒ Object
Sends a ‘sampling/createMessage` request scoped to this session.
110 111 112 113 |
# File 'lib/mcp/server_session.rb', line 110 def (related_request_id: nil, **kwargs) params = @server.build_sampling_params(client_capabilities, **kwargs) send_to_transport_request(Methods::SAMPLING_CREATE_MESSAGE, params, related_request_id: ) end |
#create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil) ⇒ Object
Sends an ‘elicitation/create` request (URL mode) scoped to this session.
127 128 129 130 131 132 133 134 135 |
# File 'lib/mcp/server_session.rb', line 127 def create_url_elicitation(message:, url:, elicitation_id:, related_request_id: nil) unless client_capabilities&.dig(:elicitation, :url) raise "Client does not support URL mode elicitation. " \ "The client must declare the `elicitation.url` capability during initialization." end params = { mode: "url", message: , url: url, elicitationId: elicitation_id } send_to_transport_request(Methods::ELICITATION_CREATE, params, related_request_id: ) end |
#handle(request) ⇒ Object
76 77 78 |
# File 'lib/mcp/server_session.rb', line 76 def handle(request) @server.handle(request, session: self) end |
#handle_json(request_json) ⇒ Object
80 81 82 |
# File 'lib/mcp/server_session.rb', line 80 def handle_json(request_json) @server.handle_json(request_json, session: self) end |
#initialized? ⇒ Boolean
Whether ‘initialize` has already completed for this session.
25 26 27 |
# File 'lib/mcp/server_session.rb', line 25 def initialized? @initialized end |
#list_roots(related_request_id: nil) ⇒ Object
Sends a ‘roots/list` request scoped to this session.
101 102 103 104 105 106 107 |
# File 'lib/mcp/server_session.rb', line 101 def list_roots(related_request_id: nil) unless client_capabilities&.dig(:roots) raise "Client does not support roots." end send_to_transport_request(Methods::ROOTS_LIST, nil, related_request_id: ) end |
#lookup_in_flight(request_id) ⇒ Object
51 52 53 |
# File 'lib/mcp/server_session.rb', line 51 def lookup_in_flight(request_id) @in_flight_mutex.synchronize { @in_flight[request_id] } end |
#mark_initialized! ⇒ Object
Called by ‘Server#init` after a successful `initialize` response, so subsequent `initialize` requests on the same session can be rejected per MCP spec (the initialization phase MUST be the first interaction).
32 33 34 |
# File 'lib/mcp/server_session.rb', line 32 def mark_initialized! @initialized = true end |
#notify_elicitation_complete(elicitation_id:) ⇒ Object
Sends an elicitation complete notification scoped to this session.
155 156 157 158 159 |
# File 'lib/mcp/server_session.rb', line 155 def notify_elicitation_complete(elicitation_id:) send_to_transport(Methods::NOTIFICATIONS_ELICITATION_COMPLETE, { elicitationId: elicitation_id }) rescue => e @server.report_exception(e, notification: "elicitation_complete") end |
#notify_log_message(data:, level:, logger: nil, related_request_id: nil) ⇒ Object
Sends a log message notification to this session only.
183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/mcp/server_session.rb', line 183 def (data:, level:, logger: nil, related_request_id: nil) effective_logging = @logging_message_notification || @server. return unless effective_logging&.should_notify?(level) params = { "data" => data, "level" => level } params["logger"] = logger if logger send_to_transport(Methods::NOTIFICATIONS_MESSAGE, params, related_request_id: ) rescue => e @server.report_exception(e, { notification: "log_message" }) end |
#notify_progress(progress_token:, progress:, total: nil, message: nil, related_request_id: nil) ⇒ Object
Sends a progress notification to this session only.
169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/mcp/server_session.rb', line 169 def notify_progress(progress_token:, progress:, total: nil, message: nil, related_request_id: nil) params = { "progressToken" => progress_token, "progress" => progress, "total" => total, "message" => , }.compact send_to_transport(Methods::NOTIFICATIONS_PROGRESS, params, related_request_id: ) rescue => e @server.report_exception(e, notification: "progress") end |
#notify_resources_updated(uri:) ⇒ Object
Sends a resource updated notification to this session only.
162 163 164 165 166 |
# File 'lib/mcp/server_session.rb', line 162 def notify_resources_updated(uri:) send_to_transport(Methods::NOTIFICATIONS_RESOURCES_UPDATED, { "uri" => uri }) rescue => e @server.report_exception(e, notification: "resources_updated") end |
#register_in_flight(request_id) ⇒ Object
Registers a ‘Cancellation` token for an in-flight request.
37 38 39 40 41 42 43 |
# File 'lib/mcp/server_session.rb', line 37 def register_in_flight(request_id) return if request_id.nil? cancellation = Cancellation.new(request_id: request_id) @in_flight_mutex.synchronize { @in_flight[request_id] = cancellation } cancellation end |
#send_peer_cancellation(nested_request_id:, related_request_id: nil, reason: nil) ⇒ Object
Sends ‘notifications/cancelled` to the peer for a nested server-to-client request that was started inside a now-cancelled parent request. `related_request_id` is the parent request id so the notification is routed to the same stream (e.g. the parent’s POST response stream on ‘StreamableHTTPTransport`) rather than the GET SSE stream.
142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/mcp/server_session.rb', line 142 def send_peer_cancellation(nested_request_id:, related_request_id: nil, reason: nil) params = { requestId: nested_request_id } params[:reason] = reason if reason send_to_transport(Methods::NOTIFICATIONS_CANCELLED, params, related_request_id: ) if @transport.respond_to?(:cancel_pending_request) @transport.cancel_pending_request(nested_request_id, reason: reason) end rescue => e MCP.configuration.exception_reporter.call(e, { notification: "cancelled", request_id: nested_request_id }) end |
#store_client_info(client:, capabilities: nil) ⇒ Object
Called by ‘Server#init` during the initialization handshake.
85 86 87 88 |
# File 'lib/mcp/server_session.rb', line 85 def store_client_info(client:, capabilities: nil) @client = client @client_capabilities = capabilities end |
#unregister_in_flight(request_id) ⇒ Object
45 46 47 48 49 |
# File 'lib/mcp/server_session.rb', line 45 def unregister_in_flight(request_id) return if request_id.nil? @in_flight_mutex.synchronize { @in_flight.delete(request_id) } end |