No markdown content negotiation
When an agent requests your page with Accept: text/markdown and gets HTML back, it has to parse the full DOM — navigation, scripts, styling — to recover the content. Offering a markdown representation of the same URL hands agents clean, structured text directly. It’s an advanced, optional signal, but a strong one for agent-first sites and docs.
Symptoms
How VibeCheck catches it
In your widget · Problems
To your coding agent · MCP
The same string in your widget and in your agent’s context — no screenshot, no copy-paste.
Root causes
The fix
Honour the Accept header: when a request prefers text/markdown, respond with a markdown representation of the same content and a text/markdown content type. For content built from markdown already, this is often just serving the source. Add a Vary: Accept header so caches keep the two representations separate.
- Detect
Accept: text/markdownon your content routes - Return the markdown representation with a
text/markdowncontent type - Send
Vary: Acceptso caches don’t mix HTML and markdown responses
if (req.headers.accept?.includes('text/markdown')) {
res.setHeader('Content-Type', 'text/markdown; charset=utf-8')
res.setHeader('Vary', 'Accept')
return res.end(page.markdownSource)
}FAQ
- Is markdown negotiation required?
- No — it’s an optional, advanced AEO signal, most valuable for docs and agent-facing sites. VibeCheck reports it as info, not an error. Skip it if your content isn’t markdown-backed.
- Why send
Vary: Accept? - So shared caches and CDNs store the HTML and markdown responses separately per
Acceptheader, instead of serving one to a client that asked for the other. - How is this different from
llms.txt? llms.txtis one static index of your key content for LLMs; markdown negotiation serves a clean markdown version of any URL on request. They complement each other —llms.txtpoints agents at the content, negotiation delivers it without the DOM noise.