---
title: "mcp_authentication_failed_error in Claude Managed Agents"
description: "Managed Agents matches vault credentials by normalized URL. A different path, subdomain or non-default port sends no credential at all — so the 401 is correct."
canonicalUrl: "https://zuplo.com/learn/mcp/errors/mcp-authentication-failed-error-managed-agents"
pageType: "mcp-error"
errorString: "mcp_authentication_failed_error"
lastVerified: "2026-07-29"
specVersion: "2026-07-28"
---

# `mcp_authentication_failed_error`

> A managed agent's connection to your MCP server failed on authentication: the server rejected the credential from the attached vault, required authentication when no matching credential was configured, or an `mcp_oauth` token refresh failed.

_MCP Specification 2026-07-28._

## Is this you?

The error says authentication failed, so check first whether any credential was sent. That is two reads and a string comparison: the URL the agent declares, and the URL the credential is keyed on. Only then is it worth asking whether the token itself still works — and for an `mcp_oauth` credential Anthropic will tell you, including the HTTP response its own `initialize` probe got back.

```bash
# 1. What URL does the agent actually declare?
#    A running session can replace agent.mcp_servers session-locally without
#    creating a new agent version, so read the session too if one exists:
#    GET /v1/sessions/$SESSION_ID | jq '.agent.mcp_servers'
curl -sS "https://api.anthropic.com/v1/agents/$AGENT_ID" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  | jq '.mcp_servers'

# 2. What URL is each credential in the attached vault keyed on?
curl -sS https://api.anthropic.com/v1/vaults/$VAULT_ID/credentials \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  | jq '.data[] | {display_name, type: .auth.type, url: .auth.mcp_server_url}'

# 3. mcp_oauth only: does the grant still work?
curl -sS -X POST \
  "https://api.anthropic.com/v1/vaults/$VAULT_ID/credentials/$CREDENTIAL_ID/mcp_oauth_validate?beta=true" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  | jq '{status, has_refresh_token, mcp_probe, refresh}'
```

**This error** — The URLs differ anywhere normalization does not reach: `/mcp` against `/mcp/v1`, `api.example.com` against `mcp.example.com`, or a `:8443` that is only on one side. That is not a rejected credential — nothing was sent. Or step 3 returns `"status": "invalid"`, with `mcp_probe.method` naming the handshake step that failed (`initialize`) and `mcp_probe.http_response` carrying what your server actually said — `"status_code": 401` and a `body` such as `{"error":"invalid_token"}` — beside a `refresh` block explaining why the token could not be renewed, `"status": "no_refresh_token"` when you never supplied one. `"status": "unknown"` is a 5xx, a 429 or a network failure: wait and retry rather than editing anything.

**Working** — The two URLs are the same string once you lowercase the scheme and host, strip a default port and strip a trailing slash — same subdomain, same path, same port. For an `mcp_oauth` credential, step 3 returns `"status": "valid"`. Anthropic publishes no validation endpoint for `static_bearer`, so a matching URL is the whole of what you can check here. If this is what you see, this is not your problem.

**Clients also report this as:**

- `Authentication with the MCP server failed: the server rejected the credential from the attached vault, required authentication when no matching credential was configured, or an OAuth token refresh failed.`
- `vault_credential.refresh_failed`
- `{"error":"invalid_token"}`

## Fix it

**In short.** Compare two strings: the `url` the agent declares in `mcp_servers[]`, and the `mcp_server_url` on the vault credential. Anthropic normalizes scheme and host casing, a default port and a trailing slash before matching them, and nothing else — so `https://mcp.example.com/mcp` and `https://mcp.example.com/mcp/v1` are two different servers. No credential is attached, the connection is attempted unauthenticated, and your server's `401` surfaces as an authentication failure rather than as the configuration error it is. `mcp_server_url` is immutable, so the fix is to archive that credential and create a new one keyed on the exact string the agent declares. If the two already match and the credential is `mcp_oauth`, `POST /v1/vaults/{vault_id}/credentials/{credential_id}/mcp_oauth_validate` returns `valid`, `invalid` — the grant is gone and the end user has to re-authorize — or `unknown`, which is transient: wait and retry.

The symptom is identical across platforms; the cause is not.

