@@ -16,8 +16,9 @@ use super::session::{
1616use crate :: {
1717 RoleServer ,
1818 model:: {
19- ClientJsonRpcMessage , ClientNotification , ClientRequest , ErrorData , GetExtensions ,
20- InitializeRequest , InitializedNotification , JsonRpcError , ProtocolVersion , RequestId ,
19+ ClientCapabilities , ClientJsonRpcMessage , ClientNotification , ClientRequest , ErrorData ,
20+ GetExtensions , Implementation , InitializeRequest , InitializeRequestParams ,
21+ InitializedNotification , JsonRpcError , ProtocolVersion , RequestId ,
2122 } ,
2223 serve_server,
2324 service:: serve_directly,
@@ -1239,10 +1240,17 @@ where
12391240 . map_err ( internal_error_response ( "get service" ) ) ?;
12401241 match message {
12411242 ClientJsonRpcMessage :: Request ( mut request) => {
1243+ // Build a peer_info so context.protocol_version() works inside handlers.
1244+ // serve_directly skips the handshake and receives None by default, making
1245+ // protocol_version() always return None in stateless mode. We reconstruct it:
1246+ // - initialize requests: version comes from the request body params
1247+ // - all other requests: version comes from the MCP-Protocol-Version header
1248+ // (already validated above; absent header defaults to 2025-03-26)
1249+ let peer_info = Self :: peer_info_for_stateless_request ( & request, & part. headers ) ;
12421250 request. request . extensions_mut ( ) . insert ( part) ;
12431251 let ( transport, mut receiver) =
12441252 OneshotTransport :: < RoleServer > :: new ( ClientJsonRpcMessage :: Request ( request) ) ;
1245- let service = serve_directly ( service, transport, None ) ;
1253+ let service = serve_directly ( service, transport, peer_info ) ;
12461254 tokio:: spawn ( async move {
12471255 // on service created
12481256 let _ = service. waiting ( ) . await ;
@@ -1331,4 +1339,34 @@ where
13311339 }
13321340 Ok ( accepted_response ( ) )
13331341 }
1342+
1343+ /// Build a `ClientInfo` (peer_info) for a stateless request so that
1344+ /// `context.protocol_version()` returns the correct value inside handlers.
1345+ ///
1346+ /// `serve_directly` skips the MCP handshake and accepts `peer_info = None`,
1347+ /// which means `context.protocol_version()` is always `None` in stateless mode.
1348+ /// We reconstruct the protocol version from the available signal per request type:
1349+ /// - initialize: version is in the request body params (authoritative)
1350+ /// - all other requests: version is in the MCP-Protocol-Version header
1351+ /// (validated before this point; absent header defaults to 2025-03-26)
1352+ fn peer_info_for_stateless_request (
1353+ request : & crate :: model:: JsonRpcRequest < ClientRequest > ,
1354+ headers : & HeaderMap ,
1355+ ) -> Option < InitializeRequestParams > {
1356+ let version = if let ClientRequest :: InitializeRequest ( ref init) = request. request {
1357+ init. params . protocol_version . clone ( )
1358+ } else {
1359+ headers
1360+ . get ( HEADER_MCP_PROTOCOL_VERSION )
1361+ . and_then ( |v| v. to_str ( ) . ok ( ) )
1362+ . and_then ( |s| serde_json:: from_value ( serde_json:: Value :: String ( s. to_owned ( ) ) ) . ok ( ) )
1363+ . unwrap_or ( ProtocolVersion :: V_2025_03_26 )
1364+ } ;
1365+ Some ( InitializeRequestParams {
1366+ meta : None ,
1367+ protocol_version : version,
1368+ capabilities : ClientCapabilities :: default ( ) ,
1369+ client_info : Implementation :: default ( ) ,
1370+ } )
1371+ }
13341372}
0 commit comments