SIGINT and SIGTERM handled for graceful shutdown
Why it matters
MCP servers running as child processes receive SIGINT when the user presses Ctrl+C and SIGTERM when the parent process shuts down. Without signal handlers, the Node.js process exits immediately (SIGINT default), potentially mid-write on an in-progress JSON-RPC response. The client receives a truncated or empty message and marks the server as crashed. CWE-404 (improper resource shutdown) covers this: open database connections, file handles, and HTTP keep-alives are abandoned rather than closed, which can leave the remote systems in inconsistent state.
Severity rationale
Medium because abrupt termination corrupts in-progress responses and leaks resources, but the immediate impact is limited to the session in progress at shutdown time.
Remediation
Register SIGINT and SIGTERM handlers that call server.close() before exiting. The SDK's close() drains in-flight messages and closes the transport cleanly.
// src/index.ts
async function shutdown() {
console.error('Shutting down...')
await server.close() // closes transport, drains in-flight messages
process.exit(0)
}
process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)
const transport = new StdioServerTransport()
await server.connect(transport)
If your server holds database connections or open file handles, close them inside shutdown() before calling process.exit(0).
Detection
-
ID:
graceful-shutdown -
Severity:
medium -
What to look for: Count all signal handlers (SIGINT, SIGTERM) and cleanup routines. Enumerate whether the server completes in-flight requests before shutting down. Check for signal handlers (
process.on('SIGINT'),process.on('SIGTERM'),signal.signal()in Python). When the server receives a shutdown signal, it should close the transport cleanly, finish any in-progress tool calls if possible, and release resources (database connections, file handles). Check that the server doesn't just callprocess.exit()immediately (which could corrupt in-progress responses). -
Pass criteria: The server handles SIGINT and SIGTERM, closes the transport cleanly, and exits. In-progress operations are either completed or cancelled gracefully. At least 1 signal handler must be registered for graceful shutdown.
-
Fail criteria: No signal handlers (abrupt termination may corrupt in-progress responses), or signal handlers that call
process.exit(0)immediately without cleanup. -
Skip (N/A) when: All checks skip when no MCP server is detected.
-
Cross-reference: For timeout handling during shutdown, see
timeout-handling. -
Detail on fail:
"No SIGINT/SIGTERM handlers — server will be killed abruptly, potentially corrupting in-progress responses or leaving resources open"or"Signal handler calls process.exit(0) immediately without closing the transport or cleaning up database connections" -
Remediation: Handle shutdown signals gracefully:
// src/index.ts — graceful shutdown process.on('SIGINT', async () => { await server.close(); process.exit(0) })const server = new McpServer({ name: 'my-server', version: '1.0.0' }) const transport = new StdioServerTransport() async function shutdown() { console.error('Shutting down...') await server.close() // closes transport and cleans up process.exit(0) } process.on('SIGINT', shutdown) process.on('SIGTERM', shutdown) await server.connect(transport)import signal import asyncio async def shutdown(server): await server.close() loop = asyncio.get_event_loop() loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(shutdown(server))) loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.create_task(shutdown(server)))
External references
- cwe · CWE-404 — Improper Resource Shutdown or Release
- iso-25010:2011 · reliability
Taxons
History
- 2026-04-18·v1.0.0·Initial import from mcp-server·automated