### Managed Agents vault

If the vault holds a credential you believe covers this server, and it still connects unauthenticated

1. Read the URL as stored rather than as you remember it. `GET /v1/agents/{agent_id}` returns `mcp_servers[]` with each `name` and `url`; pass `?version=` to read an earlier version if the agent has been modified since the credential was created. A session can also replace `agent.mcp_servers` for itself without creating a new agent version, so on a live session read the session rather than the agent. An agent may declare up to 20 MCP servers and a vault holds at most 20 credentials; each URL is matched on its own.
2. Compare that string to the credential's `mcp_server_url`. Normalization covers scheme and host casing, a default port, and a trailing slash, and nothing else: `https://api.example.com/mcp` matches `https://API.example.com/mcp/` and `https://api.example.com:443/mcp`, but not `https://api.example.com/mcp/v1`, `https://mcp.example.com/mcp`, or `https://api.example.com:8443/mcp`.
3. `mcp_server_url` is locked after creation, so the fix is to archive the credential and create a replacement keyed on the declared string — archiving purges the secret and frees the key for the new one. If the session attaches several vaults, the first vault with a match wins, so a stale credential in an earlier vault can shadow the correct one.

```bash
# The credential key is immutable: archive, then recreate on the exact URL.
curl --fail-with-body -sS -X POST \
  "https://api.anthropic.com/v1/vaults/$VAULT_ID/credentials/$CREDENTIAL_ID/archive" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01"

curl --fail-with-body -sS "https://api.anthropic.com/v1/vaults/$VAULT_ID/credentials" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  -H "content-type: application/json" \
  --data @- <<'EOF'
{
  "display_name": "Example MCP",
  "auth": {
    "type": "static_bearer",
    "mcp_server_url": "https://api.example.com/mcp/v1",
    "token": "your-token"
  }
}
EOF
```

