Skip to content

SEP-XXXX: Data-Layer Sessions for MCP#20

Open
evalstate wants to merge 29 commits intomodelcontextprotocol:mainfrom
evalstate:draft/data-layer-sessions
Open

SEP-XXXX: Data-Layer Sessions for MCP#20
evalstate wants to merge 29 commits intomodelcontextprotocol:mainfrom
evalstate:draft/data-layer-sessions

Conversation

@evalstate
Copy link
Member

Very early draft, will demo and tidy-up for feedback/development.

Motivation and Context

How Has This Been Tested?

Breaking Changes

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

Additional context

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.
"name": "notebook_append",
"arguments": { "text": "remember this" },
"_meta": {
"mcp/session": { "id": "sess-a1b2c3d4e5f6" }
Copy link

Choose a reason for hiding this comment

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

If you want an expiry, maybe, the client needs to pass back the original expiry to mitigate clock skew issues?

Copy link
Member Author

Choose a reason for hiding this comment

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

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!

@ggoodman
Copy link

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:

When a client receives HTTP 404 in response to a request containing an MCP-Session-Id, it MUST start a new session by sending a new InitializeRequest without a session ID attached.

Can we consider giving a way for servers to signal the requirement to use sessions?

@evalstate
Copy link
Member Author

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:

When a client receives HTTP 404 in response to a request containing an MCP-Session-Id, it MUST start a new session by sending a new InitializeRequest without a session ID attached.

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 discover and find out that way.

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 discover affirmatively when no SessionMetadata is passed.

Tool annotations are slightly tricker is the tools/list mechanism may itself be gated by a session requirement.

@ggoodman
Copy link

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:

When a client receives HTTP 404 in response to a request containing an MCP-Session-Id, it MUST start a new session by sending a new InitializeRequest without a session ID attached.

Can we consider giving a way for servers to signal the requirement to use sessions?

@evalstate
Copy link
Member Author

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:

When a client receives HTTP 404 in response to a request containing an MCP-Session-Id, it MUST start a new session by sending a new InitializeRequest without a session ID attached.

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 discover and find out that way.

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 discover affirmatively when no SessionMetadata is passed.

Tool annotations are slightly tricker is the tools/list mechanism may itself be gated by a session requirement.

@kurtisvg kurtisvg changed the title Draft/data layer sessions SEP-XXXX: Data-Layer Sessions for MCP Mar 2, 2026
"id": 42,
"error": {
"code": -32043,
"message": "Session not found",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we add a SESSION_REQUIRED error? to indicate that a tool requires a session but doesn't have one

Copy link
Contributor

@markdroth markdroth left a comment

Choose a reason for hiding this comment

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

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"]`:
Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

The Server could choose a policy that any session id is acceptable... the idea here was to fail early if invalid sessionids were supplied.

Copy link
Contributor

Choose a reason for hiding this comment

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

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).
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

I did think about this a bit, and came to the potentially controversial conclusion that:

  1. 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).
  2. 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.
  3. 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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link

@mikekistler mikekistler left a comment

Choose a reason for hiding this comment

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

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.

Choose a reason for hiding this comment

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

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.

Choose a reason for hiding this comment

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

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_.

Choose a reason for hiding this comment

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

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).

Choose a reason for hiding this comment

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

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).

Choose a reason for hiding this comment

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

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.

Choose a reason for hiding this comment

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

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.

Choose a reason for hiding this comment

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

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).

Choose a reason for hiding this comment

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

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.

Choose a reason for hiding this comment

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

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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

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).
Copy link
Collaborator

Choose a reason for hiding this comment

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

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.

Choose a reason for hiding this comment

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

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.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should really define what sessions are. From the rest of the proposal, it seems like these things are associated with a session:

  1. Resource subscriptions
  2. 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.
Copy link

Choose a reason for hiding this comment

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

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"?

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.

7 participants