Class: CompletionKit::McpSession
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- CompletionKit::McpSession
- Defined in:
- app/models/completion_kit/mcp_session.rb
Overview
MCP session marker — one row per active client session, kept in the database so sessions survive Puma restarts, deploys, and Rails.cache eviction.
Two things to know about why every query goes through ‘unscoped`:
-
CompletionKit::ApplicationRecord applies the host app’s tenant_scope as a default_scope. MCP sessions are per-CONNECTION, not per-tenant — the table has no organization_id column. Letting the tenant_scope into a session lookup turns “is this session live?” into either a SQL error (no such column) or a false negative (‘WHERE 1=0`), which surfaces to the client as a spurious “Session not initialized” right after init.
-
‘active?` slides expires_at forward when a session is more than halfway through its TTL, so an MCP connection that keeps making calls stays alive instead of expiring on the original 1-hour wall clock.
Expired rows are opportunistically pruned on every new session start, so the table stays bounded by recent activity.
Constant Summary collapse
- SESSION_TTL =
1.hour
Constants inherited from ApplicationRecord
ApplicationRecord::TenantScopedUniquenessValidator
Class Method Summary collapse
- .active?(session_id) ⇒ Boolean
- .destroy_session(session_id) ⇒ Object
- .prune_expired! ⇒ Object
- .slide_expiry(row) ⇒ Object
- .start! ⇒ Object
Class Method Details
.active?(session_id) ⇒ Boolean
30 31 32 33 34 35 36 37 38 |
# File 'app/models/completion_kit/mcp_session.rb', line 30 def self.active?(session_id) return false if session_id.blank? row = unscoped.where(session_id: session_id).where("expires_at > ?", Time.current).first return false unless row (row) true end |
.destroy_session(session_id) ⇒ Object
40 41 42 |
# File 'app/models/completion_kit/mcp_session.rb', line 40 def self.destroy_session(session_id) unscoped.where(session_id: session_id).delete_all end |
.prune_expired! ⇒ Object
44 45 46 |
# File 'app/models/completion_kit/mcp_session.rb', line 44 def self.prune_expired! unscoped.where("expires_at < ?", Time.current).delete_all end |
.slide_expiry(row) ⇒ Object
48 49 50 51 52 |
# File 'app/models/completion_kit/mcp_session.rb', line 48 def self.(row) half_ttl_from_now = (SESSION_TTL / 2).from_now return if row.expires_at > half_ttl_from_now row.update_column(:expires_at, SESSION_TTL.from_now) end |
.start! ⇒ Object
25 26 27 28 |
# File 'app/models/completion_kit/mcp_session.rb', line 25 def self.start! prune_expired! unscoped.create!(session_id: SecureRandom.uuid, expires_at: SESSION_TTL.from_now).session_id end |