Skip to content

SEP-1372: Clarifying Semantics of Behavior Without Initialization#1372

Closed
ZachGerman wants to merge 12 commits into
modelcontextprotocol:mainfrom
ZachGerman:skippable-initialization
Closed

SEP-1372: Clarifying Semantics of Behavior Without Initialization#1372
ZachGerman wants to merge 12 commits into
modelcontextprotocol:mainfrom
ZachGerman:skippable-initialization

Conversation

@ZachGerman
Copy link
Copy Markdown
Contributor

@ZachGerman ZachGerman commented Aug 21, 2025

Preamble

Abstract

This SEP proposes a limited clarification to the MCP specification to describe the behavior of connections that do not begin with an initialization phase. It acknowledges that such connections are already supported by some SDKs and backends, while clarifying what guarantees are lost or retained when the initialization step is omitted.

Motivation

Several MCP SDKs and servers allow clients to send requests without performing the initialization handshake. While this practice is commonly followed for what is becoming known as "stateless MCP servers", this proposal is limited to the bypassing of the initialization lifecycle phase currently defined in the specification.

This flexibility is useful, but the current specification treats initialization as strictly required.

The goal of this SEP is to clarify:

  • What it means to bypass initialization
  • What assumptions SDKs and servers can or cannot make in such cases

Side note: This also allows for bypassing initialization when server details (supported protocol versions and capabilities) were already obtained through a registry.

Specification

Please see Files changed

Rationale

  • Leaves full initialization as the normative baseline while accounting for established variance

Backward Compatibility

  • No breaking changes for any conforming implementation
  • Clients and servers relying on full initialization remain unaffected

Reference Implementation

  • C# SDK: Supports sending a request without initialization
  • Python SDK's "FastMCP": Allows for requests without initialization

Security Implications

  • Servers should treat behavior conducted without initialization with caution; they lack capabilities negotiation and client identity

@ZachGerman ZachGerman requested a review from a team August 21, 2025 03:11
@LucaButBoring
Copy link
Copy Markdown
Contributor

Is this behavior SDKs allow today? Would be interested in seeing at least one or two code references.

@ZachGerman
Copy link
Copy Markdown
Contributor Author

Is this behavior SDKs allow today? Would be interested in seeing at least one or two code references.

The clearest example is from the Python SDK's FastMCP server using stateless_http=True [ref]
I also added the reference to that code to my description as well! Appreciate the feedback!

@ZachGerman ZachGerman marked this pull request as draft August 22, 2025 17:46
@halter73
Copy link
Copy Markdown
Contributor

halter73 commented Sep 3, 2025

I'm in favor of this change. Although maybe we should go from MUST to SHOULD instead of MUST to MAY. The C# SDK currently does not enforce this behavior on the server either. However, the C# client will always send an initialization request to start a session. I think the Go SDK is similar, but @findleyr could answer for sure.

I can see how it might be convenient in some cases to skip this message on the client, and it's definitely convenient for the server to not have to enforce it. With a "stateless" or at least a sessionless server, it's impossible for the server to enforce this requirement.

We could enforce initialization in the C# SDK's Stateless mode as implemented todasy, because we currently still respond with an Mcp-Session-Id header. The Mcp-Session-Id produced by the C# SDK in Stateless mode is just an encrypted JSON payload containing client info received during initialization. However, we plan on getting rid of the Mcp-Session-Id header in Stateless mode in the near future (see modelcontextprotocol/csharp-sdk#682) so developers don't need to worry about sharing private data protection keys between server instances. And once we do that, enforcing initialization on the server really will be impossible.

If we're afraid that dropping the initialization requirement for the client will encourage people to write client code that prevents stateful servers from learning potentially valuable information about the client during initialization, we could just clarify that the server does not need to enforce the initialization requirement but still insist the client MUST start server interaction with initialization. This is basically just codifying the state of the world today.

But like I said, I'm in favor of just changing the MUST to SHOULD. The MCP-Protocol-Version request header should at least be enough for the server to reject any protocol versions it's not familiar with, although it would prevent gracefully downgrading unless we added a similar response header.

@findleyr
Copy link
Copy Markdown
Contributor

findleyr commented Sep 3, 2025

@halter73 yes, the Go SDK behaves similarly: in stateless mode no lifecycle validation is performed (because it would defeat the purpose of stateless mode), but the client still always sends initialize.

If we're going to change the wording from MUST to MAY or SHOULD, I think we also need to specify what the server assumes about the client in the case that no initialize params can be associated with the request. Probably this should be no capabilities and no client info.

But what do we do about protocol version? There is clear discussion regarding protocol version for the streamable http transport, but if we're making this change to the lifecycle section of the spec, we also need to say how protocol version needs to be handled by stdio or custom transports. One option would be to say it defaults to 2025-XX-XX (the current or next draft), because that's the draft where we'd lift this restriction. But then what do we do about future drafts? Should it always default to the "latest draft"? That's introduces all sorts of problems when clients think they're speaking 2025-10-XX and server thinks otherwise.

I don't think we should change this wording until we figure out what to do about protocol version semantics. On the other hand, I think we could codify the current stateless behavior by making it an addendum to the streamable transport section, with an asterisk in the lifecycle section saying something like "unless the transport supports implicit initialization".

@ZachGerman
Copy link
Copy Markdown
Contributor Author

This is somewhat halted from being developed further into a proper SEP until we see where #1364 lands, as it will affect the original proposal here as well as multiple points brought up by @halter73 & @findleyr above.

@mikekistler
Copy link
Copy Markdown
Contributor

I think we should be prepared to make this change in the event that #1364 (or #1442, which also plans to eliminate the initialization phase) does not land in time for the next version of the spec (which may be soon?).

In that spirit, I want to +1 earlier feedback that we should change "MUST" to "SHOULD" rather than "MAY".

And I think a section should be added to explan the expectations for sessions that do not begin with an initialization phase. I suggest something like:

When a session does not start with an Initialization phase:

  • Clients must have out-of-band knowledge of:

    • the supported protocol version of the server, or (for streamable HTTP) change protocol version "on the fly"
    • the server capabilities (or be prepared to handle error response from {tools, resources, prompts}/list, logging/setLevel, resources/subscribe requests)
    • the server info, or not care about this
    • the server instructions, or not care about this
  • Servers:

    • may reject a request with an explicit protocol version they don't support
    • must assume client capabilities and be prepared to handle errors
    • must be able to operate without client info

@findleyr
Copy link
Copy Markdown
Contributor

findleyr commented Sep 17, 2025

Agreed, both #1364 and #1442 seem rather large (and lacking consensus), so we should proceed with this effort..

I think we also need to clarify when requests are allowed, and update the streamable transport.

