fix(tray): forward --listen to the spawned core process - #1015
Merged
github-actions[bot] merged 2 commits intoAug 24, 2026
Merged
fix(tray): forward --listen to the spawned core process#1015github-actions[bot] merged 2 commits into
github-actions[bot] merged 2 commits into
Conversation
The tray ignored its own command-line arguments entirely, so launching it as `mcpproxy-tray serve --listen 0.0.0.0:8181` silently dropped the listen address: on macOS the tray prefers the unix socket for tray<->core communication, buildCoreArgs() only derived --listen for TCP/HTTP core URLs, and the core was spawned as `mcpproxy serve` with no --listen at all. The core then fell back to the config-file default (typically 127.0.0.1:8080) and the advertised port never opened. Observed live on mcpproxy-tray v0.43.0 (tray log shows the shell-wrapped spawn without --listen; lsof confirms the core listening on 8080 instead of 8181). Fix: parse --listen/-l (space and = forms) from the tray's argv and always forward it to the spawned core's argv, even when the tray talks to the core over the socket/pipe — the socket only covers tray<->core communication, while the core must still open the advertised TCP address. URL/env-derived --listen behavior for TCP endpoints is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Member
|
Merged — thanks Roman! Great catch that the tray never parsed its own argv, so Also useful method is to set using env var https://docs.mcpproxy.app/configuration/environment-variables#setting-tray-variables-on-macos |
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 25, 2026
…core-URL semantics for pinned --listen (#1036) * fix(tray): preserve host in normalizeListen + sane port-conflict and core-URL semantics for pinned --listen Three follow-ups to PR #1015 (tray --listen forwarding): 1. normalizeListen("127.0.0.1:8181") returned ":8181", silently rebinding a loopback-pinned address to every interface — and that value is forwarded verbatim as the core's --listen from both the MCPPROXY_TRAY_LISTEN path and the new CLI path. It now never widens a bind: a pinned host is preserved (IPv6 included), a bare port defaults to loopback (matching listenArgFromURL's rule for derived addresses), an explicit all-interfaces request is honored, and unparseable input passes through for the core to reject. 2. handlePortConflictError bumped the port and relaunched, expecting buildCoreArgs to derive the new one from coreURL — but an explicit CLI --listen always wins, so the retry would re-hit the same busy port forever. An explicitly pinned listen is now treated as a user contract: fail loudly (error log + state-machine error + tray notification) instead of silently retrying or silently moving off the pinned address. Unpinned setups keep the auto-bump. The choice is documented at the call site. 3. resolveCoreURL ignored the CLI listen on the TCP-fallback path, so the core bound the pinned port while the tray probed the default. It now takes the parsed CLI listen (highest priority, above MCPPROXY_TRAY_LISTEN and MCPPROXY_TRAY_PORT); wildcard binds are dialed on loopback. * fix(tray): percent-escape IPv6 zones in the derived core URL Cross-model review finding on the new coreURLFromListen(): it built the URL by string concatenation, so a zoned IPv6 listen such as "[fe80::1%en0]:8181" produced "http://[fe80::1%en0]:8181" — which url.Parse rejects outright with `invalid URL escape "%en"`. Every later parse of the core URL (readiness checks, listenArgFromURL, the port-conflict handler) would then fail on an address the function was specifically meant to support, since it already claimed IPv6 handling. Build the URL through url.URL instead, which escapes the zone ("fe80::1%en0" -> "fe80::1%25en0") and round-trips through url.Parse. Adds the zoned case to the coreURLFromListen table plus a TestCoreURLFromListen_AlwaysParseable invariant asserting that every derived core URL survives url.Parse with the expected port. Also tightens docs/tray-debug.md: an all-interfaces value supplied via MCPPROXY_TRAY_LISTEN / MCPPROXY_TRAY_PORT is narrowed to loopback (the core's --listen is derived from the URL the tray dials), so the tray's own --listen flag is the way to ask for a LAN-exposed bind. The previous wording implied the variables forwarded any host as written. * fix(tray): surface an unparseable explicit listen instead of dropping it Second cross-model review round. Before the resolveCoreURL rework, an unparseable MCPPROXY_TRAY_LISTEN / --listen produced an unparseable core URL, so listenArgFromURL returned "" and buildCoreArgs fell through to the raw env value — handing the core a bad --listen that it rejected loudly. Now coreURLFromListen returns "" for such a value and resolveCoreTCPURL falls through to the valid default endpoint, so listenArgFromURL succeeds and the explicitly configured address is dropped without a trace. Worse, when a core is already listening on that default the tray attaches to it and looks like it honoured an address it actually ignored. Add unparseableListenSources(), which reports every explicitly set listen value that cannot be turned into a dialable endpoint, and log each one at Warn from main() next to the resolved core URL. This keeps normalizeListen's stated contract — unparseable input is surfaced rather than silently rewritten — on the path that no longer reaches the core. Behaviour is otherwise unchanged: valid values (bare port, pinned host, wildcard, IPv6) report nothing, and blank/whitespace values count as unset. * fix(tray): make the unparseable-listen warning state what actually happened Third cross-model review round. The warning added in the previous commit claimed the value was "ignored" and that the tray fell back to "the default core endpoint". Both can be wrong: with an invalid --listen and a valid MCPPROXY_TRAY_LISTEN, resolution falls through to the env endpoint rather than the default, and the invalid CLI value is still forwarded to the launched core by buildCoreArgs, so it is not ignored either. Reword to the accurate, neutral claim — the value cannot form an endpoint the tray can dial and therefore did not determine the core URL — and keep the resolved core_url field so the log still shows which endpoint won. No behaviour change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
mcpproxy-tray serve --listen 0.0.0.0:8181silently drops the--listenflag on macOS (reproduced on tray v0.43.0):/bin/zsh -l -c exec '/mcpproxy' 'serve'— no--listen.lsofconfirms the core then listens on the config-file value (default127.0.0.1:8080); nothing ever opens the advertised port, so any tooling that probes it concludes the daemon is down.Root cause
cmd/mcpproxy-tray/main.gonever parses its own command line —serve --listen …is ignored entirely. On macOSresolveCoreURL()prefers the unix socket for tray↔core communication (#102), andbuildCoreArgs()only derives--listenfor TCP/HTTP core URLs, so on the socket path the core is spawned with no--listenat all and falls back to the config default.Fix
trayListenFromArgs()parses--listen/-l(both space and=forms) from the tray's argv; other args remain ignored as before. Malformed values (dangling flag, empty=form, value starting with-) are skipped and scanning continues to the first valid value.buildCoreArgs()now always forwards an explicit CLI--listento the spawned core's argv — including when the tray talks to the core over the socket/pipe, since the socket only covers tray↔core communication while the core must still open the advertised TCP address. Both the login-shell-wrapped spawn and the direct-exec fallback consume the same argv.--listenbehavior for TCP endpoints is unchanged.Tests
TestTrayListenFromArgscovers both flag forms plus the malformed-input regressions (--listen --config path,--listen= --listen :8181, dangling-l, continue-scan to a later valid--listen=:9090).TestBuildCoreArgs_ForwardsCLIListenOverSocketEndpoint— regression: CLI listen forwarded over a socket endpoint; no--listenon socket endpoints without the flag.🤖 Generated with Claude Code