Skip to content

Conversation

@JPeer264
Copy link
Member

Problem

In getsentry/sentry-mcp#652 we had to manually wait for the flush to ensure that async operations completed before the response was sent.

This was caused by the MCP server streaming data after the actual request has been finished. When using the Cloudflare SDK with MCP servers (or other streaming APIs), spans created during the streaming phase were not being flushed properly because the flush() method would complete before these async spans finished.

Solution

During the request, when a span is created for the streaming call, we now register listeners on the CloudflareClient for spanStart and spanEnd events. The client tracks all pending spans and waits for them to complete before flushing.

Specifically:

  • Added _pendingSpans Set to track active span IDs
  • Created a promise-based mechanism (_spanCompletionPromise) to wait for all spans to complete
  • Modified the flush() method to wait for pending spans before calling super.flush()
  • Added a timeout (default 5 seconds) to prevent indefinite waiting

Test

Added a comprehensive E2E test application (cloudflare-mcp) that:

  • Sets up a Cloudflare Worker with an MCP server
  • Registers a tool that creates spans during execution
  • Verifies that both the HTTP request span and the MCP tool call span are properly sent to Sentry
  • Validates span relationships (parent/child) and trace IDs

The test confirms that spans are now flushed correctly without manual intervention.

@JPeer264 JPeer264 marked this pull request as ready for review November 26, 2025 08:09
await spanCompletionRace;
}

this._resetSpanCompletionPromise();
Copy link

Choose a reason for hiding this comment

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

Bug: Race condition clears pending spans during flush

The unconditional call to _resetSpanCompletionPromise() after waiting for pending spans creates a race condition. If new spans start after the await completes but before the reset executes, their tracking state gets cleared. This causes those spans to not be waited for in subsequent flushes and potentially not be sent to Sentry. The reset should only clear the state that was waited for, not any new spans that started concurrently.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

That is actually a very good point. I actually added it for 1 reason, but by then the code looked a little different. The reason was that it could happen that there is some stale data somehow, but this couldn't happen anymore since there is a Promise.race and a conditional before.

Will remove it.

}

this._resetSpanCompletionPromise();

Copy link

Choose a reason for hiding this comment

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

Bug: Timeout budget not properly managed across operations

The timeout parameter represents the total time budget for the entire flush operation, but the implementation uses it twice: first for waiting for pending spans (line 84) and then passes the same value to super.flush(timeout) (line 101). This means the actual total time could be up to double the intended timeout. The remaining time budget should be calculated after the span wait completes and passed to the parent flush method.

Fix in Cursor Fix in Web

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.

2 participants