Skip to content

Conversation

@jlowin
Copy link
Owner

@jlowin jlowin commented Dec 6, 2025

Implements SEP-1699 SSE polling support for long-running operations.

When tools run for extended periods, load balancers often terminate idle connections. SSE polling allows the server to gracefully close connections and have clients automatically reconnect and resume from where they left off.

from fastmcp import FastMCP, Context, EventStore

mcp = FastMCP("My Server")

@mcp.tool
async def long_running_task(ctx: Context) -> str:
    for i in range(100):
        await ctx.report_progress(i, 100)
        
        # Close connection periodically to avoid LB timeouts
        if i % 30 == 0 and i > 0:
            await ctx.close_sse_stream()
        
        await do_work()
    return "Done!"

# Enable with EventStore
app = mcp.http_app(
    event_store=EventStore(),
    retry_interval=2000,
)

Changes:

  • EventStore class backed by AsyncKeyValue for pluggable storage backends
  • Context.close_sse_stream() for server-initiated disconnection
  • event_store and retry_interval parameters on http_app()

Requires MCP SDK with SEP-1699 support (modelcontextprotocol/python-sdk#1654).

@marvin-context-protocol marvin-context-protocol bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. http Related to HTTP transport, networking, or web server functionality. server Related to FastMCP server implementation or server-side functionality. labels Dec 6, 2025
@jlowin jlowin force-pushed the feature/sep-1699-sse-polling branch from 0387cf6 to 135e208 Compare December 6, 2025 15:46
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 6, 2025

Warning

Rate limit exceeded

@jlowin has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 21 minutes and 38 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 135e208 and 71d3eba.

📒 Files selected for processing (1)
  • docs/deployment/http.mdx (1 hunks)

Walkthrough

This pull request introduces SSE (Server-Sent Events) polling support for long-running operations with resumability. It adds a new EventStore class backed by an AsyncKeyValue storage backend to persist and replay events across SSE reconnections. The EventStore is integrated into the HTTP transport layer through new parameters (event_store and retry_interval) in the HTTP app factory and FastMCP server configuration. A new close_sse_stream() method is added to the Context class to terminate SSE streams. The EventStore is exported as part of the public API, and documentation is added describing the feature, configuration options, and workflow.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides good context and includes a code example, but does not follow the repository's required checklist template for contributors and reviewers. Add the required Contributors Checklist and Review Checklist sections with appropriate checkboxes to match the template and demonstrate due diligence.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding SSE polling support with EventStore for long-running operations.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jlowin
Copy link
Owner Author

jlowin commented Dec 6, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/fastmcp/server/event_store.py (1)

116-131: Potential race condition in concurrent stream updates.

The read-modify-write pattern on stream_data.event_ids is not atomic. If two store_event calls for the same stream_id execute concurrently, one event could be lost when the second write overwrites the first.

For single-stream-per-request scenarios (typical SSE usage), this is unlikely to cause issues. However, for shared streams or high-concurrency production deployments, consider documenting this limitation or adding optional locking.

If this becomes a concern in production, you could:

  1. Document that each stream should have a single writer
  2. Use Redis LPUSH/RPUSH for atomic list operations when using RedisStore
  3. Add an optional distributed lock wrapper for multi-writer scenarios
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07750ef and 135e208.

⛔ Files ignored due to path filters (1)
  • tests/server/test_event_store.py is excluded by none and included by none
📒 Files selected for processing (6)
  • docs/deployment/http.mdx (1 hunks)
  • src/fastmcp/__init__.py (2 hunks)
  • src/fastmcp/server/context.py (1 hunks)
  • src/fastmcp/server/event_store.py (1 hunks)
  • src/fastmcp/server/http.py (3 hunks)
  • src/fastmcp/server/server.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style

Files:

  • src/fastmcp/__init__.py
  • src/fastmcp/server/context.py
  • src/fastmcp/server/http.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/event_store.py
docs/**/*.mdx

📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)

docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...

Files:

  • docs/deployment/http.mdx
🧠 Learnings (1)
📚 Learning: 2025-12-04T00:17:41.238Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T00:17:41.238Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing using in-memory transport; only use HTTP transport when explicitly testing network features

Applied to files:

  • src/fastmcp/__init__.py
  • docs/deployment/http.mdx
  • src/fastmcp/server/server.py
🧬 Code graph analysis (3)
src/fastmcp/__init__.py (1)
src/fastmcp/server/event_store.py (1)
  • EventStore (40-177)
src/fastmcp/server/server.py (2)
src/fastmcp/server/event_store.py (1)
  • EventStore (40-177)
src/fastmcp/server/http.py (1)
  • StarletteWithLifespan (71-74)
src/fastmcp/server/event_store.py (1)
src/fastmcp/utilities/logging.py (1)
  • get_logger (14-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
🔇 Additional comments (10)
src/fastmcp/server/context.py (1)

489-526: LGTM! Well-documented SSE stream closing method.

The implementation correctly handles the no-op case with a debug log when the transport doesn't support SSE polling. The condition on line 520 properly checks both that the request context exists and that it has a close_sse_stream callable. The docstring provides clear usage guidance and a practical example.

src/fastmcp/__init__.py (1)

17-17: LGTM! EventStore correctly exposed in public API.

The import and __all__ export follow existing patterns and maintain alphabetical ordering.

Also applies to: 34-34

docs/deployment/http.mdx (2)

241-252: Clear workflow explanation.

The 4-step workflow is well-documented and the note about close_sse_stream() being a no-op without EventStore helps users understand the safe fallback behavior.


254-271: Good Redis backend example with configuration options.

The custom storage backend section provides practical guidance for production deployments. The parameters (max_events_per_stream, ttl) are documented inline.

src/fastmcp/server/http.py (1)

274-315: LGTM! retry_interval parameter correctly wired through.

The new parameter is properly typed, documented, and forwarded to StreamableHTTPSessionManager. The docstring correctly notes that retry_interval requires event_store to be set.

Consider whether the SDK's StreamableHTTPSessionManager validates that retry_interval is only meaningful when event_store is provided, or if FastMCP should add a warning when retry_interval is set without event_store.

src/fastmcp/server/server.py (2)

67-67: LGTM! EventStore import correctly placed.


2343-2393: LGTM! http_app method correctly extended with SSE polling parameters.

The event_store and retry_interval parameters are:

  • Properly typed with EventStore | None and int | None
  • Well documented in the docstring with clear descriptions of their purpose
  • Correctly passed only to create_streamable_http_app (not to SSE transport, which doesn't support this feature)
src/fastmcp/server/event_store.py (3)

26-37: LGTM! Clean Pydantic models for event storage.

The EventEntry and StreamEventList models are simple and well-structured. Using dict | None for the serialized message is appropriate for JSON-RPC message storage.


135-176: Solid replay implementation with good error handling.

The method handles all edge cases gracefully:

  • Missing event ID → warning log + return None
  • Missing stream → warning log + return None
  • Event ID not in stream list → warning log + return None
  • Events without messages (priming events) are correctly skipped

70-92: LGTM! Well-designed constructor with sensible defaults.

The constructor follows established patterns in the codebase (similar to ResponseCachingMiddleware and OAuthProxy). Default values are appropriate:

  • MemoryStore for simple deployments
  • 100 events per stream limit
  • 1-hour TTL

The PydanticAdapter usage ensures type-safe serialization.

@marvin-context-protocol
Copy link
Contributor

Test Failure Analysis

Summary: The integration test test_call_tool_list_commits timed out after 30 seconds due to a 429 rate limit error from GitHub Copilot's MCP API.

Root Cause: The test is calling the real GitHub Copilot MCP API endpoint (https://api.githubcopilot.com/mcp/) which returned a 429 Too Many Requests error. The error occurred during teardown, suggesting the request hung without completing, eventually hitting the 30-second pytest timeout.

This failure is unrelated to the PR's changes - the EventStore and SSE polling additions don't affect how this integration test connects to external APIs. This appears to be a transient rate limiting issue with GitHub's infrastructure.

Suggested Solution:

  1. Immediate fix: Re-run the integration tests - this is likely a transient rate limit issue
  2. Long-term improvement: Add retry logic with exponential backoff for integration tests that call external APIs, or mock the GitHub MCP API to avoid rate limiting in CI
Detailed Analysis

From the logs:

[12/06/25 15:47:05] DEBUG    [Client-b06e] called call_tool:      client.py:1197
                             list_commits                                       

The test successfully initiated the call_tool request but never received a response. During teardown:

httpx.HTTPStatusError: Client error '429 Too Many Requests' for url 'https://api.githubcopilot.com/mcp/'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429

The 429 error indicates GitHub's API rate limiting kicked in. The test waited for 30+ seconds before pytest-timeout killed it.

All other integration tests passed (13/14), suggesting this is a specific issue with the GitHub API's rate limits rather than a systemic problem.

Related Files
  • tests/integration_tests/test_github_mcp_remote.py:99 - The failing test
  • Test calls real GitHub Copilot MCP API at https://api.githubcopilot.com/mcp/
  • Uses FASTMCP_GITHUB_TOKEN environment variable for authentication

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

Labels

enhancement Improvement to existing functionality. For issues and smaller PR improvements. http Related to HTTP transport, networking, or web server functionality. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants