@@ -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,15 @@ 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. `None` means absent on the wire,
1489+ /// which peers must treat as `"complete"` (SEP-2322).
1490+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1491+ pub result_type: Option <ResultType >,
14841492 #[ serde( rename = "_meta" , default , skip_serializing_if = "Option::is_none" ) ]
14851493 pub meta: Option <MetaObject >,
14861494 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
@@ -1502,10 +1510,16 @@ macro_rules! paginated_result {
15021510 pub $i_item: $t_item,
15031511 }
15041512
1513+ impl Default for $t {
1514+ fn default ( ) -> Self {
1515+ Self :: with_all_items( Default :: default ( ) )
1516+ }
1517+ }
1518+
15051519 impl $t {
15061520 pub fn with_all_items( items: $t_item) -> Self {
15071521 Self {
1508- result_type: ResultType :: default ( ) ,
1522+ result_type: Some ( ResultType :: COMPLETE ) ,
15091523 meta: None ,
15101524 next_cursor: None ,
15111525 ttl_ms: None ,
@@ -1621,9 +1635,10 @@ pub type ReadResourceRequestParam = ReadResourceRequestParams;
16211635#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
16221636#[ non_exhaustive]
16231637pub struct ReadResourceResult {
1624- /// Result type discriminator. Absent values deserialize as `"complete"`.
1625- #[ serde( default ) ]
1626- pub result_type : ResultType ,
1638+ /// Result type discriminator. `None` means absent on the wire,
1639+ /// which peers must treat as `"complete"` (SEP-2322).
1640+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
1641+ pub result_type : Option < ResultType > ,
16271642 /// Time, in milliseconds, that this result may be treated as fresh (SEP-2549).
16281643 /// Required by spec version 2026-07-28, but optional here to maintain compatibility
16291644 /// with older spec versions.
@@ -1648,7 +1663,7 @@ impl ReadResourceResult {
16481663 /// Create a new ReadResourceResult with the given contents.
16491664 pub fn new ( contents : Vec < ResourceContents > ) -> Self {
16501665 Self {
1651- result_type : ResultType :: default ( ) ,
1666+ result_type : Some ( ResultType :: COMPLETE ) ,
16521667 ttl_ms : None ,
16531668 cache_scope : None ,
16541669 contents,
@@ -3208,24 +3223,31 @@ impl CompletionInfo {
32083223 }
32093224}
32103225
3211- #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq , Default ) ]
3226+ #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq ) ]
32123227#[ serde( rename_all = "camelCase" ) ]
32133228#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
32143229#[ non_exhaustive]
32153230pub struct CompleteResult {
3216- /// Result type discriminator. Absent values deserialize as `"complete"`.
3217- #[ serde( default ) ]
3218- pub result_type : ResultType ,
3231+ /// Result type discriminator. `None` means absent on the wire,
3232+ /// which peers must treat as `"complete"` (SEP-2322).
3233+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
3234+ pub result_type : Option < ResultType > ,
32193235 pub completion : CompletionInfo ,
32203236 #[ serde( rename = "_meta" , skip_serializing_if = "Option::is_none" ) ]
32213237 pub meta : Option < MetaObject > ,
32223238}
32233239
3240+ impl Default for CompleteResult {
3241+ fn default ( ) -> Self {
3242+ Self :: new ( CompletionInfo :: default ( ) )
3243+ }
3244+ }
3245+
32243246impl CompleteResult {
32253247 /// Create a new CompleteResult with the given completion info.
32263248 pub fn new ( completion : CompletionInfo ) -> Self {
32273249 Self {
3228- result_type : ResultType :: default ( ) ,
3250+ result_type : Some ( ResultType :: COMPLETE ) ,
32293251 completion,
32303252 meta : None ,
32313253 }
@@ -3674,14 +3696,15 @@ pub type CreateElicitationRequest = ElicitRequest;
36743696///
36753697/// Contains the content returned by the tool execution and an optional
36763698/// flag indicating whether the operation resulted in an error.
3677- #[ derive( Default , Debug , Serialize , Clone , PartialEq ) ]
3699+ #[ derive( Debug , Serialize , Clone , PartialEq ) ]
36783700#[ serde( rename_all = "camelCase" ) ]
36793701#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
36803702#[ non_exhaustive]
36813703pub struct CallToolResult {
3682- /// Result type discriminator. Absent values deserialize as `"complete"`.
3683- #[ serde( default ) ]
3684- pub result_type : ResultType ,
3704+ /// Result type discriminator. `None` means absent on the wire,
3705+ /// which peers must treat as `"complete"` (SEP-2322).
3706+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
3707+ pub result_type : Option < ResultType > ,
36853708 /// The content returned by the tool (text, images, etc.)
36863709 #[ serde( default ) ]
36873710 pub content : Vec < ContentBlock > ,
@@ -3710,7 +3733,7 @@ impl<'de> Deserialize<'de> for CallToolResult {
37103733 #[ serde( rename_all = "camelCase" ) ]
37113734 struct Helper {
37123735 #[ serde( default ) ]
3713- result_type : ResultType ,
3736+ result_type : Option < ResultType > ,
37143737 content : Option < Vec < ContentBlock > > ,
37153738 structured_content : Option < Value > ,
37163739 is_error : Option < bool > ,
@@ -3741,11 +3764,23 @@ impl<'de> Deserialize<'de> for CallToolResult {
37413764 }
37423765}
37433766
3767+ impl Default for CallToolResult {
3768+ fn default ( ) -> Self {
3769+ CallToolResult {
3770+ result_type : Some ( ResultType :: COMPLETE ) ,
3771+ content : Vec :: new ( ) ,
3772+ structured_content : None ,
3773+ is_error : None ,
3774+ meta : None ,
3775+ }
3776+ }
3777+ }
3778+
37443779impl CallToolResult {
37453780 /// Create a successful tool result with unstructured content
37463781 pub fn success ( content : Vec < ContentBlock > ) -> Self {
37473782 CallToolResult {
3748- result_type : ResultType :: default ( ) ,
3783+ result_type : Some ( ResultType :: COMPLETE ) ,
37493784 content,
37503785 structured_content : None ,
37513786 is_error : Some ( false ) ,
@@ -3803,7 +3838,7 @@ impl CallToolResult {
38033838 /// ```
38043839 pub fn error ( content : Vec < ContentBlock > ) -> Self {
38053840 CallToolResult {
3806- result_type : ResultType :: default ( ) ,
3841+ result_type : Some ( ResultType :: COMPLETE ) ,
38073842 content,
38083843 structured_content : None ,
38093844 is_error : Some ( true ) ,
@@ -3826,7 +3861,7 @@ impl CallToolResult {
38263861 /// ```
38273862 pub fn structured ( value : Value ) -> Self {
38283863 CallToolResult {
3829- result_type : ResultType :: default ( ) ,
3864+ result_type : Some ( ResultType :: COMPLETE ) ,
38303865 content : vec ! [ ContentBlock :: text( value. to_string( ) ) ] ,
38313866 structured_content : Some ( value) ,
38323867 is_error : Some ( false ) ,
@@ -3853,7 +3888,7 @@ impl CallToolResult {
38533888 /// ```
38543889 pub fn structured_error ( value : Value ) -> Self {
38553890 CallToolResult {
3856- result_type : ResultType :: default ( ) ,
3891+ result_type : Some ( ResultType :: COMPLETE ) ,
38573892 content : vec ! [ ContentBlock :: text( value. to_string( ) ) ] ,
38583893 structured_content : Some ( value) ,
38593894 is_error : Some ( true ) ,
@@ -4041,26 +4076,33 @@ impl CreateMessageResult {
40414076 }
40424077}
40434078
4044- #[ derive( Default , Debug , Serialize , Deserialize , Clone , PartialEq ) ]
4079+ #[ derive( Debug , Serialize , Deserialize , Clone , PartialEq ) ]
40454080#[ serde( rename_all = "camelCase" ) ]
40464081#[ cfg_attr( feature = "schemars" , derive( schemars:: JsonSchema ) ) ]
40474082#[ non_exhaustive]
40484083pub struct GetPromptResult {
4049- /// Result type discriminator. Absent values deserialize as `"complete"`.
4050- #[ serde( default ) ]
4051- pub result_type : ResultType ,
4084+ /// Result type discriminator. `None` means absent on the wire,
4085+ /// which peers must treat as `"complete"` (SEP-2322).
4086+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
4087+ pub result_type : Option < ResultType > ,
40524088 #[ serde( skip_serializing_if = "Option::is_none" ) ]
40534089 pub description : Option < String > ,
40544090 pub messages : Vec < PromptMessage > ,
40554091 #[ serde( rename = "_meta" , skip_serializing_if = "Option::is_none" ) ]
40564092 pub meta : Option < MetaObject > ,
40574093}
40584094
4095+ impl Default for GetPromptResult {
4096+ fn default ( ) -> Self {
4097+ Self :: new ( Vec :: new ( ) )
4098+ }
4099+ }
4100+
40594101impl GetPromptResult {
40604102 /// Create a new GetPromptResult with required fields.
40614103 pub fn new ( messages : Vec < PromptMessage > ) -> Self {
40624104 Self {
4063- result_type : ResultType :: default ( ) ,
4105+ result_type : Some ( ResultType :: COMPLETE ) ,
40644106 description : None ,
40654107 messages,
40664108 meta : None ,
@@ -4424,6 +4466,42 @@ impl ServerResult {
44244466 pub fn task_ack ( _: ( ) ) -> ServerResult {
44254467 ServerResult :: TaskAckResult ( TaskAckResult :: new ( ) )
44264468 }
4469+
4470+ /// Strip the SEP-2322 `resultType: "complete"` discriminator so the result
4471+ /// keeps the wire shape that predates protocol version `2026-07-28`.
4472+ ///
4473+ /// The server handler calls this before responding to a peer that
4474+ /// negotiated an older protocol version, where the field did not exist and
4475+ /// strict peers may reject it. Only the `"complete"` value is stripped:
4476+ /// results whose discriminator carries meaning (`"input_required"`,
4477+ /// `"task"`) are already gated to `2026-07-28`+ sessions, and custom
4478+ /// extension values are preserved.
4479+ ///
4480+ /// # Examples
4481+ ///
4482+ /// ```
4483+ /// use rmcp::model::{CallToolResult, ServerResult};
4484+ ///
4485+ /// let mut result = ServerResult::CallToolResult(CallToolResult::success(vec![]));
4486+ /// result.strip_result_type_for_legacy_peer();
4487+ ///
4488+ /// let json = serde_json::to_value(&result).unwrap();
4489+ /// assert!(json.get("resultType").is_none());
4490+ /// ```
4491+ pub fn strip_result_type_for_legacy_peer ( & mut self ) {
4492+ let result_type = match self {
4493+ ServerResult :: CompleteResult ( r) => & mut r. result_type ,
4494+ ServerResult :: GetPromptResult ( r) => & mut r. result_type ,
4495+ ServerResult :: ListPromptsResult ( r) => & mut r. result_type ,
4496+ ServerResult :: ListResourcesResult ( r) => & mut r. result_type ,
4497+ ServerResult :: ListResourceTemplatesResult ( r) => & mut r. result_type ,
4498+ ServerResult :: ReadResourceResult ( r) => & mut r. result_type ,
4499+ ServerResult :: ListToolsResult ( r) => & mut r. result_type ,
4500+ ServerResult :: CallToolResult ( r) => & mut r. result_type ,
4501+ _ => return ,
4502+ } ;
4503+ result_type. take_if ( |result_type| result_type. is_complete ( ) ) ;
4504+ }
44274505}
44284506
44294507pub type ServerJsonRpcMessage = JsonRpcMessage < ServerRequest , ServerResult , ServerNotification > ;
0 commit comments