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
- Run under tmux – launch the command inside tmux so you can inspect the
- Enable hang diagnostics – set
MCPORTER_DEBUG_HANG=1when invoking the - Inspect the handle list – look for
ChildProcess (pid=…)entries. If a - Capture the pane output – run `tmux capture-pane -p -t <session> -S
- Retry with
--timeout– if the tool itself hangs, use - Clamp OAuth waits – when the browser-based sign-in never completes,
pane even after Cursor or another agent times out.
CLI. mcporter will dump the active handles/requests after the tool finishes and around the runtime.close() call.
child remains, mcporter will now unref and force-kill it, but the debug list tells you exactly what was keeping the event loop alive.
-200` to save the diagnostic log for later review.
--timeout <ms> or MCPORTER_CALL_TIMEOUT to fail fast while still gathering diagnostics.
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=1is set. - Killing residual children is best-effort; if you see repeated
kill-failed - Always keep tmux sessions tidy after debugging: `tmux kill-session -t
- The CLI now forces
process.exit(0)after cleanup by default so Node never - You can still set
MCPORTER_FORCE_EXIT=1explicitly when you want to force - Stdio servers have their stderr output suppressed by default; set
messages, manually terminate the PID listed in the log.
<session>`.
lingers on leaked handles. Export MCPORTER_NO_FORCE_EXIT=1 if you’re debugging and need the process to stay alive.
termination even with MCPORTER_NO_FORCE_EXIT in play.
MCPORTER_STDIO_LOGS=1 to print their logs (they’re also surfaced whenever a child exits with a non-zero status).
#Upstream Tracking
@modelcontextprotocol/sdk1.21.0 is the latest release pulled into mcporter.- Open SDK issues related to stdio shutdown:
- #579 StdioClientTransport does not follow the spec on close
- #780 onerror listeners not removed after client close (stdio)
- #1049 stdio client crashes when spawned server exits unexpectedly
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.