Zuplo
MCP error

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.

TerminalRun this to check — it changes nothing
curl -i -sS -X POST https://your-server.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
This error
A 302 or 307 with a Location pointing at a login page, a 200 whose body starts with <!DOCTYPE html>, or a 403 with no WWW-Authenticate header. 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: Bearer header 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.

Vercel

If you deploy on Vercel and have Deployment Protection enabled

  1. 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. 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. 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.
Terminalbash
# Bypass header for a protected deployment
curl -X POST https://your-app.vercel.app/mcp \
  -H "x-vercel-protection-bypass: $VERCEL_AUTOMATION_BYPASS_SECRET" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Vercel: automated agent access

Azure App Service

If you use Azure App Service or Functions built-in authentication (Easy Auth)

  1. 1 By default Easy Auth redirects unauthenticated requests to the identity provider's login page.
  2. 2 Set the unauthenticated action to return 401 instead of redirecting, so API clients get a status they can act on.
  3. 3 If your MCP server validates tokens itself, set the action to allow anonymous requests and let your handler enforce auth.
Terminalbash
az webapp auth update \
  --name <app-name> \
  --resource-group <group> \
  --unauthenticated-client-action Return401
Azure App Service authentication

AWS Load Balancer

If your MCP server sits behind an AWS Application Load Balancer with an OIDC action

  1. 1 The authenticate-oidc action performs a browser redirect flow, which an MCP client cannot complete.
  2. 2 Add a listener rule that matches your MCP path and forwards straight to the target group, ahead of the authenticate rule.
  3. 3 Validate the bearer token in the MCP server, or in front of it with something that speaks the MCP auth flow.
ALB: authenticate users

oauth2-proxy

If you run oauth2-proxy in front of the server on Kubernetes

  1. 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. 2 Route the MCP path around oauth2-proxy, and validate the token in the server or at an MCP-aware gateway.
  3. 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.
oauth2-proxy configuration

Netlify

If you deploy on Netlify with site-level password protection

  1. 1 Password protection serves an HTML form rather than a 401, and a missing fallback rule can produce a bare 404 instead.
  2. 2 Remove site-level protection from the MCP function's path and authenticate inside the function.
  3. 3 Note that Netlify streaming functions carry a 10-second execution limit, which is a separate constraint worth checking while you are here.
Netlify functions reference

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
  1. MCP client to Platform auth gate

    POST /mcp Accept: text/event-stream
  2. 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/html

    Flow stops here — MCP server not reached

The MCP client posts to the server. A platform auth gate answers first with a 302 to a login page, so the exchange ends at the gate and the MCP server never sees the request. A 403 with an HTML body fails the same way.
Zuplo

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

An allow-list of tools, prompts and resources per route. A call to a capability the route does not expose is refused with MethodNotFound before it is forwarded.

What stops one agent looping on an expensive tool?

rate-limit-inbound

Limits 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_invocation

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