Architecture
Igris is a monorepo with two primary services, a shared database, and a Redis cache layer.System Overview
Components
API Server (Hono)
The backend runs on Hono and listens on port 3100. It handles:- MCP Gateway (
/v1/mcp/:slug) — intercept MCP tool calls, evaluate policies, forward to upstream - LLM Gateway (
/llm/:slug/*) — OpenAI-compatible API proxy for 60 LLM providers (see LLM Gateway below) - REST API (
/api/v1/*) — CRUD for policies, servers, sessions, audit events, billing - Auth (
/api/auth/*) — Better Auth endpoints for session management - SSE (
/api/v1/events) — real-time event stream for dashboard updates
Web Frontend (Next.js)
The dashboard runs on Next.js on port 3200 and provides:- Governance management (servers, policies, sessions)
- Real-time observe dashboard with risk heat maps
- Organization settings, member management, billing
Database (Neon PostgreSQL)
All persistent state lives in Neon PostgreSQL. The schema is managed by Drizzle ORM with auto-migrations on startup. Key tables:| Table | Purpose |
|---|---|
user, account, session | Better Auth identity |
organization, member | Multi-tenancy |
connections | Connection configs (HTTP MCP / LLM gateway, encrypted credentials) |
policies | Governance rules per connection |
agent_sessions | Active gateway sessions |
audit_events | Gateway audit trail (mutable — rows are archived to S3 and deleted on expiry) |
Cache (Upstash Redis)
Upstash Redis handles:- Policy cache — hot policies cached with TTL to avoid DB lookups on every tool call
- Rate limiting — sliding window counters for rate-limit policy rules
- SSE pub/sub — event fan-out to connected dashboard clients
- Session state — fast lookup for kill-switch status
Authentication (Better Auth)
Better Auth provides:- Email/password and OAuth (GitHub, Google) login
- Organization-scoped sessions with RBAC (operational)
- API key generation for programmatic access (proxy)
- Session tokens stored as
igris.session_tokencookies (or__Secure-igris.session_tokenin production)
LLM Gateway
The LLM Gateway is an OpenAI-compatible API proxy mounted at/llm/:slug/*. It routes requests through Igris connections so every LLM call is governed, audited, and cost-tracked alongside your MCP traffic.
Route shape
{connection-slug} identifies the Igris connection that holds the upstream provider credential. No model-prefix routing is used in the URL — the provider is determined by the provider field on the connection record.
Authentication
Bearer API key only (Authorization: Bearer ig_...). Wildcard CORS is applied (*) so browser-based clients can call the gateway directly. Session cookies are not accepted on gateway routes.
What the gateway does on each request
- Authenticates the API key and resolves the connection (slug → provider + encrypted credential)
- Evaluates policies (allow / deny / alert / redact) against the model and endpoint
- Checks pre-forward token guards and content guards
- Checks rate limits (requests / tokens / dollars, Redis sliding window)
- Forwards the request to the upstream provider with credential injection
- Streams the response to the client via a single-pass tee (client stream + accumulator)
- Writes an audit event with token counts, cost, and model after the response completes
- Updates the LLM anomaly detector asynchronously (cost spike, token burn, error rate, model shift, response length)
Provider support
60 providers are registered, including OpenAI, Anthropic, Google Gemini, Groq, Mistral, DeepSeek, Cohere, and self-hosted models via theopenai-compatible provider (requires customBaseUrl).
Enablement
The LLM Gateway is feature-flagged. SetLLM_GATEWAY_ENABLED=false to disable it; requests to /llm/* will return 404.
S3 Audit Archive
Audit events are mutable — they are retained in Postgres up to the plan’s retention limit, then archived to S3 Standard-IA and deleted from Postgres.Archive job
A daily cron runs at UTCARCHIVE_CRON_HOUR_UTC (default 03:00). For each organization it:
- Finds days in
audit_eventsolder thanretentionDays + 1buffer days - Serializes each day’s events as gzip NDJSON
- Writes the file to S3 with a deterministic key (
{orgId}/{YYYY-MM}/{YYYY-MM-DD}.ndjson.gz) — idempotent overwrite - In a single Postgres transaction: inserts a manifest row into
audit_archives, then deletes the archived rows fromaudit_events
restored_from_archive_id IS NOT NULL) are excluded from future archiving.
Key implications
audit_eventsdoes not grow unboundedly in Postgres- Restored rows are marked with
restored_from_archive_idso they are not re-archived - The audit trail is complete but not immutable — rows are deleted from Postgres after archiving
Proxy Flow
When an MCP client calls a tool through the Igris proxy:- Request arrives at
/v1/mcp/:slugwith the tool name and arguments - Auth check — validate API key or session cookie
- Org resolution — determine which organization owns this connection
- Policy evaluation — load rules from cache (or DB on cache miss), evaluate first-match against the tool name with conditions
- Action execution:
- allow → forward to upstream, log the event
- deny → return error to client, log the denial
- alert → forward to upstream, log + emit anomaly event via SSE
- Anomaly check — evaluate rate spike and destructive pattern detectors
- Audit write — persist the event to
audit_eventswith timing, result, and metadata - SSE broadcast — push real-time event to connected dashboard clients
Deployment
Igris deploys to EC2 via GitHub Actions with a systemd service:- API server runs as a systemd unit on EC2 (Ubuntu)
- Next.js frontend deployed to Vercel (or a separate EC2 instance)
- Neon for managed PostgreSQL (serverless, auto-scaling)
- Upstash for managed Redis (serverless, per-request pricing)
- Migrations applied automatically at API startup via
drizzle-orm/postgres-js/migrator(programmatic — notdrizzle-kit). The API refuses to start if any migration fails.