ruvnet/ruflo · error · MCPServerError
Server already running
Error message
Server already running
What it means
MCPServer.start() is single-shot: an internal running flag is set while the transport and tool registry come up, and a second start() on the same instance throws MCPServerError('Server already running'). The guard prevents double-binding transports and duplicated event-handler registration. stop() resets the flag, after which start() is legal again.
Source
Thrown at v3/@claude-flow/shared/src/mcp/server.ts:156
// Initialize connection pool if enabled
if (this.config.connectionPool) {
this.connectionPool = createConnectionPool(
this.config.connectionPool,
logger,
this.config.transport
);
}
// Setup event handlers
this.setupEventHandlers();
}
/**
* Start the MCP server
*/
async start(): Promise<void> {
if (this.running) {
throw new MCPServerError('Server already running');
}
const startTime = performance.now();
this.startTime = new Date();
this.logger.info('Starting MCP server', {
name: this.config.name,
version: this.config.version,
transport: this.config.transport,
});
try {
// Create and start transport
this.transport = createTransport(this.config.transport, this.logger, {
type: this.config.transport,
host: this.config.host,
port: this.config.port,
corsEnabled: this.config.corsEnabled,View on GitHub (pinned to fa13ee4ad6)
Solutions
- Memoize startup so duplicate calls coalesce: const ready = startPromise ??= server.start()
- For a genuine restart, await server.stop() first, then start() again
- Make init single-entry: create the server in one module-level singleton reachable from exactly one code path
Example fix
// before
await server.start();
// later, another init path:
await server.start(); // MCPServerError: Server already running
// after
let startPromise: Promise<void> | null = null;
function ensureStarted() { return (startPromise ??= server.start()); }
await ensureStarted();
await ensureStarted(); // coalesced, no throw Defensive patterns
Strategy: validation
Validate before calling
let startPromise: Promise<void> | null = null;
function ensureStarted(): Promise<void> {
return (startPromise ??= server.start());
}
await ensureStarted(); // safe to call from any init path Try / catch
import { MCPServerError } from '@claude-flow/shared/dist/mcp/types';
try {
await server.start();
} catch (e) {
if (e instanceof MCPServerError && e.message === 'Server already running') {
return; // treat as idempotent success
}
throw e;
} Prevention
- Memoize the start promise instead of calling start() from multiple modules
- Pair every restart with stop() first
- Create the server in exactly one place (module singleton) and export ensureStarted()
When it happens
Trigger: Awaiting server.start() from two init paths (main + bootstrap helper); a start-retry wrapper that re-invokes start() after a timeout even though the first call actually succeeded; test beforeEach starting a shared server instance that was never stopped.
Common situations: Hot reload / dual ESM-CJS module loading so init code runs twice; backoff logic wrapped blindly around startup; conditional branches that both call start(); assuming a previous start failed because a dependent health check was slow.
Related errors
- HTTP transport already running
- TransportManager already running
- Stdio transport already running
- WebSocket transport already running
- SSRF guard: invalid URL — ${rawUrl}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ba35fe012e3a9dc7.
Report an issue: GitHub.