Skip to main content

fastmcp_protocol/
common_types.rs

1//! Final MCP common wire types kept separate while the legacy type surface is migrated.
2//!
3//! This module owns structural admission and byte-preserving serialization for the PRT-02.A
4//! common-type slice.  Scheme-specific fetching, rendering, and authorization policies remain
5//! outside this wire layer.
6
7use std::collections::BTreeMap;
8use std::fmt;
9
10use base64::Engine as _;
11use serde::ser::SerializeMap;
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use serde_json::value::RawValue;
15
16/// A structural rejection at the protocol wire boundary.
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub enum CommonTypeError {
19    /// A value did not have the final-schema shape.
20    Invalid(&'static str),
21    /// A supplied value exceeded a bounded wire limit.
22    TooLong(&'static str),
23}
24
25impl fmt::Display for CommonTypeError {
26    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
27        match self {
28            Self::Invalid(field) => write!(formatter, "invalid {field}"),
29            Self::TooLong(field) => write!(formatter, "{field} exceeds its wire limit"),
30        }
31    }
32}
33
34impl std::error::Error for CommonTypeError {}
35
36/// Maximum encoded bytes admitted for ordinary URI wire fields.
37pub const MAX_ABSOLUTE_URI_BYTES: usize = 64 * 1024;
38/// Maximum bytes in a `data:` icon media-type and parameter prefix, through the comma.
39pub const MAX_ICON_DATA_URI_PREFIX_BYTES: usize = 1024;
40/// Maximum decoded bytes represented by a raw icon `data:` URI.
41pub const MAX_ICON_DATA_URI_DECODED_BYTES: usize = 8 * 1024 * 1024;
42/// Maximum encoded bytes in a raw icon `data:` URI, including its prefix.
43pub const MAX_ICON_DATA_URI_ENCODED_BYTES: usize =
44    4 * MAX_ICON_DATA_URI_DECODED_BYTES.div_ceil(3) + MAX_ICON_DATA_URI_PREFIX_BYTES;
45/// Historical cancellation-reason size used by earlier bounded profiles.
46///
47/// MCP 2024-11-05 and MCP 2026-07-28 do not impose this wire limit. Exact
48/// cancellation decoding therefore does not enforce this value.
49pub const MAX_CANCELLATION_REASON_BYTES: usize = 4 * 1024;
50/// Maximum number of retained open metadata entries.
51pub const MAX_METADATA_ENTRIES: usize = 128;
52/// Maximum UTF-8 bytes in an individual metadata key.
53pub const MAX_METADATA_KEY_BYTES: usize = 512;
54/// Maximum canonical JSON bytes in an individual metadata value.
55pub const MAX_METADATA_VALUE_BYTES: usize = 16 * 1024;
56/// Maximum UTF-8 bytes in a present pagination cursor.
57pub const MAX_CURSOR_BYTES: usize = 4 * 1024;
58/// Maximum icon size strings retained from one peer icon.
59pub const MAX_ICON_SIZE_ENTRIES: usize = 32;
60/// Maximum UTF-8 bytes in an individual peer icon size string.
61pub const MAX_ICON_SIZE_BYTES: usize = 128;
62/// Maximum encoded bytes in one common binary content payload.
63pub const MAX_CONTENT_ENCODED_BYTES: usize = 1024 * 1024;
64/// Maximum UTF-8 bytes in each W3C trace field.
65pub const MAX_TRACE_FIELD_BYTES: usize = 4 * 1024;
66/// Maximum bytes retained for one exact finite final progress number lexeme.
67pub const MAX_EXACT_PROGRESS_NUMBER_BYTES: usize = 256;
68/// Largest absolute decimal exponent accepted for one exact finite final progress number.
69pub const MAX_EXACT_PROGRESS_EXPONENT_ABS: i32 = 9_999;
70/// Maximum bytes retained for one arbitrary-width JSON integer token.
71pub const MAX_JSON_INTEGER_BYTES: usize = 4 * 1024;
72/// Largest absolute decimal exponent admitted for one JSON integer token.
73pub const MAX_JSON_INTEGER_EXPONENT_ABS: i32 = 10_000;
74
75/// A schema-valid RFC 3986 URI with a required ASCII scheme.
76///
77/// This type deliberately preserves the original wire spelling. It does not fetch, normalize,
78/// lowercase, resolve, or otherwise authorize the URI.
79#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
80#[serde(transparent)]
81pub struct AbsoluteUri(String);
82
83impl AbsoluteUri {
84    /// Parses an absolute URI without changing its bytes.
85    pub fn parse(value: impl Into<String>) -> Result<Self, CommonTypeError> {
86        let value = value.into();
87        if value.is_empty() || value.len() > MAX_ABSOLUTE_URI_BYTES {
88            return Err(if value.len() > MAX_ABSOLUTE_URI_BYTES {
89                CommonTypeError::TooLong("URI")
90            } else {
91                CommonTypeError::Invalid("absolute URI")
92            });
93        }
94        if !value.is_ascii() || value.bytes().any(|byte| byte <= 0x20 || byte == 0x7f) {
95            return Err(CommonTypeError::Invalid("absolute URI"));
96        }
97        let Some(colon) = value.find(':') else {
98            return Err(CommonTypeError::Invalid("URI scheme"));
99        };
100        let scheme = &value[..colon];
101        if scheme.is_empty()
102            || !scheme.as_bytes()[0].is_ascii_alphabetic()
103            || !scheme
104                .bytes()
105                .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.'))
106            || !valid_uri_remainder(&value[colon + 1..])
107        {
108            return Err(CommonTypeError::Invalid("absolute URI"));
109        }
110        Ok(Self(value))
111    }
112
113    /// Returns the exact schema-valid wire string.
114    #[must_use]
115    pub fn as_str(&self) -> &str {
116        &self.0
117    }
118
119    /// Returns the original scheme spelling.
120    #[must_use]
121    pub fn scheme(&self) -> &str {
122        &self.0[..self.0.find(':').expect("validated URI has a scheme")]
123    }
124
125    /// Tests a scheme with RFC 3986 ASCII-case-insensitive comparison.
126    #[must_use]
127    pub fn has_scheme(&self, scheme: &str) -> bool {
128        self.scheme().eq_ignore_ascii_case(scheme)
129    }
130}
131
132impl<'de> Deserialize<'de> for AbsoluteUri {
133    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
134    where
135        D: serde::Deserializer<'de>,
136    {
137        String::deserialize(deserializer)
138            .and_then(|value| Self::parse(value).map_err(serde::de::Error::custom))
139    }
140}
141
142/// A raw icon source URI with a dedicated data-image budget.
143///
144/// This is a wire-admission type only: it preserves a schema-valid source exactly and does not
145/// fetch, render, or otherwise grant authority over it.
146#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
147#[serde(transparent)]
148pub struct RawIconSourceUri(String);
149
150impl RawIconSourceUri {
151    /// Parses a raw icon source without normalizing its wire spelling.
152    pub fn parse(value: impl Into<String>) -> Result<Self, CommonTypeError> {
153        let value = value.into();
154        let Some(colon) = value.find(':') else {
155            return Err(CommonTypeError::Invalid("URI scheme"));
156        };
157        let scheme = &value[..colon];
158        let limit = if scheme.eq_ignore_ascii_case("data") {
159            MAX_ICON_DATA_URI_ENCODED_BYTES
160        } else {
161            MAX_ABSOLUTE_URI_BYTES
162        };
163        if value.is_empty() || value.len() > limit {
164            return Err(if value.len() > limit {
165                CommonTypeError::TooLong("icon source URI")
166            } else {
167                CommonTypeError::Invalid("absolute URI")
168            });
169        }
170        if !value.is_ascii()
171            || value.bytes().any(|byte| byte <= 0x20 || byte == 0x7f)
172            || scheme.is_empty()
173            || !scheme.as_bytes()[0].is_ascii_alphabetic()
174            || !scheme
175                .bytes()
176                .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.'))
177            || !valid_uri_remainder(&value[colon + 1..])
178        {
179            return Err(CommonTypeError::Invalid("absolute URI"));
180        }
181        if scheme.eq_ignore_ascii_case("data") {
182            let before_fragment = value
183                .split_once('#')
184                .map_or(value.as_str(), |(before_fragment, _)| before_fragment);
185            let structural_data_uri = before_fragment
186                .split_once('?')
187                .map_or(before_fragment, |(before_query, _)| before_query);
188            validate_icon_data_uri(structural_data_uri)?;
189        }
190        Ok(Self(value))
191    }
192
193    /// Returns the exact schema-valid source spelling.
194    #[must_use]
195    pub fn as_str(&self) -> &str {
196        &self.0
197    }
198}
199
200impl<'de> Deserialize<'de> for RawIconSourceUri {
201    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202    where
203        D: serde::Deserializer<'de>,
204    {
205        String::deserialize(deserializer)
206            .and_then(|value| Self::parse(value).map_err(serde::de::Error::custom))
207    }
208}
209
210fn validate_icon_data_uri(value: &str) -> Result<(), CommonTypeError> {
211    let data = &value["data:".len()..];
212    let Some((prefix, payload)) = data.split_once(',') else {
213        return Err(CommonTypeError::Invalid("icon data URI"));
214    };
215    if prefix.len() + 1 > MAX_ICON_DATA_URI_PREFIX_BYTES {
216        return Err(CommonTypeError::TooLong("icon data URI prefix"));
217    }
218    let Some(media_type) = prefix.strip_suffix(";base64") else {
219        return Err(CommonTypeError::Invalid("icon data URI"));
220    };
221    if !media_type
222        .split_once('/')
223        .is_some_and(|(kind, _)| kind.eq_ignore_ascii_case("image"))
224        || !valid_mime_type(media_type)
225    {
226        return Err(CommonTypeError::Invalid("icon data MIME type"));
227    }
228    let decoded_upper_bound = base64_decoded_upper_bound(payload)?;
229    if decoded_upper_bound > MAX_ICON_DATA_URI_DECODED_BYTES {
230        return Err(CommonTypeError::TooLong("icon data URI"));
231    }
232    validate_standard_base64(payload)
233}
234
235fn base64_decoded_upper_bound(value: &str) -> Result<usize, CommonTypeError> {
236    let unpadded = value.trim_end_matches('=');
237    if unpadded.len() % 4 == 1 || value[..unpadded.len()].contains('=') {
238        return Err(CommonTypeError::Invalid("base64 content"));
239    }
240    let groups = unpadded.len() / 4;
241    let remainder = unpadded.len() % 4;
242    groups
243        .checked_mul(3)
244        .and_then(|size| size.checked_add(if remainder == 0 { 0 } else { remainder - 1 }))
245        .ok_or(CommonTypeError::TooLong("base64 content"))
246}
247
248fn valid_uri_remainder(value: &str) -> bool {
249    let (before_fragment, fragment) = match value.split_once('#') {
250        Some((before_fragment, fragment)) if !fragment.contains('#') => {
251            (before_fragment, Some(fragment))
252        }
253        Some(_) => return false,
254        None => (value, None),
255    };
256    let (hier_part, query) = match before_fragment.split_once('?') {
257        Some((hier_part, query)) if !query.contains('?') => (hier_part, Some(query)),
258        Some((hier_part, query)) => (hier_part, Some(query)),
259        None => (before_fragment, None),
260    };
261    valid_hier_part(hier_part)
262        && query.is_none_or(valid_query_or_fragment)
263        && fragment.is_none_or(valid_query_or_fragment)
264}
265
266fn valid_hier_part(value: &str) -> bool {
267    if let Some(authority_and_path) = value.strip_prefix("//") {
268        let (authority, path) = authority_and_path
269            .split_once('/')
270            .map_or((authority_and_path, ""), |(authority, suffix)| {
271                (authority, suffix)
272            });
273        valid_authority(authority) && valid_path(path)
274    } else {
275        valid_path(value)
276    }
277}
278
279fn valid_authority(value: &str) -> bool {
280    let (userinfo, host_and_port) = match value.rsplit_once('@') {
281        Some((userinfo, host_and_port)) if !userinfo.contains('@') && valid_userinfo(userinfo) => {
282            (Some(userinfo), host_and_port)
283        }
284        Some(_) => return false,
285        None => (None, value),
286    };
287    let _ = userinfo;
288    if let Some(host) = host_and_port.strip_prefix('[') {
289        let Some((literal, port)) = host.split_once(']') else {
290            return false;
291        };
292        if port.contains(']') || !valid_port(port) {
293            return false;
294        }
295        return valid_ip_literal(literal);
296    }
297    if host_and_port.contains('[') || host_and_port.contains(']') {
298        return false;
299    }
300    let (host, port) = host_and_port
301        .rsplit_once(':')
302        .map_or((host_and_port, None), |(host, port)| (host, Some(port)));
303    valid_reg_name(host) && port.is_none_or(|port| port.bytes().all(|byte| byte.is_ascii_digit()))
304}
305
306fn valid_ip_literal(value: &str) -> bool {
307    value.parse::<std::net::Ipv6Addr>().is_ok()
308        || value
309            .strip_prefix('v')
310            .or_else(|| value.strip_prefix('V'))
311            .is_some_and(|future| {
312                let Some((version, address)) = future.split_once('.') else {
313                    return false;
314                };
315                !version.is_empty()
316                    && version.bytes().all(|byte| byte.is_ascii_hexdigit())
317                    && !address.is_empty()
318                    && address.bytes().all(is_ip_future_character)
319            })
320}
321
322fn is_ip_future_character(byte: u8) -> bool {
323    is_unreserved(byte) || is_sub_delim(byte) || byte == b':'
324}
325
326fn valid_port(value: &str) -> bool {
327    value.is_empty()
328        || (value.starts_with(':') && value[1..].bytes().all(|byte| byte.is_ascii_digit()))
329}
330
331fn valid_userinfo(value: &str) -> bool {
332    valid_component(value, |byte| is_pchar(byte) || byte == b':')
333}
334
335fn valid_reg_name(value: &str) -> bool {
336    valid_component(value, |byte| is_unreserved(byte) || is_sub_delim(byte))
337}
338
339fn valid_path(value: &str) -> bool {
340    valid_component(value, |byte| is_pchar(byte) || byte == b'/')
341}
342
343fn valid_query_or_fragment(value: &str) -> bool {
344    valid_component(value, |byte| is_pchar(byte) || matches!(byte, b'/' | b'?'))
345}
346
347fn valid_component(value: &str, permits: impl Fn(u8) -> bool) -> bool {
348    let bytes = value.as_bytes();
349    let mut index = 0;
350    while index < bytes.len() {
351        if bytes[index] == b'%' {
352            if index + 2 >= bytes.len()
353                || !bytes[index + 1].is_ascii_hexdigit()
354                || !bytes[index + 2].is_ascii_hexdigit()
355            {
356                return false;
357            }
358            index += 3;
359        } else if permits(bytes[index]) {
360            index += 1;
361        } else {
362            return false;
363        }
364    }
365    true
366}
367
368fn is_pchar(byte: u8) -> bool {
369    is_unreserved(byte) || is_sub_delim(byte) || matches!(byte, b':' | b'@')
370}
371
372fn is_unreserved(byte: u8) -> bool {
373    byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~')
374}
375
376fn is_sub_delim(byte: u8) -> bool {
377    matches!(
378        byte,
379        b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
380    )
381}
382
383/// An opaque cursor where only absence means the end of a paginated result set.
384#[derive(Clone, Debug, Eq, PartialEq)]
385pub enum OpaqueCursor {
386    /// The wire field was absent.
387    Absent,
388    /// The wire field was present, including the valid empty string.
389    Present(String),
390}
391
392impl Serialize for OpaqueCursor {
393    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
394    where
395        S: serde::Serializer,
396    {
397        match self {
398            // A standalone value cannot represent an absent object member. Enclosing wire
399            // objects must omit an absent cursor; serializing it as `null` would conflate two
400            // distinct wire states.
401            Self::Absent => Err(serde::ser::Error::custom(
402                "an absent cursor must be omitted from its enclosing object",
403            )),
404            Self::Present(value) => serializer.serialize_str(value),
405        }
406    }
407}
408
409impl<'de> Deserialize<'de> for OpaqueCursor {
410    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
411    where
412        D: serde::Deserializer<'de>,
413    {
414        String::deserialize(deserializer).and_then(|value| {
415            Self::try_from_presence(Some(value)).map_err(serde::de::Error::custom)
416        })
417    }
418}
419
420impl OpaqueCursor {
421    /// Preserves absent, empty, and nonempty cursor states distinctly.
422    #[must_use]
423    pub fn from_presence(value: Option<String>) -> Self {
424        value.map_or(Self::Absent, Self::Present)
425    }
426
427    /// Admits a bounded cursor while preserving absent and empty states.
428    pub fn try_from_presence(value: Option<String>) -> Result<Self, CommonTypeError> {
429        if value
430            .as_ref()
431            .is_some_and(|cursor| cursor.len() > MAX_CURSOR_BYTES)
432        {
433            return Err(CommonTypeError::TooLong("pagination cursor"));
434        }
435        Ok(Self::from_presence(value))
436    }
437
438    /// Returns the present value, if the field occurred on the wire.
439    #[must_use]
440    pub fn as_present(&self) -> Option<&str> {
441        match self {
442            Self::Absent => None,
443            Self::Present(value) => Some(value),
444        }
445    }
446}
447
448/// A JSON integer retained without an implementation-width bound.
449///
450/// The final schema uses JSON Schema's `integer` type, which is not limited to
451/// Rust's fixed-width integer types. The workspace enables serde_json's
452/// arbitrary-precision feature, so retaining the original [`serde_json::Number`]
453/// preserves both large positive and negative integer spellings on re-encode.
454#[derive(Clone, Debug, Eq, PartialEq)]
455pub struct JsonInteger(serde_json::Number);
456
457impl JsonInteger {
458    /// Admits one JSON number only when it is a mathematical integer.
459    pub fn try_from_number(value: serde_json::Number) -> Result<Self, CommonTypeError> {
460        validate_json_integer(value.as_str())?;
461        Ok(Self(value))
462    }
463
464    /// Returns the exact retained JSON integer spelling.
465    #[must_use]
466    pub fn as_str(&self) -> &str {
467        self.0.as_str()
468    }
469
470    /// Returns the retained number for direct `Value::Number` construction.
471    ///
472    /// Embedding a `JsonInteger` through `json!`/`to_value` re-parses the
473    /// number and canonicalizes its spelling; building the `Value` from this
474    /// accessor keeps the retained lexeme.
475    #[must_use]
476    pub fn to_number(&self) -> serde_json::Number {
477        self.0.clone()
478    }
479
480    /// Returns the mathematical value when it fits the legacy signed 32-bit
481    /// error-code domain.
482    ///
483    /// Integral JSON spellings with a fractional part or exponent, such as
484    /// `-32600.0` and `-326e2`, are accepted without changing the retained
485    /// wire lexeme.
486    #[must_use]
487    pub fn as_i32(&self) -> Option<i32> {
488        json_integer_as_i32(self.as_str())
489    }
490}
491
492impl std::str::FromStr for JsonInteger {
493    type Err = CommonTypeError;
494
495    /// Parses one JSON integer token without normalizing its spelling.
496    fn from_str(value: &str) -> Result<Self, Self::Err> {
497        validate_json_integer(value)?;
498
499        // `validate_json_integer` verifies JSON-number grammar, bounds, and mathematical
500        // integrality before retaining the caller's exact token. serde_json's
501        // parser inserts `+` into a positive exponent, so parsing first would
502        // lose a valid lexeme such as `-326e2`.
503        Ok(Self(serde_json::Number::from_string_unchecked(
504            value.to_owned(),
505        )))
506    }
507}
508
509impl TryFrom<&str> for JsonInteger {
510    type Error = CommonTypeError;
511
512    fn try_from(value: &str) -> Result<Self, Self::Error> {
513        value.parse()
514    }
515}
516
517impl fmt::Display for JsonInteger {
518    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
519        formatter.write_str(self.as_str())
520    }
521}
522
523impl From<i32> for JsonInteger {
524    fn from(value: i32) -> Self {
525        Self(serde_json::Number::from(value))
526    }
527}
528
529impl From<fastmcp_core::McpErrorCode> for JsonInteger {
530    fn from(value: fastmcp_core::McpErrorCode) -> Self {
531        Self::from(i32::from(value))
532    }
533}
534
535impl From<i64> for JsonInteger {
536    fn from(value: i64) -> Self {
537        Self(serde_json::Number::from(value))
538    }
539}
540
541impl From<u64> for JsonInteger {
542    fn from(value: u64) -> Self {
543        Self(serde_json::Number::from(value))
544    }
545}
546
547impl Serialize for JsonInteger {
548    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
549    where
550        S: serde::Serializer,
551    {
552        self.0.serialize(serializer)
553    }
554}
555
556/// serde_json's private raw-value newtype token (stable: serde_json is
557/// pinned exactly).
558const SERDE_JSON_RAW_VALUE_TOKEN: &str = "$serde_json::private::RawValue";
559/// serde_json's private arbitrary-precision number map key (stable: pinned).
560const SERDE_JSON_NUMBER_TOKEN: &str = "$serde_json::private::Number";
561
562impl<'de> Deserialize<'de> for JsonInteger {
563    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
564    where
565        D: serde::Deserializer<'de>,
566    {
567        // A plain Box<RawValue> decode preserves the wire lexeme but cannot
568        // survive serde's buffered Content replay, which every untagged
569        // context uses - most critically the JsonRpcMessage enum, where it
570        // made this crate unable to parse error responses it had itself
571        // encoded. Requesting the raw-value newtype directly and accepting
572        // BOTH magic map spellings keeps the exact lexeme on the direct
573        // parser path while still decoding through buffered replay (whose
574        // parser-canonicalized number spelling is the best available there).
575        struct JsonIntegerVisitor;
576
577        impl<'de> serde::de::Visitor<'de> for JsonIntegerVisitor {
578            type Value = JsonInteger;
579
580            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
581                formatter.write_str("a mathematically integral JSON number")
582            }
583
584            fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<JsonInteger, E> {
585                Ok(JsonInteger::from(value))
586            }
587
588            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<JsonInteger, E> {
589                Ok(JsonInteger::from(value))
590            }
591
592            fn visit_i128<E: serde::de::Error>(self, value: i128) -> Result<JsonInteger, E> {
593                value.to_string().parse().map_err(E::custom)
594            }
595
596            fn visit_u128<E: serde::de::Error>(self, value: u128) -> Result<JsonInteger, E> {
597                value.to_string().parse().map_err(E::custom)
598            }
599
600            fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<JsonInteger, E> {
601                serde_json::Number::from_f64(value)
602                    .ok_or_else(|| E::custom("JSON integer must be finite"))
603                    .and_then(|number| JsonInteger::try_from_number(number).map_err(E::custom))
604            }
605
606            fn visit_newtype_struct<D2>(self, deserializer: D2) -> Result<JsonInteger, D2::Error>
607            where
608                D2: serde::Deserializer<'de>,
609            {
610                deserializer.deserialize_any(self)
611            }
612
613            fn visit_map<A>(self, mut map: A) -> Result<JsonInteger, A::Error>
614            where
615                A: serde::de::MapAccess<'de>,
616            {
617                let Some(key) = map.next_key::<std::borrow::Cow<'_, str>>()? else {
618                    return Err(serde::de::Error::custom("JSON integer cannot be an object"));
619                };
620                if key != SERDE_JSON_RAW_VALUE_TOKEN && key != SERDE_JSON_NUMBER_TOKEN {
621                    return Err(serde::de::Error::custom("JSON integer cannot be an object"));
622                }
623                let lexeme = map.next_value::<std::borrow::Cow<'_, str>>()?;
624                lexeme.parse().map_err(serde::de::Error::custom)
625            }
626        }
627
628        deserializer.deserialize_newtype_struct(SERDE_JSON_RAW_VALUE_TOKEN, JsonIntegerVisitor)
629    }
630}
631
632/// A bounded, exact, finite JSON number used by final progress notifications.
633///
634/// The original JSON-number lexeme is retained for wire re-encoding. Ordering
635/// is computed from bounded decimal components, never through IEEE-754.
636#[derive(Clone, Debug)]
637pub struct ExactNonNegativeJsonNumber {
638    raw: Box<RawValue>,
639    negative: bool,
640    significant_digits: String,
641    decimal_point: i32,
642}
643
644impl ExactNonNegativeJsonNumber {
645    /// Admits one bounded finite JSON number while retaining its spelling.
646    pub fn try_from_number(number: serde_json::Number) -> Result<Self, CommonTypeError> {
647        Self::parse(number.as_str())
648    }
649
650    /// Parses one exact progress number from its JSON-number lexeme.
651    pub fn parse(lexeme: &str) -> Result<Self, CommonTypeError> {
652        let raw = RawValue::from_string(lexeme.to_owned())
653            .map_err(|_| CommonTypeError::Invalid("JSON progress number"))?;
654        Self::from_raw(raw)
655    }
656
657    fn from_raw(raw: Box<RawValue>) -> Result<Self, CommonTypeError> {
658        let lexeme = raw.get();
659        if lexeme.len() > MAX_EXACT_PROGRESS_NUMBER_BYTES {
660            return Err(CommonTypeError::TooLong("exact progress number"));
661        }
662        let (negative, unsigned_lexeme) = match lexeme.strip_prefix('-') {
663            Some(unsigned_lexeme) => (true, unsigned_lexeme),
664            None => (false, lexeme),
665        };
666
667        let (mantissa, exponent) =
668            match unsigned_lexeme.find(|character| matches!(character, 'e' | 'E')) {
669                Some(index) => (
670                    &unsigned_lexeme[..index],
671                    parse_bounded_progress_exponent(&unsigned_lexeme[index + 1..])?,
672                ),
673                None => (unsigned_lexeme, 0),
674            };
675        let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
676        if whole.is_empty()
677            || !whole.bytes().all(|byte| byte.is_ascii_digit())
678            || !fraction.bytes().all(|byte| byte.is_ascii_digit())
679        {
680            return Err(CommonTypeError::Invalid("JSON progress number"));
681        }
682        let digits = [whole, fraction].concat();
683        let first_significant = digits
684            .bytes()
685            .position(|byte| byte != b'0')
686            .unwrap_or(digits.len());
687        let significant_digits = if first_significant == digits.len() {
688            "0".to_owned()
689        } else {
690            digits[first_significant..].to_owned()
691        };
692        let decimal_point = i32::try_from(whole.len())
693            .map_err(|_| CommonTypeError::TooLong("exact progress number"))?
694            .checked_sub(
695                i32::try_from(first_significant)
696                    .map_err(|_| CommonTypeError::TooLong("exact progress number"))?,
697            )
698            .and_then(|point| point.checked_add(exponent))
699            .ok_or(CommonTypeError::TooLong("exact progress number"))?;
700
701        Ok(Self {
702            raw,
703            negative,
704            significant_digits,
705            decimal_point,
706        })
707    }
708
709    /// Returns the exact JSON-number spelling retained from the wire.
710    #[must_use]
711    pub fn as_str(&self) -> &str {
712        self.raw.get()
713    }
714
715    fn is_zero(&self) -> bool {
716        self.significant_digits == "0"
717    }
718
719    fn cmp_magnitude(&self, other: &Self) -> std::cmp::Ordering {
720        match self.decimal_point.cmp(&other.decimal_point) {
721            std::cmp::Ordering::Equal => {}
722            order => return order,
723        }
724        for index in 0..self
725            .significant_digits
726            .len()
727            .max(other.significant_digits.len())
728        {
729            match self
730                .significant_digits
731                .as_bytes()
732                .get(index)
733                .copied()
734                .unwrap_or(b'0')
735                .cmp(
736                    &other
737                        .significant_digits
738                        .as_bytes()
739                        .get(index)
740                        .copied()
741                        .unwrap_or(b'0'),
742                ) {
743                std::cmp::Ordering::Equal => {}
744                order => return order,
745            }
746        }
747        std::cmp::Ordering::Equal
748    }
749}
750
751impl PartialEq for ExactNonNegativeJsonNumber {
752    fn eq(&self, other: &Self) -> bool {
753        self.cmp(other).is_eq()
754    }
755}
756
757impl Eq for ExactNonNegativeJsonNumber {}
758
759impl PartialOrd for ExactNonNegativeJsonNumber {
760    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
761        Some(self.cmp(other))
762    }
763}
764
765impl Ord for ExactNonNegativeJsonNumber {
766    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
767        match (self.is_zero(), other.is_zero()) {
768            (true, true) => return std::cmp::Ordering::Equal,
769            (true, false) => return std::cmp::Ordering::Less,
770            (false, true) => return std::cmp::Ordering::Greater,
771            (false, false) => {}
772        }
773        match (self.negative, other.negative) {
774            (true, false) => std::cmp::Ordering::Less,
775            (false, true) => std::cmp::Ordering::Greater,
776            (true, true) => other.cmp_magnitude(self),
777            (false, false) => self.cmp_magnitude(other),
778        }
779    }
780}
781
782impl Serialize for ExactNonNegativeJsonNumber {
783    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
784    where
785        S: serde::Serializer,
786    {
787        self.raw.serialize(serializer)
788    }
789}
790
791impl<'de> Deserialize<'de> for ExactNonNegativeJsonNumber {
792    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
793    where
794        D: serde::Deserializer<'de>,
795    {
796        struct ExactJsonNumberVisitor;
797
798        impl<'de> serde::de::Visitor<'de> for ExactJsonNumberVisitor {
799            type Value = ExactNonNegativeJsonNumber;
800
801            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
802                formatter.write_str("a bounded finite JSON progress number")
803            }
804
805            fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Self::Value, E> {
806                ExactNonNegativeJsonNumber::parse(&value.to_string()).map_err(E::custom)
807            }
808
809            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
810                ExactNonNegativeJsonNumber::parse(&value.to_string()).map_err(E::custom)
811            }
812
813            fn visit_i128<E: serde::de::Error>(self, value: i128) -> Result<Self::Value, E> {
814                ExactNonNegativeJsonNumber::parse(&value.to_string()).map_err(E::custom)
815            }
816
817            fn visit_u128<E: serde::de::Error>(self, value: u128) -> Result<Self::Value, E> {
818                ExactNonNegativeJsonNumber::parse(&value.to_string()).map_err(E::custom)
819            }
820
821            fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
822                serde_json::Number::from_f64(value)
823                    .ok_or_else(|| E::custom("JSON progress number must be finite"))
824                    .and_then(|number| {
825                        ExactNonNegativeJsonNumber::try_from_number(number).map_err(E::custom)
826                    })
827            }
828
829            fn visit_newtype_struct<D2>(self, deserializer: D2) -> Result<Self::Value, D2::Error>
830            where
831                D2: serde::Deserializer<'de>,
832            {
833                deserializer.deserialize_any(self)
834            }
835
836            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
837            where
838                A: serde::de::MapAccess<'de>,
839            {
840                let Some(key) = map.next_key::<std::borrow::Cow<'_, str>>()? else {
841                    return Err(serde::de::Error::custom(
842                        "JSON progress number cannot be an object",
843                    ));
844                };
845                if key != SERDE_JSON_RAW_VALUE_TOKEN && key != SERDE_JSON_NUMBER_TOKEN {
846                    return Err(serde::de::Error::custom(
847                        "JSON progress number cannot be an object",
848                    ));
849                }
850                let lexeme = map.next_value::<std::borrow::Cow<'_, str>>()?;
851                ExactNonNegativeJsonNumber::parse(&lexeme).map_err(serde::de::Error::custom)
852            }
853        }
854
855        deserializer.deserialize_newtype_struct(SERDE_JSON_RAW_VALUE_TOKEN, ExactJsonNumberVisitor)
856    }
857}
858
859fn parse_bounded_progress_exponent(value: &str) -> Result<i32, CommonTypeError> {
860    let (negative, digits) = match value.strip_prefix('-') {
861        Some(digits) => (true, digits),
862        None => (false, value.strip_prefix('+').unwrap_or(value)),
863    };
864    if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
865        return Err(CommonTypeError::Invalid("JSON progress number"));
866    }
867    let magnitude = digits.bytes().try_fold(0_i32, |value, byte| {
868        value
869            .checked_mul(10)
870            .and_then(|value| value.checked_add(i32::from(byte - b'0')))
871    });
872    let Some(magnitude) = magnitude else {
873        return Err(CommonTypeError::TooLong("progress number exponent"));
874    };
875    if magnitude > MAX_EXACT_PROGRESS_EXPONENT_ABS {
876        return Err(CommonTypeError::TooLong("progress number exponent"));
877    }
878    Ok(if negative { -magnitude } else { magnitude })
879}
880
881fn validate_json_integer(value: &str) -> Result<(), CommonTypeError> {
882    if value.len() > MAX_JSON_INTEGER_BYTES {
883        return Err(CommonTypeError::TooLong("JSON integer"));
884    }
885    let (mantissa, exponent) = match value.find(|character| matches!(character, 'e' | 'E')) {
886        Some(index) => (
887            &value[..index],
888            parse_bounded_json_integer_exponent(&value[index + 1..])?,
889        ),
890        None => (value, 0),
891    };
892    let mantissa = mantissa.strip_prefix('-').unwrap_or(mantissa);
893    let (whole, fraction) = match mantissa.split_once('.') {
894        Some((whole, fraction)) => (whole, Some(fraction)),
895        None => (mantissa, None),
896    };
897    if whole.is_empty()
898        || !(whole == "0"
899            || (whole.as_bytes()[0].is_ascii_digit()
900                && whole.as_bytes()[0] != b'0'
901                && whole.bytes().all(|byte| byte.is_ascii_digit())))
902        || fraction.is_some_and(|fraction| {
903            fraction.is_empty() || !fraction.bytes().all(|byte| byte.is_ascii_digit())
904        })
905    {
906        return Err(CommonTypeError::Invalid("JSON integer"));
907    }
908    let fraction = fraction.unwrap_or("");
909    let digits = [whole, fraction].concat();
910    if digits.bytes().all(|byte| byte == b'0') {
911        return Ok(());
912    }
913    let scale = (fraction.len() as isize)
914        .checked_sub(exponent)
915        .ok_or(CommonTypeError::TooLong("JSON integer exponent"))?;
916    if scale <= 0
917        || digits
918            .bytes()
919            .rev()
920            .take_while(|byte| *byte == b'0')
921            .count()
922            >= usize::try_from(scale).unwrap_or(usize::MAX)
923    {
924        Ok(())
925    } else {
926        Err(CommonTypeError::Invalid("JSON integer"))
927    }
928}
929
930fn json_integer_as_i32(value: &str) -> Option<i32> {
931    let (mantissa, exponent) = match value.find(|character| matches!(character, 'e' | 'E')) {
932        Some(index) => (
933            &value[..index],
934            parse_bounded_json_integer_exponent(&value[index + 1..]).ok()?,
935        ),
936        None => (value, 0),
937    };
938    let (negative, mantissa) = match mantissa.strip_prefix('-') {
939        Some(mantissa) => (true, mantissa),
940        None => (false, mantissa),
941    };
942    let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
943    if whole
944        .bytes()
945        .chain(fraction.bytes())
946        .all(|digit| digit == b'0')
947    {
948        return Some(0);
949    }
950    let scale = (fraction.len() as isize).checked_sub(exponent)?;
951    let source_length = whole.len().checked_add(fraction.len())?;
952    let retained_source_length = if scale.is_positive() {
953        source_length.saturating_sub(usize::try_from(scale).ok()?)
954    } else {
955        source_length
956    };
957    let appended_zeroes = if scale.is_negative() {
958        scale.unsigned_abs()
959    } else {
960        0
961    };
962    let maximum = if negative {
963        i64::from(i32::MAX) + 1
964    } else {
965        i64::from(i32::MAX)
966    };
967
968    let mut magnitude = 0_i64;
969    let mut saw_nonzero = false;
970    for digit in whole
971        .bytes()
972        .chain(fraction.bytes())
973        .take(retained_source_length)
974    {
975        if !saw_nonzero && digit == b'0' {
976            continue;
977        }
978        saw_nonzero = true;
979        magnitude = magnitude
980            .checked_mul(10)?
981            .checked_add(i64::from(digit - b'0'))?;
982        if magnitude > maximum {
983            return None;
984        }
985    }
986
987    if !saw_nonzero {
988        return Some(0);
989    }
990    if appended_zeroes >= 10 {
991        return None;
992    }
993    for _ in 0..appended_zeroes {
994        magnitude = magnitude.checked_mul(10)?;
995        if magnitude > maximum {
996            return None;
997        }
998    }
999
1000    if negative {
1001        if magnitude == i64::from(i32::MAX) + 1 {
1002            Some(i32::MIN)
1003        } else {
1004            Some(-(magnitude as i32))
1005        }
1006    } else {
1007        Some(magnitude as i32)
1008    }
1009}
1010
1011fn parse_bounded_json_integer_exponent(value: &str) -> Result<isize, CommonTypeError> {
1012    let (negative, digits) = match value.strip_prefix('-') {
1013        Some(digits) => (true, digits),
1014        None => (false, value.strip_prefix('+').unwrap_or(value)),
1015    };
1016    if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
1017        return Err(CommonTypeError::Invalid("JSON integer"));
1018    }
1019    let magnitude = digits.bytes().try_fold(0_i32, |value, byte| {
1020        value
1021            .checked_mul(10)
1022            .and_then(|value| value.checked_add(i32::from(byte - b'0')))
1023    });
1024    let Some(magnitude) = magnitude else {
1025        return Err(CommonTypeError::TooLong("JSON integer exponent"));
1026    };
1027    if magnitude > MAX_JSON_INTEGER_EXPONENT_ABS {
1028        return Err(CommonTypeError::TooLong("JSON integer exponent"));
1029    }
1030    let exponent = isize::try_from(magnitude)
1031        .map_err(|_| CommonTypeError::TooLong("JSON integer exponent"))?;
1032    Ok(if negative { -exponent } else { exponent })
1033}
1034
1035/// Final implementation identity.
1036#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1037#[serde(rename_all = "camelCase")]
1038pub struct Implementation {
1039    /// Programmatic implementation name.
1040    pub name: String,
1041    /// Implementation version.
1042    pub version: String,
1043    /// Optional display title. An empty present title remains present.
1044    #[serde(skip_serializing_if = "Option::is_none")]
1045    pub title: Option<String>,
1046    /// Optional human-readable description.
1047    #[serde(skip_serializing_if = "Option::is_none")]
1048    pub description: Option<String>,
1049    /// Optional, untrusted website identity.
1050    #[serde(skip_serializing_if = "Option::is_none")]
1051    pub website_url: Option<AbsoluteUri>,
1052    /// Optional wire-preserving icon set.
1053    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1054    pub icons: Vec<RawIcon>,
1055    /// Schema-allowed members retained without assigning them protocol meaning.
1056    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
1057    pub additional: BTreeMap<String, Value>,
1058}
1059
1060/// Final MCP logging severities, aligned to RFC 5424 names.
1061#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
1062#[serde(rename_all = "lowercase")]
1063pub enum LoggingLevel {
1064    /// Debug diagnostic information.
1065    Debug,
1066    /// Informational event.
1067    Info,
1068    /// Significant normal event.
1069    Notice,
1070    /// Warning event.
1071    Warning,
1072    /// Error event.
1073    Error,
1074    /// Critical event.
1075    Critical,
1076    /// Alert event.
1077    Alert,
1078    /// Emergency event.
1079    Emergency,
1080}
1081
1082impl Implementation {
1083    /// Constructs an implementation identity with required nonempty fields.
1084    pub fn try_new(
1085        name: impl Into<String>,
1086        version: impl Into<String>,
1087    ) -> Result<Self, CommonTypeError> {
1088        let name = name.into();
1089        let version = version.into();
1090        if name.is_empty() || version.is_empty() {
1091            return Err(CommonTypeError::Invalid("implementation identity"));
1092        }
1093        Ok(Self {
1094            name,
1095            version,
1096            title: None,
1097            description: None,
1098            website_url: None,
1099            icons: Vec::new(),
1100            additional: BTreeMap::new(),
1101        })
1102    }
1103
1104    /// Returns the effective display name without synthesizing a wire title.
1105    #[must_use]
1106    pub fn display_name(&self) -> &str {
1107        self.title.as_deref().unwrap_or(&self.name)
1108    }
1109}
1110
1111#[derive(Deserialize)]
1112#[serde(rename_all = "camelCase")]
1113struct ImplementationWire {
1114    name: String,
1115    version: String,
1116    #[serde(default)]
1117    title: Option<String>,
1118    #[serde(default)]
1119    description: Option<String>,
1120    #[serde(default)]
1121    website_url: Option<AbsoluteUri>,
1122    #[serde(default)]
1123    icons: Vec<RawIcon>,
1124    #[serde(flatten, default)]
1125    additional: BTreeMap<String, Value>,
1126}
1127
1128impl<'de> Deserialize<'de> for Implementation {
1129    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1130    where
1131        D: serde::Deserializer<'de>,
1132    {
1133        let value = Value::deserialize(deserializer)?;
1134        reject_explicit_null_fields(&value, &["title", "description", "websiteUrl", "icons"])
1135            .map_err(serde::de::Error::custom)?;
1136        let wire: ImplementationWire =
1137            serde_json::from_value(value).map_err(serde::de::Error::custom)?;
1138        let mut implementation =
1139            Self::try_new(wire.name, wire.version).map_err(serde::de::Error::custom)?;
1140        implementation.title = wire.title;
1141        implementation.description = wire.description;
1142        implementation.website_url = wire.website_url;
1143        implementation.icons = wire.icons;
1144        implementation.additional = wire.additional;
1145        Ok(implementation)
1146    }
1147}
1148
1149/// Open `_meta` values with typed access to final reserved keys.
1150#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
1151#[serde(transparent)]
1152pub struct OpenMetadata(BTreeMap<String, Value>);
1153
1154impl OpenMetadata {
1155    /// Validates every key and preserves valid unknown peer entries exactly.
1156    pub fn try_from_entries(
1157        entries: impl IntoIterator<Item = (String, Value)>,
1158    ) -> Result<Self, CommonTypeError> {
1159        let metadata = Self::try_from_open_entries(entries, valid_metadata_key)?;
1160        metadata.validate_reserved_values()?;
1161        Ok(metadata)
1162    }
1163
1164    /// Validates notification-role metadata without assigning request/result
1165    /// semantics to otherwise schema-open reserved keys.
1166    ///
1167    /// The final schema gives only `subscriptionId` a typed meaning in
1168    /// notification metadata. Every other syntactically valid key/value is
1169    /// retained exactly and remains inert.
1170    pub fn try_from_notification_entries(
1171        entries: impl IntoIterator<Item = (String, Value)>,
1172    ) -> Result<Self, CommonTypeError> {
1173        let metadata = Self::try_from_open_entries(entries, valid_open_metadata_key)?;
1174        metadata.validate_notification_values()?;
1175        Ok(metadata)
1176    }
1177
1178    fn try_from_open_entries(
1179        entries: impl IntoIterator<Item = (String, Value)>,
1180        valid_key: fn(&str) -> bool,
1181    ) -> Result<Self, CommonTypeError> {
1182        let mut values = BTreeMap::new();
1183        for (key, value) in entries {
1184            let value_bytes = serde_json::to_vec(&value)
1185                .map_err(|_| CommonTypeError::Invalid("metadata value"))?
1186                .len();
1187            if values.len() == MAX_METADATA_ENTRIES
1188                || key.len() > MAX_METADATA_KEY_BYTES
1189                || value_bytes > MAX_METADATA_VALUE_BYTES
1190                || !valid_key(&key)
1191                || values.insert(key, value).is_some()
1192            {
1193                return Err(CommonTypeError::Invalid("metadata key"));
1194            }
1195        }
1196        Ok(Self(values))
1197    }
1198
1199    /// Returns the exact retained entry for a valid unknown key.
1200    #[must_use]
1201    pub fn get(&self, key: &str) -> Option<&Value> {
1202        self.0.get(key)
1203    }
1204
1205    /// Returns all retained entries without granting them authority.
1206    #[must_use]
1207    pub fn entries(&self) -> &BTreeMap<String, Value> {
1208        &self.0
1209    }
1210
1211    /// Reads the typed protocol version, if this metadata role declares it.
1212    pub fn protocol_version(&self) -> Result<Option<&str>, CommonTypeError> {
1213        self.optional_string("io.modelcontextprotocol/protocolVersion")
1214    }
1215
1216    /// Reads typed client capabilities, if present.
1217    pub fn client_capabilities(
1218        &self,
1219    ) -> Result<Option<&serde_json::Map<String, Value>>, CommonTypeError> {
1220        match self.0.get("io.modelcontextprotocol/clientCapabilities") {
1221            None => Ok(None),
1222            Some(Value::Object(value)) => Ok(Some(value)),
1223            Some(_) => Err(CommonTypeError::Invalid("client capabilities")),
1224        }
1225    }
1226
1227    /// Decodes the self-reported client identity without treating it as authority.
1228    pub fn client_info(&self) -> Result<Option<Implementation>, CommonTypeError> {
1229        self.typed_implementation("io.modelcontextprotocol/clientInfo")
1230    }
1231
1232    /// Decodes the self-reported server identity from final result metadata.
1233    ///
1234    /// Final result envelopes carry this only under
1235    /// `io.modelcontextprotocol/serverInfo` in `_meta`.
1236    pub fn server_info(&self) -> Result<Option<Implementation>, CommonTypeError> {
1237        self.typed_implementation("io.modelcontextprotocol/serverInfo")
1238    }
1239
1240    /// Reads the exact optional logging-level metadata value.
1241    pub fn log_level(&self) -> Result<Option<LoggingLevel>, CommonTypeError> {
1242        self.0
1243            .get("io.modelcontextprotocol/logLevel")
1244            .map_or(Ok(None), |value| {
1245                serde_json::from_value(value.clone())
1246                    .map(Some)
1247                    .map_err(|_| CommonTypeError::Invalid("logging level metadata"))
1248            })
1249    }
1250
1251    fn optional_string(&self, key: &str) -> Result<Option<&str>, CommonTypeError> {
1252        match self.0.get(key) {
1253            None => Ok(None),
1254            Some(Value::String(value)) => Ok(Some(value)),
1255            Some(_) => Err(CommonTypeError::Invalid("metadata string")),
1256        }
1257    }
1258
1259    fn typed_implementation(&self, key: &str) -> Result<Option<Implementation>, CommonTypeError> {
1260        self.0.get(key).map_or(Ok(None), |value| {
1261            serde_json::from_value(value.clone())
1262                .map(Some)
1263                .map_err(|_| CommonTypeError::Invalid("implementation metadata"))
1264        })
1265    }
1266
1267    fn validate_reserved_values(&self) -> Result<(), CommonTypeError> {
1268        if self.protocol_version()?.is_some() && self.client_capabilities()?.is_none() {
1269            return Err(CommonTypeError::Invalid("client capabilities"));
1270        }
1271        for key in [
1272            "io.modelcontextprotocol/clientInfo",
1273            "io.modelcontextprotocol/serverInfo",
1274        ] {
1275            if self.0.contains_key(key) && self.typed_implementation(key)?.is_none() {
1276                return Err(CommonTypeError::Invalid("implementation metadata"));
1277            }
1278        }
1279        let _ = self.log_level()?;
1280        if let Some(value) = self.0.get("io.modelcontextprotocol/subscriptionId") {
1281            let valid = matches!(value, Value::String(_))
1282                || matches!(value, Value::Number(number) if JsonInteger::try_from_number(number.clone()).is_ok());
1283            if !valid {
1284                return Err(CommonTypeError::Invalid("subscription ID metadata"));
1285            }
1286        }
1287        Ok(())
1288    }
1289
1290    fn validate_notification_values(&self) -> Result<(), CommonTypeError> {
1291        if let Some(value) = self.0.get("io.modelcontextprotocol/subscriptionId") {
1292            let valid = matches!(value, Value::String(_))
1293                || matches!(value, Value::Number(number) if JsonInteger::try_from_number(number.clone()).is_ok());
1294            if !valid {
1295                return Err(CommonTypeError::Invalid("subscription ID metadata"));
1296            }
1297        }
1298        Ok(())
1299    }
1300}
1301
1302impl<'de> Deserialize<'de> for OpenMetadata {
1303    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1304    where
1305        D: serde::Deserializer<'de>,
1306    {
1307        BTreeMap::<String, Value>::deserialize(deserializer)
1308            .and_then(|entries| Self::try_from_entries(entries).map_err(serde::de::Error::custom))
1309    }
1310}
1311
1312fn valid_metadata_key(key: &str) -> bool {
1313    let Some((prefix, name)) = split_metadata_key(key) else {
1314        return false;
1315    };
1316    if prefix == Some("io.modelcontextprotocol")
1317        && !matches!(
1318            name,
1319            "protocolVersion"
1320                | "clientCapabilities"
1321                | "clientInfo"
1322                | "logLevel"
1323                | "serverInfo"
1324                | "subscriptionId"
1325        )
1326    {
1327        return false;
1328    }
1329    valid_metadata_name(name)
1330}
1331
1332fn valid_open_metadata_key(key: &str) -> bool {
1333    split_metadata_key(key).is_some_and(|(_, name)| valid_metadata_name(name))
1334}
1335
1336fn split_metadata_key(key: &str) -> Option<(Option<&str>, &str)> {
1337    let (prefix, name) = match key.split_once('/') {
1338        Some((prefix, name)) if !name.contains('/') => (Some(prefix), name),
1339        Some(_) => return None,
1340        None => (None, key),
1341    };
1342    if let Some(prefix) = prefix {
1343        if !valid_reverse_dns_prefix(prefix) {
1344            return None;
1345        }
1346    }
1347    Some((prefix, name))
1348}
1349
1350fn valid_metadata_name(name: &str) -> bool {
1351    name.is_empty()
1352        || (name.as_bytes()[0].is_ascii_alphanumeric()
1353            && name.as_bytes()[name.len() - 1].is_ascii_alphanumeric()
1354            && name
1355                .bytes()
1356                .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.')))
1357}
1358
1359fn reject_bare_unknown_members(value: &Value, known: &[&str]) -> Result<(), CommonTypeError> {
1360    let object = value
1361        .as_object()
1362        .ok_or(CommonTypeError::Invalid("wire object"))?;
1363    for key in object.keys() {
1364        let qualified = matches!(split_metadata_key(key), Some((Some(_), _)));
1365        if !known.contains(&key.as_str()) && !qualified {
1366            return Err(CommonTypeError::Invalid("unrecognized bare wire member"));
1367        }
1368    }
1369    Ok(())
1370}
1371
1372fn reject_explicit_null_fields(value: &Value, fields: &[&str]) -> Result<(), CommonTypeError> {
1373    let object = value
1374        .as_object()
1375        .ok_or(CommonTypeError::Invalid("wire object"))?;
1376    if fields
1377        .iter()
1378        .any(|field| object.get(*field).is_some_and(Value::is_null))
1379    {
1380        return Err(CommonTypeError::Invalid("optional non-null field"));
1381    }
1382    Ok(())
1383}
1384
1385fn valid_reverse_dns_prefix(prefix: &str) -> bool {
1386    let labels = prefix.split('.').collect::<Vec<_>>();
1387    !labels.is_empty()
1388        && labels.into_iter().all(|label| {
1389            !label.is_empty()
1390                && label.as_bytes()[0].is_ascii_alphabetic()
1391                && label.as_bytes()[label.len() - 1].is_ascii_alphanumeric()
1392                && label
1393                    .bytes()
1394                    .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
1395        })
1396}
1397
1398/// Bounded trace-context fields preserved from open metadata.
1399#[derive(Clone, Debug, Default, Eq, PartialEq)]
1400pub struct TraceContext {
1401    /// W3C trace parent value.
1402    pub traceparent: Option<String>,
1403    /// W3C trace state value.
1404    pub tracestate: Option<String>,
1405    /// W3C baggage value.
1406    pub baggage: Option<String>,
1407}
1408
1409impl TraceContext {
1410    /// Extracts trace fields only when they are strings in valid metadata.
1411    pub fn try_from_metadata(metadata: &OpenMetadata) -> Result<Self, CommonTypeError> {
1412        let field = |name| {
1413            metadata.optional_string(name).and_then(|value| {
1414                if value.is_some_and(|field| {
1415                    field.len() > MAX_TRACE_FIELD_BYTES
1416                        || !field.is_ascii()
1417                        || field.bytes().any(|byte| byte <= 0x20 || byte == 0x7f)
1418                }) {
1419                    Err(CommonTypeError::TooLong("trace context"))
1420                } else {
1421                    Ok(value.map(ToOwned::to_owned))
1422                }
1423            })
1424        };
1425        let traceparent = field("traceparent")?;
1426        if traceparent
1427            .as_deref()
1428            .is_some_and(|value| !valid_traceparent(value))
1429        {
1430            return Err(CommonTypeError::Invalid("traceparent"));
1431        }
1432        Ok(Self {
1433            traceparent,
1434            tracestate: field("tracestate")?,
1435            baggage: field("baggage")?,
1436        })
1437    }
1438}
1439
1440fn valid_traceparent(value: &str) -> bool {
1441    let mut fields = value.split('-');
1442    let (Some(version), Some(trace_id), Some(parent_id), Some(flags), None) = (
1443        fields.next(),
1444        fields.next(),
1445        fields.next(),
1446        fields.next(),
1447        fields.next(),
1448    ) else {
1449        return false;
1450    };
1451    valid_lower_hex(version, 2)
1452        && version != "ff"
1453        && valid_lower_hex(trace_id, 32)
1454        && trace_id.bytes().any(|byte| byte != b'0')
1455        && valid_lower_hex(parent_id, 16)
1456        && parent_id.bytes().any(|byte| byte != b'0')
1457        && valid_lower_hex(flags, 2)
1458}
1459
1460fn valid_lower_hex(value: &str, length: usize) -> bool {
1461    value.len() == length
1462        && value
1463            .bytes()
1464            .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
1465}
1466
1467/// A peer cancellation request identifier.
1468#[derive(Clone, Debug, Eq, PartialEq)]
1469pub enum CancellationRequestId {
1470    /// String JSON-RPC identifier.
1471    String(String),
1472    /// Canonically spelled signed 64-bit JSON-RPC integer identifier.
1473    Integer(i64),
1474    /// Mathematical JSON-RPC integer whose original numeric lexeme must be retained.
1475    IntegerExact(JsonInteger),
1476}
1477
1478impl Serialize for CancellationRequestId {
1479    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1480    where
1481        S: serde::Serializer,
1482    {
1483        match self {
1484            Self::String(value) => value.serialize(serializer),
1485            Self::Integer(value) => value.serialize(serializer),
1486            Self::IntegerExact(value) => value.serialize(serializer),
1487        }
1488    }
1489}
1490
1491impl<'de> Deserialize<'de> for CancellationRequestId {
1492    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1493    where
1494        D: serde::Deserializer<'de>,
1495    {
1496        match Value::deserialize(deserializer)? {
1497            Value::String(value) => Ok(Self::String(value)),
1498            Value::Number(value) => {
1499                let integer =
1500                    JsonInteger::try_from_number(value).map_err(serde::de::Error::custom)?;
1501                match integer.as_str().parse::<i64>() {
1502                    Ok(value) if integer.as_str() == value.to_string() => Ok(Self::Integer(value)),
1503                    _ => Ok(Self::IntegerExact(integer)),
1504                }
1505            }
1506            _ => Err(serde::de::Error::custom(
1507                "cancellation request ID must be a string or mathematical integer",
1508            )),
1509        }
1510    }
1511}
1512
1513/// A peer-provided cancellation reason that deliberately has no raw string
1514/// accessor, formatter, or serializer.
1515#[derive(Clone, Eq, PartialEq)]
1516pub struct UntrustedCancellationReason(String);
1517
1518/// Final `notifications/cancelled` payload.
1519#[derive(Clone, Eq, PartialEq)]
1520pub struct CancellationNotification {
1521    /// Required non-null request identifier.
1522    pub request_id: CancellationRequestId,
1523    reason: Option<UntrustedCancellationReason>,
1524}
1525
1526impl CancellationNotification {
1527    /// Constructs an exact cancellation payload while retaining an untrusted
1528    /// reason without rendering or interpreting it.
1529    pub fn try_new(
1530        request_id: CancellationRequestId,
1531        reason: Option<String>,
1532    ) -> Result<Self, CommonTypeError> {
1533        let reason = reason.map(UntrustedCancellationReason);
1534        Ok(Self { request_id, reason })
1535    }
1536
1537    /// Indicates whether a peer reason was present without rendering or exposing it.
1538    #[must_use]
1539    pub fn has_untrusted_reason(&self) -> bool {
1540        self.reason.is_some()
1541    }
1542}
1543
1544/// Icon theme values admitted by the final schema.
1545#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
1546#[serde(rename_all = "lowercase")]
1547pub enum IconTheme {
1548    /// An icon designed for a light background.
1549    Light,
1550    /// An icon designed for a dark background.
1551    Dark,
1552}
1553
1554/// A raw, structurally valid icon source. Rendering admission is deliberately separate.
1555#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1556#[serde(rename_all = "camelCase")]
1557pub struct RawIcon {
1558    /// Required schema URI source.
1559    pub src: RawIconSourceUri,
1560    /// Optional peer MIME declaration.
1561    #[serde(skip_serializing_if = "Option::is_none")]
1562    pub mime_type: Option<String>,
1563    /// Presence-aware size strings. Empty is distinct from absent.
1564    #[serde(skip_serializing_if = "Option::is_none")]
1565    pub sizes: Option<Vec<String>>,
1566    /// Optional theme without serialization defaults.
1567    #[serde(skip_serializing_if = "Option::is_none")]
1568    pub theme: Option<IconTheme>,
1569    /// Schema-allowed members retained without assigning them display semantics.
1570    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
1571    pub additional: BTreeMap<String, Value>,
1572}
1573
1574impl RawIcon {
1575    /// Constructs a raw icon with a required absolute source.
1576    pub fn try_new(src: impl Into<String>) -> Result<Self, CommonTypeError> {
1577        Ok(Self {
1578            src: RawIconSourceUri::parse(src)?,
1579            mime_type: None,
1580            sizes: None,
1581            theme: None,
1582            additional: BTreeMap::new(),
1583        })
1584    }
1585
1586    /// Adds exact wire-preserving optional icon fields with bounded peer sizes.
1587    pub fn try_with_details(
1588        src: impl Into<String>,
1589        mime_type: Option<String>,
1590        sizes: Option<Vec<String>>,
1591        theme: Option<IconTheme>,
1592    ) -> Result<Self, CommonTypeError> {
1593        if sizes.as_ref().is_some_and(|values| {
1594            values.len() > MAX_ICON_SIZE_ENTRIES
1595                || values.iter().any(|value| value.len() > MAX_ICON_SIZE_BYTES)
1596        }) {
1597            return Err(CommonTypeError::TooLong("icon sizes"));
1598        }
1599        Ok(Self {
1600            src: RawIconSourceUri::parse(src)?,
1601            mime_type,
1602            sizes,
1603            theme,
1604            additional: BTreeMap::new(),
1605        })
1606    }
1607
1608    /// Returns the documented effective size class without changing wire presence.
1609    #[must_use]
1610    pub fn effective_any_size(&self) -> bool {
1611        self.sizes.is_none()
1612    }
1613}
1614
1615#[derive(Deserialize)]
1616#[serde(rename_all = "camelCase")]
1617struct RawIconWire {
1618    src: RawIconSourceUri,
1619    #[serde(default)]
1620    mime_type: Option<String>,
1621    #[serde(default)]
1622    sizes: Option<Vec<String>>,
1623    #[serde(default)]
1624    theme: Option<IconTheme>,
1625    #[serde(flatten, default)]
1626    additional: BTreeMap<String, Value>,
1627}
1628
1629impl<'de> Deserialize<'de> for RawIcon {
1630    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1631    where
1632        D: serde::Deserializer<'de>,
1633    {
1634        let value = Value::deserialize(deserializer)?;
1635        reject_explicit_null_fields(&value, &["mimeType", "sizes", "theme"])
1636            .map_err(serde::de::Error::custom)?;
1637        let wire: RawIconWire = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
1638        let mut icon =
1639            Self::try_with_details(wire.src.as_str(), wire.mime_type, wire.sizes, wire.theme)
1640                .map_err(serde::de::Error::custom)?;
1641        icon.additional = wire.additional;
1642        Ok(icon)
1643    }
1644}
1645
1646/// Annotation audience values.
1647#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
1648#[serde(rename_all = "lowercase")]
1649pub enum AnnotationAudience {
1650    /// Intended for a user.
1651    User,
1652    /// Intended for an assistant.
1653    Assistant,
1654}
1655
1656/// Optional content annotations.
1657#[derive(Clone, Debug, Default, PartialEq, Serialize)]
1658#[serde(rename_all = "camelCase")]
1659pub struct Annotations {
1660    /// Intended audience roles.
1661    #[serde(skip_serializing_if = "Option::is_none")]
1662    pub audience: Option<Vec<AnnotationAudience>>,
1663    /// Finite inclusive priority hint.
1664    #[serde(skip_serializing_if = "Option::is_none")]
1665    pub priority: Option<f64>,
1666    /// Peer timestamp string preserved without inventing a schema rejection.
1667    #[serde(skip_serializing_if = "Option::is_none")]
1668    pub last_modified: Option<String>,
1669    /// Schema-allowed members retained without assigning them annotation semantics.
1670    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
1671    pub additional: BTreeMap<String, Value>,
1672}
1673
1674impl Annotations {
1675    /// Validates the only schema-constrained numeric field.
1676    pub fn try_with_priority(priority: f64) -> Result<Self, CommonTypeError> {
1677        if !priority.is_finite() || !(0.0..=1.0).contains(&priority) {
1678            return Err(CommonTypeError::Invalid("annotation priority"));
1679        }
1680        Ok(Self {
1681            priority: Some(priority),
1682            ..Self::default()
1683        })
1684    }
1685}
1686
1687#[derive(Deserialize)]
1688#[serde(rename_all = "camelCase")]
1689struct AnnotationsWire {
1690    #[serde(default)]
1691    audience: Option<Vec<AnnotationAudience>>,
1692    #[serde(default)]
1693    priority: Option<f64>,
1694    #[serde(default)]
1695    last_modified: Option<String>,
1696    #[serde(flatten, default)]
1697    additional: BTreeMap<String, Value>,
1698}
1699
1700impl<'de> Deserialize<'de> for Annotations {
1701    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1702    where
1703        D: serde::Deserializer<'de>,
1704    {
1705        let value = Value::deserialize(deserializer)?;
1706        reject_explicit_null_fields(&value, &["audience", "priority", "lastModified"])
1707            .map_err(serde::de::Error::custom)?;
1708        let wire: AnnotationsWire =
1709            serde_json::from_value(value).map_err(serde::de::Error::custom)?;
1710        if let Some(priority) = wire.priority {
1711            Self::try_with_priority(priority).map_err(serde::de::Error::custom)?;
1712        }
1713        Ok(Self {
1714            audience: wire.audience,
1715            priority: wire.priority,
1716            last_modified: wire.last_modified,
1717            additional: wire.additional,
1718        })
1719    }
1720}
1721
1722/// A final `resource_link` content block.
1723#[derive(Clone, Debug, PartialEq)]
1724pub struct ResourceLink {
1725    /// Optional sized icons for display.
1726    pub icons: Option<Vec<RawIcon>>,
1727    /// Required programmatic resource name.
1728    pub name: String,
1729    /// Optional user-facing resource title.
1730    pub title: Option<String>,
1731    /// Exact resource identity.
1732    pub uri: AbsoluteUri,
1733    /// Optional description of the resource.
1734    pub description: Option<String>,
1735    /// Optional MIME type declared for the resource.
1736    pub mime_type: Option<String>,
1737    /// Optional link annotations.
1738    pub annotations: Option<Annotations>,
1739    /// Optional raw size of the resource in bytes.
1740    pub size: Option<JsonInteger>,
1741    /// Preserved open metadata.
1742    pub meta: Option<OpenMetadata>,
1743    /// Schema-allowed members retained without assigning them protocol meaning.
1744    pub additional: BTreeMap<String, Value>,
1745}
1746
1747impl Serialize for ResourceLink {
1748    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1749    where
1750        S: serde::Serializer,
1751    {
1752        let field_count = 3
1753            + usize::from(self.icons.is_some())
1754            + usize::from(self.title.is_some())
1755            + usize::from(self.description.is_some())
1756            + usize::from(self.mime_type.is_some())
1757            + usize::from(self.annotations.is_some())
1758            + usize::from(self.size.is_some())
1759            + usize::from(self.meta.is_some())
1760            + self.additional.len();
1761        let mut state = serializer.serialize_map(Some(field_count))?;
1762        state.serialize_entry("type", "resource_link")?;
1763        if let Some(icons) = &self.icons {
1764            state.serialize_entry("icons", icons)?;
1765        }
1766        state.serialize_entry("name", &self.name)?;
1767        if let Some(title) = &self.title {
1768            state.serialize_entry("title", title)?;
1769        }
1770        state.serialize_entry("uri", &self.uri)?;
1771        if let Some(description) = &self.description {
1772            state.serialize_entry("description", description)?;
1773        }
1774        if let Some(mime_type) = &self.mime_type {
1775            state.serialize_entry("mimeType", mime_type)?;
1776        }
1777        if let Some(annotations) = &self.annotations {
1778            state.serialize_entry("annotations", annotations)?;
1779        }
1780        if let Some(size) = &self.size {
1781            state.serialize_entry("size", size)?;
1782        }
1783        if let Some(meta) = &self.meta {
1784            state.serialize_entry("_meta", meta)?;
1785        }
1786        for (name, value) in &self.additional {
1787            state.serialize_entry(name, value)?;
1788        }
1789        state.end()
1790    }
1791}
1792
1793#[derive(Deserialize)]
1794#[serde(rename_all = "camelCase")]
1795struct ResourceLinkWire {
1796    #[serde(rename = "type")]
1797    kind: ResourceLinkKind,
1798    #[serde(default)]
1799    icons: Option<Vec<RawIcon>>,
1800    name: String,
1801    #[serde(default)]
1802    title: Option<String>,
1803    uri: AbsoluteUri,
1804    #[serde(default)]
1805    description: Option<String>,
1806    #[serde(rename = "mimeType", default)]
1807    mime_type: Option<String>,
1808    #[serde(default)]
1809    annotations: Option<Annotations>,
1810    #[serde(default)]
1811    size: Option<JsonInteger>,
1812    #[serde(rename = "_meta", default)]
1813    meta: Option<OpenMetadata>,
1814    #[serde(flatten, default)]
1815    additional: BTreeMap<String, Value>,
1816}
1817
1818#[derive(Deserialize)]
1819enum ResourceLinkKind {
1820    #[serde(rename = "resource_link")]
1821    ResourceLink,
1822}
1823
1824impl<'de> Deserialize<'de> for ResourceLink {
1825    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1826    where
1827        D: serde::Deserializer<'de>,
1828    {
1829        let value = Value::deserialize(deserializer)?;
1830        reject_explicit_null_fields(
1831            &value,
1832            &[
1833                "icons",
1834                "title",
1835                "description",
1836                "mimeType",
1837                "annotations",
1838                "size",
1839                "_meta",
1840            ],
1841        )
1842        .map_err(serde::de::Error::custom)?;
1843        let wire: ResourceLinkWire =
1844            serde_json::from_value(value).map_err(serde::de::Error::custom)?;
1845        let ResourceLinkKind::ResourceLink = wire.kind;
1846        Ok(Self {
1847            icons: wire.icons,
1848            name: wire.name,
1849            title: wire.title,
1850            uri: wire.uri,
1851            description: wire.description,
1852            mime_type: wire.mime_type,
1853            annotations: wire.annotations,
1854            size: wire.size,
1855            meta: wire.meta,
1856            additional: wire.additional,
1857        })
1858    }
1859}
1860
1861/// Text or blob resource contents embedded in content.
1862#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1863#[serde(rename_all = "camelCase")]
1864#[serde(untagged)]
1865pub enum EmbeddedResourceContents {
1866    /// Text resource contents.
1867    Text {
1868        uri: AbsoluteUri,
1869        text: String,
1870        #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
1871        mime_type: Option<String>,
1872        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
1873        meta: Option<OpenMetadata>,
1874        #[serde(flatten)]
1875        additional: BTreeMap<String, Value>,
1876    },
1877    /// Blob resource contents.
1878    Blob {
1879        uri: AbsoluteUri,
1880        blob: String,
1881        #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
1882        mime_type: Option<String>,
1883        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
1884        meta: Option<OpenMetadata>,
1885        #[serde(flatten)]
1886        additional: BTreeMap<String, Value>,
1887    },
1888}
1889
1890#[derive(Deserialize)]
1891#[serde(rename_all = "camelCase")]
1892#[serde(untagged)]
1893enum EmbeddedResourceContentsWire {
1894    Text {
1895        uri: AbsoluteUri,
1896        text: String,
1897        #[serde(rename = "mimeType", default)]
1898        mime_type: Option<String>,
1899        #[serde(rename = "_meta", default)]
1900        meta: Option<OpenMetadata>,
1901        #[serde(flatten, default)]
1902        additional: BTreeMap<String, Value>,
1903    },
1904    Blob {
1905        uri: AbsoluteUri,
1906        blob: String,
1907        #[serde(rename = "mimeType", default)]
1908        mime_type: Option<String>,
1909        #[serde(rename = "_meta", default)]
1910        meta: Option<OpenMetadata>,
1911        #[serde(flatten, default)]
1912        additional: BTreeMap<String, Value>,
1913    },
1914}
1915
1916impl<'de> Deserialize<'de> for EmbeddedResourceContents {
1917    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1918    where
1919        D: serde::Deserializer<'de>,
1920    {
1921        let value = Value::deserialize(deserializer)?;
1922        let object = value
1923            .as_object()
1924            .ok_or_else(|| serde::de::Error::custom("embedded resource must be an object"))?;
1925        let has_text = object.contains_key("text");
1926        let has_blob = object.contains_key("blob");
1927        if has_text == has_blob {
1928            return Err(serde::de::Error::custom(
1929                "embedded resource requires exactly one of text or blob",
1930            ));
1931        }
1932        // Schema-allowed additional properties are namespaced extension
1933        // members; bare strangers (for example a snake_case mime_type
1934        // shadowing the canonical mimeType) must reject.
1935        reject_bare_unknown_members(&value, &["uri", "text", "blob", "mimeType", "_meta"])
1936            .map_err(serde::de::Error::custom)?;
1937        reject_explicit_null_fields(&value, &["mimeType", "_meta"])
1938            .map_err(serde::de::Error::custom)?;
1939        let wire: EmbeddedResourceContentsWire =
1940            serde_json::from_value(value).map_err(serde::de::Error::custom)?;
1941        let resource = match wire {
1942            EmbeddedResourceContentsWire::Text {
1943                uri,
1944                text,
1945                mime_type,
1946                meta,
1947                additional,
1948            } => Self::Text {
1949                uri,
1950                text,
1951                mime_type,
1952                meta,
1953                additional,
1954            },
1955            EmbeddedResourceContentsWire::Blob {
1956                uri,
1957                blob,
1958                mime_type,
1959                meta,
1960                additional,
1961            } => Self::Blob {
1962                uri,
1963                blob,
1964                mime_type,
1965                meta,
1966                additional,
1967            },
1968        };
1969        validate_embedded_resource(&resource).map_err(serde::de::Error::custom)?;
1970        Ok(resource)
1971    }
1972}
1973
1974/// Final common content discriminators.
1975#[derive(Clone, Debug, PartialEq, Serialize)]
1976#[serde(tag = "type", rename_all = "snake_case")]
1977pub enum ContentBlock {
1978    /// Text content.
1979    Text {
1980        text: String,
1981        #[serde(skip_serializing_if = "Option::is_none")]
1982        annotations: Option<Annotations>,
1983        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
1984        meta: Option<OpenMetadata>,
1985        #[serde(flatten)]
1986        additional: BTreeMap<String, Value>,
1987    },
1988    /// Binary image content.
1989    Image {
1990        data: String,
1991        #[serde(rename = "mimeType")]
1992        mime_type: String,
1993        #[serde(skip_serializing_if = "Option::is_none")]
1994        annotations: Option<Annotations>,
1995        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
1996        meta: Option<OpenMetadata>,
1997        #[serde(flatten)]
1998        additional: BTreeMap<String, Value>,
1999    },
2000    /// Binary audio content.
2001    Audio {
2002        data: String,
2003        #[serde(rename = "mimeType")]
2004        mime_type: String,
2005        #[serde(skip_serializing_if = "Option::is_none")]
2006        annotations: Option<Annotations>,
2007        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
2008        meta: Option<OpenMetadata>,
2009        #[serde(flatten)]
2010        additional: BTreeMap<String, Value>,
2011    },
2012    /// A resource link uses the exact `resource_link` discriminator.
2013    ResourceLink {
2014        #[serde(skip_serializing_if = "Option::is_none")]
2015        icons: Option<Vec<RawIcon>>,
2016        name: String,
2017        #[serde(skip_serializing_if = "Option::is_none")]
2018        title: Option<String>,
2019        uri: AbsoluteUri,
2020        #[serde(skip_serializing_if = "Option::is_none")]
2021        description: Option<String>,
2022        #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
2023        mime_type: Option<String>,
2024        #[serde(skip_serializing_if = "Option::is_none")]
2025        annotations: Option<Annotations>,
2026        #[serde(skip_serializing_if = "Option::is_none")]
2027        size: Option<JsonInteger>,
2028        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
2029        meta: Option<OpenMetadata>,
2030        #[serde(flatten)]
2031        additional: BTreeMap<String, Value>,
2032    },
2033    /// An embedded resource uses the exact `resource` discriminator.
2034    Resource {
2035        resource: EmbeddedResourceContents,
2036        #[serde(skip_serializing_if = "Option::is_none")]
2037        annotations: Option<Annotations>,
2038        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
2039        meta: Option<OpenMetadata>,
2040        #[serde(flatten)]
2041        additional: BTreeMap<String, Value>,
2042    },
2043}
2044
2045#[derive(Deserialize)]
2046#[serde(tag = "type", rename_all = "snake_case")]
2047enum ContentBlockWire {
2048    Text {
2049        text: String,
2050        #[serde(default)]
2051        annotations: Option<Annotations>,
2052        #[serde(rename = "_meta", default)]
2053        meta: Option<OpenMetadata>,
2054        #[serde(flatten, default)]
2055        additional: BTreeMap<String, Value>,
2056    },
2057    Image {
2058        data: String,
2059        #[serde(rename = "mimeType")]
2060        mime_type: String,
2061        #[serde(default)]
2062        annotations: Option<Annotations>,
2063        #[serde(rename = "_meta", default)]
2064        meta: Option<OpenMetadata>,
2065        #[serde(flatten, default)]
2066        additional: BTreeMap<String, Value>,
2067    },
2068    Audio {
2069        data: String,
2070        #[serde(rename = "mimeType")]
2071        mime_type: String,
2072        #[serde(default)]
2073        annotations: Option<Annotations>,
2074        #[serde(rename = "_meta", default)]
2075        meta: Option<OpenMetadata>,
2076        #[serde(flatten, default)]
2077        additional: BTreeMap<String, Value>,
2078    },
2079    ResourceLink {
2080        #[serde(default)]
2081        icons: Option<Vec<RawIcon>>,
2082        name: String,
2083        #[serde(default)]
2084        title: Option<String>,
2085        uri: AbsoluteUri,
2086        #[serde(default)]
2087        description: Option<String>,
2088        #[serde(rename = "mimeType", default)]
2089        mime_type: Option<String>,
2090        #[serde(default)]
2091        annotations: Option<Annotations>,
2092        #[serde(default)]
2093        size: Option<JsonInteger>,
2094        #[serde(rename = "_meta", default)]
2095        meta: Option<OpenMetadata>,
2096        #[serde(flatten, default)]
2097        additional: BTreeMap<String, Value>,
2098    },
2099    Resource {
2100        resource: EmbeddedResourceContents,
2101        #[serde(default)]
2102        annotations: Option<Annotations>,
2103        #[serde(rename = "_meta", default)]
2104        meta: Option<OpenMetadata>,
2105        #[serde(flatten, default)]
2106        additional: BTreeMap<String, Value>,
2107    },
2108}
2109
2110impl<'de> Deserialize<'de> for ContentBlock {
2111    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2112    where
2113        D: serde::Deserializer<'de>,
2114    {
2115        let value = Value::deserialize(deserializer)?;
2116        let kind = value
2117            .get("type")
2118            .and_then(Value::as_str)
2119            .ok_or_else(|| serde::de::Error::custom("missing content discriminator"))?;
2120        if !matches!(
2121            kind,
2122            "text" | "image" | "audio" | "resource_link" | "resource"
2123        ) {
2124            return Err(serde::de::Error::custom("content discriminator"));
2125        }
2126        // Schema-allowed additional properties are namespaced extension
2127        // members; a bare unknown member is a shadow/squatting risk and must
2128        // reject without consuming the block.
2129        let known_members: &[&str] = match kind {
2130            "text" => &["type", "text", "annotations", "_meta"],
2131            "image" | "audio" => &["type", "data", "mimeType", "annotations", "_meta"],
2132            "resource_link" => &[
2133                "type",
2134                "icons",
2135                "name",
2136                "title",
2137                "uri",
2138                "description",
2139                "mimeType",
2140                "annotations",
2141                "size",
2142                "_meta",
2143            ],
2144            _ => &["type", "resource", "annotations", "_meta"],
2145        };
2146        reject_bare_unknown_members(&value, known_members).map_err(serde::de::Error::custom)?;
2147        let optional_non_null_fields = match kind {
2148            "resource_link" => &[
2149                "icons",
2150                "title",
2151                "description",
2152                "mimeType",
2153                "annotations",
2154                "size",
2155                "_meta",
2156            ][..],
2157            _ => &["annotations", "_meta"][..],
2158        };
2159        reject_explicit_null_fields(&value, optional_non_null_fields)
2160            .map_err(serde::de::Error::custom)?;
2161        if kind == "resource" {
2162            let resource = value
2163                .get("resource")
2164                .ok_or_else(|| serde::de::Error::custom("missing embedded resource"))?;
2165            let _ = serde_json::from_value::<EmbeddedResourceContents>(resource.clone())
2166                .map_err(serde::de::Error::custom)?;
2167        }
2168        let wire: ContentBlockWire =
2169            serde_json::from_value(value).map_err(serde::de::Error::custom)?;
2170        let content = match wire {
2171            ContentBlockWire::Text {
2172                text,
2173                annotations,
2174                meta,
2175                additional,
2176            } => Self::Text {
2177                text,
2178                annotations,
2179                meta,
2180                additional,
2181            },
2182            ContentBlockWire::Image {
2183                data,
2184                mime_type,
2185                annotations,
2186                meta,
2187                additional,
2188            } => {
2189                valid_binary_content(&data, &mime_type, "image/")
2190                    .map_err(serde::de::Error::custom)?;
2191                Self::Image {
2192                    data,
2193                    mime_type,
2194                    annotations,
2195                    meta,
2196                    additional,
2197                }
2198            }
2199            ContentBlockWire::Audio {
2200                data,
2201                mime_type,
2202                annotations,
2203                meta,
2204                additional,
2205            } => {
2206                valid_binary_content(&data, &mime_type, "audio/")
2207                    .map_err(serde::de::Error::custom)?;
2208                Self::Audio {
2209                    data,
2210                    mime_type,
2211                    annotations,
2212                    meta,
2213                    additional,
2214                }
2215            }
2216            ContentBlockWire::ResourceLink {
2217                icons,
2218                name,
2219                title,
2220                uri,
2221                description,
2222                mime_type,
2223                annotations,
2224                size,
2225                meta,
2226                additional,
2227            } => Self::ResourceLink {
2228                icons,
2229                name,
2230                title,
2231                uri,
2232                description,
2233                mime_type,
2234                annotations,
2235                size,
2236                meta,
2237                additional,
2238            },
2239            ContentBlockWire::Resource {
2240                resource,
2241                annotations,
2242                meta,
2243                additional,
2244            } => {
2245                validate_embedded_resource(&resource).map_err(serde::de::Error::custom)?;
2246                Self::Resource {
2247                    resource,
2248                    annotations,
2249                    meta,
2250                    additional,
2251                }
2252            }
2253        };
2254        FinalCommonTypesSchema::validate_content(&content).map_err(serde::de::Error::custom)?;
2255        Ok(content)
2256    }
2257}
2258
2259impl ContentBlock {
2260    /// Constructs text content.
2261    #[must_use]
2262    pub fn text(text: impl Into<String>) -> Self {
2263        Self::Text {
2264            text: text.into(),
2265            annotations: None,
2266            meta: None,
2267            additional: BTreeMap::new(),
2268        }
2269    }
2270
2271    /// Constructs image content after validating base64 and image MIME shape.
2272    pub fn image(
2273        data: impl Into<String>,
2274        mime_type: impl Into<String>,
2275    ) -> Result<Self, CommonTypeError> {
2276        let data = data.into();
2277        let mime_type = mime_type.into();
2278        valid_binary_content(&data, &mime_type, "image/")?;
2279        Ok(Self::Image {
2280            data,
2281            mime_type,
2282            annotations: None,
2283            meta: None,
2284            additional: BTreeMap::new(),
2285        })
2286    }
2287
2288    /// Constructs audio content after validating base64 and audio MIME shape.
2289    pub fn audio(
2290        data: impl Into<String>,
2291        mime_type: impl Into<String>,
2292    ) -> Result<Self, CommonTypeError> {
2293        let data = data.into();
2294        let mime_type = mime_type.into();
2295        valid_binary_content(&data, &mime_type, "audio/")?;
2296        Ok(Self::Audio {
2297            data,
2298            mime_type,
2299            annotations: None,
2300            meta: None,
2301            additional: BTreeMap::new(),
2302        })
2303    }
2304
2305    /// Constructs a resource-link content block.
2306    pub fn resource_link(
2307        uri: impl Into<String>,
2308        name: impl Into<String>,
2309    ) -> Result<Self, CommonTypeError> {
2310        Ok(Self::ResourceLink {
2311            icons: None,
2312            name: name.into(),
2313            title: None,
2314            uri: AbsoluteUri::parse(uri)?,
2315            description: None,
2316            mime_type: None,
2317            annotations: None,
2318            size: None,
2319            meta: None,
2320            additional: BTreeMap::new(),
2321        })
2322    }
2323
2324    /// Constructs embedded text resource content.
2325    pub fn resource(
2326        uri: impl Into<String>,
2327        text: impl Into<String>,
2328        mime_type: Option<String>,
2329    ) -> Result<Self, CommonTypeError> {
2330        Ok(Self::Resource {
2331            resource: EmbeddedResourceContents::Text {
2332                uri: AbsoluteUri::parse(uri)?,
2333                text: text.into(),
2334                mime_type,
2335                meta: None,
2336                additional: BTreeMap::new(),
2337            },
2338            annotations: None,
2339            meta: None,
2340            additional: BTreeMap::new(),
2341        })
2342    }
2343}
2344
2345/// Final sampling-only content blocks.
2346///
2347/// Tool use and tool result are intentionally absent from [`ContentBlock`]:
2348/// they are legal only in the final sampling message/result union. Tool result
2349/// bodies, in turn, use the general content union and therefore cannot nest
2350/// further tool-use/result blocks.
2351#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
2352#[serde(tag = "type", rename_all = "snake_case")]
2353pub enum SamplingContentBlock {
2354    /// Text sampling content.
2355    Text {
2356        text: String,
2357        #[serde(default, skip_serializing_if = "Option::is_none")]
2358        annotations: Option<Annotations>,
2359        #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
2360        meta: Option<OpenMetadata>,
2361        #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
2362        additional: BTreeMap<String, Value>,
2363    },
2364    /// Image sampling content.
2365    Image {
2366        data: String,
2367        #[serde(rename = "mimeType")]
2368        mime_type: String,
2369        #[serde(default, skip_serializing_if = "Option::is_none")]
2370        annotations: Option<Annotations>,
2371        #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
2372        meta: Option<OpenMetadata>,
2373        #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
2374        additional: BTreeMap<String, Value>,
2375    },
2376    /// Audio sampling content.
2377    Audio {
2378        data: String,
2379        #[serde(rename = "mimeType")]
2380        mime_type: String,
2381        #[serde(default, skip_serializing_if = "Option::is_none")]
2382        annotations: Option<Annotations>,
2383        #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
2384        meta: Option<OpenMetadata>,
2385        #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
2386        additional: BTreeMap<String, Value>,
2387    },
2388    /// A requested assistant tool call.
2389    ToolUse {
2390        id: String,
2391        name: String,
2392        input: serde_json::Map<String, Value>,
2393        #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
2394        meta: Option<OpenMetadata>,
2395        #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
2396        additional: BTreeMap<String, Value>,
2397    },
2398    /// A result for a preceding tool call.
2399    ToolResult {
2400        #[serde(rename = "toolUseId")]
2401        tool_use_id: String,
2402        content: Vec<ContentBlock>,
2403        /// Presence remains distinct from the protocol default of false.
2404        #[serde(rename = "isError", default, skip_serializing_if = "Option::is_none")]
2405        is_error: Option<bool>,
2406        #[serde(
2407            rename = "structuredContent",
2408            default,
2409            skip_serializing_if = "Option::is_none",
2410            deserialize_with = "deserialize_present_json_value"
2411        )]
2412        structured_content: Option<Value>,
2413        #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
2414        meta: Option<OpenMetadata>,
2415        #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
2416        additional: BTreeMap<String, Value>,
2417    },
2418}
2419
2420fn deserialize_present_json_value<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
2421where
2422    D: serde::Deserializer<'de>,
2423{
2424    Value::deserialize(deserializer).map(Some)
2425}
2426
2427fn valid_binary_content(
2428    data: &str,
2429    mime_type: &str,
2430    required_prefix: &str,
2431) -> Result<(), CommonTypeError> {
2432    if data.len() > MAX_CONTENT_ENCODED_BYTES {
2433        return Err(CommonTypeError::TooLong("binary content"));
2434    }
2435    if !mime_type.starts_with(required_prefix)
2436        || mime_type.len() == required_prefix.len()
2437        || !valid_mime_type(mime_type)
2438    {
2439        return Err(CommonTypeError::Invalid("binary MIME type"));
2440    }
2441    validate_standard_base64(data)
2442}
2443
2444fn validate_embedded_resource(resource: &EmbeddedResourceContents) -> Result<(), CommonTypeError> {
2445    match resource {
2446        EmbeddedResourceContents::Text { mime_type, .. } => {
2447            if mime_type
2448                .as_deref()
2449                .is_some_and(|value| !valid_mime_type(value))
2450            {
2451                return Err(CommonTypeError::Invalid("resource MIME type"));
2452            }
2453        }
2454        EmbeddedResourceContents::Blob {
2455            blob, mime_type, ..
2456        } => {
2457            if blob.len() > MAX_CONTENT_ENCODED_BYTES {
2458                return Err(CommonTypeError::TooLong("binary content"));
2459            }
2460            validate_standard_base64(blob)?;
2461            if mime_type
2462                .as_deref()
2463                .is_some_and(|value| !valid_mime_type(value))
2464            {
2465                return Err(CommonTypeError::Invalid("resource MIME type"));
2466            }
2467        }
2468    }
2469    Ok(())
2470}
2471
2472fn validate_standard_base64(value: &str) -> Result<(), CommonTypeError> {
2473    base64::engine::general_purpose::STANDARD
2474        .decode(value)
2475        .or_else(|_| base64::engine::general_purpose::STANDARD_NO_PAD.decode(value))
2476        .map(|_| ())
2477        .map_err(|_| CommonTypeError::Invalid("base64 content"))
2478}
2479
2480fn valid_mime_type(value: &str) -> bool {
2481    let Some((kind, subtype)) = value.split_once('/') else {
2482        return false;
2483    };
2484    !kind.is_empty()
2485        && !subtype.is_empty()
2486        && !subtype.contains('/')
2487        && kind.bytes().all(is_mime_token)
2488        && subtype.bytes().all(is_mime_token)
2489}
2490
2491fn is_mime_token(byte: u8) -> bool {
2492    byte.is_ascii_alphanumeric()
2493        || matches!(
2494            byte,
2495            b'!' | b'#' | b'$' | b'&' | b'^' | b'_' | b'-' | b'.' | b'+'
2496        )
2497}
2498
2499/// Direction of a final common-type wire envelope.
2500#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2501pub enum CommonWireDirection {
2502    /// A client or server request.
2503    Request,
2504    /// A notification, which has no JSON-RPC response.
2505    Notification,
2506    /// A request result.
2507    Result,
2508}
2509
2510/// Structural schema admission for the final common wire slice.
2511///
2512/// It validates exact spellings and direction-sensitive metadata without converting peer input
2513/// into a locally authorized resource, icon, or cancellation action.
2514#[derive(Clone, Copy, Debug, Default)]
2515pub struct FinalCommonTypesSchema;
2516
2517impl FinalCommonTypesSchema {
2518    /// The exact final-schema URI owners; `Icon.src` has a separately typed raw icon source.
2519    pub const FINAL_URI_OWNERS: [&'static str; 12] = [
2520        "BlobResourceContents.uri",
2521        "ElicitRequestURLParams.url",
2522        "Icon.src",
2523        "Implementation.websiteUrl",
2524        "ReadResourceRequestParams.uri",
2525        "Resource.uri",
2526        "ResourceContents.uri",
2527        "ResourceLink.uri",
2528        "ResourceRequestParams.uri",
2529        "ResourceUpdatedNotificationParams.uri",
2530        "Root.uri",
2531        "TextResourceContents.uri",
2532    ];
2533
2534    /// Validates a final common wire object for its declared direction.
2535    pub fn validate(direction: CommonWireDirection, wire: &Value) -> Result<(), CommonTypeError> {
2536        let object = wire
2537            .as_object()
2538            .ok_or(CommonTypeError::Invalid("common wire object"))?;
2539        match (direction, object.get("_meta")) {
2540            (CommonWireDirection::Request, Some(meta)) => Self::validate_request_metadata(meta)?,
2541            (CommonWireDirection::Request, None) => {
2542                return Err(CommonTypeError::Invalid("request metadata"));
2543            }
2544            (_, Some(meta)) => {
2545                let metadata = Self::validate_open_metadata(meta)?;
2546                let _ = TraceContext::try_from_metadata(&metadata)?;
2547            }
2548            (_, None) => {}
2549        }
2550        if let Some(kind) = object.get("type") {
2551            let kind = kind
2552                .as_str()
2553                .ok_or(CommonTypeError::Invalid("content discriminator"))?;
2554            if !matches!(
2555                kind,
2556                "text" | "image" | "audio" | "resource_link" | "resource"
2557            ) {
2558                return Err(CommonTypeError::Invalid("content discriminator"));
2559            }
2560            let content: ContentBlock = serde_json::from_value(wire.clone())
2561                .map_err(|_| CommonTypeError::Invalid("content block"))?;
2562            Self::validate_content(&content)?;
2563        }
2564        if object.contains_key("src") {
2565            let _ = Self::validate_icon(wire)?;
2566        }
2567        if object.get("method").and_then(Value::as_str) == Some("notifications/cancelled") {
2568            if direction != CommonWireDirection::Notification {
2569                return Err(CommonTypeError::Invalid("cancellation direction"));
2570            }
2571            Self::validate_cancellation_params(
2572                object
2573                    .get("params")
2574                    .ok_or(CommonTypeError::Invalid("cancellation params"))?,
2575            )?;
2576        }
2577        Ok(())
2578    }
2579
2580    /// Validates the raw icon wire shape while preserving absent versus present-empty sizes.
2581    pub fn validate_icon(wire: &Value) -> Result<RawIcon, CommonTypeError> {
2582        let object = wire
2583            .as_object()
2584            .ok_or(CommonTypeError::Invalid("icon object"))?;
2585        for field in ["mimeType", "sizes", "theme"] {
2586            if object.get(field).is_some_and(Value::is_null) {
2587                return Err(CommonTypeError::Invalid("icon optional field"));
2588            }
2589        }
2590        let icon: RawIcon =
2591            serde_json::from_value(wire.clone()).map_err(|_| CommonTypeError::Invalid("icon"))?;
2592        RawIcon::try_with_details(
2593            icon.src.as_str(),
2594            icon.mime_type.clone(),
2595            icon.sizes.clone(),
2596            icon.theme,
2597        )?;
2598        Ok(icon)
2599    }
2600
2601    /// Produces the deterministic JSON form used by frozen golden-wire records.
2602    pub fn canonical_json(wire: &Value) -> Result<String, CommonTypeError> {
2603        serde_json::to_string(wire).map_err(|_| CommonTypeError::Invalid("canonical JSON"))
2604    }
2605
2606    /// Validates a wire object and requires its canonical JSON to equal the frozen golden.
2607    pub fn validate_golden(
2608        direction: CommonWireDirection,
2609        wire: &Value,
2610        golden: &str,
2611    ) -> Result<(), CommonTypeError> {
2612        Self::validate(direction, wire)?;
2613        if Self::canonical_json(wire)? != golden {
2614            return Err(CommonTypeError::Invalid("golden wire"));
2615        }
2616        Ok(())
2617    }
2618
2619    fn validate_open_metadata(meta: &Value) -> Result<OpenMetadata, CommonTypeError> {
2620        let entries = meta
2621            .as_object()
2622            .ok_or(CommonTypeError::Invalid("metadata object"))?
2623            .iter()
2624            .map(|(key, value)| (key.clone(), value.clone()));
2625        OpenMetadata::try_from_entries(entries)
2626    }
2627
2628    fn validate_request_metadata(meta: &Value) -> Result<(), CommonTypeError> {
2629        let metadata = Self::validate_open_metadata(meta)?;
2630        if metadata.protocol_version()?.is_none() || metadata.client_capabilities()?.is_none() {
2631            return Err(CommonTypeError::Invalid("required request metadata"));
2632        }
2633        let _ = metadata.client_info()?;
2634        let _ = TraceContext::try_from_metadata(&metadata)?;
2635        Ok(())
2636    }
2637
2638    fn validate_content(content: &ContentBlock) -> Result<(), CommonTypeError> {
2639        match content {
2640            ContentBlock::Image {
2641                data,
2642                mime_type,
2643                annotations,
2644                ..
2645            } => {
2646                Self::validate_annotations(annotations)?;
2647                valid_binary_content(data, mime_type, "image/")
2648            }
2649            ContentBlock::Audio {
2650                data,
2651                mime_type,
2652                annotations,
2653                ..
2654            } => {
2655                Self::validate_annotations(annotations)?;
2656                valid_binary_content(data, mime_type, "audio/")
2657            }
2658            ContentBlock::ResourceLink {
2659                uri,
2660                icons,
2661                annotations,
2662                ..
2663            } => {
2664                Self::validate_annotations(annotations)?;
2665                Self::validate_icons(icons)?;
2666                AbsoluteUri::parse(uri.as_str()).map(|_| ())
2667            }
2668            ContentBlock::Resource {
2669                resource,
2670                annotations,
2671                ..
2672            } => {
2673                Self::validate_annotations(annotations)?;
2674                validate_embedded_resource(resource)?;
2675                match resource {
2676                    EmbeddedResourceContents::Text { uri, .. }
2677                    | EmbeddedResourceContents::Blob { uri, .. } => {
2678                        AbsoluteUri::parse(uri.as_str()).map(|_| ())
2679                    }
2680                }
2681            }
2682            ContentBlock::Text { annotations, .. } => Self::validate_annotations(annotations),
2683        }
2684    }
2685
2686    fn validate_annotations(annotations: &Option<Annotations>) -> Result<(), CommonTypeError> {
2687        if annotations
2688            .as_ref()
2689            .and_then(|value| value.priority)
2690            .is_some_and(|priority| !priority.is_finite() || !(0.0..=1.0).contains(&priority))
2691        {
2692            return Err(CommonTypeError::Invalid("annotation priority"));
2693        }
2694        Ok(())
2695    }
2696
2697    fn validate_icons(icons: &Option<Vec<RawIcon>>) -> Result<(), CommonTypeError> {
2698        if let Some(icons) = icons {
2699            for icon in icons {
2700                let _ = RawIcon::try_with_details(
2701                    icon.src.as_str(),
2702                    icon.mime_type.clone(),
2703                    icon.sizes.clone(),
2704                    icon.theme,
2705                )?;
2706            }
2707        }
2708        Ok(())
2709    }
2710
2711    fn validate_cancellation_params(params: &Value) -> Result<(), CommonTypeError> {
2712        let params = params
2713            .as_object()
2714            .ok_or(CommonTypeError::Invalid("cancellation params"))?;
2715        let request_id = params
2716            .get("requestId")
2717            .ok_or(CommonTypeError::Invalid("cancellation request ID"))?;
2718        let request_id = serde_json::from_value::<CancellationRequestId>(request_id.clone())
2719            .map_err(|_| CommonTypeError::Invalid("cancellation request ID"))?;
2720        let reason = match params.get("reason") {
2721            None => None,
2722            Some(Value::String(value)) => Some(value.clone()),
2723            Some(_) => return Err(CommonTypeError::Invalid("cancellation reason")),
2724        };
2725        match params.get("_meta") {
2726            None => {}
2727            Some(Value::Object(entries)) => {
2728                let _ = OpenMetadata::try_from_notification_entries(
2729                    entries.clone().into_iter().collect::<BTreeMap<_, _>>(),
2730                )?;
2731            }
2732            Some(_) => return Err(CommonTypeError::Invalid("cancellation metadata")),
2733        }
2734        let _ = CancellationNotification::try_new(request_id, reason)?;
2735        Ok(())
2736    }
2737}
2738
2739#[cfg(test)]
2740mod tests {
2741    use serde_json::json;
2742
2743    use super::*;
2744
2745    fn assert_json_integer_rejected_by_public_constructors(
2746        source: &str,
2747        expected: CommonTypeError,
2748    ) {
2749        assert_eq!(source.parse::<JsonInteger>(), Err(expected.clone()));
2750        assert_eq!(JsonInteger::try_from(source), Err(expected.clone()));
2751        let number = serde_json::from_str::<serde_json::Number>(source)
2752            .expect("bounded test token is valid JSON");
2753        assert_eq!(JsonInteger::try_from_number(number), Err(expected));
2754        assert!(
2755            serde_json::from_str::<JsonInteger>(source).is_err(),
2756            "deserialization must apply the same admission bound"
2757        );
2758    }
2759
2760    #[test]
2761    fn json_integer_bounded_i32_adapters_accept_equivalent_integral_spellings() {
2762        for (source, expected) in [
2763            ("-32600.0", -32_600),
2764            ("-326e2", -32_600),
2765            ("2147483647.0", i32::MAX),
2766            ("-2147483648e0", i32::MIN),
2767        ] {
2768            let value = JsonInteger::try_from(source).expect("integral JSON integer");
2769
2770            assert_eq!(value.as_i32(), Some(expected));
2771            assert_eq!(value.as_str(), source, "the input lexeme remains exact");
2772            assert_eq!(
2773                serde_json::to_string(&value).expect("integer serializes"),
2774                source,
2775                "serialization does not normalize the input lexeme"
2776            );
2777        }
2778    }
2779
2780    #[test]
2781    fn json_integer_from_value_accepts_full_width_integer_visitors() {
2782        for source in ["9007199254740993123456789", "-9007199254740993123456789"] {
2783            let value = serde_json::from_str::<Value>(source).expect("valid arbitrary-width JSON");
2784            let integer = serde_json::from_value::<JsonInteger>(value)
2785                .expect("Value replay retains an arbitrary-width mathematical integer");
2786
2787            assert_eq!(integer.as_str(), source);
2788        }
2789    }
2790
2791    #[test]
2792    fn json_integer_bounded_i32_adapters_reject_fractional_and_out_of_range_values() {
2793        for source in ["-32600.1", "2147483647.1"] {
2794            assert_eq!(
2795                JsonInteger::try_from(source),
2796                Err(CommonTypeError::Invalid("JSON integer")),
2797                "changing only the nonzero fractional digit rejects {source}"
2798            );
2799        }
2800
2801        for source in ["2147483648.0", "-2147483649e0"] {
2802            let value = source
2803                .parse::<JsonInteger>()
2804                .expect("exact out-of-range integer");
2805
2806            assert_eq!(value.as_i32(), None);
2807            assert_eq!(
2808                value.as_str(),
2809                source,
2810                "the out-of-range lexeme remains exact"
2811            );
2812        }
2813    }
2814
2815    #[test]
2816    fn json_integer_public_constructors_enforce_token_and_exponent_bounds() {
2817        let at_token_limit = "1".repeat(MAX_JSON_INTEGER_BYTES);
2818        for value in [
2819            at_token_limit
2820                .parse::<JsonInteger>()
2821                .expect("token at the retention bound parses"),
2822            JsonInteger::try_from(at_token_limit.as_str())
2823                .expect("TryFrom accepts token at the retention bound"),
2824            JsonInteger::try_from_number(
2825                serde_json::from_str::<serde_json::Number>(&at_token_limit)
2826                    .expect("token at the retention bound is JSON"),
2827            )
2828            .expect("number constructor accepts token at the retention bound"),
2829            serde_json::from_str::<JsonInteger>(&at_token_limit)
2830                .expect("deserialization accepts token at the retention bound"),
2831        ] {
2832            assert_eq!(value.as_str(), at_token_limit);
2833        }
2834        assert_json_integer_rejected_by_public_constructors(
2835            &format!("{at_token_limit}0"),
2836            CommonTypeError::TooLong("JSON integer"),
2837        );
2838
2839        let at_positive_exponent_limit = format!("1e{MAX_JSON_INTEGER_EXPONENT_ABS}");
2840        let at_negative_exponent_limit = format!("0e-{MAX_JSON_INTEGER_EXPONENT_ABS}");
2841        for source in [&at_positive_exponent_limit, &at_negative_exponent_limit] {
2842            assert!(source.parse::<JsonInteger>().is_ok(), "{source}");
2843            assert!(JsonInteger::try_from(source.as_str()).is_ok(), "{source}");
2844            assert!(
2845                JsonInteger::try_from_number(
2846                    serde_json::from_str::<serde_json::Number>(source)
2847                        .expect("exponent-bound token is JSON"),
2848                )
2849                .is_ok(),
2850                "{source}"
2851            );
2852            assert!(
2853                serde_json::from_str::<JsonInteger>(source).is_ok(),
2854                "{source}"
2855            );
2856        }
2857        assert_json_integer_rejected_by_public_constructors(
2858            &format!("1e{}", MAX_JSON_INTEGER_EXPONENT_ABS + 1),
2859            CommonTypeError::TooLong("JSON integer exponent"),
2860        );
2861        assert_json_integer_rejected_by_public_constructors(
2862            &format!("0e-{}", MAX_JSON_INTEGER_EXPONENT_ABS + 1),
2863            CommonTypeError::TooLong("JSON integer exponent"),
2864        );
2865    }
2866
2867    #[test]
2868    fn json_integer_from_str_and_try_from_preserve_huge_lexemes() {
2869        const HUGE: &str = "12345678901234567890123456789012345678901234567890";
2870
2871        let parsed = HUGE.parse::<JsonInteger>().expect("huge integer parses");
2872        let converted = JsonInteger::try_from(HUGE).expect("huge integer converts");
2873
2874        assert_eq!(parsed.as_str(), HUGE);
2875        assert_eq!(converted.as_str(), HUGE);
2876        assert_eq!(
2877            serde_json::to_string(&parsed).expect("huge integer serializes"),
2878            HUGE
2879        );
2880        let exponent = serde_json::from_str::<JsonInteger>("-326e2")
2881            .expect("integral exponent JSON token deserializes");
2882        assert_eq!(exponent.as_str(), "-326e2");
2883        assert_eq!(
2884            serde_json::to_string(&exponent).expect("deserialized exponent serializes"),
2885            "-326e2"
2886        );
2887        for invalid_json_number in ["01", "1."] {
2888            assert_eq!(
2889                JsonInteger::try_from(invalid_json_number),
2890                Err(CommonTypeError::Invalid("JSON integer")),
2891                "the string conversion only admits JSON number grammar"
2892            );
2893        }
2894    }
2895
2896    #[test]
2897    fn exact_finite_json_numbers_preserve_signed_lexemes_and_compare_mathematically() {
2898        let large = ExactNonNegativeJsonNumber::parse("123456789012345678901234567890")
2899            .expect("large integer exact progress number");
2900        let decimal = ExactNonNegativeJsonNumber::parse("1.20e+4")
2901            .expect("decimal exponent exact progress number");
2902        let equivalent = ExactNonNegativeJsonNumber::parse("12000.0")
2903            .expect("equivalent decimal exact progress number");
2904        let greater =
2905            ExactNonNegativeJsonNumber::parse("12000.0001").expect("greater exact progress number");
2906        let negative =
2907            ExactNonNegativeJsonNumber::parse("-1.20e+4").expect("negative exact progress number");
2908        let more_negative = ExactNonNegativeJsonNumber::parse("-12000.0001")
2909            .expect("more negative exact progress number");
2910
2911        assert_eq!(large.as_str(), "123456789012345678901234567890");
2912        assert_eq!(decimal.as_str(), "1.20e+4");
2913        assert_eq!(decimal, equivalent);
2914        assert!(greater > decimal);
2915        assert!(more_negative < negative);
2916        assert!(negative < decimal);
2917        assert_eq!(
2918            serde_json::to_string(&decimal).expect("exact progress number serializes"),
2919            "1.20e+4",
2920            "the decimal/exponent lexeme re-encodes without an IEEE-754 conversion"
2921        );
2922        assert_eq!(
2923            serde_json::to_string(&negative).expect("negative exact progress number serializes"),
2924            "-1.20e+4",
2925            "the signed decimal/exponent lexeme re-encodes without an IEEE-754 conversion"
2926        );
2927        assert_eq!(
2928            ExactNonNegativeJsonNumber::parse("1e10000"),
2929            Err(CommonTypeError::TooLong("progress number exponent")),
2930            "the exact comparison representation bounds decimal exponents"
2931        );
2932    }
2933
2934    #[test]
2935    fn exact_finite_json_number_deserialization_retains_direct_wire_exponents() {
2936        for source in ["1e400", "1.20e+4", "-7.30E-12"] {
2937            let number = serde_json::from_str::<ExactNonNegativeJsonNumber>(source)
2938                .expect("bounded exact progress number deserializes");
2939
2940            assert_eq!(number.as_str(), source);
2941            assert_eq!(
2942                serde_json::to_string(&number).expect("exact progress number re-serializes"),
2943                source
2944            );
2945        }
2946    }
2947
2948    #[test]
2949    fn exact_finite_json_number_deserialization_rejects_only_the_bound_violation() {
2950        let accepted = serde_json::from_str::<ExactNonNegativeJsonNumber>("1e9999")
2951            .expect("largest admitted exponent deserializes");
2952        assert_eq!(accepted.as_str(), "1e9999");
2953
2954        assert!(
2955            serde_json::from_str::<ExactNonNegativeJsonNumber>("1e10000").is_err(),
2956            "only the exponent bound changes from the accepted token"
2957        );
2958        assert_eq!(
2959            serde_json::to_string(&accepted).expect("accepted boundary re-serializes"),
2960            "1e9999",
2961            "rejecting the adjacent exponent cannot mutate the accepted value"
2962        );
2963
2964        let at_byte_limit = "1".repeat(MAX_EXACT_PROGRESS_NUMBER_BYTES);
2965        let bounded = serde_json::from_str::<ExactNonNegativeJsonNumber>(&at_byte_limit)
2966            .expect("an exact progress number at the byte ceiling deserializes");
2967        assert_eq!(bounded.as_str(), at_byte_limit);
2968        let oversized = format!("1{}", "0".repeat(MAX_EXACT_PROGRESS_NUMBER_BYTES));
2969        assert_eq!(oversized.len(), MAX_EXACT_PROGRESS_NUMBER_BYTES + 1);
2970        assert!(
2971            serde_json::from_str::<ExactNonNegativeJsonNumber>(&oversized).is_err(),
2972            "adding one byte is the only changed dimension and exceeds the byte ceiling"
2973        );
2974        assert_eq!(
2975            serde_json::to_string(&bounded).expect("bounded number re-serializes"),
2976            at_byte_limit,
2977            "the over-limit rejection cannot alter the admitted 256-byte lexeme"
2978        );
2979    }
2980
2981    #[test]
2982    fn exact_finite_json_number_deserialization_handles_all_finite_number_visitors() {
2983        let from_i64 = <ExactNonNegativeJsonNumber as serde::Deserialize>::deserialize(
2984            serde::de::value::I64Deserializer::<serde::de::value::Error>::new(i64::MIN),
2985        )
2986        .expect("i64 visitor admits a finite JSON number");
2987        let from_u64 = <ExactNonNegativeJsonNumber as serde::Deserialize>::deserialize(
2988            serde::de::value::U64Deserializer::<serde::de::value::Error>::new(u64::MAX),
2989        )
2990        .expect("u64 visitor admits a finite JSON number");
2991        let from_i128 = <ExactNonNegativeJsonNumber as serde::Deserialize>::deserialize(
2992            serde::de::value::I128Deserializer::<serde::de::value::Error>::new(i128::MIN),
2993        )
2994        .expect("i128 visitor admits a finite JSON number");
2995        let from_u128 = <ExactNonNegativeJsonNumber as serde::Deserialize>::deserialize(
2996            serde::de::value::U128Deserializer::<serde::de::value::Error>::new(u128::MAX),
2997        )
2998        .expect("u128 visitor admits a finite JSON number");
2999        let from_f64 = <ExactNonNegativeJsonNumber as serde::Deserialize>::deserialize(
3000            serde::de::value::F64Deserializer::<serde::de::value::Error>::new(1.25),
3001        )
3002        .expect("finite f64 visitor admits a JSON number");
3003
3004        assert_eq!(from_i64.as_str(), "-9223372036854775808");
3005        assert_eq!(from_u64.as_str(), "18446744073709551615");
3006        assert_eq!(
3007            from_i128.as_str(),
3008            "-170141183460469231731687303715884105728"
3009        );
3010        assert_eq!(
3011            from_u128.as_str(),
3012            "340282366920938463463374607431768211455"
3013        );
3014        assert_eq!(from_f64.as_str(), "1.25");
3015
3016        assert!(
3017            <ExactNonNegativeJsonNumber as serde::Deserialize>::deserialize(
3018                serde::de::value::F64Deserializer::<serde::de::value::Error>::new(f64::NAN),
3019            )
3020            .is_err(),
3021            "changing only the finite f64 to NaN must fail closed"
3022        );
3023        assert_eq!(
3024            serde_json::to_string(&from_f64).expect("finite f64 result re-serializes"),
3025            "1.25",
3026            "the rejected non-finite visitor cannot change the prior finite result"
3027        );
3028    }
3029
3030    #[test]
3031    fn exact_finite_json_number_value_replay_uses_the_value_number_representation() {
3032        let direct = serde_json::from_str::<ExactNonNegativeJsonNumber>("1.20e+4")
3033            .expect("direct raw wire lexeme is admitted");
3034        let value = serde_json::from_str::<Value>("1.20e+4")
3035            .expect("the same raw number enters a serde Value replay");
3036        let expected = value
3037            .as_number()
3038            .expect("Value remains a number")
3039            .as_str()
3040            .to_owned();
3041        let replayed = serde_json::from_value::<ExactNonNegativeJsonNumber>(value)
3042            .expect("Value replay admits the finite number representation");
3043
3044        assert_eq!(direct.as_str(), "1.20e+4");
3045        assert_eq!(replayed.as_str(), expected);
3046        assert_eq!(
3047            serde_json::to_string(&replayed).expect("replayed number re-serializes"),
3048            replayed.as_str(),
3049            "replay must not apply another numeric normalization"
3050        );
3051    }
3052
3053    #[test]
3054    fn prt_02_a_positive() {
3055        let implementation = Implementation::try_new("fastmcp", "0.1.0").expect("implementation");
3056        let metadata = OpenMetadata::try_from_entries([
3057            ("".to_owned(), json!("empty name is valid")),
3058            ("com.example/".to_owned(), json!({"future": true})),
3059            (
3060                "io.modelcontextprotocol/protocolVersion".to_owned(),
3061                json!("2026-07-28"),
3062            ),
3063            (
3064                "io.modelcontextprotocol/clientCapabilities".to_owned(),
3065                json!({}),
3066            ),
3067            (
3068                "io.modelcontextprotocol/clientInfo".to_owned(),
3069                serde_json::to_value(&implementation).expect("identity JSON"),
3070            ),
3071            (
3072                "traceparent".to_owned(),
3073                json!("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"),
3074            ),
3075        ])
3076        .expect("metadata");
3077        assert_eq!(
3078            metadata.protocol_version().expect("version"),
3079            Some("2026-07-28")
3080        );
3081        assert_eq!(
3082            TraceContext::try_from_metadata(&metadata)
3083                .expect("trace")
3084                .traceparent
3085                .as_deref(),
3086            Some("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00")
3087        );
3088
3089        let icon = RawIcon::try_new("https://example.test/icon.png?variant=1#exact").expect("icon");
3090        assert!(icon.effective_any_size());
3091        assert_eq!(
3092            OpaqueCursor::from_presence(Some(String::new())).as_present(),
3093            Some("")
3094        );
3095        let content = ContentBlock::image("aGVsbG8=", "image/png").expect("image");
3096        let encoded = serde_json::to_value(&content).expect("serialize content");
3097        assert_eq!(encoded["type"], "image");
3098        assert_eq!(
3099            serde_json::from_value::<ContentBlock>(encoded).expect("round trip"),
3100            content
3101        );
3102    }
3103
3104    #[test]
3105    fn prt_02_a_planted_negative() {
3106        let accepted = OpenMetadata::try_from_entries([(
3107            "com.example/valid".to_owned(),
3108            json!({"kept": true}),
3109        )])
3110        .expect("accepted baseline");
3111        let baseline = accepted.clone();
3112        let rejection = OpenMetadata::try_from_entries([(
3113            "com..example/valid".to_owned(),
3114            json!({"kept": true}),
3115        )]);
3116        assert_eq!(rejection, Err(CommonTypeError::Invalid("metadata key")));
3117        assert_eq!(
3118            accepted, baseline,
3119            "the rejected one-variable key change cannot mutate accepted state"
3120        );
3121    }
3122
3123    #[test]
3124    fn notification_metadata_remains_schema_open_but_bounded() {
3125        let at_entry_limit =
3126            (0..MAX_METADATA_ENTRIES).map(|index| (format!("com.example/key{index}"), Value::Null));
3127        OpenMetadata::try_from_notification_entries(at_entry_limit)
3128            .expect("notification metadata accepts N entries");
3129        let over_entry_limit = (0..=MAX_METADATA_ENTRIES)
3130            .map(|index| (format!("com.example/key{index}"), Value::Null));
3131        assert_eq!(
3132            OpenMetadata::try_from_notification_entries(over_entry_limit),
3133            Err(CommonTypeError::Invalid("metadata key"))
3134        );
3135
3136        let at_key_limit = "a".repeat(MAX_METADATA_KEY_BYTES);
3137        OpenMetadata::try_from_notification_entries([(at_key_limit, Value::Null)])
3138            .expect("notification metadata accepts an N-byte key");
3139        let over_key_limit = "a".repeat(MAX_METADATA_KEY_BYTES + 1);
3140        assert_eq!(
3141            OpenMetadata::try_from_notification_entries([(over_key_limit, Value::Null)]),
3142            Err(CommonTypeError::Invalid("metadata key"))
3143        );
3144
3145        let at_value_limit = json!("x".repeat(MAX_METADATA_VALUE_BYTES - 2));
3146        assert_eq!(
3147            serde_json::to_vec(&at_value_limit)
3148                .expect("bounded metadata value serializes")
3149                .len(),
3150            MAX_METADATA_VALUE_BYTES
3151        );
3152        OpenMetadata::try_from_notification_entries([("future".to_owned(), at_value_limit)])
3153            .expect("notification metadata accepts an N-byte value");
3154        let over_value_limit = json!("x".repeat(MAX_METADATA_VALUE_BYTES - 1));
3155        assert_eq!(
3156            serde_json::to_vec(&over_value_limit)
3157                .expect("oversized metadata value serializes")
3158                .len(),
3159            MAX_METADATA_VALUE_BYTES + 1
3160        );
3161        assert_eq!(
3162            OpenMetadata::try_from_notification_entries([("future".to_owned(), over_value_limit,)]),
3163            Err(CommonTypeError::Invalid("metadata key"))
3164        );
3165
3166        OpenMetadata::try_from_notification_entries([(
3167            "io.modelcontextprotocol/futureCancellationHint".to_owned(),
3168            json!({"schemaOpen": true}),
3169        )])
3170        .expect("unknown reserved notification metadata remains inert and admitted");
3171    }
3172
3173    #[test]
3174    fn prt_02_b_positive() {
3175        let request = json!({
3176            "_meta": {
3177                "com.example/future": {"nullIsData": null},
3178                "io.modelcontextprotocol/clientCapabilities": {},
3179                "io.modelcontextprotocol/protocolVersion": "2026-07-28",
3180                "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"
3181            }
3182        });
3183        FinalCommonTypesSchema::validate(CommonWireDirection::Request, &request)
3184            .expect("request metadata schema");
3185        let golden = "{\"_meta\":{\"com.example/future\":{\"nullIsData\":null},\"io.modelcontextprotocol/clientCapabilities\":{},\"io.modelcontextprotocol/protocolVersion\":\"2026-07-28\",\"traceparent\":\"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00\"}}";
3186        assert_eq!(
3187            FinalCommonTypesSchema::canonical_json(&request).expect("canonical JSON"),
3188            golden
3189        );
3190        FinalCommonTypesSchema::validate_golden(CommonWireDirection::Request, &request, golden)
3191            .expect("exact request golden");
3192
3193        let content = ContentBlock::image("aGVsbG8=", "image/png").expect("image content");
3194        let wire = serde_json::to_value(&content).expect("content wire");
3195        FinalCommonTypesSchema::validate(CommonWireDirection::Result, &wire)
3196            .expect("content schema");
3197        assert_eq!(wire["type"], "image");
3198        assert_eq!(
3199            serde_json::from_value::<ContentBlock>(wire).expect("content round trip"),
3200            content
3201        );
3202        assert_eq!(
3203            OpaqueCursor::try_from_presence(Some(String::new()))
3204                .expect("bounded empty cursor")
3205                .as_present(),
3206            Some("")
3207        );
3208        let icon = json!({
3209            "src": "HTTPS://example.test/icon.svg?variant=1",
3210            "sizes": [],
3211            "theme": "dark"
3212        });
3213        let icon = FinalCommonTypesSchema::validate_icon(&icon).expect("icon schema");
3214        assert!(
3215            !icon.effective_any_size(),
3216            "present empty sizes stay present"
3217        );
3218        let cancellation = json!({
3219            "method": "notifications/cancelled",
3220            "params": {"requestId": 9, "reason": "bounded"}
3221        });
3222        FinalCommonTypesSchema::validate(CommonWireDirection::Notification, &cancellation)
3223            .expect("notification-only cancellation");
3224        let bounded_cursor = OpaqueCursor::try_from_presence(Some("x".repeat(MAX_CURSOR_BYTES)))
3225            .expect("cursor at exact bound");
3226        assert_eq!(
3227            bounded_cursor.as_present().map(str::len),
3228            Some(MAX_CURSOR_BYTES)
3229        );
3230        assert_eq!(FinalCommonTypesSchema::FINAL_URI_OWNERS.len(), 12);
3231    }
3232
3233    #[test]
3234    fn final_common_content_bridge_round_trips_complete_resource_link() {
3235        let icon = RawIcon::try_with_details(
3236            "https://example.test/icons/report.svg",
3237            Some("image/svg+xml".to_owned()),
3238            Some(vec!["48x48".to_owned(), "any".to_owned()]),
3239            Some(IconTheme::Dark),
3240        )
3241        .expect("final icon");
3242        let annotations = Annotations {
3243            audience: Some(vec![
3244                AnnotationAudience::User,
3245                AnnotationAudience::Assistant,
3246            ]),
3247            priority: Some(0.75),
3248            last_modified: Some("2026-07-28T15:00:58Z".to_owned()),
3249            additional: BTreeMap::new(),
3250        };
3251        let metadata = OpenMetadata::try_from_entries([(
3252            "com.example/renderHint".to_owned(),
3253            json!({"preserve": true}),
3254        )])
3255        .expect("content metadata");
3256        let resource_link = ResourceLink {
3257            icons: Some(vec![icon.clone()]),
3258            name: "report".to_owned(),
3259            title: Some("Quarterly report".to_owned()),
3260            uri: AbsoluteUri::parse("https://example.test/reports/q3").expect("resource URI"),
3261            description: Some("Raw quarterly figures".to_owned()),
3262            mime_type: Some("text/markdown".to_owned()),
3263            annotations: Some(annotations.clone()),
3264            size: Some(JsonInteger::from(4096_i64)),
3265            meta: Some(metadata.clone()),
3266            additional: BTreeMap::new(),
3267        };
3268        let resource_link_wire = serde_json::to_value(&resource_link).expect("resource link");
3269        assert_eq!(resource_link_wire["type"], "resource_link");
3270        assert_eq!(
3271            resource_link_wire["icons"][0]["sizes"],
3272            json!(["48x48", "any"])
3273        );
3274        assert_eq!(resource_link_wire["icons"][0]["theme"], "dark");
3275        assert_eq!(
3276            resource_link_wire["annotations"]["audience"],
3277            json!(["user", "assistant"])
3278        );
3279        assert_eq!(
3280            resource_link_wire["_meta"]["com.example/renderHint"]["preserve"],
3281            true
3282        );
3283        assert_eq!(
3284            serde_json::from_value::<ResourceLink>(resource_link_wire.clone())
3285                .expect("resource link round trip"),
3286            resource_link
3287        );
3288        FinalCommonTypesSchema::validate(CommonWireDirection::Result, &resource_link_wire)
3289            .expect("final resource link schema");
3290
3291        let content = ContentBlock::ResourceLink {
3292            icons: Some(vec![icon]),
3293            name: "report".to_owned(),
3294            title: Some("Quarterly report".to_owned()),
3295            uri: AbsoluteUri::parse("https://example.test/reports/q3").expect("content URI"),
3296            description: Some("Raw quarterly figures".to_owned()),
3297            mime_type: Some("text/markdown".to_owned()),
3298            annotations: Some(annotations),
3299            size: Some(JsonInteger::from(4096_i64)),
3300            meta: Some(metadata),
3301            additional: BTreeMap::new(),
3302        };
3303        let content_wire = serde_json::to_value(&content).expect("content wire");
3304        assert_eq!(
3305            serde_json::from_value::<ContentBlock>(content_wire).expect("content round trip"),
3306            content
3307        );
3308
3309        for (level, wire) in [
3310            (LoggingLevel::Debug, "debug"),
3311            (LoggingLevel::Info, "info"),
3312            (LoggingLevel::Notice, "notice"),
3313            (LoggingLevel::Warning, "warning"),
3314            (LoggingLevel::Error, "error"),
3315            (LoggingLevel::Critical, "critical"),
3316            (LoggingLevel::Alert, "alert"),
3317            (LoggingLevel::Emergency, "emergency"),
3318        ] {
3319            assert_eq!(serde_json::to_value(level).expect("logging level"), wire);
3320            assert_eq!(
3321                serde_json::from_value::<LoggingLevel>(json!(wire)).expect("logging level"),
3322                level
3323            );
3324        }
3325    }
3326
3327    #[test]
3328    fn final_resource_link_rejects_legacy_icon_sizes_without_mutating_accepted_wire() {
3329        let accepted = json!({
3330            "type": "resource_link",
3331            "icons": [{
3332                "src": "https://example.test/icons/report.svg",
3333                "sizes": ["48x48"],
3334                "theme": "dark"
3335            }],
3336            "name": "report",
3337            "uri": "https://example.test/reports/q3"
3338        });
3339        FinalCommonTypesSchema::validate(CommonWireDirection::Result, &accepted)
3340            .expect("accepted final resource link");
3341        let baseline = accepted.clone();
3342        let mut planted = accepted.clone();
3343        planted["icons"][0]["sizes"] = json!("48x48");
3344        assert_eq!(
3345            FinalCommonTypesSchema::validate(CommonWireDirection::Result, &planted),
3346            Err(CommonTypeError::Invalid("content block"))
3347        );
3348        assert_eq!(
3349            accepted, baseline,
3350            "the rejected one-field legacy size spelling cannot mutate final wire state"
3351        );
3352    }
3353
3354    #[test]
3355    fn final_resource_link_size_is_an_optional_integer() {
3356        let accepted: Value = serde_json::from_str(
3357            r#"{
3358                "type":"resource_link",
3359                "name":"report",
3360                "uri":"https://example.test/reports/q3",
3361                "size":922337203685477580812345678901234567890
3362            }"#,
3363        )
3364        .expect("large integer wire parses");
3365        let resource_link: ResourceLink = serde_json::from_value(accepted.clone())
3366            .expect("integer resource-link size is admitted");
3367        assert_eq!(
3368            resource_link.size.as_ref().map(JsonInteger::as_str),
3369            Some("922337203685477580812345678901234567890")
3370        );
3371        assert_eq!(
3372            serde_json::to_value(&resource_link).expect("integer resource-link size encodes"),
3373            accepted
3374        );
3375
3376        let negative: Value = serde_json::from_str(
3377            r#"{
3378                "type":"resource_link",
3379                "name":"report",
3380                "uri":"https://example.test/reports/q3",
3381                "size":-922337203685477580812345678901234567890
3382            }"#,
3383        )
3384        .expect("large negative integer wire parses");
3385        let negative_link: ResourceLink = serde_json::from_value(negative.clone())
3386            .expect("a schema-integer resource-link size may be negative");
3387        assert_eq!(
3388            negative_link.size.as_ref().map(JsonInteger::as_str),
3389            Some("-922337203685477580812345678901234567890")
3390        );
3391        assert_eq!(
3392            serde_json::to_value(&negative_link)
3393                .expect("negative integer resource-link size encodes"),
3394            negative
3395        );
3396
3397        let negative_content: ContentBlock = serde_json::from_value(negative.clone())
3398            .expect("content resource links preserve schema-integer sizes");
3399        assert_eq!(
3400            serde_json::to_value(&negative_content)
3401                .expect("negative content resource-link size encodes"),
3402            negative
3403        );
3404
3405        let missing = json!({
3406            "type": "resource_link",
3407            "name": "report",
3408            "uri": "https://example.test/reports/q3"
3409        });
3410        let missing_size: ResourceLink =
3411            serde_json::from_value(missing.clone()).expect("resource-link size is optional");
3412        assert_eq!(missing_size.size, None);
3413        assert_eq!(
3414            serde_json::to_value(missing_size).expect("absent size remains absent"),
3415            missing
3416        );
3417
3418        let wrong_type = json!({
3419            "type": "resource_link",
3420            "name": "report",
3421            "uri": "https://example.test/reports/q3",
3422            "size": 4096.5
3423        });
3424        assert!(
3425            serde_json::from_value::<ResourceLink>(wrong_type).is_err(),
3426            "a fractional resource-link size is not an integer"
3427        );
3428    }
3429
3430    #[test]
3431    fn final_common_types_preserve_schema_allowed_additional_properties() {
3432        let implementation = json!({
3433            "name": "FastMCP",
3434            "version": "0.1",
3435            "com.example/implementation": {"stable": true}
3436        });
3437        let implementation: Implementation = serde_json::from_value(implementation.clone())
3438            .expect("schema-allowed implementation property is retained");
3439        assert_eq!(
3440            implementation.additional.get("com.example/implementation"),
3441            Some(&json!({"stable": true}))
3442        );
3443        assert_eq!(
3444            serde_json::to_value(&implementation).expect("implementation property re-emits"),
3445            json!({
3446                "name": "FastMCP",
3447                "version": "0.1",
3448                "com.example/implementation": {"stable": true}
3449            })
3450        );
3451
3452        let resource_link = json!({
3453            "type": "resource_link",
3454            "name": "report",
3455            "uri": "https://example.test/reports/q3",
3456            "com.example/resourceLink": ["preserved"]
3457        });
3458        let resource_link: ResourceLink = serde_json::from_value(resource_link.clone())
3459            .expect("schema-allowed resource-link property is retained");
3460        assert_eq!(
3461            resource_link.additional.get("com.example/resourceLink"),
3462            Some(&json!(["preserved"]))
3463        );
3464        assert_eq!(
3465            serde_json::to_value(&resource_link).expect("resource-link property re-emits"),
3466            json!({
3467                "type": "resource_link",
3468                "name": "report",
3469                "uri": "https://example.test/reports/q3",
3470                "com.example/resourceLink": ["preserved"]
3471            })
3472        );
3473        FinalCommonTypesSchema::validate(
3474            CommonWireDirection::Result,
3475            &serde_json::to_value(&resource_link).expect("resource-link validation wire"),
3476        )
3477        .expect("schema-allowed resource-link property remains valid");
3478
3479        let content = json!({
3480            "type": "text",
3481            "text": "report ready",
3482            "com.example/content": {"priority": "display"}
3483        });
3484        let content: ContentBlock = serde_json::from_value(content.clone())
3485            .expect("schema-allowed content property is retained");
3486        assert_eq!(
3487            serde_json::to_value(&content).expect("content property re-emits"),
3488            json!({
3489                "type": "text",
3490                "text": "report ready",
3491                "com.example/content": {"priority": "display"}
3492            })
3493        );
3494        FinalCommonTypesSchema::validate(
3495            CommonWireDirection::Result,
3496            &serde_json::to_value(&content).expect("content validation wire"),
3497        )
3498        .expect("schema-allowed content property remains valid");
3499    }
3500
3501    #[test]
3502    fn final_common_nested_open_fields_and_subscription_integer_round_trip() {
3503        let resource_link = json!({
3504            "type": "resource_link",
3505            "icons": [{
3506                "src": "https://example.test/icons/report.svg",
3507                "com.example/icon": {"retained": true}
3508            }],
3509            "name": "report",
3510            "uri": "https://example.test/reports/q3",
3511            "annotations": {
3512                "com.example/annotation": ["retained"]
3513            }
3514        });
3515        let resource_link: ResourceLink = serde_json::from_value(resource_link.clone())
3516            .expect("schema-open icon and annotation fields decode");
3517        assert_eq!(
3518            serde_json::to_value(&resource_link).expect("nested extensions re-encode"),
3519            json!({
3520                "type": "resource_link",
3521                "icons": [{
3522                    "src": "https://example.test/icons/report.svg",
3523                    "com.example/icon": {"retained": true}
3524                }],
3525                "name": "report",
3526                "uri": "https://example.test/reports/q3",
3527                "annotations": {
3528                    "com.example/annotation": ["retained"]
3529                }
3530            })
3531        );
3532
3533        let embedded = json!({
3534            "type": "resource",
3535            "resource": {
3536                "uri": "https://example.test/resources/report",
3537                "text": "ready",
3538                "_meta": {"com.example/source": "cache"},
3539                "com.example/resource": {"retained": true}
3540            }
3541        });
3542        let embedded_content: ContentBlock = serde_json::from_value(embedded.clone())
3543            .expect("embedded resource metadata and open fields decode");
3544        assert_eq!(
3545            serde_json::to_value(&embedded_content).expect("embedded resource re-encodes"),
3546            embedded
3547        );
3548
3549        let sampling = json!({
3550            "type": "tool_result",
3551            "toolUseId": "call-7",
3552            "content": [{"type": "text", "text": "done"}],
3553            "com.example/sampling": {"retained": true}
3554        });
3555        let sampling_content: SamplingContentBlock =
3556            serde_json::from_value(sampling.clone()).expect("sampling extension decodes");
3557        assert_eq!(
3558            serde_json::to_value(&sampling_content).expect("sampling extension re-encodes"),
3559            sampling
3560        );
3561
3562        let subscription: Value = serde_json::from_str(
3563            r#"{
3564                "io.modelcontextprotocol/subscriptionId":922337203685477580812345678901234567890
3565            }"#,
3566        )
3567        .expect("large subscription ID wire parses");
3568        let metadata: OpenMetadata = serde_json::from_value(subscription.clone())
3569            .expect("arbitrary-precision subscription ID decodes");
3570        assert_eq!(
3571            serde_json::to_value(metadata).expect("subscription ID re-encodes"),
3572            subscription
3573        );
3574
3575        let cancellation: Value = serde_json::from_str(
3576            r#"{
3577                "method":"notifications/cancelled",
3578                "params":{"requestId":922337203685477580812345678901234567890}
3579            }"#,
3580        )
3581        .expect("large cancellation ID wire parses");
3582        FinalCommonTypesSchema::validate(CommonWireDirection::Notification, &cancellation)
3583            .expect("arbitrary-precision cancellation ID is admitted");
3584        assert!(matches!(
3585            serde_json::from_value::<CancellationRequestId>(
3586                cancellation["params"]["requestId"].clone()
3587            )
3588            .expect("large cancellation ID decodes"),
3589            CancellationRequestId::IntegerExact(value)
3590                if value.as_str() == "922337203685477580812345678901234567890"
3591        ));
3592    }
3593
3594    #[test]
3595    fn prt_02_b_planted_negative() {
3596        let accepted = json!({
3597            "_meta": {
3598                "com.example/future": {"kept": true},
3599                "io.modelcontextprotocol/clientCapabilities": {},
3600                "io.modelcontextprotocol/protocolVersion": "2026-07-28"
3601            }
3602        });
3603        FinalCommonTypesSchema::validate(CommonWireDirection::Request, &accepted)
3604            .expect("accepted baseline");
3605        let baseline = accepted.clone();
3606        let mut planted = accepted.clone();
3607        let meta = planted
3608            .get_mut("_meta")
3609            .and_then(Value::as_object_mut)
3610            .expect("metadata object");
3611        let preserved = meta
3612            .remove("com.example/future")
3613            .expect("one valid open key");
3614        meta.insert("io.modelcontextprotocol/future".to_owned(), preserved);
3615        assert_eq!(
3616            FinalCommonTypesSchema::validate(CommonWireDirection::Request, &planted),
3617            Err(CommonTypeError::Invalid("metadata key"))
3618        );
3619        assert_eq!(
3620            accepted, baseline,
3621            "the one-key rejection cannot mutate retained wire state"
3622        );
3623    }
3624
3625    #[test]
3626    fn cancellation_request_id_preserves_integer_lexemes_and_rejects_fractional_values() {
3627        let accepted: Value = serde_json::from_str(
3628            r#"{"method":"notifications/cancelled","params":{"requestId":-0}}"#,
3629        )
3630        .expect("negative-zero cancellation wire parses");
3631        FinalCommonTypesSchema::validate(CommonWireDirection::Notification, &accepted)
3632            .expect("a schema-valid negative-zero cancellation ID is admitted");
3633        let typed: CancellationRequestId =
3634            serde_json::from_value(accepted["params"]["requestId"].clone())
3635                .expect("negative-zero cancellation ID decodes");
3636        // The pinned serde_json (=1.0.151, arbitrary_precision) normalizes
3637        // every natively representable number at parse time, so a wire `-0`
3638        // reaches typed decoding as the integer 0; exact-lexeme retention
3639        // applies only to integers that overflow the native representations.
3640        assert!(matches!(&typed, CancellationRequestId::Integer(0)));
3641        assert_eq!(
3642            serde_json::to_value(&typed).expect("negative-zero ID re-encodes"),
3643            serde_json::json!(0),
3644            "a natively representable cancellation ID round-trips by value"
3645        );
3646
3647        let baseline = accepted.clone();
3648        let mut planted = accepted.clone();
3649        planted
3650            .get_mut("params")
3651            .and_then(Value::as_object_mut)
3652            .expect("cancellation parameter object")
3653            .insert(
3654                "requestId".to_owned(),
3655                serde_json::from_str("-0.5").expect("fractional JSON value parses"),
3656            );
3657        assert_eq!(
3658            FinalCommonTypesSchema::validate(CommonWireDirection::Notification, &planted),
3659            Err(CommonTypeError::Invalid("cancellation request ID")),
3660            "changing only requestId to a fractional value rejects cancellation"
3661        );
3662        assert!(
3663            serde_json::from_value::<CancellationRequestId>(planted["params"]["requestId"].clone())
3664                .is_err(),
3665            "typed cancellation ID decoding rejects the same fractional field"
3666        );
3667        assert_eq!(
3668            serde_json::to_value(&typed).expect("accepted ID remains serializable"),
3669            baseline["params"]["requestId"].clone(),
3670            "fractional rejection cannot mutate the admitted cancellation ID"
3671        );
3672    }
3673
3674    #[test]
3675    fn final_sampling_tool_content_round_trips_without_widening_general_content() {
3676        let wire = json!({
3677            "type": "tool_result",
3678            "toolUseId": "call-7",
3679            "content": [{"type": "text", "text": "done"}],
3680            "structuredContent": {"ok": true},
3681            "_meta": {"com.example/cache": "hit"}
3682        });
3683        let content: SamplingContentBlock =
3684            serde_json::from_value(wire.clone()).expect("final tool-result content is admitted");
3685        assert!(matches!(content, SamplingContentBlock::ToolResult { .. }));
3686        assert_eq!(
3687            serde_json::to_value(&content).expect("tool-result re-encodes"),
3688            wire
3689        );
3690
3691        assert!(
3692            serde_json::from_value::<ContentBlock>(wire).is_err(),
3693            "sampling-only tool_result never widens the general content union"
3694        );
3695    }
3696
3697    #[test]
3698    fn final_sampling_tool_result_preserves_absent_and_explicit_null_structured_content() {
3699        let absent_wire = json!({
3700            "type": "tool_result",
3701            "toolUseId": "call-8",
3702            "content": []
3703        });
3704        let absent: SamplingContentBlock =
3705            serde_json::from_value(absent_wire.clone()).expect("absent structuredContent is valid");
3706        assert_eq!(
3707            serde_json::to_value(absent).expect("absent structuredContent re-encodes"),
3708            absent_wire
3709        );
3710
3711        let null_wire = json!({
3712            "type": "tool_result",
3713            "toolUseId": "call-8",
3714            "content": [],
3715            "structuredContent": null
3716        });
3717        let explicit_null: SamplingContentBlock = serde_json::from_value(null_wire.clone())
3718            .expect("explicit-null structuredContent is a present JSON value");
3719        assert!(matches!(
3720            &explicit_null,
3721            SamplingContentBlock::ToolResult {
3722                structured_content: Some(Value::Null),
3723                ..
3724            }
3725        ));
3726        assert_eq!(
3727            serde_json::to_value(explicit_null).expect("explicit null re-encodes"),
3728            null_wire
3729        );
3730    }
3731}