Skip to content

Remote Hosts (SSH Port Forwarding)

Chenglei Yuan edited this page Jun 4, 2026 · 1 revision

Remote Hosts (SSH Port Forwarding)

You can run the upstream MCP server (or the proxy itself) on a remote machine and still complete the interactive OAuth login from your local browser. The trick is SSH port forwarding plus the fact that the proxy prints the authorize URL to stderr.

The challenge

The interactive authorization_code flow needs two things to reach your laptop:

  1. Your browser must open the IdP authorize URL.
  2. The IdP must redirect back to the proxy's local callback listener (127.0.0.1:53682 by default).

When the proxy runs on a remote host, neither happens automatically. Port forwarding bridges the callback, and copy-pasting the printed URL bridges the browser.

Recommended setup: run the proxy locally, tunnel to the upstream

If only the upstream MCP server is remote, the simplest approach is to run the proxy on your laptop and forward a local port to the remote upstream:

# Forward local 8443 -> remote upstream on the bastion/VM
ssh -L 8443:mcp.internal:443 user@bastion

Then point the proxy at the loopback end of the tunnel:

UPSTREAM_URL=https://127.0.0.1:8443/mcp \
OAUTH2_GRANT=authorization_code \
OAUTH2_CLIENT_ID=<your-client-id> \
  npx -y mcp-oauth2-proxy

Because 127.0.0.1 is loopback, the cleartext/secure checks are satisfied even if you terminate TLS at the tunnel. The browser and callback both stay on your laptop — no extra forwarding needed.

Running the proxy on the remote host

If the proxy must run remotely (e.g. it's launched by a remote MCP client), forward the callback port back to your laptop and open the URL manually.

  1. Open a reverse-friendly tunnel for the callback. Forward your local 53682 to the remote host's 53682:

    ssh -L 53682:127.0.0.1:53682 user@remote-host

    This makes the remote callback listener reachable from your local browser at http://127.0.0.1:53682/callback.

  2. Launch the proxy on the remote host as usual.

  3. Open the printed URL locally. The proxy writes the authorize URL to stderr (it can't open a browser on a headless box). Copy it from the logs and paste it into your local browser. After you authenticate, the IdP redirects to http://127.0.0.1:53682/callback, which the tunnel delivers to the remote listener.

Notes and gotchas

  • Redirect URI registration. Whatever host/port the listener uses, the resulting http://127.0.0.1:53682/callback (or your override) must be registered as a redirect URI with the IdP. The loopback address is what the browser hits, so register the loopback form.
  • Port collisions. If 53682 is taken, set OAUTH2_CALLBACK_PORT and forward that port instead. Keep both sides of the tunnel in sync.
  • Keep the callback on loopback. Don't set callbackHost to 0.0.0.0 to "make it reachable" — the proxy hardens the listener against non-loopback Host headers and will warn you. Use SSH forwarding instead. See Security.
  • Windows. Use the OpenSSH client (ssh -L …) the same way; or run the proxy locally with a tunnel to the upstream as above.
  • Docker. Publish/forward the callback port out of the container (-p 53682:53682) and bind the listener to 127.0.0.1 inside the container; reach it via the host.
  • Bastion / jump host. Chain hops with ssh -J bastion user@target and add the -L forward for the callback port on the final connection.

For the listener's security checks, see Security; for the flow itself, see OAuth2 Grants and Tokens.

Clone this wiki locally