Class: ActionMCP::Server::BaseSession

Inherits:
Object
  • Object
show all
Defined in:
lib/action_mcp/server/base_session.rb

Overview

Base session object that mimics ActiveRecord Session with common functionality

Defined Under Namespace

Classes: MessageCollection, SubscriptionCollection

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}, store = nil) ⇒ BaseSession

Returns a new instance of BaseSession.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/action_mcp/server/base_session.rb', line 14

def initialize(attributes = {}, store = nil)
  @store = store
  @messages = Concurrent::Array.new
  @subscriptions = Concurrent::Array.new
  @message_counter = Concurrent::AtomicFixnum.new(0)
  @new_record = true

  # Initialize consents and session_data as empty hashes if not provided
  @consents = {}
  @session_data = {}

  attributes.each do |key, value|
    send("#{key}=", value) if respond_to?("#{key}=")
  end
end

Instance Attribute Details

#client_capabilitiesObject

Returns the value of attribute client_capabilities.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def client_capabilities
  @client_capabilities
end

#client_infoObject

Returns the value of attribute client_info.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def client_info
  @client_info
end

#consentsObject

Returns the value of attribute consents.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def consents
  @consents
end

#created_atObject

Returns the value of attribute created_at.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def created_at
  @created_at
end

#ended_atObject

Returns the value of attribute ended_at.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def ended_at
  @ended_at
end

#idObject

Returns the value of attribute id.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def id
  @id
end

#initializedObject

Returns the value of attribute initialized.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def initialized
  @initialized
end

#last_event_idObject

Returns the value of attribute last_event_id.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def last_event_id
  @last_event_id
end

#messages_countObject

Returns the value of attribute messages_count.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def messages_count
  @messages_count
end

#prompt_registryObject

Returns the value of attribute prompt_registry.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def prompt_registry
  @prompt_registry
end

#protocol_versionObject

Returns the value of attribute protocol_version.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def protocol_version
  @protocol_version
end

#resource_registryObject

Returns the value of attribute resource_registry.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def resource_registry
  @resource_registry
end

#roleObject

Returns the value of attribute role.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def role
  @role
end

#server_capabilitiesObject

Returns the value of attribute server_capabilities.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def server_capabilities
  @server_capabilities
end

#server_infoObject

Returns the value of attribute server_info.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def server_info
  @server_info
end

#session_dataObject

Returns the value of attribute session_data.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def session_data
  @session_data
end

#statusObject

Returns the value of attribute status.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def status
  @status
end

#tool_registryObject

Returns the value of attribute tool_registry.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def tool_registry
  @tool_registry
end

#updated_atObject

Returns the value of attribute updated_at.



7
8
9
# File 'lib/action_mcp/server/base_session.rb', line 7

def updated_at
  @updated_at
end

Instance Method Details

#begin_initialization!Object



73
74
75
76
77
78
# File 'lib/action_mcp/server/base_session.rb', line 73

def begin_initialization!
  return false unless status == "pre_initialize" && !initialized?

  self.status = "initializing"
  save
end

#cancel_in_flight_request(request_id) ⇒ Object



111
112
113
114
115
116
117
118
119
120
# File 'lib/action_mcp/server/base_session.rb', line 111

def cancel_in_flight_request(request_id)
  return if request_id.nil?

  request = received_requests_with_id(request_id).find do |message|
    !message[:request_acknowledged] && !message[:request_cancelled]
  end
  return if request && message_payload(request)[:method] == JsonRpcHandlerBase::Methods::INITIALIZE

  request&.tap { |message| message[:request_cancelled] = true }
end

#client_request_for_progress(progress_token) ⇒ Object



122
123
124
125
126
127
128
129
# File 'lib/action_mcp/server/base_session.rb', line 122

def client_request_for_progress(progress_token)
  return if progress_token.nil?

  issued_client_requests.find do |request|
    message_payload(request).dig(:params, :_meta, :progressToken) == progress_token &&
      client_request_accepts_progress?(request)
  end
end

#client_request_for_task(task_id) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/action_mcp/server/base_session.rb', line 131

def client_request_for_task(task_id)
  return unless task_id.is_a?(String) && task_id.present?

  response = @messages.reverse_each.find do |message|
    message[:direction] == role &&
      message_payload(message).dig(:result, :task, :taskId) == task_id
  end
  return unless response

  response_id = message_payload(response)[:id]
  issued_client_requests.find do |request|
    payload = message_payload(request)
    payload[:id] == response_id && payload.dig(:params, :task).is_a?(Hash)
  end
end

#close!Object



88
89
90
91
92
# File 'lib/action_mcp/server/base_session.rb', line 88

def close!
  self.status = "closed"
  self.ended_at = Time.current
  save
