Skip to content

fix: async onclose, stdin EOF detection, SIGTERM in examples#1814

Open
MayCXC wants to merge 1 commit intomodelcontextprotocol:mainfrom
MayCXC:fix/stdio-server-stdin-eof
Open

fix: async onclose, stdin EOF detection, SIGTERM in examples#1814
MayCXC wants to merge 1 commit intomodelcontextprotocol:mainfrom
MayCXC:fix/stdio-server-stdin-eof

Conversation

@MayCXC
Copy link
Copy Markdown

@MayCXC MayCXC commented Mar 29, 2026

Summary

Three related improvements to server lifecycle handling.

1. Allow async onclose callbacks

MCP servers that hold external resources (browser sessions, database connections) need to await cleanup before the process exits. onclose is the only transport/protocol callback called from an awaitable context (transport.close() is async, awaited by server.close()). The other callbacks (onmessage, onerror) fire from event emitters that cannot await.

The onclose signature changes from () => void to () => void | Promise<void>, matching the existing pattern used by onsessionclosed in StreamableHTTPServerTransport. All transports and Protocol._onclose now await the callback.

Changed files: Transport interface, Protocol, StdioServerTransport, StreamableHTTPServerTransport, StdioClientTransport, WebSocketClientTransport, StreamableHTTPClientTransport, SSEClientTransport, InMemoryTransport, and mock transports in tests.

2. Close StdioServerTransport when stdin ends

The transport listened for data and error on stdin but not EOF. When the MCP client disconnects (closing stdin), the transport stays open and onclose never fires. This prevents servers from cleaning up resources.

This is especially visible with containerized MCP servers using docker run --rm: without onclose, the server process never exits, the container never stops, and containers accumulate on each client reconnect.

3. Add SIGTERM handlers in examples

All 10 examples only handle SIGINT (Ctrl+C). MCP servers run as background processes spawned by clients, not interactively. SIGTERM is what container runtimes and process managers send to stop a process. Added SIGTERM handlers alongside SIGINT in all examples.

Test plan

  • New test: should close when stdin ends (push null to stdin, verify onclose fires)
  • New test: should await async onclose callback (async cleanup completes before close() resolves)
  • Existing debounce test passes (state cleared synchronously before async callbacks)
  • All server tests pass (39/39)
  • All core tests pass (440/440)
  • Client test failure is pre-existing on main (jose/RSA base64 error message mismatch)

@MayCXC MayCXC requested a review from a team as a code owner March 29, 2026 12:04
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 29, 2026

🦋 Changeset detected

Latest commit: 3f70c00

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/core Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/client Patch
@modelcontextprotocol/node Patch
@modelcontextprotocol/express Patch
@modelcontextprotocol/hono Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@MayCXC MayCXC changed the title fix(server): close StdioServerTransport when stdin ends fix: async onclose, stdin EOF detection, SIGTERM in examples Mar 29, 2026
@MayCXC MayCXC force-pushed the fix/stdio-server-stdin-eof branch 2 times, most recently from 8cff2e5 to 5559b99 Compare March 29, 2026 14:26
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 29, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@1814

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@1814

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@1814

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@1814

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@1814

commit: 3f70c00

@MayCXC MayCXC force-pushed the fix/stdio-server-stdin-eof branch from 5559b99 to 3d3234b Compare March 29, 2026 14:30
Three related improvements to server lifecycle handling:

1. Allow async onclose callbacks on Transport and Protocol.
   MCP servers that hold external resources (browser sessions,
   database connections) need to await cleanup before the process
   exits. The onclose signature changes from `() => void` to
   `() => void | Promise<void>`, matching the existing pattern
   used by onsessionclosed in StreamableHTTPServerTransport.
   All transports and Protocol._onclose now await the callback.

2. Close StdioServerTransport when stdin ends. The transport
   listened for data and error but not EOF. When the MCP client
   disconnects, the transport stays open and onclose never fires.
   This is especially visible with containerized servers using
   docker run with automatic removal: without onclose the server
   never exits and the container accumulates.

3. Add SIGTERM handlers alongside SIGINT in all examples. MCP
   servers run as background processes spawned by clients, not
   interactively. SIGTERM is what container runtimes and process
   managers send to stop a process.
@MayCXC MayCXC force-pushed the fix/stdio-server-stdin-eof branch from 3d3234b to 3f70c00 Compare March 29, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant