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>
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import { z } from "zod";
|
|
|
|
export const MemoryScope = z.enum(["project", "user"]);
|
|
export type MemoryScope = z.infer<typeof MemoryScope>;
|
|
|
|
export const MemoryVisibility = z.enum(["private", "shared", "team"]);
|
|
export type MemoryVisibility = z.infer<typeof MemoryVisibility>;
|
|
|
|
export const ProjectKey = z
|
|
.string()
|
|
.min(1)
|
|
.max(200)
|
|
.regex(/^[a-zA-Z0-9._\-/]+$/, "project key may only contain alphanumerics, ._-/");
|
|
export type ProjectKey = z.infer<typeof ProjectKey>;
|
|
|
|
export const MemoryContent = z.string().min(1).max(64_000);
|
|
|
|
export const Tags = z
|
|
.array(z.string().min(1).max(64).regex(/^[a-zA-Z0-9._\-]+$/, "tag must be alphanumeric ._-"))
|
|
.max(32)
|
|
.default([]);
|
|
|
|
export const MemoryWriteInput = z.object({
|
|
content: MemoryContent,
|
|
project: ProjectKey.optional(),
|
|
tags: Tags.optional(),
|
|
scope: MemoryScope.default("project"),
|
|
});
|
|
export type MemoryWriteInput = z.infer<typeof MemoryWriteInput>;
|
|
|
|
export const MemoryListInput = z.object({
|
|
project: ProjectKey.optional(),
|
|
scope: MemoryScope.optional(),
|
|
tags: z.array(z.string()).optional(),
|
|
limit: z.number().int().min(1).max(200).default(50),
|
|
cursor: z.string().optional(),
|
|
});
|
|
export type MemoryListInput = z.infer<typeof MemoryListInput>;
|
|
|
|
export const MemoryIdInput = z.object({
|
|
id: z.string().uuid(),
|
|
});
|
|
export type MemoryIdInput = z.infer<typeof MemoryIdInput>;
|
|
|
|
export const ProjectIdentifyInput = z.object({
|
|
key: ProjectKey,
|
|
display_name: z.string().min(1).max(200).optional(),
|
|
});
|
|
export type ProjectIdentifyInput = z.infer<typeof ProjectIdentifyInput>;
|