hehrmann.com for developers and agents

Everything on this site is public, read-only, and available by machine. This page lists the doors, what comes back through each one, and the rules that keep the service predictable.

Start here

Three requests cover most of what an agent needs.

# Any page as Markdown
curl -H "Accept: text/markdown" https://hehrmann.com/services/

# The MCP server's tools
curl -X POST https://hehrmann.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Liveness
curl https://hehrmann.com/api/health

No API key, no OAuth, no account. The access model is spelled out in auth.md, and the short version is that nothing here takes a token because nothing here writes.

MCP server

https://hehrmann.com/mcp speaks the Model Context Protocol over Streamable HTTP. It is stateless and read-only: four tools, one resource per page, no session id, no authentication. Protocol versions 2026-07-28 (per-request _meta) and 2025-03-26 through 2025-11-25 (the initialize handshake) are both served. The server card at /.well-known/mcp/server-card.json describes the server, its tools, and their schemas; the same document answers at /mcp/server-card.

ToolWhat it doesArguments
list_pagesEvery public page with its canonical URL and Markdown twinoptional section, for example notes
get_pageOne page as Markdownpath, for example /services/
search_siteFull-text search across every page and note, with the URL to citequery, optional limit (1 to 8)
contact_instructionsHow to reach Erik on a user’s behalf; sends nothingoptional purpose: discovery-call, engagement, or question

Client configuration, in the shape most MCP clients accept:

{
  "mcpServers": {
    "hehrmann-com": {
      "type": "streamable-http",
      "url": "https://hehrmann.com/mcp"
    }
  }
}

REST endpoints

The OpenAPI 3.1 description lives at /api/openapi.json. The RFC 9727 catalog at /.well-known/api-catalog lists this surface and the MCP server, and every page response carries an RFC 8288 Link header naming the catalog, llms.txt, the capability manifest, and the page’s own Markdown twin.

PathMethodReturns
/{page}/GETHTML, or Markdown for Accept: text/markdown
/{page}/index.mdGETThe Markdown twin directly
/llms.txtGETAgent guide with links to every entry point
/llms-full.txtGETEvery page in one plain-text file
/pricing.mdGETThe fee table for the seven engagements
/sitemap.xmlGETCanonical URL list
/api/healthGETLiveness, JSON
/mcpPOSTJSON-RPC 2.0, MCP
/.well-known/api-catalog, /.well-known/ai-catalog.json, /.well-known/agent-skills/index.json, /.well-known/mcp/server-card.jsonGETDiscovery documents

Markdown for every page

Four routes lead to the same Markdown twin of a page, so whichever convention an agent tries first works:

  • Accept: text/markdown on the page URL, the negotiated route; both representations carry Vary: Accept.
  • ?mode=agent on the page URL, for clients that cannot set headers.
  • The page URL plus index.md, for example https://hehrmann.com/services/index.md.
  • The page URL with .md appended, for example https://hehrmann.com/services.md or https://hehrmann.com/notes/timesheets.md.

Each Markdown document opens with a frontmatter block (title, description, canonical, last-updated), so the metadata arrives without scraping. The machine documents take .md as well: /api/openapi.json.md, /api/health.md, or /.well-known/api-catalog.md return the same JSON inside a fenced block with a heading and the canonical URL.

Three scoped indexes cover one area each, in the llms.txt shape: /developers/llms.txt for this page, /services/llms.txt for the engagements and fees, and /notes/llms.txt for the case studies.

Errors

HTTP-level errors on /api/*, /mcp, and /.well-known/* come back as RFC 9457 problem details with Content-Type: application/problem+json:

{
  "type": "https://hehrmann.com/developers/#error-not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "No endpoint at /api/nope. The OpenAPI description at /api/openapi.json lists what exists; /developers/ explains it.",
  "instance": "/api/nope",
  "see": "https://hehrmann.com/api/openapi.json"
}

type is stable and safe to branch on, detail is for people, and extension members such as see, allow, and available point at the recovery. Inside the MCP endpoint, protocol errors stay JSON-RPC error objects, as the protocol requires. Page URLs keep their own 404: a short Markdown recovery body for agents and the designed HTML page for browsers. The types:

bad-request

Status 400. The request could not be read.

not-found

Status 404. No document or endpoint at that path. see names the catalog that lists what exists.

method-not-allowed

Status 405. Wrong HTTP method. The Allow header and the allow member list the accepted ones.

not-acceptable

Status 406. The Accept header ruled out every representation of a page. available lists them; a wildcard in Accept avoids this.

payload-too-large

Status 413. An MCP request body over 64 KiB.

rate-limited

Status 429. The request budget below is used up. Retry-After says when to try again.

service-unavailable

Status 503. The asset layer behind the site did not answer; /api/health reports the same condition.

Rate limits

/api/* and /mcp allow 100 requests per minute per client IP, counted by the edge instance that serves the connection (a client that opens a new connection per request can land on several instances, each with its own count; Cloudflare’s own protections cover the aggregate). Every response on those paths carries the IETF rate-limit fields, and a 429 adds Retry-After:

RateLimit-Policy: "api";q=100;w=60
RateLimit: "api";r=97;t=41

Read r (requests left) and t (seconds until the window resets) as the count of the instance that answered, pace to them, and treat a 429 with Retry-After as the authoritative signal. Pages, llms.txt, llms-full.txt, and the discovery documents are not metered; Cloudflare’s edge protections apply to everything. One request per page is enough, and llms-full.txt replaces crawling.

Versioning and deprecation

The REST surface is version 1 and carries no version in its paths on purpose: nothing in it has changed shape since launch, and additive changes (a new field, a new endpoint) ship without notice. A breaking change would get a new path such as /api/v2/, and the old one would keep answering for at least 180 days with Deprecation (RFC 9745) and Sunset (RFC 8594) headers pointing at the replacement, announced here and in the info.description of the OpenAPI document. The MCP server lists its protocol versions in the server card and adds new ones next to old ones; a version is retired the same way, 180 days after its Sunset date is published.

Sandbox

There is none, because there is nothing to protect: every endpoint is read-only, idempotent, and safe to call against production as often as the rate limit allows. The contact form is the one write path on the site, a person submits it, and the contact_instructions tool and the contact-erik-hehrmann skill explain how to hand a user to it.

Source, skills, and plugins

The two agent skills that describe this site, use-hehrmann-com and contact-erik-hehrmann, are published at /.well-known/agent-skills/index.json and mirrored in the public repository ehehrmann/hehrmann-agent-skills, which also carries an AGENTS.md for coding agents and an Agent Plugins manifest that bundles the MCP server. Install the skills with:

npx skills add ehehrmann/hehrmann-agent-skills

The site itself is a Hugo build behind a Cloudflare Worker, and that repository stays private.

Status and contact

Liveness: /api/health. Questions about any of this go to erik@hehrmann.com. Privacy notes for the site are at /privacy/; the fee table for people is on /services/.