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
feat(upstream): launch local HTTP/SSE upstreams via launcher (Phase 1)
Wires internal/upstream/launcher into Client.Connect for the http,
sse, and streamable-http transports when ServerConfig.Command is also
set. The launcher spawns the child process, blocks on
launcher.WaitForURL until the listener accepts a TCP connection, then
hands off to the existing transport-level connectHTTP / connectSSE.
On Disconnect, the MCP client is closed first (so the child sees the
network drop cleanly) and the launcher Handle is Stop()ed second
(SIGTERM -> grace -> SIGKILL).
Stdio is deliberately excluded: mcp-go's Stdio transport spawns its
own child via its CommandFunc, and routing that through launcher.Spawn
would require patching mcp-go. Instead, both the stdio CommandFunc
path and the new HTTP/SSE launcher path call into the same set of
*Client command-prep helpers (setupDockerIsolation, wrapWithUserShell,
injectEnvVarsIntoDockerArgs, insertCidfileIntoShellDockerCommand),
preserving the spec's "Docker isolation in one place" requirement
without the riskier stdio refactor.
Config / API:
- ServerConfig.LauncherWaitTimeout (Duration, default 30s) caps the
wait between spawn and listener-up. CopyServerConfig carries it.
- Client gains launcherHandle / launcherCIDFile fields. Stdio Clients
leave them nil so existing cleanup paths (killProcessGroup,
processCmd.Process.Kill) keep their single owner.
Behaviour back-compat: when Command is set together with a URL but
no explicit protocol, Command still wins -> stdio, URL ignored
(matches today). To opt into the launcher you must set protocol to
"http", "sse", or "streamable-http" explicitly.
Locking: Connect holds c.mu for its duration, so the failure-cleanup
path inlines the launcher teardown and releases c.mu briefly around
handle.Stop (which can block until the child is reaped). The
connecting flag prevents a concurrent Connect from sneaking in
during that window. DisconnectWithContext releases c.mu before its
stopLauncher call, so the public method uses normal locking.
Watch goroutine: on unexpected child exit while connected,
watchLauncher invokes Disconnect so the existing reconnect loop in
internal/upstream/manager takes over instead of waiting for the
transport's keepalive to time out.
Verified by go vet + the full internal/upstream/... and
internal/config/... test suites (existing tests stay green; deadlock
in connect-failure cleanup that surfaced in TestClient_Connect_SSE_
NotSupported was fixed before commit).
Refs spec: specs/046-local-launcher-for-http-sse/plan.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ReconnectOnUsebool`json:"reconnect_on_use,omitempty" mapstructure:"reconnect-on-use"`// Attempt reconnection when a tool call targets a disconnected server
227
+
228
+
// LauncherWaitTimeout caps how long mcpproxy will wait for a locally-launched
229
+
// HTTP/SSE upstream's URL to become reachable after Spawn(). Only consulted
230
+
// when the server is configured with both Command and an HTTP/SSE URL — i.e.,
231
+
// mcpproxy starts the process AND connects via network. Stdio servers ignore
0 commit comments