Class: VectorMCP::Session

Inherits:
Object
  • Object
show all
Defined in:
lib/vector_mcp/session.rb

Overview

Represents the state of a single client-server connection session in MCP. It tracks initialization status, and negotiated capabilities between the client and server.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(server, transport = nil, id: SecureRandom.uuid, request_context: nil) ⇒ Session

Initializes a new session.

Parameters:

  • server (VectorMCP::Server)

    The server instance managing this session.

  • transport (VectorMCP::Transport::Base, nil) (defaults to: nil)

    The transport handling this session. Required for sampling.

  • id (String) (defaults to: SecureRandom.uuid)

    A unique identifier for this session (e.g., from transport layer).

  • request_context (RequestContext, Hash, nil) (defaults to: nil)

    The request context for this session.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/vector_mcp/session.rb', line 28

def initialize(server, transport = nil, id: SecureRandom.uuid, request_context: nil)
  @server = server
  @transport = transport # Store the transport for sending requests
  @id = id
  @initialized_state = :pending # :pending, :succeeded, :failed
  @client_info = nil
  @client_capabilities = nil
  @data = {} # Initialize user data hash
  @logger = server.logger

  # Initialize request context
  @request_context = case request_context
                     when RequestContext
                       request_context
                     when Hash
                       RequestContext.new(**request_context)
                     else
                       RequestContext.new
                     end
end

Instance Attribute Details

#client_capabilitiesHash? (readonly)

Capabilities supported by the client, received during initialization.

Returns:

  • (Hash, nil)

    the current value of client_capabilities



18
19
20
# File 'lib/vector_mcp/session.rb', line 18

def client_capabilities
  @client_capabilities
end

#client_infoHash? (readonly)

Information about the client, received during initialization.

Returns:

  • (Hash, nil)

    the current value of client_info



18
19
20
# File 'lib/vector_mcp/session.rb', line 18

def client_info
  @client_info
end

#dataObject

For user-defined session-specific storage



20
21
22
# File 'lib/vector_mcp/session.rb', line 20

def data
  @data
end

#idObject (readonly)

Returns the value of attribute id.



19
20
21
# File 'lib/vector_mcp/session.rb', line 19

def id
  @id
end

#protocol_versionString (readonly)

The MCP protocol version used by the server.

Returns:

  • (String)

    the current value of protocol_version



18
19
20
# File 'lib/vector_mcp/session.rb', line 18

def protocol_version
  @protocol_version
end

#request_contextRequestContext

The request context for this session.

Returns:



18
19
20
# File 'lib/vector_mcp/session.rb', line 18

def request_context
  @request_context
end

#serverObject (readonly)

Returns the value of attribute server.



19
20
21
# File 'lib/vector_mcp/session.rb', line 19

def server
  @server
end

#server_capabilitiesHash (readonly)

Capabilities supported by the server.

Returns:

  • (Hash)

    the current value of server_capabilities



18
19
20
# File 'lib/vector_mcp/session.rb', line 18

def server_capabilities
  @server_capabilities
end

#server_infoHash (readonly)

Information about the server.

Returns:

  • (Hash)

    the current value of server_info



18
19
20
# File 'lib/vector_mcp/session.rb', line 18

def server_info
  @server_info
end

#transportObject (readonly)

Returns the value of attribute transport.



19
20
21
# File 'lib/vector_mcp/session.rb', line 19

def transport
  @transport
end

Instance Method Details

#initialize!(params) ⇒ Hash

Marks the session as initialized using parameters from the client’s ‘initialize` request.

Parameters:

  • params (Hash)

    The parameters from the client’s ‘initialize` request. Expected keys include “protocolVersion”, “clientInfo”, and “capabilities”.

