# Making a small business site agent ready: what broke, what held, and what to copy

I checked 95 well-known domains for a valid A2A agent card and found none. Here is what did work on real small business sites, with sites to copy and a free kit.

Author: J.A. Watte
Published: September 26, 2026
Source: https://jwatte.com/blog/agent-ready-small-business-website/

---

I spent most of September wiring small business websites for AI agents: a sober living house, two laundromats, a motel group with twelve property sites and a hub, my book sites, a news site and a housing data site I run, and this one. Depending on the site, that meant an A2A agent that answers questions about the site, a read-only MCP server, the discovery files that point at both, robots.txt repairs, link preview repairs and, for six of them, a connector submitted to Meta's Muse.

Most of what I learned isn't written in any spec. It lives in the gap between a file that validates and a thing that works. This post is the general version: each practice that held up, why it matters, what it looked like when I got it wrong, and public sites that do it well. The numbers are my own measurements with their dates, or they come from the sources listed at the end. Every template is in a free Markdown kit near the bottom.

One correction first. In April I wrote [a post arguing that every website needs an AI agent card](/blog/blog-ai-agent-cards-2026/) and described putting cards on 52 sites. The word "every" was wrong. An A2A agent card describes an agent that other agents can hand work to. With no agent behind it, the card is a false statement sitting at a standard address. When I ran the cards on eleven sites I maintain through a readiness scanner this month, it rejected all eleven for missing required fields. Ten of those sites now publish a card that passes, and each one points at an agent that answers.

## The rule under everything: a card is a promise

An A2A agent card, an MCP server card, an AI Catalog entry and an api-catalog anchor all tell a client that something exists at an address. The client can't see you. If it believes the card, calls the address and gets a 404, it has wasted a round trip and learned that your domain describes itself wrongly.

So the order never changes. Build the endpoint first. Generate the card at build time from the same config file the endpoint runs, so the two can't drift apart. Remove both in the same deploy.

Three measurements convinced me this is the rule that matters most.

- **Scanners don't call the endpoint.** In my August tests, the readiness scanner at isitagentready.com passed an MCP server card because the file existed. It never contacted the server the card names, so a card with nothing behind it scores exactly like a real one.
- **A wrong file at a standard path is worse than no file.** One client site had a plugin-style JSON file sitting at the A2A card path. The scanner reported an "Invalid A2A Agent Card". Deleting the file moved the result to "not found", which was the honest state for a site with no agent.
- **Real agent cards are rare, and most published ones fail.** On September 26 I checked 95 well-known domains at both A2A card paths: search engines, cloud providers, payment companies, retailers, travel sites, news sites and several AI companies. Six served JSON. None of the six passed the validator I use, which checks the fields the A2A 1.0 specification marks as required. Eight more answered the card path with an ordinary HTML page and a 200 status, which a scanner that only checks status codes would count as a card. Outside the sites I maintain, I have yet to find one that passes cleanly. This site's card, at [/.well-known/agent-card.json](https://jwatte.com/.well-known/agent-card.json), passes with no errors or warnings, and the agent it names answers at /a2a.

That last finding carries a cheap lesson: a path you don't serve should return a real 404, not your home page.

## An A2A site guide that actually answers

The agent behind a small business card doesn't need to be clever. Mine is a read-only site guide. Another agent sends a plain-language question, and the guide answers with up to five pages from the site's own page list, each with a title and a link, plus how to reach the business. It never books, buys, submits a form or stores the message, and every answer opens by saying it is an automated site guide.

The card has to satisfy two generations of client libraries. A2A 1.0 is the current specification, and it requires `supportedInterfaces`, `version` and `skills`. The scanner found `skills` and `supportedInterfaces` missing on every old card I had, and `version` on most of them. Libraries built for A2A 0.3 refuse a card that lacks three older fields: `url`, `preferredTransport` and `protocolVersion`. A 1.0 client ignores fields it doesn't know, so the card carries both sets. Here is a complete card for a made-up laundromat; it passes the same validator, and the kit at the end has it ready to copy:

```json
{
  "name": "Example Laundry site guide",
  "description": "Automated, read-only guide to Example Laundry (example.com). Ask a plain-language question and it replies with up to 5 matching pages from the site's own page list, each with its title and a link, plus how to contact the business. It does not book, buy, submit forms or store what you send.",
  "supportedInterfaces": [
    { "url": "https://example.com/a2a", "protocolBinding": "JSONRPC", "protocolVersion": "1.0" },
    { "url": "https://example.com/a2a", "protocolBinding": "JSONRPC", "protocolVersion": "0.3" }
  ],
  "provider": { "organization": "Example Laundry LLC", "url": "https://example.com/" },
  "version": "1.0.0",
  "documentationUrl": "https://example.com/llms.txt",
  "capabilities": { "streaming": false, "pushNotifications": false, "extendedAgentCard": false },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain", "application/json"],
  "skills": [
    {
      "id": "site-guide",
      "name": "Find pages on example.com",
      "description": "Read-only. Answers a plain-language question with up to 5 of the most relevant pages on example.com, found by searching only the site's own page list, plus a short line on how to contact the business. It never books, buys, submits forms, stores messages or calls third parties.",
      "tags": ["site-guide", "search", "read-only", "laundromat"],
      "examples": ["What are your hours?", "How much does a large washer cost?", "Where do I report a broken machine?"],
      "inputModes": ["text/plain"],
      "outputModes": ["text/plain", "application/json"]
    }
  ],
  "url": "https://example.com/a2a",
  "preferredTransport": "JSONRPC",
  "protocolVersion": "0.3"
}
```