How about something like the following wording changes to lifecycle (EDIT: I realized that I used 'MAY' here rather than 'SHOULD', but that decision is orthogonal to the rest of the wording).

1.1 Initialization

Clients MAY execute an initialization phase before other interactions with the server. To perform initialization,
the client sends an initialize request to the server. This MUST be the first interaction between client and
server.

...etc etc

The server MUST respond with its own capabilities and information.

...etc etc

After successful initialization, the client MUST send an initialized notification to indicate it is ready to begin normal
operations.

When the client executes an initialization phase, it must be completed before performing other MCP interactions:

  • The client SHOULD NOT send requests other than pings before the server has responded to the initialize request.
  • The server SHOULD NOT send requests other than pings and logging before receiving the initialized notification.

Alternatively, the client MAY interact with the server without an explicit initialization phase. In this case, client capabilities
and protocol version are determined in the transport layer.

...and then add a section to the streamable transport:

2.7.1 Stateless support

Clients that do not execute an initialization phase must send an MCP-Protocol-Version header corresponding to the
latest version of the protocol that the client supports.

Servers that do not support this protocol version MUST respond with 400 Bad Request, and set the
MCP-Protocol-Version response header to the protocol version that the server wants to use.

Clients may then retry their request with the server's indicated protocol version.

@mikekistler
Copy link
Copy Markdown
Contributor

In this case, client capabilities and protocol version are determined in the transport layer.

I don't think this is true. There are no mechanisms in either current transport to "determine" client capabilities.

And I still think we need some statement that clients and servers must either have out-of-band knowledge of each other's capabilities and other metadata or make assumptions and be prepared to handle errors in those assumptions.

@findleyr
Copy link
Copy Markdown
Contributor

I don't think this is true. There are no mechanisms in either current transport to "determine" client capabilities.

I disagree, but only on semantics. I phrased it this way because there must be some convention for what the client capabilities are in stateless mode. Of course, the convention must be that there ARE no capabilities, but that's still a determination.

I don't think "Make assumptions and be prepared to handle errors" is viable, because we'd also need to specify what those errors are, and how they should be handled. Since they will be HTTP errors, and (AFAICT) http headers are the way to transmit additional structured information with the error, this has to be left to the transport.

@mikekistler
Copy link
Copy Markdown
Contributor

This is an excellent discussion! There is so much to unpack here.

I think the point of this SEP is to acknowledge that some/most/all? current SDKs do not enforce the requirement that connections start with an initialization phase, and that changing the SDKs to do so would be breaking, so we should instead weaken the language from MUST to SHOULD.

In that spirit, I think the SEP should not be imposing new restrictions. When you say

Of course, the convention must be that there ARE no capabilities, but that's still a determination.

are you saying that you believe this is how all SDKs work today? I'm not sure that that is true. In particular, I know that a server written with the C# SDK can send an elicitation or sampling request the client even if the client has not declared these capabilities. And if the client supports elicitation or sampling, it sends a successful response.

Are you proposing that this be disallowed if there is no initialization phase in the connection?

Also, if the server must determine that the client has no capabilities, would the same be true for clients and server capabilities? Meaning, without an initialization phase, must the client assume that the server has no capabilites? No tools, no prompts, no resources? I think that would be problematic and unlikely to jive with current practice.

@findleyr
Copy link
Copy Markdown
Contributor

This is an excellent discussion! There is so much to unpack here.

Agreed! And I suspect that as SDK maintainers we've been thinking about these questions in our silos. It's really interesting to compare notes.

In that spirit, I think the SEP should not be imposing new restrictions.

The intent is not to impose restrictions on the ways SDKs currently behave in practice, but I think if we're going to change MUST to SHOULD or MAY, we need to also clarify the consequences when initialization doesn't occur. I agree that most SDKs have a 'stateless' mode that disables initialization validation, but the way stateless mode is supposed to behave is not at all clear around the edges. I think most of the use cases are "just let me call a tool".

In fact, I think changing MUST to SHOULD or MAY is dangerous, because I think for most SDKs stateless mode is explicitly enabled on the server. This change to MAY means that all servers must allow uninitialized interactions, and there's no way for the server to reject the request to say "no, you really need to initialize".

I know that a server written with the C# SDK can send an elicitation or sampling request the client even if the client has not declared these capabilities

