feat: Phase 1 — Authentik auth, MCP endpoint, persistent memory

End-to-end Phase 1 of shared-memory: a logged-in Authentik user can sign
into the Web UI (/me debug page), and an MCP client with an Authentik-
issued bearer token can call memory.write / memory.list / memory.get /
memory.delete plus project.identify against /api/mcp.

Stack:
- Next.js 15 (App Router) + React 19 + TypeScript, pnpm workspaces
- Drizzle ORM + Postgres 16 + pgvector + pg_trgm
- Auth.js v5 with Authentik provider (Web UI)
- jose + Authentik JWKS for MCP bearer-token validation
- JSON-RPC 2.0 dispatcher implementing the MCP wire protocol over plain
  HTTP POST (hand-rolled to fit Next.js App Router; switches to SSE in a
  later phase if server-initiated events are needed)
- bge-small embeddings sidecar deferred to Phase 2; the schema already
  reserves the vector(384) column + IVFFlat index, FTS via a STORED
  tsvector column, and the visibility enum (private/shared/team) so
  cross-user memory sharing can be added without a future migration

Deployment supports two modes (set in .env, never committed):
- Behind an external reverse proxy (HAProxy / nginx / Cloudflare Tunnel /
  Traefik) — DEFAULT; the app exposes APP_PORT on the host with
  X-Forwarded-* trusted, no in-container TLS
- Built-in TLS via Caddy — opt-in with `docker compose --profile tls up`

Discovery endpoint at /.well-known/oauth-protected-resource (RFC 9728)
points MCP clients at the Authentik authorization server after a 401.

README walks through both Authentik providers (Web UI + MCP resource
server), the audience scope mapping, redirect URIs, and includes a worked
HAProxy config snippet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 07:04:11 -07:00
co-authored by Claude Opus 4.7
parent d5be753cfb
commit 077d0a0825
37 changed files with 7294 additions and 8 deletions
+68
View File
@@ -0,0 +1,68 @@
# syntax=docker/dockerfile:1.7
# -----------------------------------------------------------------------------
# Multi-stage build for @shared-memory/web.
#
# deps — pnpm install with workspace context
# builder — next build (standalone) + bundled migrator
# runner — minimal Node runtime, non-root, runs server.js
#
# Build from the repo root:
# docker build -t shared-memory-web -f apps/web/Dockerfile .
# -----------------------------------------------------------------------------
FROM node:20-alpine AS base
RUN corepack enable
WORKDIR /app
# ---------- deps ----------
FROM base AS deps
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./
COPY apps/web/package.json ./apps/web/
COPY packages/schemas/package.json ./packages/schemas/
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
# ---------- builder ----------
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/apps/web/node_modules ./apps/web/node_modules
COPY . .
# Build the Next.js standalone bundle. Env validation is bypassed here so
# the image can be built without real OIDC/DB secrets baked in; runtime
# validation in `env.ts` re-checks all vars on first request.
ENV SKIP_ENV_VALIDATION=true \
NEXT_TELEMETRY_DISABLED=1
RUN pnpm --filter @shared-memory/web build
# Bundle the migrator into a single ESM file so the runtime image doesn't
# need tsx or the rest of devDependencies.
RUN pnpm --filter @shared-memory/web exec esbuild apps/web/scripts/migrate.ts \
--bundle --platform=node --target=node20 --format=esm \
--outfile=apps/web/migrate.mjs
# ---------- runner ----------
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production \
PORT=3000 \
HOSTNAME=0.0.0.0 \
NEXT_TELEMETRY_DISABLED=1
# `wget` is alpine's tiny default; used by the docker healthcheck.
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 --ingroup nodejs nextjs
# Standalone bundle includes traced node_modules + server.js.
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/drizzle ./apps/web/drizzle
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/migrate.mjs ./apps/web/migrate.mjs
USER nextjs
EXPOSE 3000
# Default command runs the server. The compose `migrator` service overrides
# this to run migrations once before the app comes up.
CMD ["node", "apps/web/server.js"]