Skip to content

Conversation

@arabold
Copy link
Owner

@arabold arabold commented Dec 5, 2025

Implement a Server-Sent Events (SSE) heartbeat mechanism to keep the MCP connection alive and prevent disconnections after a period of inactivity. This change includes corresponding tests to verify the functionality of the heartbeat feature.

Fixes #285

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements an SSE heartbeat mechanism to prevent MCP connection timeouts during periods of inactivity. A heartbeat message (SSE comment) is sent every 30 seconds to keep connections alive.

Key Changes:

  • Added heartbeat interval mechanism that sends ": heartbeat\n\n" messages every 30 seconds on SSE connections
  • Enhanced cleanup logic to properly clear heartbeat intervals when connections close or error
  • Added error handlers for both SSE and Streamable HTTP endpoints to ensure proper resource cleanup

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/services/mcpService.ts Implements heartbeat mechanism with 30-second intervals for /sse endpoint, adds cleanup logic for intervals, and adds error event handlers for both /sse and /mcp endpoints
test/mcp-http-e2e.test.ts Adds E2E test that verifies heartbeat messages are sent by directly connecting to the SSE endpoint and waiting for the heartbeat comment
src/services/mcpService.test.ts Adds unit tests for service registration and cleanup, verifying that heartbeat intervals are properly tracked and cleaned up

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Check that routes are registered (printRoutes uses a tree format)
const routes = server.printRoutes();
expect(routes).toContain("essages");
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion checks for "essages" instead of the full route path "/messages" or "messages". While this may work if printRoutes() outputs routes in a tree format, it's fragile and unclear. Consider using a more specific assertion that matches the full route path, or add a comment explaining why a partial match is necessary.

Suggested change
expect(routes).toContain("essages");
expect(routes).toContain("/messages");

Copilot uses AI. Check for mistakes.
- Remove unused connectionClosed variable in E2E test
- Remove misleading unit test that didn't actually verify heartbeat
- Add clarifying comments for Fastify radix tree route assertions
- Improve route assertions to be more specific
@arabold
Copy link
Owner Author

arabold commented Dec 5, 2025

Addressed the review comments in commit 4c976f3:

  1. Unused connectionClosed variable - Removed the unused variable and replaced the end event handler with a descriptive comment.

  2. Misleading heartbeat unit test - Removed the test that claimed to verify heartbeat intervals but didn't actually test the behavior. Added a comment explaining that heartbeat verification is done in the E2E test which can observe raw SSE stream data. The remaining unit tests focus on setup, cleanup, and internal state verification.

  3. Partial route matching (essages, cp) - These partial matches are actually correct because Fastify's printRoutes() uses a radix tree format where common prefixes are shared. The routes /messages and /mcp share the m prefix, so they appear as:

    └── m
        ├── essages (POST)
        └── cp (POST)
    

    Added detailed comments explaining this format. Made the /mcp assertion more specific (cp (POST)) to avoid ambiguity.

All tests pass (5 unit tests, 3 E2E tests).

@arabold arabold merged commit 0de896c into main Dec 5, 2025
3 checks passed
@arabold arabold deleted the fix/285-sse-heartbeat branch December 5, 2025 19:48
@github-actions
Copy link

github-actions bot commented Dec 5, 2025

🎉 This PR is included in version 1.32.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP will stop after a period of time

2 participants