Title: Close servlet streamable HTTP transports on async lifecycle events by lxq19991111 · Pull Request #1027 · modelcontextprotocol/java-sdk · GitHub
Open Graph Title: Close servlet streamable HTTP transports on async lifecycle events by lxq19991111 · Pull Request #1027 · modelcontextprotocol/java-sdk
X Title: Close servlet streamable HTTP transports on async lifecycle events by lxq19991111 · Pull Request #1027 · modelcontextprotocol/java-sdk
Description: Summary Fixes #1021. Register servlet async lifecycle cleanup for Streamable HTTP SSE transports created by HttpServletStreamableServerTransportProvider. This PR makes the current HTTP/SSE transport close when the servlet async context completes, times out, or errors. It also routes SSE write failures through the transport close() path instead of directly removing the logical MCP session from the session registry. Motivation and Context The servlet Streamable HTTP transport creates async SSE responses for multiple request paths: GET listening streams GET replay streams POST streaming responses When a client disconnects, the current HTTP/SSE response can become unusable while the SDK still keeps the async context and transport state alive. In production this can leave server-side sockets in CLOSE-WAIT and keep Tomcat resources tied up until an external timeout or kernel keepalive eventually reclaims them. This issue was observed from Spring AI WebMVC usage first, but the lifecycle gap is in the MCP Java SDK core servlet transport. Related Issues and PRs Fixes #1021. Related to #952 and #972: a request-specific SSE write failure should not immediately remove the logical MCP session. Related to #920: first transient SSE write failure should not orphan the session. Related to #726 only by production symptom (CLOSE-WAIT / Tomcat resource growth). #726 targets the older WebMvcSseServerTransportProvider / McpServerSession path, while this PR targets the core HttpServletStreamableServerTransportProvider Streamable HTTP servlet path. Follow-up logical session eviction after repeated keep-alive failures is handled separately in #1028. Changes Add a shared servlet async lifecycle listener helper. Register async lifecycle cleanup for GET replay streams. Reuse the same cleanup helper for GET listening streams. Register async lifecycle cleanup for POST streaming responses. Close replay and POST streaming transports through the transport close() path on handling failures. Close only the current transport on SSE write failure instead of removing the logical MCP session. Add focused regression coverage for GET listening cleanup, GET replay cleanup, POST streaming response cleanup, and write-failure behavior. Rationale A TCP/SSE stream lifecycle is not the same as an MCP logical session lifecycle. A servlet async error, timeout, completion, or response write failure proves that the current HTTP/SSE transport is no longer usable. It does not necessarily prove that the logical MCP session should be removed from the session registry. This distinction matters for Streamable HTTP because request-specific POST responses can fail or be closed after the response has already been delivered. Removing the logical session on that single transport failure causes the next request with the same mcp-session-id to fail with Session not found. This PR therefore closes the current transport and completes the associated servlet async context, while leaving logical session eviction to existing protocol-level paths such as DELETE, server shutdown, or follow-up liveness policy such as #1028. The async listener reuses the existing stream/transport close paths. These paths are guarded and idempotent, so cleanup can be triggered consistently from servlet async completion, timeout, error, and write-failure paths. Scope This PR intentionally focuses on the MCP Java SDK core servlet Streamable HTTP transport: HttpServletStreamableServerTransportProvider Out of scope: Keep-alive failed-ping logical session eviction, handled separately in #1028 Explicit session TTL Session persistence / distributed session storage Last-Event-ID resumability / event store support Spring AI WebMVC-specific follow-up Deprecated HttpServletSseServerTransportProvider How Has This Been Tested? mvn -pl mcp-core -Dtest=HttpServletStreamableServerTransportProviderTests -DforkCount=0 test mvn -pl mcp-core test The added tests cover: GET listening stream async error closes the listening stream GET replay request async error closes the current transport POST streaming response async error closes the current transport SSE write failure closes only the current transport and does not remove the logical MCP session 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
Open Graph Description: Summary Fixes #1021. Register servlet async lifecycle cleanup for Streamable HTTP SSE transports created by HttpServletStreamableServerTransportProvider. This PR makes the current HTTP/SSE transpor...
X Description: Summary Fixes #1021. Register servlet async lifecycle cleanup for Streamable HTTP SSE transports created by HttpServletStreamableServerTransportProvider. This PR makes the current HTTP/SSE transpor...
Opengraph URL: https://github.com/modelcontextprotocol/java-sdk/pull/1027
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:dc9c15d3-477f-1c64-bc24-7c1391ce1e2d |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | A7E4:2F98EE:100B680:14B1A14:6A5C3241 |
| html-safe-nonce | 9185d779aadfa682288093c8457d56db4430ecaf503fb523231aac82c5e04810 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBN0U0OjJGOThFRToxMDBCNjgwOjE0QjFBMTQ6NkE1QzMyNDEiLCJ2aXNpdG9yX2lkIjoiMTU0NDE3MTk2ODgzMjIyMTc2MiIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 4cf8271101fb59892ef14fe030a9292795659d96b8eab35e7e8b3023ca68aa68 |
| hovercard-subject-tag | pull_request:3860372456 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/java-sdk/pull/1027/files |
| twitter:image | https://avatars.githubusercontent.com/u/268150704?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/268150704?s=400&v=4 |
| og:image:alt | Summary Fixes #1021. Register servlet async lifecycle cleanup for Streamable HTTP SSE transports created by HttpServletStreamableServerTransportProvider. This PR makes the current HTTP/SSE transpor... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5290d7e14309ad1e76106a9c4237bd1041517e83ea182c8ab756752cb0c6940b |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/java-sdk git https://github.com/modelcontextprotocol/java-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 919609219 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/java-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 919609219 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/java-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c975978430e9ad293956f2bbdaf153b1bd84a99 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width