Skip to main content

rmcp/model/
meta.rs

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
12/// Access to the metadata carried by a message envelope's [`Extensions`].
13///
14/// The metadata type differs by message kind: requests carry a
15/// [`RequestMetaObject`] and notifications carry a [`NotificationMetaObject`].
16///
17/// The envelope extensions are the canonical runtime location for `_meta`:
18/// deserialization strips the wire `params._meta` into the extensions (typed
19/// params `meta` fields stay empty), and the service loop moves it into
20/// [`RequestContext::meta`] / [`NotificationContext::meta`] before dispatch.
21/// Typed params `meta` fields are honored when serializing outgoing messages;
22/// on key conflicts the extensions-level metadata wins.
23///
24/// [`RequestContext::meta`]: crate::service::RequestContext
25/// [`NotificationContext::meta`]: crate::service::NotificationContext
26pub trait GetMeta {
27    /// The metadata type for this message kind.
28    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
38/// Trait for request params that contain the `_meta` field.
39///
40/// Per the MCP spec, all request params may have an optional `_meta`
41/// field ([`RequestMetaObject`]) that can contain a `progressToken` for
42/// tracking long-running operations.
43pub trait RequestParamsMeta {
44    /// Get a reference to the meta field
45    fn meta(&self) -> Option<&RequestMetaObject>;
46    /// Get a mutable reference to the meta field
47    fn meta_mut(&mut self) -> &mut Option<RequestMetaObject>;
48    /// Set the meta field
49    fn set_meta(&mut self, meta: RequestMetaObject) {
50        *self.meta_mut() = Some(meta);
51    }
52    /// Get the progress token from meta, if present
53    fn progress_token(&self) -> Option<ProgressToken> {
54        self.meta().and_then(|m| m.get_progress_token())
55    }
56    /// Set a progress token in meta
57    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    /// Get the W3C `traceparent` value from meta, if present (SEP-414)
68    fn traceparent(&self) -> Option<&str> {
69        self.meta().and_then(|m| m.get_traceparent())
70    }
71    /// Set the W3C `traceparent` value in meta (SEP-414)
72    fn set_traceparent(&mut self, value: &str) {
73        self.meta_or_default().set_traceparent(value);
74    }
75    /// Get the W3C `tracestate` value from meta, if present (SEP-414)
76    fn tracestate(&self) -> Option<&str> {
77        self.meta().and_then(|m| m.get_tracestate())
78    }
79    /// Set the W3C `tracestate` value in meta (SEP-414)
80    fn set_tracestate(&mut self, value: &str) {
81        self.meta_or_default().set_tracestate(value);
82    }
83    /// Get the W3C `baggage` value from meta, if present (SEP-414)
84    fn baggage(&self) -> Option<&str> {
85        self.meta().and_then(|m| m.get_baggage())
86    }
87    /// Set the W3C `baggage` value in meta (SEP-414)
88    fn set_baggage(&mut self, value: &str) {
89        self.meta_or_default().set_baggage(value);
90    }
91    /// Get a mutable reference to meta, inserting an empty one if absent.
92    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/// General-purpose `_meta` map (spec `MetaObject`).
233///
234/// This is the metadata shape used by results, content blocks, and catalog
235/// descriptors (tools, prompts, resources, roots, ...). It preserves arbitrary
236/// extension keys and offers helpers for the reserved W3C Trace Context keys
237/// (SEP-414).
238///
239/// Request and notification `_meta` maps have additional reserved keys; see
240/// [`RequestMetaObject`] and [`NotificationMetaObject`].
241#[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    /// Reserved `_meta` key for the W3C Trace Context `traceparent` value (SEP-414).
248    const TRACEPARENT_FIELD: &str = "traceparent";
249    /// Reserved `_meta` key for the W3C Trace Context `tracestate` value (SEP-414).
250    const TRACESTATE_FIELD: &str = "tracestate";
251    /// Reserved `_meta` key for the W3C Baggage value (SEP-414).
252    const BAGGAGE_FIELD: &str = "baggage";
253
254    /// Create an empty metadata map.
255    pub fn new() -> Self {
256        Self(JsonObject::new())
257    }
258
259    /// Read a string-valued `_meta` field, or `None` if absent or not a string.
260    fn get_str(&self, field: &str) -> Option<&str> {
261        self.0.get(field).and_then(Value::as_str)
262    }
263
264    /// Write a string-valued `_meta` field.
265    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    /// Get the W3C `traceparent` value (SEP-414), if present.
271    pub fn get_traceparent(&self) -> Option<&str> {
272        self.get_str(Self::TRACEPARENT_FIELD)
273    }
274
275    /// Set the W3C `traceparent` value (SEP-414).
276    ///
277    /// ```
278    /// use rmcp::model::MetaObject;
279    ///
280    /// let mut meta = MetaObject::new();
281    /// meta.set_traceparent("00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01");
282    /// assert_eq!(
283    ///     meta.get_traceparent(),
284    ///     Some("00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"),
285    /// );
286    /// ```
287    pub fn set_traceparent(&mut self, value: impl Into<String>) {
288        self.set_str(Self::TRACEPARENT_FIELD, value);
289    }
290
291    /// Get the W3C `tracestate` value (SEP-414), if present.
292    pub fn get_tracestate(&self) -> Option<&str> {
293        self.get_str(Self::TRACESTATE_FIELD)
294    }
295
296    /// Set the W3C `tracestate` value (SEP-414).
297    pub fn set_tracestate(&mut self, value: impl Into<String>) {
298        self.set_str(Self::TRACESTATE_FIELD, value);
299    }
300
301    /// Get the W3C `baggage` value (SEP-414), if present.
302    pub fn get_baggage(&self) -> Option<&str> {
303        self.get_str(Self::BAGGAGE_FIELD)
304    }
305
306    /// Set the W3C `baggage` value (SEP-414).
307    pub fn set_baggage(&mut self, value: impl Into<String>) {
308        self.set_str(Self::BAGGAGE_FIELD, value);
309    }
310
311    /// Insert every entry of `other`, overwriting existing keys on conflict.
312    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/// The `_meta` map carried by requests (spec `RequestMetaObject`).
369///
370/// In addition to arbitrary extension keys, requests reserve:
371/// - `progressToken` for progress tracking
372/// - `io.modelcontextprotocol/protocolVersion` (SEP-2575)
373/// - `io.modelcontextprotocol/clientInfo` (SEP-2575)
374/// - `io.modelcontextprotocol/clientCapabilities` (SEP-2575)
375/// - `io.modelcontextprotocol/logLevel` (SEP-2575)
376///
377/// The 2026-07-28 draft schema requires the protocol-version and
378/// client-capabilities keys; client-info is optional. Earlier protocol versions
379/// do not know them. All keys therefore stay optional at runtime and in the
380/// generated (version-shared) JSON schema — use
381/// [`RequestMetaObject::missing_required_keys`] to validate a request against
382/// the negotiated protocol version.
383///
384/// This type dereferences to [`MetaObject`] (and transitively to the underlying
385/// map), so general helpers such as the SEP-414 trace-context accessors remain
386/// available.
387#[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    /// Request `_meta` keys the 2026-07-28 draft schema marks as required.
400    pub const DRAFT_REQUIRED_KEYS: [&str; 2] = [
401        Self::META_KEY_PROTOCOL_VERSION,
402        Self::META_KEY_CLIENT_CAPABILITIES,
403    ];
404
405    /// Create an empty request metadata map.
406    pub fn new() -> Self {
407        Self::default()
408    }
409
410    /// Create a new request meta with a progress token set
411    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    /// Create request metadata with the client context SEP-2575 requires on every request.
418    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    /// Get the progress token carried in `_meta`, if present and valid.
436    pub fn get_progress_token(&self) -> Option<ProgressToken> {
437        self.0.decode_value(Self::PROGRESS_TOKEN_FIELD)
438    }
439
440    /// Set the progress token carried in `_meta`.
441    pub fn set_progress_token(&mut self, token: ProgressToken) {
442        self.0.insert_serialized(Self::PROGRESS_TOKEN_FIELD, token);
443    }
444
445    /// Get the MCP protocol version carried in `_meta`, if present and valid.
446    pub fn protocol_version(&self) -> Option<ProtocolVersion> {
447        self.0.decode_value(Self::META_KEY_PROTOCOL_VERSION)
448    }
449
450    /// Set the MCP protocol version carried in `_meta`.
451    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    /// Get the client implementation identity carried in `_meta`, if present and valid.
459    pub fn client_info(&self) -> Option<Implementation> {
460        self.0.decode_value(Self::META_KEY_CLIENT_INFO)
461    }
462
463    /// Set the client implementation identity carried in `_meta`.
464    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    /// Get the client capabilities carried in `_meta`, if present and valid.
470    pub fn client_capabilities(&self) -> Option<ClientCapabilities> {
471        self.0.decode_value(Self::META_KEY_CLIENT_CAPABILITIES)
472    }
473
474    /// Set the client capabilities carried in `_meta`.
475    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    /// Get the requested per-request log level carried in `_meta`, if present and valid.
481    pub fn log_level(&self) -> Option<LoggingLevel> {
482        self.0.decode_value(Self::META_KEY_LOG_LEVEL)
483    }
484
485    /// Set the requested per-request log level carried in `_meta`.
486    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    /// Return the [`Self::DRAFT_REQUIRED_KEYS`] whose values are absent or
492    /// invalid in this map, if `protocol_version` requires them.
493    ///
494    /// A key counts as missing when it is not present *or* when its value does
495    /// not decode into the expected type (e.g. a numeric `protocolVersion` or
496    /// a string `clientInfo`), matching what the typed accessors return.
497    ///
498    /// Protocol versions before 2026-07-28 have no required request metadata,
499    /// so this always returns an empty list for them.
500    ///
501    /// # Examples
502    ///
503    /// ```
504    /// use rmcp::model::{ProtocolVersion, RequestMetaObject};
505    ///
506    /// let meta = RequestMetaObject::new();
507    /// // Older protocols have no required request metadata.
508    /// assert!(
509    ///     meta.missing_required_keys(&ProtocolVersion::V_2025_11_25)
510    ///         .is_empty()
511    /// );
512    /// // The 2026-07-28 protocol requires per-request context.
513    /// assert_eq!(
514    ///     meta.missing_required_keys(&ProtocolVersion::V_2026_07_28),
515    ///     RequestMetaObject::DRAFT_REQUIRED_KEYS.to_vec(),
516    /// );
517    /// ```
518    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    /// Insert every entry of `other`, overwriting existing keys on conflict.
533    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        // rmcp generates one schema shared by every supported protocol
576        // version, so the keys validated for 2026-07-28 are left
577        // optional here: a 2025-11-25 request whose `_meta` only carries
578        // `progressToken` is valid. Version-specific validation is available at
579        // runtime via [`RequestMetaObject::missing_required_keys`].
580        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/// The `_meta` map carried by notifications (spec `NotificationMetaObject`).
598///
599/// In addition to arbitrary extension keys, notifications reserve
600/// `io.modelcontextprotocol/subscriptionId` to correlate a notification with a
601/// prior subscription request.
602///
603/// This type dereferences to [`MetaObject`] (and transitively to the underlying
604/// map), so general helpers such as the SEP-414 trace-context accessors remain
605/// available.
606#[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    /// Create an empty notification metadata map.
615    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    /// Get the subscription id carried in `_meta`, if present and valid.
625    ///
626    /// # Examples
627    ///
628    /// ```
629    /// use rmcp::model::{NotificationMetaObject, RequestId};
630    ///
631    /// let mut meta = NotificationMetaObject::new();
632    /// assert_eq!(meta.subscription_id(), None);
633    /// meta.set_subscription_id(RequestId::Number(7));
634    /// assert_eq!(meta.subscription_id(), Some(RequestId::Number(7)));
635    /// ```
636    pub fn subscription_id(&self) -> Option<RequestId> {
637        self.0.decode_value(Self::META_KEY_SUBSCRIPTION_ID)
638    }
639
640    /// Set the subscription id carried in `_meta`.
641    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    /// Insert every entry of `other`, overwriting existing keys on conflict.
647    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}