Postgres-backed Anubis.Server.Session.Store for the Anubis MCP transports.
Why
Anubis keeps session state in memory. Persisting its session record lets a
client reconnect with its Mcp-Session-Id after a deploy without repeating
protocol initialization. Under Anubis 2.0's normal Streamable HTTP recovery
path, only initialization metadata is recovered; the saved live frame and
its assigns are not restored.
Anubis ships only a Redis adapter; this implements the same behaviour against
an Ecto.Repo, for a Postgres-only deployment that does not run Redis.
Wiring
# config/config.exs
config :anubis_mcp, :session_store,
enabled: true,
adapter: AttestoMCP.Anubis.SessionStore.Ecto,
repo: MyApp.Repo,
ttl: to_timeout(minute: 30)The :repo may also be set as config :attesto_mcp, repo: MyApp.Repo. Run
mix attesto_mcp.gen.session_migration --repo MyApp.Repo to create the
backing attesto_mcp_anubis_sessions table. The Anubis-specific name keeps
this schema separate from attesto_mcp_server's incompatible native
session record.
No process
The store is stateless: every callback is a direct repo query and the repo is
already supervised, so start_link/1 returns :ignore instead of booting an
idle GenServer. Anubis lists the adapter as a child (the {adapter, config}
child-spec form when enabled: true); :ignore tells the supervisor there is
nothing to start.
Postgres has no native key TTL, so expired rows are reaped lazily on load/2
and by cleanup_expired/1 (call it on a schedule from the host).
Session identifiers are restricted to 1–255 visible ASCII bytes, matching
the generated varchar(255) column and the MCP Streamable HTTP header
contract. Malformed identifiers never reach the Repo.
The sanitizer uses the JSON library configured for Postgrex (Jason by
default). If a Repo uses another JSON library, set the same module as
:json_library in the :anubis_mcp, :session_store configuration.
Anubis persists frame assigns. Hosts should implement their server module's
serialize_assigns/1 callback to limit what is written and retain only
non-sensitive application data needed by another supported or custom path;
omit raw token claims, principals, tokens, callback-only owner markers, and
request-only values. Anubis 2.0's normal Streamable HTTP recovery path checks
persisted initialization state but does not restore the saved frame, so this
adapter cannot extend callback-level owner binding across a process or node
restart. The table is application-wide rather than namespaced per Anubis
server, so independently trusted servers must use separate stores unless
their store adapter provides a server namespace.
Compile-guarded on Anubis.Server.Session.Store and Ecto.Schema.