Case study
LivingCV
The self-hosted portfolio platform and AI recruiter concierge powering this site.
Highlights
- Public MCP endpoint with exactly 2 read-only tools, enforced server-side
- ~380MB idle RAM across 4 containers
- Persona-neutral: any self-hoster's identity lives only in the DB
- Recruiter chat agent gated behind a work-email verification step
This portfolio is running on software I built myself. LivingCV is a self-hosted platform that renders a recruiter-facing site straight from a database (nothing is baked in at build time), and pairs it with an AI concierge that can answer a recruiter's questions using my real career data.
The trust boundary is enforced at the server, not in the prompt. The public chat agent talks to a jobops MCP server over a /mcp/public mount that has exactly two tools registered: get_career_packet and get_story_bank_public. Every mutating tool, importing contacts, drafting outreach, running a negotiation brief, simply isn't registered on that mount, so a hostile prompt that convinces the model to try calling one of them still fails; the tool doesn't exist on that surface. A separate bearer-gated /mcp mount is reachable only by my own tooling.
Under the hood it's four small containers: a Next.js web app, an Express orchestrator running the chat-turn logic, a jobops instance (consumed as an npm dependency, not vendored), and Caddy for automatic HTTPS, all sharing one SQLite file over WAL. The whole stack idles around 380MB of RAM, comfortably inside a free-tier Oracle Ampere A1 instance. Every persona detail, name, projects, case studies, lives in the database and the setup wizard rather than in the codebase, so anyone can self-host the same system for themselves.