Wrap OAuth around all of MCP and the directories report zero tools
A developer's four-tool server read as empty on glama, Smithery and mcp.directory, because tools/list arrives without a token. The fix is a four-method allowlist.
The Engineer · Build desk
What happened
- A remote MCP server behind OAuth 2.1 was registered across the directories, and every one of them listed it as having no tools.
- glama's API returned the server with its name and hosting attributes intact and an empty tools array.
- Smithery's page and mcp.directory showed the same nothing for a server running four working tools.
- Smithery's scanner log halted at an authorization prompt and only recorded four tools after a human completed the OAuth flow by hand.
- The cause was placement: tools/list is an ordinary JSON-RPC method, so wrapping the handler put capability discovery behind the token.
Why it matters
- What it limits constraint Registry coverage buys nothing without a capability list, and because directories mirror each other, the empty version travels further than any correction will.
- What doesn't add up contradiction Smithery holds four tools from an authorized scan while its public page shows none, so asking a directory to re-crawl does not fix an operator's listing; only changing the server does.
- What it costs cost The bill arrives as installs that never happen, and it never shows up in server logs, so the detector of last resort is a human reading your own directory page.
- What's now possible capability Splitting the surface lets an anonymous crawler read the tool list while anything touching credits or user data stays gated, with the charging path refusing calls that arrive with no caller.
The allowlist that repairs this is four method names long: initialize, notifications/initialized, ping, and tools/list [12]. Nothing on that list spends money, which is the test the author of the dev.to writeup applies. Describing what a server offers is not privileged, and calling those tools is [12]. The original wrapper was not sloppy, either. Every tool call in that server spends credits, so every tool call needs an identified user [7], and the docs and examples encourage wrapping the handler [6]. The wrapper simply also caught the one method that costs nothing to answer.
The conditions around the allowlist are the part worth reading twice, because each is a failure someone else will rediscover in production. A request carrying an Authorization header goes down the authenticated path even when its method is public; skip that and a client holding an expired token gets quiet anonymous service instead of the 401 that triggers a refresh [13]. The gate applies only to POST, because in Streamable HTTP a GET opens the SSE stream and a DELETE ends the session, and neither carries a JSON-RPC method you can inspect [14]. And since JSON-RPC permits batching, the check has to hold for every message in the array rather than any of them, or a tools/call rides in beside a tools/list [15].
That last condition has a runtime price: the classifier clones and parses the body of every POST before it knows whether auth applies [17]. Behind it sits a second stop. The public path runs with no caller in context, so a tools/call that slipped through finds no user and the charging function refuses [16]. The author also names the mistake that would cost more than an empty listing: a hand-rolled 401 that stops advertising WWW-Authenticate, which strips compliant clients of the header they rely on for OAuth discovery [19].
Three separate directories published the same empty array for the same server [18], which says the emptiness is not one crawler's bug but the predictable output of an anonymous handshake meeting a blanket 401 [8]. A crawler cannot distinguish "requires auth" from "does nothing" [8], and neither can your logs, where its 401 looks like any other unauthenticated request [20]. Middleware that wraps an exported handler operates at the granularity of the transport, not the method, and MCP's first useful move is anonymous by construction [4]. Auth strength was never in question here. Placement was, and placement is the thing no test suite was watching.
What to watch
- Whether glama, Smithery or mcp.directory start rendering capabilities captured under an authorized scan instead of the anonymous one, and whether they label "auth required" separately from "no tools".
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence52
- Adoption28
- Hype gap+22
- Incentives58
- Confidence56
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
A developer shipped a remote MCP server behind OAuth 2.1, registered it across directories, and found every directory listing it as having no tools at all.
- [2]
glama's API returned the server as {"name": "FrameThrower MCP Server", "attributes": ["author:official", "hosting:remote-capable"], "tools": []}.
- [3]
Smithery's page and mcp.directory rendered the same empty capability list for a server with four working tools.
- [4]
An MCP client discovers what a server can do by calling tools/list, a normal JSON-RPC method, so a handler wrapped in auth puts tools/list behind auth along with everything else.
- [5]
The server's handler used withMcpAuth, returned a 401 Unauthorized when no session user id was present, and exported the same authed function as GET, POST and DELETE.
- [6]
The author states that wrapping the handler in auth is what the docs and every example encourage.
- [7]
Every tool call on the server spends credits, so every tool call needs an identified user.
- [8]
A directory crawler has no account: it performs the handshake, gets a 401, and records a name, a URL and an empty capability list, and it cannot tell the difference between a server that requires auth and one that does nothing.
- [9]
Smithery's scanner log discovered server metadata, reported OAuth required, stopped at "Authentication required. Please authorize at: https://connect.smithery.ai/...", and reported "Capabilities found: 4 tools" only after a human clicked through the interactive OAuth authorization.
- [10]
The scanner's four-tool result came from a one-off human authorization and is not what the public Smithery page renders, so the listing still told visitors the server had no capabilities.
- [11]
MCP directories are the discovery layer, a listing with no tools is dead rather than weak, and every directory that mirrors another copies the emptiness forward, so a server can be registered on every registry and still be invisible.
- [12]
The fix defines PUBLIC_METHODS as initialize, notifications/initialized, ping and tools/list, routes a request classified as a public handshake to the unauthenticated handler and everything else to the authed handler, on the principle that describing what a server offers is not privileged while calling those tools is.
- [13]
A request carrying an Authorization header is sent down the authenticated path even when the method is public, because without that rule a client holding an expired token would silently fall back to anonymous access instead of getting the 401 that triggers a refresh.
- [14]
The public path applies to POST only, because in Streamable HTTP a GET opens the SSE stream and a DELETE terminates the session, and neither carries a JSON-RPC method that can be inspected.
- [15]
JSON-RPC allows batching, so the classifier requires every message in a batch to be a public method (.every, never .some); a batch mixing tools/list with tools/call is not a public request.
- [16]
As a second line of defence, the public path runs with no caller in context, so a tools/call reaching it finds no user and the charging function refuses, making the gate fail closed from both directions.
- [17]
The published classifier inspects the request by calling req.clone().json() and checking the method of each message before deciding whether the request is a public handshake.
- [18]
Three distinct directories (glama, Smithery and mcp.directory) published an empty capability list for the same server.
- [19]
The author identifies losing OAuth discovery as the failure mode that would actually hurt: if the 401 stops advertising WWW-Authenticate, compliant clients no longer have that signal.
- [20]
The author states that if you run a remote MCP server behind OAuth 2.1 this is almost certainly happening to you and nothing in your logs will tell you.
Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toIf your MCP server uses OAuth, every directory thinks it has zero tools
1 article · August 22, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- Agent tool discoveryFollow
- MCP directories and registriesFollow
- Silent Failure ModesFollow
- MCP server authorizationFollow