@@ -776,6 +776,13 @@ impl From<EmptyResult> for () {
776776/// so unknown values are preserved rather than rejected. Servers implementing this
777777/// protocol version MUST include `resultType` in every result. For backward
778778/// compatibility, clients MUST treat an absent field as `"complete"`.
779+ ///
780+ /// Ordinary results model the field as `Option<ResultType>`: `None` means the
781+ /// field is absent on the wire. Constructors default to `Some(COMPLETE)`, and
782+ /// the server handler strips the `"complete"` discriminator before responding
783+ /// to peers that negotiated a protocol version older than `2026-07-28`, so
784+ /// legacy sessions keep their historical wire shape (see
785+ /// [`ServerResult::strip_result_type_for_legacy_peer`]).
779786#[ derive( Debug , Clone , PartialEq , Eq ) ]
780787#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
781788pub struct ResultType ( Cow < ' static , str > ) ;
@@ -1473,14 +1480,22 @@ macro_rules! paginated_result {
14731480 ( $t: ident {
14741481 $i_item: ident: $t_item: ty
14751482 } ) => {
1476- #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq , Default ) ]
1483+ #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq ) ]
14771484 #[ serde( rename_all = "camelCase" ) ]
14781485 #[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
14791486 #[ expect( clippy:: exhaustive_structs, reason = "intentionally exhaustive" ) ]
14801487 pub struct $t {
1481- /// Result type discriminator. Absent values deserialize as `"complete"`.
1482- #[ serde( default ) ]
1483- pub result_type: ResultType ,
1488+ /// Result type discriminator (SEP-2322). `None` means absent on the wire.
1489+ ///
1490+ /// Per the [spec schema]: "Servers implementing this protocol version
1491+ /// MUST include this field. For backward compatibility, when a client
1492+ /// receives a result from a server implementing an earlier protocol
1493+ /// version (which does not include `resultType`), the client MUST treat
1494+ /// the absent field as `"complete"`."
1495+ ///
1496+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
1497+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1498+ pub result_type: Option <ResultType >,
14841499 #[ serde( rename = "_meta" , default , skip_serializing_if = "Option::is_none" ) ]
14851500 pub meta: Option <MetaObject >,
14861501 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
@@ -1502,10 +1517,16 @@ macro_rules! paginated_result {
15021517 pub $i_item: $t_item,
15031518 }
15041519
1520+ impl Default for $t {
1521+ fn default ( ) -> Self {
1522+ Self :: with_all_items( Default :: default ( ) )
1523+ }
1524+ }
1525+
15051526 impl $t {
15061527 pub fn with_all_items( items: $t_item) -> Self {
15071528 Self {
1508- result_type: ResultType :: default ( ) ,
1529+ result_type: Some ( ResultType :: COMPLETE ) ,
15091530 meta: None ,
15101531 next_cursor: None ,
15111532 ttl_ms: None ,
@@ -1621,9 +1642,17 @@ pub type ReadResourceRequestParam = ReadResourceRequestParams;
16211642#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
16221643#[ non_exhaustive]
16231644pub struct ReadResourceResult {
1624- /// Result type discriminator. Absent values deserialize as `"complete"`.
1625- #[ serde( default ) ]
1626- pub result_type : ResultType ,
1645+ /// Result type discriminator (SEP-2322). `None` means absent on the wire.
1646+ ///
1647+ /// Per the [spec schema]: "Servers implementing this protocol version
1648+ /// MUST include this field. For backward compatibility, when a client
1649+ /// receives a result from a server implementing an earlier protocol
1650+ /// version (which does not include `resultType`), the client MUST treat
1651+ /// the absent field as `"complete"`."
1652+ ///
1653+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
1654+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1655+ pub result_type : Option < ResultType > ,
16271656 /// Time, in milliseconds, that this result may be treated as fresh (SEP-2549).
16281657 /// Required by spec version 2026-07-28, but optional here to maintain compatibility
16291658 /// with older spec versions.
@@ -1648,7 +1677,7 @@ impl ReadResourceResult {
16481677 /// Create a new ReadResourceResult with the given contents.
16491678 pub fn new ( contents : Vec < ResourceContents > ) -> Self {
16501679 Self {
1651- result_type : ResultType :: default ( ) ,
1680+ result_type : Some ( ResultType :: COMPLETE ) ,
16521681 ttl_ms : None ,
16531682 cache_scope : None ,
16541683 contents,
@@ -3208,24 +3237,38 @@ impl CompletionInfo {
32083237 }
32093238}
32103239
3211- #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq , Default ) ]
3240+ #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq ) ]
32123241#[ serde( rename_all = "camelCase" ) ]
32133242#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
32143243#[ non_exhaustive]
32153244pub struct CompleteResult {
3216- /// Result type discriminator. Absent values deserialize as `"complete"`.
3217- #[ serde( default ) ]
3218- pub result_type : ResultType ,
3245+ /// Result type discriminator (SEP-2322). `None` means absent on the wire.
3246+ ///
3247+ /// Per the [spec schema]: "Servers implementing this protocol version
3248+ /// MUST include this field. For backward compatibility, when a client
3249+ /// receives a result from a server implementing an earlier protocol
3250+ /// version (which does not include `resultType`), the client MUST treat
3251+ /// the absent field as `"complete"`."
3252+ ///
3253+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
3254+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
3255+ pub result_type : Option < ResultType > ,
32193256 pub completion : CompletionInfo ,
32203257 #[ serde( rename = "_meta" , skip_serializing_if = "Option::is_none" ) ]
32213258 pub meta : Option < MetaObject > ,
32223259}
32233260
3261+ impl Default for CompleteResult {
3262+ fn default ( ) -> Self {
3263+ Self :: new ( CompletionInfo :: default ( ) )
3264+ }
3265+ }
3266+
32243267impl CompleteResult {
32253268 /// Create a new CompleteResult with the given completion info.
32263269 pub fn new ( completion : CompletionInfo ) -> Self {
32273270 Self {
3228- result_type : ResultType :: default ( ) ,
3271+ result_type : Some ( ResultType :: COMPLETE ) ,
32293272 completion,
32303273 meta : None ,
32313274 }
@@ -3674,14 +3717,22 @@ pub type CreateElicitationRequest = ElicitRequest;
36743717///
36753718/// Contains the content returned by the tool execution and an optional
36763719/// flag indicating whether the operation resulted in an error.
3677- #[ derive( Default , Debug , Serialize , Clone , PartialEq ) ]
3720+ #[ derive( Debug , Serialize , Clone , PartialEq ) ]
36783721#[ serde( rename_all = "camelCase" ) ]
36793722#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
36803723#[ non_exhaustive]
36813724pub struct CallToolResult {
3682- /// Result type discriminator. Absent values deserialize as `"complete"`.
3683- #[ serde( default ) ]
3684- pub result_type : ResultType ,
3725+ /// Result type discriminator (SEP-2322). `None` means absent on the wire.
3726+ ///
3727+ /// Per the [spec schema]: "Servers implementing this protocol version
3728+ /// MUST include this field. For backward compatibility, when a client
3729+ /// receives a result from a server implementing an earlier protocol
3730+ /// version (which does not include `resultType`), the client MUST treat
3731+ /// the absent field as `"complete"`."
3732+ ///
3733+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
3734+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
3735+ pub result_type : Option < ResultType > ,
36853736 /// The content returned by the tool (text, images, etc.)
36863737 #[ serde( default ) ]
36873738 pub content : Vec < ContentBlock > ,
@@ -3710,7 +3761,7 @@ impl<'de> Deserialize<'de> for CallToolResult {
37103761 #[ serde( rename_all = "camelCase" ) ]
37113762 struct Helper {
37123763 #[ serde( default ) ]
3713- result_type : ResultType ,
3764+ result_type : Option < ResultType > ,
37143765 content : Option < Vec < ContentBlock > > ,
37153766 structured_content : Option < Value > ,
37163767 is_error : Option < bool > ,
@@ -3741,11 +3792,23 @@ impl<'de> Deserialize<'de> for CallToolResult {
37413792 }
37423793}
37433794
3795+ impl Default for CallToolResult {
3796+ fn default ( ) -> Self {
3797+ CallToolResult {
3798+ result_type : Some ( ResultType :: COMPLETE ) ,
3799+ content : Vec :: new ( ) ,
3800+ structured_content : None ,
3801+ is_error : None ,
3802+ meta : None ,
3803+ }
3804+ }
3805+ }
3806+
37443807impl CallToolResult {
37453808 /// Create a successful tool result with unstructured content
37463809 pub fn success ( content : Vec < ContentBlock > ) -> Self {
37473810 CallToolResult {
3748- result_type : ResultType :: default ( ) ,
3811+ result_type : Some ( ResultType :: COMPLETE ) ,
37493812 content,
37503813 structured_content : None ,
37513814 is_error : Some ( false ) ,
@@ -3803,7 +3866,7 @@ impl CallToolResult {
38033866 /// ```
38043867 pub fn error ( content : Vec < ContentBlock > ) -> Self {
38053868 CallToolResult {
3806- result_type : ResultType :: default ( ) ,
3869+ result_type : Some ( ResultType :: COMPLETE ) ,
38073870 content,
38083871 structured_content : None ,
38093872 is_error : Some ( true ) ,
@@ -3826,7 +3889,7 @@ impl CallToolResult {
38263889 /// ```
38273890 pub fn structured ( value : Value ) -> Self {
38283891 CallToolResult {
3829- result_type : ResultType :: default ( ) ,
3892+ result_type : Some ( ResultType :: COMPLETE ) ,
38303893 content : vec ! [ ContentBlock :: text( value. to_string( ) ) ] ,
38313894 structured_content : Some ( value) ,
38323895 is_error : Some ( false ) ,
@@ -3853,7 +3916,7 @@ impl CallToolResult {
38533916 /// ```
38543917 pub fn structured_error ( value : Value ) -> Self {
38553918 CallToolResult {
3856- result_type : ResultType :: default ( ) ,
3919+ result_type : Some ( ResultType :: COMPLETE ) ,
38573920 content : vec ! [ ContentBlock :: text( value. to_string( ) ) ] ,
38583921 structured_content : Some ( value) ,
38593922 is_error : Some ( true ) ,
@@ -4041,26 +4104,40 @@ impl CreateMessageResult {
40414104 }
40424105}
40434106
4044- #[ derive( Default , Debug , Serialize , Deserialize , Clone , PartialEq ) ]
4107+ #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq ) ]
40454108#[ serde( rename_all = "camelCase" ) ]
40464109#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
40474110#[ non_exhaustive]
40484111pub struct GetPromptResult {
4049- /// Result type discriminator. Absent values deserialize as `"complete"`.
4050- #[ serde( default ) ]
4051- pub result_type : ResultType ,
4112+ /// Result type discriminator (SEP-2322). `None` means absent on the wire.
4113+ ///
4114+ /// Per the [spec schema]: "Servers implementing this protocol version
4115+ /// MUST include this field. For backward compatibility, when a client
4116+ /// receives a result from a server implementing an earlier protocol
4117+ /// version (which does not include `resultType`), the client MUST treat
4118+ /// the absent field as `"complete"`."
4119+ ///
4120+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
4121+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
4122+ pub result_type : Option < ResultType > ,
40524123 #[ serde( skip_serializing_if = "Option::is_none" ) ]
40534124 pub description : Option < String > ,
40544125 pub messages : Vec < PromptMessage > ,
40554126 #[ serde( rename = "_meta" , skip_serializing_if = "Option::is_none" ) ]
40564127 pub meta : Option < MetaObject > ,
40574128}
40584129
4130+ impl Default for GetPromptResult {
4131+ fn default ( ) -> Self {
4132+ Self :: new ( Vec :: new ( ) )
4133+ }
4134+ }
4135+
40594136impl GetPromptResult {
40604137 /// Create a new GetPromptResult with required fields.
40614138 pub fn new ( messages : Vec < PromptMessage > ) -> Self {
40624139 Self {
4063- result_type : ResultType :: default ( ) ,
4140+ result_type : Some ( ResultType :: COMPLETE ) ,
40644141 description : None ,
40654142 messages,
40664143 meta : None ,
@@ -4424,6 +4501,42 @@ impl ServerResult {
44244501 pub fn task_ack ( _: ( ) ) -> ServerResult {
44254502 ServerResult :: TaskAckResult ( TaskAckResult :: new ( ) )
44264503 }
4504+
4505+ /// Strip the SEP-2322 `resultType: "complete"` discriminator so the result
4506+ /// keeps the wire shape that predates protocol version `2026-07-28`.
4507+ ///
4508+ /// The server handler calls this before responding to a peer that
4509+ /// negotiated an older protocol version, where the field did not exist and
4510+ /// strict peers may reject it. Only the `"complete"` value is stripped:
4511+ /// results whose discriminator carries meaning (`"input_required"`,
4512+ /// `"task"`) are already gated to `2026-07-28`+ sessions, and custom
4513+ /// extension values are preserved.
4514+ ///
4515+ /// # Examples
4516+ ///
4517+ /// ```
4518+ /// use rmcp::model::{CallToolResult, ServerResult};
4519+ ///
4520+ /// let mut result = ServerResult::CallToolResult(CallToolResult::success(vec![]));
4521+ /// result.strip_result_type_for_legacy_peer();
4522+ ///
4523+ /// let json = serde_json::to_value(&result).unwrap();
4524+ /// assert!(json.get("resultType").is_none());
4525+ /// ```
4526+ pub fn strip_result_type_for_legacy_peer ( & mut self ) {
4527+ let result_type = match self {
4528+ ServerResult :: CompleteResult ( r) => & mut r. result_type ,
4529+ ServerResult :: GetPromptResult ( r) => & mut r. result_type ,
4530+ ServerResult :: ListPromptsResult ( r) => & mut r. result_type ,
4531+ ServerResult :: ListResourcesResult ( r) => & mut r. result_type ,
4532+ ServerResult :: ListResourceTemplatesResult ( r) => & mut r. result_type ,
4533+ ServerResult :: ReadResourceResult ( r) => & mut r. result_type ,
4534+ ServerResult :: ListToolsResult ( r) => & mut r. result_type ,
4535+ ServerResult :: CallToolResult ( r) => & mut r. result_type ,
4536+ _ => return ,
4537+ } ;
4538+ result_type. take_if ( |result_type| result_type. is_complete ( ) ) ;
4539+ }
44274540}
44284541
44294542pub type ServerJsonRpcMessage = JsonRpcMessage < ServerRequest , ServerResult , ServerNotification > ;
0 commit comments