Your MCP server returns a login page or 403 instead of 401
Something in front of your MCP server is enforcing authentication built for humans in browsers. Instead of the 401 an MCP client needs, it returns a login page, a redirect to one, or a bare 403 — so the client never learns how to authenticate.
- MCP Specification
- 2026-07-28
Is this you?
Call the endpoint with no credentials and read the raw status line and headers. This separates a platform gate from an application-level auth problem before any code changes.
- This error
- A 302 or 307 with a
Locationpointing at a login page, a 200 whose body starts with<!DOCTYPE html>, or a 403 with noWWW-Authenticateheader. Any of these means the gate answered, not your server. - Then the fix is the next section.
- Working
- HTTP/1.1 401, a JSON body, and — if you are running the spec OAuth flow — a
WWW-Authenticate: Bearerheader naming your resource metadata URL. A successful 200 with a JSON-RPC result is also fine; it means auth is not the problem. - Then this is not your problem — try the error reference.
3 other strings clients print for this
- MCP client gets HTML instead of JSON
- Unexpected token '<', "<!DOCTYPE "... is not valid JSON
- MCP server connection failed, received 403 Forbidden
Fix it
A platform-level access gate is intercepting the request before your server sees it. Either disable that gate for the MCP route and handle authentication in your own code, or configure it to return 401 for unauthenticated API requests instead of redirecting. Every fix below is one of those two moves.
Have your agent fix this
Copies a prompt that sends your coding agent to this page, then has it find and apply the right fix for your project.
The cause depends on your platform. Pick yours.
What is in front of your server?
Vercel
If you deploy on Vercel and have Deployment Protection enabled
- 1 Vercel's own documentation states that automated systems receive a login page or a 403 rather than your deployment content. This applies to Vercel Authentication and Password Protection alike.
- 2 For a preview or internal deployment, send the bypass secret as a header on every request. Generate it in Project Settings under Deployment Protection.
- 3 For a production MCP server, turn Deployment Protection off for that project and authenticate in your own handler instead — the gate is not a substitute for application auth.
Azure App Service
If you use Azure App Service or Functions built-in authentication (Easy Auth)
- 1 By default Easy Auth redirects unauthenticated requests to the identity provider's login page.
- 2 Set the unauthenticated action to return 401 instead of redirecting, so API clients get a status they can act on.
- 3 If your MCP server validates tokens itself, set the action to allow anonymous requests and let your handler enforce auth.
AWS Load Balancer
If your MCP server sits behind an AWS Application Load Balancer with an OIDC action
- 1 The
authenticate-oidcaction performs a browser redirect flow, which an MCP client cannot complete. - 2 Add a listener rule that matches your MCP path and forwards straight to the target group, ahead of the authenticate rule.
- 3 Validate the bearer token in the MCP server, or in front of it with something that speaks the MCP auth flow.
oauth2-proxy
If you run oauth2-proxy in front of the server on Kubernetes
- 1 oauth2-proxy is a browser cookie-session proxy. It is the reflexive answer for protecting an internal service and it is structurally wrong for MCP, because the client presents a bearer token and has no cookie jar.
- 2 Route the MCP path around oauth2-proxy, and validate the token in the server or at an MCP-aware gateway.
- 3 If you only need to accept JWTs from your existing identity provider, a JWT filter on your ingress or gateway is the simpler and correct tool.
Netlify
If you deploy on Netlify with site-level password protection
- 1 Password protection serves an HTML form rather than a 401, and a missing fallback rule can produce a bare 404 instead.
- 2 Remove site-level protection from the MCP function's path and authenticate inside the function.
- 3 Note that Netlify streaming functions carry a 10-second execution limit, which is a separate constraint worth checking while you are here.
Something else
Any gate built for browser sessions fails the same way, so the fix has the same two shapes wherever it lives. Either exempt the MCP route from the gate and authenticate inside your server, or configure the gate to answer an unauthenticated API request with 401 rather than a redirect. Find the component that answered the curl above — a proxy, an ingress annotation, an identity-aware proxy, a platform setting — and change it at that layer, not in your MCP handler.
Why it happens
Platform access controls — Vercel Deployment Protection, Azure Easy Auth, an AWS load balancer OIDC action, oauth2-proxy, a site password — assume the caller is a browser that can follow a redirect, render a login form, and come back with a cookie. An MCP client can do none of that. It expects HTTP 401, and for the spec OAuth flow it expects a WWW-Authenticate header telling it where to authenticate. A redirect to a login page is a 200 or 302 carrying HTML, so the client either reports a JSON parse error or a generic connection failure, and the error text rarely mentions authentication at all.
The exchange
Each step in the exchange, in order.
- MCP client
- Platform auth gate expects a browser
- MCP server
-
MCP client to Platform auth gate
POST /mcp Accept: text/event-stream -
An MCP client cannot render a login form or come back with a cookie. It needs a 401, and for the spec OAuth flow a WWW-Authenticate header naming where to authenticate. It reports a JSON parse error or a bare connection failure instead.
Platform auth gate to MCP client
302 Found → /login Content-Type: text/htmlFlow stops here — MCP server not reached
What lands on you next
A login redirect meant no agent ever reached the route. Once one can, the route is a public entry point into your tools, and the questions change shape.
-
Anyone with a token can now reach every tool on this server.
-
mcp-capability-filter-inboundAn allow-list of tools, prompts and resources per route. A call to a capability the route does not expose is refused with
MethodNotFoundbefore it is forwarded. -
What stops one agent looping on an expensive tool?
-
rate-limit-inboundLimits counted against the authenticated identity rather than a shared egress IP, so one agent's retry storm does not spend another team's budget.
-
The platform gate used to be our audit log. What replaces it?
-
capability_invocationEvery tool call emits an event carrying the caller, the capability, the upstream, the outcome and the latency. Tokens and request bodies are never logged.
All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out.
Stop debugging auth on every MCP server
A gateway in front of your MCP servers handles discovery, audience binding, and token validation once — for every server and every client.