Zuplo
MCP error

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.

TerminalRun this to check — it changes nothing
# 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.
Then the fix is the next section.
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.
Then this is not your problem — try the error reference.
3 other strings clients print for this
  • 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

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.

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.

Managed Agents vault

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

  1. 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. 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. 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.
Terminalbash
# 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

mcp_oauth credential

If the credential is mcp_oauth and the URLs already match

  1. 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. 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. 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.
Terminalbash
# 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

static_bearer credential

If the credential is static_bearer

  1. 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. 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. 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.
Terminalbash
# 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

Anthropic MCP tunnel

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

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

Zuplo MCP Gateway

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

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

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

Each step in the exchange, in order.

  • Your backend creates the session
  • Managed agent Anthropic control plane
  • Your MCP server requires a bearer token
  1. Session creation does not validate MCP connectivity or credentials. It succeeds; the match is attempted later, when the agent first connects.

    Your backend to Managed agent

    POST /v1/sessions vault_ids: ["vlt_01ABC…"]
  2. Inside Managed agent

    Match credential by normalized URL no match: /mcp vs /mcp/v1
  3. With nothing matched, the connection is attempted unauthenticated. The 401 is your server behaving correctly — it is not the bug.

    Managed agent to Your MCP server

    POST /mcp no Authorization header
  4. Your MCP server to Managed agent

    401 Unauthorized WWW-Authenticate: Bearer
  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.

    Managed agent to Your backend

    session.error mcp_authentication_failed_error

    Flow stops here

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

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.

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.