Title: Proposal: Add client state persistence API to allow MCP clients to survive restarts · Issue #376 · modelcontextprotocol/java-sdk · GitHub
Open Graph Title: Proposal: Add client state persistence API to allow MCP clients to survive restarts · Issue #376 · modelcontextprotocol/java-sdk
X Title: Proposal: Add client state persistence API to allow MCP clients to survive restarts · Issue #376 · modelcontextprotocol/java-sdk
Description: Motivation Problem Statement The current Java MCP SDK provides no mechanism for persisting client runtime state. Without SDK support, it is impossible for applications to resume a prior session, reuse an existing access token, or skip re...
Open Graph Description: Motivation Problem Statement The current Java MCP SDK provides no mechanism for persisting client runtime state. Without SDK support, it is impossible for applications to resume a prior session, re...
X Description: Motivation Problem Statement The current Java MCP SDK provides no mechanism for persisting client runtime state. Without SDK support, it is impossible for applications to resume a prior session, re...
Opengraph URL: https://github.com/modelcontextprotocol/java-sdk/issues/376
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Proposal: Add client state persistence API to allow MCP clients to survive restarts","articleBody":"# Motivation\n\n## Problem Statement\n\nThe **current Java MCP SDK provides no mechanism for persisting client runtime state**. Without SDK support, it is **impossible** for applications to resume a prior session, reuse an existing access token, or skip redundant protocol negotiation after a restart.\n\n## Example Failure Scenario\n\n1. **Startup** – An `McpClient` sends an `initialize` request to `https://example.com/mcp`. \n2. **Session established** – The server responds with `Mcp-Session-Id: 1868a90c…`; the client continues issuing requests within that session. The server may create associated state (e.g., cached prompts, resources, intermediate results) tied to the session ID. \n3. **Crash / redeploy** – The JVM crashes or the container restarts. \n4. **Restart** – The new client instance knows nothing about the previous session ID, access token, or negotiated metadata. As a result, it must repeat the OAuth flow and send a new `initialize` request, forcing the server to discard prior state and degrading the user experience.\n\n## Impact\n\nIntroducing a **first-class SDK abstraction** for state persistence will deliver:\n\n* **Improve reliability** – Maintain continuity for long-running operations that depend on server-side session state\n* **Better user experience** – Seamless resumption without re-authentication delays\n* **Production readiness** – Essential for containerized environments with frequent deployments\n* **Operational simplicity** – Let the host application decide *where* and *how* to store this data (e.g., files, JDBC, Redis, secret vault) while keeping the SDK storage-agnostic\n\n## Use Cases\n\nThis feature is particularly valuable for:\n- **Long-running applications** that need to survive restarts without losing context\n- **Containerized environments** with frequent deployments and rolling updates\n- **Enterprise applications** where remote MCP servers are desirable because they are easier to manage across thousands of employees\n- **Batch processing systems** that require session continuity across job restarts\n\n\n# Proposed API\n\nHere is an example API to support discussions around this request. \n\n```java\npackage io.modelcontextprotocol.spec;\n\n/** Wraps the OAuth 2.1 access token used in Authorization headers. */\npublic record TokenInfo(String accessToken) { }\n\n/**\n* Negotiated protocol version and server capabilities returned by the server.\n*\n* @param protocolVersion the agreed MCP protocol version\n* @param capabilitiesJson server capabilities serialized as JSON (or any text format)\n*/\npublic record ServerMetadata(String protocolVersion, String capabilitiesJson) { }\n```\n\n```java\npackage io.modelcontextprotocol.spec;\n\nimport java.util.Optional;\n\n/**\n * Persists MCP client state so a Streamable-HTTP client can resume seamlessly\n * after a JVM restart. Implementations MUST be thread-safe.\n *\n * \u003cp\u003eHost applications remain in full control of where \u0026 how the data is stored.\n *\n * \u003cp\u003eExample usage:\n *\n * \u003cpre\u003e{@code\n * StreamableHttpClientStateStore myStateStore = new MyJdbcBackedStateStore(\"my-client-key\");\n * var transport = HttpClientStreamableHttpTransport.builder(\"https://example.com/mcp\")\n * .clientStateStore(myStateStore)\n * .build();\n *\n * McpSyncClient client = McpClient.sync(transport).build();\n * }\u003c/pre\u003e\n */\npublic interface StreamableHttpClientStateStore {\n\n /* ------- Session ------- */\n\n /**\n * Called by the MCP client when a remote MCP server returns an MCP-Session-Id\n * per the Streamable HTTP specification after an initialize request.\n *\n * @param the session ID returned by the server.\n */\n void setSessionId(String sessionId);\n\n /**\n * Called by the MCP client before sending any requests to the server.\n * If a value is present, it will be included in the Mcp-Session-Id header.\n *\n * @return an Optional containing the stored session ID if one was previously stored.\n */\n Optional\u003cString\u003e getSessionId();\n\n /**\n * Called by the MCP client when the server indicates that the session has expired or is invalid,\n * to clear the stored session ID.\n */\n void clearSessionId();\n\n /* ------- Access Token ------- */\n\n /**\n * Called by the MCP client after obtaining a new OAuth 2.1 access token from the authorization server.\n *\n * @param token the TokenInfo containing the new access token.\n */\n void setAccessToken(TokenInfo token);\n\n /**\n * Called by the MCP client before sending any authorized requests to the server.\n * If a value is present, it will be included in the Authorization header as a Bearer token.\n *\n * @return an Optional containing the stored access token if one was previously stored.\n */\n Optional\u003cTokenInfo\u003e getAccessToken();\n\n /**\n * Called by the MCP client when the access token is revoked, expires, or is no longer valid,\n * to clear the stored token.\n */\n void clearAccessToken();\n\n /* ------- Server Metadata ------- */\n\n /**\n * Called by the MCP client after initialization to store the negotiated\n * protocol version and server capabilities for future reuse.\n *\n * @param metadata the ServerMetadata containing protocol version and capabilitiesJson.\n */\n void setServerMetadata(ServerMetadata metadata);\n\n /**\n * Called by the MCP client before initialization to retrieve any previously stored\n * protocol version and server capabilities.\n *\n * @return an Optional containing the stored ServerMetadata if one was previously stored.\n */\n Optional\u003cServerMetadata\u003e getServerMetadata();\n\n /**\n * Called by the MCP client to clear any stored server metadata,\n * for example if the protocol version becomes incompatible.\n */\n void clearServerMetadata();\n}\n```\n\n## Design Rationale\n\n| Aspect | Reasoning |\n| ------ | --------- |\n| **Atomic value objects** | strongly typed interfaces, ability to maybe add new fields if needed but we could drop these for just strings if we want to keep things super simple |\n| **`capabilitiesJson` as `String`** | Simplest for JDBC / key-value stores—no schema required. |\n| **Storage-agnostic** | Host application chooses file, DB, secret vault, etc.; SDK stays neutral. |\n\n## Implementation Details\n\n### Integration Point\nThe state store would be configured on the various transport builders for example `HttpClientStreamableHttpTransport.Builder`:\n\n```java\nvar transport = HttpClientStreamableHttpTransport.builder(\"https://example.com/mcp\")\n .clientStateStore(new FileBasedStateStore(\"./mcp-state.json\"))\n .build();\n```","author":{"url":"https://github.com/asaikali","@type":"Person","name":"asaikali"},"datePublished":"2025-07-06T06:14:46.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":2},"url":"https://github.com/376/java-sdk/issues/376"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:1242b71c-7d9b-2a40-0fdf-45acfad9a6dd |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 9B82:103DBC:272B8E3:3498E14:6A59B2E9 |
| html-safe-nonce | 6f8c824272a1d954acae622110cd22ba9405e82b4705f85c5e8a43d25c0982f8 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5QjgyOjEwM0RCQzoyNzJCOEUzOjM0OThFMTQ6NkE1OUIyRTkiLCJ2aXNpdG9yX2lkIjoiNTM3ODc4NTc3MjE5MDY3NTY4OSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 99dbb14fb1caad179de35a602c256e0d3f913b53882f691fa75c00a9707f8cfd |
| hovercard-subject-tag | issue:3206160843 |
| github-keyboard-shortcuts | repository,issues,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/_view_fragments/issues/show/modelcontextprotocol/java-sdk/376/issue_layout |
| twitter:image | https://opengraph.githubassets.com/8b7b8ea05596607204eb335258ab0d82e80a05f93e409d0e98002f92adef81cb/modelcontextprotocol/java-sdk/issues/376 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/8b7b8ea05596607204eb335258ab0d82e80a05f93e409d0e98002f92adef81cb/modelcontextprotocol/java-sdk/issues/376 |
| og:image:alt | Motivation Problem Statement The current Java MCP SDK provides no mechanism for persisting client runtime state. Without SDK support, it is impossible for applications to resume a prior session, re... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | asaikali |
| hostname | github.com |
| expected-hostname | github.com |
| None | ba3976babb66479b1c943a8edc0777d96157da48fadc0161f9ddb219deee8353 |
| turbo-cache-control | no-preview |
| 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 |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | ab680789ae4a316cdaf0d5a292a1760140931cc4 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width