AttestoMCP.Anubis.SessionStore.Ecto (AttestoMCP v2.2.3)

Copy Markdown View Source

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.