跳到正文
DEV Community· wolfejam.dev·· 3 小時前AI 評分65

MCP Server Card 普查重現指南:流程與四種統計口徑

How to Shuffle the Pack: MCP Server Card census reproduction guidance

AI 導讀

wolfejam.dev 提供重現 MCP Server Card 普查的流程,使用三個唯讀腳本讀取公開登錄資料、檢查伺服器並彙總結果。截至 10 月 20 日,流程把 162,532 個登錄條目縮至 30,142 個唯一地址,排除 304 個範本後檢查 29,838 個地址。

正文

TL;DR: The numbers in Checked Yours? come from three small scripts, 166 lines in all, that only ever read. You can run them yourself in an afternoon. This is how, step by step, and the four choices that move the answer from 8.5% to under 3%.

In Plain English
What the census is. A count of how many MCP servers in the public registry publish a Server Card, the small file that tells an AI app who a server is and how to connect.
What this post does. Walks through the count in three steps: read the registry, check each server, add it up. Then a fourth, to compare two runs.
Why you'd run it. To check our numbers, to count again next week, or to point the same scripts at your own list of servers.

The steps follow below.

What you need

  • Node.js 22 or later, and git.
  • About three and a half hours, mostly waiting. Step 2 is resumable: stop it and start it again, and it picks up where it left off.
  • Nothing else. No keys, no accounts. Everything it reads is public.
git clone https://github.com/Wolfe-Jam/mcp-server-card-check
cd mcp-server-card-check
npm ci && npm run build
export CENSUS_DIR=./census-data && mkdir -p "$CENSUS_DIR"

Step 1: read the deck

node scripts/census/registry.mjs

The MCP Registry lists every published version of every server, 100 to a page. registry.mjs reads every page (about 20 minutes) and keeps one card per server:

  • the latest version only, not every release;
  • only servers with a remote endpoint (streamable-http). A server you install and run locally has no URL to check;
  • each URL once. Many entries point at the same address.

On October 20 that took 162,532 entries down to 30,142 unique addresses. 304 of those were templates, like https://{tenant}.example.com/mcp. They can't be requested as written, so they're set aside, not guessed. That leaves 29,838 to check.

If a page fails, the script waits and tries again, up to five times. It saves a checkpoint as it goes, so a dropped connection costs a page, not the whole read.

Step 2: check every card, read-only

CONCURRENCY=16 node scripts/census/probe.mjs

For each address, probe.mjs runs the same discovery checks as the Server Card Check web page: it looks for a card at <url>/server-card, then in the AI Catalog at /.well-known/ai-catalog.json, and checks what it finds against the v1 schema and the hosting rules (CORS, caching).

It can't change anything, by construction. Every request goes through this guard:

const readOnlyFetch = (url, init = {}) => {
  const method = (init.method ?? "GET").toUpperCase();
  if (method !== "GET" && method !== "OPTIONS") return Promise.reject(new Error(`census is read-only: ${method} refused`));
  return fetch(url, { ...init, redirect: "follow", headers: { ...(init.headers ?? {}), "User-Agent": UA } });
};

GET and OPTIONS only. The checker's transport checks send a POST, so for the census they're recorded as skipped, and nothing is ever sent to a server's MCP endpoint.

Be a good guest. At most 16 requests at a time, a 10-second timeout each, and a User-Agent that names the project and links to it, so anyone who sees the traffic knows what it is.

Results go to census.jsonl, one line per address. Stop it whenever you like; addresses already in the file are skipped next time. On October 20 the checks ran from 12:02 to 15:14 UTC, with no errors.

Step 3: count the hand

node scripts/census/summarize.mjs

No network. summarize.mjs is arithmetic over the two files from steps 1 and 2, and writes summary.json. A card is a 200 at a card location that parses as JSON. A valid card also passes the v1 schema. A 200 that isn't JSON (usually a website's catch-all page) is counted separately.

That gives four outcomes for every address, and they are different problems with different fixes. A reader asked for this split after the October 20 post. Here it is for the 27,966 endpoints outside the largest publisher:

Outcome Endpoints Share The fix
No card 25,654 91.7% Publish one
A web page where the card should be 733 2.6% Serve JSON there, or a 404
A card that fails validation 912 3.3% The checker names the field
A valid card 667 2.4%

More cards fail than pass. Every row comes from summary.json: no card is checked minus answered; failing is JSON cards minus valid ones.

Shuffle the pack: four choices that move the answer

The same rows give very different headlines, depending on what you divide by. October 20:

Count by Valid Share
Endpoint 2,539 of 29,838 8.51%
Endpoint, leaving out the largest publisher 667 of 27,966 2.39%
Distinct host 586 of 21,526 2.72%
Registry namespace 528 of 18,856 2.80%
Namespaces with a single endpoint 471 of 17,519 2.69%
  1. Entries are not servers. One publisher put 1,872 valid cards behind one gateway. Count endpoints and they're 74% of the valid cards. Count hosts, namespaces, or leave that publisher out, and every answer lands under 3%. The script reports the largest publisher separately, so you can see the effect, but it doesn't name it in the headline.
  2. Latest version only. Counting every release would count the same server many times.
  3. Templates are skipped, not filled in. Guessing a tenant name would be checking a server that may not exist.
  4. A web page isn't a card. 733 addresses answered the card's URL with 200 OK and HTML. Count every 200 as "has a card" and the figure goes up by about a fifth (4,184 instead of 3,451).

None of these is hidden. Each one is a line in summarize.mjs, and summary.json carries every denominator, so you can pick another one and say so.

Step 4: compare two runs, or two checkers

CENSUS_DIR=./census-data A=0.1.1 B=0.4.1 SAMPLE=200 npm run census:diff

When the checker's rules change between two counts, is a change in the numbers the servers or the checker? census:diff answers that. It installs both versions from npm and checks each sampled endpoint with A and then B, in the same minute, so a changed verdict comes from the code, not from the server changing in between. The sample is seeded (SEED), so the same command picks the same endpoints again.

Before the October 20 post we ran it on 200 endpoints. The October 12 way of counting CORS gave the same verdict on every JSON card in the sample under both versions. So 179 → 324 cards sending all four CORS headers was the servers, not the yardstick.

Run yours

Point the same scripts at your own servers, or count the registry again next week and compare. If your numbers differ from ours, open an issue on the repository. A count anyone can repeat is worth more than one only we can.

Run a remote MCP server? Paste its address into Server Card Check. Checked yours?


Originally published at faf.one on October 21, 2026.

來源:DEV Community · dev.to