Your MCP Server Is Invisible to Directories
A 401 on initialize hides your MCP server from every directory that indexes them. How to check yours, and the rate-limit trap in the fix.
Our MCP server had been listed on two of the biggest directories for weeks. Both showed it. Neither had ever managed to read a single tool from it.
Glama said Unhealthy, with a line underneath that turned out to be the whole story:
Tool Schema Changelog: No tool schema history has been recorded yet.
Smithery was worse, and more specific:
mcp.useanima.sh ✕ AUTH TIMED OUT 5m 12s
mcp.useanima.sh CANCELLED 3m 34s
Five minutes of a crawler waiting for an authentication handshake that was never going to complete. Both listings existed. Both were dead.
The cause is one line of correct-looking behaviour#
Our server required a Bearer token on every request, including initialize. A
credential-less client got:
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://mcp.useanima.sh/.well-known/oauth-protected-resource"That is a defensible reading of the auth spec. It is also, in practice, a decision to be invisible.
Every directory that indexes MCP servers works the same way: open a connection, call
initialize, list the tools, record what it found. Glama says so outright — it checks
every connector hourly by opening an MCP connection and listing its tools. If initialize
returns 401, the crawler learns nothing, and a listing with no tools is a listing nobody
finds. Glama only indexes healthy connectors in its search, so ours was excluded from the
one place it would have been discovered.
It cascades further than that. The awesome-mcp-servers list — 94k stars — gates new
entries on a Glama listing that passes checks, and a bot says so on the pull request. Ours
had nothing to point at: a connector Glama has marked unhealthy has no passing checks. One
bad reply to initialize had quietly closed a second distribution channel too.
Check yours in thirty seconds#
No credentials. If this prints tools, crawlers can see you:
EP=https://your-server.example/mcp
SID=$(curl -s -D - -o /dev/null -X POST "$EP" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"probe","version":"1.0"}}}' \
| grep -i '^mcp-session-id:' | tr -d '\r' | awk '{print $2}')
curl -s -X POST "$EP" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' -o /dev/null
curl -s -X POST "$EP" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| grep -o '"name":"[a-z_]*"' | wc -lA 401 on the first call, or 0 at the end, means every directory sees what ours saw.
The fix, and what to keep#
Tool discovery is public API-surface documentation. Ours was already published in our docs, our skills and the MCP registry manifest, so requiring a token to read it protected nothing and cost us the listings. Tool execution is a different question and stays authenticated.
So: a request with no Authorization header gets an anonymous session limited to an
allowlist.
const ANONYMOUS_METHODS = new Set([
"initialize",
"notifications/initialized",
"ping",
"tools/list",
"prompts/list",
"resources/list",
"resources/templates/list",
]);Anything outside it — tools/call included — gets the same 401 and the same
WWW-Authenticate pointer it would have received before, so a client that meant to do real
work still learns exactly where to authenticate.
Three details matter more than the list itself:
Make it an allowlist, not a denylist. A method added upstream is then refused by default rather than silently reachable.
Include notifications/initialized. The client sends it to complete the handshake.
Refuse it and you break the exact flow you are trying to enable.
Give the anonymous client no credentials at all. Ours is constructed with an empty key, so a bug that let a tool through would reach the upstream API unauthenticated and be refused a second time.
The result, within minutes of deploying: Glama went from Unhealthy to Healthy with 67
tools recorded, and Smithery's five-minute timeout became SUCCESS in 19 seconds.
The trap on the other side#
Two hours later, Glama emailed to say the connector was unhealthy again.
HTTP 429 - Error connecting to MCP
Opening up introspection meant credential-less clients could now create sessions — and
those sessions were being metered against the authenticated budget. Ten concurrent per
key, keyed on the single shared string "anonymous", held for a thirty-minute idle
timeout. Crawlers do not send DELETE. The bucket filled and stayed full:
1:200 2:200 3:200 4:200 5:429 6:429 7:429 ... 14:429
We had written, in the pull request that introduced this, that a per-key bucket meant anonymous traffic "cannot crowd out an authenticated caller." That was true and beside the point. It crowded out other anonymous callers — which is every directory that indexes you, which was the entire reason for the change. Glama and Smithery were competing for the same ten slots.
Anonymous sessions are a different shape of thing: a handshake, a tools/list, seconds of
work, no upstream call. They need their own class.
| authenticated | anonymous | |
|---|---|---|
| concurrent sessions | 10 | 250 |
| idle timeout | 30 min | 2 min |
The part worth stealing#
Every test we had passed throughout both outages.
They passed during the first one because none of them asserted that tools/list actually
returned tools — only that the handshake succeeded. A server that accepts initialize and
then refuses discovery looks identical from that altitude, and that was precisely our
state.
They passed during the second because none of them opened a second session. Asserting one handshake proves nothing about the eleventh.
If you take one thing from this: a green suite is not evidence that anyone else can see your server. The only check that means anything is the one a crawler performs — no credentials, from outside, twelve times in a row.
We build Anima, identity and communication infrastructure for AI agents — an inbox, a phone number and a credential vault an agent owns. The MCP server described here is ours, and every mistake in this post was too.