end

Consent management methods

Returns:

  • (Boolean)


294
295
296
297
298
# File 'lib/action_mcp/server/base_session.rb', line 294

def consent_granted_for?(key)
  consents_hash = consents.is_a?(String) ? JSON.parse(consents) : consents
  consents_hash ||= {}
  consents_hash[key] == true
end

#destroyObject



61
62
63
# File 'lib/action_mcp/server/base_session.rb', line 61

def destroy
  @store&.delete_session(id)
end


300
301
302
303
304
305
306
# File 'lib/action_mcp/server/base_session.rb', line 300

def grant_consent(key)
  consents_hash = consents.is_a?(String) ? JSON.parse(consents) : consents
  consents_hash ||= {}
  consents_hash[key] = true
  self.consents = consents_hash
  save!
end

#initialize!Object



80
81
82
83
84
85
86
# File 'lib/action_mcp/server/base_session.rb', line 80

def initialize!
  return false unless status == "initializing" && !initialized?

  self.initialized = true
  self.status = "initialized"
  save
end

#initialized?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/action_mcp/server/base_session.rb', line 69

def initialized?
  initialized
end

#messagesObject



103
104
105
# File 'lib/action_mcp/server/base_session.rb', line 103

def messages
  MessageCollection.new(@messages)
end

#new_record?Boolean

ActiveRecord-like interface

Returns:

  • (Boolean)


31
32
33
# File 'lib/action_mcp/server/base_session.rb', line 31

def new_record?
  @new_record
end

#persisted?Boolean

Returns:

  • (Boolean)


35
36
37
# File 'lib/action_mcp/server/base_session.rb', line 35

def persisted?
  !@new_record
end

#read(data) ⇒ Object



99
100
101
# File 'lib/action_mcp/server/base_session.rb', line 99

def read(data)
  record_message(data, role)
end

#register_prompt(prompt_class_or_name) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/action_mcp/server/base_session.rb', line 223

def register_prompt(prompt_class_or_name)
  prompt_name = normalize_name(prompt_class_or_name, :prompt)
  return false unless prompt_exists?(prompt_name)

  self.prompt_registry ||= []
  unless self.prompt_registry.include?(prompt_name)
    self.prompt_registry << prompt_name
    save!
    send_prompts_list_changed_notification
  end
  true
end

#register_resource_template(template_class_or_name) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/action_mcp/server/base_session.rb', line 246

def register_resource_template(template_class_or_name)
  template_name = normalize_name(template_class_or_name, :resource_template)
  return false unless resource_template_exists?(template_name)

  self.resource_registry ||= []
  unless self.resource_registry.include?(template_name)
    self.resource_registry << template_name
    save!
    send_resources_list_changed_notification
  end
  true
end

#register_tool(tool_class_or_name) ⇒ Object

Registry management methods



200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/action_mcp/server/base_session.rb', line 200

def register_tool(tool_class_or_name)
  tool_name = normalize_name(tool_class_or_name, :tool)
  return false unless tool_exists?(tool_name)

  self.tool_registry ||= []
  unless self.tool_registry.include?(tool_name)
    self.tool_registry << tool_name
    save!
    send_tools_list_changed_notification
  end
  true
end

#registered_promptsObject



277
278
279
280
281
282
283
# File 'lib/action_mcp/server/base_session.rb', line 277

def registered_prompts
  (self.prompt_registry || []).filter_map do |prompt_name|
    ActionMCP::PromptsRegistry.find(prompt_name)
  rescue StandardError
    nil
  end
end

#registered_resource_templatesObject



285
286
287
288
289
290
291
# File 'lib/action_mcp/server/base_session.rb', line 285

def registered_resource_templates
  (self.resource_registry || []).filter_map do |template_name|
    ActionMCP::ResourceTemplatesRegistry.find(template_name)
  rescue StandardError
    nil
  end
end

#registered_toolsObject



269
270
271
272
273
274
275
# File 'lib/action_mcp/server/base_session.rb', line 269

def registered_tools
  (self.tool_registry || []).filter_map do |tool_name|
    ActionMCP::ToolsRegistry.find(tool_name)
  rescue StandardError
    nil
  end
end

#reloadObject



65
66
67
# File 'lib/action_mcp/server/base_session.rb', line 65

def reload
  self
end

#resource_subscribe(uri) ⇒ Object

Subscription management



174
175
176
177
178
# File 'lib/action_mcp/server/base_session.rb', line 174

def resource_subscribe(uri)
  return if @subscriptions.any? { |s| s[:uri] == uri }

  @subscriptions << { uri: uri, created_at: Time.current }
end

#resource_subscribed?(uri) ⇒ Boolean

Returns:

  • (Boolean)


