Skip to main content
This page is the operator map for a self-hosted 3ngram. It explains what runs where, what to check after a deploy, and which operational guarantees are intentionally not promised before v1.0. Operations for the managed 3ngram platform are handled by the maintainers and are not covered in this repository.

Runtime topology

A 3ngram deployment is four portable pieces: Everything runs on stock Postgres and Redis, so deploy to whatever you already operate: the bundled Compose stack on a single host, a PaaS such as Railway or Fly.io, managed Postgres such as Neon or Supabase, or any Docker-compatible runtime. The Self-host quickstart starts Postgres, Redis, and the server with one command.

Deploy verification

After a server deploy, verify the health and OAuth discovery surfaces against your own base URL:

Auth and access

3ngram is its own OAuth 2.1 authorization server and resource server.
  • MCP clients use Client ID Metadata Documents or dynamic registration fallback, PKCE, and OAuth bearer tokens.
  • Scripts, CLI, and SDK integrations use API keys through X-API-Key.
  • All user-owned database access is tenant-scoped through RLS and the runtime app_user role.
The public API reference describes the REST contract. The MCP tool reference describes each tool and its required scope.

Rate limits

Production deployments require Redis for cross-instance limits. Local development can fall back to in-memory buckets, but that is not a production limit. Current limit dimensions are:
  • per-user on /mcp;
  • per-IP on login and OAuth authorization-server endpoints;
  • per API key/source on /api/v1.
If Redis is unavailable, rate limiting fails open so the service keeps serving. Operators should treat repeated rate-limit: store unavailable warnings as a degraded-protection incident.

Caching

3ngram does not use an application cache for memory data in v1. Postgres is the source of truth; Redis is not a memory cache. The MCP tool and prompt definitions are a separate, deployment-versioned catalog: 2026-07-28 responses mark those tenant-independent lists public-cacheable for one hour.

MCP 2026 compatibility rollout

Keep the migration on a dedicated compatibility track and promote it only after a staging soak:
  1. Apply database migration 0027_cimd_clients.sql before starting the new server.
  2. Exercise one retained DCR client and one public CIMD+PKCE client through authorization, code exchange, refresh, and /mcp.
  3. Run the official-client contract tests for both legacy and 2026-07-28.
  4. Verify authorization-server metadata advertises client_id_metadata_document_supported: true while retaining registration_endpoint.
  5. Monitor OAuth invalid_client/metadata-fetch failures, protocol negotiation, and the bounded mcp.header_requests status labels during the soak.
Do not remove DCR during rollback or cleanup. Before CIMD grants are issued, the server can roll back without data cleanup; after issuance, retain a CIMD-capable binary so existing grants continue to resolve. Materialized client rows are foreign-key/display state and must not be treated as fetched-metadata authority.

Scaling

The server is stateless across requests, so it can run as multiple replicas behind a load balancer once shared-state dependencies are healthy. Before scaling replicas, verify Redis is configured and Postgres connection pressure is acceptable. Database scaling is your Postgres provider’s operation. Background workers use BullMQ, ship as their own image (ghcr.io/b3dmar/3ngram-worker), and are part of both Compose stacks (docker-compose.yml, compose.selfhost.yml).

Recovery

Use the smallest recovery action that matches the failure: After recovery, rerun the deploy verification checks and one authenticated read path before declaring the incident closed.

Current gaps

  • Metrics counters are wired, but dashboards and alert policies are not complete.
  • The session closer ships on the worker but stays SESSION_CLOSER_ENABLED=false until its commitment-recall validation bar is measured (see Session continuity); with it off the sweep scheduler is removed and closer jobs no-op, while consolidation, surfacing, and OAuth-client GC always run.