Dark green cover graphic showing a grid of blank rounded nameplates with one highlighted in coral, titled Publish an MCP Server Card agents can actually find.
Dark green cover graphic showing a grid of blank rounded nameplates with one highlighted in coral, titled Publish an MCP Server Card agents can actually find.
AI Readiness

Publish an MCP Server Card So Agents Can Find Your Tools

We probed 79 hosts in the official MCP Registry and only 7 publish a server card. Here is how to write server.json and serve a card agents can really read.

AAsif Rahman 10 Sept 2026 11 min read
#MCP#server card#agent discovery#well-known#AI readiness

This guide covers P1 · Capabilities, P2 · Capabilities, P3 · Capabilities, P4 · Capabilities, E2 · Capabilities.

Table of contents

On 10 September 2026 we probed every host running a remote MCP server listed in the official MCP Registry: 79 domains, three requests each. Seven of them publish a parseable server card at /.well-known/mcp/server-card.json. Those seven files carry 31 distinct top-level fields between them, and exactly one field appears in all seven. Publishing your own card takes about twenty minutes, and this guide covers both the registry route and the self-hosted file, plus how to tell which one your scanner is grading.

Quick summary

If you want to…Do thisTimeWhat it changes
Be findable by MCP clients and marketplacesPublish server.json to the official MCP Registry with mcp-publisher20 minYour server appears in the registry REST API that aggregators poll
Pass a scanner's server-card checkServe a JSON card at /.well-known/mcp/server-card.json10 minAIScan check P2, and Cloudflare's mcpServerCard, stop reporting 404
Prove you own the domain you publish underServe /.well-known/mcp-registry-auth5 minLets you claim a com.example/* namespace instead of io.github.you/*
Check what you already exposeRun a scan and read checks P1 to P4 and E21 minTells you which discovery surfaces exist today

The honest headline: the registry format is specified and stable, the well-known card is not. Write server.json first, then serve a subset of it at the well-known path, because the working group defining the card says the card should stay close to a subset of server.json anyway.

Two different things are both called MCP discovery

An MCP client that already knows your endpoint URL has a specified way to ask what you can do. The MCP specification dated 2026-07-28 defines a JSON-RPC method called server/discover, and its wording is unambiguous: servers MUST implement it. Fetched from the specification on 10 September 2026, the response returns supportedVersions, capabilities, an instructions string, and a serverInfo block the spec itself warns about, in its own words: serverInfo "is self-reported by the server and is not verified by the protocol."

That method answers the second question. It cannot answer the first one, which is where is your server at all. A client has to already hold the URL before it can call anything.

The server card is the answer to that first question: a static JSON document, fetched over plain HTTP with no session and no handshake, that names the endpoint, the transport, the auth model and the tools. Think of server/discover as the phone extension inside the building and the card as the nameplate by the front door. The nameplate is what a crawler, a marketplace or a readiness scanner reads, because none of them are going to open a session with an unknown host just to find out whether it is worth opening a session with.

The card is still a draft, and the seven live files show it

The Model Context Protocol project runs a Server Card Working Group, and its charter is the clearest statement of where this stands. According to the charter, the group exists to "define what constitutes an MCP Server Card, the standardized document format a Server Card must follow, and how clients discover a Server Card for a given server." Its in-scope list names the discovery mechanism explicitly as unsettled work: "Specification of how an MCP Server Card document is discovered (well-known URL, resource-based discovery, etc.)."

Its single active work item, SEP-2127, is listed with status Draft. And the charter's note on coordination with the Registry working group is the most useful sentence in the document for anyone publishing today: the "Server Card format should stay as close as possible to a subset of server.json."

So there is no ratified schema, and the files in the wild look exactly like a convention with no schema. Across the seven parseable cards we fetched:

Top-level fieldCards carrying it (of 7)
description7
version, serverInfo, capabilities6
transport, authentication5
name, $schema4
tools, resources3
21 further fields1 or 2 each

Four of the seven declare a $schema. Between those four there are three different schema URLs, and every one of them returns HTTP 404:

https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json          404
https://static.modelcontextprotocol.io/schemas/server-card/draft/server-card.schema.json  404
https://modelcontextprotocol.io/schemas/server-card/draft-1.json                404
https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json    200, 22,090 bytes

The only MCP schema URL in that list that resolves is server.json's. Two more hosts returned HTTP 200 for the card path and served a body byte-identical to a control path that does not exist on the same host, which is the soft-200 pattern our CI-gate guide diagnoses in detail. Both reproduced on a second pass. One host serves a 303,527-byte card, which is a catalogue wearing a nameplate's filename.

The one MCP file your domain is specified to serve

There is a well-known path the MCP project does define, and it is not the card. Domain-based publishing authentication reads /.well-known/mcp-registry-auth, a one-line file in the form v=MCPv1; k=ed25519; p=<public key>. It exists so you can publish under com.example/your-server instead of a GitHub-derived namespace.

Adoption follows specification almost perfectly. Of the same 79 hosts, 19 (24%) serve a valid mcp-registry-auth file against 7 (8.9%) publishing a parseable card. The specified file is nearly three times as common as the unspecified one, on identical infrastructure, measured in the same minute.

Two more anchors worth holding while you read vendor blog posts on this. IANA's Well-Known URIs registry lists 213 suffixes and registers agent-card.json, but registers neither mcp nor webmcp. And according to Cloudflare Radar, which measured the 200,000 most visited domains for its April 2026 agent-readiness study, "MCP Server Cards and API Catalogs (RFC 9727) together appear on fewer than 15 sites in the entire dataset."

Path one: publish server.json to the official MCP Registry

Best for anyone who wants clients and marketplaces to find the server without knowing your domain. The registry is in preview, and according to its own documentation, breaking changes or data resets may occur before general availability.

  1. Install the CLI: brew install mcp-publisher, or download the release binary for your platform.
  2. Run mcp-publisher init in the server's project directory to generate a server.json template.
  3. Set the three required fields. Verified on 10 September 2026 against the published schema, server.json requires exactly name, description and version. The name is reverse-DNS with exactly one forward slash, as in com.example/analytics. The version should follow semantic versioning.
  4. For a remote server, add a remotes entry. A streamable-http remote requires type and url, and nothing else:
{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
  "name": "com.example/analytics",
  "title": "Example Analytics",
  "description": "Real-time reporting for the Example platform.",
  "version": "2.0.0",
  "remotes": [
    { "type": "streamable-http", "url": "https://example.com/mcp" }
  ]
}
  1. Authenticate. GitHub login is the quickest and gives you an io.github.<user>/* namespace. For a company domain, use DNS login after adding a TXT record of the form v=MCPv1; k=ed25519; p=<public key>, or HTTP login and serve the same string at /.well-known/mcp-registry-auth:
mcp-publisher login github
mcp-publisher login dns  --domain "example.com" --private-key "$KEY"
mcp-publisher login http --domain "example.com"
  1. Run mcp-publisher publish, then confirm the entry is live by reading it back from the registry REST API.

Publish an sse remote only if you need to support existing clients: the documentation records that transport as deprecated.

Path two: serve a card at your own well-known path

Best for anyone whose readiness score reports a 404 today, and for closed-source or single-tenant servers the registry will not accept. Because the format is unsettled, the defensible choice is a subset of server.json plus the endpoint details a client needs to connect.

  1. Write the file. Keep the server.json field names rather than inventing your own, and keep it small enough to read in one request.
{
  "name": "com.example/analytics",
  "title": "Example Analytics",
  "description": "Real-time reporting for the Example platform.",
  "version": "2.0.0",
  "remotes": [
    { "type": "streamable-http", "url": "https://example.com/mcp" }
  ],
  "capabilities": { "tools": {}, "resources": {} },
  "websiteUrl": "https://example.com"
}
  1. Serve it at /.well-known/mcp/server-card.json with content-type: application/json. Where that file lives depends on your stack:
StackWhere the file goesGate
Hugo, Docusaurus, Astrostatic/.well-known/mcp/ (Docusaurus also honours baseUrl)none
Next.jspublic/.well-known/mcp/, or a route handlernone
Lovable, Replitpublic/.well-known/ and client/public/.well-known/none
WordPressa real file under the web root, since .well-known is served before PHPnone
FramerSite settings, Hosting, FilesPro and above
WebflowEnterprise-only APIEnterprise
Ghostblocked: theme middleware denies .json outside /assets/n/a
Squarespace, Wixno route exists on any plann/a

Wix gives a documented reason rather than leaving it a mystery. According to its developer documentation, a custom site API answers only at <baseUrl>/_functions/<functionName>, so checks P1 through P4 are structurally unreachable there. On Ghost and Squarespace the 404 is correct and there is nothing to fix.

  1. Confirm the file is real, not a catch-all response. Request a path under .well-known/mcp/ that has never existed. If it returns the same body, your platform is answering everything with the app shell and the card proves nothing.

  2. On WordPress the card is one surface out of several, and the surfaces have to agree with each other. Start with ThinkRank: one plugin owns the whole agent-facing set, so you never end up arbitrating between two SEO plugins that each believe they own robots.txt, and switching costs nothing because it imports what Rank Math, Yoast, All in One SEO or SEOPress already hold. The alternatives are real: Rank Math's on-page keyword scoring is better, Yoast integrates with more page builders. Neither of them writes an llms.txt file.

  3. On Shopify the same logic points at StoreSEO, which builds llms.txt out of your live catalogue (products, collections, pages, articles) and gives you an agents.md editor. That is the half a .liquid edit cannot keep current, because the catalogue moves and the file has to move with it. Its listing showed a 5.0 rating across 742 reviews on the Shopify App Store when checked on 10 September 2026. Worth knowing before you start: a Shopify storefront already answers POST /api/mcp whether a card exists or not, which is the mismatch the next section is about.

Confirm the card is actually being served

One command answers this. Run npx aiscan-cli yoursite.com, or drop the URL into the box at aiscan.site; there is no signup and no charge. The capabilities dimension reads back every discovery surface in one pass: P1 API catalog, P2 MCP server card, P3 Agent Skills index, P4 OAuth discovery and E2 the service description. Pass on P2 means the card was found and read. The check IDs are documented on the capabilities checks page.

If you would rather check by hand, three requests settle it:

curl -sS -o /dev/null -w '%{http_code} %{content_type} %{size_download}\n' \
  https://yoursite.com/.well-known/mcp/server-card.json
curl -sS -o /dev/null -w '%{http_code} %{size_download}\n' \
  https://yoursite.com/.well-known/mcp/zz-does-not-exist.json
curl -sS https://yoursite.com/.well-known/mcp/server-card.json | jq -e '.name, .description, .version'
What you seeWhat it meansFirst step
200, application/json, control path 404Card is live and realNothing
200 on both, same byte countCatch-all route, not a cardFix routing before anything else
200 but text/htmlYour host is serving the app shellSet the content type explicitly
jq exits non-zeroMissing a required fieldAdd name, description, version
404No cardFollow path two above

Re-check after any deploy that changes routing. A card is a static file, so the failure mode is silent: nothing breaks, the file just stops being served.

Where AIScan fits, and where it doesn't

Two limits in our own check, both measured rather than guessed, both open on our backlog.

P2 decides on a status code. Grouping the evidence strings across 502 corpus hosts on 10 September 2026 returns a short vocabulary: HTTP 404 on 375 informational results, and the literal string HTTP 200 on 43 partials and 19 passes. The same evidence, two verdicts, and no statement of what the file contained. A live example from this run: one host serving a card with name, description, version, remotes and capabilities was graded partial, evidence "HTTP 200". The fix is one line, to parse the body and say what was found, and it belongs in our codebase rather than in your configuration.

P2 probes one path and misses the live endpoint. Across 30 Shopify storefronts, zero published /.well-known/mcp/server-card.json while 19 answered POST /api/mcp with a tool list. Those stores have a working MCP server and score as though they have nothing. The same shape affects P3, which probes /.well-known/agent-skills/index.json and not the legacy /.well-known/skills/index.json; across 52 documentation services the two paths reach 10 and 5 sites, the union is 11, and only 4 publish both.

So a passing P2 is evidence the file exists. It is not yet evidence the file is any good, and a failing P2 is not evidence you have no MCP server. Read it alongside what your platform already exposes.

Publish the card before the specification lands

The nameplate convention is unfinished, and the seven live files prove nobody is waiting for it to be finished. Write server.json because it is specified today, publish it to the registry if your server is public, and serve a subset at the well-known path so the scanners and marketplaces that already probe it find something to read.

Then check the whole discovery layer rather than one file: run npx aiscan-cli yoursite.com and read P1, P2, P3, P4 and E2 together, since a card pointing at an endpoint with no OAuth metadata leaves an agent one step short. Our platform walkthroughs, including the WebMCP measurement, the agentic commerce protocol study and the CI gate that catches a phantom file, are collected at aiscan.site/guides.

Frequently asked questions

My AIScan report says P2 is HTTP 404, but my MCP server works. Is the scan wrong?

Both are right. P2 probes one static path, /.well-known/mcp/server-card.json, and reports what it finds there. A working MCP endpoint at a different URL is invisible to that probe. Across 30 Shopify storefronts we measured, zero published a card while 19 answered POST /api/mcp with a tool list. If your server is live, the 404 means you have no card, not that you have no server. Publish the card so clients that have never met you can find the endpoint.

I published a card and AIScan reports P2 as partial rather than pass. What is missing?

Probably nothing in your file. Grouping P2's evidence strings across 502 corpus hosts on 10 September 2026 returns the literal string HTTP 200 for 43 partials and 19 passes, so the check awards two different verdicts on identical recorded evidence and never says what the body contained. This is an open bug on our side, not a configuration problem on yours. Confirm your card parses as JSON and carries name, description and version, then treat a partial as a pass.

My server card returns HTTP 200 but agents still cannot read it. What is wrong?

Check two things. First, the content type: if your host returns text/html, most clients will not parse it, so set application/json explicitly. Second, request a path under .well-known/mcp/ that has never existed. If it returns the same body and byte count as your card, your platform is answering every unknown path with the app shell and your card is not really being served. Two of the 79 hosts we probed were in exactly that state, reproduced on a second pass.

Which schema should I validate my server card against?

None of the card schemas resolve today. Four of the seven live cards we fetched declare a $schema, between them naming three different URLs, and all three return HTTP 404. The one MCP schema URL that does resolve is server.json's, hosted at static.modelcontextprotocol.io. Validate against that and use its field names, because the Server Card Working Group charter says the card format should stay as close as possible to a subset of server.json.

Do I need both a registry entry and a well-known card?

They answer different questions. The registry entry gets you into the REST API that MCP marketplaces and aggregators poll, which is how a client that has never heard of your domain finds you at all. The well-known card is what a crawler or a readiness scanner reads when it already has your domain. If your server is public, do both. If the registry will not accept it, the card is your only discovery surface.

mcp-publisher publish fails with a namespace error. How do I fix it?

The name field in server.json has to match the namespace your login authorised. GitHub login only authorises io.github.<your-username>/*, so a name like com.example/analytics will be rejected. Either change the name to match, or authenticate against the domain instead using DNS or HTTP login. For npm packages there is a second trap: the mcpName property in package.json must equal the name in server.json exactly.

Can I publish a private or internal MCP server to the official registry?

No. According to the registry documentation, it does not support servers that are only reachable by a narrow set of users, such as a host on a private network or a package on a private artifact registry. A remote server must be publicly accessible at its stated URL. For internal servers, run your own registry implementing the published OpenAPI interface, or serve a well-known card on the internal host and skip the registry entirely.

Where does the card go on WordPress, Shopify, Squarespace or Wix?

On WordPress, a real file under the web root, because .well-known is served before PHP runs. On Shopify you cannot add an arbitrary .well-known file, though the storefront already answers POST /api/mcp. On Squarespace and Wix there is no route for it on any plan. Wix documents the reason: a custom site API answers only at <baseUrl>/_functions/<functionName>, so checks P1 through P4 cannot pass there and the 404 is correct.

Related guides