Shipping a remote MCP server with OAuth: what the clients actually check A developer at Prisma Reviews deployed a remote MCP server in front of the company's reviews API, letting Claude, Cursor, VS Code and other MCP clients read reviews across 84 platforms and, with user approval, publish replies where platform APIs allow. The writeup details the OAuth discovery flow clients actually follow: the server returns a 401 with a WWW-Authenticate header pointing to RFC 9728 resource metadata, exposes its tool list via a static server card at /.well-known/mcp/server-card.json rather than answering unauthenticated initialize calls with 200, and accepts both OAuth and Bearer/X-API-Key headers. The server uses stateless JSON responses with no SSE stream or session id, supports three MCP protocol versions, and marks its seven read-only tools with readOnlyHint while flagging reply_to_review as destructive. Two weeks ago we put a remote MCP server in front of our reviews API. It lets Claude, Cursor, VS Code and other MCP clients read reviews from the 84 platforms the API covers, and publish a reply, with the user's approval, on the ones whose API allows replies. How few of them do is the subject of an earlier post https://dev.to/fransopa/state-of-review-platform-apis-in-2026-only-8-of-32-let-you-reply-16-have-no-api-at-all-4dnb . The tools were the easy part, because each one forwards to a REST route we already had. Most of the work went into getting clients to connect without a pasted API key. These are the things that mattered, in the order we ran into them. A connector UI that supports OAuth doesn't know your login page. It finds it by calling the server with no token and reading the 401 response: HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer resource metadata="https://api.prismareviews.com/.well-known/oauth-protected-resource/mcp", scope="reviews" Access-Control-Expose-Headers: WWW-Authenticate The resource metadata URL RFC 9728 points to a small JSON document that names the authorization server. From there the client reads /.well-known/oauth-authorization-server and gets the authorize, token, registration and revocation endpoints. Ours advertises only authorization code with PKCE S256 and public clients token endpoint auth methods supported: "none" , because that's all an MCP client needs. Two things bit us: Access-Control-Expose-Headers the client never sees WWW-Authenticate and gives up. initialize and tools/list without auth, so that directories could list our tools. We didn't. A 200 on the first call is how a client decides that a server needs no login. If we had returned one, every connector would have skipped OAuth and then failed on the first real call. Instead we publish the tool list, with the input schemas, as a static server card at /.well-known/mcp/server-card.json . Directories read that; the MCP endpoint itself still answers 401. MCP clients usually register themselves RFC 7591 before they start the OAuth flow, and there are many of them. We didn't want a table that grows by one row each time someone opens a new Cursor window. So the flow keeps no state on our side: /v1 with a normal key, so the MCP layer doesn't need a second auth path. Validating redirect URIs took more care than we expected. Desktop and CLI clients come back to http://localhost or 127.0.0.1 on a random port, and editors use their own schemes, such as cursor:// and vscode:// . We allow https, loopback http and custom schemes. We reject plain http on the internet, URLs with a fragment, and javascript: , data: , file: and similar schemes. The consent screen asks whether to connect Live your real accounts or Test sample data from every platform . People who are only trying the connector don't need to connect a platform account first. It also gave the directory review a test account with data to look at, instead of an empty one. Claude Code, Cursor and VS Code configs take a header, and some people prefer an API key to OAuth. So the same URL also accepts Authorization: Bearer