184
185
186
# File 'lib/action_mcp/server/base_session.rb', line 184

def resource_subscribed?(uri)
  @subscriptions.any? { |subscription| subscription[:uri] == uri }
end

#resource_unsubscribe(uri) ⇒ Object



180
181
182
# File 'lib/action_mcp/server/base_session.rb', line 180

def resource_unsubscribe(uri)
  @subscriptions.delete_if { |s| s[:uri] == uri }
end


308
309
310
311
312
313
314
# File 'lib/action_mcp/server/base_session.rb', line 308

def revoke_consent(key)
  consents_hash = consents.is_a?(String) ? JSON.parse(consents) : consents
  consents_hash ||= {}
  consents_hash.delete(key)
  self.consents = consents_hash
  save!
end

#saveObject



39
40
41
42
43
44
# File 'lib/action_mcp/server/base_session.rb', line 39

def save
  self.updated_at = Time.current
  @store&.save_session(self)
  @new_record = false
  true
end

#save!Object



46
47
48
# File 'lib/action_mcp/server/base_session.rb', line 46

def save!
  save
end

#send_progress_notification(progressToken:, progress:, total: nil, message: nil) ⇒ Object

Progress notification



189
190
191
192
193
194
195
196
197
# File 'lib/action_mcp/server/base_session.rb', line 189

def send_progress_notification(progressToken:, progress:, total: nil, message: nil)
  handler = ActionMCP::Server::TransportHandler.new(self)
  handler.send_progress_notification(
    progressToken: progressToken,
    progress: progress,
    total: total,
    message: message
  )
end

#server_capabilities_payloadObject

Capability methods



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/action_mcp/server/base_session.rb', line 148

def server_capabilities_payload
  payload = {
    protocolVersion: ActionMCP::LATEST_VERSION,
    serverInfo: server_info,
    capabilities: capabilities_for_protocol(server_capabilities)
  }
  # Add instructions at top level if configured
  instructions = ActionMCP.configuration.instructions
  payload[:instructions] = instructions if instructions
  payload
end

#set_protocol_version(version) ⇒ Object



160
161
162
163
# File 'lib/action_mcp/server/base_session.rb', line 160

def set_protocol_version(version)
  self.protocol_version = version
  save
end

#store_client_capabilities(capabilities) ⇒ Object



169
170
171
# File 'lib/action_mcp/server/base_session.rb', line 169

def store_client_capabilities(capabilities)
  self.client_capabilities = capabilities
end

#store_client_info(info) ⇒ Object



165
166
167
# File 'lib/action_mcp/server/base_session.rb', line 165

def store_client_info(info)
  self.client_info = info
end

#subscriptionsObject



107
108
109
# File 'lib/action_mcp/server/base_session.rb', line 107

def subscriptions
  SubscriptionCollection.new(@subscriptions)
end

#unregister_prompt(prompt_class_or_name) ⇒ Object



236
237
238
239
240
241
242
243
244
# File 'lib/action_mcp/server/base_session.rb', line 236

def unregister_prompt(prompt_class_or_name)
  prompt_name = normalize_name(prompt_class_or_name, :prompt)
  self.prompt_registry ||= []

  return unless self.prompt_registry.delete(prompt_name)

  save!
  send_prompts_list_changed_notification
end

#unregister_resource_template(template_class_or_name) ⇒ Object



259
260
261
262
263
264
265
266
267
# File 'lib/action_mcp/server/base_session.rb', line 259

def unregister_resource_template(template_class_or_name)
  template_name = normalize_name(template_class_or_name, :resource_template)
  self.resource_registry ||= []

  return unless self.resource_registry.delete(template_name)

  save!
  send_resources_list_changed_notification
end

#unregister_tool(tool_class_or_name) ⇒ Object



213
214
215
216
217
218
219
220
221
# File 'lib/action_mcp/server/base_session.rb', line 213

def unregister_tool(tool_class_or_name)
  tool_name = normalize_name(tool_class_or_name, :tool)
  self.tool_registry ||= []

  return unless self.tool_registry.delete(tool_name)

  save!
  send_tools_list_changed_notification
end

#update(attributes) ⇒ Object



50
51
52
53
54
55
# File 'lib/action_mcp/server/base_session.rb', line 50

def update(attributes)
  attributes.each do |key, value|
    send("#{key}=", value) if respond_to?("#{key}=")
  end
  save
end

#update!(attributes) ⇒ Object



57
58
59
# File 'lib/action_mcp/server/base_session.rb', line 57

def update!(attributes)
  update(attributes)
end

#write(data) ⇒ Object

Message management



95
96
97
# File 'lib/action_mcp/server/base_session.rb', line 95

def write(data)
  record_message(data, writer_role)
end