Class: CompletionKit::McpSession
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
ApplicationRecord::TenantScopedUniquenessValidator
Class Method Summary
collapse
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
slide_expiry(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.slide_expiry(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
|