Isn't that an error? Shouldn't we reject this request before even sending it to the client? (I thought we might already be doing this in the Go SDK, but we're not, so I suppose we're in the same boat as the C# SDK).

Furthermore, if we're primarily talking about stateless streamable servers here, how would that client request ever get back to the server. If the client hasn't performed initialization, it doesn't have a session ID, and therefore has no way to address the session with its response to the elicitation.

Are you proposing that this be disallowed if there is no initialization phase in the connection?

Also, if the server must determine that the client has no capabilities, would the same be true for clients and server capabilities? Meaning, without an initialization phase, must the client assume that the server has no capabilites?

I guess I was, but I'm OK with backing off wording. My assumption is that client capabilities only work if there's a stateful MCP session, and in that case we must have initialization. You're right to call out the asymmetry of this assertion.

So, in summary, I'm OK with changing MUST to MAY or should but note:

  • I believe this will cause a change in behavior for most SDKs (they'll have to allow stateless sessions by default).
  • We should be explicit about what happens in the error case, when a protocol version or capability is unsupported. If we're not going to handle negotiation at the transport level, that's fine by me (it already feels wrong that MCP-Protocol-Version is part of the streamable transport). However, we then need to have a formal way to handle this at the protocol level. Perhaps encoding information in jsonrpc error data is an option (https://www.jsonrpc.org/specification#error_object).

@mikekistler
Copy link
Copy Markdown
Contributor

This is an excellent discussion! There is so much to unpack here.
Agreed! And I suspect that as SDK maintainers we've been thinking about these questions in our silos.

I hope we can encourage other SDK maintainers to join this discussion!

I agree that most SDKs have a 'stateless' mode that disables initialization validation, but the way stateless mode is supposed to behave is not at all clear around the edges.

I don't think connections without an initialization phase are the same as "stateless". I agree that they do not have the state that would be obtained from the initialization handshake -- capabilities, clent/sever info, server instructions -- but they may still have other state like tools / resources / prompts lists, notification configurations, etc.

I think of connections without an initialization phase as "move fast and break things" connections. "Capabilities? We don't need no stinkin capabilities!".

Again, based on the actual behavior of the C# SDK, a request (using streamable HTTP) sent on a connection that does not have an initialization phase currently gets a response with a sessionId. And subsequent requests that bear this sessionId are "part of the session" -- i.e. "logicially related" in the language of the spec.

In short, I think some/most? SDKs support connections without an initialization phase and may also support stateful (as ill-defined as this may be in the spec) connections without an initialization phase. So we should endeavor in this SEP to clarify what this means.

@ZachGerman
Copy link
Copy Markdown
Contributor Author

ZachGerman commented Sep 18, 2025

Coming back to this as Mike pointed out those other SEPs aren't nearing consensus, so we should be prepared for interim spec guidance regarding this. Will try to draft something today in complete SEP format with consideration for the discussion thus far.

@ZachGerman ZachGerman changed the title Adjust initialization lifecycle wording for stateless cases SEP-1372: Clarifying Semantics of Initialization-Free Connections Sep 19, 2025
@ZachGerman ZachGerman changed the title SEP-1372: Clarifying Semantics of Initialization-Free Connections SEP-1372: Clarifying Semantics of Connections Without Initialization Sep 19, 2025
@ZachGerman ZachGerman changed the title SEP-1372: Clarifying Semantics of Connections Without Initialization SEP-1372: Clarifying Semantics of Behavior Without Initialization Sep 19, 2025
@ZachGerman
Copy link
Copy Markdown
Contributor Author

ZachGerman commented Sep 19, 2025

I believe I've accounted for both Python's FastMCP and the C# SDK variances with the changes now described in the SEP. There's definitely still room for discussion.

I believe the updated wording will cleanly accommodate the currently agreed-upon aspects of the related initialization/session SEPs that allow for pre-discovered server support expectations.

@mikekistler What do you think of the changes to Initialization? Do you think it's sufficient as-is? Should we take away the ", and initialization bypass support" and define specific error details for when bypassing initialization is not allowed by a server?

Somewhat working under the assumption that the popular cases this accommodates are ones where clients are either:

  • Strictly used in accordance to the server details because they're developed by same entity as the server
  • Getting server details from a registry.

Maybe just changing "MUST" to "SHOULD" is the way to go after all, but I wanted to try and clarify the reason for why initialization might be skipped.

@mikekistler
Copy link
Copy Markdown
Contributor

@ZachGerman

I believe I've accounted for both Python's FastMCP and the #C SDK variances with the changes now described in the SEP.

Sorry ... where is that exactly? I still only see one line changed in this PR.

@ZachGerman ZachGerman force-pushed the skippable-initialization branch from 1524f92 to ef09329 Compare September 21, 2025 18:43
@ZachGerman
Copy link
Copy Markdown
Contributor Author

@mikekistler it was just in the SEP description above. I've now updated the PR to reflect the same.

Comment thread docs/specification/draft/basic/transports.mdx
@ZachGerman ZachGerman force-pushed the skippable-initialization branch from ef09329 to d54a81c Compare September 24, 2025 03:02
Copy link
Copy Markdown
Contributor

@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! 👍

@mikekistler
Copy link
Copy Markdown
Contributor

@modelcontextprotocol/core-maintainers could we get one or two reviews on this? Or can we schedule this for a vote in the next core maintainers meeting? Or what is the next step?

@ZachGerman
Copy link
Copy Markdown
Contributor Author

Looks good! 👍

Should I mark you as the sponsor @mikekistler ?

@ZachGerman ZachGerman marked this pull request as ready for review September 25, 2025 19:04
securely generated UUID, a JWT, or a cryptographic hash).
- The session ID **MUST** only contain visible ASCII characters (ranging from 0x21 to
0x7E).
2. If an `Mcp-Session-Id` is returned by the server during initialization, clients using
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.

Is it confusing that this still references initialization? If we don't edit this line, what is the meaning of the Mcp-Session-Id value returned for non-initialization requests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The difference is utilizing or not utilizing negotiation.

Initialization-phase-provided Session-Id:

  • Negotiated protocol version
  • Negotiated capabilities
  • Logical grouping of requests

Non-Initialization-phase-provided Session-Id:

  • Logical grouping of requests (which may utilize various protocol versions or capabilities)

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.

I think there is general consensus that we should make initialization optional, since most/maybe all the official SDKs do not actually require it.

But I think we'd be overstepping if we tried to make sessions exclusive to connections that include an initialization phase. I don't know how the other SDKs work, but the C# SDK will offer a sessionId in the response of any request that did not include one. The client is free to ignore this or use it to connect that requests with others it may send.

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.

Oh I absolutely agree that we shouldn't make sessions exclusive to connections that include an initialization phase. I just think we should say a bit more, because unless I missed it the new wording doesn't explicitly say how to treat non-initialization session IDs. This section says

If an Mcp-Session-Id is, returned by the server during initialization, clients using the Streamable HTTP transport MUST include it in the Mcp-Session-Id header on all of their subsequent HTTP requests meant for that session.

It says nothing about Mcp-Session-Ids that are provided by non-initialization requests. It seems like we should say something, or adjust the wording here to cover all session IDs, not just those provided during initialization.

We could either drop "during initialization" here, or add something like "For session IDs originating from non-initialization requests, clients may choose to include them on subsequent requests if they want to take advantage of session-oriented features."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think dropping "during initialization" is reasonable, as the practice of including them in requests meant for that session applies in both cases.

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.

@mikekistler BTW, the Go SDK used to always offer a session ID, and upon a closer reading of the spec we intentionally changed it to only offer session IDs during initialization. But if this is accepted, I will happily change it back.

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.

the C# SDK will offer a sessionId in the response of any request that did not include one

A small clarification to make here is that this is the default behavior for the C# SDK, but not how it behaves in the opt-in "stateless" mode. The point still stands that we want to be able support sessions even when initialization gets skipped though.

In stateless mode, the C# SDK currently only responds with a session ID during initialization because it uses parts of the InitializeRequest to generate the ID which is really an encrypted JSON payload containing things like the client name and version. But after I merge modelcontextprotocol/csharp-sdk#760, stateless mode won't generate a session ID at all.

to requests containing that session ID with HTTP 404 Not Found.
4. 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`
`Mcp-Session-Id` obtained through an [initialization phase](/specification/draft/basic/lifecycle),
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.

Ditto here: what is the client meant to do with session IDs obtained from a non-initialization request?

It seems like we're just accommodating servers that happen to return session IDs for all requests, but why do we need to do that? If the only session IDs that matter are those assigned during initialization, do we even need to mention the others?

Copy link
Copy Markdown
Contributor Author

@ZachGerman ZachGerman Sep 26, 2025

Choose a reason for hiding this comment

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

What is the client meant to do with session IDs obtained from a non-initialization requests?

Use them or discard them based on whether they want to group them with subsequent requests and utilize the benefits of sessions. Sessions can still be useful without initialization when doing things such as establishing GET/Listening streams, utilizing resumability with SSE, etc.

Big emphasis on SSE last-event-id, which is unique per session-id. If my MCP client serves multiple application clients downstream, it may be useful for me to conduct each of my sub-clients' requests within a dedicated session-id for each one to allow for replaying SSE events on a per-sub-client basis (not having to resume/replay all and filter down to a specific sub-client's events).

An application that utilizes an MCP client to serve its own "sub-clients" is a great reason why initialization sometimes only needs to happen once (to get server's supported versions/capabilities), then create & manage multiple sessions without repeating the initialization process unnecessarily.

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.

Thanks. This all makes sense, and this is overall a very good change to the spec. I just think we should say something about session IDs obtained through non-initialization requests. The wording here makes sense though: I'll comment on the other thread.

@mikekistler
Copy link
Copy Markdown
Contributor

Regarding:

What is the actual problem it's solving?

The purpose of this SEP is simply to clarify the current state of the world, which is that most/maybe all the official MCP SDKs do not (on the server side) require a connection to start with an initialize request. The SEP started as a simple one-word change, from "MUST" to "SHOULD", but then we discovered this has ramifications in other parts of the spec so more changes wre needed. I think your comment above is pointing out one more that we missed previously.

But this also has value because it removes one roadblock to allowing MCP to operating as a stateless protocol. In a stateless protocol, every request is independent and stands alone, so requiring an Initialize request before any other request is fundamentally at odds with that.

Comment on lines +193 to +194
it **MUST** stop using that `Mcp-Sesssion-Id` and **SHOULD** start a new session by
sending a new `InitializeRequest` without a session ID attached.
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.

@ZachGerman @simonrussell what do you think of this revision?

Suggested change
it **MUST** stop using that `Mcp-Sesssion-Id` and **SHOULD** start a new session by
sending a new `InitializeRequest` without a session ID attached.
it **MUST** stop using that `Mcp-Sesssion-Id` and **SHOULD** start a new session for
interactions that are logicially related.

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.

I'm not sure that's quite right because the client can't start a new session, it can only stop using a previous session. I think it needs to be more along the lines of "sending a new request without a session ID attached"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

+1 on client's not really "starting" sessions, but "utilizing" them when an ID is offered by the server, which is being done either through initialization or by sending a request without a session ID attached. Maybe something like:

   it **MUST** stop using that `Mcp-Sesssion-Id` and **SHOULD** utilize a new session for
   subsequent interactions meant to be logicially related.

@kurtisvg
Copy link
Copy Markdown
Contributor

kurtisvg commented Oct 9, 2025

I am (fairly strongly) against this proposal being accepted.

Process and Principle: Spec First, Not SDK First

My first objection is procedural. The core premise of a shared protocol like MCP is that the specification is the definitive source of truth. This principle ensures that a client written in any language can interoperate with any server, so long as both adhere to the spec.

This SEP proposes we codify behavior that already exists in some SDKs, using that pre-existence as its main justification. This "SDK-first" approach fundamentally undermines the protocol's integrity. It risks making the SDKs the de facto standard, not the specification. We should evaluate proposed changes based on their independent technical merit and benefit to the entire ecosystem, not simply because a feature has been implemented somewhere in isolation.

Insufficient Justification and Evidence of Use

The proposal does not make a compelling case that this behavior is frequently or consistently used in practice. If we are to accept the argument that "it exists in some SDKs today," the proposal should be specific about which SDKs and how popular those implementations are.

For example, I see that FastMCP has a stateless_http mode, but it doesn't seem to be mentioned in the official docs or examples. Is this feature actually used frequently by the community, or is it an obscure, unsupported mode? Its mere existence isn't a strong argument for a protocol-level change.

Discrepancy Between Proposed Spec and Existing Implementations

There also appears to be a mismatch between the behavior described in existing SDKs and the proposed spec change. Specifically, a comment from findleyr above mentions:

in stateless mode no lifecycle validation is performed (because it would defeat the purpose of stateless mode), but the client still always sends initialize.

This is fundamentally different from what the spec proposes, which is that initialization "SHOULD be the first interaction between a client and a server" implying it "MAY be skipped." These are two distinct behaviors: one is "initialize but ignore the result," and the other is "do not initialize at all." This inconsistency makes it unclear what behavior we are actually trying to standardize.

Technical Arguments Against Stateless Sessions

Beyond the process issues, I believe the proposed behavior is a poor choice for the protocol for several reasons:

  1. It introduces explicit stateless vs. stateful modes. Justin's comment on the original Streamable HTTP PR says this is an undesirable outcome:

We've concluded that we do not want to bifurcate the protocol into "stateless" and "stateful" variants, as that might as well be two separate projects at that point. We feel good about this broad approach being the correct one to pursue for now, modulo detailed inline comments above that I'll address.

  1. It multiples the compatibility matrix problem: Core contributors often cite the complexity of the "N clients x M transports" compatibility matrix as a reason to rule out new or alternatives transports. This proposal introduces a new behavioral mode (stateless vs. stateful), effectively multiplying compatibility concerns. A developer can no longer just write their application logic once and expect it to work with any MCP server. They now have to write code that handles both stateful and stateless modes, which breaks the promise that application code can work with any MCP server. This is arguably a worse complication than adding a new transport, because at least with new transports a user's application code can remain consistent.

  2. It further confuses the meaning of a "session." This SEP attempts to redefine what a session means but ultimately compounds the confusion. In issue Clarifications on the intent/use-case for sessions #984, we asked the question, "Should sessions be for transport stuff or logical stuff?" It was clear from the discussions with users that they rely on sessions for both. This SEP contradicts that by stating, "Bypassing initialization is appropriate when the client has prior knowledge of, and accounts for, the protocol versions, capabilities, and initialization bypass support of all servers it interacts with." This implies that sessions only provide transport-layer guarantees, ignoring established use cases where sessions carry critical logical, application-level context.

@evalstate
Copy link
Copy Markdown
Member

I initially took a look at the "single word change" to the spec thinking it would be a simple improvement, and enable "initialize free" MCP Server Usage. In practice as I read the spec, it clearly had a large ripple effect so I backed away... and I'd like to acknowledge @ZachGerman and others for the large effort of working through this one and it's details - it's a helpful exercise.

re: SDKs - for example, the TypeScript SDK leaves 404 handling to the MCP Server Developer, and my guess is that a lot of MCP Server developers use the GUID generator and don't write the code to check session validity. Another example, the Python client SDK does not open the GET endpoint if an Mcp-Session-Id is not supplied, the TypeScript client SDK does .

I agree with the proposal to go full "stateless" - where that means that every MCP interchange has what would have been in the initialization attached to it, and Session Management is explicit (will add the PR#s momentarily).

@findleyr
Copy link
Copy Markdown
Contributor

findleyr commented Oct 9, 2025

Thanks @kurtisvg

You make some very valid points, and I think it is right to consider whether we want to commit to this behavior when there are more changes to sessions on the horizon. I'll have to think about your points more, but for now have a few quick comments based on my experience, that may inform discussion:

Regarding SDK support: I've seen this supported (inconsistently) across at least Python, Typescript, CSharp, and Go (the latter being my doing). For example, the typescript SDK enables stateless mode only if the sessionID is undefined. Part of the motivation for this SEP was (I think) to standardize what stateless mode actually means.

Regarding evidence of use: I don't actually know how prevalent usage is, but I will say that I heard about this pretty quickly and loudly from users during the development of the Go SDK (for example in modelcontextprotocol/go-sdk#148 or in face-to-face discussion). The main use case is having a "distributable" server, which isn't possible with session ID validation, absent some very complicated intra-service communication. Additionally, users have indicated that they still want a session ID as the key to their logical application state.

Regarding discrepancy between proposed spec and existing implementations: you pointed out that I noted the Go SDK behaves differently than C#. I think this is in true, in at least two ways:

  • The Go client always performs initialization, even if the server is stateless.
  • The Go SDK only returns session IDs during initialization, as prescribed by the (current) spec.

In both of these cases, we'd change the behavior of the Go SDK as a result of this SEP: we'd offer a client option to bypass initialization, and we'd return session IDs on every HTTP request.

@kurtisvg
Copy link
Copy Markdown
Contributor

kurtisvg commented Oct 9, 2025

The Go client always performs initialization, even if the server is stateless.
The Go SDK only returns session IDs during initialization, as prescribed by the (current) spec.

I think part of my confusion here is I'm now noticing the PR description doesn't match the PR contents. The current PR description does not say anything about returning session id headers outside of the initialization handshake (but also looks like it hasn't been updated since Aug 20).

Is this "return session-id" header behavior actually implemented in any SDKs right now? If not, it seems like we are even farther from "this is the current state of the world".

@simonrussell
Copy link
Copy Markdown
Contributor

The purpose of this SEP is simply to clarify the current state of the world, which is that most/maybe all the official MCP SDKs do not (on the server side) require a connection to start with an initialize request. The SEP started as a simple one-word change, from "MUST" to "SHOULD", but then we discovered this has ramifications in other parts of the spec so more changes wre needed. I think your comment above is pointing out one more that we missed previously.

That's fine, but for compatibility all clients should assume that servers require it right?

Maybe the only change that needs to be made is actually removing the requirement for the "client initialized" notification, which does end up (if you're being strict about things) requiring that the server refuse any requests until it's received that. I think that could just be dropped from the HTTP transport (it still makes sense for stdio), and replaced with an implicit client initialize assumption if the client makes any future requests. I don't think that notification adds much value and yes does force statefulness if you're going to strictly follow the spec.

(As an aside, it does seem very strange to have official SDKs that don't strictly follow the spec.)

But this also has value because it removes one roadblock to allowing MCP to operating as a stateless protocol. In a stateless protocol, every request is independent and stands alone, so requiring an Initialize request before any other request is fundamentally at odds with that.

I think there is value in some of those other proposals to unbundle session creation, protocol negotation and server capabilities, but having an "initialize" call on its own (sans the notification mentioned above) doesn't force statefulness on the server.

@ZachGerman
Copy link
Copy Markdown
Contributor Author

ZachGerman commented Oct 10, 2025

@kurtisvg Just want to preface this with the fact that I don't see this as any sort of replacement for #1442, which I do hope is adopted ASAP. The original plan for this SEP was to simply change:

"The initialization phase MUST be the first interaction between client and server."
to
"The initialization phase MAY be the first interaction between client and server."

...and it has always been my intention to simply clarify in the spec, for users of official SDKs, that the initialization phase isn't always the first interaction between client and server. Regardless of whether this SEP is adopted, I think it has already helped us prepare for writing updated specification documents if/when #1442 is accepted by highlighting some of the specific impacts that not requiring initialization will have on the existing spec. I think it will prove to have been a "helpful exercise", as @evalstate put it.

This SEP proposes we codify behavior that already exists in some SDKs, using that pre-existence as its main justification. This "SDK-first" approach fundamentally undermines the protocol's integrity. It risks making the SDKs the de facto standard, not the specification.

In my experience participating in SEP discussions for MCP, the most important question to everyone seems to me to be "why is this not achievable under the current specification"? The reason that we have good maintainers for SDKs who have permitted implementations that bypass initialization in non-compliance with the spec is because there's demand for functionality that is not achievable under the current specification. I don't believe that recognizing this with minimal change to the spec is "undermining the protocol's integrity", but bolstering it, as integrity is about being undivided, and having a spec that accounts for use-cases demanded by the community building the ecosystem in which it lives brings more integrity.

Insufficient Justification and Evidence of Use

Existence in the SDKs by our good maintainers is already more "evidence of use" than the vast majority of accepted SEPs have provided. In fact, I can't recall another SEP that provided any "evidence of use" whatsoever (beyond example implementations required by SEP format). I could've added anecdotes regarding the chatter I see around FastMCP and how popular discussion surrounding it is in internal AWS discussions, but I don't think we want to start trying to use anecdotes as leverage for SEPs. I think you, of all people, know how important it is to developers to allow for stateless behavior with the spec, which inherently includes the ability to bypass the initialization phase as it's defined today. That's really all this SEP is advocating for: a single facet of your multi-faceted SEP (#1442) that already exists in SDKs due to demand for functionality we're all aware of.

@simonrussell

(As an aside, it does seem very strange to have official SDKs that don't strictly follow the spec.)

It does, doesn't it? I suppose it's a consequence of a protocol being in a bleeding-edge space of tech with incredible amounts of money being thrown at development around it.


Again, I'd personally rather see #1442 adopted so we don't have to worry about reconciling the use-cases that have popped up which this SEP addresses specifically, because #1442 addresses them as well as other important issues. I've been looking at this SEP as a temporary band-aid or stepping stone to #1442.

@ZachGerman
Copy link
Copy Markdown
Contributor Author

ZachGerman commented Oct 10, 2025

Is this "return session-id" header behavior actually implemented in any SDKs right now? If not, it seems like we are even farther from "this is the current state of the world".

If you're referring to the implied functionality of the spec change to:

1. A server using the Streamable HTTP transport **MAY** assign a session ID by including
   it in an `Mcp-Session-Id` header on any HTTP response."

then yes, this is what's practiced in the C# SDK.

@simonrussell
Copy link
Copy Markdown
Contributor

If you're referring to the implied functionality of the spec change to:

1. A server using the Streamable HTTP transport **MAY** assign a session ID by including
   it in an `Mcp-Session-Id` header on any HTTP response."

then yes, this is what's practiced in the C# SDK.

Is that the SDK client-side accepting a fresh Mcp-Session-Id on any response and keeping track of it, or the SDK server-side sending a new Mcp-Session-Id on non-initialize responses? If it's the latter, are there any compatibility problems with common clients?

To answer this myself I had a dig around in the C# SDK code. I think the answer is yes, it will set a session ID on any request where there isn't a session ID, but given that clients will be sending the initialize request first, it probably works out fine. It seems like it would be a minor change to make it only issue new session IDs on initialize calls, but obviously I'm new to the codebase so I am probably missing something. It also has a stateless mode which doesn't support session IDs or anything that needs a client capability, so I assume that's not what we're talking about.

So while yes it's true that the current C# SDK does set a fresh session ID on any response where the request didn't come in with one, this actually follows the spec behaviour as long as the client is following the spec and only ever doing this on initialize calls. I couldn't really figure out what it does if no initialize call happens, I'm going to guess it assumes the client has no capabilities.

I'd totally understand if the current SDKs didn't enforce "don't allow any request until the client initialize notification is received" as that's pretty annoying to implement (and I took the same shortcut in the very light SDK I built). It seems a little more necessary for stdio servers, but also it feels like it could probably be removed and very little would break.

Without that requirement, there isn't really an "initialization phase" that the server needs state to support. (There is a need to keep the client capabilities around, if they're important to the server; just removing the initialize call doesn't really solve this, it just makes the client capabilities unknown. Hence the other SEP.)

@findleyr
Copy link
Copy Markdown
Contributor

(As an aside, it does seem very strange to have official SDKs that don't strictly follow the spec.)

I can only speak authoritatively about the Go SDK, but I believe it does strictly follow the spec: clients always follow the initialization lifecycle, and this is enforced by servers unless they are explicitly configured as stateless (which I don't believe violates the spec -- see below).

My impression is that there are a large number of users who just want to serve a stateless tool from a scalable service, and the spec makes that hard or impossible if enforced strictly from the server SDK, so there has been a convention across SDKs to support this "just let me serve a tool" use-case. Being a relative latecomer, there was a lot of prior art and expectation when we considered this for the Go SDK.

Allowing stateless servers hardly diverges from the current spec, in that clients still must follow the initialization flow, but servers don't (and can't) enforce it. The spec says "The client SHOULD NOT send requests other than pings before the server has responded to the initialize request.", which doesn't say anything about whether the server has to reject such requests.

But the other aspect of this SEP is being able to avoid the unnecessary cost of the additional 'initialize' and 'notifications/initialized' messages, if the client knows a priori that the server is stateless. As I said, this is not currently possible in the Go SDK, but we'd make it possible as a result of this proposed change.

Allowing clients to bypass initialization is a much bigger change, because it exposes the 'statelessness' bit to the spec, and now there must be systems in place for clients to track whether a server is stateless. This diverges from the presumed goal of the spec to define a protocol where clients and servers can communicate without any prior knowledge of each other.

From discussions with other maintainers, I understood that the cost of initialization is a real concern, and the wording proposed here "Bypassing initialization is appropriate when the client has prior knowledge" seemed sufficiently specific such as to allow certain use cases without affecting the default behavior. With that said, perhaps we should be more cautious about hinting at such an under-specified use case.

My initial thought was that we should just codify 'stateless' mode for server by adding a new section of the spec describing how it should work. This would not require any change to the rest of the spec, but also wouldn't address the use-case of bypassing initialization entirely. Maybe we should do that, or maybe we should do nothing at all, and allow "bypassing initialization" to be handle by another, more comprehensive change to the spec.

@kurtisvg
Copy link
Copy Markdown
Contributor

I can only speak authoritatively about the Go SDK, but I believe it does strictly follow the spec: clients always follow the initialization lifecycle, and this is enforced by servers unless they are explicitly configured as stateless (which I don't believe violates the spec -- see below).

The spec also says if the server responds with a session_id it MUST send that in every request after that. So if the Go client is making the initialization request but ignoring if it returns a session_id, then it's not compliant with the spec.

This is also why stateless mode doesn't make sense to me. It keeps being said the server needs to decide if it's stateless or not (be returning a session_id), but then we say we need an option for the client to be stateless anyway. So which is it?

@findleyr
Copy link
Copy Markdown
Contributor

findleyr commented Oct 10, 2025

The spec also says if the server responds with a session_id it MUST send that in every request after that. So if the Go client is making the initialization request but ignoring if it returns a session_id, then it's not compliant with the spec.

But the Go client DOES use the session_id on subsequent requests. Go SDK clients are stateful, but as a result of this SEP we'd expose an option to bypass initialization, which I'm assuming is what you mean by stateless client. This is what I meant when I said "As I said, this is not currently possible in the Go SDK, but we'd make it possible as a result of this proposed change".

This is also why stateless mode doesn't make sense to me. It keeps being said the server needs to decide if it's stateless or not (be returning a session_id), but then we say we need an option for the client to be stateless anyway. So which is it?

The server being stateless or not is orthogonalnot entirely the same as whether it returns a session ID. All 'stateless' means is that there is no validation of the initialization lifecycle, and whether the result of that lifecycle (client capabilities) are available to the server. Stateless servers can still return session ids, and those session ids can be available to tools as a possible key into application state, because they MUST be provided by the client request.

but then we say we need an option for the client to be stateless anyway. So which is it?

I'm confused by your confusion :), because I thought this was exactly the distinction I was trying to call out in my previous comment.

In summary:

  • I think there's a difference between stateless servers and stateless clients, which is what (I thought) I was pointing out in my previous comment!
  • From what I've heard from users, stateless server support is the most important thing, because it allows for distributable services, but from discussions with other SDK maintainers there is also a demand for bypassing initialization entirely (=stateless clients).
  • Stateless servers are arguably not even a violation of the current spec.
  • Allowing stateless clients could have significant emergent complexity, but I thought this was limited by the wording of this SEP, and thought this was accepted as very important.

If it's not agreed upon that stateless clients are important, or that this is an acceptable way to enable them, then I would be fine doing nothing, or perhaps modifying this SEP to simply clarify the behavior of stateless servers.

@findleyr
Copy link
Copy Markdown
Contributor

findleyr commented Oct 10, 2025

After a discussion with @kurtisvg, I agree with him that we should probably retract this SEP, but let me be explicit as to why because I’m coming at this from a relatively narrow perspective as an SDK maintainer. I apologize for what turned into a long winded response: TL;DR: the current stateless mode is problematic, and we shouldn't be further codifying or enabling it.

Background

Stateless servers: I first encountered statelessness when talking to users that “just need to host a simple tool”, and the current HTTP transports make this difficult/impossible if implemented strictly, due to the initialization handshake. It seems clear that the spec originally assumed one client process speaking to one server process (correct me if I'm wrong). After all how is a server supposed to use a client capability if the server process is not addressable to receive the response? But this was not clear.

With the addition of the MCP-Protocol-Version header, the 2025-06-18 version of the spec seemed to confirm that there is meant to be an implicit stateless mode where we ignore client capabilities and initialization. I commented my confusion here, but we went on to add such a stateless mode in the Go SDK, initially implemented by a GetSessionID callback (if empty, enable stateless mode). Later, we separated the concept of statelessness from session id1, which may have been a mistake.

As I posted above, I don’t think stateless servers are actually in conflict with the current spec, it’s just problematic that there are elements of the spec that clearly exist to enable stateless mode without spelling out how exactly stateless mode works.

Stateless clients: However, I’ve also recently heard from other maintainers that when they know the server is stateless, they want to be able to skip the costly and pointless initialization, when they have prior knowledge of the server’s capabilities. I think that this SEP is really about enabling stateless clients, by relaxing conditions to allow skipping initialization.

It seemed harmless to allow stateless clients when caveated with “when you have prior knowledge”, and probably does solve some real problems, but Kurtis convinced me otherwise.

Rationale for not adopting this SEP

The reason I think we should abandon this SEP is that it doesn’t define stateless mode or sessions, or address any of the problems with stateless mode. Furthermore, it introduces yet another implicit mode of operation (this time for clients), which may have unintended consequences and make it harder to actually solve stateless serving in the future.

Problems with stateless mode

Stateless servers are second-class citizens, and don’t have access to client capabilities. Therefore, server authors must have prior knowledge of their transport, which is (I believe) failing one of the design principles of MCP.
Stateless servers cannot present a coherent view of capabilities or features. If I am a client interacting with a stateless server, I can't know whether the capabilities the server advertised (or the features it listed) are still valid. Again, this means the client must have prior knowledge of it’s server’s transport, and cannot rely on its initialization handshake, which again appears to violate a design principle of MCP.

Fundamentally, MCP defines client and server capabilities that enable rich interactions between AI host and server, and in some sense stateless mode abandons those capabilities, as well as the integrity of its data model, to solve a transport and hosting problem.

Problems with skipping initialization

Simply put, saying that clients can skip initialization turns an SDK problem into a spec problem (again, problems are going in the wrong direction!). If we codify that clients can skip initialization, we may close off some opportunities to fix stateless mode, and encourage use of MCP as a glorified RPC protocol.

Alternatives

I think we should consider other SEPs2 that try to define a stateless model for MCP in such a way that initialization or affinity is not necessary, and yet servers and clients can still have access to the full set of features offered by MCP. Such a SEP will necessarily have significant implications beyond the transport layer, because it will formalize the actual MCP server as stateless, which will have consequences for SDKs and users. However, if we accept that there is a need for stateless serving, it is better to map MCP capabilities onto the stateless model, than to sacrifice current and future MCP capabilities in an undefined and undiscoverable way.

It also seems fine to adopt a bidirectional streaming transport technology (such as websockets) that has established hosting solutions. However, I don't know enough about this domain to say if this is a viable path forward.

When I first learned about MCP, it reminded me of LSP, and that made sense to me, but it is morphing into something different. This transition is probably natural and inevitable, and it is better to address it explicitly. Thinking about SDK design, maybe there’s a way that a stateful server is just a special case of a stateless server, rather than the other way around 3.

Footnotes

  1. some users were using session IDs as a key to their application state, and requested that we separate the concept of stateless mode from session IDs: we’d still return session IDs but just wouldn’t enforce initialization. This struck me even then as a layering violation, but we justified it by saying that the session id was a property of the session, and it just happens to be used by the streamable transport.

  2. Maybe Kurtis’s SEP-1442: Make MCP Stateless (by default) #1442, though I have not read it thoroughly.

  3. In other words, there could be a specialized API extension where servers can assume that tool calls happen in the same process, but SDKs would still be stateless.

@simonrussell
Copy link
Copy Markdown
Contributor

The server being stateless or not is orthogonalnot entirely the same as whether it returns a session ID. All 'stateless' means is that there is no validation of the initialization lifecycle, and whether the result of that lifecycle (client capabilities) are available to the server. Stateless servers can still return session ids, and those session ids can be available to tools as a possible key into application state, because they MUST be provided by the client request.

Just wanted to underline this one because it's very true -- session IDs only imply state if that's how you choose to implement them. You could choose to encode some client attributes from the initialize request in the session ID and use that in later requests, without having any storage requirements on the server.

(I have suggested a few times that morphing session IDs into something closer to a cookie would be useful for this situation)

* **From what I've heard from users, stateless server support is the most important thing, because it allows for distributable services**, but from discussions with other SDK maintainers there is also a demand for bypassing initialization entirely (=stateless clients).

I'm really curious what the use-case for stateless clients is, or where the cost for the initialization call comes from.

* **Stateless servers are arguably not even a violation of the current spec**.

They technically kind of are because of this line: "The server SHOULD NOT send requests other than pings and logging before receiving the initialized notification." But I think that could be relaxed or removed for the HTTP transport, or potentially completely. (And it is a should so...)

But basically that would be it. If that was removed, then stateless servers are in every way completely fine, and very useful.

@ZachGerman
Copy link
Copy Markdown
Contributor Author

ZachGerman commented Oct 13, 2025

@kurtisvg I wanted to address the three "technical argument" points you initially brought up, as I spent the majority of my last reply regarding a subjective disagreement on undermining integrity and "evidence of use" standards:

  1. This is addressed below in my response to @findleyr

  2. I disagree with this. We've seen in this SEP that the impact of bypassing initialization. It's extremely limited in impact, only affecting session management, and even then it only truly affects session ID distribution and server-side awareness of capabilities. As the author of one of the Java SHTTP transport providers, I happen to know that this wouldn't be difficult to support for the Java SDK. It'd be a matter of the server creating a session for incoming requests that have none. I also believe servers shouldn't need to know client capabilities, and should just declare the required capabilities for the resources they advertise (e.g. tools), allowing the client to determine whether it can and wants to meet the requirements. This allows clients to play their cards closer to their chest, enabling them to safeguard against things like sampling abuse or roots abuse by servers. After all: why should I tell you I support roots if I don't want your server calling roots/list unless I see it as a requirement by your server for an interaction I desire? This is a significant security benefit for clients reaching out to third-party servers discovered on a registry.
    Tl;DR: It wouldn't multiply complexity, or even increase it very much. @mikekistler did it?

  3. The changes from this SEP on today's concept of an MCP session:

    • A session might only contain a single request/response.
    • A session doesn't always have capabilities context. However, in the case where it doesn't: the client is already aware of the server's capabilities, and doesn't want to share theirs with the server, so that's not a bad thing.
    • A session can begin with any request currently not associated to a session.

    ...so IMO, "attempts to redefine what a session means" is an exaggeration.


@findleyr I think your comment was very well stated, and points out the result (regardless of the intent) of this SEP, which is indeed enabling stateless clients (without really calling that out in the description, which I will change).

Stateless servers are second-class citizens

Interestingly enough, I see a similar comment [ref] for stateful servers in the discussion of #1442. This makes me question the intent Justin had in the comment referenced by @kurtisvg here when he used the words "...for now". Maybe we can get @jspahrsummers's thoughts on the matter. There seems to be desire for both stateful and stateless modes to be "first-class". Is it true that such a bifurcation would really mean that it "...might as well be two separate projects at that point"? I'm not convinced that the impact reaches far beyond session management changes outlined by this SEP.

Stateless servers cannot present a coherent view of capabilities or features

WRT clients being unaware if server capabilities it used to know are still valid... This is not an issue and I'll detail why:

  • Stale server-side capabilities can happen on "stateful" servers all the same. They MAY delete the sessions they no longer support and 404. They MAY just result in errors when clients try to use those deprecated capabilities. All of this applies to the single-request, client-capability-free ("stateless") sessions enabled by this SEP in the same way.
  • Also, FWIW they can still send an initialize request with the current version and get a response to validate its expectations without sending a subsequent initialized notification.

Fundamentally, MCP defines client and server capabilities that enable rich interactions between AI host and server, and in some sense stateless mode abandons those capabilities, as well as the integrity of its data model, to solve a transport and hosting problem.

Allowing clients to choose whether they want to disclose their capabilities is not tantamount to "abandoning" them. It's enabling a more secure stance for clients contacting third-party servers.

Simply put, saying that clients can skip initialization turns an SDK problem into a spec problem

Allowing non-disclosure of client capabilities and enabling sessions without unnecessary requests is not an SDK problem. I think it's a great SDK feature, and so do the others who are using it.


Can't say it enough: For server ignorance of client-side capability support to be a total non-issue, they must declare their required capabilities for the resources they advertise so clients can choose whether they can and want to meet them! (#1385)

@findleyr
Copy link
Copy Markdown
Contributor

Thanks for the thoughtful response, @ZachGerman.

Allowing non-disclosure of client capabilities and enabling sessions without unnecessary requests is not an SDK problem. I think it's a great SDK feature, and so do the others who are using it.

I think this is a critical perspective to understand on both sides of the argument, because (1) statelessness enables scalable remote MCP servers which couldn't otherwise exist, yet (2) limit the types of interactions that are possible between client and server.

I was originally in favor of this proposal because of (1), although I perhaps didn't sufficiently appreciate the complexity of enabling stateless clients.

However, I changed my mind after speaking with @kurtisvg, who pointed out that there are lots of scalable use-cases where the server really wants to have access to client capabilities such as elicitation. If we double-down on this current model for statelessness, the world of MCP servers bifurcates into stateful servers, which are more like LSP sessions, and stateless servers, which are more like REST APIs.

@ZachGerman
Copy link
Copy Markdown
Contributor Author

ZachGerman commented Oct 13, 2025

there are lots of scalable use-cases where the server really wants to have access to client capabilities such as elicitation

Again, I think that addressing that should be done by the server declaring that it wants to know that. Whether the client wants to allow it and continue down that road should be decided by the client based on whether that interaction is worth it to them. Server's can always say "Hey, I have this really cool thing I can do, but I need you to support elicitation for it to work", and clients can respond accordingly based on their level of trust for a given server and desire for that functionality, usually by initiating that functionality via a request that invokes it (or not).

@findleyr
Copy link
Copy Markdown
Contributor

there are lots of scalable use-cases where the server really wants to have access to client capabilities such as elicitation

Again, I think that addressing that should be done by the server declaring that it wants to know that. Whether the client wants to allow it and continue down that road should be decided by the client based on whether that interaction is worth it to them. Server's can always say "Hey, I have this really cool thing I can do, but I need you to support elicitation for it to work", and clients can respond accordingly based on their level of trust for a given server and desire for that functionality, usually by initiating that functionality via a request that invokes it (or not).

That all makes sense to me, but let me be sure we're on the same page about something: generally speaking stateless servers distributed across multiple processes can't use elicitation, because there's no way for the elicitation response (another POST message) to be routed to the same server. Or is there an established precedent for sticky routing of responses? (and if so, why do we need statelessness? :) ).

We should not move toward a world where "MCP" means two different things depending on the hosting model. There are multiple paths that avoid such a future--websockets could be another alternative. This is similar to the sentiment expressed in the comment you linked.

My concern with this SEP is that enabling stateless clients encourages this bifurcation into stateful and stateless.

@ZachGerman
Copy link
Copy Markdown
Contributor Author

ZachGerman commented Oct 14, 2025

generally speaking stateless servers distributed across multiple processes can't use elicitation, because there's no way for the elicitation response (another POST message) to be routed to the same server.

There are ways for servers to utilize unique request IDs for their requests and leverage the spec requirement:

  • "Responses MUST include the same ID as the request they correspond to."

...in order to achieve specific routing.

Copy link
Copy Markdown
Contributor

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

We should revert the changes to permit sessionIds be returned from any request other than Initialize. That goes beyond the intent of this SEP to simply make Initialize optional.

Comment thread docs/specification/draft/basic/transports.mdx Outdated
Comment thread docs/specification/draft/basic/transports.mdx Outdated
Comment thread docs/specification/draft/basic/transports.mdx Outdated
without a session ID attached.
4. When a client receives HTTP 404 in response to a request containing an `Mcp-Session-Id`,
it **MUST** stop using that `Mcp-Sesssion-Id` and **SHOULD** start a new session by
sending a new `InitializeRequest` without a session ID attached.
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.

The current spec only allows sessions to be established by InitializeRequest, and we should not expand that in this PR (I'm backing out changes that allowed this).

Co-authored-by: Mike Kistler <mikekistler@microsoft.com>
@ZachGerman ZachGerman requested a review from a team as a code owner October 23, 2025 21:38
ZachGerman and others added 2 commits October 23, 2025 14:39
Co-authored-by: Mike Kistler <mikekistler@microsoft.com>
Co-authored-by: Mike Kistler <mikekistler@microsoft.com>
@ZachGerman
Copy link
Copy Markdown
Contributor Author

Committed those suggestions and revised the description accordingly.

@ZachGerman
Copy link
Copy Markdown
Contributor Author

This is due for closure in favor of #1442
Was nice to dive deeper into this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

draft SEP proposal with a sponsor. in-review SEP proposal ready for review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants