ruvnet/ruflo · critical · MCPServerError

INTERNAL_ERROR

INTERNAL_ERROR

Error message

Failed to start server

What it means

This is the catch-all at the end of MCPServer.start(): any failure raised while bringing up the transport or server internals is re-thrown as MCPServerError('Failed to start server') with code ErrorCodes.INTERNAL_ERROR (-32603), and the original error is attached in the error data (third constructor argument { error }). The wrapper message is generic on purpose - the actionable cause is the attached original error and the preceding 'Failed to start MCP server' log line.

Source

Thrown at v3/@claude-flow/shared/src/mcp/server.ts:212

      // Register built-in tools
      await this.registerBuiltInTools();

      this.running = true;
      this.startupDuration = performance.now() - startTime;

      this.logger.info('MCP server started', {
        startupTime: `${this.startupDuration.toFixed(2)}ms`,
        tools: this.toolRegistry.getToolCount(),
      });

      this.emit('server:started', {
        startupTime: this.startupDuration,
        tools: this.toolRegistry.getToolCount(),
      });

    } catch (error) {
      this.logger.error('Failed to start MCP server', { error });
      throw new MCPServerError('Failed to start server', ErrorCodes.INTERNAL_ERROR, { error });
    }
  }

  /**
   * Stop the MCP server
   */
  async stop(): Promise<void> {
    if (!this.running) {
      return;
    }

    this.logger.info('Stopping MCP server');

    try {
      // Stop transport
      if (this.transport) {
        await this.transport.stop();
        this.transport = undefined;

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Read the attached cause first: catch the error and inspect (err as MCPServerError).data.error, or the 'Failed to start MCP server' log entry - fix the underlying error, not this wrapper
  2. Port conflict: find and stop the holder (lsof -i :PORT) or change the configured port
  3. Validate transport config before start(): host/port present for http/ws, usable streams for stdio
  4. In non-TTY environments, switch away from stdio transport or provide explicit inputStream/outputStream in config

Example fix

// before
await server.start(); // opaque 'Failed to start server' INTERNAL_ERROR

// after
import { MCPServerError } from './mcp/types';
try {
  await server.start();
} catch (e) {
  const cause = (e instanceof MCPServerError ? (e.data as any)?.error : undefined) ?? e;
  if ((cause as NodeJS.ErrnoException)?.code === 'EADDRINUSE') {
    console.error('Port already in use - stop the old process or change the port');
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight common causes before start()
import { checkPortInUse } from './net';
if (config.transport === 'http' || config.transport === 'websocket') {
  if (await checkPortInUse(config.port)) {
    throw new Error(`Port ${config.port} already in use - choose another or stop the holder`);
  }
}
await server.start();

Try / catch

try {
  await server.start();
} catch (e) {
  const mcpErr = e as MCPServerError;
  const cause = (mcpErr?.data as { error?: unknown } | undefined)?.error ?? e;
  logger.error('MCP server failed to start', { cause }); // surface the REAL cause
  process.exitCode = 1; // fail fast: do not run half-started
}

Prevention

When it happens

Trigger: HTTP/WebSocket transport binding an already-used port (EADDRINUSE); stdio transport with closed or unavailable stdin/stdout (containers, daemons); transport config malformed so createTransport throws inside start(); tool registry or event-handler setup throwing during startup.

Common situations: Two processes configured on the same port because an old dev-server instance still holds it; running stdio transport where stdin is not available; a version upgrade adding required config fields; insufficient permissions to bind a privileged port.

Related errors


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