Work · Case file 09 · This website (private repository, live endpoints) · 2026
This site: a résumé engine, an MCP server and a build that checks itself
At a glance
- Result
- A live MCP server and résumé engine behind this site, callable from this page, and a build that refuses to ship a skill whose proof does not resolve.
- Problem
- A portfolio's claims are usually prose a reader cannot check, and each tailored résumé is retyped with nothing tying it to the evidence.
- My role
- Solo, with Claude Code as the coding agent: the résumé engine, the MCP server, the prerender step, the build gates and the agent skills.I direct the work and approved every block of résumé text; the code is written with Claude Code, and the commits say so. The repository is private.
- Stack
- TypeScript · React prerendering · Vercel Functions · pdf-lib and docx · MCP over Streamable HTTP
Proof
The problem
A portfolio usually asks a reader to trust prose. Numbers drift from their sources, a skills list grows past what the work shows, and each tailored résumé is retyped by hand. An agent reading the site gets HTML to scrape and nothing to call.
The idea. Keep every fact in one set of TypeScript data files and serve it three ways from the same code: prerendered pages for people, a JSON export and an MCP server for agents, and résumés composed only by selecting approved blocks. Then make the build refuse to ship a claim whose proof does not resolve.
Fig. 01 · Two paths, one set of data
How it works
Résumés from approved blocks
src/data/resume.ts holds every summary, skills line and bullet with its approved phrasings. A spec names block ids, never text: validateSpec rejects unknown ids, composeResume orders the sections, and the layout steps the type size down until the page budget fits. server/render-pdf.ts draws the plan with pdf-lib's built-in Helvetica and fixed dates, so the same spec gives the same bytes; Word and Markdown come from the same resolved résumé. The builder on /resumes runs the same layout code.
Tailoring that cannot write
build_tailored_resume counts which terms from my attested vocabulary a posting asks for, scores each role edition by whether it demonstrates a term in a bullet or only lists it in skills, and starts from the winner. The posting is data: none of its words reach the document, a job URL is hashed and never fetched, and the report names the blocks that would supply a missing term.
An MCP server in one stateless function
api/mcp.ts hands each POST to server/mcp/server.ts, which answers initialize, ping, tools/list and tools/call over Streamable HTTP with no session. Arguments are validated against each tool's JSON Schema. Portfolio and résumé tools are open; job preferences and the application tracker refuse any caller without my bearer token, compared as hashes in constant time.
Prerendered, then hydrated
scripts/prerender.ts renders every route with React's prerender API from the same App the browser runs, with its own title, description, Open Graph tags and JSON-LD, plus sitemap.xml, feed.xml and a 404 page. The browser hydrates that HTML; /llms.txt and /api/portfolio project the same data as text and JSON.
A build that checks itself
npm run build runs check-words (banned terms, em dashes, loose résumé files), check-links (records which outbound links answer), both TypeScript projects, the client and server builds, the prerender, and check-skills, which fails when a skill has no proof, a cited anchor is missing from the prerendered page, or a résumé skills line names a skill the site does not prove. The repository's agent skills add commands run before shipping: npm run verify (screenshots, axe, hydration and layout shift in both themes), audit:evidence, lint:casefiles and smoke.
What was hard
Deciding how much an agent may write. A tool that drafts bullets would match more postings, and would also put sentences on my résumé that I never wrote. I chose selection only: callers pass block and phrasing ids, the spec has no field for text, and the posting is only terms matched against a fixed vocabulary. The trade-off is reach: when a posting asks for something none of my blocks demonstrates, the report marks the term missing instead of writing a sentence that claims it.
Limits. The unit tests were added in November 2026 and cover logic, not how the pages behave in a browser beyond the automated page checks. The repository is private, so this page shows excerpts read from the source at build time and links to live endpoints, not code. check-skills proves that each proof link resolves, not that the work behind it is good. Tailoring is term counting against my own vocabulary, not semantic matching, and has not been evaluated against a recruiter's judgement.
Evidence
- MCP tools
- 18 tools in server/mcp/tools.ts on 11 Nov 2026: 13 open, 5 need the owner's bearer token. The console reads the live list.server/mcp/tools.ts; tools/list on /api/mcp
- Protocol
- Streamable HTTP, stateless, JSON responses; protocol versions 2025-06-18, 2025-03-26 and 2024-11-05; 1 to 20 JSON-RPC messages per POST; GET answers 405.server/mcp/server.ts
- Résumés
- 7 editions, each as PDF, Word and Markdown, plus any spec built from the blocks; a posting may be up to 40,000 characters.src/data/resume.ts; server/tailor.ts; /resumes
- Reproducible files
- resume_version is rv_ plus 16 hex characters of a SHA-256 over the edition and the canonical spec; fixed PDF dates make one spec one file.server/resumes.ts; server/render-pdf.ts
- Build gates
- check-words, check-links (records, never fails), tsc on 2 projects, the client and server builds, prerender, check-skills.package.json, scripts.build
- Agent skills
- 5 skills in .claude/skills: case-file-authoring, demo-capture, evidence-audit, release-check, site-verify; run as npm run verify, audit:evidence, lint:casefiles and smoke..claude/skills; package.json
- Tests
- Unit tests (vitest) cover the résumé engine, the MCP server, the JSON export, the skills data, the pond simulation and the redirect table; a GitHub Actions workflow runs them with the build, the audits and the page checks on each pull request.tests/; .github/workflows/ci.yml
Stack
- TypeScript
- React 19 with react-dom/static prerender
- Vite and esbuild
- Tailwind CSS
- Vercel Functions (Node)
- pdf-lib and docx
- MCP over Streamable HTTP
- onnxruntime-web
- Playwright and axe-core (agent skills only)