[Anthropic: Managed Agents MCP connector](https://platform.claude.com/docs/en/managed-agents/mcp-connector)

### mcp_oauth credential

If the credential is `mcp_oauth` and the URLs already match

1. Ask Anthropic what happened. `mcp_oauth_validate` returns one of three statuses and each implies a different action: `valid` means the token works and the problem is elsewhere, `invalid` means the grant is gone or the authorization server rejected the refresh with a 4xx and the end user has to re-authorize, `unknown` means a transient 5xx, 429 or network failure and you should wait and retry.
2. Anthropic only refreshes when you supplied a `refresh` block. Without one, the validation response comes back with `"has_refresh_token": false` and `refresh.status: "no_refresh_token"`, and the access token stays whatever you last stored. With one, `refresh.token_endpoint_auth.type` has to match what your authorization server actually accepts — `none` for a public client, `client_secret_basic`, or `client_secret_post`.
3. Rotate the parts you can. `access_token`, `expires_at` and `refresh.refresh_token` are updatable and running sessions re-resolve credentials without a restart; `token_endpoint` and `client_id` are locked after creation, so changing either means archive and recreate. Subscribe to the `vault_credential.refresh_failed` webhook so a dead grant reaches you before the next session does.

```bash
# Rotate the token in place. Structural fields cannot be updated.
curl --fail-with-body -sS \
  "https://api.anthropic.com/v1/vaults/$VAULT_ID/credentials/$CREDENTIAL_ID" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  -H "content-type: application/json" \
  --data @- <<'EOF'
{
  "auth": {
    "type": "mcp_oauth",
    "access_token": "xoxp-new-...",
    "expires_at": "2099-12-31T23:59:59Z",
    "refresh": {"refresh_token": "xoxe-1-new-..."}
  }
}
EOF
```

[Anthropic: diagnose an OAuth refresh failure](https://platform.claude.com/docs/en/managed-agents/vaults#diagnose-an-oauth-refresh-failure)

### static_bearer credential

If the credential is `static_bearer`

1. Anthropic documents one validation endpoint and it is for `mcp_oauth` credentials, so there is nothing to ask about this one: credentials "are stored as provided and are not validated until session runtime." Test the token yourself with the same `initialize` request the OAuth probe makes, and read the status code rather than the body.
2. Split the result. If the server accepts the token from `curl` but the session still fails, the credential is not reaching it and the URL match above is your bug. If the server rejects it from `curl` too, the token is wrong, revoked, or issued for a different workspace or account than the one the server expects.
3. `static_bearer` sends the token as a bearer token and nothing else. An `mcp_servers[]` entry takes only `type`, `name` and `url`, and the two MCP credential types are `mcp_oauth` and `static_bearer`, so there is no documented way to send `X-API-Key` or any other header from Managed Agents. A server that insists on a custom header has to accept `Authorization: Bearer` as well, or sit behind something that translates. A fixed key is out of scope of the MCP authorization spec, which makes authorization OPTIONAL — not in conflict with it.

```bash
# The same handshake step the vault probe reports as "initialize".
curl -sS -o /dev/null -w '%{http_code}\n' -X POST \
  https://api.example.com/mcp/v1 \
  -H "Authorization: Bearer $MCP_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
        "protocolVersion":"2025-11-25",
        "capabilities":{},
        "clientInfo":{"name":"curl","version":"1.0"}}}'
```

[Anthropic: add a credential](https://platform.claude.com/docs/en/managed-agents/vaults#add-a-credential)

### Anthropic MCP tunnel

If the MCP server is private and reachable only through an MCP tunnel

1. The tunnel is transport, not authentication. Anthropic says it plainly: "the tunnel carries encrypted traffic to your MCP server but does not authenticate to it. If the upstream MCP server requires its own authentication (OAuth, bearer token), supply it the same way you would for any other MCP server." Configuring OAuth on each MCP server is listed as your organization's responsibility, not Anthropic's.
2. Key the credential on the tunnel URL, never the internal one. Each server gets a hostname under your tunnel domain, and when you attach it to a Managed Agents session in the Console you supply the Subdomain and the Path while the "Resolves to" line shows the exact URL. Copy that string into `mcp_server_url`; `https://mcp.internal.svc:8080/mcp` will never normalize to it. The path is your server's own — "the proxy forwards the path untouched."
3. Check which error you actually have before touching the tunnel stack. A routing or TLS fault in the tunnel is a network failure, which Anthropic classes as `mcp_connection_failed_error`; this page's error means the request reached something that wanted a credential. Note also that MCP tunnels are a research preview, provided "as-is" without any uptime, support, or continuity commitment, and tunnels created through the Console are not available as connectors in claude.ai.

[Anthropic: MCP tunnels overview](https://platform.claude.com/docs/en/agents-and-tools/mcp-tunnels/overview)

### Zuplo MCP Gateway

If the URL that has to match keeps moving, or one vault token is standing in for many upstream servers

1. Give the agent one origin that does not move. Each MCP route on the gateway is a URL you own, and a single deployment fronts any number of upstream MCP servers, one route per upstream — so the string in `mcp_servers[].url` and the string in `mcp_server_url` stop tracking whichever internal host and path the server happens to have this quarter.
2. The vault then holds one credential to the gateway, and the gateway owns the upstream side. `mcp-token-exchange-inbound` brokers a separate credential per upstream, storing and refreshing each user's grant; `set-upstream-api-key-inbound` covers an upstream that only takes a static key. The token presented to the gateway is never the token the gateway sends upstream, which is what the spec's `MUST NOT` on token passthrough requires.
3. If the upstream is private, the Zuplo secure tunnel dials outbound from inside your network and the gateway routes to internal DNS names, so nothing has to be exposed to the internet and there is no inbound rule to write.

[Zuplo MCP Gateway: upstream credentials](/docs/mcp-gateway/auth/upstream-oauth)

### Something else

Whatever the credential is, diagnose it in this order and stop at the first failure. One: was anything sent — does a credential in an attached vault match the declared URL once both are normalized? Two: does the token still work when you present it yourself? Three: can Anthropic renew it — is there a `refresh` block, and does its `token_endpoint_auth` match what your authorization server accepts? Because session creation validates none of this, `session.error` on the event stream and the `vault_credential.refresh_failed` webhook are the only signals you get, and the connection will keep retrying on every idle-to-running transition until the configuration changes.

## Why it happens

Managed Agents splits MCP configuration in two. Agent creation declares each server by `name` and `url`; session creation attaches credentials by passing `vault_ids`, and nothing joins the two halves except the URL string. Both sides are normalized before matching — scheme and host lowercased, default ports and trailing slashes stripped — and Anthropic is explicit that a different path, subdomain, or non-default port does not match, in which case "the connection is attempted unauthenticated." None of it is checked when you create the session: the session starts, interaction stays possible, and the failure arrives later as a `session.error` event carrying the affected `mcp_server_name` and a `retry_status`, retried on the next `session.status_idle` to `session.status_running` transition.

### The exchange

Your backend creates a session that references a vault. The managed agent tries to match a vault credential to the server URL the agent declared, normalizing both first, and finds nothing because the paths differ. It connects to the MCP server with no Authorization header, the server answers 401, and the failure reaches your backend as a session.error event of type mcp_authentication_failed_error — long after session creation returned successfully.

**Participants:** Your backend (creates the session), Managed agent (Anthropic control plane), Your MCP server (requires a bearer token)

1. **Your backend → Managed agent** — `POST /v1/sessions` — `vault_ids: ["vlt_01ABC…"]`
2. **Managed agent** (internal step) — Match credential by normalized URL — `no match: /mcp vs /mcp/v1`
3. **Managed agent → Your MCP server** — `POST /mcp` — `no Authorization header`
4. **Your MCP server → Managed agent** — `401 Unauthorized` — `WWW-Authenticate: Bearer` _(response)_
5. **Managed agent → Your backend** — `session.error` — `mcp_authentication_failed_error` _(response; this step fails — the flow stops here)_

Notes:

- **Steps 1–2** — Session creation does not validate MCP connectivity or credentials. It succeeds; the match is attempted later, when the agent first connects.
- **Steps 3–4** — With nothing matched, the connection is attempted unauthenticated. The 401 is your server behaving correctly — it is not the bug.
- **Step 5** — The event carries the mcp_server_name and a retry_status. The connection is retried on the next idle-to-running transition, so the same error repeats until the credential key changes.

## What lands on you next

The credential matches now and the agent is connected. Everything asked of you from here is about what it does with that connection.

- **We disabled that tool in the agent's `mcp_toolset`. Is it actually unreachable?** — `mcp-capability-filter-inbound` — That switch lives in the agent definition. A route-level allow-list drops the tool from `tools/list` and refuses it with `MethodNotFound` before forwarding — though it is static per route, with no per-user axis.
- **Something ran `delete_repository`. Which caller, and when?** — `capability_invocation` — Every parsed tool call emits an event carrying the authenticated caller (`subjectId`), the capability, the upstream, the outcome and the latency. Tokens and request bodies are never logged.
- **Forty users, forty upstream grants, each on its own clock. Who renews them?** — `mcp-token-exchange-inbound` — The gateway. Each user completes upstream OAuth once and the gateway stores that credential encrypted per user and refreshes it, so the agent holds a credential to you rather than to the upstream.
- **A session is looping on `search_issues`. Can we cap it without touching the server?** — `rate-limit-inbound` — `rateLimitBy: "user"` counts each tool call against the authenticated identity rather than a shared IP, and several windows compose on one route.

All of these attach to one MCP route's `policies.inbound` — the same policy engine on the way in and on the way out. See [Zuplo MCP Gateway](/mcp-gateway).

## Keep reading

- **Prevent it** — [How an Anthropic-managed agent authenticates to your MCP server](/learn/mcp/authentication/anthropic-managed-agents) — Reaching your MCP server from an agent that runs on Anthropic's infrastructure
- **Similar error** — [`Couldn't reach the MCP server`](/learn/mcp/errors/mcp-server-works-in-claude-code-but-not-claude-ai) — One toast covers four different failures. Claude Code and `curl` reach your server from your machine; a claude.ai connector reaches it from Anthropic's infrastructure over the public internet, so the request dies at whichever hop only exists on that path — DNS, your edge, a redirect, or OAuth discovery.
- **Similar error** — [`Authorization with the MCP server failed`](/learn/mcp/errors/oauth-completes-but-no-token-sent) — A token was issued and the client says it is connected, but the credential never reaches your server intact: either the client does not attach it, or it carries an audience your server is right to reject.
- **Error reference** — [every documented MCP error](/learn/mcp/errors)
