mcprack: cold-start proxy call to phone-mcp fails with 502 "startup handshake" timeout, even though a live client session against the same server_id works
Repo: VitexSoftware/mcprack
Instance tested: http://10.11.182.99:8912/
Config under test: claude_desktop_config.json generated by mcprack for user id 3 / server id 9 (phone-mcp)
{
"mcpServers": {
"phone-mcp": {
"type": "http",
"url": "http://10.11.182.99:8912/proxy/mcp/eyJ1IjozLCJzIjo5fQ.amccJw.9dluKXpW9YegJ5AgqKPpVV_3MbU/9"
}
}
}
Summary
A fresh HTTP connection to the per-user proxy URL for phone-mcp (server_id 9,
user_id 3) consistently returns 502 Bad Gateway with a JSON-RPC error saying
the upstream server "failed its startup handshake within 5s". This happens on
every cold attempt (GET, or POST with a proper initialize message), 100%
reproducible, in ~3.2–3.5s each time.
However, in the same time window, an already-connected MCP client (Claude
Code, using this exact same config/token) is able to call tools against
phone-mcp on this instance and gets legitimate application-level responses
(e.g. check_device_connection → "No device found. Please connect a device and ensure USB debugging is enabled.", and the full tool list — 20+ tools —
loads correctly). So the backend phone-mcp process itself is clearly
capable of running and answering correctly; it's specifically new/cold
connections to the proxy that fail.
This suggests the bug is in the proxy's spawn-on-demand path, not in the
phone-mcp server itself — e.g. a race/collision when a second instance is
spawned for a (user, server) pair that already has a live instance running
(possible port conflict, PID/lock file collision, or ADB server port 5037
already bound by the existing instance), rather than the proxy reusing the
existing warm instance or queuing behind it.
Steps to reproduce
URL="http://10.11.182.99:8912/proxy/mcp/eyJ1IjozLCJzIjo5fQ.amccJw.9dluKXpW9YegJ5AgqKPpVV_3MbU/9"
# Plain GET
curl -sS -m 15 -D - "$URL"
# Proper JSON-RPC initialize via POST
curl -sS -m 15 -X POST "$URL" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"diag","version":"0.1"}}}'
Observed
Base app is healthy and reachable:
$ curl -sS -m 8 -D - -o /dev/null http://10.11.182.99:8912/
HTTP/1.1 302 FOUND
Server: gunicorn
Location: /login?next=%2F
Vary: Cookie
Set-Cookie: session=...; HttpOnly; Path=/
Every cold call to the phone-mcp proxy path 502s, consistently, in ~3.2-3.5s:
$ curl -sS -m 15 -o /tmp/a1.json -w "HTTP:%{http_code} time_total:%{time_total}s\n" "$URL"
HTTP:502 time_total:3.273953s
{"jsonrpc": "2.0", "id": null, "error": {"code": -32000, "message": "Upstream MCP server 'phone-mcp' failed its startup handshake within 5s — the registered command is likely broken (check its log at /var/lib/mcprack/user-proxies/u3-s9.log, or its registration in Admin → Servers)."}}
$ curl -sS -m 15 -o /tmp/a2.json -w "HTTP:%{http_code} time_total:%{time_total}s\n" "$URL"
HTTP:502 time_total:3.276102s
{"jsonrpc": "2.0", "id": null, "error": {"code": -32000, "message": "Upstream MCP server 'phone-mcp' failed its startup handshake within 5s — ..."}}
# Same result with a real initialize payload instead of a bare GET
$ curl -sS -m 15 -X POST "$URL" -H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize", ...}' \
-o /tmp/a3.json -w "HTTP:%{http_code} time_total:%{time_total}s\n"
HTTP:502 time_total:3.504097s
{"jsonrpc": "2.0", "id": 1, "error": {"code": -32000, "message": "... failed its startup handshake within 5s ..."}}
Expected
Either:
- The proxy reuses/attaches to the already-running
phone-mcp instance for
(user=3, server=9) instead of trying to spawn a new one and failing, or
- If a fresh spawn really is required per connection, it should succeed
(or the timeout/registration should be fixed so it doesn't fail every time).
Contradicting evidence that the backend itself is fine
Within the same test window, an existing MCP client session using this exact
token successfully:
- Loaded the full
phone-mcp tool catalog (call_number, send_text_message,
get_contacts, list_installed_apps, check_device_connection, etc. —
30+ tools)
- Called
check_device_connection and got a real, correctly-formatted
application response: "No device found. Please connect a device and ensure USB debugging is enabled."
This rules out "phone-mcp binary/command is broken" as the cause (the error
message's own suggested diagnosis) — the process runs and answers fine, at
least for however produced that first working session.
Admin UI shows the instance as never started
On http://10.11.182.99:8912/admin/proxy-instances, the row for this
exact (user, server) pair reads:
kirtap phone-mcp 2026-07-27 08:51 not started yet
i.e. the admin-tracked status is "not started yet", not "running",
"crashed", or "failed handshake". This is a third, inconsistent view of the
same instance's state, on top of:
- The proxy HTTP endpoint, which actively attempts (and fails) a spawn on
every cold call, returning a 502 "failed its startup handshake within
5s" error — implying an attempt did happen.
- A live MCP client session (this one) that successfully listed all
phone-mcp tools and got a correct application-level response from
check_device_connection.
So depending on which surface you look at, this single (user=3, server=9)
instance appears simultaneously as: never started, actively failing to
start, and successfully running and answering calls. That strongly suggests
/admin/proxy-instances is not reading the same state the proxy's spawn
logic uses (e.g. the admin page reads a DB row that's only updated by an
explicit "Start" action, while the proxy path spawns/tracks processes
independently and never writes back to that row), rather than the phone-mcp
process itself having three different real states.
Suggested next steps for maintainer
- Check
/var/lib/mcprack/user-proxies/u3-s9.log on the mcprack host for
the actual stderr/stdout of the spawn attempt during a reproduction of
the curl commands above — the error message itself points here.
- Check whether the proxy's spawn logic creates a new subprocess/port
per incoming HTTP connection rather than per (user, server) pair as the
README/architecture doc describes ("one isolated instance per (user,
server) pair"). If so, a collision with an already-running instance
(bound port, PID/lock file, or ADB server on 5037) would explain a
consistent, fast (~3.3s) handshake failure on every cold call while an
existing warm session keeps working.
- Confirm whether GET vs POST-with-initialize should behave differently at
this endpoint (both currently 502 identically) — if GET is meant to open
an SSE stream and POST is meant to run a fresh JSON-RPC exchange, they
are presently indistinguishable in behavior/error handling.
- Reconcile
/admin/proxy-instances status tracking with the proxy's
actual spawn logic — the admin page shows "not started yet" for this
instance even after repeated spawn attempts (and even though a live
session against the same token has worked), suggesting the status
column isn't updated by the on-demand spawn path at all.
Environment
- mcprack host:
10.11.182.99:8912 (Flask/gunicorn)
- Client: Claude Code / Claude Desktop, config auto-generated by mcprack
(claude_desktop_config.json, type: http)
- Tested: 2026-07-27
mcprack: cold-start proxy call to
phone-mcpfails with 502 "startup handshake" timeout, even though a live client session against the same server_id worksRepo: VitexSoftware/mcprack
Instance tested:
http://10.11.182.99:8912/Config under test:
claude_desktop_config.jsongenerated by mcprack for user id 3 / server id 9 (phone-mcp){ "mcpServers": { "phone-mcp": { "type": "http", "url": "http://10.11.182.99:8912/proxy/mcp/eyJ1IjozLCJzIjo5fQ.amccJw.9dluKXpW9YegJ5AgqKPpVV_3MbU/9" } } }Summary
A fresh HTTP connection to the per-user proxy URL for
phone-mcp(server_id 9,user_id 3) consistently returns
502 Bad Gatewaywith a JSON-RPC error sayingthe upstream server "failed its startup handshake within 5s". This happens on
every cold attempt (GET, or POST with a proper
initializemessage), 100%reproducible, in ~3.2–3.5s each time.
However, in the same time window, an already-connected MCP client (Claude
Code, using this exact same config/token) is able to call tools against
phone-mcpon this instance and gets legitimate application-level responses(e.g.
check_device_connection→"No device found. Please connect a device and ensure USB debugging is enabled.", and the full tool list — 20+ tools —loads correctly). So the backend
phone-mcpprocess itself is clearlycapable of running and answering correctly; it's specifically new/cold
connections to the proxy that fail.
This suggests the bug is in the proxy's spawn-on-demand path, not in the
phone-mcpserver itself — e.g. a race/collision when a second instance isspawned for a (user, server) pair that already has a live instance running
(possible port conflict, PID/lock file collision, or ADB server port 5037
already bound by the existing instance), rather than the proxy reusing the
existing warm instance or queuing behind it.
Steps to reproduce
Observed
Base app is healthy and reachable:
Every cold call to the
phone-mcpproxy path 502s, consistently, in ~3.2-3.5s:Expected
Either:
phone-mcpinstance for(user=3, server=9) instead of trying to spawn a new one and failing, or
(or the timeout/registration should be fixed so it doesn't fail every time).
Contradicting evidence that the backend itself is fine
Within the same test window, an existing MCP client session using this exact
token successfully:
phone-mcptool catalog (call_number,send_text_message,get_contacts,list_installed_apps,check_device_connection, etc. —30+ tools)
check_device_connectionand got a real, correctly-formattedapplication response:
"No device found. Please connect a device and ensure USB debugging is enabled."This rules out "phone-mcp binary/command is broken" as the cause (the error
message's own suggested diagnosis) — the process runs and answers fine, at
least for however produced that first working session.
Admin UI shows the instance as never started
On
http://10.11.182.99:8912/admin/proxy-instances, the row for thisexact (user, server) pair reads:
i.e. the admin-tracked status is "not started yet", not "running",
"crashed", or "failed handshake". This is a third, inconsistent view of the
same instance's state, on top of:
every cold call, returning a 502 "failed its startup handshake within
5s" error — implying an attempt did happen.
phone-mcptools and got a correct application-level response fromcheck_device_connection.So depending on which surface you look at, this single (user=3, server=9)
instance appears simultaneously as: never started, actively failing to
start, and successfully running and answering calls. That strongly suggests
/admin/proxy-instancesis not reading the same state the proxy's spawnlogic uses (e.g. the admin page reads a DB row that's only updated by an
explicit "Start" action, while the proxy path spawns/tracks processes
independently and never writes back to that row), rather than the phone-mcp
process itself having three different real states.
Suggested next steps for maintainer
/var/lib/mcprack/user-proxies/u3-s9.logon the mcprack host forthe actual stderr/stdout of the spawn attempt during a reproduction of
the curl commands above — the error message itself points here.
per incoming HTTP connection rather than per (user, server) pair as the
README/architecture doc describes ("one isolated instance per (user,
server) pair"). If so, a collision with an already-running instance
(bound port, PID/lock file, or ADB server on 5037) would explain a
consistent, fast (~3.3s) handshake failure on every cold call while an
existing warm session keeps working.
this endpoint (both currently 502 identically) — if GET is meant to open
an SSE stream and POST is meant to run a fresh JSON-RPC exchange, they
are presently indistinguishable in behavior/error handling.
/admin/proxy-instancesstatus tracking with the proxy'sactual spawn logic — the admin page shows "not started yet" for this
instance even after repeated spawn attempts (and even though a live
session against the same token has worked), suggesting the status
column isn't updated by the on-demand spawn path at all.
Environment
10.11.182.99:8912(Flask/gunicorn)(
claude_desktop_config.json,type: http)