1- using  Microsoft . AspNetCore . DataProtection ; 
2- using  Microsoft . AspNetCore . Http ; 
1+ using  Microsoft . AspNetCore . Http ; 
32using  Microsoft . AspNetCore . Http . Features ; 
43using  Microsoft . AspNetCore . WebUtilities ; 
54using  Microsoft . Extensions . Logging ; 
65using  Microsoft . Extensions . Options ; 
76using  Microsoft . Net . Http . Headers ; 
8- using  ModelContextProtocol . AspNetCore . Stateless ; 
97using  ModelContextProtocol . Protocol ; 
108using  ModelContextProtocol . Server ; 
119using  System . IO . Pipelines ; 
1210using  System . Security . Claims ; 
1311using  System . Security . Cryptography ; 
14- using  System . Text . Json ; 
1512using  System . Text . Json . Serialization . Metadata ; 
1613
1714namespace  ModelContextProtocol . AspNetCore ; 
@@ -21,7 +18,6 @@ internal sealed class StreamableHttpHandler(
2118    IOptionsFactory < McpServerOptions >  mcpServerOptionsFactory , 
2219    IOptions < HttpServerTransportOptions >  httpServerTransportOptions , 
2320    StatefulSessionManager  sessionManager , 
24-     IDataProtectionProvider  dataProtection , 
2521    ILoggerFactory  loggerFactory , 
2622    IServiceProvider  applicationServices ) 
2723{ 
@@ -30,8 +26,6 @@ internal sealed class StreamableHttpHandler(
3026
3127    public  HttpServerTransportOptions  HttpServerTransportOptions  =>  httpServerTransportOptions . Value ; 
3228
33-     private  IDataProtector  Protector  {  get ;  }  =  dataProtection . CreateProtector ( "Microsoft.AspNetCore.StreamableHttpHandler.StatelessSessionId" ) ; 
34- 
3529    public  async  Task  HandlePostRequestAsync ( HttpContext  context ) 
3630    { 
3731        // The Streamable HTTP spec mandates the client MUST accept both application/json and text/event-stream. 
@@ -118,17 +112,6 @@ public async Task HandleDeleteRequestAsync(HttpContext context)
118112            await  WriteJsonRpcErrorAsync ( context ,  "Bad Request: Mcp-Session-Id header is required" ,  StatusCodes . Status400BadRequest ) ; 
119113            return  null ; 
120114        } 
121-         else  if  ( HttpServerTransportOptions . Stateless ) 
122-         { 
123-             var  sessionJson  =  Protector . Unprotect ( sessionId ) ; 
124-             var  statelessSessionId  =  JsonSerializer . Deserialize ( sessionJson ,  StatelessSessionIdJsonContext . Default . StatelessSessionId ) ; 
125-             var  transport  =  new  StreamableHttpServerTransport 
126-             { 
127-                 Stateless  =  true , 
128-                 SessionId  =  sessionId , 
129-             } ; 
130-             session  =  await  CreateSessionAsync ( context ,  transport ,  sessionId ,  statelessSessionId ) ; 
131-         } 
132115        else  if  ( ! sessionManager . TryGetValue ( sessionId ,  out  session ) ) 
133116        { 
134117            // -32001 isn't part of the MCP standard, but this is what the typescript-sdk currently does. 
@@ -160,6 +143,13 @@ await WriteJsonRpcErrorAsync(context,
160143        { 
161144            return  await  StartNewSessionAsync ( context ) ; 
162145        } 
146+         else  if  ( HttpServerTransportOptions . Stateless ) 
147+         { 
148+             // In stateless mode, we should not be getting existing sessions via sessionId 
149+             // This path should not be reached in stateless mode 
150+             await  WriteJsonRpcErrorAsync ( context ,  "Bad Request: The Mcp-Session-Id header is not supported in stateless mode" ,  StatusCodes . Status400BadRequest ) ; 
151+             return  null ; 
152+         } 
163153        else 
164154        { 
165155            return  await  GetSessionAsync ( context ,  sessionId ) ; 
@@ -183,14 +173,12 @@ private async ValueTask<StreamableHttpSession> StartNewSessionAsync(HttpContext
183173        } 
184174        else 
185175        { 
186-             // "(uninitialized stateless id)" is not written anywhere. We delay writing the MCP-Session-Id 
187-             // until after we receive the initialize request with the client info we need to serialize. 
188-             sessionId  =  "(uninitialized stateless id)" ; 
176+             // In stateless mode, each request is independent. Don't set any session ID on the transport. 
177+             sessionId  =  "" ; 
189178            transport  =  new ( ) 
190179            { 
191180                Stateless  =  true , 
192181            } ; 
193-             ScheduleStatelessSessionIdWrite ( context ,  transport ) ; 
194182        } 
195183
196184        return  await  CreateSessionAsync ( context ,  transport ,  sessionId ) ; 
@@ -199,21 +187,19 @@ private async ValueTask<StreamableHttpSession> StartNewSessionAsync(HttpContext
199187    private  async  ValueTask < StreamableHttpSession >  CreateSessionAsync ( 
200188        HttpContext  context , 
201189        StreamableHttpServerTransport  transport , 
202-         string  sessionId , 
203-         StatelessSessionId ?  statelessId  =  null ) 
190+         string  sessionId ) 
204191    { 
205192        var  mcpServerServices  =  applicationServices ; 
206193        var  mcpServerOptions  =  mcpServerOptionsSnapshot . Value ; 
207-         if  ( statelessId   is  not  null  ||  HttpServerTransportOptions . ConfigureSessionOptions  is  not null ) 
194+         if  ( HttpServerTransportOptions . Stateless  ||  HttpServerTransportOptions . ConfigureSessionOptions  is  not null ) 
208195        { 
209196            mcpServerOptions  =  mcpServerOptionsFactory . Create ( Options . DefaultName ) ; 
210197
211-             if  ( statelessId   is  not  null ) 
198+             if  ( HttpServerTransportOptions . Stateless ) 
212199            { 
213200                // The session does not outlive the request in stateless mode. 
214201                mcpServerServices  =  context . RequestServices ; 
215202                mcpServerOptions . ScopeRequests  =  false ; 
216-                 mcpServerOptions . KnownClientInfo  =  statelessId . ClientInfo ; 
217203            } 
218204
219205            if  ( HttpServerTransportOptions . ConfigureSessionOptions  is  {  }  configureSessionOptions ) 
@@ -225,7 +211,7 @@ private async ValueTask<StreamableHttpSession> CreateSessionAsync(
225211        var  server  =  McpServerFactory . Create ( transport ,  mcpServerOptions ,  loggerFactory ,  mcpServerServices ) ; 
226212        context . Features . Set ( server ) ; 
227213
228-         var  userIdClaim  =  statelessId ? . UserIdClaim   ??   GetUserIdClaim ( context . User ) ; 
214+         var  userIdClaim  =  GetUserIdClaim ( context . User ) ; 
229215        var  session  =  new  StreamableHttpSession ( sessionId ,  transport ,  server ,  userIdClaim ,  sessionManager ) ; 
230216
231217        var  runSessionAsync  =  HttpServerTransportOptions . RunSessionHandler  ??  RunSessionAsync ; 
@@ -264,23 +250,6 @@ internal static string MakeNewSessionId()
264250        return  WebEncoders . Base64UrlEncode ( buffer ) ; 
265251    } 
266252
267-     private  void  ScheduleStatelessSessionIdWrite ( HttpContext  context ,  StreamableHttpServerTransport  transport ) 
268-     { 
269-         transport . OnInitRequestReceived  =  initRequestParams => 
270-         { 
271-             var  statelessId  =  new  StatelessSessionId 
272-             { 
273-                 ClientInfo  =  initRequestParams ? . ClientInfo , 
274-                 UserIdClaim  =  GetUserIdClaim ( context . User ) , 
275-             } ; 
276- 
277-             var  sessionJson  =  JsonSerializer . Serialize ( statelessId ,  StatelessSessionIdJsonContext . Default . StatelessSessionId ) ; 
278-             transport . SessionId  =  Protector . Protect ( sessionJson ) ; 
279-             context . Response . Headers [ McpSessionIdHeaderName ]  =  transport . SessionId ; 
280-             return  ValueTask . CompletedTask ; 
281-         } ; 
282-     } 
283- 
284253    internal  static Task  RunSessionAsync ( HttpContext  httpContext ,  IMcpServer  session ,  CancellationToken  requestAborted ) 
285254        =>  session . RunAsync ( requestAborted ) ; 
286255
0 commit comments