Operations

Debugging Hanging mcporter Calls

Debugging Hanging mcporter Calls

When mcporter call prints a tool response but the process never exits, it usually means Node still has active handles it is waiting on. The most common culprit is a child MCP server process that keeps the stdio transport alive.

#Quick Checklist

  1. Run under tmux – launch the command inside tmux so you can inspect the
  2. pane even after Cursor or another agent times out.

  3. Enable hang diagnostics – set MCPORTER_DEBUG_HANG=1 when invoking the
  4. CLI. mcporter will dump the active handles/requests after the tool finishes and around the runtime.close() call.

  5. Inspect the handle list – look for ChildProcess (pid=…) entries. If a
  6. child remains, mcporter will now unref and force-kill it, but the debug list tells you exactly what was keeping the event loop alive.

  7. Capture the pane output – run `tmux capture-pane -p -t <session> -S
  8. -200` to save the diagnostic log for later review.

  9. Retry with --timeout – if the tool itself hangs, use
  10. --timeout <ms> or MCPORTER_CALL_TIMEOUT to fail fast while still gathering diagnostics.

  11. Clamp OAuth waits – when the browser-based sign-in never completes,
  12. run with --oauth-timeout <ms> (or MCPORTER_OAUTH_TIMEOUT_MS) so the CLI tears down the pending flow instead of waiting the full minute.

#Example Session

tmux new-session -d -s mcphang \
  'cd /path/to/project && \
   MCPORTER_DEBUG_HANG=1 \
   CHROME_DEVTOOLS_MCP_BROWSER_URL=http://127.0.0.1:9222 \
   pnpm --dir "$HOME/projects/mcporter" exec tsx \
   "$HOME/projects/mcporter/src/cli.ts" call \
   --config "$PWD/config/mcporter.json" \
   --root "$PWD" \
   chrome-devtools --tool list_pages'

sleep 5
tmux capture-pane -p -t mcphang -S -200

Sample diagnostic output (abridged):

[mcporter] [debug] after call (object result): 6 active handle(s), 0 request(s)
[mcporter] [debug] handle => ChildProcess (pid=78480)
[mcporter] [debug] beginning runtime.close()
[mcporter] [debug] after runtime.close: 6 active handle(s), 0 request(s)
[mcporter] [debug] forcibly killed child pid=78480 (runtime.finally)

This confirms the CLI response completed and that the lingering handle was a child process, which mcporter will now terminate during shutdown.

#Notes

  • The diagnostics only appear when MCPORTER_DEBUG_HANG=1 is set.
  • Killing residual children is best-effort; if you see repeated kill-failed
  • messages, manually terminate the PID listed in the log.

  • Always keep tmux sessions tidy after debugging: `tmux kill-session -t
  • <session>`.

  • The CLI now forces process.exit(0) after cleanup by default so Node never
  • lingers on leaked handles. Export MCPORTER_NO_FORCE_EXIT=1 if you’re debugging and need the process to stay alive.

  • You can still set MCPORTER_FORCE_EXIT=1 explicitly when you want to force
  • termination even with MCPORTER_NO_FORCE_EXIT in play.

  • Stdio servers have their stderr output suppressed by default; set
  • MCPORTER_STDIO_LOGS=1 to print their logs (they’re also surfaced whenever a child exits with a non-zero status).

#Upstream Tracking

We keep a local checkout of the SDK under ~/Projects/typescript-sdk/ so we can diff against upstream and craft repros/patches quickly. Any mcporter-specific workarounds live in src/sdk-patches.ts until the upstream fixes land.