You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We run mcporter as the single MCP bridge for coding agents (Claude Code / Codex): one user-scope mcporter serve --stdio entry exposing ~9 remote servers. It works great — except when a token expires mid-session.
When any bridged tool call hits a 401, the daemon runs the interactive OAuth flow and unconditionally launches the system browser. From the user's perspective a browser tab steals focus mid-task with zero context about which server wants auth or why. In an agent harness the desired flow is the opposite: fail the tool call fast, let the agent mint a link via mcporter auth <name> --no-browser --json, and present it in chat where the user can click it deliberately (the flow #169 added — thank you for that one).
#169's --no-browser / MCPORTER_OAUTH_NO_BROWSER solved this for auth and config login, but the env var is parsed only in the auth command's argv handling. Setting it on the serve process does nothing.
Current behavior (0.12.4 and 0.13.0)
PersistentOAuthClientProvider.redirectToAuthorization (src/oauth.ts) calls openExternal(...) unless options.suppressBrowserLaunch is set — and the only caller that sets it is the auth command with --no-browser.
On the serve/daemon path there is no way to reach that option, so every 401 → browser launch.
It also compounds into a tab loop when auth keeps failing:
connectWithAuth makes up to 3 attempts (maxAttempts = 3), each re-entering interactive authorization;
a pending authorization older than INTERACTIVE_AUTHORIZATION_TTL_MS (5 min) is treated as abandoned, so the next request prompts again;
When suppressed, fail the pending authorization promptly (or honor a short MCPORTER_OAUTH_TIMEOUT_MS as today) with an error that includes the server name, so the caller/agent can surface its own auth flow.
A per-server config field ("browser": false) or a serve --no-browser flag would also work; the env var is the minimal, consistent-with-#169 option.
Workaround we use today
A PATH shim replacing open for the bridge process tree (logs the URL instead of launching), plus MCPORTER_OAUTH_TIMEOUT_MS=20000 so the suppressed flow fails fast — works, but it depends on openExternal spawning a PATH-resolved open, which is an implementation detail we'd rather not lean on.
Motivation
We run mcporter as the single MCP bridge for coding agents (Claude Code / Codex): one user-scope
mcporter serve --stdioentry exposing ~9 remote servers. It works great — except when a token expires mid-session.When any bridged tool call hits a 401, the daemon runs the interactive OAuth flow and unconditionally launches the system browser. From the user's perspective a browser tab steals focus mid-task with zero context about which server wants auth or why. In an agent harness the desired flow is the opposite: fail the tool call fast, let the agent mint a link via
mcporter auth <name> --no-browser --json, and present it in chat where the user can click it deliberately (the flow #169 added — thank you for that one).#169's
--no-browser/MCPORTER_OAUTH_NO_BROWSERsolved this forauthandconfig login, but the env var is parsed only in the auth command's argv handling. Setting it on the serve process does nothing.Current behavior (0.12.4 and 0.13.0)
PersistentOAuthClientProvider.redirectToAuthorization(src/oauth.ts) callsopenExternal(...)unlessoptions.suppressBrowserLaunchis set — and the only caller that sets it is theauthcommand with--no-browser.It also compounds into a tab loop when auth keeps failing:
connectWithAuthmakes up to 3 attempts (maxAttempts = 3), each re-entering interactive authorization;INTERACTIVE_AUTHORIZATION_TTL_MS(5 min) is treated as abandoned, so the next request prompts again;A persistently failing server (e.g. an IdP rejecting the client) produces an endless stream of context-free browser tabs.
Proposed fix
Honor browser suppression on the runtime/serve path. Smallest version:
MCPORTER_OAUTH_NO_BROWSER(same semantics as Add a flag to suppress browser launch inauth/config loginfor headless workflows #169) when constructing the OAuth session in the runtime, and passsuppressBrowserLaunch: truethrough toPersistentOAuthClientProvider.MCPORTER_OAUTH_TIMEOUT_MSas today) with an error that includes the server name, so the caller/agent can surface its own auth flow.A per-server config field (
"browser": false) or aserve --no-browserflag would also work; the env var is the minimal, consistent-with-#169 option.Workaround we use today
A PATH shim replacing
openfor the bridge process tree (logs the URL instead of launching), plusMCPORTER_OAUTH_TIMEOUT_MS=20000so the suppressed flow fails fast — works, but it depends onopenExternalspawning a PATH-resolvedopen, which is an implementation detail we'd rather not lean on.Happy to send a PR for option 1.