Skip to content

mcp stdio server exits before handling any request (process.exit(0) race in bin.js) #386

@beckitrue

Description

@beckitrue

Check existing issues

Describe the bug

Summary

mppx --mcp starts an MCP stdio server, but the process exits before any MCP request can be handled. The root cause is a premature process.exit(0) in dist/bin.js:

// dist/bin.js
#!/usr/bin/env node
import cli from './cli/cli.js';
cli.serve().then(() => process.exit(0));

Under --mcp, cli.serve() eventually awaits server.connect(transport) on StdioServerTransport. StdioServerTransport.start() just registers stdin listeners and resolves synchronously — it does not keep the promise pending for the lifetime of the connection. So cli.serve() resolves on the next microtask, process.exit(0) fires, and the server dies before any initialize request can be read from stdin.

Environment

Link to Minimal Reproducible Example

https://github.com/woven-record-media/mppx-mcp-repro/blob/main/bug1-exit-race.mjs

Steps To Reproduce

Reproduction

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' \
  | npx --yes mppx --mcp

Expected: a JSON-RPC response to initialize on stdout.
Actual: the process exits silently with code 0, no response written.

Any MCP client registering npx mppx --mcp as a stdio server sees the subprocess disappear immediately after spawn. In Claude Code, /mcp shows the server as failed/disconnected.

Package Version

[email protected]

Anything else?

Fix

Remove the .then(() => process.exit(0)). Node's stdin listeners keep the event loop alive naturally; the process terminates cleanly when stdin closes. A CLI invocation (non-MCP) still exits promptly because those commands don't register long-lived listeners.

-cli.serve().then(() => process.exit(0));
+await cli.serve();

or equivalently, just:

cli.serve();

Verified locally: a wrapper script that imports cli/cli.js directly and calls cli.serve() without the exit handler makes MCP mode reachable (the initialize request round-trips). But see Issue — the server still doesn't work end-to-end because CLI command handlers write their output with console.log instead of returning it through the runtime.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions