@@ -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,23 @@ 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). Required by the [spec schema]
1489+ /// for servers implementing protocol version `2026-07-28`, but optional
1490+ /// here because this type also models results from older protocol
1491+ /// versions, which do not carry the field: `None` means absent on the
1492+ /// wire, and per the spec "the client MUST treat the absent field as
1493+ /// `"complete"`". Constructors default to `Some(ResultType::COMPLETE)`;
1494+ /// the server handler clears the field when responding to peers that
1495+ /// negotiated an older version.
1496+ ///
1497+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
1498+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1499+ pub result_type: Option <ResultType >,
14841500 #[ serde( rename = "_meta" , default , skip_serializing_if = "Option::is_none" ) ]
14851501 pub meta: Option <MetaObject >,
14861502 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
@@ -1502,10 +1518,16 @@ macro_rules! paginated_result {
15021518 pub $i_item: $t_item,
15031519 }
15041520
1521+ impl Default for $t {
1522+ fn default ( ) -> Self {
1523+ Self :: with_all_items( Default :: default ( ) )
1524+ }
1525+ }
1526+
15051527 impl $t {
15061528 pub fn with_all_items( items: $t_item) -> Self {
15071529 Self {
1508- result_type: ResultType :: default ( ) ,
1530+ result_type: Some ( ResultType :: COMPLETE ) ,
15091531 meta: None ,
15101532 next_cursor: None ,
15111533 ttl_ms: None ,
@@ -1621,9 +1643,18 @@ pub type ReadResourceRequestParam = ReadResourceRequestParams;
16211643#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
16221644#[ non_exhaustive]
16231645pub struct ReadResourceResult {
1624- /// Result type discriminator. Absent values deserialize as `"complete"`.
1625- #[ serde( default ) ]
1626- pub result_type : ResultType ,
1646+ /// Result type discriminator (SEP-2322). Required by the [spec schema]
1647+ /// for servers implementing protocol version `2026-07-28`, but optional
1648+ /// here because this type also models results from older protocol
1649+ /// versions, which do not carry the field: `None` means absent on the
1650+ /// wire, and per the spec "the client MUST treat the absent field as
1651+ /// `"complete"`". Constructors default to `Some(ResultType::COMPLETE)`;
1652+ /// the server handler clears the field when responding to peers that
1653+ /// negotiated an older version.
1654+ ///
1655+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
1656+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1657+ pub result_type : Option < ResultType > ,
16271658 /// Time, in milliseconds, that this result may be treated as fresh (SEP-2549).
16281659 /// Required by spec version 2026-07-28, but optional here to maintain compatibility
16291660 /// with older spec versions.
@@ -1648,7 +1679,7 @@ impl ReadResourceResult {
16481679 /// Create a new ReadResourceResult with the given contents.
16491680 pub fn new ( contents : Vec < ResourceContents > ) -> Self {
16501681 Self {
1651- result_type : ResultType :: default ( ) ,
1682+ result_type : Some ( ResultType :: COMPLETE ) ,
16521683 ttl_ms : None ,
16531684 cache_scope : None ,
16541685 contents,
@@ -3208,24 +3239,39 @@ impl CompletionInfo {
32083239 }
32093240}
32103241
3211- #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq , Default ) ]
3242+ #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq ) ]
32123243#[ serde( rename_all = "camelCase" ) ]
32133244#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
32143245#[ non_exhaustive]
32153246pub struct CompleteResult {
3216- /// Result type discriminator. Absent values deserialize as `"complete"`.
3217- #[ serde( default ) ]
3218- pub result_type : ResultType ,
3247+ /// Result type discriminator (SEP-2322). Required by the [spec schema]
3248+ /// for servers implementing protocol version `2026-07-28`, but optional
3249+ /// here because this type also models results from older protocol
3250+ /// versions, which do not carry the field: `None` means absent on the
3251+ /// wire, and per the spec "the client MUST treat the absent field as
3252+ /// `"complete"`". Constructors default to `Some(ResultType::COMPLETE)`;
3253+ /// the server handler clears the field when responding to peers that
3254+ /// negotiated an older version.
3255+ ///
3256+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
3257+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
3258+ pub result_type : Option < ResultType > ,
32193259 pub completion : CompletionInfo ,
32203260 #[ serde( rename = "_meta" , skip_serializing_if = "Option::is_none" ) ]
32213261 pub meta : Option < MetaObject > ,
32223262}
32233263
3264+ impl Default for CompleteResult {
3265+ fn default ( ) -> Self {
3266+ Self :: new ( CompletionInfo :: default ( ) )
3267+ }
3268+ }
3269+
32243270impl CompleteResult {
32253271 /// Create a new CompleteResult with the given completion info.
32263272 pub fn new ( completion : CompletionInfo ) -> Self {
32273273 Self {
3228- result_type : ResultType :: default ( ) ,
3274+ result_type : Some ( ResultType :: COMPLETE ) ,
32293275 completion,
32303276 meta : None ,
32313277 }
@@ -3674,14 +3720,23 @@ pub type CreateElicitationRequest = ElicitRequest;
36743720///
36753721/// Contains the content returned by the tool execution and an optional
36763722/// flag indicating whether the operation resulted in an error.
3677- #[ derive( Default , Debug , Serialize , Clone , PartialEq ) ]
3723+ #[ derive( Debug , Serialize , Clone , PartialEq ) ]
36783724#[ serde( rename_all = "camelCase" ) ]
36793725#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
36803726#[ non_exhaustive]
36813727pub struct CallToolResult {
3682- /// Result type discriminator. Absent values deserialize as `"complete"`.
3683- #[ serde( default ) ]
3684- pub result_type : ResultType ,
3728+ /// Result type discriminator (SEP-2322). Required by the [spec schema]
3729+ /// for servers implementing protocol version `2026-07-28`, but optional
3730+ /// here because this type also models results from older protocol
3731+ /// versions, which do not carry the field: `None` means absent on the
3732+ /// wire, and per the spec "the client MUST treat the absent field as
3733+ /// `"complete"`". Constructors default to `Some(ResultType::COMPLETE)`;
3734+ /// the server handler clears the field when responding to peers that
3735+ /// negotiated an older version.
3736+ ///
3737+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
3738+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
3739+ pub result_type : Option < ResultType > ,
36853740 /// The content returned by the tool (text, images, etc.)
36863741 #[ serde( default ) ]
36873742 pub content : Vec < ContentBlock > ,
@@ -3710,7 +3765,7 @@ impl<'de> Deserialize<'de> for CallToolResult {
37103765 #[ serde( rename_all = "camelCase" ) ]
37113766 struct Helper {
37123767 #[ serde( default ) ]
3713- result_type : ResultType ,
3768+ result_type : Option < ResultType > ,
37143769 content : Option < Vec < ContentBlock > > ,
37153770 structured_content : Option < Value > ,
37163771 is_error : Option < bool > ,
@@ -3741,11 +3796,23 @@ impl<'de> Deserialize<'de> for CallToolResult {
37413796 }
37423797}
37433798
3799+ impl Default for CallToolResult {
3800+ fn default ( ) -> Self {
3801+ CallToolResult {
3802+ result_type : Some ( ResultType :: COMPLETE ) ,
3803+ content : Vec :: new ( ) ,
3804+ structured_content : None ,
3805+ is_error : None ,
3806+ meta : None ,
3807+ }
3808+ }
3809+ }
3810+
37443811impl CallToolResult {
37453812 /// Create a successful tool result with unstructured content
37463813 pub fn success ( content : Vec < ContentBlock > ) -> Self {
37473814 CallToolResult {
3748- result_type : ResultType :: default ( ) ,
3815+ result_type : Some ( ResultType :: COMPLETE ) ,
37493816 content,
37503817 structured_content : None ,
37513818 is_error : Some ( false ) ,
@@ -3803,7 +3870,7 @@ impl CallToolResult {
38033870 /// ```
38043871 pub fn error ( content : Vec < ContentBlock > ) -> Self {
38053872 CallToolResult {
3806- result_type : ResultType :: default ( ) ,
3873+ result_type : Some ( ResultType :: COMPLETE ) ,
38073874 content,
38083875 structured_content : None ,
38093876 is_error : Some ( true ) ,
@@ -3826,7 +3893,7 @@ impl CallToolResult {
38263893 /// ```
38273894 pub fn structured ( value : Value ) -> Self {
38283895 CallToolResult {
3829- result_type : ResultType :: default ( ) ,
3896+ result_type : Some ( ResultType :: COMPLETE ) ,
38303897 content : vec ! [ ContentBlock :: text( value. to_string( ) ) ] ,
38313898 structured_content : Some ( value) ,
38323899 is_error : Some ( false ) ,
@@ -3853,7 +3920,7 @@ impl CallToolResult {
38533920 /// ```
38543921 pub fn structured_error ( value : Value ) -> Self {
38553922 CallToolResult {
3856- result_type : ResultType :: default ( ) ,
3923+ result_type : Some ( ResultType :: COMPLETE ) ,
38573924 content : vec ! [ ContentBlock :: text( value. to_string( ) ) ] ,
38583925 structured_content : Some ( value) ,
38593926 is_error : Some ( true ) ,
@@ -4041,26 +4108,41 @@ impl CreateMessageResult {
40414108 }
40424109}
40434110
4044- #[ derive( Default , Debug , Serialize , Deserialize , Clone , PartialEq ) ]
4111+ #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq ) ]
40454112#[ serde( rename_all = "camelCase" ) ]
40464113#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
40474114#[ non_exhaustive]
40484115pub struct GetPromptResult {
4049- /// Result type discriminator. Absent values deserialize as `"complete"`.
4050- #[ serde( default ) ]
4051- pub result_type : ResultType ,
4116+ /// Result type discriminator (SEP-2322). Required by the [spec schema]
4117+ /// for servers implementing protocol version `2026-07-28`, but optional
4118+ /// here because this type also models results from older protocol
4119+ /// versions, which do not carry the field: `None` means absent on the
4120+ /// wire, and per the spec "the client MUST treat the absent field as
4121+ /// `"complete"`". Constructors default to `Some(ResultType::COMPLETE)`;
4122+ /// the server handler clears the field when responding to peers that
4123+ /// negotiated an older version.
4124+ ///
4125+ /// [spec schema]: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/5bed7b30527019e34ccb0eb474636651424501f6/schema/draft/schema.ts#L225-L234
4126+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
4127+ pub result_type : Option < ResultType > ,
40524128 #[ serde( skip_serializing_if = "Option::is_none" ) ]
40534129 pub description : Option < String > ,
40544130 pub messages : Vec < PromptMessage > ,
40554131 #[ serde( rename = "_meta" , skip_serializing_if = "Option::is_none" ) ]
40564132 pub meta : Option < MetaObject > ,
40574133}
40584134
4135+ impl Default for GetPromptResult {
4136+ fn default ( ) -> Self {
4137+ Self :: new ( Vec :: new ( ) )
4138+ }
4139+ }
4140+
40594141impl GetPromptResult {
40604142 /// Create a new GetPromptResult with required fields.
40614143 pub fn new ( messages : Vec < PromptMessage > ) -> Self {
40624144 Self {
4063- result_type : ResultType :: default ( ) ,
4145+ result_type : Some ( ResultType :: COMPLETE ) ,
40644146 description : None ,
40654147 messages,
40664148 meta : None ,
@@ -4424,6 +4506,42 @@ impl ServerResult {
44244506 pub fn task_ack ( _: ( ) ) -> ServerResult {
44254507 ServerResult :: TaskAckResult ( TaskAckResult :: new ( ) )
44264508 }
4509+
4510+ /// Strip the SEP-2322 `resultType: "complete"` discriminator so the result
4511+ /// keeps the wire shape that predates protocol version `2026-07-28`.
4512+ ///
4513+ /// The server handler calls this before responding to a peer that
4514+ /// negotiated an older protocol version, where the field did not exist and
4515+ /// strict peers may reject it. Only the `"complete"` value is stripped:
4516+ /// results whose discriminator carries meaning (`"input_required"`,
4517+ /// `"task"`) are already gated to `2026-07-28`+ sessions, and custom
4518+ /// extension values are preserved.
4519+ ///
4520+ /// # Examples
4521+ ///
4522+ /// ```
4523+ /// use rmcp::model::{CallToolResult, ServerResult};
4524+ ///
4525+ /// let mut result = ServerResult::CallToolResult(CallToolResult::success(vec![]));
4526+ /// result.strip_result_type_for_legacy_peer();
4527+ ///
4528+ /// let json = serde_json::to_value(&result).unwrap();
4529+ /// assert!(json.get("resultType").is_none());
4530+ /// ```
4531+ pub fn strip_result_type_for_legacy_peer ( & mut self ) {
4532+ let result_type = match self {
4533+ ServerResult :: CompleteResult ( r) => & mut r. result_type ,
4534+ ServerResult :: GetPromptResult ( r) => & mut r. result_type ,
4535+ ServerResult :: ListPromptsResult ( r) => & mut r. result_type ,
4536+ ServerResult :: ListResourcesResult ( r) => & mut r. result_type ,
4537+ ServerResult :: ListResourceTemplatesResult ( r) => & mut r. result_type ,
4538+ ServerResult :: ReadResourceResult ( r) => & mut r. result_type ,
4539+ ServerResult :: ListToolsResult ( r) => & mut r. result_type ,
4540+ ServerResult :: CallToolResult ( r) => & mut r. result_type ,
4541+ _ => return ,
4542+ } ;
4543+ result_type. take_if ( |result_type| result_type. is_complete ( ) ) ;
4544+ }
44274545}
44284546
44294547pub type ServerJsonRpcMessage = JsonRpcMessage < ServerRequest , ServerResult , ServerNotification > ;
0 commit comments