ruvnet/ruflo · error · Error

Failed to register MCP tools: ${registration.failed.join(',

Error message

Failed to register MCP tools: ${registration.failed.join(', ')}

What it means

When the server wires up its tools it calls registerTools(cliTools), which returns a per-tool registration result; if any tool fails to register (name collision, invalid definition), start aborts with the list of failed tool names. This is a fail-fast at boot: the server refuses to come up in a degraded state where some advertised tools are missing.

Source

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

    const dualLoopback = this.options.host === 'localhost';
    const mcpServer = createMCPServer(
      {
        name: 'Claude-Flow MCP Server V3',
        version: '3.0.0',
        transport: this.options.transport as 'http' | 'websocket',
        host: dualLoopback ? '127.0.0.1' : this.options.host,
        additionalHosts: dualLoopback && this.options.transport === 'http' ? ['::1'] : undefined,
        port: this.options.port,
        enableMetrics: true,
        enableCaching: true,
      },
      logger
    );
    const registration = mcpServer.registerTools(
      cliTools as Parameters<typeof mcpServer.registerTools>[0]
    );
    if (registration.failed.length > 0) {
      throw new Error(`Failed to register MCP tools: ${registration.failed.join(', ')}`);
    }
    await mcpServer.start();
    this.mcpServers = [mcpServer];
  }

  /**
   * Wait for server to be ready
   */
  private async waitForReady(timeout = 10000): Promise<void> {
    // For stdio transport, we're ready immediately (in-process)
    if (this.options.transport === 'stdio') {
      return;
    }

    const startTime = Date.now();

    while (Date.now() - startTime < timeout) {
      const health = await this.checkHealth();

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Read the comma-separated tool names in the message — those are exactly the definitions that failed
  2. Find and rename the colliding definition (namespace custom tools, e.g. 'acme_search' instead of 'search') or disable the conflicting plugin
  3. Validate the tool definition (unique name, well-formed inputSchema) before handing it to the server
  4. Update all plugins and the CLI together so registration APIs match

Example fix

// before — custom tool collides with a built-in name
const myTools = [{ name: 'memory_search', /* ... */ }];
server.registerTools(myTools); // failed: memory_search

// after — namespace custom tools
const myTools = [{ name: 'acme_memory_search', /* ... */ }];
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate names are unique before handing tools to the server
const seen = new Set<string>();
for (const t of myTools) {
  if (seen.has(t.name)) throw new Error(`duplicate tool name: ${t.name}`);
  seen.add(t.name);
}

Try / catch

try {
  await server.start();
} catch (e) {
  const m = /Failed to register MCP tools: (.+)/.exec((e as Error).message);
  if (m) {
    // m[1] lists exactly the colliding/invalid tool names — disable or rename them
    throw new Error(`registration failed for: ${m[1]}. Rename/disable those tools.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Two plugins registering tools with the same name; a tool definition with a malformed schema or duplicate field; a custom tool added to cliTools whose name collides with a built-in; version skew where a plugin was built against a different registration API than the shipped server.

Common situations: Adding an in-house plugin that uses a generic name like 'search' that already exists; upgrading @claude-flow/cli while stale plugin builds remain in the plugins directory; forking built-ins without renaming.

Related errors


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