1use std::ops::{Deref, DerefMut};
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::{
7 ClientCapabilities, ClientNotification, ClientRequest, CustomNotification, CustomRequest,
8 Extensions, Implementation, JsonObject, JsonRpcMessage, LoggingLevel, ProgressToken,
9 ProtocolVersion, RequestId, ServerNotification, ServerRequest,
10};
11
12pub trait GetMeta {
27 type Metadata: Default;
29 fn get_meta_mut(&mut self) -> &mut Self::Metadata;
30 fn get_meta(&self) -> &Self::Metadata;
31}
32
33pub trait GetExtensions {
34 fn extensions(&self) -> &Extensions;
35 fn extensions_mut(&mut self) -> &mut Extensions;
36}
37
38pub trait RequestParamsMeta {
44 fn meta(&self) -> Option<&RequestMetaObject>;
46 fn meta_mut(&mut self) -> &mut Option<RequestMetaObject>;
48 fn set_meta(&mut self, meta: RequestMetaObject) {
50 *self.meta_mut() = Some(meta);
51 }
52 fn progress_token(&self) -> Option<ProgressToken> {
54 self.meta().and_then(|m| m.get_progress_token())
55 }
56 fn set_progress_token(&mut self, token: ProgressToken) {
58 match self.meta_mut() {
59 Some(meta) => meta.set_progress_token(token),
60 none => {
61 let mut meta = RequestMetaObject::new();
62 meta.set_progress_token(token);
63 *none = Some(meta);
64 }
65 }
66 }
67 fn traceparent(&self) -> Option<&str> {
69 self.meta().and_then(|m| m.get_traceparent())
70 }
71 fn set_traceparent(&mut self, value: &str) {
73 self.meta_or_default().set_traceparent(value);
74 }
75 fn tracestate(&self) -> Option<&str> {
77 self.meta().and_then(|m| m.get_tracestate())
78 }
79 fn set_tracestate(&mut self, value: &str) {
81 self.meta_or_default().set_tracestate(value);
82 }
83 fn baggage(&self) -> Option<&str> {
85 self.meta().and_then(|m| m.get_baggage())
86 }
87 fn set_baggage(&mut self, value: &str) {
89 self.meta_or_default().set_baggage(value);
90 }
91 fn meta_or_default(&mut self) -> &mut RequestMetaObject {
93 self.meta_mut().get_or_insert_with(RequestMetaObject::new)
94 }
95}
96
97impl GetExtensions for CustomNotification {
98 fn extensions(&self) -> &Extensions {
99 &self.extensions
100 }
101 fn extensions_mut(&mut self) -> &mut Extensions {
102 &mut self.extensions
103 }
104}
105
106impl GetMeta for CustomNotification {
107 type Metadata = NotificationMetaObject;
108 fn get_meta_mut(&mut self) -> &mut NotificationMetaObject {
109 self.extensions_mut().get_or_insert_default()
110 }
111 fn get_meta(&self) -> &NotificationMetaObject {
112 self.extensions()
113 .get::<NotificationMetaObject>()
114 .unwrap_or(NotificationMetaObject::static_empty())
115 }
116}
117
118impl GetExtensions for CustomRequest {
119 fn extensions(&self) -> &Extensions {
120 &self.extensions
121 }
122 fn extensions_mut(&mut self) -> &mut Extensions {
123 &mut self.extensions
124 }
125}
126
127impl GetMeta for CustomRequest {
128 type Metadata = RequestMetaObject;
129 fn get_meta_mut(&mut self) -> &mut RequestMetaObject {
130 self.extensions_mut().get_or_insert_default()
131 }
132 fn get_meta(&self) -> &RequestMetaObject {
133 self.extensions()
134 .get::<RequestMetaObject>()
135 .unwrap_or(RequestMetaObject::static_empty())
136 }
137}
138
139macro_rules! variant_extension {
140 (
141 $Enum: ident: $Metadata: ident {
142 $($variant: ident)*
143 }
144 ) => {
145 impl GetExtensions for $Enum {
146 fn extensions(&self) -> &Extensions {
147 match self {
148 $(
149 $Enum::$variant(v) => &v.extensions,
150 )*
151 }
152 }
153 fn extensions_mut(&mut self) -> &mut Extensions {
154 match self {
155 $(
156 $Enum::$variant(v) => &mut v.extensions,
157 )*
158 }
159 }
160 }
161 impl GetMeta for $Enum {
162 type Metadata = $Metadata;
163 fn get_meta_mut(&mut self) -> &mut $Metadata {
164 self.extensions_mut().get_or_insert_default()
165 }
166 fn get_meta(&self) -> &$Metadata {
167 self.extensions().get::<$Metadata>().unwrap_or($Metadata::static_empty())
168 }
169 }
170 };
171}
172
173variant_extension! {
174 ClientRequest: RequestMetaObject {
175 PingRequest
176 InitializeRequest
177 DiscoverRequest
178 CompleteRequest
179 SetLevelRequest
180 GetPromptRequest
181 ListPromptsRequest
182 ListResourcesRequest
183 ListResourceTemplatesRequest
184 ReadResourceRequest
185 SubscriptionsListenRequest
186 SubscribeRequest
187 UnsubscribeRequest
188 CallToolRequest
189 ListToolsRequest
190 CustomRequest
191 GetTaskRequest
192 UpdateTaskRequest
193 CancelTaskRequest
194 }
195}
196
197variant_extension! {
198 ServerRequest: RequestMetaObject {
199 PingRequest
200 CreateMessageRequest
201 ListRootsRequest
202 ElicitRequest
203 CustomRequest
204 }
205}
206
207variant_extension! {
208 ClientNotification: NotificationMetaObject {
209 CancelledNotification
210 ProgressNotification
211 InitializedNotification
212 RootsListChangedNotification
213 CustomNotification
214 }
215}
216
217variant_extension! {
218 ServerNotification: NotificationMetaObject {
219 CancelledNotification
220 ProgressNotification
221 LoggingMessageNotification
222 ResourceUpdatedNotification
223 ResourceListChangedNotification
224 ToolListChangedNotification
225 PromptListChangedNotification
226 SubscriptionsAcknowledgedNotification
227 TaskStatusNotification
228 CustomNotification
229 }
230}
231
232#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
242#[serde(transparent)]
243#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
244pub struct MetaObject(pub JsonObject);
245
246impl MetaObject {
247 const TRACEPARENT_FIELD: &str = "traceparent";
249 const TRACESTATE_FIELD: &str = "tracestate";
251 const BAGGAGE_FIELD: &str = "baggage";
253
254 pub fn new() -> Self {
256 Self(JsonObject::new())
257 }
258
259 fn get_str(&self, field: &str) -> Option<&str> {
261 self.0.get(field).and_then(Value::as_str)
262 }
263
264 fn set_str(&mut self, field: &str, value: impl Into<String>) {
266 self.0
267 .insert(field.to_string(), Value::String(value.into()));
268 }
269
270 pub fn get_traceparent(&self) -> Option<&str> {
272 self.get_str(Self::TRACEPARENT_FIELD)
273 }
274
275 pub fn set_traceparent(&mut self, value: impl Into<String>) {
288 self.set_str(Self::TRACEPARENT_FIELD, value);
289 }
290
291 pub fn get_tracestate(&self) -> Option<&str> {
293 self.get_str(Self::TRACESTATE_FIELD)
294 }
295
296 pub fn set_tracestate(&mut self, value: impl Into<String>) {
298 self.set_str(Self::TRACESTATE_FIELD, value);
299 }
300
301 pub fn get_baggage(&self) -> Option<&str> {
303 self.get_str(Self::BAGGAGE_FIELD)
304 }
305
306 pub fn set_baggage(&mut self, value: impl Into<String>) {
308 self.set_str(Self::BAGGAGE_FIELD, value);
309 }
310
311 pub fn extend(&mut self, other: MetaObject) {
313 self.0.extend(other.0);
314 }
315
316 fn decode_value<T>(&self, key: &str) -> Option<T>
317 where
318 T: for<'de> Deserialize<'de>,
319 {
320 self.0.get(key).and_then(|value| T::deserialize(value).ok())
321 }
322
323 fn insert_serialized<T>(&mut self, key: &str, value: T)
324 where
325 T: Serialize,
326 {
327 let value = serde_json::to_value(value)
328 .expect("MCP meta helper value should serialize to valid JSON");
329 self.0.insert(key.to_string(), value);
330 }
331}
332
333impl Deref for MetaObject {
334 type Target = JsonObject;
335
336 fn deref(&self) -> &Self::Target {
337 &self.0
338 }
339}
340
341impl DerefMut for MetaObject {
342 fn deref_mut(&mut self) -> &mut Self::Target {
343 &mut self.0
344 }
345}
346
347impl From<JsonObject> for MetaObject {
348 fn from(object: JsonObject) -> Self {
349 Self(object)
350 }
351}
352
353#[cfg(feature = "schemars")]
354impl schemars::JsonSchema for MetaObject {
355 fn schema_name() -> std::borrow::Cow<'static, str> {
356 std::borrow::Cow::Borrowed("MetaObject")
357 }
358
359 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
360 schemars::json_schema!({
361 "description": "See [MCP general fields](https://modelcontextprotocol.io/specification/2026-07-28/basic#general-fields) for notes on _meta usage.",
362 "type": "object",
363 "additionalProperties": true,
364 })
365 }
366}
367
368#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
388#[serde(transparent)]
389#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
390pub struct RequestMetaObject(pub MetaObject);
391
392impl RequestMetaObject {
393 const PROGRESS_TOKEN_FIELD: &str = "progressToken";
394 const META_KEY_PROTOCOL_VERSION: &str = "io.modelcontextprotocol/protocolVersion";
395 const META_KEY_CLIENT_INFO: &str = "io.modelcontextprotocol/clientInfo";
396 const META_KEY_CLIENT_CAPABILITIES: &str = "io.modelcontextprotocol/clientCapabilities";
397 const META_KEY_LOG_LEVEL: &str = "io.modelcontextprotocol/logLevel";
398
399 pub const DRAFT_REQUIRED_KEYS: [&str; 2] = [
401 Self::META_KEY_PROTOCOL_VERSION,
402 Self::META_KEY_CLIENT_CAPABILITIES,
403 ];
404
405 pub fn new() -> Self {
407 Self::default()
408 }
409
410 pub fn with_progress_token(token: ProgressToken) -> Self {
412 let mut meta = Self::new();
413 meta.set_progress_token(token);
414 meta
415 }
416
417 pub fn with_client_context(
419 protocol_version: ProtocolVersion,
420 client_info: Implementation,
421 client_capabilities: ClientCapabilities,
422 ) -> Self {
423 let mut meta = Self::new();
424 meta.set_protocol_version(protocol_version);
425 meta.set_client_info(client_info);
426 meta.set_client_capabilities(client_capabilities);
427 meta
428 }
429
430 pub(crate) fn static_empty() -> &'static Self {
431 static EMPTY: std::sync::OnceLock<RequestMetaObject> = std::sync::OnceLock::new();
432 EMPTY.get_or_init(Default::default)
433 }
434
435 pub fn get_progress_token(&self) -> Option<ProgressToken> {
437 self.0.decode_value(Self::PROGRESS_TOKEN_FIELD)
438 }
439
440 pub fn set_progress_token(&mut self, token: ProgressToken) {
442 self.0.insert_serialized(Self::PROGRESS_TOKEN_FIELD, token);
443 }
444
445 pub fn protocol_version(&self) -> Option<ProtocolVersion> {
447 self.0.decode_value(Self::META_KEY_PROTOCOL_VERSION)
448 }
449
450 pub fn set_protocol_version(&mut self, protocol_version: ProtocolVersion) {
452 self.0.0.insert(
453 Self::META_KEY_PROTOCOL_VERSION.to_string(),
454 Value::String(protocol_version.to_string()),
455 );
456 }
457
458 pub fn client_info(&self) -> Option<Implementation> {
460 self.0.decode_value(Self::META_KEY_CLIENT_INFO)
461 }
462
463 pub fn set_client_info(&mut self, client_info: Implementation) {
465 self.0
466 .insert_serialized(Self::META_KEY_CLIENT_INFO, client_info);
467 }
468
469 pub fn client_capabilities(&self) -> Option<ClientCapabilities> {
471 self.0.decode_value(Self::META_KEY_CLIENT_CAPABILITIES)
472 }
473
474 pub fn set_client_capabilities(&mut self, client_capabilities: ClientCapabilities) {
476 self.0
477 .insert_serialized(Self::META_KEY_CLIENT_CAPABILITIES, client_capabilities);
478 }
479
480 pub fn log_level(&self) -> Option<LoggingLevel> {
482 self.0.decode_value(Self::META_KEY_LOG_LEVEL)
483 }
484
485 pub fn set_log_level(&mut self, log_level: LoggingLevel) {
487 self.0
488 .insert_serialized(Self::META_KEY_LOG_LEVEL, log_level);
489 }
490
491 pub fn missing_required_keys(&self, protocol_version: &ProtocolVersion) -> Vec<&'static str> {
519 if protocol_version.as_str() < ProtocolVersion::V_2026_07_28.as_str() {
520 return Vec::new();
521 }
522 let mut missing = Vec::new();
523 if self.protocol_version().is_none() {
524 missing.push(Self::META_KEY_PROTOCOL_VERSION);
525 }
526 if self.client_capabilities().is_none() {
527 missing.push(Self::META_KEY_CLIENT_CAPABILITIES);
528 }
529 missing
530 }
531
532 pub fn extend(&mut self, other: RequestMetaObject) {
534 self.0.extend(other.0);
535 }
536}
537
538impl Deref for RequestMetaObject {
539 type Target = MetaObject;
540
541 fn deref(&self) -> &Self::Target {
542 &self.0
543 }
544}
545
546impl DerefMut for RequestMetaObject {
547 fn deref_mut(&mut self) -> &mut Self::Target {
548 &mut self.0
549 }
550}
551
552impl From<MetaObject> for RequestMetaObject {
553 fn from(meta: MetaObject) -> Self {
554 Self(meta)
555 }
556}
557
558impl From<JsonObject> for RequestMetaObject {
559 fn from(object: JsonObject) -> Self {
560 Self(MetaObject(object))
561 }
562}
563
564#[cfg(feature = "schemars")]
565impl schemars::JsonSchema for RequestMetaObject {
566 fn schema_name() -> std::borrow::Cow<'static, str> {
567 std::borrow::Cow::Borrowed("RequestMetaObject")
568 }
569
570 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
571 let progress_token = generator.subschema_for::<ProgressToken>();
572 let client_info = generator.subschema_for::<Implementation>();
573 let client_capabilities = generator.subschema_for::<ClientCapabilities>();
574 let log_level = generator.subschema_for::<LoggingLevel>();
575 schemars::json_schema!({
581 "description": "Metadata reserved by MCP on requests. Extension keys are also allowed.",
582 "type": "object",
583 "properties": {
584 "progressToken": progress_token,
585 "io.modelcontextprotocol/protocolVersion": {
586 "type": "string",
587 },
588 "io.modelcontextprotocol/clientInfo": client_info,
589 "io.modelcontextprotocol/clientCapabilities": client_capabilities,
590 "io.modelcontextprotocol/logLevel": log_level,
591 },
592 "additionalProperties": true,
593 })
594 }
595}
596
597#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
607#[serde(transparent)]
608#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
609pub struct NotificationMetaObject(pub MetaObject);
610
611impl NotificationMetaObject {
612 const META_KEY_SUBSCRIPTION_ID: &str = "io.modelcontextprotocol/subscriptionId";
613
614 pub fn new() -> Self {
616 Self::default()
617 }
618
619 pub(crate) fn static_empty() -> &'static Self {
620 static EMPTY: std::sync::OnceLock<NotificationMetaObject> = std::sync::OnceLock::new();
621 EMPTY.get_or_init(Default::default)
622 }
623
624 pub fn subscription_id(&self) -> Option<RequestId> {
637 self.0.decode_value(Self::META_KEY_SUBSCRIPTION_ID)
638 }
639
640 pub fn set_subscription_id(&mut self, subscription_id: RequestId) {
642 self.0
643 .insert_serialized(Self::META_KEY_SUBSCRIPTION_ID, subscription_id);
644 }
645
646 pub fn extend(&mut self, other: NotificationMetaObject) {
648 self.0.extend(other.0);
649 }
650}
651
652impl Deref for NotificationMetaObject {
653 type Target = MetaObject;
654
655 fn deref(&self) -> &Self::Target {
656 &self.0
657 }
658}
659
660impl DerefMut for NotificationMetaObject {
661 fn deref_mut(&mut self) -> &mut Self::Target {
662 &mut self.0
663 }
664}
665
666impl From<MetaObject> for NotificationMetaObject {
667 fn from(meta: MetaObject) -> Self {
668 Self(meta)
669 }
670}
671
672impl From<JsonObject> for NotificationMetaObject {
673 fn from(object: JsonObject) -> Self {
674 Self(MetaObject(object))
675 }
676}
677
678#[cfg(feature = "schemars")]
679impl schemars::JsonSchema for NotificationMetaObject {
680 fn schema_name() -> std::borrow::Cow<'static, str> {
681 std::borrow::Cow::Borrowed("NotificationMetaObject")
682 }
683
684 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
685 let subscription_id = generator.subschema_for::<RequestId>();
686 schemars::json_schema!({
687 "description": "Metadata reserved by MCP on notifications. Extension keys are also allowed.",
688 "type": "object",
689 "properties": {
690 "io.modelcontextprotocol/subscriptionId": subscription_id,
691 },
692 "additionalProperties": true,
693 })
694 }
695}
696
697impl<Req, Resp, Noti> JsonRpcMessage<Req, Resp, Noti>
698where
699 Req: GetExtensions,
700 Noti: GetExtensions,
701{
702 pub fn insert_extension<T: Clone + Send + Sync + 'static>(&mut self, value: T) {
703 match self {
704 JsonRpcMessage::Request(json_rpc_request) => {
705 json_rpc_request.request.extensions_mut().insert(value);
706 }
707 JsonRpcMessage::Notification(json_rpc_notification) => {
708 json_rpc_notification
709 .notification
710 .extensions_mut()
711 .insert(value);
712 }
713 _ => {}
714 }
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721 use crate::model::NumberOrString;
722
723 #[derive(Default)]
724 struct Params {
725 meta: Option<RequestMetaObject>,
726 }
727
728 impl RequestParamsMeta for Params {
729 fn meta(&self) -> Option<&RequestMetaObject> {
730 self.meta.as_ref()
731 }
732 fn meta_mut(&mut self) -> &mut Option<RequestMetaObject> {
733 &mut self.meta
734 }
735 }
736
737 const TRACEPARENT: &str = "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01";
738
739 #[test]
740 fn trace_context_round_trip() {
741 let mut meta = MetaObject::new();
742 meta.set_traceparent(TRACEPARENT);
743 meta.set_tracestate("vendor1=value1,vendor2=value2");
744 meta.set_baggage("userId=alice,region=us-east-1");
745 assert_eq!(meta.get_traceparent(), Some(TRACEPARENT));
746 assert_eq!(meta.get_tracestate(), Some("vendor1=value1,vendor2=value2"));
747 assert_eq!(meta.get_baggage(), Some("userId=alice,region=us-east-1"));
748 }
749
750 #[test]
751 fn absent_field_is_none() {
752 let meta = MetaObject::new();
753 assert_eq!(meta.get_traceparent(), None);
754 assert_eq!(meta.get_tracestate(), None);
755 assert_eq!(meta.get_baggage(), None);
756 }
757
758 #[test]
759 fn non_string_value_is_none() {
760 let mut meta = MetaObject::new();
761 meta.0
762 .insert(MetaObject::TRACEPARENT_FIELD.to_string(), Value::from(42));
763 assert_eq!(meta.get_traceparent(), None);
764 }
765
766 #[test]
767 fn trait_setter_inserts_meta_when_absent() {
768 let mut params = Params::default();
769 assert_eq!(params.traceparent(), None);
770 params.set_traceparent(TRACEPARENT);
771 assert_eq!(params.traceparent(), Some(TRACEPARENT));
772 }
773
774 #[test]
775 fn request_meta_derefs_to_general_helpers() {
776 let mut meta = RequestMetaObject::new();
777 meta.set_traceparent(TRACEPARENT);
778 meta.set_progress_token(ProgressToken(NumberOrString::Number(7)));
779 assert_eq!(meta.get_traceparent(), Some(TRACEPARENT));
780 assert_eq!(
781 meta.get_progress_token(),
782 Some(ProgressToken(NumberOrString::Number(7)))
783 );
784 }
785
786 mod subscription_id {
787 use super::*;
788
789 #[test]
790 fn returns_none_when_absent() {
791 let meta = NotificationMetaObject::new();
792 assert_eq!(meta.subscription_id(), None);
793 }
794
795 #[test]
796 fn round_trips_number_id() {
797 let mut meta = NotificationMetaObject::new();
798 meta.set_subscription_id(RequestId::Number(42));
799 assert_eq!(meta.subscription_id(), Some(RequestId::Number(42)));
800 }
801
802 #[test]
803 fn round_trips_string_id() {
804 let mut meta = NotificationMetaObject::new();
805 meta.set_subscription_id(RequestId::String("sub-1".into()));
806 assert_eq!(
807 meta.subscription_id(),
808 Some(RequestId::String("sub-1".into()))
809 );
810 }
811 }
812
813 mod missing_required_keys {
814 use super::*;
815
816 #[test]
817 fn is_empty_for_pre_draft_protocols() {
818 let meta = RequestMetaObject::new();
819 assert!(
820 meta.missing_required_keys(&ProtocolVersion::V_2025_11_25)
821 .is_empty()
822 );
823 }
824
825 #[test]
826 fn lists_all_draft_keys_for_empty_meta() {
827 let meta = RequestMetaObject::new();
828 assert_eq!(
829 meta.missing_required_keys(&ProtocolVersion::V_2026_07_28),
830 RequestMetaObject::DRAFT_REQUIRED_KEYS.to_vec()
831 );
832 }
833
834 #[test]
835 fn treats_malformed_values_as_missing() {
836 let meta: RequestMetaObject = serde_json::from_value(serde_json::json!({
837 "io.modelcontextprotocol/protocolVersion": 123,
838 "io.modelcontextprotocol/clientCapabilities": null,
839 }))
840 .unwrap();
841 assert_eq!(
842 meta.missing_required_keys(&ProtocolVersion::V_2026_07_28),
843 RequestMetaObject::DRAFT_REQUIRED_KEYS.to_vec()
844 );
845 }
846
847 #[test]
848 fn is_empty_when_draft_keys_are_present() {
849 let mut meta = RequestMetaObject::new();
850 meta.set_protocol_version(ProtocolVersion::V_2026_07_28);
851 meta.set_client_info(Implementation::from_build_env());
852 meta.set_client_capabilities(ClientCapabilities::default());
853 assert!(
854 meta.missing_required_keys(&ProtocolVersion::V_2026_07_28)
855 .is_empty()
856 );
857 }
858 }
859}