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.

Diyan BogdanovDiyan Bogdanov4 min read
#mcp#distribution#auth

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 -l

A 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.

authenticatedanonymous
concurrent sessions10250
idle timeout30 min2 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.

Stay Updated

Get the latest on AI agent identity, delivered weekly.