Seneschal › Gopher over HTTPS

Gopher over HTTPS

A tiny, open convention for machine-readable service discovery: serve a 1991-vintage Gopher menu over plain HTTPS at /.well-known/agent.gopher. An AI agent learns what you offer — and drills into only the branch it needs — in a few hundred bytes instead of parsing a multi-kilobyte HTML page or JSON index.

It is a cheap table of contents an agent reads before it connects — a typed, navigable cousin of llms.txt. Not a replacement for MCP: MCP (JSON-RPC, full input schemas) is how you call a tool; this is how an agent finds it.

Seneschal runs it live, including a terse mirror of the official MCP registry.

View the live menu » As HTML As JSON

Why bother — the numbers

Tokens are money and latency. A discovery format should be terse and let an agent fetch only the branch it cares about. Gopher's menu line does both. Measured on Seneschal's real directory:

"Tokens" are estimated as bytes / 4 — a stable proxy for the ordering, not an exact tokeniser count. The win is in discovery; tool results should stay JSON, where you want the schema and types.

See it live

This is exactly what GET https://seneschal.space/.well-known/agent.gopher returns today:

iAgent/MCP services — terse index for machines (Gopher-over-HTTPS).	
iLines are <type><label>TAB<selector>. Type 1 = drill in, 0 = read, h = URL.	
i	
1Seneschal — Monero/Zcash payment webhooks + Ethereum data	/.well-known/agent/seneschal
1FlashBank — non-custodial flash loans + P2P term loans	/.well-known/agent/flashbank
1winbit32 — agent-facing private payments (FROST co-sign)	/.well-known/agent/winbit32
1secresea — privacy NFT marketplace (Zcash shielded)	/.well-known/agent/secresea
1MCP registry — live mirror of the official registry	/.well-known/agent/registry
i	
0about — what this directory is and why	/.well-known/agent/about
0agents — how to consume it over HTTPS	/.well-known/agent/agents
.

A leading 1 means "submenu — GET this selector to drill in". The menu ends with a line containing only a dot. Add ?format=json or ?format=html to any node for the same content in another shape.

How to read one (for agents)

  1. GET the selector path over HTTPS. The root is always /.well-known/agent.gopher.
  2. Split the body on newlines. Each non-empty line is <type><label>TAB<selector>: the first character is the type, then the display label, a single TAB, then the selector.
  3. Act on the type:
TypeMeaningWhat an agent does
1submenu (directory)GET the selector to drill in
0text leafGET the selector and read it
hexternal linkselector is URL:https://… — follow it
iinfopresentational only — ignore for navigation
7searchappend a query to the selector
3error

A line that is only . ends the menu (RFC 1436). One-liner to see the whole tree:

curl -s https://seneschal.space/.well-known/agent.gopher
curl -s https://seneschal.space/.well-known/agent/registry   # drill into the MCP registry mirror

Already connected over MCP? Seneschal also exposes this exact directory as an MCP toolseneschal_agent_directory on mcp.seneschal.space (args: section, format, cursor). Same tree, including the registry mirror, without leaving the session — so the discovery layer lives inside MCP as well as over plain HTTPS.

Set up your own

The whole convention is: serve a Gopher menu over HTTPS at /.well-known/agent.gopher with Content-Type: application/gopher; charset=utf-8. Use absolute selectors (e.g. /.well-known/agent/catalogue) so a client can drill down by re-requesting them. That's it — there is no registry to join and no library you must use.

Option A — a static file

Simplest path: drop a file in your web root and set the content type. This is how the satellite sites in this ecosystem publish theirs.

The file (fields separated by a literal TAB; ends with a lone .):

iMy services — terse index for machines.	
1Catalogue	/.well-known/agent/catalogue
0about — what this is	/.well-known/agent/about
hMCP server	URL:https://mcp.example.com/
.

nginx

location = /.well-known/agent.gopher {
    default_type application/gopher;
    charset utf-8;
    add_header X-Content-Type-Options nosniff;
}

On an SPA, make sure the real file is matched before your try_files fallback to index.html, or you'll serve HTML with a 200 instead of the menu. Check with curl -I that the Content-Type is application/gopher, not text/html.

Option B — generate it

For a dynamic directory (or to avoid hand-typing tabs), use the open-source gophermap primitive from payments-gateway (MIT). It builds, parses and sanitises menus so the type codes live in one place.

import {
  info, menu, textItem, link, buildMenu
} from "payments-gateway/gophermap";

// Compact mode is the default (host+port dropped).
const body = buildMenu([
  info("My services — terse index for machines."),
  menu("Catalogue", "/.well-known/agent/catalogue"),
  textItem("about", "/.well-known/agent/about"),
  link("MCP server", "https://mcp.example.com/")
]);

// Serve it (Fastify shown; any framework works):
app.get("/.well-known/agent.gopher", (req, reply) =>
  reply
    .header("content-type", "application/gopher; charset=utf-8")
    .header("cache-control", "public, max-age=600")
    .send(body));

Behind a reverse proxy, hand the well-known paths to your app:

# Caddy
handle /.well-known/agent.gopher {
    reverse_proxy 127.0.0.1:8810
}
handle /.well-known/agent/* {
    reverse_proxy 127.0.0.1:8810
}

Conventions worth following

Mirroring the open MCP registry

Seneschal's /.well-known/agent/registry branch is a live, cached (10 min) terse lens over the official MCP registry — public, API-first, Apache-2.0. Each remote server becomes a connectable h URL: line. We deliberately mirror only the official, open registry, not commercial directories with unclear terms. If you run a registry of your own, the same transform makes it agent-cheap to browse.

Honest caveats

Further reading

Live menu » payments-gateway (gophermap, MIT) Seneschal llms.txt RFC 1436 (Gopher) llms.txt convention