AIScan cover: Your MCP card on Hugo, with the Hugo name chip and three steps: pick static or generated, add the card file, build and curl it.
AIScan cover: Your MCP card on Hugo, with the Hugo name chip and three steps: pick static or generated, add the card file, build and curl it.
AI Readiness

How to publish /.well-known/mcp/server-card.json on Hugo

Publish /.well-known/mcp/server-card.json on Hugo: a file in static/ or a card generated from hugo.toml, plus the build mistake that hides it, with real output.

AAIScan Content Team 8 Oct 2026 5 min read
#MCP#server card#Hugo#well-known

This guide covers P2 · Capabilities.

Table of contents

Hugo has two ways to put an MCP server card at /.well-known/mcp/server-card.json, and one of them can build cleanly while writing the file to the wrong place. This guide covers the card for check P2 on Hugo. I built a site on Hugo v0.167.0 and ran both routes, so every path and output below comes from a real build.

The card is the JSON document that tells an agent where your MCP server lives. Its schema is still a draft, so read Publish an MCP Server Card So Agents Can Find Your Tools for the fields. This post is only the Hugo mechanics for check P2, which AIScan grades.

Quick summary

If you want...Do thisTime
A fixed card, edited by handPut the file in static/.well-known/mcp/server-card.json5 min
A card built from hugo.toml valuesAdd a ServerCard output format, a section page with url: /.well-known/mcp/ and a layout15 min
To know which one wins when both existThe generated one (tested below)1 min

Check first: run npx aiscan-cli yoursite.com and read check P2 in the Capabilities group.

Option 1: a file in static/

Hugo copies everything in static/ to public/ on build, and the directory structure docs list robots.txt and ownership-verification files as typical contents. A dot-prefixed folder is no exception. Create:

static/.well-known/mcp/server-card.json

{
  "serverInfo": { "name": "example-mcp", "version": "1.0.0" },
  "description": "Search the example catalog",
  "transport": { "type": "streamable-http", "url": "https://example.com/mcp" },
  "tools": [{ "name": "search", "description": "Search the catalog" }]
}

Replace the example values with your real server name, endpoint and tools. The field names follow the shape in AIScan's own P2 guidance and are a convention, not a ratified schema. Running hugo produced public/.well-known/mcp/server-card.json with no flags and no config.

Option 2: generate the card from hugo.toml

Use this when the endpoint or version lives in your config and you do not want a second copy to drift. Hugo's output formats let one page render as JSON at a path you choose.

1. Add the values and the format to hugo.toml:

[params.mcp]
  name = "example-mcp"
  version = "1.0.0"
  description = "Search the example catalog"
  endpoint = "https://example.com/mcp"

[outputFormats.ServerCard]
  mediaType = "application/json"
  baseName = "server-card"
  isPlainText = true
  notAlternative = true

application/json is a built-in media type, so I did not need a [mediaTypes] block. Removing it changed nothing.

2. Create content/mcp/_index.md:

---
title: "MCP server card"
url: /.well-known/mcp/
outputs: ["ServerCard"]
---

3. Create layouts/mcp/list.servercard.json:

{{- $m := site.Params.mcp -}}
{{- dict "serverInfo" (dict "name" $m.name "version" $m.version) "description" $m.description "transport" (dict "type" "streamable-http" "url" $m.endpoint) "tools" (slice (dict "name" "search" "description" "Search the catalog")) | jsonify (dict "indent" "  ") -}}

Build with hugo. The card appeared at public/.well-known/mcp/server-card.json. jsonify sorts keys alphabetically, so description comes before serverInfo, which does not matter to a parser. It also escapes & and < as & and <, and a JSON parser reads them back as the original characters. With --minify the file shrank to one line and stayed valid.

The mistake that builds cleanly

Leave out the url: line and Hugo builds without a warning, but writes the card to public/mcp/server-card.json. The path AIScan checks returns 404 and nothing in the build output tells you. Always list public/.well-known/mcp/ after a build.

If you use both

I put a different file in static/ at the same path, then built with the generated version in place. The generated card won and the static one was not used. Delete one of them so a future reader does not edit the wrong file.

Publish, then test the live domain

Hugo writes files and sets no headers. The content-type your visitors see comes from the host in front of public/, and I did not test any host. Deploy, then request the card on the domain agents will use.

Verify the card

Run AIScan first:

npx aiscan-cli yoursite.com

Find P2 · MCP Server Card in the Capabilities group. It should pass. By hand, one request does the same job:

curl -si https://yoursite.com/.well-known/mcp/server-card.json

Expected output, trimmed:

HTTP/2 200
content-type: application/json

{"description":"Search the example catalog","serverInfo":{...}

On hugo server, the card answered 200 with Content-Type: application/json, and a misspelled server-carb.json returned 404.

When the card still fails

  • 404 at the right path. The file is in static/well-known/ without the leading dot, or the section page has no url: line. List public/.well-known/mcp/.
  • No card file, and a warning. Hugo prints found no layout file for "servercard" for kind "section". The layout is named for the wrong page kind. A section page needs list.servercard.json, and single.servercard.json does not match it.
  • 200 but the body is not JSON. Your host rewrites unknown paths to a page. Look at content-type and the first bytes, never only the status.
  • The card shows old values. You edited static/ while the generated card still exists. Remove one source.

Where to go next

Run npx aiscan-cli yoursite.com, confirm check P2, then browse the other fixes in the AIScan guides. The capabilities checks reference lists P1 to P4 and E2. If you also need llms.txt on Hugo, the llms.txt guide for Hugo uses the same output-format idea, and the Hugo setup guide covers the wider checklist. The Astro version of this guide covers the same check on another static generator.

Frequently asked questions

Where does the MCP server card go in a Hugo project?

Put it at static/.well-known/mcp/server-card.json, or generate it with a ServerCard output format and a section page whose url is /.well-known/mcp/. Both build to public/.well-known/mcp/server-card.json.

Does Hugo copy a folder that starts with a dot?

Yes. On Hugo v0.167.0 a static/.well-known/ folder was copied to public/.well-known/ with no configuration.

Why does my build succeed but the card path return 404?

The section page probably has no url line, so Hugo wrote the card to public/mcp/server-card.json. Add url: /.well-known/mcp/ to the front matter and list public/.well-known/mcp/ after the build.

Why does Hugo warn that it found no layout file for servercard?

The layout is named for the wrong page kind. A section page needs layouts/mcp/list.servercard.json. A file called single.servercard.json is not used for it, and no card is written.

Why does the card URL return 200 but AIScan still fails check P2?

Your host may answer unknown paths with an HTML page and a 200 status. Check that content-type is application/json and that the body starts with a JSON object.

What happens if I have both a static file and a generated card?

On Hugo v0.167.0 the generated card won and the static file at the same path was not used. Delete one source so nobody edits the wrong one.

Why do I see \u0026 in my generated card?

The jsonify function escapes ampersands and angle brackets. A JSON parser reads them back as the original characters, so the card is still valid.

Which fields should the card contain?

The shape in AIScan's P2 guidance uses serverInfo, description, transport and tools. The schema is still a draft, so treat the field names as a convention and keep the card small and accurate.

Related guides