SEP-XXXX: Data-Layer Sessions for MCP#20
SEP-XXXX: Data-Layer Sessions for MCP#20evalstate wants to merge 29 commits intomodelcontextprotocol:mainfrom
Conversation
Lightweight discussion starter for application-level sessions in MCP. Includes: - Capability advertisement shape (experimental.session) - session/create, session/list, session/delete JSON-RPC methods - Cookie echo pattern via _meta["mcp/session"] - Revocation and error handling shapes - Selective enforcement discussion - Transport-layer vs data-layer comparison - Open questions to guide working group discussion Companion JSONC file with raw API shapes for quick reference. Based on experimentation with fast-agent and the sessions track brief.
- Align title strings ('Code Review Session' in both files)
- Reorder: create → list → delete (natural CRUD lifecycle)
- Renumber JSON-RPC ids to match new order
- Add .gitignore for local tooling artifacts
- session/list: add cursor/nextCursor pagination (matches tools/list,
resources/list, prompts/list pattern from PaginatedRequestParams /
PaginatedResult)
- session/create: return session object in result body AND cookie in
_meta (result body for inspection, _meta for echo cycle — consistent
with Result having additionalProperties: {})
- Add 'Session Cookie: Placement' design discussion — named property
(like progressToken) vs. convention key (mcp/session) in _meta bag,
with trade-off table
- Add 'Revocation via null' design note — null in _meta is schema-valid
but novel; document alternatives (omit key, dedicated method)
- Add Schema Compatibility Notes section documenting review against
draft schema (DRAFT-2026-v1)
- Expand open questions to 10 (add cookie placement + revocation signal)
- Add companion file cross-reference
- Align .jsonc with all .md changes
Lightweight discussion starter for application-level sessions in MCP. Phase 1 scope: - session/create - session/resume - session/delete - cookie echo/revocation via _meta["mcp/session"] Phase 2 (deferred): - session/list - session/recover Includes use cases, implementation-informed considerations, and open questions for working-group discussion, plus a companion JSONC quick-reference of wire shapes.
…ate/transports-wg into draft/data-layer-sessions
…resume result bodies
| "name": "notebook_append", | ||
| "arguments": { "text": "remember this" }, | ||
| "_meta": { | ||
| "mcp/session": { "id": "sess-a1b2c3d4e5f6" } |
There was a problem hiding this comment.
If you want an expiry, maybe, the client needs to pass back the original expiry to mitigate clock skew issues?
There was a problem hiding this comment.
Expiry is intended as a moderately strong UX hint rather than a guarantee either way. I'd like to avoid handling clock skew here if we can!
…ate/transports-wg into draft/data-layer-sessions
…ate/transports-wg into draft/data-layer-sessions
|
This shifts session creation from the server to the client. I can understand why clients might want some measure of control over this. But what I feel might be missing is a server-sent hint or error-type that would require the client to operate within a session. Previously, that's been a pretty janky dance:
Can we consider giving a way for servers to signal the requirement to use sessions? |
Overall the stateless transport design is preferring optimistic usage (e.g. try and fail). I made a small update that the recommended thing to do would be to call I do think that there may be situations where sessions are optional - which in this current design would be indicated by the server supporting the capability, and responding to Tool annotations are slightly tricker is the |
|
This shifts session creation from the server to the client. I can understand why clients might want some measure of control over this. But what I feel might be missing is a server-sent hint or error-type that would require the client to operate within a session. Previously, that's been a pretty janky dance:
Can we consider giving a way for servers to signal the requirement to use sessions? |
Overall the stateless transport design is preferring optimistic usage (e.g. try and fail). I made a small update that the recommended thing to do would be to call I do think that there may be situations where sessions are optional - which in this current design would be indicated by the server supporting the capability, and responding to Tool annotations are slightly tricker is the |
| "id": 42, | ||
| "error": { | ||
| "code": -32043, | ||
| "message": "Session not found", |
There was a problem hiding this comment.
Should we add a SESSION_REQUIRED error? to indicate that a tool requires a session but doesn't have one
markdroth
left a comment
There was a problem hiding this comment.
Thanks for writing this up, Shawn! I'm really looking forward to seeing this go through.
|
|
||
| #### Using Sessions | ||
|
|
||
| To use a Session the Client request includes SessionMetadata in `_meta["io.modelcontextprotocol/session"]`: |
There was a problem hiding this comment.
If we want sessions to be more of a first-class thing, should this be a new top-level field in params rather than being in metadata?
There was a problem hiding this comment.
Leaving this TBC (and will keep the current convention in the drafts so it's a one-time change later)!
| } | ||
| ``` | ||
|
|
||
| 1. Unknown sessions **MUST** result in a `-32043 SESSION_NOT_FOUND` Error. The Client **SHOULD** treat the Session as permanently invalidated. |
There was a problem hiding this comment.
Why is this a MUST? What if the server has some calls that need session data and others that don't? It seems like if a call really doesn't happen to interact with session data in any way, the server shouldn't be required to fail that call just because the session is bad.
There was a problem hiding this comment.
The Server could choose a policy that any session id is acceptable... the idea here was to fail early if invalid sessionids were supplied.
There was a problem hiding this comment.
The wording here, "unknown sessions", implies (at least to me) that the session ID is not one that was assigned by the server. If the session was assigned by the server, it wouldn't be unknown. So this requirement would therefore mean that a server cannot actually choose a policy of accepting any arbitrary session ID, even if the call doesn't actually require a session.
I'm okay with this either way, but we should be clear about exactly which stance we're taking here.
|
|
||
| ### Use of a single `state` value rather than KV store. | ||
|
|
||
| A single opaque "state" value mirrors the MRTR design, reduces the chance of KV merge errors, and keeps client behaviour simple (simply echo bytes back). |
There was a problem hiding this comment.
I think there are actually a lot more use-cases here than there are for MRTR request state.
In MRTR, the request state is used solely in the ephemeral workflow and solely for passing data between retries of the same logical request. This means that there probably won't be too many different values to retain, and all values are going to be closely related (i.e., they will all be implemented by the same tool call implementation). In that case, I think it's feasible to represent everything in a single string.
In contrast, for session state, I can imagine a larger variety of use-cases that might co-exist in the same session. Some examples:
- Horizontal functionality implemented in a shared library to do things like authentication or user preferences.
- Application-specific state such as a tool call that unlocks other tool calls in the tool call list.
- Transactional state updated iteratively across multiple tool calls and then committed via another tool call.
All of those things are likely to be implemented by different pieces of code in the MCP server, and those different pieces of code may not be aware of each other, which would make it a bit more challenging to store data for all of them in a single string.
To be clear, I am not necessarily opposed to using a single string here, and I do recognize that it makes the protocol simpler. But I do want to make sure that we're considering the differences between session state and MRTR request state here and making an informed decision, rather than just blindly copying what we're proposing for MRTR. Let's be confident that this will actually be sufficient before we go forward.
There was a problem hiding this comment.
I did think about this a bit, and came to the potentially controversial conclusion that:
- Exposing a KV store is setting higher expectations than intended of what this should be used for, and invites potential misuse of storage (e.g. sensitive data).
- We should set an upper size bound for this data (2/4kb), again reinforcing that it is a lightweight convenience mechanism for simple applications that want to attach simple state to the client that gets transmitted with every request.
- To avoid complexity of message sequencing etc. we want to make sure that complex requirements are handled by the MCP Server author at the Server when necessary.
There was a problem hiding this comment.
I think restricting the size may cause problems for some use-cases, and it's not clear to me that there's any benefit in doing so. If someone has a use-case that needs a slightly larger amount of data, it seems unnecessarily restrictive to say that we're imposing an arbitary limit that prevents that case from working. I don't believe there's any size limit in HTTP cookies, as an example. (There's probably a header size limit in HTTP that limits the size of a single cookie, but I don't believe there's a limit to the total number of cookies that can be set, so there's no limit to the total amount of data.)
Stepping back a bit, I think we really need to write down a set of specific use-cases that we and do not intend to address via session state, so that we can evaluate any specific proposal against those use cases. In the absence of these use-cases to use as a rubric, it's hard to evaluate whether or not any specific proposal is the right thing.
mikekistler
left a comment
There was a problem hiding this comment.
Looks good. I left some comments mainly on details to clarify or that might make good discussion topics in the workgroup meeting.
|
|
||
| ### User Interaction Model | ||
|
|
||
| Sessions are designed to be **application-driven**, with host applications determining how and when to establish sessions based on their need. |
There was a problem hiding this comment.
Not just how and when to establish, but also what subsequent requests are bound to the session.
|
|
||
| Sessions are designed to be **application-driven**, with host applications determining how and when to establish sessions based on their need. | ||
|
|
||
| It is normally expected that applications will establish one session per conversation thread or task, but this is not required. |
There was a problem hiding this comment.
Does "task" refer to MCP Tasks or the general notion of "task"? Might want to clarify.
|
|
||
| The Client **MUST NOT** send `io.modelcontextprotocol/session` with the sessions/create request. | ||
|
|
||
| The Client **MUST** securely associate retained sessions with the issuing Server. The Client will typically establish identity through a mixture of _connection target_ and _user identity_. |
There was a problem hiding this comment.
At least the "illusion" of the issuing Server, which could be a cluster of server instances behind a load balancer.
|
|
||
| #### Receiving Notifications | ||
|
|
||
| Clients subscribe to server-initiated notifications using `messages/listen`. This opens a persistent delivery channel (SSE stream over HTTP/logical subscription over STDIO). |
There was a problem hiding this comment.
I cringe when I see that we are baking in a dependency on SSE here. I think we should at least offer an alternative that does not rely on SSE. E.g., allow messages/listen to return an array of notifications and complete. Then the client would issue a new messages/listen to receive subsequent notifications.
|
|
||
| #### Receiving Notifications | ||
|
|
||
| Clients subscribe to server-initiated notifications using `messages/listen`. This opens a persistent delivery channel (SSE stream over HTTP/logical subscription over STDIO). |
There was a problem hiding this comment.
Does this mean that the existing notification channels are deprecated? Maybe that is part of SEP-1442? (Sorry my memory is fuzzy ... if that's the case it would be helpful to state that here)
| ``` | ||
|
|
||
|
|
||
| Global listeners receive **only** broadcast notifications that are not scoped to any session — for example, `notifications/tools/list_changed`. Session-scoped notifications such as resource updates are **never** delivered on a global listener. |
There was a problem hiding this comment.
This wording suggests that tools/list_changed notifications are always outside a session and resource updated notifications are always within a session. I don't think that's true, based on my understanding of the proposal, so this is probably just a wording thing, but would be good to clarify.
|
|
||
| ##### STDIO Transport Behaviour | ||
|
|
||
| For STDIO, `messages/listen` acts as a capabilities check. The Client sends the request; the Server responds with `notifications/messages/listen`. The Server **MAY** then send notifications for the declared scope at any time for the duration of the STDIO connection, as it does today. |
There was a problem hiding this comment.
I think this needs elaboration. Does this mean that NO server-to-client notifications flow in STDIO until a messages/listen is sent? If so, that is not "as it is today".
Also, what "capability" is being checked here? The server already declares if it will send tools/list_changed or resouce/updated notifications in its capabilities, which I assume are available from the "discover" request. Is this just "double checking"? Or is it saying something different?
| _The following notes on sessionId are taken from the existing Streamable HTTP Transport guidance._ | ||
|
|
||
| **sessionId:** | ||
| 1. The `sessionId` **SHOULD** be globally unique and cryptographically secure (e.g., a securely generated UUID, a JWT, or a cryptographic hash). |
There was a problem hiding this comment.
Given that the client must send both the sessionId and sessionData in requests, I don't think it's strictly necessary for sessionIds to be cryptographically secure. The server could easily store a signature for the sessionId in the encrypted sessionData and verify it on subsequent requests.
|
|
||
| ### SEP-2243 HTTP Standardization | ||
|
|
||
| When _meta["io.modelcontextprotocol/session"] is present and using the StreamableHttp transport, the sessionId should be included as `Mcp-Session-Id` in the HTTP Headers. |
There was a problem hiding this comment.
This again may be something already in SEP-1442, but are transport-level sessions being eliminated? Would be good to state that here.
|
|
||
| `messages/listen` is the **delivery channel**; `resources/subscribe` is the **subscription mechanism**. | ||
|
|
||
| Resource subscriptions **MAY** be associated with a session. If the `resources/subscribe` request includes session metadata, the resulting notifications/resources/updated notifications are delivered on that session's listen stream. If no session is specified, notifications are delivered on a global listener. |
There was a problem hiding this comment.
Didn't we say in the summit that we were going to change subscriptions to be self-contained long-lived requests? i.e. the listen would contain the subscriptions, and you'd update subscriptions by re-issuing the listen.
Not sure if we've moved away from that or not.
|
|
||
| Because MCP clients may execute multiple requests concurrently (e.g., parallel tool calling), there are no inherent ordering guarantees for request/response cycles. This creates a Last-Write-Wins race condition if the server relies entirely on the client-echoed state token to manage highly mutable data. Additionally Client state saving error conditions are not known to the Server. | ||
|
|
||
| To resolve this, servers dealing with concurrent mutations SHOULD NOT rely on the state token. Instead, the server SHOULD use the sessionId as a lookup key for a server-side state management mechanism (e.g., a database, cache, or in-memory store). |
There was a problem hiding this comment.
Hmm, if there's mutable state in state and we're using LWW then it seems this would be rarely useful? We can't rule out clients doing concurrent tool calls, so servers need to deal with that, and it seems you can't really do that in general, so you are forced into adopting LWW semantics, which seems to be of little utility.
What % of stateful servers do we think would want to use state and can handle concurrent mutating tool calls? Thinking maybe it would be better to just start with sessionId and maybe introduce this later as an optimization if we find a lot of servers would benefit from it?
My sense is that either (a) barely anyone would use this, or (b) people would use it and be prone to race conditions if clients start doing concurrent calls.
|
|
||
| ##### STDIO Transport Behaviour | ||
|
|
||
| For STDIO, `messages/listen` acts as a capabilities check. The Client sends the request; the Server responds with `notifications/messages/listen`. The Server **MAY** then send notifications for the declared scope at any time for the duration of the STDIO connection, as it does today. |
There was a problem hiding this comment.
How will the STDIO client know which session a notification pertains to? Is the sessionId sent in the notification?
| It is normally expected that applications will establish one session per conversation thread or task, but this is not required. | ||
|
|
||
| MCP Servers may wish to offer capabilities in a mixture of authentication and session modalities. | ||
|
|
There was a problem hiding this comment.
We should really define what sessions are. From the rest of the proposal, it seems like these things are associated with a session:
- Resource subscriptions
- Arbitrary application state
Is the list of tools/resources etc. scoped to a session? (are they allowed to be?)
I'm still somewhat unsure (or at least unconvinced) that we even need sessions...
|
|
||
| ## Abstract | ||
|
|
||
| This proposal introduces application level sessions within the MCP Data Layer. Sessions are created by the Client, and allow the Server to store an opaque state token. |
There was a problem hiding this comment.
Minor: the wording "allow the Server to store an opaque state token." is a bit unclear here. Do you mean "and allow the Server to send to the Client an opaque state token that will be sent back for each relevant request"?
Very early draft, will demo and tidy-up for feedback/development.
Motivation and Context
How Has This Been Tested?
Breaking Changes
Types of changes
Checklist
Additional context