Skip to content

fix: bound streamable HTTP memory usage - #970

Merged
DaleSeo merged 3 commits into
mainfrom
fix/bound-streamable-http-memory
Jul 14, 2026
Merged

fix: bound streamable HTTP memory usage#970
DaleSeo merged 3 commits into
mainfrom
fix/bound-streamable-http-memory

Conversation

@DaleSeo

Copy link
Copy Markdown
Member

Motivation and Context

The Streamable HTTP client previously allowed an SSE event to grow indefinitely while waiting for its terminating blank line. The server similarly buffered complete POST bodies without a size limit, allowing a peer to exhaust process memory.
This PR applies configurable limits of 16 MiB per SSE event and 4 MiB per POST body by default. Oversized SSE streams terminate with a typed error without reconnecting, and oversized POST bodies receive HTTP 413.

How Has This Been Tested?

Added regression coverage for unterminated and multi-line SSE events.

Breaking Changes

No source-level breaking changes are expected.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@github-actionsgithub-actionsBot added T-dependencies Dependencies related changes T-config Configuration file changes T-core Core library changes T-transport Transport layer changes labels Jul 10, 2026
@DaleSeo
DaleSeoforce-pushed the fix/bound-streamable-http-memory branch from fcc5928 to d3176f8CompareJuly 10, 2026 20:31
@DaleSeo
DaleSeo marked this pull request as ready for review July 10, 2026 21:06
@DaleSeo
DaleSeo requested a review from a team as a code ownerJuly 10, 2026 21:06
@DaleSeoDaleSeo self-assigned this Jul 10, 2026
alexhancock
alexhancock previously approved these changes Jul 14, 2026
if auth_token.is_none() {
auth_token = Some(self.get_access_token().await?);
}
self.http_client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My only Q would be whether we can apply the new defaults at the HTTP client level for all requests, instead of providing these methods for per request type limit setting. If not, feel free to go ahead as-is.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@alexhancock The value here is actually a single client-wide setting that gets threaded through each call. I see the method signature make it look like a per-request-type limit. I'll add some comment to clear that up.

@alexhancockalexhancockJul 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My question was more why we needed the new wrapper methods, and why the limit couldn't be applied internally in self.http_client.get_stream_with_max_sse_event_size for example

Just not yet understanding

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@alexhancock The limit isn't stored on the client, so there's nothing for AuthClient to read internally. It lives on the transport config, and the clients are separate objects passed into with_client(client, config). One of them is reqwest::Client, which is a foreign type we can't add a field to, so the value has to come from the worker at call time.

Also, AuthClient isn't where the bounding can happen. The cap is applied at the raw byte layer, where response.bytes_stream() gets wrapped into an SseStream. That only happens in the leaf client (reqwest or unix socket). AuthClient just adds the auth token and forwards to the inner client, so all it can do is pass max_sse_event_size along.

So the value flows from the worker, through AuthClient, down to the client that actually reads the bytes. Dropping the parameter would mean storing the limit on the client, but because it's implemented on the foreign reqwest::Client that needs a wrapper type and removing the bare impl. I'd rather keep that as a separate follow-up.

@DaleSeo
DaleSeo merged commit 24ba526 into mainJul 14, 2026
21 checks passed
@DaleSeo
DaleSeo deleted the fix/bound-streamable-http-memory branch July 14, 2026 19:05
@github-actionsgithub-actionsBot mentioned this pull request Jul 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-configConfiguration file changesT-coreCore library changesT-dependenciesDependencies related changesT-transportTransport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@DaleSeo@alexhancock