Skip to content

Error 401: invalid_token — OAuth token rejected

Your agent got a 401 from the tunnel:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
error_description="the access token is missing, expired, or invalid",
resource_metadata="https://<your-host>/.well-known/oauth-protected-resource"
{
"error": "invalid_token",
"error_description": "this tunnel requires an OAuth 2.1 access token",
"resource_metadata": "https://<your-host>/.well-known/oauth-protected-resource"
}

This only happens on tunnels whose auth mode is OAuth. A tunnel in the default mode does not gate agent requests at all, so if you are seeing this and did not expect authentication, check Settings → the tunnel’s auth mode first.

Every rejection reason collapses to invalid_token. Malformed token, unsupported algorithm, unknown signing key, bad signature, expired, wrong issuer, wrong audience — all of them. The code does not tell you which.

The one exception is a missing token: the WWW-Authenticate header then carries no error= parameter at all, just the resource_metadata pointer. (The JSON body still says "error": "unauthorized" in that case — the header and the body use different words for the same situation.)

So do not try to branch on the code. Work through the causes below instead.

No token at all. The most common case, and the one the challenge is designed for — see the discovery flow below.

Wrong audience. A token minted for a different host or tunnel. Each tunnel’s tokens are bound to its own canonical https://<host>, so a token that works for one tunnel is invalid on another by design. This is the failure to suspect when a token that “definitely works” suddenly does not — check which host it was issued for.

Expired. Access tokens are short-lived. Headless agents that cache a token and never refresh will hit this on a schedule.

Consent revoked. The tunnel owner can revoke an agent’s consent from the dashboard. Existing tokens stop being accepted.

Wrong issuer, bad signature, unknown key. Points at a token from an entirely different system, or a signing key that has been rotated out.

The resource_metadata pointer is the fix path

Section titled “The resource_metadata pointer is the fix path”

That URL is a RFC 9728 Protected Resource Metadata document, and a spec-compliant MCP client uses it automatically: it reads the challenge, fetches the metadata, discovers where to get a token, obtains one and retries — without you configuring an issuer anywhere.

If your client supports that flow, a 401 on first contact is not an error to fix; it is the handshake working. If your client does not, you need to attach a token yourself.

The interactive flow expects a human at a browser to approve the agent on a consent screen. For a cron job, a container or a server-side agent, mint a Service Token in the tunnel’s Settings instead. It is validated exactly like a regular access token — same signature, same audience binding, same revocation — but it is long-lived and needs no browser.

See Authentication: OAuth & Service Tokens.

Two OAuth failures deliberately return 500, because they are service-side problems and not something the caller can fix by presenting a better token:

  • "OAuth is not configured on this edge" — the tunnel asks for OAuth mode but no issuer is configured.
  • "token issuer is unreachable" — the signing keys could not be fetched, and mcppipe failed closed rather than accepting an unverifiable token.

If you see either, retrying with a different token will not help.

  • Request blocked before authentication ran: 403 — the IP allowlist is checked first.
  • Token accepted, quota or rate limit refused it: 429.