A resume for two audiences
More and more, the first reader of a resume is an AI agent. So I built this site to be read by both people and agents — Markdown twins, llms.txt, JSON Resume, content negotiation, and a small read-only MCP server.
Recruiters, sourcing tools, and hiring managers increasingly put an AI between themselves and a resume. The model reads it first and summarizes it for the person. So I built this site for two readers: a person skimming in a browser, and an agent reading it on that person’s behalf.
The rule: one source, many renderings
Everything about me lives in one typed data file and a folder of Markdown posts. Every surface is built from those two inputs at build time:
- HTML pages for people.
- A Markdown twin of every page. Add
.mdto any URL, for example /resume.md or /writing/the-agent-is-an-identity.md. Each page also announces its twin with<link rel="alternate" type="text/markdown">. - /llms.txt and /llms-full.txt, following the llms.txt convention: a short index, and everything in one file for a single fetch.
- /resume.json in the JSON Resume schema, for tools that want structure instead of prose.
- schema.org JSON-LD on every page:
Person,ProfilePage, andBlogPosting. - Content negotiation. Request any page with
Accept: text/markdownand you get the Markdown version at the same URL.
The “Agent view” toggle in the header shows the exact Markdown an agent receives for the page you’re on. A person can see the same thing a model sees.
A read-only MCP server
There’s also a small Model Context Protocol endpoint at /api/mcp. It speaks stateless Streamable HTTP and offers a few tools: get_profile, get_resume, list_posts, get_post, and search. Point an MCP client at it and an agent can query my background directly instead of scraping HTML.
I wrote it by hand without an SDK, which was a deliberate security choice:
- Read-only by construction. Every tool returns data that’s already public on this site. There’s nothing to write, no secrets, and no state between requests.
- Small and auditable. It’s one file you can read in a few minutes. It parses JSON-RPC, checks the method, and returns content. Unknown methods get a standard error.
- Bounded. Request bodies are size-capped, search queries are length-capped, and every response comes from a fixed in-memory corpus. There’s no database or outbound call to abuse.
It’s the same thing I ask for from any MCP server at work: least privilege, a narrow surface, and nothing that would be a problem if an attacker controlled the input.
Boring security, done properly
A security leader’s website should pass its own review:
- The site is fully static except for the MCP function, so there is very little attack surface.
- Strict headers: a Content-Security-Policy with no
unsafe-inlinescripts, HSTS,frame-ancestors 'none', and a tight Permissions-Policy. - No third-party scripts, no trackers, and fonts hosted on this domain.
- A security.txt so researchers know how to reach me.
robots.txtexplicitly welcomes AI crawlers. On this site, I want to be read.
The source is on GitHub. Every change goes through a pull request with a Vercel preview build before it ships.