Skip to content

Bulk tool registration causes EventEmitter memory leak warnings #842

@cameroncooke

Description

@cameroncooke

Problem Description

When registering many tools dynamically (80+ tools), the MCP SDK triggers EventEmitter memory leak warnings due to individual sendToolListChanged() calls per tool registration.

Root Cause

  • Each server.tool() call automatically triggers sendToolListChanged()
  • Rapid notifications overwhelm stdout buffer in StdioServerTransport
  • Multiple 'drain' listeners accumulate temporarily, exceeding Node.js default limit (10)

Stack Trace

MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added to [Socket]. MaxListeners is 10.
    at genericNodeError (node:internal/errors:983:15)
    at wrappedFn (node:internal/errors:537:14)
    at _addListener (node:events:581:17)
    at Socket.addListener (node:events:599:10)
    at Readable.on (node:internal/streams/readable:1131:35)
    at Socket.once (node:events:643:8)
    at StdioServerTransport.send (/@modelcontextprotocol/sdk/dist/esm/server/stdio.js:73:30)
    at Promise.resolve.then.then._a (/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:151:83)

Reproduction Steps

  1. Create an MCP server with dynamic tool registration
  2. Register 80+ tools rapidly in a loop:
    for (const tool of manyTools) {
      server.tool(tool.name, tool.description, tool.schema, tool.handler);
    }
  3. Observe EventEmitter memory leak warnings in stderr

Expected Behavior

  • Bulk tool registration should not trigger memory leak warnings
  • Efficient batch operations without notification spam

Current Workaround

// Increase maxListeners before bulk registration
process.stdout.setMaxListeners(100);

Proposed Solutions

  1. Batch Notification API: Add optional defer parameter to tool() method
  2. Bulk Registration: Add registerTools(tools[]) method with single notification
  3. Increase Default MaxListeners: Set higher default for stdio transport
  4. Documentation: Document bulk registration patterns and best practices

Use Case

Servers like XcodeBuildMCP that load 80+ tools dynamically based on user context need efficient bulk registration without warning spam. Dynamic tool loading is becoming common for context-aware MCP servers.

Environment

  • MCP SDK Version: 1.17.1
  • Node.js Version: 18+
  • Transport: StdioServerTransport
  • Platform: macOS/Linux

Impact

  • False positive memory leak warnings confuse developers
  • Performance degradation from stdout buffer backpressure
  • Client receives excessive individual ToolListChangedNotification messages

The core issue is that the MCP SDK wasn't designed for bulk tool registration scenarios common in dynamic, context-aware servers.

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