How the MCP Server Handles SIGTERM and SIGINT Signals for Graceful Shutdown

The MCP server registers signal listeners in src/index.ts that trigger the stopServer helper in src/server.ts, which closes the underlying MCP transport and exits the process with code 0.

The regenrek/deepwiki-mcp repository implements a robust signal handling mechanism to ensure the Model Context Protocol (MCP) server shuts down gracefully when receiving termination signals. This prevents data loss and ensures open transports close properly before the Node.js process exits.

Signal Registration in the Entry Point

The CLI entry point at src/index.ts registers handlers for both SIGTERM and SIGINT before starting any network transport. This guarantees the handlers remain active for the entire process lifetime.

process.on('SIGTERM', () => stopServer(mcp))
process.on('SIGINT',  () => stopServer(mcp))

These listeners are attached before the stdio, HTTP, or SSE transport initializes, ensuring that even early termination attempts trigger the graceful shutdown sequence. The handlers capture the mcp server instance and pass it to the stopServer utility defined in src/server.ts.

Graceful Shutdown Logic Implementation

The actual shutdown logic resides in src/server.ts within the exported stopServer function. This implementation ensures the MCP SDK closes all active connections before forcing process termination.

Closing the MCP Transport

The function first attempts to close the underlying MCP server using the SDK's native close method. This method shuts down any open transports, including HTTP listeners, SSE sessions, or stdio pipes.

export async function stopServer(server: McpServer) {
  try {
    await server.close()
  } catch (error) {
    console.error('Error occurred during server stop:', error)
  } finally {
    process.exit(0)
  }
}

Errors during the close operation are caught and logged to stderr, but they do not prevent the shutdown sequence from completing.

Process Termination Guarantees

The finally block ensures process.exit(0) executes regardless of whether the server closed successfully or threw an error. This guarantees the Node.js process terminates with exit code 0, signaling to the operating system that the shutdown was intentional and successful.

Practical Implementation Example

When building a custom MCP server using this pattern, mirror the built-in CLI structure to ensure robust signal handling:

import { createServer, startServer, stopServer } from './src/server'

// Create the MCP instance
const mcp = createServer({ name: 'my-mcp', version: '1.0.0' })

// Register graceful-shutdown handlers before starting transport
process.on('SIGTERM', () => stopServer(mcp))
process.on('SIGINT',  () => stopServer(mcp))

// Start with the desired transport (e.g., HTTP on port 4000)
await startServer(mcp, { type: 'http', port: 4000, endpoint: '/mcp' })

Testing Signal Handling

Verify the graceful shutdown behavior using shell commands:


# Run the server in the background

node ./dist/cli.mjs &
SERVER_PID=$!

# Give it a moment to start

sleep 1

# Send SIGTERM

kill -TERM $SERVER_PID

# Wait and verify the process exited cleanly

wait $SERVER_PID && echo "Graceful shutdown completed"

Summary

  • Early Registration: Signal handlers attach in src/index.ts (lines 27-28) before transport initialization, ensuring coverage for the entire process lifetime.
  • SDK Integration: The stopServer function in src/server.ts (lines 83-92) delegates to the MCP SDK's close method to terminate transports cleanly.
  • Guaranteed Exit: A finally block ensures process.exit(0) executes even if shutdown errors occur, preventing zombie processes.
  • OS Compliance: Exit code 0 signals successful termination to process managers like systemd, Docker, and Kubernetes.

Frequently Asked Questions

What happens if the MCP server is in the middle of a request when SIGTERM is received?

The server.close() method from the MCP SDK initiates a graceful shutdown, allowing existing connections to complete while preventing new ones. However, the current implementation in regenrek/deepwiki-mcp does not implement a custom timeout; it relies on the SDK's internal behavior and immediately proceeds to process.exit(0) after the close promise resolves or rejects.

Can I customize the shutdown behavior to run additional cleanup tasks?

Yes. You can modify the stopServer function in src/server.ts to include additional cleanup logic before or after the server.close() call. For example, you could flush logs to disk, close database connections, or notify external services. Ensure you wrap cleanup operations in try-catch blocks to prevent errors from blocking the final process.exit(0) call in the finally block.

Why does the server exit with code 0 instead of a different exit code?

Exit code 0 indicates successful termination to the operating system and process managers like systemd, Docker, or Kubernetes. Since the shutdown is intentional and successful (even if minor errors occurred during transport closure), code 0 is appropriate. If you need to distinguish between different shutdown scenarios for monitoring purposes, you could modify the process.exit() call to use different codes based on error conditions, though this would require changes to src/server.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →