this site, MCP, llms.txt, web security

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 .md to 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, and BlogPosting.
  • Content negotiation. Request any page with Accept: text/markdown and 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-inline scripts, 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.txt explicitly 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.