Returns:

  • (Hash)

    A hash suitable for the server’s ‘initialize` response result.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/vector_mcp/session.rb', line 54

def initialize!(params)
  raise InitializationError, "Session already initialized or initialization attempt in progress." unless @initialized_state == :pending

  # TODO: More robust validation of params against MCP spec for initialize request
  params["protocolVersion"]
  client_capabilities_raw = params["capabilities"]
  client_info_raw = params["clientInfo"]

  # For now, we mostly care about clientInfo and capabilities for the session object.
  # Protocol version matching is more of a server/transport concern at a lower level if strict checks are needed.
  @client_info = client_info_raw.transform_keys(&:to_sym) if client_info_raw.is_a?(Hash)
  @client_capabilities = client_capabilities_raw.transform_keys(&:to_sym) if client_capabilities_raw.is_a?(Hash)

  @initialized_state = :succeeded
  @logger.info("[Session #{@id}] Initialized successfully. Client: #{@client_info&.dig(:name)}")

  {
    protocolVersion: @server.protocol_version,
    serverInfo: @server.server_info,
    capabilities: @server.server_capabilities
  }
rescue StandardError => e
  @initialized_state = :failed
  @logger.error("[Session #{@id}] Initialization failed: #{e.message}")
  # Re-raise as an InitializationError if it's not already one of our ProtocolErrors
  raise e if e.is_a?(ProtocolError)

  raise InitializationError, "Initialization processing error: #{e.message}", details: { original_error: e.to_s }
end

#initialized?Boolean

Checks if the session has been successfully initialized.

Returns:

  • (Boolean)

    True if the session is initialized, false otherwise.



87
88
89
# File 'lib/vector_mcp/session.rb', line 87

def initialized?
  @initialized_state == :succeeded
end

#request_header(name) ⇒ String?

Convenience method to get a request header value.

Parameters:

  • name (String)

    The header name.

Returns:

  • (String, nil)

    The header value or nil if not found.



148
149
150
# File 'lib/vector_mcp/session.rb', line 148

def request_header(name)
  @request_context.header(name)
end

#request_headers?Boolean

Convenience method to check if the session has request headers.

Returns:

  • (Boolean)

    True if the request context has headers, false otherwise.



133
134
135
# File 'lib/vector_mcp/session.rb', line 133

def request_headers?
  @request_context.headers?
end

#request_param(name) ⇒ String?

Convenience method to get a request parameter value.

Parameters:

  • name (String)

    The parameter name.

Returns:

  • (String, nil)

    The parameter value or nil if not found.



156
157
158
# File 'lib/vector_mcp/session.rb', line 156

def request_param(name)
  @request_context.param(name)
end

#request_params?Boolean

Convenience method to check if the session has request parameters.

Returns:

  • (Boolean)

    True if the request context has parameters, false otherwise.



140
141
142
# File 'lib/vector_mcp/session.rb', line 140

def request_params?
  @request_context.params?
end

#sample(request_params, timeout: nil) ⇒ VectorMCP::Sampling::Result

Initiates an MCP sampling request to the client associated with this session. This is a blocking call that waits for the client’s response.

Parameters:

  • request_params (Hash)

    Parameters for the ‘sampling/createMessage` request. See `VectorMCP::Sampling::Request` for expected structure (e.g., :messages, :max_tokens).

  • timeout (Numeric, nil) (defaults to: nil)

    Optional timeout in seconds for this specific request. Defaults to the transport’s default request timeout.

Returns:

Raises:

  • (VectorMCP::SamplingError)

    if the sampling request fails, is rejected, or times out.

  • (StandardError)

    if the session’s transport does not support ‘send_request`.



177
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
208
209
210
211
212
213
214
215
216
217
# File 'lib/vector_mcp/session.rb', line 177

def sample(request_params, timeout: nil)
  validate_sampling_preconditions

  # Create middleware context for sampling
  context = VectorMCP::Middleware::Context.new(
    operation_type: :sampling,
    operation_name: "createMessage",
    params: request_params,
    session: self,
    server: @server,
    metadata: { start_time: Time.now, timeout: timeout }
  )

  # Execute before_sampling_request hooks
  context = @server.middleware_manager.execute_hooks(:before_sampling_request, context)
  raise context.error if context.error?

  begin
    sampling_req_obj = VectorMCP::Sampling::Request.new(request_params)
    @logger.debug("[Session #{@id}] Sending sampling/createMessage request to client.")

    result = send_sampling_request(sampling_req_obj, timeout)

    # Set result in context
    context.result = result

    # Execute after_sampling_response hooks
    context = @server.middleware_manager.execute_hooks(:after_sampling_response, context)

    context.result
  rescue StandardError => e
    # Set error in context and execute error hooks
    context.error = e
    context = @server.middleware_manager.execute_hooks(:on_sampling_error, context)

    # Re-raise unless middleware handled the error
    raise e unless context.result

    context.result
  end
end

#update_request_context(**attributes) ⇒ RequestContext

Updates the request context with new data. This merges the provided attributes with the existing context.

Parameters:

  • attributes (Hash)

    The attributes to merge into the request context.

Returns:



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/vector_mcp/session.rb', line 114

def update_request_context(**attributes)
  current_attrs = @request_context.to_h

  # Deep merge nested hashes like headers and params
  merged_attrs = current_attrs.dup
  attributes.each do |key, value|
    merged_attrs[key] = if value.is_a?(Hash) && current_attrs[key].is_a?(Hash)
                          current_attrs[key].merge(value)
                        else
                          value
                        end
  end

  @request_context = RequestContext.new(**merged_attrs)
end