Aident AI

How to Fix MCP OAuth Callback Errors in Docker, WSL, and Remote Clients
If MCP login succeeds in the browser but ends with localhost refused to connect, redirect_uri_mismatch, invalid state parameter, or another 401 Unauthorized, debug the flow in order: discovery metadata, callback reachability, registered redirect URI, state and PKCE, then token audience. Most failures come from two parts of the flow disagreeing about an origin, port, or client instance.
Do not solve the problem by publishing a random callback port to the internet or disabling OAuth checks. Keep the callback local, make its route reachable from the browser that completed login, and ensure every public metadata URL uses the external HTTPS origin.
This guide is for developers operating an HTTP MCP server or client across Docker, WSL, SSH, or a reverse proxy. The job is complete when one fresh OAuth attempt reaches the client-owned callback, exchanges its code once, calls a read-only MCP tool, and still works after the client reconnects.
Quick Diagnosis
Symptom | Most likely cause | First check |
|---|---|---|
| The authorization server has a different callback URI registered | Compare scheme, host, port, path, and trailing slash exactly |
Browser shows | The MCP client's local callback listener is not reachable from that browser | Check which host, container, or WSL instance owns the listener |
| The callback reached a different or expired login attempt | Start one fresh flow and use its newest callback URL |
Login completes, then the client returns to | The token was issued for the wrong MCP resource, scope, or audience | Compare the requested |
Metadata contains | The server generated discovery URLs from an internal request origin | Fix proxy forwarding and public-origin configuration |
OAuth works on the host but not in Docker or WSL |
| Use a reachable local listener or the client's manual callback flow |
The browser callback belongs to the MCP client, not the remote MCP server. A server running in Docker does not automatically mean its OAuth callback should point into that container.
Prerequisites
You need:
The canonical remote MCP endpoint, such as
https://mcp.example.com/mcp.Access to the MCP client configuration and its OAuth login flow.
Access to sanitized server or reverse-proxy logs. Never paste authorization codes, access tokens, refresh tokens, client secrets, or cookies into an issue or chat.
curlandjqfor checking discovery metadata.A fixed callback port if the authorization server does not support dynamic client registration or Client ID Metadata Documents.
If you are deciding between API keys and OAuth before troubleshooting a deployment, start with MCP API Keys vs OAuth. This guide assumes the server should use OAuth and the connection is already failing.
Step 1: Check the Initial 401 and Protected Resource Metadata
Request the MCP endpoint without credentials:
A protected HTTP MCP server should return 401 Unauthorized and identify its Protected Resource Metadata in the WWW-Authenticate header. The current MCP authorization specification also permits discovery at a well-known URI.
Check that document directly:
Expected result:
resourceidentifies the public MCP server;authorization_serverscontains at least one HTTPS issuer;every URL is reachable from the MCP client;
no URL exposes
localhost, a private container hostname, or an internal HTTP port.
If the initial request returns a tool-level JSON error instead of HTTP 401, the client may never start OAuth discovery. Enforce authentication at the HTTP boundary.
Step 2: Verify Authorization Server Metadata
Fetch metadata from the issuer returned in authorization_servers:
Some providers use OpenID Connect discovery instead:
Expected result: the issuer and endpoints use the externally reachable HTTPS origin. A reverse proxy that forwards requests to http://oauth:3000 must not cause metadata to advertise that internal address.
When metadata is wrong, fix the application's public base URL and ensure the proxy preserves the original scheme and host. Common forwarding headers are X-Forwarded-Proto and X-Forwarded-Host, but the exact trusted-proxy configuration belongs to the framework you use.
Step 3: Find Where the Callback Listener Actually Runs
The OAuth client opens a temporary local listener before sending the browser to the authorization server. Determine whether that client is running:
directly on macOS, Windows, or Linux;
inside WSL;
inside a Docker container;
on a remote development machine while the browser runs locally.
If you chose port 8080, inspect it on the machine or environment that runs the MCP client:
On Linux or inside WSL:
Then test from the same environment as the browser:
Any HTTP response proves that the route is reachable. Connection refused means nothing is listening at that address from the browser's network namespace.
Client runs on the host
Keep the callback on http://localhost:<port>/callback. The remote MCP server can stay in Docker; it does not own this listener.
Client runs inside Docker
Inside a container, localhost refers to the container. A browser on the host cannot reach that listener unless the client binds to a reachable interface and the port is published. Publishing a port does not help when the client binds only to container loopback.
Prefer the client's supported manual callback flow or run the interactive MCP client on the host. On Linux, host networking can align the namespaces for a trusted local development setup, but it changes the container's network isolation and should not be a default production fix.
Docker Desktop provides host.docker.internal for a container that needs to call a service on the host. That name solves container-to-host routing; it does not automatically make a host browser reach a callback listener trapped inside the container.
Client runs in WSL
WSL 2 normally uses NAT, so a browser on Windows and a listener in Linux may not share the same loopback route in every setup. Microsoft documents mirrored networking for Windows 11 22H2 and later, which lets Windows and WSL connect through 127.0.0.1 in more cases.
If you cannot change networking safely, use the MCP client's manual callback option: complete login, copy the full callback URL from the browser, and paste it back into the client that owns the OAuth state.
Client runs on a remote machine
A callback to localhost in your laptop browser returns to the laptop, not the remote development host. Use the client's documented callback forwarding, manual callback, or a fixed local port with an authenticated tunnel designed for this flow. Do not expose a temporary unauthenticated listener on a public interface.
If you are still deciding where the client and server should run, use Local vs Remote MCP Servers to choose the boundary before changing callback routing.
Step 4: Make the Registered Redirect URI Match Exactly
OAuth authorization codes are bound to the client and redirect URI. Compare the value sent in the authorization request with the value registered for that OAuth client.
These are different redirect URIs:
Match the scheme, host, port, path, and trailing slash. If the provider requires a pre-registered URI, select a fixed local port instead of letting the client choose a random one.
Claude Code supports this directly:
Register http://localhost:8080/callback for the same OAuth client. If automatic client registration is unavailable, add the provider-issued client ID through the client's supported credential flow rather than storing a secret in a project file.
Expected result: after consent, the browser returns to the exact local URI and the client receives the authorization code.
Step 5: Reset an invalid state parameter Failure
The state value binds the browser callback to the login attempt that created it. A mismatch commonly happens when you:
launch several OAuth attempts and finish an older browser tab;
restart the MCP client before the callback arrives;
reuse a callback URL from history;
pass the callback through a proxy that rewrites or drops query parameters;
complete login in a different client instance than the one that started it.
Fix it with a clean sequence:
Close old authorization tabs.
Clear only the failed server's saved authentication through the MCP client.
Start one new login attempt.
Complete that attempt immediately.
If manual callback is required, copy the entire URL, including
codeandstate, into the same client instance.
Do not log or share the copied URL. The authorization code is short-lived but still sensitive.
Step 6: Diagnose PKCE and Token Exchange
If the callback reaches the client but token exchange fails, inspect sanitized logs for the phase that failed. The client must send the same PKCE verifier that corresponds to the challenge in the authorization request, and it must not reuse an authorization code.
Check for these conditions without recording secrets:
token endpoint hostname and issuer match the discovered metadata;
redirect_uriin token exchange matches the authorization request;the code is exchanged once;
the same client registration is used before and after the callback;
the token request includes the canonical MCP resource when required;
the authorization server accepts the client's registration method.
If dynamic client registration is used, store the returned client identity for the duration required by the provider. Re-registering a new client halfway through the flow can make a valid code unusable.
Step 7: Stop a Successful Login From Looping Back to 401
An OAuth flow can finish correctly while the MCP server still rejects the token. At that point, callback networking is no longer the problem.
Verify that:
the token audience or
resourceis the canonical MCP server URI;the requested scopes cover the operation being called;
the MCP server validates the issuer and audience it advertised;
the reverse proxy forwards the
Authorizationheader;a refresh-token rotation did not leave the client using an older token;
system clocks are close enough for token time checks.
The current MCP specification requires the resource parameter in authorization and token requests. Using the authorization server's URL, a private upstream URL, or a different trailing-slash form can produce a token that the MCP resource correctly rejects.
A Reproducible End-to-End Test
Run this test after each configuration change:
Request the MCP endpoint and capture only the status code and
WWW-Authenticateheader.Fetch Protected Resource Metadata and authorization server metadata.
Confirm every advertised URL uses the public origin.
Start one OAuth attempt from a clean client state.
Record the callback host and port without recording its query string.
Confirm the listener is reachable from the browser environment.
Complete login once.
Call one read-only MCP tool.
Restart the client and call the same tool to verify token persistence or refresh.
Expected result: discovery returns public metadata, the browser reaches the local callback, one code exchange succeeds, and a read-only tool works after reconnecting.
Why This Fix Works
An MCP OAuth connection crosses four identities:
the public MCP resource;
the authorization server;
the registered OAuth client;
the local callback listener owned by the MCP client.
Docker, WSL, SSH, and reverse proxies add network boundaries, but they do not change those roles. Debugging each boundary separately prevents the common mistake of changing the remote server to compensate for a local callback problem.
The same structure made Aident's Ollama networking guide useful: name the exact failure, test one boundary at a time, show the expected result, and explain why the route works. Here, the durable lesson is to keep public discovery on the public origin and local callbacks local.
Use Managed Authentication When You Do Not Need to Operate OAuth
If your goal is to let an agent call a SaaS integration rather than build an OAuth-capable MCP server, Aident Loadout keeps connection state in Vault and exposes searchable capabilities without asking the agent to handle raw credentials.
If Aident is not installed yet, give your coding agent this exact instruction:
Follow https://aident.ai/SETUP.md
Verify the installed CLI and current connections:
Then search for the operation you need:
Ready to avoid another hand-built OAuth callback for a supported integration? Connect one tool with Aident Loadout and verify one read-only action end to end.
When to Refresh This Guide
Refresh this page when the MCP authorization specification changes its discovery or resource rules; Claude Code changes callback-port or manual-callback behavior; Docker Desktop or WSL changes loopback routing; or recurring issue reports introduce a new error that the current diagnostic sequence does not cover. Preserve the stable URL and retest the commands and expected results against the current primary documentation.


