Skip to content

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.

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-key value (or MCPPIPE_API_KEY). It starts with mtk_.
  • 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.

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.

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 --json for 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.

When you use --stdio, the CLI spawns the command as a subprocess.

  • Command not found — on Windows, npx/pnpm/yarn are .cmd shims; the CLI resolves those automatically. For other tools, make sure the program is on your PATH.
  • The server crashes — the CLI auto-restarts it with backoff. Repeated crashes within 2s escalate the delay. The server’s stderr is logged at warn level; run with -v to 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.

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-check if egress is locked down.

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.

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 500 with "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. A 500 "token issuer is unreachable" means the service could not reach the issuer’s JWKS and failed closed.

See Authentication: OAuth & Service Tokens.

A 403 means mcppipe applied a per-tunnel control. The error field tells you which:

error / codeCauseFix
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_deniedThe 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_violationThe 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)“.

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.

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 an upgrade_url to /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 returns 429 “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, a Retry-After header, 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 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.

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 pending and 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.

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_error is surfaced there.
  • If you would rather not use ACME, upload your own PEM (cert + key); an uploaded certificate is stored as active immediately. See Custom domains & TLS.

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.