Skip to content

Unhandled 'EPIPE' in 'StdioServerTransport' causes fatal process crash on client disconnect #1564

@manthanghasadiya

Description

@manthanghasadiya

Description

There is a stability issue in the StdioServerTransport implementation of the TypeScript SDK. If an MCP client sends a request but abruptly closes the stdio pipes (stdin/stdout) before the server can write the response, the Node.js process throws an unhandled EPIPE exception and fatally crashes.

Because stdout.write() is not wrapped in a try/catch or lacking an 'error' event listener on the stream, standard client-side timeouts or abrupt disconnects lead to a complete server crash rather than a graceful shutdown. This acts as a Denial of Service (DoS) for local AI agents relying on the server.

Steps to Reproduce

Here is a minimal Python Proof of Concept (PoC) that reproduces the crash by initiating a valid handshake and instantly closing the pipe:

import subprocess
import time
import json

# 1. Start any MCP server using the TS SDK (e.g., server-everything)
process = subprocess.Popen(
    ["npx", "-y", "@modelcontextprotocol/server-everything"],
    stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)

# 2. Send a valid JSON-RPC initialization request
req = {
    "jsonrpc": "2.0", "id": 1, "method": "initialize", 
    "params": {
        "protocolVersion": "2024-11-05", 
        "capabilities": {}, 
        "clientInfo": {"name": "poc", "version": "1.0"}
    }
}
process.stdin.write(json.dumps(req) + "\n")
process.stdin.flush()

# 3. Instantly close the pipes before Node can reply
process.stdin.close()
process.stdout.close()

# 4. Read the stderr crash log
time.sleep(1)
print(process.stderr.read())

Actual Behavior (Stack Trace)

node:events:486
      throw er; // Unhandled 'error' event
      ^
Error: EPIPE: broken pipe, write
    at Socket._write (node:internal/net:75:18)
    at writeOrBuffer (node:internal/streams/writable:570:12)
    at _write (node:internal/streams/writable:499:10)
    at Writable.write (node:internal/streams/writable:508:10)
    at file:///.../node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js:66:30
    at new Promise (<anonymous>)
    at StdioServerTransport.send (file:///.../node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js:64:16)

Expected Behavior

The transport layer should catch the EPIPE error and handle it gracefully (e.g., emitting a close event and shutting down the session cleanly) rather than bubbling up as an unhandled exception that kills the host process.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions