Troubleshooting
Most problems fall into two buckets: the CLI cannot connect, or mcppipe refuses a request with a 401/402/403/404/429. This page covers both. Every refusal mcppipe returns carries a machine-readable JSON body (and, for tool-call refusals, a JSON-RPC error envelope) so you can tell exactly which gate fired.
The tunnel will not connect
Section titled “The tunnel will not connect””authentication failed”
Section titled “”authentication failed””mcppipe rejected your API key. The CLI prints authentication failed and exits — it does not retry, because an invalid key never becomes valid.
- Double-check the
--api-keyvalue (orMCPPIPE_API_KEY). It starts withmtk_. - If the key was rotated in the dashboard, the old one stops working immediately. Copy the current key from the tunnel’s Settings tab (CLI key section).
”session_limit” — concurrent session cap reached
Section titled “”session_limit” — concurrent session cap reached”mcppipe accepted your key but refused a new session because the tunnel is already at its plan’s concurrent-session cap. You will see an honest session_limit error like:
free plan allows 3 concurrent sessions per tunnel (3 already connected)This is never a silent kill — existing sessions keep running; only the new connect is rejected. The caps are Free 3 / Pro 10 / Scale 25 sessions per tunnel.
- A resume of an existing session does not count against the cap, so a flapping connection will not exhaust it.
- If you genuinely need more agents on one tunnel, upgrade the plan, or split the load across multiple tunnels.
See Reliability: reconnect & sessions for how multiple agents share a tunnel.
It hangs without printing a subdomain
Section titled “It hangs without printing a subdomain”The CLI connected but never received mcppipe’s Welcome. Re-run with -v (or -vv) to see the handshake, and confirm the control-plane endpoint your --broker / MCPPIPE_BROKER value points at is reachable. The ws://127.0.0.1:7100 default is local-dev only; against the hosted service this must be the mcppipe broker’s wss:// URL. See CLI reference.
Dropped sessions and reconnects
Section titled “Dropped sessions and reconnects”A flaky network is expected, not an error. The CLI keeps a session alive across short outages:
- It sends an application-level heartbeat every 25s; mcppipe drops the session only after 35s of silence.
- On a drop the CLI reconnects with exponential backoff (1s → 30s) and resumes the same session — mcppipe replays buffered frames that were in flight, so a tool call is not silently lost.
- A dropped session can be resumed for up to 60s before it is cleaned up.
If reconnects are constant, the link itself is the problem — see Network restrictions below.
Auto-detect found nothing, or the wrong server
Section titled “Auto-detect found nothing, or the wrong server”mcppipe up with no --upstream/--stdio scans the configs of Claude Desktop, Cursor, Windsurf, VS Code, Cline, Continue.dev, and Zed.
- See exactly what it finds:
mcppipe detect(add--jsonfor raw output). - Nothing detected? Point at a config file explicitly with
--config <path>, or skip detection entirely with--upstream <url>/--stdio "<cmd>". - In a non-interactive shell (CI, containers) with several servers detected, the CLI aggregates them headlessly instead of prompting. Pass
--upstream/--stdio(repeatable) or--upstreams-file <path>to be explicit. See Multi-upstream aggregation.
stdio bridge problems
Section titled “stdio bridge problems”When you use --stdio, the CLI spawns the command as a subprocess.
- Command not found — on Windows,
npx/pnpm/yarnare.cmdshims; the CLI resolves those automatically. For other tools, make sure the program is on yourPATH. - The server crashes — the CLI auto-restarts it with backoff. Repeated crashes within 2s escalate the delay. The server’s stderr is logged at
warnlevel; run with-vto see it. - Garbled output — anything the server writes to stdout that is not a single line of JSON is logged and discarded; it does not corrupt the protocol.
Network restrictions
Section titled “Network restrictions”The CLI only ever makes one outbound connection — a WebSocket to mcppipe. It never listens for inbound connections, so you do not need to open a port or touch your router.
- If outbound WebSocket traffic is blocked by a corporate proxy or firewall, the tunnel cannot connect. WebSocket runs over HTTPS (port 443) in production.
- The update check makes one extra outbound request to the GitHub API. Disable it with
--no-update-checkif egress is locked down.
The agent gets a 401, 403, 404, or 429
Section titled “The agent gets a 401, 403, 404, or 429”These come from mcppipe, not the CLI — the tunnel is up but the service refused the request before (or instead of) forwarding it. Each response has a JSON body with an error code you can match on.
401 — OAuth required or token rejected
Section titled “401 — OAuth required or token rejected”The tunnel is in OAuth auth mode and the request had no valid Bearer token. mcppipe returns 401 with a WWW-Authenticate header pointing at the Protected Resource Metadata document (/.well-known/oauth-protected-resource, RFC 9728), so a spec-compliant client can discover where to get a token and retry.
A 401 also fires when the token is present but rejected: bad signature, expired, wrong issuer, wrong audience (a token minted for a different host/tunnel), a tid that no longer matches the tunnel, or a consent the owner has revoked. In those cases the challenge code is invalid_token.
- If your agent is headless, mint a Service Token in the tunnel’s Settings instead of running the interactive consent flow.
- A
500with"OAuth is not configured on this edge"means the tunnel asks for OAuth mode but the service has no issuer configured — that is a service-side misconfiguration, not a client error. A500"token issuer is unreachable"means the service could not reach the issuer’s JWKS and failed closed.
See Authentication: OAuth & Service Tokens.
403 — blocked by an access gate
Section titled “403 — blocked by an access gate”A 403 means mcppipe applied a per-tunnel control. The error field tells you which:
error / code | Cause | Fix |
|---|---|---|
ip_forbidden — “your client IP is not in this tunnel’s allowlist” | The caller’s resolved IP is not in the tunnel’s CIDR allowlist (checked before auth and quota). | Add the caller’s IP/CIDR, or apply a vendor preset, in Settings. Behind a proxy, confirm trusted-proxy resolution. See IP allowlist & access control. |
| ”this tunnel has been suspended” | An operator suspended the tunnel (abuse kill-switch). Checked before any other gate. | Contact the operator. |
JSON-RPC data.policy_code = tool_denied | The tool is not allowed by the tunnel’s tool policy. | Allow the tool, or switch the policy mode, in Settings → Tool policy. See Tool governance. |
JSON-RPC data.policy_code = tool_pin_violation | The tool’s live description drifted from the owner-approved baseline (rug-pull) and pin enforcement is set to block. | Review the change in the Tools tab; if legitimate, approve a new baseline. Otherwise the block is doing its job. |
Tool-policy and pin refusals arrive as a JSON-RPC error envelope (code: -32000) so the agent routes the error to its pending request; the surface message is “Tool denied by tunnel policy” or “Tool blocked: changed from approved baseline (rug-pull)“.
404 — host serves nothing
Section titled “404 — host serves nothing”If the tunnel has a verified custom domain and the owner enabled default-subdomain disable, requests to the default <subdomain>--<slug>.<zone> host return 404 while the custom domain keeps resolving. Use the custom domain. A 404 on the custom domain itself usually means it is not verified/routable yet — see Custom-domain not verified below.
429 — quota exceeded or rate-limited
Section titled “429 — quota exceeded or rate-limited”Two different limits both return 429; the body distinguishes them:
- Plan quota — you hit the monthly bandwidth or tool-call limit for your plan. The body carries the reason, the
plan, and anupgrade_urlto/billing. Either upgrade or wait for the monthly counter to reset. The quota fail mode is configurable and defaults to fail-closed (EDGE_QUOTA_FAILMODE), so a quota-backend outage returns429“quota backend unavailable” rather than serving for free. - Per-method / per-tool rate limit — a sliding-window cap you set in Settings → Rate limits tripped. The body has
error: rate_limited, aRetry-Afterheader, and a JSON-RPC envelope; the message reads “rate limit reached for<scope><name>(N/min); retry in Ns”.
See Billing & plans and Tool governance.
Creating a tunnel fails with 402
Section titled “Creating a tunnel fails with 402”Creating a tunnel over your plan’s tunnel cap returns 402 Payment Required — “the <plan> plan allows N tunnel(s) — upgrade your plan to create more”. Caps are Free 2 / Pro 5 / Scale 20. Delete an unused tunnel (it enters a 7-day recovery window first) or upgrade. See Billing & plans.
Custom domain not verified yet
Section titled “Custom domain not verified yet”Before mcppipe routes a custom domain (or requests an ACME certificate), you must prove ownership with a DNS TXT record:
- Publish a TXT record at
_mcppipe-challenge.<your-domain>with the token shown in Settings → Custom domain, then click Verify. - While the record is missing or not yet propagated, verification stays
pendingand the domain does not route. - A
503“DNS lookup failed; try again” means the resolver could not be reached — retry; it is not a record problem.
Also confirm the domain’s CNAME points at the target zone: <subdomain>--<slug>.cname.<zone>. See Custom domains & TLS.
Certificate stuck on “pending”
Section titled “Certificate stuck on “pending””After ownership is verified, an ACME (Let’s Encrypt) certificate is requested and its status moves from pending to active. If it stays pending:
- ACME uses an HTTP-01 challenge — make sure the custom domain’s CNAME is in place and resolves to the service so the challenge can be served.
- Check the cert status widget in Settings; a
last_erroris surfaced there. - If you would rather not use ACME, upload your own PEM (cert + key); an uploaded certificate is stored as
activeimmediately. See Custom domains & TLS.
Getting more detail
Section titled “Getting more detail”Raise CLI log verbosity with -v (info), -vv (debug), or -vvv (trace), or set RUST_LOG directly. Logs go to stderr, so they never corrupt protocol output. For server-side gates, the JSON error/error_description (or JSON-RPC data.policy_code) on the response tells you which check fired.
Still stuck? Open an issue at github.com/mcppipe/mcppipe with the -vv output and the response body.