Skip to content

Proxy spawn for phone-mcp: cold calls 502 with startup-handshake timeout, while admin UI shows instance as 'not started yet' and a live session works fine #1

Description

@kirtapos

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:

  1. 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.
  2. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions