@@ -6,14 +6,61 @@ use super::{
66 CustomNotification , CustomRequest , Extensions , Meta , Notification , NotificationNoParam ,
77 Request , RequestNoParam , RequestOptionalParam ,
88} ;
9- #[ derive( Serialize , Deserialize ) ]
9+ #[ derive( Deserialize ) ]
1010struct WithMeta < ' a , P > {
11- #[ serde( skip_serializing_if = "Option::is_none" ) ]
1211 _meta : Option < Cow < ' a , Meta > > ,
1312 #[ serde( flatten) ]
1413 _rest : P ,
1514}
1615
16+ impl < P : Serialize > Serialize for WithMeta < ' _ , P > {
17+ fn serialize < S > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error >
18+ where
19+ S : serde:: Serializer ,
20+ {
21+ use serde:: ser:: SerializeMap ;
22+
23+ // Serialize _rest to a Value so we can inspect and strip any duplicate _meta
24+ let mut rest_value =
25+ serde_json:: to_value ( & self . _rest ) . map_err ( serde:: ser:: Error :: custom) ?;
26+
27+ // Extract _meta from the serialized params (if it's an object containing one)
28+ let params_meta: Option < Meta > = rest_value
29+ . as_object_mut ( )
30+ . and_then ( |obj| obj. remove ( "_meta" ) )
31+ . and_then ( |v| serde_json:: from_value ( v) . ok ( ) ) ;
32+
33+ // Merge: params-level _meta as base, extensions-level _meta overwrites on conflict
34+ let merged_meta = match ( self . _meta . as_deref ( ) , params_meta) {
35+ ( Some ( ext_meta) , Some ( mut params_meta) ) => {
36+ params_meta. extend ( ext_meta. clone ( ) ) ;
37+ Some ( params_meta)
38+ }
39+ ( Some ( ext_meta) , None ) => Some ( ext_meta. clone ( ) ) ,
40+ ( None , Some ( params_meta) ) => Some ( params_meta) ,
41+ ( None , None ) => None ,
42+ } ;
43+
44+ // Serialize as a flat map: single _meta + remaining params fields
45+ let rest_obj = match rest_value {
46+ serde_json:: Value :: Object ( map) => map,
47+ _ => serde_json:: Map :: new ( ) ,
48+ } ;
49+ let meta_count = usize:: from ( merged_meta. is_some ( ) ) ;
50+ let mut map = serializer. serialize_map ( Some ( rest_obj. len ( ) + meta_count) ) ?;
51+
52+ if let Some ( meta) = & merged_meta {
53+ map. serialize_entry ( "_meta" , meta) ?;
54+ }
55+
56+ for ( k, v) in & rest_obj {
57+ map. serialize_entry ( k, v) ?;
58+ }
59+
60+ map. end ( )
61+ }
62+ }
63+
1764#[ derive( Serialize , Deserialize ) ]
1865struct Proxy < ' a , M , P > {
1966 method : M ,
@@ -359,7 +406,9 @@ impl<'de> Deserialize<'de> for CustomNotification {
359406mod test {
360407 use serde_json:: json;
361408
362- use crate :: model:: ListToolsRequest ;
409+ use crate :: model:: {
410+ CallToolRequest , CallToolRequestParams , CustomRequest , Extensions , ListToolsRequest , Meta ,
411+ } ;
363412
364413 #[ test]
365414 fn test_deserialize_lost_tools_request ( ) {
@@ -370,4 +419,222 @@ mod test {
370419 ) )
371420 . unwrap ( ) ;
372421 }
422+
423+ #[ test]
424+ fn test_no_duplicate_meta_both_sources ( ) {
425+ // When both extensions and params contain _meta, the output should have
426+ // a single merged _meta key (not two separate ones).
427+ let mut extensions = Extensions :: new ( ) ;
428+ let mut ext_meta = Meta :: new ( ) ;
429+ ext_meta. 0 . insert ( "traceId" . to_string ( ) , json ! ( "abc" ) ) ;
430+ extensions. insert ( ext_meta) ;
431+
432+ let mut params_meta = Meta :: new ( ) ;
433+ params_meta. 0 . insert ( "progressToken" . to_string ( ) , json ! ( 1 ) ) ;
434+
435+ let req = CallToolRequest {
436+ extensions,
437+ method : Default :: default ( ) ,
438+ params : CallToolRequestParams {
439+ meta : Some ( params_meta) ,
440+ name : "my_tool" . into ( ) ,
441+ arguments : None ,
442+ task : None ,
443+ } ,
444+ } ;
445+
446+ let value = serde_json:: to_value ( & req) . unwrap ( ) ;
447+ let params = value. get ( "params" ) . unwrap ( ) ;
448+
449+ // There should be exactly one _meta key (JSON objects naturally deduplicate)
450+ let meta = params. get ( "_meta" ) . unwrap ( ) ;
451+
452+ // Both entries should be present in the merged _meta
453+ assert_eq ! ( meta. get( "traceId" ) . unwrap( ) , "abc" ) ;
454+ assert_eq ! ( meta. get( "progressToken" ) . unwrap( ) , 1 ) ;
455+
456+ // Verify the raw JSON string has exactly one occurrence of "_meta"
457+ let raw = serde_json:: to_string ( & req) . unwrap ( ) ;
458+ assert_eq ! (
459+ raw. matches( "\" _meta\" " ) . count( ) ,
460+ 1 ,
461+ "Expected exactly one _meta key in serialized output, got: {}" ,
462+ raw
463+ ) ;
464+ }
465+
466+ #[ test]
467+ fn test_meta_only_from_extensions ( ) {
468+ let mut extensions = Extensions :: new ( ) ;
469+ let mut ext_meta = Meta :: new ( ) ;
470+ ext_meta. 0 . insert ( "traceId" . to_string ( ) , json ! ( "ext-only" ) ) ;
471+ extensions. insert ( ext_meta) ;
472+
473+ let req = CallToolRequest {
474+ extensions,
475+ method : Default :: default ( ) ,
476+ params : CallToolRequestParams {
477+ meta : None ,
478+ name : "my_tool" . into ( ) ,
479+ arguments : None ,
480+ task : None ,
481+ } ,
482+ } ;
483+
484+ let value = serde_json:: to_value ( & req) . unwrap ( ) ;
485+ let meta = value[ "params" ] [ "_meta" ] . as_object ( ) . unwrap ( ) ;
486+ assert_eq ! ( meta. get( "traceId" ) . unwrap( ) , "ext-only" ) ;
487+ }
488+
489+ #[ test]
490+ fn test_meta_only_from_params ( ) {
491+ let mut params_meta = Meta :: new ( ) ;
492+ params_meta. 0 . insert ( "progressToken" . to_string ( ) , json ! ( 42 ) ) ;
493+
494+ let req = CallToolRequest {
495+ extensions : Extensions :: new ( ) ,
496+ method : Default :: default ( ) ,
497+ params : CallToolRequestParams {
498+ meta : Some ( params_meta) ,
499+ name : "my_tool" . into ( ) ,
500+ arguments : None ,
501+ task : None ,
502+ } ,
503+ } ;
504+
505+ let value = serde_json:: to_value ( & req) . unwrap ( ) ;
506+ let meta = value[ "params" ] [ "_meta" ] . as_object ( ) . unwrap ( ) ;
507+ assert_eq ! ( meta. get( "progressToken" ) . unwrap( ) , 42 ) ;
508+ }
509+
510+ #[ test]
511+ fn test_no_meta_emitted_when_neither_source ( ) {
512+ let req = CallToolRequest {
513+ extensions : Extensions :: new ( ) ,
514+ method : Default :: default ( ) ,
515+ params : CallToolRequestParams {
516+ meta : None ,
517+ name : "my_tool" . into ( ) ,
518+ arguments : None ,
519+ task : None ,
520+ } ,
521+ } ;
522+
523+ let value = serde_json:: to_value ( & req) . unwrap ( ) ;
524+ assert ! (
525+ value[ "params" ] . get( "_meta" ) . is_none( ) ,
526+ "Expected no _meta when neither source is populated"
527+ ) ;
528+ }
529+
530+ #[ test]
531+ fn test_extensions_meta_takes_priority_on_conflict ( ) {
532+ // When both sources have the same key, extensions should win.
533+ let mut extensions = Extensions :: new ( ) ;
534+ let mut ext_meta = Meta :: new ( ) ;
535+ ext_meta
536+ . 0
537+ . insert ( "shared_key" . to_string ( ) , json ! ( "from_extensions" ) ) ;
538+ extensions. insert ( ext_meta) ;
539+
540+ let mut params_meta = Meta :: new ( ) ;
541+ params_meta
542+ . 0
543+ . insert ( "shared_key" . to_string ( ) , json ! ( "from_params" ) ) ;
544+ params_meta
545+ . 0
546+ . insert ( "params_only" . to_string ( ) , json ! ( "kept" ) ) ;
547+
548+ let req = CallToolRequest {
549+ extensions,
550+ method : Default :: default ( ) ,
551+ params : CallToolRequestParams {
552+ meta : Some ( params_meta) ,
553+ name : "my_tool" . into ( ) ,
554+ arguments : None ,
555+ task : None ,
556+ } ,
557+ } ;
558+
559+ let value = serde_json:: to_value ( & req) . unwrap ( ) ;
560+ let meta = value[ "params" ] [ "_meta" ] . as_object ( ) . unwrap ( ) ;
561+ assert_eq ! ( meta. get( "shared_key" ) . unwrap( ) , "from_extensions" ) ;
562+ assert_eq ! ( meta. get( "params_only" ) . unwrap( ) , "kept" ) ;
563+ }
564+
565+ #[ test]
566+ fn test_round_trip_preserves_meta ( ) {
567+ let mut extensions = Extensions :: new ( ) ;
568+ let mut ext_meta = Meta :: new ( ) ;
569+ ext_meta
570+ . 0
571+ . insert ( "traceId" . to_string ( ) , json ! ( "round-trip" ) ) ;
572+ extensions. insert ( ext_meta) ;
573+
574+ let req = CallToolRequest {
575+ extensions,
576+ method : Default :: default ( ) ,
577+ params : CallToolRequestParams {
578+ meta : None ,
579+ name : "my_tool" . into ( ) ,
580+ arguments : Some ( serde_json:: Map :: from_iter ( [ ( "x" . to_string ( ) , json ! ( 1 ) ) ] ) ) ,
581+ task : None ,
582+ } ,
583+ } ;
584+
585+ let serialized = serde_json:: to_string ( & req) . unwrap ( ) ;
586+ let deserialized: CallToolRequest = serde_json:: from_str ( & serialized) . unwrap ( ) ;
587+
588+ // Extensions should have the meta after round-trip
589+ let meta = deserialized. extensions . get :: < Meta > ( ) . unwrap ( ) ;
590+ assert_eq ! ( meta. 0 . get( "traceId" ) . unwrap( ) , "round-trip" ) ;
591+
592+ // Params should be preserved
593+ assert_eq ! ( deserialized. params. name, "my_tool" ) ;
594+ assert_eq ! (
595+ deserialized
596+ . params
597+ . arguments
598+ . as_ref( )
599+ . unwrap( )
600+ . get( "x" )
601+ . unwrap( ) ,
602+ & json!( 1 )
603+ ) ;
604+ }
605+
606+ #[ test]
607+ fn test_custom_request_no_duplicate_meta ( ) {
608+ // CustomRequest uses Option<Value> as params — verify no duplicate _meta.
609+ let mut extensions = Extensions :: new ( ) ;
610+ let mut ext_meta = Meta :: new ( ) ;
611+ ext_meta
612+ . 0
613+ . insert ( "traceId" . to_string ( ) , json ! ( "custom-ext" ) ) ;
614+ extensions. insert ( ext_meta) ;
615+
616+ let params = Some ( json ! ( {
617+ "_meta" : { "progressToken" : 99 } ,
618+ "foo" : "bar"
619+ } ) ) ;
620+
621+ let req = CustomRequest {
622+ extensions,
623+ method : "custom/method" . into ( ) ,
624+ params,
625+ } ;
626+
627+ let raw = serde_json:: to_string ( & req) . unwrap ( ) ;
628+ assert_eq ! (
629+ raw. matches( "\" _meta\" " ) . count( ) ,
630+ 1 ,
631+ "Expected exactly one _meta key in CustomRequest output, got: {}" ,
632+ raw
633+ ) ;
634+
635+ let value: serde_json:: Value = serde_json:: from_str ( & raw ) . unwrap ( ) ;
636+ let meta = value[ "params" ] [ "_meta" ] . as_object ( ) . unwrap ( ) ;
637+ assert_eq ! ( meta. get( "traceId" ) . unwrap( ) , "custom-ext" ) ;
638+ assert_eq ! ( meta. get( "progressToken" ) . unwrap( ) , 99 ) ;
639+ }
373640}
0 commit comments