ruvnet/ruflo · error · Error

MCP Server already running

Error message

MCP Server already running (PID: ${status.pid})

What it means

MCPServer.start() first calls getStatus(); if it reports a running server under a different PID than the current process, it throws 'MCP Server already running (PID: N)'. The PID self-check exists because in stdio mode getStatus() reports the current process as running before it actually starts — any other PID means a genuine second instance, and the server refuses to double-bind the transport/state.

Solutions

  1. Stop the existing instance first: `kill <PID from message>` or call MCPServer.stop() (mcp-server.ts:222), then start again
  2. If the reported PID no longer exists (`ps -p <PID>` empty), the status record is stale — remove the status/PID file the getStatus() probe reads, or run `npx @claude-flow/cli@latest doctor` to clean up
  3. Wrap start in a guard script: check getStatus().running before calling start()

Example fix

# before — second start collides
npx @claude-flow/cli@latest mcp start  # Error: MCP Server already running (PID: 4242)

# after — stop, then start (or reuse the running one)
kill 4242   # or: npx @claude-flow/cli@latest daemon stop
npx @claude-flow/cli@latest mcp start
Defensive patterns

Strategy: validation

Validate before calling

const status = await server.getStatus();
if (status.running && status.pid !== process.pid) {
  console.log(`server already running (PID ${status.pid}) — stopping first`);
  await server.stop(); // mcp-server.ts:222
}
await server.start();

Try / catch

try { await server.start(); }
catch (e) {
  const m = /already running \(PID: (\d+)\)/.exec((e as Error).message);
  if (m) { await server.stop(); await server.start(); } // or: kill stale PID and retry once
  else throw e;
}

Prevention

When it happens

Trigger: A previous `mcp start`/daemon still alive in another terminal or tmux pane; a background server from an earlier session that survived shell exit; an orphaned process after a crash whose status record still resolves to a live PID; CI jobs overlapping because the previous job's server was never stopped.

Common situations: Dev loop where the daemon was started once and forgotten; machine/CI runner reuse where stray processes persist between jobs; running both an interactive MCP session and a background daemon against the same state directory.

Related errors


AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-08-18). Data as JSON: /api/errors/c5f421aceac090c4. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/mcp-server.ts:185

    // spread last below and therefore takes precedence over this env fallback.
    const environmentTools = parseMcpToolSelection(process.env.CLAUDE_FLOW_MCP_TOOLS);
    this.options = {
      ...DEFAULT_OPTIONS,
      ...(environmentTools === 'all' ? {} : { tools: environmentTools }),
      ...options,
    };
  }

  /**
   * Start the MCP server
   */
  async start(): Promise<MCPServerStatus> {
    // Check if already running (skip if status reports our own PID —
    // getStatus() returns running=true for the current process in stdio mode
    // even before the server is actually started)
    const status = await this.getStatus();
    if (status.running && status.pid !== process.pid) {
      throw new Error(`MCP Server already running (PID: ${status.pid})`);
    }

    const startTime = performance.now();
    this.startTime = new Date();

    this.emit('starting', { options: this.options });

    try {
      if (this.options.transport === 'stdio') {
        // For stdio transport, spawn the server process
        await this.startStdioServer();
      } else {
        // For HTTP/WebSocket, start in-process server
        await this.startHttpServer();
      }

      const duration = performance.now() - startTime;

View on GitHub (pinned to 2602b642d9)