The last three fields are the 0.3 compatibility set. Everything above them is the 1.0 card.

The endpoint then has to do what the card says. It answers with a Message, never a Task, because the answer is immediate. A task lookup gets error -32001, a streaming request gets -32004 and a push notification request gets -32003, since the card declares neither, and a plain GET gets 405. I tested the pattern with the official JavaScript client library at versions 1.2.1 and 0.3.14. On this site today, the kit's live checker passes 23 of 23 checks.

Two things decide whether the answers are any good.

**The page list.** If llms.txt lists 12 links, the guide can only ever recommend those 12 pages. The housing data site I run had exactly that: 12 links in llms.txt and 38,496 URLs in its sitemap. Asked "Is Idaho a buyer's market?", the guide reading only llms.txt found nothing. With llms.txt and the sitemap merged, the Idaho state page came first. Merging costs memory, about 17 MB of heap for those 38,496 pages and up to about 32 MB at a 60,000 URL cap, so plan on 256 MB and keep it out of a 128 MB edge isolate.

**The deadline.** Reading the page list gets a 3 second budget. When the site's own files are slow, the honest "the page list is unavailable" answer still lands inside the 5 second default timeout of httpx, the HTTP library the Python A2A SDK uses.

One thing to keep out: the config's description and contact line are repeated in every answer, so neither may carry a price or a claim the site doesn't publish.

Two build traps cost me time. Eleventy silently skipped the card template on sites whose config pins `templateFormats` without `11ty.js`, so no card was written at all. And a CI job that runs `npx astro build` directly never runs npm's `prebuild` script, so the card check needs a step of its own there.

**Worth copying:** [Tavily's card](https://www.tavily.com/.well-known/agent-card.json) has the best skills block I found. Each of its five skills has two or three concrete example prompts and its own input and output types, which is the part of a card another agent reads when deciding whether to delegate. Its interface block uses the older top-level `url` form, so take that part from the 1.0 specification or from the kit.

## A read-only MCP server small enough to trust

MCP gives an agent named tools to call instead of pages to read. For a small business the right size is a few read-only tools over things the site already publishes: hours, services, prices, locations, how to apply, how to report a problem.

**Serve both protocol eras from one endpoint.** The 2026-07-28 revision made MCP stateless: no `initialize` handshake, a `server/discover` method, and the protocol version and client capabilities carried inside every request. Clients built for the 2025 revisions still send `initialize`. My servers answer both and never issue a session id. I checked them with the official TypeScript SDK 1.30.1, and with `@modelcontextprotocol/client` 2.1.0 in its default mode, in auto mode (which negotiated 2026-07-28 through `server/discover`) and pinned to 2026-07-28. On this site today, the kit's live checker passes 53 of 53.

**GET /mcp must answer 405.** Not a friendly description page, and not a cached JSON body. The official TypeScript SDK 1.30 opens a GET to listen for server messages, reads a 200 as a stream that ended, and reopens it about once a second for as long as it stays connected. On one laundromat's server, a connected client made 20 GETs in 20 seconds. After the fix, a 405 with `Allow: POST, OPTIONS` and `Vary: Accept`, it made one and stopped. If a CDN caches anything at that path, the Vary rule has to reach the cache too; on Netlify that also means `Netlify-Vary: header=Accept`.

**Serve the server card at three addresses.** This changed while I was working. The current server card extension says to publish an AI Catalog at `/.well-known/ai-catalog.json` and to host each card at the MCP address plus `/server-card`, as `application/mcp-server-card+json`. It lists a `.well-known` card path among the placements it does not recommend. Scanners still read two older paths, `/.well-known/mcp/server-card.json` and `/.well-known/mcp.json`. So I serve one object, byte for byte, at all three, and a build check fails if they ever differ.

**Treat the `$schema` value as a name, not a link.** The card schema requires one exact `$schema` URL, and on September 26 that URL answered 404. Validate against `schema.json` in the [ext-server-card repository](https://github.com/modelcontextprotocol/ext-server-card) instead of trying to fetch it.

**Worth copying:**

- **GitHub** keeps the chain short. [Its AI Catalog](https://github.com/.well-known/ai-catalog.json) has one entry pointing at `api.githubcopilot.com/mcp/server-card`, which is served with the typed media type. Catalog, card, endpoint, and nothing repeated between them.
- **Hugging Face** publishes the same kind of catalog and lets anyone call its [MCP server](https://huggingface.co/mcp) without signing in. All four tools are marked read-only and non-destructive, so a client can list them safely before any login.
- **Microsoft Learn's** [MCP server](https://learn.microsoft.com/api/mcp) has three tools, each with a human title and a read-only mark, and an instructions block telling the model what each tool returns and when to use it.
- **Pinecone's marketing site** is the closest match to a small business I found. [Its MCP server](https://www.pinecone.io/mcp/) has one read-only tool, `search_website`, and instructions that say what it covers and point to a separate docs server for API questions, so an agent knows where this server stops.
- **The Cloudflare blog's** [server](https://blog.mcp.cloudflare.com/mcp) maps four read-only tools onto what a reader does on a blog: search posts, list posts, read a post, browse tags. It's a good template for a small business blog or news page.

## Tools that quote the site and never invent

This is where the real risk lives. A price or an hour typed into a tool drifts away from the page, and the tool keeps repeating it with total confidence.

**Generate every fact from the site.** On the sober living site, each fact a tool can return is copied verbatim from the site's own pages by a build script. Its check mode fails when a page has changed and the facts haven't, so it belongs before every deploy. An independent review found all 381 of 381 served fact strings word for word in the site's HTML. Every answer carries the link to the page it quotes.

**When the site says something several ways, return all of them.** That site states its monthly cost three ways: a monthly figure on the program page, the same figure marked subject to change in the rental agreement, and a line in the FAQ asking people to call for current rates. The cost tool returns all three as published. Picking one is the owner's job, not the tool's.

**Test any quoted price against whatever actually charges.** At the motel group, the quote tool and the booking function carried separate copies of the price code. A test now prices a 650-case matrix through both and fails on any difference. At one laundromat, a dryer price had moved from $1.75 to $2.00 since I last wrote it down, so the build now emits one facts file that the pages and the agent files both read.

**Never state what the business hasn't published.** The motel group's availability feed holds capacity, meaning how many rooms of a type exist, while its llms.txt, AGENTS.md, AI Catalog and home page still said rooms came off a live count. An assistant reading that prose could have told someone ten rooms were free for a week in November, on the strength of a number that means "we have ten of these". The files now call the numbers capacity, name the dated endpoint that can say what's actually free, and a gate checks them against the feed.

**Put a disclosure line in every answer, in the text and in the structured data.** The sober living site's tools say in every answer that this is automated site information, that the business is a sober living residence and not a licensed treatment facility, that it isn't medical or treatment advice, and they give 911 and 988. The news site's tools say its analysis is AI generated by fictional personas, isn't news reporting and isn't investment advice. On a sensitive topic the safety lines come first, and the directory category you choose should be honest too: housing, not medical.

Public examples of the same honesty:

- The [Pinecone docs server](https://docs.pinecone.io/mcp) tells the model: "Do not claim access to private or authenticated content unless the current MCP session is authenticated." Its one write tool, for documentation feedback, is marked as not read-only rather than hidden among the read-only ones.
- [Stripe's docs llms.txt](https://docs.stripe.com/llms.txt) tells agents to "always check the npm registry for the latest version rather than relying on memorized version numbers." That's the same rule as reading prices from the page, never from memory.
- [Vercel's llms.txt](https://vercel.com/llms.txt) has a section on how agents should use the service, including "Ask for approval before changing account resources."

## Read-only by construction, not by label

MCP tools carry annotations such as `readOnlyHint`, and the specification tells clients to treat annotations as untrusted unless the server itself is trusted. So the annotation is a courtesy. The guarantee has to live in the code.

- **No booking, lead or payment tools.** A reservation at the motel group creates a signed lodging agreement, so an agent that could book could sign for somebody. A tool that writes into a one-person business's inbox fills it with synthetic leads, and the owner is the one who sorts them out.
- **A "report a problem" tool returns instructions and files nothing.** Both laundromats have one.
- **A tool that takes a URL returns a link instead of fetching it.** This site's `audit_url` tool hands back a link to the right free tool with the address filled in, and the audit runs in the user's own browser when they open it. The server never contacts the address, so there's nothing to aim at an internal network, and in testing it refused all 97 non-public or malformed addresses thrown at it.
- **Gate it, and know what the gate proves.** My check searches the server code for write helpers, environment variable reads and outbound requests, with comments stripped first, because the server's own header comment promising it never touched the booking system tripped the raw search. It's a lint, not a proof, so I still read the code.

## Treat every question as hostile data

A question sent to your agent is text from a stranger that your site is about to repeat. These rules come from things that broke, or nearly did:

- Echo only the words of a question, letters and digits, never its links or markup. Then nobody can plant a link inside your own site's reply.
- List only pages on your own site, and strip bidirectional text control characters from every string. A page title carrying U+202E renders backwards.
- Cap the request body and validate every argument against its schema, with a `maxLength` next to every `pattern`. Before the kit required that, 29 characters against the pattern `^(a+)+$` took 15.8 seconds, and a running regular expression can't be interrupted, so it blocks every other request on that instance.
- Serialize strict JSON. JavaScript tolerates a lone UTF-16 surrogate; strict parsers such as Rust's `serde_json` reject the whole message.
- Keep tool failures and protocol failures apart. A tool that fails returns a normal result with `isError: true` and text the model can act on, and a bad argument counts as a tool failure so the model can fix its call. A malformed request gets a JSON-RPC error. A notification gets no reply body.

On the sober living server this held through 199 of 199 attack checks, including five prompt injection payloads. Links and markup planted in a question never came back in an answer, and a made-up tool called `book_a_bed` got error -32602. The MCP specification's own list is short enough to tape to the monitor: servers must validate all tool inputs, implement proper access controls, rate limit tool invocations and sanitize tool outputs.

## Plumbing that fails quietly

My sites run on Netlify, so the specifics below are Netlify's, but every one of these traps has a cousin on other hosts.

**Rate limit at the platform.** Agent endpoints are public by design. An Origin or Referer allowlist, which this site uses on its other functions, would refuse every outside agent, so the two agent endpoints skip it. Instead each one declares Netlify's code-based rate limit: 60 requests a minute per IP and domain, answered with a 429. On the sober living draft, 70 unpaced GETs all got the normal 405, and in the next burst of 150, every request after the first got 429. Free, Starter and Personal plans get two code-based rules per project, and an A2A agent plus an MCP server use both. A rule the plan can't take is skipped without failing the deploy, so look for it in the deploy log. Pace your own checkers as well. One of my own verification runs fired its probes too fast, drew 1,837 responses of 429, and the host then refused connections from that address for a while.

**Route by the function's own path, never a redirect.** A 301 turns the JSON-RPC POST into a GET and drops the body. Put each function's files in a subfolder, because Netlify deploys every top-level file in `netlify/functions` as its own function, and helper files turn into broken endpoints. Exclude `/a2a`, `/mcp` and `/mcp/*` from every edge function that runs on `/*`, or that function can answer the POST first. On two of my sites whose project folder sits inside another project folder, the deploy tool treated the outer folder as the project and shipped a stale edge function bundle, so a new exclusion never reached production. One way to spot it is to time GET /a2a with a markdown Accept header against a JSON one: the stale function adds a sub-request.

**Keep function source private.** If you publish from the repository root, your function code sits inside the publish folder. On the sober living site's first draft, a forced 404 rule for `/netlify/*` was already in place, and the source still came back with a 200 for `/NETLIFY/functions/mcp/mcp-tools.mjs`, for the capitalized `/Netlify/` spelling and for a `%2F` spelling. Redirect rules match case-sensitively, static files are served case-insensitively, and `%2F` gets decoded. A guard in the edge function that runs first closed it: 99 spelling variants then gave 90 responses of 404 and 9 of 400, and 669 paced traversal probes found nothing. The better fix is to stage the publish folder without the functions in it at all.

**Let the service worker skip agent traffic.** `/a2a`, `/mcp`, `/mcp/*` and everything under `/.well-known/` answer machines and change with the server behind them, so a card or a JSON-RPC answer must never come out of an offline cache. Bypass them, bump the cache version so the old worker's cache is deleted, and pass `ignoreVary` to every cache lookup if your pages vary on Accept. In a fresh browser profile on the sober living draft, none of the nine agent and `.well-known` addresses came from the worker, nor did a POST, and the upgrade deleted the old cache.

**Don't let a glob cache your 404s for a year.** A header rule such as `for = "*.png"` with `immutable` and a one-year max-age is matched on the request path, so it stamps 404s too. A web app manifest icon that didn't exist yet got cached as a year-long 404 (the CDN reported `ttl=31534047`), which keeps a site from installing as an app even after the file appears. Use exact-path rules for immutable files, a moderate cache time for icons and screenshots, and new filenames if a broken icon address already went out. After the fix, 70 of 70 checks showed missing images returning 404 with `max-age=0, must-revalidate`, while the real icons kept their caching.

**Build markdown twins at build time.** One pricing page on a client site was 71,005 bytes of HTML and 5,743 bytes of markdown, 92 percent smaller. Send `Vary: Accept` on both the markdown and the HTML answers, keep `*/*` (what curl sends) on HTML, and exclude `/*.md` from the edge function so the twin fetch doesn't loop back through it. [Stripe's docs](https://docs.stripe.com/testing) get the Vary part right: the same address answers markdown or HTML depending on Accept, and both answers carry `Vary: Accept`.

**Answer Range requests honestly.** Facebook's preview crawler sends a Range header; the Sharing Debugger reported a 206 when I tested on September 24. On two of my sites, an edge script injected a snippet after the range had been cut, so a 206 promised 222,966 bytes and delivered 223,071. Skip the injection for Range requests, or answer them with a plain 200.

## Discovery files: point at the cards, and link them from every page

**Point at cards, not endpoints.** llms.txt links the agent card and the server card, never `/a2a` or `/mcp` themselves. The site guide reads llms.txt, so a linked `/a2a` would turn into a "page" it recommends. The RFC 9727 api-catalog gets an anchor for each endpoint, described by its card. The AI Catalog gets one entry per agent or server, keyed by `identifier` (a file using `id` parses fine and is rejected whole), with exactly one of `url` or `data` and 2 to 5 `representativeQueries`. AGENTS.md gets a section describing the server, which doubles as the documentation address a connector form asks for.

**Link the surface from every page.** The motel group published availability JSON, llms.txt, AGENTS.md, an AI Catalog and a correct api-catalog. Every file answered 200 with the right type, and not one of the 439 HTML pages linked to any of them. Asked about the site, an assistant cited three HTML pages, never found the feed, and guessed that an availability file "typically manages real-time vacancy", which is exactly what that feed exists to deny. A validator scores files; it never asks whether a reader can reach them. The fix is two lines in every page's head:

```html
<link rel="api-catalog" href="https://example.com/.well-known/api-catalog">
<link rel="describedby" type="text/plain" href="https://example.com/llms.txt">
```

Both relations are registered, so nothing mistakes them for typos. Then test by walking the chain on the live site: a page, the catalog it links, the file the catalog names.

**Compute counts and facts at build time, and sweep every file type when you fix one.** This site's AGENTS.md, markdown home page and api-catalog said 309 tools while the registry held 356, until the number came from the registry at build time. At the motel group, a coordinate fix walked only `.html` files and left `llms-full.txt` wrong by up to 1.9 miles, in the one file written for AI assistants. And allowlist the fields you publish: internal data files keep notes such as `phoneNote` right beside the real values, and spreading the whole object publishes them.

**Worth copying:**

- [llmstxt.org's own llms.txt](https://llmstxt.org/llms.txt) is the minimal form in 637 bytes: an H1, which is the only required part, a short blockquote, then a list of links to clean markdown.
- [Svelte's llms.txt](https://svelte.dev/llms.txt) links three sizes, from about 53 KB to about 1.2 MB, and its Notes section says what the smaller ones leave out, so an agent with a small context can choose on purpose.
- [Cloudflare's developer docs llms.txt](https://developers.cloudflare.com/llms.txt) stays short by sending each product to its own llms.txt. That's the shape for a business with many locations or properties.
- [Vercel's api-catalog](https://vercel.com/.well-known/api-catalog) uses the `item` relation to point at an API on another host, then describes that host. It fits a small business whose booking system lives with a vendor.
- [Cloudflare's agents site api-catalog](https://agents.cloudflare.com/.well-known/api-catalog) anchors an MCP endpoint and links its server card as `service-meta`, so a client that only knows RFC 9727 still finds the server.
- [The Cloudflare blog](https://blog.cloudflare.com/) starts the chain in an HTTP header on its home page, `Link: </.well-known/api-catalog>; rel="api-catalog"`, and the catalog leads on to the server card.
- [Cloudflare's developer docs AI Catalog](https://developers.cloudflare.com/.well-known/ai-catalog.json) uses exactly the fields strict validators want: a `did:web` host identifier, `identifier` keys, one address per entry and two representative queries each.

A note on AGENTS.md. The format is defined for code repositories, with the file "at the root of the repository", and projects such as [OpenAI's Codex](https://raw.githubusercontent.com/openai/codex/main/AGENTS.md) and [Apache Airflow](https://raw.githubusercontent.com/apache/airflow/main/AGENTS.md) use it that way, with specific rules like which commands never to run. Publishing one at a website's root, as this site does, is my own extension. It's handy as the human-readable documentation for an MCP server, but no standard asks for it.

## robots.txt, read the way RFC 9309 reads it

Three rules from RFC 9309 do all the damage. A crawler obeys only the group that names it. Several groups that name the same crawler get combined. The `*` group applies only when no group names the crawler at all. So a named group inherits nothing from `*`, and since a group runs until the next `User-agent` line, rules written below a comment or a blank line still belong to the group above them.

This site had 22 user-agent groups and one `Content-Signal` line, so 21 of its 22 groups carried no preference at all. Today it has 22 groups and 22 signals. Two client sites had 20 groups and 1 signal. The readiness scanner passed the Content-Signal check on all three, because it searches the file for the line instead of parsing the groups. Separately, six of my sites let named AI crawlers into a thank-you page the `*` group blocked, because the named groups never repeated the Disallow.

Repeat every `*` rule and the `Content-Signal` line inside every named group, or don't name crawlers at all. Content-Signal itself is not a standard. It states three separate preferences, `search`, `ai-input` (using pages to ground AI answers) and `ai-train`, whose definitions come from an individual Internet-Draft that expired in April 2026, and nobody is obliged to honor them. The [robots.txt Simulator](/tools/robots-txt-simulator/) tests a crawler and a list of URLs against your file.

For Meta: allow meta-webindexer, since Meta says allowing it "helps us cite and link to your content in Meta AI's responses", and allow facebookexternalhit, which builds link previews. Decide meta-externalagent, the training crawler, separately. Meta says meta-externalfetcher may bypass robots.txt because a person asked for the page, and that its crawlers may cache robots.txt for up to 24 hours. Don't build user agent rules or IP allowlists for Muse, because Meta documents neither.

**Worth copying:**

- [Cloudflare's developer docs robots.txt](https://developers.cloudflare.com/robots.txt) has a single `*` group carrying the Content-Signal line, and a comment block at the top explaining what search, ai-input and ai-train mean. With one group there's no inheritance trap.
- [Vercel's robots.txt](https://vercel.com/robots.txt) is one group too, and shows that the signals are separate choices: `search=yes, ai-input=yes, ai-train=no`. It also allows its preview image routes under a broad `Disallow: /api/`, which keeps link previews working.
- [GitHub's robots.txt](https://github.com/robots.txt) has one named group for GPTBot, OAI-SearchBot, ClaudeBot, anthropic-ai and PerplexityBot that repeats all 72 of the `*` group's Disallow lines; my group parser found none missing. That's the correct fix for the trap above, and several tokens sharing one block keeps it short.
- [The New York Times robots.txt](https://www.nytimes.com/robots.txt) works as a token checklist even if your policy is the opposite of theirs. Its 63 groups name GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-User, Claude-SearchBot, meta-externalagent, meta-externalfetcher and meta-webindexer, among many others.

## Link previews, the old surface that still breaks

Meta documents the limits. An og:image file "must not exceed 8 MB", the minimum is 200 by 200 pixels, 1200 by 630 is recommended, and width and height tags let the crawler render the image on the first share. WhatsApp wants the image "under 600KB" and the `<head>` within the first 300 KB of the HTML. Meta caches images by URL, so a replacement needs a new filename.

On September 24 I ran three of my own pages through Facebook's Sharing Debugger. A WebP og:image rendered as the normal large card, an SVG rendered as a small cropped square, and a 9.4 MB PNG was rejected as over 8 MB. On the sober living site, 13 pages declared 1200 by 630 for a logo that is really 1090 by 588, and 24 declared no size at all. After the repair all 37 pages passed 458 checks with no failures. The full set of rules is in [my post on what Meta documents for websites](/blog/meta-muse-website-compatibility/), with its own checklist.

## WebMCP without breaking the page

WebMCP lets a page register tools for an agent working inside the browser tab. Use `document.modelContext.registerTool`. The older `navigator.modelContext.provideContext` call was removed from the specification, and both Chrome's origin trial, which starts at Chrome 149, and ChatGPT's Site tools use `registerTool`. ChatGPT's documentation adds two limits: tools declared through HTML form attributes aren't available as site tools, and tools registered inside iframes aren't discovered. This site, and another one I run, still called only `provideContext` when I checked this month. This one now uses `registerTool`, keeping `provideContext` only as a fallback for older builds.

Wrap it in feature detection and a try/catch, and make sure it can't break anything else in the same file. On the sober living site, the same script also registers the service worker and syncs the theme color. I tested it with the API missing, with only `provideContext`, with `registerTool` throwing, with the getter throwing, and with both calls present. All five left the worker and the theme working, with no page errors. And don't let an MCP server card claim a `webmcp` transport unless your pages actually load that code. Two of my sites did.

Keep expectations in proportion. The specification is a Draft Community Group Report, and it says it is "not a W3C Standard nor is it on the W3C Standards Track". Meta documents no WebMCP support. [My May post on WebMCP](/blog/blog-webmcp-first-real-agent-api/) used names the current draft doesn't have, such as a `/.well-known/webmcp` file, `data-webmcp` attributes and a `window.webmcp` object, so read it with this correction in mind.

**Worth copying:** Google's Chrome team publishes demos aimed at exactly this audience. The [French bistro demo](https://googlechromelabs.github.io/webmcp-tools/demos/french-bistro/) turns its reservation form into a tool with two attributes, `toolname` and `tooldescription`, and also registers the tool from script with `registerTool`, which a real site needs since ChatGPT ignores the form attributes. The [hotel demo](https://googlechromelabs.github.io/webmcp-tools/demos/hotel-chain/) marks its search results tool `readOnlyHint: true`.

## What I deliberately don't publish

A readiness score tempts you to publish files until the number goes up. Some checks should stay red.

- **No OAuth or Protected Resource Metadata when nothing is protected.** A 404 at `/.well-known/oauth-protected-resource` is how a server says there's no auth. The scanner's auth.md check can't pass without that metadata, so passing it would take the exact false claim a correct auth.md refuses to make.
- **No commerce manifests on a site that sells nothing,** and no UCP profile without a checkout behind it.
- **No Web Bot Auth key directory.** That file belongs to whoever operates a bot, not to the site the bot visits.
- **In the Muse connector form, authentication is "Other: None".**

If you do protect something, copy the big providers. The MCP authorization specification says servers must publish Protected Resource Metadata and should put a scope hint in the `WWW-Authenticate` header. [Stripe's MCP server](https://mcp.stripe.com) answers an unauthenticated request with a 401 whose header points to that metadata and whose body links the docs for a human reader. [Linear's server](https://mcp.linear.app/mcp) adds the scope hint. [Vercel's metadata](https://mcp.vercel.com/.well-known/oauth-protected-resource) is the richest I saw, with a documentation link and the organization's name, so a client can show a person who they're about to authorize.

## Verify like you don't trust yourself

A card that parses and an endpoint that answers are separate facts, so I check both, locally before a deploy and live after it. The kit checkers speak the protocol end to end, and then the official client libraries connect for real. Each MCP client run first gets one deliberately tampered answer, a result that breaks the tool's output schema, to prove the client is really validating. Every run threw on it. Today this site passes 23 of 23 A2A checks and 53 of 53 MCP checks live, and the sober living site passed 23 and 59, with 8 tools, on the day it went live.

Scanners are useful for their list of checks and unreliable for their verdict. Call isitagentready.com's scan API directly, read each check's own spec for the exact field names, scan one site at a time, and confirm every fail with a direct fetch. In my runs it passed a server card on the file alone, passed a Content-Signal layout that only one group could see, gave every `*.netlify.app` host a free DNS pass from a wildcard record, reported a client site failing while a direct fetch showed 23 Content-Signal lines (a stale copy somewhere downstream), and counted a rate-limited check as a fail. A registry isn't the market either. In August, 10 of the 19 live first-party MCP servers I found were missing from the official MCP registry.

## Meta Muse: there's a form, not a standard

When I last checked Meta's documentation, on September 24, it had published no website standard for Muse and no discovery file. The only way in is the reviewed form at [muse.ai/platform](https://muse.ai/platform), where Meta says it will "review your connector for functional, security, and legal requirements". The form behind the login offers a raw API or an existing MCP server at a hosted HTTPS address, then asks for an authentication choice, privacy, terms and documentation addresses, a support contact, a 512 by 512 icon and example prompts.

Between September 24 and 26 I submitted six: the motel group, both laundromats, the news site, this site and the sober living house. Each was checked against its live server first. For each one I wrote a form pack before opening the form: every field's value, the site's own app icon at 512 by 512, and example prompts I had already seen the live server answer. For businesses I don't own, I confirmed I was authorized to submit for them before doing it. The motel group got one connector covering all twelve property sites, because twelve near-duplicates would clutter the directory and invite a rejection.

I have no review outcome to report yet, and Meta doesn't say whether it accepts read-only connectors at all.

## The order I ship in

1. Integrate on a copy of the site.
2. Deploy a draft, never production.
3. Have a separate reviewer try to break the draft and return GO or a list of blockers.
4. Deploy production only when the owner says so.
5. Run the live checks, paced under the rate limit.
6. Only then submit a connector.

The sober living site shows why. The first draft leaked function source through the capitalized paths. A probe of the draft caught it, and that draft was replaced. The second passed two independent reviews with zero blockers, went to production once approved, passed the live checks, and only then went to Meta.

Check the bytes the site serves, not the deploy tool's message. I've watched "Deploy is live!" print while the CDN still served the old files, and a deploy reported READY that shipped an index whose digests didn't match the files it served.

## A checklist you can run

1. **Decide what you really offer.** No agent, no agent card. No server, no server card. Nothing protected, no OAuth documents.
2. **Build the endpoint first**, then generate its card from the same config.
3. **Keep tools read-only** and make them quote your own pages, link the source and carry a disclosure line.
4. **Treat questions as hostile:** words only, same-site links, a maxLength everywhere, strict JSON.
5. **Rate limit at the platform**, route by path, and keep agent paths out of your edge functions and your service worker.
6. **Point discovery files at the cards** and link them from every page's head.
7. **Repeat every `*` rule and the Content-Signal line** in every named robots.txt group.
8. **Fix previews:** one og:image in the head, real dimensions declared, under 600 KB.
9. **Verify with the checkers and the official clients**, then again live.
10. **Ship in order:** draft, review, production, live checks, then connectors.

If you'd rather hand the job to an AI coding agent, or to your developer, **[agent-ready-setup-checklist.md](/downloads/agent-ready-setup-checklist.md)** has every template from this post filled with `example.com` placeholders: the agent card, the MCP server card, a tool definition, the llms.txt and AGENTS.md sections, the api-catalog and AI Catalog, robots.txt groups with Content-Signal, the Netlify config for routing, rate limits, source privacy and 404 caching, a service worker bypass, a WebMCP snippet, a Muse connector worksheet and a verification checklist. Every JSON block in it parses, the agent card passes the A2A kit's validator, and the server card passes the schema in the ext-server-card repository. For the preview side, use the [Meta crawler and link preview checklist](/downloads/meta-crawler-preview-checklist.md), and before connecting to anyone else's server, the [MCP server vetting checklist](/downloads/mcp-server-vetting-checklist.md).

The free tools cover most of the checks in a browser, with no signup: the [Agent Ready Audit](/tools/agent-ready-audit/), [MCP Server Audit](/tools/mcp-server-audit/), [LLMs.txt Validator](/tools/llms-txt-validator/), [.well-known Audit](/tools/well-known-audit/), [Service Worker Audit](/tools/service-worker-audit/), [Web App Manifest Audit](/tools/web-app-manifest-audit/), [Agent Rate-Limit Probe](/tools/agent-rate-limit-probe/), [Prompt Injection Defense audit](/tools/prompt-injection-defense/) and the [Meta AI Crawler and Link Preview Audit](/tools/meta-ai-crawler-audit/).

If you're doing this for your own business rather than paying someone, *The $20 Dollar Agency* is the book I wrote for that: getting found in search and in AI answers with cheap tools and your own hands.

## Related reading

- [Meta Muse has no website standard: here is what Meta actually documents](/blog/meta-muse-website-compatibility/)
- [Agent readiness past llms.txt: eleven files, and the five that silently fail validation](/blog/agent-readiness-beyond-llms-txt/)
- [MCP servers for small business, and why the registry misses half of them](/blog/mcp-servers-for-small-business/)
- [The four agent protocols and where each one fits](/blog/blog-agent-protocol-stack/)
- [What the Cloudflare agent readiness score actually checks](/blog/blog-cloudflare-agent-readiness-score/)

## Fact-check notes and sources

Every external page below was read, and every example site was fetched and checked for the specific thing this post says about it, on September 26, 2026. The measurements on my own and my clients' sites are my own tests, dated where they appear.

- **Source**: https://a2a-protocol.org/latest/specification/ establishes A2A 1.0 as the current version, the `/.well-known/agent-card.json` location and the required `supportedInterfaces` field.
- **Source**: https://github.com/a2aproject/a2a-js is the official JavaScript client library used for the A2A interop tests (versions 1.2.1 and 0.3.14).
- **Source**: https://modelcontextprotocol.io/specification/2026-07-28 is the current MCP revision. Its tools page, https://modelcontextprotocol.io/specification/2026-07-28/server/tools, establishes that clients must treat annotations as untrusted unless they come from trusted servers and that servers must validate inputs, implement access controls, rate limit invocations and sanitize outputs. Its authorization page, https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization, establishes that authorization is optional, that servers which use it must implement Protected Resource Metadata (RFC 9728), and that they should include a scope parameter in `WWW-Authenticate`.
- **Source**: https://github.com/modelcontextprotocol/ext-server-card (its docs/discovery.md and schema.json) establishes the AI Catalog plus `<streamable-http-url>/server-card` discovery path, the `application/mcp-server-card+json` media type, the `.well-known` card placement listed as not recommended, and the required `$schema` value. That `$schema` URL, https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json, answered 404 on September 26. The proposal itself is SEP-2127, https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127.
- **Source**: https://github.com/modelcontextprotocol/typescript-sdk is the official MCP client used for the interop tests (`@modelcontextprotocol/sdk` 1.30.1 and `@modelcontextprotocol/client` 2.1.0).
- **Source**: https://github.com/Agent-Card/ai-catalog establishes the AI Catalog format, its `identifier` field and the rule of exactly one `url` or `data` per entry. https://agenticresourcediscovery.org/spec/ asks each entry to carry `representativeQueries`, and the readiness scanner's own ARD guide, https://isitagentready.com/.well-known/agent-skills/ard/SKILL.md, asks for 2 to 5.
- **Source**: https://llmstxt.org/ establishes the llms.txt format and that the H1 is the only required section.
- **Source**: https://agents.md/ establishes AGENTS.md as a format for coding agents with the file at the root of the repository.
- **Source**: https://www.rfc-editor.org/rfc/rfc9309 establishes robots.txt group matching, the combining of matching groups and the `*` fallback. https://www.rfc-editor.org/rfc/rfc9727 establishes the api-catalog well-known URI and relation, built on the linkset format of https://www.rfc-editor.org/rfc/rfc9264. https://www.rfc-editor.org/rfc/rfc9728 establishes Protected Resource Metadata. https://www.rfc-editor.org/rfc/rfc8246 establishes the `immutable` cache directive.
- **Source**: https://datatracker.ietf.org/doc/draft-romm-aipref-contentsignals/ is the individual Internet-Draft that defines the search, AI input and AI training categories and their labels. It expired on April 4, 2026.
- **Source**: https://webmachinelearning.github.io/webmcp/ establishes WebMCP as a Draft Community Group Report that is not a W3C standard, with `registerTool`. https://github.com/webmachinelearning/webmcp/pull/132 removed `provideContext`. https://developer.chrome.com/docs/ai/webmcp establishes the origin trial from Chrome 149, and https://developer.chrome.com/docs/ai/webmcp/imperative-api the `registerTool` API and `readOnlyHint`. https://learn.chatgpt.com/docs/webmcp establishes ChatGPT's Site tools as a WebMCP implementation using `registerTool`, and that form-attribute tools and tools inside iframes are not available.
- **Source**: https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/ establishes Meta's five crawler tokens, the meta-webindexer citation sentence, that meta-externalfetcher may bypass robots.txt, and the 24-hour robots.txt cache. https://developers.facebook.com/docs/sharing/webmasters/images/ establishes the 8 MB cap, the 200 by 200 minimum, the 1200 by 630 recommendation and the width and height tags. https://developers.facebook.com/docs/sharing/webmasters/ establishes that images are cached by URL. https://developers.facebook.com/documentation/business-messaging/whatsapp/link-previews establishes the 600KB image and 300KB head limits.
- **Source**: https://github.com/a2aproject/a2a-python (its pyproject.toml) establishes that the Python A2A SDK depends on httpx, and https://www.python-httpx.org/advanced/timeouts/ establishes httpx's default 5 second timeout.
- **Source**: https://muse.ai/platform establishes that Meta reviews connectors for functional, security and legal requirements. The form fields are as they stood when I filled them in, September 24 to 26, 2026.
- **Source**: https://docs.netlify.com/manage/security/secure-access-to-sites/rate-limiting/ establishes code-based rate limit rules, the 429 response, and 2 rules per project on Free, Starter and Personal plans (5 on Pro).
- **Source**: https://developers.openai.com/api/docs/bots, https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler and https://developers.google.com/crawling/docs/crawlers-fetchers/google-common-crawlers establish the OpenAI, Anthropic and Google crawler tokens.
- **Examples checked September 26, 2026**: github.com and api.githubcopilot.com (AI Catalog, typed server card, 401 with `resource_metadata`); huggingface.co (AI Catalog, typed server card, four read-only tools without sign-in); learn.microsoft.com/api/mcp (three read-only titled tools, instructions); www.pinecone.io/mcp/ and docs.pinecone.io/mcp (one read-only tool; the quoted instructions; the one write tool marked); blog.mcp.cloudflare.com/mcp and blog.cloudflare.com (four read-only tools; the Link header and api-catalog); mcp.stripe.com, mcp.linear.app/mcp and mcp.vercel.com (401 responses and their metadata); developers.cloudflare.com, vercel.com and agents.cloudflare.com (api-catalogs, AI Catalogs, llms.txt, robots.txt); llmstxt.org, svelte.dev and docs.stripe.com (llms.txt files and their sizes; Stripe's `Vary: Accept` on both answers); github.com and nytimes.com robots.txt (group counts and tokens, parsed into RFC 9309 groups); the Chrome Labs WebMCP demos; the OpenAI Codex and Apache Airflow AGENTS.md files; and www.tavily.com's agent card (skills, and the validator result).
- **My own tests**: the 95-domain A2A card check (September 26, 2026, both well-known paths, JSON cards run through the A2A kit validator); this site's live A2A (23 of 23) and MCP (53 of 53) checks, its 22 groups with 22 Content-Signal lines and its 309 versus 356 tool count (September 26); the scanner results on eleven sites and the A2A and MCP client library tests, including the 20 GETs in 20 seconds on a laundromat server and the regular expression timing (September 24); the 97 addresses `audit_url` refused (September 25); the sober living site's facts, attack, rate limit, source privacy, service worker, cache and preview checks (September 26, on its drafts, then live), and the year-long 404 on its manifest icon (July 31); the Sharing Debugger test (September 24); the motel group's price matrix and page linking (September 9), capacity wording (September 8) and coordinates (September 3); the dryer price, the scanner behavior and the markdown byte counts (August 31); and the registry comparison (August 22).

*This post is informational, not legal or SEO-consulting advice. The specifications discussed include drafts that will change. Mentions of GitHub, Hugging Face, Microsoft, Pinecone, Cloudflare, Stripe, Linear, Vercel, Svelte, Tavily, The New York Times, OpenAI, Anthropic, Google, Apache Airflow, Netlify, Meta and other parties are nominative fair use. No affiliation is implied.*


---

Canonical HTML: https://jwatte.com/blog/agent-ready-small-business-website/
RSS: https://jwatte.com/feed.xml
JSON Feed: https://jwatte.com/feed.json
Hero image: https://jwatte.com/images/agent-ready-small-business-website.webp
