Skip to main content

fastmcp_protocol/
messages.rs

1//! MCP protocol messages.
2//!
3//! Request and response types for the MCP methods currently implemented here.
4
5use std::collections::BTreeMap;
6
7use serde::de::{DeserializeOwned, DeserializeSeed, MapAccess, Visitor};
8use serde::ser::SerializeMap;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use serde_json::Value;
11
12use crate::common_types::{
13    AbsoluteUri, ContentBlock, EmbeddedResourceContents, ExactNonNegativeJsonNumber,
14    Implementation, JsonInteger, LoggingLevel, OpenMetadata,
15};
16use crate::jsonrpc::{JsonRpcRequest, JsonRpcResponse, RequestId};
17use crate::methods::{
18    COMPLETION_COMPLETE, Final2026EnvelopeKind, Final2026Peer, INITIALIZE, LOGGING_SET_LEVEL,
19    NOTIFICATIONS_CANCELLED, NOTIFICATIONS_MESSAGE, NOTIFICATIONS_PROGRESS,
20    NOTIFICATIONS_PROMPTS_LIST_CHANGED, NOTIFICATIONS_RESOURCES_LIST_CHANGED,
21    NOTIFICATIONS_RESOURCES_UPDATED, NOTIFICATIONS_SUBSCRIPTIONS_ACKNOWLEDGED,
22    NOTIFICATIONS_TOOLS_LIST_CHANGED, PING, PROMPTS_GET, PROMPTS_LIST, RESOURCES_LIST,
23    RESOURCES_READ, RESOURCES_SUBSCRIBE, RESOURCES_TEMPLATES_LIST, RESOURCES_UNSUBSCRIBE,
24    SAMPLING_CREATE_MESSAGE, SERVER_DISCOVER, SUBSCRIPTIONS_LISTEN, TOOLS_CALL, TOOLS_LIST,
25    final_2026_07_28_method,
26};
27use crate::protocol_policy::ProtocolEra;
28use crate::protocol_version::{FINAL_PROTOCOL_VERSION, RequestVersionMetadata};
29use crate::result::{
30    CompleteResult, CoreResultDiscriminatorPolicy, DecodedResult, ExactJsonObject, ExactJsonValue,
31    FinalResultMetadataRole, InputRequiredResult, ResultDecodeError, ResultPeerDiagnostic,
32    UnknownResultMembers, decode_peer_result_for_era_with_metadata_role, deserialize_exact_object,
33    encode_complete_result, encode_result, exact_json_to_serde, has_final_only_metadata,
34};
35use crate::types::{
36    ClientCapabilities, ClientInfo, LegacyContent, LegacyMetadata, LegacyPromptMessage,
37    LegacyResourceContent, Prompt, Resource, ResourceTemplate, ServerCapabilities, ServerInfo,
38    Tool,
39};
40
41// ============================================================================
42// Progress Marker
43// ============================================================================
44
45/// Progress marker used to correlate progress notifications with requests.
46///
47/// Per MCP spec, progress markers can be either strings or arbitrary-width
48/// JSON integers.
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(untagged)]
51pub enum ProgressMarker {
52    /// String progress marker.
53    String(String),
54    /// Arbitrary-width JSON integer progress marker.
55    Number(JsonInteger),
56}
57
58impl From<String> for ProgressMarker {
59    fn from(s: String) -> Self {
60        ProgressMarker::String(s)
61    }
62}
63
64impl From<&str> for ProgressMarker {
65    fn from(s: &str) -> Self {
66        ProgressMarker::String(s.to_owned())
67    }
68}
69
70impl From<i64> for ProgressMarker {
71    fn from(n: i64) -> Self {
72        ProgressMarker::Number(JsonInteger::from(n))
73    }
74}
75
76impl From<JsonInteger> for ProgressMarker {
77    fn from(n: JsonInteger) -> Self {
78        Self::Number(n)
79    }
80}
81
82impl std::hash::Hash for ProgressMarker {
83    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
84        match self {
85            Self::String(value) => {
86                std::hash::Hash::hash(&0_u8, state);
87                std::hash::Hash::hash(value, state);
88            }
89            Self::Number(value) => {
90                std::hash::Hash::hash(&1_u8, state);
91                std::hash::Hash::hash(value.as_str(), state);
92            }
93        }
94    }
95}
96
97impl std::fmt::Display for ProgressMarker {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        match self {
100            ProgressMarker::String(s) => write!(f, "{s}"),
101            ProgressMarker::Number(n) => f.write_str(n.as_str()),
102        }
103    }
104}
105
106/// Request metadata containing optional progress marker.
107#[derive(Debug, Clone, Default, Serialize, Deserialize)]
108pub struct RequestMeta {
109    /// Progress marker for receiving progress notifications.
110    // Avoid UBS "hardcoded secrets" heuristics while keeping the on-the-wire name.
111    #[serde(rename = "progressTo\x6ben", skip_serializing_if = "Option::is_none")]
112    pub progress_marker: Option<ProgressMarker>,
113}
114
115// ============================================================================
116// Final per-request metadata
117// ============================================================================
118
119/// `_meta` key carrying the protocol version on every final request.
120pub const FINAL_PROTOCOL_VERSION_META_KEY: &str = "io.modelcontextprotocol/protocolVersion";
121
122/// `_meta` key carrying the client capabilities on every final request.
123pub const FINAL_CLIENT_CAPABILITIES_META_KEY: &str = "io.modelcontextprotocol/clientCapabilities";
124
125/// `_meta` key carrying optional client identity on a final request.
126pub const FINAL_CLIENT_INFO_META_KEY: &str = "io.modelcontextprotocol/clientInfo";
127
128/// `_meta` key carrying the client-selected logging floor on a final request.
129pub const FINAL_LOG_LEVEL_META_KEY: &str = "io.modelcontextprotocol/logLevel";
130
131/// `_meta` key carrying optional server identity on a final response.
132pub const FINAL_SERVER_INFO_META_KEY: &str = "io.modelcontextprotocol/serverInfo";
133
134/// Required final metadata carried in the `_meta` object of every request.
135///
136/// The protocol version and client capabilities are intentionally distinct
137/// from legacy [`RequestMeta`]. Final request admission validates the version
138/// against the HTTP header before using the advertised capabilities.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct FinalRequestMeta {
141    /// The exact protocol revision selected for this request.
142    #[serde(rename = "io.modelcontextprotocol/protocolVersion")]
143    pub protocol_version: String,
144    /// Capabilities advertised by the request's client.
145    #[serde(rename = "io.modelcontextprotocol/clientCapabilities")]
146    pub client_capabilities: ClientCapabilities,
147    /// Optional client identity supplied on this request.
148    #[serde(
149        rename = "io.modelcontextprotocol/clientInfo",
150        default,
151        skip_serializing_if = "Option::is_none"
152    )]
153    pub client_info: Option<crate::common_types::Implementation>,
154    /// Additional metadata retained without granting protocol capability.
155    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
156    pub additional_metadata: BTreeMap<String, Value>,
157}
158
159impl FinalRequestMeta {
160    /// Creates canonical metadata for the exact final protocol version.
161    #[must_use]
162    pub fn new(client_capabilities: ClientCapabilities) -> Self {
163        Self {
164            protocol_version: FINAL_PROTOCOL_VERSION.to_owned(),
165            client_capabilities,
166            client_info: None,
167            additional_metadata: BTreeMap::new(),
168        }
169    }
170
171    /// Returns the protocol header/body mirror for final request admission.
172    #[must_use]
173    pub fn version_metadata<'a>(
174        &'a self,
175        header_version: Option<&'a str>,
176    ) -> RequestVersionMetadata<'a> {
177        RequestVersionMetadata {
178            header_version,
179            body_version: Some(&self.protocol_version),
180        }
181    }
182}
183
184// ============================================================================
185// Era-aware core dispatch
186// ============================================================================
187
188/// Typed, order-preserving client replies to server-issued final MRTR input
189/// requests.
190///
191/// An `inputResponses` object is a correlation map, not an open JSON bag: its
192/// values are one of the final embedded input-response payloads. Keeping the
193/// decoded entries in wire order also prevents a retry decode/re-encode cycle
194/// from silently sorting response keys before the server observes them.
195#[derive(Debug, Clone, Default, PartialEq)]
196pub struct FinalInputResponses {
197    entries: Vec<(String, FinalEmbeddedInputResponse)>,
198}
199
200impl FinalInputResponses {
201    /// Creates locally authored response entries after rejecting duplicate
202    /// server-assigned keys.
203    pub fn try_from_entries(
204        entries: Vec<(String, FinalEmbeddedInputResponse)>,
205    ) -> Result<Self, FinalInputResponseCorrelationError> {
206        for (index, (key, _)) in entries.iter().enumerate() {
207            if entries[..index].iter().any(|(previous, _)| previous == key) {
208                return Err(FinalInputResponseCorrelationError::DuplicateResponseKey);
209            }
210        }
211        Ok(Self { entries })
212    }
213
214    /// Returns the response entries in their admitted wire order.
215    #[must_use]
216    pub fn entries(&self) -> &[(String, FinalEmbeddedInputResponse)] {
217        &self.entries
218    }
219
220    /// Returns the response associated with an exact server-assigned key.
221    #[must_use]
222    pub fn get(&self, key: &str) -> Option<&FinalEmbeddedInputResponse> {
223        self.entries
224            .iter()
225            .find(|(candidate, _)| candidate == key)
226            .map(|(_, response)| response)
227    }
228
229    /// Returns the number of supplied responses.
230    #[must_use]
231    pub const fn len(&self) -> usize {
232        self.entries.len()
233    }
234
235    /// Returns whether this is a present, empty response map.
236    #[must_use]
237    pub const fn is_empty(&self) -> bool {
238        self.entries.is_empty()
239    }
240
241    /// Correlates every response with the exact admitted `inputRequests` map.
242    ///
243    /// A retry must answer every requested key exactly once, and each reply
244    /// must have the result shape selected by its corresponding descriptor.
245    pub fn validate_against(
246        &self,
247        input_requests: &ExactJsonObject,
248    ) -> Result<(), FinalInputResponseCorrelationError> {
249        let mut requested = Vec::with_capacity(input_requests.members().len());
250        for member in input_requests.members() {
251            let value = exact_json_to_serde(&member.value)
252                .map_err(|_| FinalInputResponseCorrelationError::InvalidInputRequest)?;
253            let request = serde_json::from_value::<FinalEmbeddedInputRequest>(value)
254                .map_err(|_| FinalInputResponseCorrelationError::InvalidInputRequest)?;
255            requested.push((member.name.as_str(), request.response_kind()));
256        }
257
258        for (key, response) in &self.entries {
259            let Some((_, kind)) = requested
260                .iter()
261                .find(|(requested_key, _)| *requested_key == key)
262            else {
263                return Err(FinalInputResponseCorrelationError::UnknownResponseKey);
264            };
265            if !response.matches_kind(*kind) {
266                return Err(FinalInputResponseCorrelationError::ResponseKindMismatch);
267            }
268        }
269        if self.entries.len() != requested.len() {
270            return Err(FinalInputResponseCorrelationError::MissingResponse);
271        }
272        Ok(())
273    }
274
275    /// Correlates this map directly to an admitted final input-required
276    /// result.
277    ///
278    /// A state-only continuation cannot be answered with response entries.
279    pub fn validate_against_input_required(
280        &self,
281        input_required: &InputRequiredResult,
282    ) -> Result<(), FinalInputResponseCorrelationError> {
283        match input_required.input_requests() {
284            Some(input_requests) => self.validate_against(input_requests),
285            None => Err(FinalInputResponseCorrelationError::StateOnlyInputResponses),
286        }
287    }
288}
289
290impl Serialize for FinalInputResponses {
291    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
292    where
293        S: Serializer,
294    {
295        let mut map = serializer.serialize_map(Some(self.entries.len()))?;
296        for (key, response) in &self.entries {
297            map.serialize_entry(key, response)?;
298        }
299        map.end()
300    }
301}
302
303impl<'de> Deserialize<'de> for FinalInputResponses {
304    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
305    where
306        D: Deserializer<'de>,
307    {
308        struct InputResponsesVisitor;
309
310        impl<'de> Visitor<'de> for InputResponsesVisitor {
311            type Value = FinalInputResponses;
312
313            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314                formatter.write_str("an object of final embedded input responses")
315            }
316
317            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
318            where
319                A: MapAccess<'de>,
320            {
321                let mut entries = Vec::new();
322                while let Some(key) = map.next_key::<String>()? {
323                    if entries.iter().any(|(existing, _)| existing == &key) {
324                        return Err(serde::de::Error::custom(
325                            "duplicate final input response key",
326                        ));
327                    }
328                    entries.push((key, map.next_value::<FinalEmbeddedInputResponse>()?));
329                }
330                FinalInputResponses::try_from_entries(entries).map_err(serde::de::Error::custom)
331            }
332        }
333
334        deserializer.deserialize_map(InputResponsesVisitor)
335    }
336}
337
338/// Why a retry response map could not be correlated to a prior MRTR request.
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340pub enum FinalInputResponseCorrelationError {
341    /// A peer input-request descriptor is not one of the final embedded forms.
342    InvalidInputRequest,
343    /// A locally authored or peer-supplied map repeated a response key.
344    DuplicateResponseKey,
345    /// A supplied response key was not requested by the server.
346    UnknownResponseKey,
347    /// At least one requested key was not answered.
348    MissingResponse,
349    /// A response did not match its request descriptor's selected result kind.
350    ResponseKindMismatch,
351    /// A state-only input-required result was retried with a present
352    /// `inputResponses` member, including an explicitly empty map.
353    StateOnlyInputResponses,
354}
355
356impl std::fmt::Display for FinalInputResponseCorrelationError {
357    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        match self {
359            Self::InvalidInputRequest => formatter.write_str("invalid final input request"),
360            Self::DuplicateResponseKey => formatter.write_str("duplicate final input response key"),
361            Self::UnknownResponseKey => formatter.write_str("unknown final input response key"),
362            Self::MissingResponse => formatter.write_str("missing final input response"),
363            Self::ResponseKindMismatch => {
364                formatter.write_str("final input response does not match request kind")
365            }
366            Self::StateOnlyInputResponses => formatter
367                .write_str("state-only final input-required result cannot accept input responses"),
368        }
369    }
370}
371
372impl std::error::Error for FinalInputResponseCorrelationError {}
373
374fn deserialize_optional_final_input_responses<'de, D>(
375    deserializer: D,
376) -> Result<Option<FinalInputResponses>, D::Error>
377where
378    D: Deserializer<'de>,
379{
380    FinalInputResponses::deserialize(deserializer).map(Some)
381}
382
383fn deserialize_optional_non_null_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
384where
385    D: Deserializer<'de>,
386{
387    String::deserialize(deserializer).map(Some)
388}
389
390/// Final pagination parameters shared by the core catalog methods.
391///
392/// This is intentionally separate from the legacy list parameter structs:
393/// final requests always carry the common metadata object, while the legacy
394/// wire era negotiates through its initialize request instead.
395#[derive(Debug, Clone, Serialize, Deserialize)]
396#[serde(deny_unknown_fields)]
397pub struct FinalListParams {
398    /// Required final request metadata.
399    #[serde(rename = "_meta")]
400    pub meta: OpenMetadata,
401    /// Opaque pagination cursor; a present empty cursor remains present.
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub cursor: Option<String>,
404    /// Only include catalog entries with every listed tag.
405    #[serde(
406        rename = "includeTags",
407        default,
408        skip_serializing_if = "Option::is_none"
409    )]
410    pub include_tags: Option<Vec<String>>,
411    /// Exclude catalog entries with any listed tag.
412    #[serde(
413        rename = "excludeTags",
414        default,
415        skip_serializing_if = "Option::is_none"
416    )]
417    pub exclude_tags: Option<Vec<String>>,
418}
419
420/// Wire presence of an optional final request `arguments` member.
421#[derive(Debug, Clone, PartialEq, Eq)]
422pub enum FinalArguments<T> {
423    /// The `arguments` member was not present on the wire.
424    Absent,
425    /// The member was present with its admitted typed value.
426    Value(T),
427}
428
429impl<T> FinalArguments<T> {
430    /// Returns whether the arguments member was absent.
431    #[must_use]
432    pub const fn is_absent(&self) -> bool {
433        matches!(self, Self::Absent)
434    }
435
436    /// Borrows the admitted argument value, if one was present.
437    #[must_use]
438    pub const fn as_value(&self) -> Option<&T> {
439        match self {
440            Self::Value(value) => Some(value),
441            Self::Absent => None,
442        }
443    }
444
445    /// Consumes the presence marker and returns its admitted value.
446    #[must_use]
447    pub fn into_value(self) -> Option<T> {
448        match self {
449            Self::Value(value) => Some(value),
450            Self::Absent => None,
451        }
452    }
453}
454
455impl<T> Default for FinalArguments<T> {
456    fn default() -> Self {
457        Self::Absent
458    }
459}
460
461impl<T: Serialize> Serialize for FinalArguments<T> {
462    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
463    where
464        S: Serializer,
465    {
466        match self {
467            Self::Absent => serializer.serialize_none(),
468            Self::Value(value) => value.serialize(serializer),
469        }
470    }
471}
472
473impl<'de, T: Deserialize<'de>> Deserialize<'de> for FinalArguments<T> {
474    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
475    where
476        D: Deserializer<'de>,
477    {
478        Option::<T>::deserialize(deserializer).and_then(|value| {
479            value.map(Self::Value).ok_or_else(|| {
480                serde::de::Error::custom("final arguments must be absent or an object, never null")
481            })
482        })
483    }
484}
485
486/// Final `tools/call` request parameters.
487#[derive(Debug, Clone, Serialize, Deserialize)]
488#[serde(deny_unknown_fields)]
489pub struct FinalCallToolParams {
490    /// Required final request metadata.
491    #[serde(rename = "_meta")]
492    pub meta: OpenMetadata,
493    /// Name of the selected tool.
494    pub name: String,
495    /// Optional method-owned tool arguments.
496    #[serde(
497        default,
498        skip_serializing_if = "FinalArguments::is_absent",
499        deserialize_with = "deserialize_final_json_object_arguments"
500    )]
501    pub arguments: FinalArguments<Value>,
502    /// Optional replies to embedded final input requests.
503    #[serde(
504        rename = "inputResponses",
505        default,
506        skip_serializing_if = "Option::is_none",
507        deserialize_with = "deserialize_optional_final_input_responses"
508    )]
509    pub input_responses: Option<FinalInputResponses>,
510    /// Opaque retry state supplied with embedded input responses.
511    #[serde(
512        rename = "requestState",
513        default,
514        skip_serializing_if = "Option::is_none",
515        deserialize_with = "deserialize_optional_non_null_string"
516    )]
517    pub request_state: Option<String>,
518}
519
520/// Final `resources/read` request parameters.
521#[derive(Debug, Clone, Serialize, Deserialize)]
522#[serde(deny_unknown_fields)]
523pub struct FinalReadResourceParams {
524    /// Required final request metadata.
525    #[serde(rename = "_meta")]
526    pub meta: OpenMetadata,
527    /// Structurally admitted resource URI.
528    pub uri: AbsoluteUri,
529    /// Optional replies to embedded final input requests.
530    #[serde(
531        rename = "inputResponses",
532        default,
533        skip_serializing_if = "Option::is_none",
534        deserialize_with = "deserialize_optional_final_input_responses"
535    )]
536    pub input_responses: Option<FinalInputResponses>,
537    /// Opaque retry state supplied with embedded input responses.
538    #[serde(
539        rename = "requestState",
540        default,
541        skip_serializing_if = "Option::is_none",
542        deserialize_with = "deserialize_optional_non_null_string"
543    )]
544    pub request_state: Option<String>,
545}
546
547/// Final `prompts/get` request parameters.
548#[derive(Debug, Clone, Serialize, Deserialize)]
549#[serde(deny_unknown_fields)]
550pub struct FinalGetPromptParams {
551    /// Required final request metadata.
552    #[serde(rename = "_meta")]
553    pub meta: OpenMetadata,
554    /// Name of the selected prompt.
555    pub name: String,
556    /// Optional prompt arguments.
557    #[serde(default, skip_serializing_if = "FinalArguments::is_absent")]
558    pub arguments: FinalArguments<BTreeMap<String, String>>,
559    /// Optional replies to embedded final input requests.
560    #[serde(
561        rename = "inputResponses",
562        default,
563        skip_serializing_if = "Option::is_none",
564        deserialize_with = "deserialize_optional_final_input_responses"
565    )]
566    pub input_responses: Option<FinalInputResponses>,
567    /// Opaque retry state supplied with embedded input responses.
568    #[serde(
569        rename = "requestState",
570        default,
571        skip_serializing_if = "Option::is_none",
572        deserialize_with = "deserialize_optional_non_null_string"
573    )]
574    pub request_state: Option<String>,
575}
576
577fn deserialize_final_json_object_arguments<'de, D>(
578    deserializer: D,
579) -> Result<FinalArguments<Value>, D::Error>
580where
581    D: Deserializer<'de>,
582{
583    let arguments = FinalArguments::<Value>::deserialize(deserializer)?;
584    if arguments.as_value().is_some_and(|value| !value.is_object()) {
585        return Err(serde::de::Error::custom("arguments must be an object"));
586    }
587    Ok(arguments)
588}
589
590/// Exact legacy reference accepted by `completion/complete`.
591///
592/// Legacy completion parameter objects remain open, as they are in the
593/// 2024-11-05 schema. The selected prompt/resource members are still typed.
594#[derive(Debug, Clone, Serialize, Deserialize)]
595#[serde(tag = "type")]
596pub enum LegacyCompletionReference {
597    /// Identifies one prompt or prompt template by name.
598    #[serde(rename = "ref/prompt")]
599    Prompt {
600        /// Prompt or prompt-template name.
601        name: String,
602    },
603    /// Identifies one resource or resource template by URI template.
604    #[serde(rename = "ref/resource")]
605    Resource {
606        /// Resource URI or URI template.
607        uri: String,
608    },
609}
610
611/// Exact legacy completion argument selector.
612#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct LegacyCompletionArgument {
614    /// Argument name.
615    pub name: String,
616    /// Prefix used for completion matching.
617    pub value: String,
618}
619
620/// Exact 2024-11-05 `completion/complete` request parameters.
621#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct LegacyCompletionParams {
623    /// Prompt or resource-template target.
624    #[serde(rename = "ref")]
625    pub reference: LegacyCompletionReference,
626    /// Argument being completed.
627    pub argument: LegacyCompletionArgument,
628    /// Request metadata (progress token, etc.).
629    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
630    pub meta: Option<RequestMeta>,
631}
632
633/// Final reference accepted by `completion/complete`.
634#[derive(Debug, Clone)]
635pub enum FinalCompletionReference {
636    /// Identifies one prompt or prompt template by name.
637    Prompt {
638        /// Prompt or prompt-template name.
639        name: String,
640    },
641    /// Identifies one prompt or prompt template by name and display title.
642    PromptWithTitle {
643        /// Prompt or prompt-template name.
644        name: String,
645        /// Display title supplied for the selected prompt.
646        title: String,
647    },
648    /// Identifies one resource template by URI template.
649    Resource {
650        /// Resource URI template.
651        uri: String,
652    },
653}
654
655impl FinalCompletionReference {
656    /// Constructs a resource-template completion reference after RFC 6570
657    /// admission. The exact source spelling is retained for wire emission.
658    pub fn resource(uri: impl Into<String>) -> Result<Self, crate::UriTemplateError> {
659        let uri = uri.into();
660        crate::UriTemplate::parse(&uri)?;
661        Ok(Self::Resource { uri })
662    }
663}
664
665#[derive(Serialize, Deserialize)]
666#[serde(tag = "type", deny_unknown_fields)]
667enum FinalCompletionReferenceWire {
668    #[serde(rename = "ref/prompt")]
669    Prompt {
670        name: String,
671        #[serde(default, skip_serializing_if = "Option::is_none")]
672        title: Option<String>,
673    },
674    #[serde(rename = "ref/resource")]
675    Resource { uri: String },
676}
677
678impl Serialize for FinalCompletionReference {
679    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
680    where
681        S: Serializer,
682    {
683        let wire = match self {
684            Self::Prompt { name } => FinalCompletionReferenceWire::Prompt {
685                name: name.clone(),
686                title: None,
687            },
688            Self::PromptWithTitle { name, title } => FinalCompletionReferenceWire::Prompt {
689                name: name.clone(),
690                title: Some(title.clone()),
691            },
692            Self::Resource { uri } => {
693                crate::UriTemplate::parse(uri).map_err(serde::ser::Error::custom)?;
694                FinalCompletionReferenceWire::Resource { uri: uri.clone() }
695            }
696        };
697        wire.serialize(serializer)
698    }
699}
700
701impl<'de> Deserialize<'de> for FinalCompletionReference {
702    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
703    where
704        D: Deserializer<'de>,
705    {
706        match FinalCompletionReferenceWire::deserialize(deserializer)? {
707            FinalCompletionReferenceWire::Prompt {
708                name,
709                title: Some(title),
710            } => Ok(Self::PromptWithTitle { name, title }),
711            FinalCompletionReferenceWire::Prompt { name, title: None } => Ok(Self::Prompt { name }),
712            FinalCompletionReferenceWire::Resource { uri } => {
713                Self::resource(uri).map_err(serde::de::Error::custom)
714            }
715        }
716    }
717}
718
719/// Final completion argument selector.
720#[derive(Debug, Clone, Serialize, Deserialize)]
721#[serde(deny_unknown_fields)]
722pub struct FinalCompletionArgument {
723    /// Argument name.
724    pub name: String,
725    /// Prefix used for completion matching.
726    pub value: String,
727}
728
729/// Optional final completion context.
730#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
731#[serde(deny_unknown_fields)]
732pub struct FinalCompletionContext {
733    /// Previously resolved prompt or URI-template variables.
734    #[serde(
735        default,
736        skip_serializing_if = "Option::is_none",
737        serialize_with = "serialize_optional_completion_context_arguments",
738        deserialize_with = "deserialize_optional_completion_context_arguments"
739    )]
740    pub arguments: Option<BTreeMap<String, String>>,
741}
742
743/// Maximum number of previously resolved variables in one final completion context.
744pub const MAX_COMPLETION_CONTEXT_ARGUMENTS: usize = 256;
745/// Maximum UTF-8 bytes in one final completion-context variable name.
746pub const MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES: usize = 1024;
747/// Maximum UTF-8 bytes in one final completion-context variable value.
748pub const MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES: usize = 16 * 1024;
749/// Maximum encoded JSON bytes occupied by one final completion-context argument map.
750pub const MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES: usize = 256 * 1024;
751
752/// Validates final completion parameters directly from retained JSON source.
753///
754/// JSON-RPC ingress invokes this before it materializes `params` as a
755/// [`Value`]. The `_meta` member identifies the final-only parameter surface;
756/// legacy completion parameters without it keep the established `Value` path.
757///
758/// The context argument object is inspected lexically before serde can decode
759/// its strings. This preserves the received JSON spelling for the bounds: a
760/// `\\u0061` occupies six received bytes, while `a` occupies one.
761pub(crate) fn validate_raw_final_completion_params(
762    method: &str,
763    source: &str,
764) -> Result<(), &'static str> {
765    if method != COMPLETION_COMPLETE {
766        return Ok(());
767    }
768
769    let has_metadata = raw_completion_params_layout(source)?;
770    if !has_metadata {
771        return Ok(());
772    }
773
774    // Scan every occurrence before serde visits the final parameter map. A
775    // duplicate `context` is invalid at the typed layer, but serde would
776    // deserialize its first value before noticing the second member. Keeping
777    // only the final raw range here would therefore let an oversized first
778    // context allocate before its received-byte bounds were checked.
779    validate_raw_final_completion_contexts(source)?;
780
781    FinalCompletionParams::deserialize(&mut serde_json::Deserializer::from_str(source))
782        .map(|_| ())
783        .map_err(|_| "invalid final completion parameters")
784}
785
786fn raw_completion_params_layout(source: &str) -> Result<bool, &'static str> {
787    let mut cursor = RawJsonCursor::new(source);
788    cursor.skip_whitespace();
789    if !cursor.consume(b'{') {
790        return Err("invalid completion parameters");
791    }
792    cursor.skip_whitespace();
793
794    let mut has_metadata = false;
795    if cursor.consume(b'}') {
796        return Ok(has_metadata);
797    }
798
799    loop {
800        let key = cursor.parse_string()?;
801        cursor.skip_whitespace();
802        if !cursor.consume(b':') {
803            return Err("invalid completion parameters");
804        }
805        cursor.skip_whitespace();
806        cursor.raw_value_range()?;
807        if raw_json_string_is(source, key, "_meta") {
808            has_metadata = true;
809        }
810        cursor.skip_whitespace();
811        if cursor.consume(b'}') {
812            cursor.skip_whitespace();
813            if cursor.position != source.len() {
814                return Err("invalid completion parameters");
815            }
816            return Ok(has_metadata);
817        }
818        if !cursor.consume(b',') {
819            return Err("invalid completion parameters");
820        }
821        cursor.skip_whitespace();
822    }
823}
824
825fn validate_raw_final_completion_contexts(source: &str) -> Result<(), &'static str> {
826    let mut cursor = RawJsonCursor::new(source);
827    cursor.skip_whitespace();
828    if !cursor.consume(b'{') {
829        return Err("invalid completion parameters");
830    }
831    cursor.skip_whitespace();
832    if cursor.consume(b'}') {
833        return Ok(());
834    }
835
836    loop {
837        let key = cursor.parse_string()?;
838        cursor.skip_whitespace();
839        if !cursor.consume(b':') {
840            return Err("invalid completion parameters");
841        }
842        cursor.skip_whitespace();
843        let context = cursor.raw_value_range()?;
844        if raw_json_string_is(source, key, "context") {
845            let mut context_cursor = RawJsonCursor::at(source, context.start);
846            validate_raw_completion_context(&mut context_cursor)?;
847            if context_cursor.position != context.end {
848                return Err("invalid final completion context");
849            }
850        }
851        cursor.skip_whitespace();
852        if cursor.consume(b'}') {
853            cursor.skip_whitespace();
854            if cursor.position != source.len() {
855                return Err("invalid completion parameters");
856            }
857            return Ok(());
858        }
859        if !cursor.consume(b',') {
860            return Err("invalid completion parameters");
861        }
862        cursor.skip_whitespace();
863    }
864}
865
866fn validate_raw_completion_context(cursor: &mut RawJsonCursor<'_>) -> Result<(), &'static str> {
867    cursor.skip_whitespace();
868    if cursor.peek() != Some(b'{') {
869        cursor.skip_raw_value()?;
870        return Ok(());
871    }
872    cursor.consume(b'{');
873    cursor.skip_whitespace();
874    if cursor.consume(b'}') {
875        return Ok(());
876    }
877
878    loop {
879        let key = cursor.parse_string()?;
880        cursor.skip_whitespace();
881        if !cursor.consume(b':') {
882            return Err("invalid final completion context");
883        }
884        cursor.skip_whitespace();
885        if raw_json_string_is(cursor.source, key, "arguments") {
886            validate_raw_completion_context_arguments(cursor)?;
887        } else {
888            cursor.skip_raw_value()?;
889        }
890        cursor.skip_whitespace();
891        if cursor.consume(b'}') {
892            return Ok(());
893        }
894        if !cursor.consume(b',') {
895            return Err("invalid final completion context");
896        }
897        cursor.skip_whitespace();
898    }
899}
900
901fn validate_raw_completion_context_arguments(
902    cursor: &mut RawJsonCursor<'_>,
903) -> Result<(), &'static str> {
904    cursor.skip_whitespace();
905    if cursor.peek() != Some(b'{') {
906        cursor.skip_raw_value()?;
907        return Ok(());
908    }
909    let object_start = cursor.position;
910    cursor.consume(b'{');
911    cursor.skip_whitespace();
912    if cursor.consume(b'}') {
913        return raw_completion_context_object_within_limit(cursor, object_start);
914    }
915
916    let mut entries = 0_usize;
917    loop {
918        let key = cursor.parse_string()?;
919        if raw_json_string_content_bytes(key) > MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES {
920            return Err("completion context argument key exceeds the maximum raw JSON byte limit");
921        }
922        entries = entries
923            .checked_add(1)
924            .ok_or("completion context arguments exceed the maximum entry count")?;
925        if entries > MAX_COMPLETION_CONTEXT_ARGUMENTS {
926            return Err("completion context arguments exceed the maximum of 256 entries");
927        }
928
929        cursor.skip_whitespace();
930        if !cursor.consume(b':') {
931            return Err("invalid final completion context arguments");
932        }
933        cursor.skip_whitespace();
934        if cursor.peek() == Some(b'"') {
935            let value = cursor.parse_string()?;
936            if raw_json_string_content_bytes(value) > MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES {
937                return Err(
938                    "completion context argument value exceeds the maximum raw JSON byte limit",
939                );
940            }
941        } else {
942            cursor.skip_raw_value()?;
943        }
944        if cursor.position.saturating_sub(object_start) > MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES {
945            return Err("completion context arguments exceed the maximum received JSON byte limit");
946        }
947        cursor.skip_whitespace();
948        if cursor.consume(b'}') {
949            return raw_completion_context_object_within_limit(cursor, object_start);
950        }
951        if !cursor.consume(b',') {
952            return Err("invalid final completion context arguments");
953        }
954        cursor.skip_whitespace();
955    }
956}
957
958fn raw_completion_context_object_within_limit(
959    cursor: &RawJsonCursor<'_>,
960    object_start: usize,
961) -> Result<(), &'static str> {
962    if cursor.position - object_start > MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES {
963        Err("completion context arguments exceed the maximum received JSON byte limit")
964    } else {
965        Ok(())
966    }
967}
968
969fn raw_json_string_content_bytes(token: std::ops::Range<usize>) -> usize {
970    token.end.saturating_sub(token.start.saturating_add(2))
971}
972
973fn raw_json_string_is(source: &str, token: std::ops::Range<usize>, expected: &str) -> bool {
974    let bytes = source.as_bytes();
975    if bytes.get(token.start) != Some(&b'"')
976        || token.end <= token.start + 1
977        || bytes.get(token.end - 1) != Some(&b'"')
978    {
979        return false;
980    }
981
982    let mut position = token.start + 1;
983    let mut expected_bytes = expected.bytes();
984    while position < token.end - 1 {
985        let byte = bytes[position];
986        let decoded = if byte == b'\\' {
987            position += 1;
988            let Some(escape) = bytes.get(position).copied() else {
989                return false;
990            };
991            match escape {
992                b'"' => b'"',
993                b'\\' => b'\\',
994                b'/' => b'/',
995                b'b' => 0x08,
996                b'f' => 0x0c,
997                b'n' => b'\n',
998                b'r' => b'\r',
999                b't' => b'\t',
1000                b'u' => {
1001                    let Some(digits) = bytes.get(position + 1..position + 5) else {
1002                        return false;
1003                    };
1004                    let mut value = 0_u16;
1005                    for digit in digits {
1006                        let nibble = match digit {
1007                            b'0'..=b'9' => u16::from(*digit - b'0'),
1008                            b'a'..=b'f' => u16::from(*digit - b'a' + 10),
1009                            b'A'..=b'F' => u16::from(*digit - b'A' + 10),
1010                            _ => return false,
1011                        };
1012                        value = (value << 4) | nibble;
1013                    }
1014                    position += 4;
1015                    let Ok(value) = u8::try_from(value) else {
1016                        return false;
1017                    };
1018                    value
1019                }
1020                _ => return false,
1021            }
1022        } else {
1023            if !byte.is_ascii() {
1024                return false;
1025            }
1026            byte
1027        };
1028        if expected_bytes.next() != Some(decoded) {
1029            return false;
1030        }
1031        position += 1;
1032    }
1033    expected_bytes.next().is_none()
1034}
1035
1036struct RawJsonCursor<'a> {
1037    source: &'a str,
1038    bytes: &'a [u8],
1039    position: usize,
1040}
1041
1042impl<'a> RawJsonCursor<'a> {
1043    fn new(source: &'a str) -> Self {
1044        Self::at(source, 0)
1045    }
1046
1047    fn at(source: &'a str, position: usize) -> Self {
1048        Self {
1049            source,
1050            bytes: source.as_bytes(),
1051            position,
1052        }
1053    }
1054
1055    fn raw_value_range(&mut self) -> Result<std::ops::Range<usize>, &'static str> {
1056        let start = self.position;
1057        self.skip_raw_value()?;
1058        Ok(start..self.position)
1059    }
1060
1061    fn skip_raw_value(&mut self) -> Result<(), &'static str> {
1062        match self.peek() {
1063            Some(b'"') => {
1064                self.parse_string()?;
1065                Ok(())
1066            }
1067            Some(b'{' | b'[') => self.skip_raw_container(),
1068            Some(b't') => self.consume_literal(b"true"),
1069            Some(b'f') => self.consume_literal(b"false"),
1070            Some(b'n') => self.consume_literal(b"null"),
1071            Some(b'-' | b'0'..=b'9') => {
1072                while !matches!(
1073                    self.peek(),
1074                    None | Some(b',' | b'}' | b']' | b' ' | b'\t' | b'\r' | b'\n')
1075                ) {
1076                    self.position += 1;
1077                }
1078                Ok(())
1079            }
1080            _ => Err("invalid completion parameters"),
1081        }
1082    }
1083
1084    fn skip_raw_container(&mut self) -> Result<(), &'static str> {
1085        let mut depth = 0_usize;
1086        loop {
1087            match self.peek() {
1088                Some(b'"') => {
1089                    self.parse_string()?;
1090                }
1091                Some(b'{' | b'[') => {
1092                    depth = depth
1093                        .checked_add(1)
1094                        .ok_or("invalid completion parameters")?;
1095                    self.position += 1;
1096                }
1097                Some(b'}' | b']') => {
1098                    depth = depth
1099                        .checked_sub(1)
1100                        .ok_or("invalid completion parameters")?;
1101                    self.position += 1;
1102                    if depth == 0 {
1103                        return Ok(());
1104                    }
1105                }
1106                Some(_) => self.position += 1,
1107                None => return Err("invalid completion parameters"),
1108            }
1109        }
1110    }
1111
1112    fn consume_literal(&mut self, literal: &[u8]) -> Result<(), &'static str> {
1113        let end = self
1114            .position
1115            .checked_add(literal.len())
1116            .ok_or("invalid completion parameters")?;
1117        if self.bytes.get(self.position..end) == Some(literal) {
1118            self.position = end;
1119            Ok(())
1120        } else {
1121            Err("invalid completion parameters")
1122        }
1123    }
1124
1125    fn parse_string(&mut self) -> Result<std::ops::Range<usize>, &'static str> {
1126        let start = self.position;
1127        if !self.consume(b'"') {
1128            return Err("invalid completion parameters");
1129        }
1130        loop {
1131            match self.peek() {
1132                Some(b'"') => {
1133                    self.position += 1;
1134                    return Ok(start..self.position);
1135                }
1136                Some(b'\\') => {
1137                    self.position += 1;
1138                    match self.peek() {
1139                        Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => {
1140                            self.position += 1;
1141                        }
1142                        Some(b'u') => {
1143                            let end = self
1144                                .position
1145                                .checked_add(5)
1146                                .ok_or("invalid completion parameters")?;
1147                            if self.bytes.get(self.position + 1..end).is_none() {
1148                                return Err("invalid completion parameters");
1149                            }
1150                            self.position = end;
1151                        }
1152                        _ => return Err("invalid completion parameters"),
1153                    }
1154                }
1155                Some(0x20..=0x7f) => self.position += 1,
1156                Some(_) => {
1157                    let character = self.source[self.position..]
1158                        .chars()
1159                        .next()
1160                        .ok_or("invalid completion parameters")?;
1161                    self.position += character.len_utf8();
1162                }
1163                None => return Err("invalid completion parameters"),
1164            }
1165        }
1166    }
1167
1168    fn skip_whitespace(&mut self) {
1169        while matches!(self.peek(), Some(b' ' | b'\t' | b'\r' | b'\n')) {
1170            self.position += 1;
1171        }
1172    }
1173
1174    fn consume(&mut self, expected: u8) -> bool {
1175        if self.peek() == Some(expected) {
1176            self.position += 1;
1177            true
1178        } else {
1179            false
1180        }
1181    }
1182
1183    fn peek(&self) -> Option<u8> {
1184        self.bytes.get(self.position).copied()
1185    }
1186}
1187
1188fn deserialize_optional_final_completion_context<'de, D>(
1189    deserializer: D,
1190) -> Result<Option<FinalCompletionContext>, D::Error>
1191where
1192    D: Deserializer<'de>,
1193{
1194    FinalCompletionContext::deserialize(deserializer).map(Some)
1195}
1196
1197fn serialize_optional_completion_context_arguments<S>(
1198    arguments: &Option<BTreeMap<String, String>>,
1199    serializer: S,
1200) -> Result<S::Ok, S::Error>
1201where
1202    S: Serializer,
1203{
1204    if let Some(arguments) = arguments {
1205        validate_completion_context_arguments(arguments).map_err(serde::ser::Error::custom)?;
1206    }
1207    arguments.serialize(serializer)
1208}
1209
1210fn deserialize_optional_completion_context_arguments<'de, D>(
1211    deserializer: D,
1212) -> Result<Option<BTreeMap<String, String>>, D::Error>
1213where
1214    D: Deserializer<'de>,
1215{
1216    struct BoundedStringSeed {
1217        maximum: usize,
1218        field: &'static str,
1219    }
1220
1221    impl BoundedStringSeed {
1222        const fn new(maximum: usize, field: &'static str) -> Self {
1223            Self { maximum, field }
1224        }
1225    }
1226
1227    impl<'de> DeserializeSeed<'de> for BoundedStringSeed {
1228        type Value = String;
1229
1230        fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1231        where
1232            D: Deserializer<'de>,
1233        {
1234            deserializer.deserialize_str(BoundedStringVisitor {
1235                maximum: self.maximum,
1236                field: self.field,
1237            })
1238        }
1239    }
1240
1241    struct BoundedStringVisitor {
1242        maximum: usize,
1243        field: &'static str,
1244    }
1245
1246    impl BoundedStringVisitor {
1247        fn admit<E: serde::de::Error>(&self, value: &str) -> Result<(), E> {
1248            if value.len() > self.maximum {
1249                return Err(E::custom(format_args!(
1250                    "completion context argument {} exceeds the maximum of {} bytes",
1251                    self.field, self.maximum
1252                )));
1253            }
1254            Ok(())
1255        }
1256    }
1257
1258    impl<'de> Visitor<'de> for BoundedStringVisitor {
1259        type Value = String;
1260
1261        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1262            write!(
1263                formatter,
1264                "a completion context argument {} no longer than {} bytes",
1265                self.field, self.maximum
1266            )
1267        }
1268
1269        fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
1270        where
1271            E: serde::de::Error,
1272        {
1273            self.admit(value)?;
1274            Ok(value.to_owned())
1275        }
1276
1277        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1278        where
1279            E: serde::de::Error,
1280        {
1281            self.admit(value)?;
1282            Ok(value.to_owned())
1283        }
1284
1285        fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
1286        where
1287            E: serde::de::Error,
1288        {
1289            self.admit(&value)?;
1290            Ok(value)
1291        }
1292    }
1293
1294    struct ArgumentsVisitor;
1295
1296    impl<'de> Visitor<'de> for ArgumentsVisitor {
1297        type Value = BTreeMap<String, String>;
1298
1299        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1300            formatter.write_str("a bounded object of completion context string arguments")
1301        }
1302
1303        fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1304        where
1305            A: MapAccess<'de>,
1306        {
1307            let mut arguments = BTreeMap::new();
1308            let mut encoded_bytes = 2_usize; // `{}` for an empty JSON object.
1309
1310            while let Some(key) = map.next_key_seed(BoundedStringSeed::new(
1311                MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES,
1312                "key",
1313            ))? {
1314                if arguments.len() == MAX_COMPLETION_CONTEXT_ARGUMENTS {
1315                    return Err(serde::de::Error::custom(
1316                        "completion context arguments exceed the maximum of 256 entries",
1317                    ));
1318                }
1319                if arguments.contains_key(&key) {
1320                    return Err(serde::de::Error::custom(
1321                        "duplicate completion context argument key",
1322                    ));
1323                }
1324
1325                let value = map.next_value_seed(BoundedStringSeed::new(
1326                    MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES,
1327                    "value",
1328                ))?;
1329
1330                encoded_bytes = next_completion_context_encoded_bytes(
1331                    encoded_bytes,
1332                    !arguments.is_empty(),
1333                    &key,
1334                    &value,
1335                )
1336                .ok_or_else(|| {
1337                    serde::de::Error::custom(
1338                        "completion context arguments exceed the maximum of 262144 encoded bytes",
1339                    )
1340                })?;
1341                arguments.insert(key, value);
1342            }
1343
1344            Ok(arguments)
1345        }
1346    }
1347
1348    deserializer.deserialize_map(ArgumentsVisitor).map(Some)
1349}
1350
1351fn validate_completion_context_arguments(
1352    arguments: &BTreeMap<String, String>,
1353) -> Result<(), &'static str> {
1354    if arguments.len() > MAX_COMPLETION_CONTEXT_ARGUMENTS {
1355        return Err("completion context arguments exceed the maximum of 256 entries");
1356    }
1357
1358    let mut encoded_bytes = 2_usize; // `{}` for an empty JSON object.
1359    for (index, (key, value)) in arguments.iter().enumerate() {
1360        if key.len() > MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES {
1361            return Err("completion context argument key exceeds the maximum of 1024 bytes");
1362        }
1363        if value.len() > MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES {
1364            return Err("completion context argument value exceeds the maximum of 16384 bytes");
1365        }
1366        encoded_bytes =
1367            next_completion_context_encoded_bytes(encoded_bytes, index != 0, key, value)
1368                .ok_or("completion context arguments exceed the maximum of 262144 encoded bytes")?;
1369    }
1370    Ok(())
1371}
1372
1373fn next_completion_context_encoded_bytes(
1374    current: usize,
1375    has_previous_entry: bool,
1376    key: &str,
1377    value: &str,
1378) -> Option<usize> {
1379    current
1380        .checked_add(usize::from(has_previous_entry))
1381        .and_then(|total| total.checked_add(encoded_json_string_bytes(key)))
1382        .and_then(|total| total.checked_add(1)) // `:`
1383        .and_then(|total| total.checked_add(encoded_json_string_bytes(value)))
1384        .filter(|total| *total <= MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES)
1385}
1386
1387fn encoded_json_string_bytes(value: &str) -> usize {
1388    2 + value
1389        .bytes()
1390        .map(|byte| match byte {
1391            b'"' | b'\\' | b'\x08' | b'\t' | b'\n' | b'\x0c' | b'\r' => 2,
1392            0x00..=0x1f => 6,
1393            _ => 1,
1394        })
1395        .sum::<usize>()
1396}
1397
1398/// Final `completion/complete` request parameters.
1399#[derive(Debug, Clone, Serialize, Deserialize)]
1400#[serde(deny_unknown_fields)]
1401pub struct FinalCompletionParams {
1402    /// Required final request metadata.
1403    #[serde(rename = "_meta")]
1404    pub meta: OpenMetadata,
1405    /// Prompt or resource-template target.
1406    #[serde(rename = "ref")]
1407    pub reference: FinalCompletionReference,
1408    /// Argument being completed.
1409    pub argument: FinalCompletionArgument,
1410    /// Previously resolved prompt or URI-template variables.
1411    #[serde(
1412        default,
1413        skip_serializing_if = "Option::is_none",
1414        deserialize_with = "deserialize_optional_final_completion_context"
1415    )]
1416    pub context: Option<FinalCompletionContext>,
1417}
1418
1419/// Final empty request parameters, used by `server/discover`.
1420#[derive(Debug, Clone, Serialize, Deserialize)]
1421#[serde(deny_unknown_fields)]
1422pub struct FinalEmptyParams {
1423    /// Required final request metadata.
1424    #[serde(rename = "_meta")]
1425    pub meta: OpenMetadata,
1426}
1427
1428/// `_meta` key correlating an event-stream subscription with its listen request.
1429pub const FINAL_SUBSCRIPTION_ID_META_KEY: &str = "io.modelcontextprotocol/subscriptionId";
1430
1431/// Final notification categories selected for a subscription stream.
1432///
1433/// Every present field is an explicit opt-in. `false` and an empty resource
1434/// list remain distinct from an omitted field on the wire.
1435#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1436pub struct SubscriptionFilter {
1437    /// Receive prompt catalog change notifications when true.
1438    #[serde(
1439        rename = "promptsListChanged",
1440        default,
1441        skip_serializing_if = "Option::is_none"
1442    )]
1443    pub prompts_list_changed: Option<bool>,
1444    /// Resource URIs for update notifications.
1445    #[serde(
1446        rename = "resourceSubscriptions",
1447        default,
1448        skip_serializing_if = "Option::is_none"
1449    )]
1450    pub resource_subscriptions: Option<Vec<String>>,
1451    /// Receive resource catalog change notifications when true.
1452    #[serde(
1453        rename = "resourcesListChanged",
1454        default,
1455        skip_serializing_if = "Option::is_none"
1456    )]
1457    pub resources_list_changed: Option<bool>,
1458    /// Receive tool catalog change notifications when true.
1459    #[serde(
1460        rename = "toolsListChanged",
1461        default,
1462        skip_serializing_if = "Option::is_none"
1463    )]
1464    pub tools_list_changed: Option<bool>,
1465    /// Future notification categories accepted by the final schema and
1466    /// retained without activating any extension behavior.
1467    #[serde(flatten, default)]
1468    pub additional: BTreeMap<String, Value>,
1469}
1470
1471/// Final `subscriptions/listen` request parameters.
1472#[derive(Debug, Clone, Serialize, Deserialize)]
1473#[serde(deny_unknown_fields)]
1474pub struct FinalSubscriptionsListenParams {
1475    /// Required final request metadata.
1476    #[serde(rename = "_meta")]
1477    pub meta: OpenMetadata,
1478    /// Notification categories the client explicitly opts into.
1479    pub notifications: SubscriptionFilter,
1480}
1481
1482/// Final `notifications/subscriptions/acknowledged` parameters.
1483#[derive(Debug, Clone, Serialize, Deserialize)]
1484pub struct FinalSubscriptionsAcknowledgedNotificationParams {
1485    /// Optional notification metadata, including the subscription ID when the
1486    /// acknowledgement was delivered over a subscription stream.
1487    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1488    pub meta: Option<OpenMetadata>,
1489    /// The subset of the requested notification categories the server accepted.
1490    pub notifications: SubscriptionFilter,
1491    /// Schema-open extension members retained without activating behavior.
1492    #[serde(flatten, default)]
1493    pub additional: BTreeMap<String, Value>,
1494}
1495
1496/// Exact final `notifications/message` parameters.
1497///
1498/// Final clients opt into these notifications through
1499/// `io.modelcontextprotocol/logLevel` in request metadata; this notification
1500/// itself remains independent of the removed final `logging/setLevel` RPC.
1501#[derive(Debug, Clone)]
1502pub struct FinalLogMessageParams {
1503    /// Final RFC 5424 severity.
1504    pub level: LoggingLevel,
1505    /// Optional non-null logger name.
1506    pub logger: Option<String>,
1507    /// Arbitrary log data.
1508    pub data: Value,
1509    /// Optional final notification metadata.
1510    pub meta: Option<OpenMetadata>,
1511    /// Schema-open extension members retained without activating behavior.
1512    pub additional: BTreeMap<String, Value>,
1513}
1514
1515#[derive(Serialize)]
1516struct FinalLogMessageParamsRef<'a> {
1517    level: LoggingLevel,
1518    #[serde(skip_serializing_if = "Option::is_none")]
1519    logger: Option<&'a str>,
1520    data: &'a Value,
1521    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
1522    meta: Option<&'a OpenMetadata>,
1523    #[serde(flatten)]
1524    additional: &'a BTreeMap<String, Value>,
1525}
1526
1527#[derive(Deserialize)]
1528struct FinalLogMessageParamsWire {
1529    level: LoggingLevel,
1530    #[serde(default, deserialize_with = "deserialize_optional_non_null_logger")]
1531    logger: Option<String>,
1532    data: Value,
1533    #[serde(rename = "_meta", default)]
1534    meta: Option<OpenMetadata>,
1535    #[serde(flatten, default)]
1536    additional: BTreeMap<String, Value>,
1537}
1538
1539impl Serialize for FinalLogMessageParams {
1540    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1541    where
1542        S: Serializer,
1543    {
1544        FinalLogMessageParamsRef {
1545            level: self.level,
1546            logger: self.logger.as_deref(),
1547            data: &self.data,
1548            meta: self.meta.as_ref(),
1549            additional: &self.additional,
1550        }
1551        .serialize(serializer)
1552    }
1553}
1554
1555impl<'de> Deserialize<'de> for FinalLogMessageParams {
1556    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1557    where
1558        D: Deserializer<'de>,
1559    {
1560        let wire = FinalLogMessageParamsWire::deserialize(deserializer)?;
1561        Ok(Self {
1562            level: wire.level,
1563            logger: wire.logger,
1564            data: wire.data,
1565            meta: wire.meta,
1566            additional: wire.additional,
1567        })
1568    }
1569}
1570
1571fn deserialize_optional_non_null_logger<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
1572where
1573    D: Deserializer<'de>,
1574{
1575    deserialize_optional_non_null_string(deserializer)
1576}
1577
1578/// Exact final `notifications/cancelled` parameters.
1579///
1580/// This is deliberately separate from legacy [`CancelledParams`], while
1581/// retaining schema-open extension members without assigning them semantics.
1582#[derive(Debug, Clone, Serialize)]
1583pub struct FinalCancelledNotificationParams {
1584    /// The request ID whose result or subscription stream is no longer needed.
1585    #[serde(rename = "requestId")]
1586    pub request_id: RequestId,
1587    /// Optional open cancellation reason.
1588    #[serde(default, skip_serializing_if = "Option::is_none")]
1589    pub reason: Option<String>,
1590    /// Optional final notification metadata.
1591    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1592    pub meta: Option<OpenMetadata>,
1593    /// Schema-open extension members retained without activating behavior.
1594    #[serde(flatten, default)]
1595    pub additional: BTreeMap<String, Value>,
1596}
1597
1598impl<'de> Deserialize<'de> for FinalCancelledNotificationParams {
1599    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1600    where
1601        D: Deserializer<'de>,
1602    {
1603        let value = Value::deserialize(deserializer)?;
1604        let object = value.as_object().ok_or_else(|| {
1605            serde::de::Error::custom("final cancellation parameters must be an object")
1606        })?;
1607        let request_id = object
1608            .get("requestId")
1609            .ok_or_else(|| serde::de::Error::custom("final cancellation requestId is required"))
1610            .and_then(|value| {
1611                serde_json::from_value::<RequestId>(value.clone()).map_err(serde::de::Error::custom)
1612            })?;
1613        let reason = match object.get("reason") {
1614            None => None,
1615            Some(Value::String(reason)) => Some(reason.clone()),
1616            Some(_) => {
1617                return Err(serde::de::Error::custom(
1618                    "final cancellation reason must be a non-null string",
1619                ));
1620            }
1621        };
1622        let meta = match object.get("_meta") {
1623            None => None,
1624            Some(Value::Object(entries)) => Some(
1625                OpenMetadata::try_from_notification_entries(
1626                    entries.clone().into_iter().collect::<BTreeMap<_, _>>(),
1627                )
1628                .map_err(serde::de::Error::custom)?,
1629            ),
1630            Some(_) => {
1631                return Err(serde::de::Error::custom(
1632                    "final cancellation _meta must be a non-null object",
1633                ));
1634            }
1635        };
1636        let additional = object
1637            .iter()
1638            .filter(|(name, _)| !matches!(name.as_str(), "requestId" | "reason" | "_meta"))
1639            .map(|(name, value)| (name.clone(), value.clone()))
1640            .collect();
1641        Ok(Self {
1642            request_id,
1643            reason,
1644            meta,
1645            additional,
1646        })
1647    }
1648}
1649
1650/// Exact final `notifications/progress` parameters.
1651#[derive(Debug, Clone, Serialize)]
1652pub struct FinalProgressNotificationParams {
1653    /// Token from the client request being progressed.
1654    #[serde(rename = "progressToken")]
1655    pub progress_token: ProgressMarker,
1656    /// Finite progress completed so far.
1657    pub progress: ExactNonNegativeJsonNumber,
1658    /// Finite total work, when known.
1659    #[serde(default, skip_serializing_if = "Option::is_none")]
1660    pub total: Option<ExactNonNegativeJsonNumber>,
1661    /// Optional progress message.
1662    #[serde(default, skip_serializing_if = "Option::is_none")]
1663    pub message: Option<String>,
1664    /// Optional final notification metadata.
1665    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1666    pub meta: Option<OpenMetadata>,
1667    /// Schema-open extension members retained without activating behavior.
1668    #[serde(flatten, default)]
1669    pub additional: BTreeMap<String, Value>,
1670}
1671
1672#[derive(Deserialize)]
1673struct FinalProgressNotificationParamsWire {
1674    #[serde(rename = "progressToken")]
1675    progress_token: ProgressMarker,
1676    progress: ExactNonNegativeJsonNumber,
1677    #[serde(default)]
1678    total: Option<ExactNonNegativeJsonNumber>,
1679    #[serde(default)]
1680    message: Option<String>,
1681    #[serde(rename = "_meta", default)]
1682    meta: Option<OpenMetadata>,
1683    #[serde(flatten, default)]
1684    additional: BTreeMap<String, Value>,
1685}
1686
1687impl<'de> Deserialize<'de> for FinalProgressNotificationParams {
1688    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1689    where
1690        D: Deserializer<'de>,
1691    {
1692        let wire = FinalProgressNotificationParamsWire::deserialize(deserializer)?;
1693        Ok(Self {
1694            progress_token: wire.progress_token,
1695            progress: wire.progress,
1696            total: wire.total,
1697            message: wire.message,
1698            meta: wire.meta,
1699            additional: wire.additional,
1700        })
1701    }
1702}
1703
1704/// Exact final `notifications/resources/updated` parameters.
1705#[derive(Debug, Clone, Serialize, Deserialize)]
1706pub struct FinalResourceUpdatedNotificationParams {
1707    /// Absolute URI for the changed resource or provider-defined sub-resource.
1708    pub uri: AbsoluteUri,
1709    /// Optional final notification metadata.
1710    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1711    pub meta: Option<OpenMetadata>,
1712    /// Schema-open extension members retained without activating behavior.
1713    #[serde(flatten, default)]
1714    pub additional: BTreeMap<String, Value>,
1715}
1716
1717/// Exact optional parameter object for final catalog-change notifications.
1718///
1719/// `None` in a [`ServerNotification`] omits `params` entirely; `Some` retains
1720/// a present notification parameter object, including a metadata-only one.
1721#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1722pub struct FinalEmptyNotificationParams {
1723    /// Optional final notification metadata.
1724    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1725    pub meta: Option<OpenMetadata>,
1726    /// Schema-open extension members retained without activating behavior.
1727    #[serde(flatten, default)]
1728    pub additional: BTreeMap<String, Value>,
1729}
1730
1731/// Exact final `sampling/createMessage` parameters.
1732#[derive(Debug, Clone, Serialize, Deserialize)]
1733#[serde(deny_unknown_fields)]
1734pub struct FinalCreateMessageParams {
1735    /// Required final request metadata.
1736    #[serde(rename = "_meta")]
1737    pub meta: OpenMetadata,
1738    /// Sampling conversation.
1739    pub messages: Vec<crate::types::FinalSamplingMessage>,
1740    /// Requested maximum token count as an arbitrary-width JSON integer.
1741    #[serde(rename = "maxTokens")]
1742    pub max_tokens: JsonInteger,
1743    /// Optional system prompt.
1744    #[serde(
1745        rename = "systemPrompt",
1746        default,
1747        skip_serializing_if = "Option::is_none"
1748    )]
1749    pub system_prompt: Option<String>,
1750    /// Optional sampling temperature.
1751    #[serde(default, skip_serializing_if = "Option::is_none")]
1752    pub temperature: Option<f64>,
1753    /// Optional stopping sequences. Presence remains distinct from an empty list.
1754    #[serde(
1755        rename = "stopSequences",
1756        default,
1757        skip_serializing_if = "Option::is_none"
1758    )]
1759    pub stop_sequences: Option<Vec<String>>,
1760    /// Optional model-selection preferences.
1761    #[serde(
1762        rename = "modelPreferences",
1763        default,
1764        skip_serializing_if = "Option::is_none"
1765    )]
1766    pub model_preferences: Option<crate::types::ModelPreferences>,
1767    /// Optional requested MCP context inclusion.
1768    #[serde(
1769        rename = "includeContext",
1770        default,
1771        skip_serializing_if = "Option::is_none"
1772    )]
1773    pub include_context: Option<IncludeContext>,
1774    /// Optional provider-specific metadata.
1775    #[serde(default, skip_serializing_if = "Option::is_none")]
1776    pub metadata: Option<serde_json::Map<String, Value>>,
1777    /// Optional tools the model may call.
1778    #[serde(default, skip_serializing_if = "Option::is_none")]
1779    pub tools: Option<Vec<crate::types::FinalTool>>,
1780    /// Optional tool-selection controls.
1781    #[serde(
1782        rename = "toolChoice",
1783        default,
1784        skip_serializing_if = "Option::is_none"
1785    )]
1786    pub tool_choice: Option<crate::types::FinalToolChoice>,
1787}
1788
1789/// Exact final `sampling/createMessage` complete payload.
1790#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1791#[serde(deny_unknown_fields)]
1792pub struct FinalCreateMessageResult {
1793    /// Generated final sampling content.
1794    pub content: crate::types::FinalSamplingMessageContent,
1795    /// Model name selected by the client.
1796    pub model: String,
1797    /// Generated message role.
1798    pub role: crate::types::Role,
1799    /// Optional open sampling stop reason.
1800    #[serde(
1801        rename = "stopReason",
1802        default,
1803        skip_serializing_if = "Option::is_none"
1804    )]
1805    pub stop_reason: Option<String>,
1806    /// Optional final metadata on this embedded MRTR input response value.
1807    ///
1808    /// This payload is not a JSON-RPC result envelope, so it deliberately
1809    /// carries no `resultType` discriminator.
1810    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1811    pub meta: Option<OpenMetadata>,
1812}
1813
1814/// Exact final `sampling/createMessage` input-required result.
1815#[derive(Debug, Clone, Serialize, Deserialize)]
1816#[serde(deny_unknown_fields)]
1817pub struct FinalCreateMessageInputRequiredResult {
1818    /// Mandatory final discriminator, fixed to `input_required`.
1819    #[serde(rename = "resultType")]
1820    pub result_type: FinalInputRequiredResultType,
1821    /// Optional final result metadata.
1822    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1823    pub meta: Option<OpenMetadata>,
1824    /// Server-initiated requests that must be fulfilled before retrying.
1825    #[serde(
1826        rename = "inputRequests",
1827        default,
1828        skip_serializing_if = "Option::is_none"
1829    )]
1830    pub input_requests: Option<BTreeMap<String, Value>>,
1831    /// Opaque state retained for the retry.
1832    #[serde(
1833        rename = "requestState",
1834        default,
1835        skip_serializing_if = "Option::is_none"
1836    )]
1837    pub request_state: Option<String>,
1838}
1839
1840impl FinalCreateMessageInputRequiredResult {
1841    /// Validates the final input-required presence invariant.
1842    pub fn validate(&self) -> Result<(), CoreDispatchError> {
1843        if self.input_requests.is_some() || self.request_state.is_some() {
1844            Ok(())
1845        } else {
1846            Err(CoreDispatchError::InvalidResult {
1847                era: ProtocolEra::Modern2026,
1848                method: SAMPLING_CREATE_MESSAGE,
1849            })
1850        }
1851    }
1852}
1853
1854/// Final input-required discriminator.
1855#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1856pub enum FinalInputRequiredResultType {
1857    /// Additional input is required before retrying the original request.
1858    #[serde(rename = "input_required")]
1859    InputRequired,
1860}
1861
1862/// A final MRTR descriptor embedded in a Task, without a JSON-RPC envelope.
1863///
1864/// Task input requests are correlated exclusively by their containing map
1865/// key.  Consequently these descriptors deliberately omit `jsonrpc`, `id`,
1866/// and the outer request `_meta` capability object.
1867#[derive(Debug, Clone)]
1868pub enum FinalEmbeddedInputRequest {
1869    /// A final sampling descriptor.
1870    Sampling(FinalEmbeddedCreateMessageParams),
1871    /// A roots-list descriptor.
1872    Roots(FinalEmbeddedRootsListParams),
1873    /// A form or URL elicitation descriptor.
1874    Elicitation(FinalEmbeddedElicitationParams),
1875}
1876
1877impl FinalEmbeddedInputRequest {
1878    /// Returns the response kind that may answer this descriptor.
1879    #[must_use]
1880    pub const fn response_kind(&self) -> FinalEmbeddedInputKind {
1881        match self {
1882            Self::Sampling(_) => FinalEmbeddedInputKind::Sampling,
1883            Self::Roots(_) => FinalEmbeddedInputKind::Roots,
1884            Self::Elicitation(FinalEmbeddedElicitationParams::Form(_)) => {
1885                FinalEmbeddedInputKind::FormElicitation
1886            }
1887            Self::Elicitation(FinalEmbeddedElicitationParams::Url(_)) => {
1888                FinalEmbeddedInputKind::UrlElicitation
1889            }
1890        }
1891    }
1892}
1893
1894impl Serialize for FinalEmbeddedInputRequest {
1895    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1896    where
1897        S: Serializer,
1898    {
1899        let mut object = serde_json::Map::new();
1900        match self {
1901            Self::Sampling(params) => {
1902                object.insert(
1903                    "method".to_owned(),
1904                    Value::String("sampling/createMessage".to_owned()),
1905                );
1906                object.insert(
1907                    "params".to_owned(),
1908                    serde_json::to_value(params).map_err(serde::ser::Error::custom)?,
1909                );
1910            }
1911            Self::Roots(params) => {
1912                object.insert("method".to_owned(), Value::String("roots/list".to_owned()));
1913                if !params.is_empty() {
1914                    object.insert(
1915                        "params".to_owned(),
1916                        serde_json::to_value(params).map_err(serde::ser::Error::custom)?,
1917                    );
1918                }
1919            }
1920            Self::Elicitation(params) => {
1921                object.insert(
1922                    "method".to_owned(),
1923                    Value::String("elicitation/create".to_owned()),
1924                );
1925                object.insert(
1926                    "params".to_owned(),
1927                    serde_json::to_value(params).map_err(serde::ser::Error::custom)?,
1928                );
1929            }
1930        }
1931        Value::Object(object).serialize(serializer)
1932    }
1933}
1934
1935impl<'de> Deserialize<'de> for FinalEmbeddedInputRequest {
1936    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1937    where
1938        D: Deserializer<'de>,
1939    {
1940        let value = Value::deserialize(deserializer)?;
1941        let (method, params) =
1942            take_embedded_request_members(value).map_err(serde::de::Error::custom)?;
1943        match method.as_str() {
1944            "sampling/createMessage" => {
1945                let params = params.ok_or_else(|| {
1946                    serde::de::Error::custom("sampling descriptor requires params")
1947                })?;
1948                serde_json::from_value(params)
1949                    .map(Self::Sampling)
1950                    .map_err(serde::de::Error::custom)
1951            }
1952            "roots/list" => {
1953                let params = params.unwrap_or_else(|| Value::Object(serde_json::Map::new()));
1954                let params: FinalEmbeddedRootsListParams =
1955                    serde_json::from_value(params).map_err(serde::de::Error::custom)?;
1956                params.validate().map_err(serde::de::Error::custom)?;
1957                Ok(Self::Roots(params))
1958            }
1959            "elicitation/create" => {
1960                let params = params.ok_or_else(|| {
1961                    serde::de::Error::custom("elicitation descriptor requires params")
1962                })?;
1963                serde_json::from_value(params)
1964                    .map(Self::Elicitation)
1965                    .map_err(serde::de::Error::custom)
1966            }
1967            _ => Err(serde::de::Error::custom(
1968                "unsupported embedded input method",
1969            )),
1970        }
1971    }
1972}
1973
1974fn take_embedded_request_members(value: Value) -> Result<(String, Option<Value>), &'static str> {
1975    let Value::Object(mut members) = value else {
1976        return Err("embedded input request must be an object");
1977    };
1978    let method = members
1979        .remove("method")
1980        .and_then(|value| value.as_str().map(str::to_owned))
1981        .ok_or("embedded input request requires a string method")?;
1982    let params = members.remove("params");
1983    if members.is_empty() {
1984        Ok((method, params))
1985    } else {
1986        Err("embedded input request has unknown envelope members")
1987    }
1988}
1989
1990/// The selected final MRTR response kind for one Task map key.
1991#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1992pub enum FinalEmbeddedInputKind {
1993    /// A sampling response.
1994    Sampling,
1995    /// A roots-list response.
1996    Roots,
1997    /// A form-elicitation response.
1998    FormElicitation,
1999    /// A URL-elicitation response.
2000    UrlElicitation,
2001}
2002
2003/// Exact final parameters for an embedded sampling descriptor.
2004#[derive(Debug, Clone, Serialize, Deserialize)]
2005#[serde(deny_unknown_fields)]
2006pub struct FinalEmbeddedCreateMessageParams {
2007    /// Sampling conversation.
2008    pub messages: Vec<crate::types::FinalSamplingMessage>,
2009    /// Requested maximum token count as an arbitrary-width JSON integer.
2010    #[serde(rename = "maxTokens")]
2011    pub max_tokens: JsonInteger,
2012    /// Optional system prompt.
2013    #[serde(
2014        rename = "systemPrompt",
2015        default,
2016        skip_serializing_if = "Option::is_none"
2017    )]
2018    pub system_prompt: Option<String>,
2019    /// Optional sampling temperature.
2020    #[serde(default, skip_serializing_if = "Option::is_none")]
2021    pub temperature: Option<f64>,
2022    /// Optional stopping sequences.
2023    #[serde(
2024        rename = "stopSequences",
2025        default,
2026        skip_serializing_if = "Option::is_none"
2027    )]
2028    pub stop_sequences: Option<Vec<String>>,
2029    /// Optional model-selection preferences.
2030    #[serde(
2031        rename = "modelPreferences",
2032        default,
2033        skip_serializing_if = "Option::is_none"
2034    )]
2035    pub model_preferences: Option<crate::types::ModelPreferences>,
2036    /// Optional requested MCP context inclusion.
2037    #[serde(
2038        rename = "includeContext",
2039        default,
2040        skip_serializing_if = "Option::is_none"
2041    )]
2042    pub include_context: Option<IncludeContext>,
2043    /// Optional provider-specific metadata.
2044    #[serde(default, skip_serializing_if = "Option::is_none")]
2045    pub metadata: Option<serde_json::Map<String, Value>>,
2046    /// Optional tools the model may call.
2047    #[serde(default, skip_serializing_if = "Option::is_none")]
2048    pub tools: Option<Vec<crate::types::FinalTool>>,
2049    /// Optional tool-selection controls.
2050    #[serde(
2051        rename = "toolChoice",
2052        default,
2053        skip_serializing_if = "Option::is_none"
2054    )]
2055    pub tool_choice: Option<crate::types::FinalToolChoice>,
2056}
2057
2058/// Bounded generic metadata permitted on an embedded roots descriptor.
2059#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2060#[serde(deny_unknown_fields)]
2061pub struct FinalEmbeddedRootsListParams {
2062    /// Generic inert metadata. It cannot carry final request authority.
2063    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
2064    pub meta: Option<OpenMetadata>,
2065}
2066
2067impl FinalEmbeddedRootsListParams {
2068    fn is_empty(&self) -> bool {
2069        self.meta.is_none()
2070    }
2071
2072    fn validate(&self) -> Result<(), &'static str> {
2073        let Some(meta) = &self.meta else {
2074            return Ok(());
2075        };
2076        if meta.entries().contains_key(FINAL_PROTOCOL_VERSION_META_KEY)
2077            || meta
2078                .entries()
2079                .contains_key(FINAL_CLIENT_CAPABILITIES_META_KEY)
2080        {
2081            return Err("embedded roots metadata cannot carry outer request authority");
2082        }
2083        Ok(())
2084    }
2085}
2086
2087/// Exact final parameters for an embedded elicitation descriptor.
2088#[derive(Debug, Clone)]
2089pub enum FinalEmbeddedElicitationParams {
2090    /// In-band form elicitation.
2091    Form(FinalEmbeddedFormElicitationParams),
2092    /// External URL elicitation.
2093    Url(FinalEmbeddedUrlElicitationParams),
2094}
2095
2096impl Serialize for FinalEmbeddedElicitationParams {
2097    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2098    where
2099        S: Serializer,
2100    {
2101        match self {
2102            Self::Form(params) if params.mode == ElicitMode::Form => params.serialize(serializer),
2103            Self::Url(params) if params.mode == ElicitMode::Url => params.serialize(serializer),
2104            Self::Form(_) => Err(serde::ser::Error::custom(
2105                "form elicitation descriptor must use mode form",
2106            )),
2107            Self::Url(_) => Err(serde::ser::Error::custom(
2108                "URL elicitation descriptor must use mode url",
2109            )),
2110        }
2111    }
2112}
2113
2114impl<'de> Deserialize<'de> for FinalEmbeddedElicitationParams {
2115    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2116    where
2117        D: Deserializer<'de>,
2118    {
2119        let value = Value::deserialize(deserializer)?;
2120        let mode = value
2121            .as_object()
2122            .and_then(|members| members.get("mode"))
2123            .and_then(Value::as_str)
2124            .ok_or_else(|| serde::de::Error::custom("elicitation descriptor requires mode"))?;
2125        match mode {
2126            "form" => serde_json::from_value(value)
2127                .map(Self::Form)
2128                .map_err(serde::de::Error::custom),
2129            "url" => serde_json::from_value(value)
2130                .map(Self::Url)
2131                .map_err(serde::de::Error::custom),
2132            _ => Err(serde::de::Error::custom("unsupported elicitation mode")),
2133        }
2134    }
2135}
2136
2137/// Form elicitation request parameters without an outer request envelope.
2138#[derive(Debug, Clone, Serialize, Deserialize)]
2139#[serde(deny_unknown_fields)]
2140pub struct FinalEmbeddedFormElicitationParams {
2141    /// Exact form discriminator.
2142    pub mode: ElicitMode,
2143    /// User-facing request text.
2144    pub message: String,
2145    /// Requested form schema.
2146    #[serde(rename = "requestedSchema")]
2147    pub requested_schema: crate::schema::AdmittedFinalFormSchema,
2148}
2149
2150/// URL elicitation request parameters without legacy elicitation identity.
2151#[derive(Debug, Clone, Serialize, Deserialize)]
2152#[serde(deny_unknown_fields)]
2153pub struct FinalEmbeddedUrlElicitationParams {
2154    /// Exact URL discriminator.
2155    pub mode: ElicitMode,
2156    /// User-facing request text.
2157    pub message: String,
2158    /// Structurally admitted external URL.
2159    pub url: AbsoluteUri,
2160}
2161
2162/// A final MRTR result payload embedded in a Task, without a JSON-RPC envelope.
2163#[derive(Debug, Clone, PartialEq)]
2164#[allow(
2165    clippy::large_enum_variant,
2166    reason = "the public Task-input response union keeps every protocol-selected payload inline; boxing only sampling would add allocation and an asymmetric dereference to an otherwise uniform typed API"
2167)]
2168pub enum FinalEmbeddedInputResponse {
2169    /// Sampling result payload.
2170    Sampling(FinalCreateMessageResult),
2171    /// Roots-list result payload.
2172    Roots(FinalEmbeddedRootsListResult),
2173    /// Elicitation result payload. The request ledger determines form versus URL.
2174    Elicitation(FinalEmbeddedElicitationResult),
2175}
2176
2177impl FinalEmbeddedInputResponse {
2178    /// Returns whether this response can answer the supplied descriptor kind.
2179    #[must_use]
2180    pub fn matches_kind(&self, kind: FinalEmbeddedInputKind) -> bool {
2181        match (self, kind) {
2182            (Self::Sampling(_), FinalEmbeddedInputKind::Sampling)
2183            | (Self::Roots(_), FinalEmbeddedInputKind::Roots) => true,
2184            (Self::Elicitation(response), FinalEmbeddedInputKind::FormElicitation) => {
2185                response.valid_for_form()
2186            }
2187            (Self::Elicitation(response), FinalEmbeddedInputKind::UrlElicitation) => {
2188                response.valid_for_url()
2189            }
2190            _ => false,
2191        }
2192    }
2193}
2194
2195impl Serialize for FinalEmbeddedInputResponse {
2196    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2197    where
2198        S: Serializer,
2199    {
2200        match self {
2201            Self::Sampling(response) => response.serialize(serializer),
2202            Self::Roots(response) => response.serialize(serializer),
2203            Self::Elicitation(response) => response.serialize(serializer),
2204        }
2205    }
2206}
2207
2208impl<'de> Deserialize<'de> for FinalEmbeddedInputResponse {
2209    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2210    where
2211        D: Deserializer<'de>,
2212    {
2213        let value = Value::deserialize(deserializer)?;
2214        let object = value
2215            .as_object()
2216            .ok_or_else(|| serde::de::Error::custom("embedded input response must be an object"))?;
2217        if object.contains_key("jsonrpc")
2218            || object.contains_key("id")
2219            || object.contains_key("result")
2220        {
2221            return Err(serde::de::Error::custom(
2222                "embedded input response cannot be a JSON-RPC envelope",
2223            ));
2224        }
2225        if object.contains_key("model") || object.contains_key("role") {
2226            return serde_json::from_value(value)
2227                .map(Self::Sampling)
2228                .map_err(serde::de::Error::custom);
2229        }
2230        if object.contains_key("roots") {
2231            return serde_json::from_value(value)
2232                .map(Self::Roots)
2233                .map_err(serde::de::Error::custom);
2234        }
2235        if object.contains_key("action") {
2236            return serde_json::from_value(value)
2237                .map(Self::Elicitation)
2238                .map_err(serde::de::Error::custom);
2239        }
2240        Err(serde::de::Error::custom(
2241            "unsupported embedded input response",
2242        ))
2243    }
2244}
2245
2246/// Exact roots-list result payload embedded in a Task input response map.
2247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2248#[serde(deny_unknown_fields)]
2249pub struct FinalEmbeddedRootsListResult {
2250    /// Roots supplied by the client.
2251    pub roots: Vec<Root>,
2252}
2253
2254/// Exact elicitation response payload embedded in a Task input response map.
2255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2256#[serde(deny_unknown_fields)]
2257pub struct FinalEmbeddedElicitationResult {
2258    /// User action.
2259    pub action: ElicitAction,
2260    /// Optional submitted form data.
2261    #[serde(default, skip_serializing_if = "Option::is_none")]
2262    pub content: Option<BTreeMap<String, ElicitContentValue>>,
2263}
2264
2265impl FinalEmbeddedElicitationResult {
2266    fn valid_for_form(&self) -> bool {
2267        match self.action {
2268            ElicitAction::Accept => self.content.is_some(),
2269            ElicitAction::Decline | ElicitAction::Cancel => self.content.is_none(),
2270        }
2271    }
2272
2273    fn valid_for_url(&self) -> bool {
2274        self.content.is_none()
2275    }
2276}
2277
2278/// Final `tools/list` result payload.
2279#[derive(Debug, Clone, Serialize, Deserialize)]
2280pub struct FinalListToolsResult {
2281    /// Catalog tools in their selected order.
2282    pub tools: Vec<crate::types::FinalTool>,
2283    /// Opaque next cursor, if another page is available.
2284    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
2285    pub next_cursor: Option<String>,
2286    /// Required lossless final cache lifetime.
2287    #[serde(rename = "ttlMs")]
2288    pub ttl_ms: crate::result::CacheTtl,
2289    /// Required final cache sharing scope.
2290    #[serde(
2291        rename = "cacheScope",
2292        serialize_with = "serialize_cache_scope",
2293        deserialize_with = "deserialize_cache_scope"
2294    )]
2295    pub cache_scope: crate::result::CacheScope,
2296}
2297
2298/// Final `tools/call` result payload using final common content blocks.
2299#[derive(Debug, Clone, Serialize, Deserialize)]
2300pub struct FinalCallToolResult {
2301    /// Final common output content.
2302    pub content: Vec<ContentBlock>,
2303    /// Whether the tool execution completed with a tool-level error.
2304    #[serde(
2305        rename = "isError",
2306        default,
2307        skip_serializing_if = "std::ops::Not::not"
2308    )]
2309    pub is_error: bool,
2310    /// Optional structured tool output, validated by the advertised output schema.
2311    #[serde(
2312        rename = "structuredContent",
2313        default,
2314        skip_serializing_if = "Option::is_none",
2315        deserialize_with = "deserialize_present_json_value"
2316    )]
2317    pub structured_content: Option<Value>,
2318}
2319
2320impl crate::result::CompleteResultPayload for FinalCallToolResult {
2321    const KNOWN_MEMBER_NAMES: &'static [&'static str] =
2322        &["content", "isError", "structuredContent"];
2323
2324    fn decode_known_members(
2325        members: &mut crate::result::TypedCompleteMembers<'_>,
2326    ) -> Result<Self, ResultDecodeError> {
2327        let mut selected = serde_json::Map::new();
2328        for name in Self::KNOWN_MEMBER_NAMES {
2329            if let Some(value) = members.take(name)? {
2330                selected.insert((*name).to_owned(), exact_json_to_serde(&value)?);
2331            }
2332        }
2333        serde_json::from_value(Value::Object(selected))
2334            .map_err(|_| ResultDecodeError::invalid_known_member("$.content"))
2335    }
2336}
2337
2338fn deserialize_present_json_value<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
2339where
2340    D: Deserializer<'de>,
2341{
2342    Value::deserialize(deserializer).map(Some)
2343}
2344
2345/// Final `resources/list` result payload.
2346#[derive(Debug, Clone, Serialize, Deserialize)]
2347pub struct FinalListResourcesResult {
2348    /// Catalog resources in their selected order.
2349    pub resources: Vec<crate::types::FinalResource>,
2350    /// Opaque next cursor, if another page is available.
2351    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
2352    pub next_cursor: Option<String>,
2353    /// Required lossless final cache lifetime.
2354    #[serde(rename = "ttlMs")]
2355    pub ttl_ms: crate::result::CacheTtl,
2356    /// Required final cache sharing scope.
2357    #[serde(
2358        rename = "cacheScope",
2359        serialize_with = "serialize_cache_scope",
2360        deserialize_with = "deserialize_cache_scope"
2361    )]
2362    pub cache_scope: crate::result::CacheScope,
2363}
2364
2365/// Final `resources/templates/list` result payload.
2366#[derive(Debug, Clone, Serialize, Deserialize)]
2367pub struct FinalListResourceTemplatesResult {
2368    /// Catalog templates in their selected order.
2369    #[serde(rename = "resourceTemplates")]
2370    pub resource_templates: Vec<crate::types::FinalResourceTemplate>,
2371    /// Opaque next cursor, if another page is available.
2372    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
2373    pub next_cursor: Option<String>,
2374    /// Required lossless final cache lifetime.
2375    #[serde(rename = "ttlMs")]
2376    pub ttl_ms: crate::result::CacheTtl,
2377    /// Required final cache sharing scope.
2378    #[serde(
2379        rename = "cacheScope",
2380        serialize_with = "serialize_cache_scope",
2381        deserialize_with = "deserialize_cache_scope"
2382    )]
2383    pub cache_scope: crate::result::CacheScope,
2384}
2385
2386/// Final `resources/read` result payload using final common resource content.
2387#[derive(Debug, Clone, Serialize, Deserialize)]
2388pub struct FinalReadResourceResult {
2389    /// Read resource contents.
2390    pub contents: Vec<EmbeddedResourceContents>,
2391    /// Required lossless final cache lifetime.
2392    #[serde(rename = "ttlMs")]
2393    pub ttl_ms: crate::result::CacheTtl,
2394    /// Required final cache sharing scope.
2395    #[serde(
2396        rename = "cacheScope",
2397        serialize_with = "serialize_cache_scope",
2398        deserialize_with = "deserialize_cache_scope"
2399    )]
2400    pub cache_scope: crate::result::CacheScope,
2401}
2402
2403/// Final `prompts/list` result payload.
2404#[derive(Debug, Clone, Serialize, Deserialize)]
2405pub struct FinalListPromptsResult {
2406    /// Catalog prompts in their selected order.
2407    pub prompts: Vec<crate::types::FinalPrompt>,
2408    /// Opaque next cursor, if another page is available.
2409    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
2410    pub next_cursor: Option<String>,
2411    /// Required lossless final cache lifetime.
2412    #[serde(rename = "ttlMs")]
2413    pub ttl_ms: crate::result::CacheTtl,
2414    /// Required final cache sharing scope.
2415    #[serde(
2416        rename = "cacheScope",
2417        serialize_with = "serialize_cache_scope",
2418        deserialize_with = "deserialize_cache_scope"
2419    )]
2420    pub cache_scope: crate::result::CacheScope,
2421}
2422
2423fn serialize_cache_scope<S>(
2424    scope: &crate::result::CacheScope,
2425    serializer: S,
2426) -> Result<S::Ok, S::Error>
2427where
2428    S: serde::Serializer,
2429{
2430    serializer.serialize_str(match scope {
2431        crate::result::CacheScope::Public => "public",
2432        crate::result::CacheScope::Private => "private",
2433    })
2434}
2435
2436fn deserialize_cache_scope<'de, D>(deserializer: D) -> Result<crate::result::CacheScope, D::Error>
2437where
2438    D: serde::Deserializer<'de>,
2439{
2440    match String::deserialize(deserializer)?.as_str() {
2441        "public" => Ok(crate::result::CacheScope::Public),
2442        "private" => Ok(crate::result::CacheScope::Private),
2443        _ => Err(serde::de::Error::custom(
2444            "cacheScope must be `public` or `private`",
2445        )),
2446    }
2447}
2448
2449/// One final prompt message using a final common content block.
2450#[derive(Debug, Clone, Serialize, Deserialize)]
2451pub struct FinalPromptMessage {
2452    /// Role of the prompt message author.
2453    pub role: crate::types::Role,
2454    /// Final common message content.
2455    pub content: ContentBlock,
2456}
2457
2458/// Final `prompts/get` result payload.
2459#[derive(Debug, Clone, Serialize, Deserialize)]
2460pub struct FinalGetPromptResult {
2461    /// Optional prompt description.
2462    #[serde(skip_serializing_if = "Option::is_none")]
2463    pub description: Option<String>,
2464    /// Prompt messages.
2465    pub messages: Vec<FinalPromptMessage>,
2466}
2467
2468// ============================================================================
2469// Final directional notification unions
2470// ============================================================================
2471
2472/// Typed admission error for an MCP 2026-07-28 notification union.
2473#[derive(Debug, Clone, PartialEq, Eq)]
2474pub enum FinalNotificationError {
2475    /// The public JSON-RPC struct contained invalid envelope data.
2476    InvalidEnvelope { method: String },
2477    /// A notification union was given a request with an ID.
2478    RequestIdPresent { method: String },
2479    /// The method is not part of the active final core method table.
2480    UnsupportedMethod { method: String },
2481    /// The method is a final request rather than a final notification.
2482    WrongEnvelope { method: String },
2483    /// The selected peer cannot originate this notification method.
2484    WrongDirection {
2485        /// Exact notification method literal.
2486        method: String,
2487        /// Peer that attempted to originate it.
2488        sender: Final2026Peer,
2489    },
2490    /// The method's required parameter shape was missing or invalid.
2491    InvalidParams { method: &'static str },
2492    /// A locally constructed typed parameter object could not be encoded.
2493    EncodeFailure { method: &'static str },
2494}
2495
2496impl std::fmt::Display for FinalNotificationError {
2497    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2498        match self {
2499            Self::InvalidEnvelope { method } => {
2500                write!(
2501                    formatter,
2502                    "invalid JSON-RPC notification envelope for {method}"
2503                )
2504            }
2505            Self::RequestIdPresent { method } => {
2506                write!(formatter, "{method} must be a JSON-RPC notification")
2507            }
2508            Self::UnsupportedMethod { method } => {
2509                write!(formatter, "{method} is not an active final notification")
2510            }
2511            Self::WrongEnvelope { method } => {
2512                write!(formatter, "{method} is a final request, not a notification")
2513            }
2514            Self::WrongDirection { method, sender } => {
2515                write!(
2516                    formatter,
2517                    "{sender:?} cannot send final notification {method}"
2518                )
2519            }
2520            Self::InvalidParams { method } => {
2521                write!(
2522                    formatter,
2523                    "invalid final notification parameters for {method}"
2524                )
2525            }
2526            Self::EncodeFailure { method } => {
2527                write!(
2528                    formatter,
2529                    "unable to encode final notification parameters for {method}"
2530                )
2531            }
2532        }
2533    }
2534}
2535
2536impl std::error::Error for FinalNotificationError {}
2537
2538/// The one notification a final client may originate.
2539#[derive(Debug, Clone)]
2540pub enum ClientNotification {
2541    /// `notifications/cancelled` for a client-owned request.
2542    Cancelled(FinalCancelledNotificationParams),
2543}
2544
2545impl ClientNotification {
2546    /// Admits one JSON-RPC notification only from the exact final client union.
2547    pub fn decode(request: &JsonRpcRequest) -> Result<Self, FinalNotificationError> {
2548        admit_final_notification(request, Final2026Peer::Client)?;
2549        match request.method.as_str() {
2550            NOTIFICATIONS_CANCELLED => {
2551                decode_required_final_notification_params(request).map(Self::Cancelled)
2552            }
2553            _ => Err(FinalNotificationError::WrongDirection {
2554                method: request.method.clone(),
2555                sender: Final2026Peer::Client,
2556            }),
2557        }
2558    }
2559
2560    /// Returns this notification's exact method literal.
2561    #[must_use]
2562    pub const fn method(&self) -> &'static str {
2563        match self {
2564            Self::Cancelled(_) => NOTIFICATIONS_CANCELLED,
2565        }
2566    }
2567
2568    /// Encodes this typed union as an ID-free JSON-RPC notification.
2569    pub fn encode(&self) -> Result<JsonRpcRequest, FinalNotificationError> {
2570        let params = match self {
2571            Self::Cancelled(params) => {
2572                encode_final_notification_params(NOTIFICATIONS_CANCELLED, params)?
2573            }
2574        };
2575        Ok(JsonRpcRequest::notification(self.method(), Some(params)))
2576    }
2577}
2578
2579/// The eight notifications a final server may originate.
2580#[derive(Debug, Clone)]
2581pub enum ServerNotification {
2582    /// `notifications/cancelled` for a server-terminated subscription stream.
2583    Cancelled(FinalCancelledNotificationParams),
2584    /// `notifications/progress` for an in-flight client request.
2585    Progress(FinalProgressNotificationParams),
2586    /// `notifications/message` log event.
2587    Message(FinalLogMessageParams),
2588    /// `notifications/resources/updated` resource change event.
2589    ResourceUpdated(FinalResourceUpdatedNotificationParams),
2590    /// `notifications/resources/list_changed` catalog change event.
2591    ResourcesListChanged(Option<FinalEmptyNotificationParams>),
2592    /// `notifications/tools/list_changed` catalog change event.
2593    ToolsListChanged(Option<FinalEmptyNotificationParams>),
2594    /// `notifications/prompts/list_changed` catalog change event.
2595    PromptsListChanged(Option<FinalEmptyNotificationParams>),
2596    /// `notifications/subscriptions/acknowledged` stream acknowledgement.
2597    SubscriptionsAcknowledged(FinalSubscriptionsAcknowledgedNotificationParams),
2598}
2599
2600impl ServerNotification {
2601    /// Admits one JSON-RPC notification only from the exact final server union.
2602    pub fn decode(request: &JsonRpcRequest) -> Result<Self, FinalNotificationError> {
2603        admit_final_notification(request, Final2026Peer::Server)?;
2604        match request.method.as_str() {
2605            NOTIFICATIONS_CANCELLED => {
2606                decode_required_final_notification_params(request).map(Self::Cancelled)
2607            }
2608            NOTIFICATIONS_PROGRESS => {
2609                decode_required_final_notification_params(request).map(Self::Progress)
2610            }
2611            NOTIFICATIONS_MESSAGE => {
2612                decode_required_final_notification_params(request).map(Self::Message)
2613            }
2614            NOTIFICATIONS_RESOURCES_UPDATED => {
2615                decode_required_final_notification_params(request).map(Self::ResourceUpdated)
2616            }
2617            NOTIFICATIONS_RESOURCES_LIST_CHANGED => {
2618                decode_optional_final_notification_params(request).map(Self::ResourcesListChanged)
2619            }
2620            NOTIFICATIONS_TOOLS_LIST_CHANGED => {
2621                decode_optional_final_notification_params(request).map(Self::ToolsListChanged)
2622            }
2623            NOTIFICATIONS_PROMPTS_LIST_CHANGED => {
2624                decode_optional_final_notification_params(request).map(Self::PromptsListChanged)
2625            }
2626            NOTIFICATIONS_SUBSCRIPTIONS_ACKNOWLEDGED => {
2627                decode_required_final_notification_params(request)
2628                    .map(Self::SubscriptionsAcknowledged)
2629            }
2630            _ => Err(FinalNotificationError::WrongDirection {
2631                method: request.method.clone(),
2632                sender: Final2026Peer::Server,
2633            }),
2634        }
2635    }
2636
2637    /// Decodes a final server notification with the exact received `params` JSON.
2638    ///
2639    /// Only the modern progress branch needs this companion to preserve native-size
2640    /// decimal and exponent spellings that a materialized [`Value`] normalizes.
2641    /// The caller must pass the raw `params` member from the same admitted frame.
2642    /// Other final notification branches use [`Self::decode`] unchanged.
2643    pub fn decode_with_raw_params(
2644        request: &JsonRpcRequest,
2645        raw_params: &str,
2646    ) -> Result<Self, FinalNotificationError> {
2647        admit_final_notification(request, Final2026Peer::Server)?;
2648        if request.method != NOTIFICATIONS_PROGRESS {
2649            return Self::decode(request);
2650        }
2651
2652        let parsed_params = serde_json::from_str(raw_params).map_err(|_| {
2653            FinalNotificationError::InvalidParams {
2654                method: NOTIFICATIONS_PROGRESS,
2655            }
2656        })?;
2657        if request.params.as_ref() != Some(&parsed_params) {
2658            return Err(FinalNotificationError::InvalidParams {
2659                method: NOTIFICATIONS_PROGRESS,
2660            });
2661        }
2662        serde_json::from_str(raw_params)
2663            .map(Self::Progress)
2664            .map_err(|_| FinalNotificationError::InvalidParams {
2665                method: NOTIFICATIONS_PROGRESS,
2666            })
2667    }
2668
2669    /// Returns this notification's exact method literal.
2670    #[must_use]
2671    pub const fn method(&self) -> &'static str {
2672        match self {
2673            Self::Cancelled(_) => NOTIFICATIONS_CANCELLED,
2674            Self::Progress(_) => NOTIFICATIONS_PROGRESS,
2675            Self::Message(_) => NOTIFICATIONS_MESSAGE,
2676            Self::ResourceUpdated(_) => NOTIFICATIONS_RESOURCES_UPDATED,
2677            Self::ResourcesListChanged(_) => NOTIFICATIONS_RESOURCES_LIST_CHANGED,
2678            Self::ToolsListChanged(_) => NOTIFICATIONS_TOOLS_LIST_CHANGED,
2679            Self::PromptsListChanged(_) => NOTIFICATIONS_PROMPTS_LIST_CHANGED,
2680            Self::SubscriptionsAcknowledged(_) => NOTIFICATIONS_SUBSCRIPTIONS_ACKNOWLEDGED,
2681        }
2682    }
2683
2684    /// Encodes this typed union as an ID-free JSON-RPC notification.
2685    pub fn encode(&self) -> Result<JsonRpcRequest, FinalNotificationError> {
2686        let params = match self {
2687            Self::Cancelled(params) => Some(encode_final_notification_params(
2688                NOTIFICATIONS_CANCELLED,
2689                params,
2690            )?),
2691            Self::Progress(params) => Some(encode_final_notification_params(
2692                NOTIFICATIONS_PROGRESS,
2693                params,
2694            )?),
2695            Self::Message(params) => Some(encode_final_notification_params(
2696                NOTIFICATIONS_MESSAGE,
2697                params,
2698            )?),
2699            Self::ResourceUpdated(params) => Some(encode_final_notification_params(
2700                NOTIFICATIONS_RESOURCES_UPDATED,
2701                params,
2702            )?),
2703            Self::ResourcesListChanged(params) => params
2704                .as_ref()
2705                .map(|params| {
2706                    encode_final_notification_params(NOTIFICATIONS_RESOURCES_LIST_CHANGED, params)
2707                })
2708                .transpose()?,
2709            Self::ToolsListChanged(params) => params
2710                .as_ref()
2711                .map(|params| {
2712                    encode_final_notification_params(NOTIFICATIONS_TOOLS_LIST_CHANGED, params)
2713                })
2714                .transpose()?,
2715            Self::PromptsListChanged(params) => params
2716                .as_ref()
2717                .map(|params| {
2718                    encode_final_notification_params(NOTIFICATIONS_PROMPTS_LIST_CHANGED, params)
2719                })
2720                .transpose()?,
2721            Self::SubscriptionsAcknowledged(params) => Some(encode_final_notification_params(
2722                NOTIFICATIONS_SUBSCRIPTIONS_ACKNOWLEDGED,
2723                params,
2724            )?),
2725        };
2726        Ok(JsonRpcRequest::notification(self.method(), params))
2727    }
2728
2729    /// Serializes this notification as its exact JSON-RPC wire frame.
2730    ///
2731    /// [`encode`](Self::encode) carries its params as `serde_json::Value`,
2732    /// whose map representation cannot preserve member order, so byte-exact
2733    /// emission and round-trip fidelity proofs must use this string encoder:
2734    /// the typed params serialize directly, preserving declaration order and
2735    /// raw number lexemes.
2736    pub fn encode_wire(&self) -> Result<String, FinalNotificationError> {
2737        let method = self.method();
2738        let params = match self {
2739            Self::Cancelled(params) => Some(serde_json::to_string(params)),
2740            Self::Progress(params) => Some(serde_json::to_string(params)),
2741            Self::Message(params) => Some(serde_json::to_string(params)),
2742            Self::ResourceUpdated(params) => Some(serde_json::to_string(params)),
2743            Self::ResourcesListChanged(params)
2744            | Self::ToolsListChanged(params)
2745            | Self::PromptsListChanged(params) => params.as_ref().map(serde_json::to_string),
2746            Self::SubscriptionsAcknowledged(params) => Some(serde_json::to_string(params)),
2747        };
2748        let params = params
2749            .transpose()
2750            .map_err(|_| FinalNotificationError::EncodeFailure { method })?;
2751        Ok(match params {
2752            Some(params) => {
2753                format!(r#"{{"jsonrpc":"2.0","method":"{method}","params":{params}}}"#)
2754            }
2755            None => format!(r#"{{"jsonrpc":"2.0","method":"{method}"}}"#),
2756        })
2757    }
2758}
2759
2760fn admit_final_notification(
2761    request: &JsonRpcRequest,
2762    sender: Final2026Peer,
2763) -> Result<(), FinalNotificationError> {
2764    if request.validate().is_err() {
2765        return Err(FinalNotificationError::InvalidEnvelope {
2766            method: request.method.clone(),
2767        });
2768    }
2769    if !request.is_notification() {
2770        return Err(FinalNotificationError::RequestIdPresent {
2771            method: request.method.clone(),
2772        });
2773    }
2774    let Some(method) = final_2026_07_28_method(&request.method) else {
2775        return Err(FinalNotificationError::UnsupportedMethod {
2776            method: request.method.clone(),
2777        });
2778    };
2779    if !matches!(method.envelope, Final2026EnvelopeKind::Notification) {
2780        return Err(FinalNotificationError::WrongEnvelope {
2781            method: request.method.clone(),
2782        });
2783    }
2784    if !method.admits_notification_from(sender) {
2785        return Err(FinalNotificationError::WrongDirection {
2786            method: request.method.clone(),
2787            sender,
2788        });
2789    }
2790    Ok(())
2791}
2792
2793fn decode_required_final_notification_params<T: DeserializeOwned>(
2794    request: &JsonRpcRequest,
2795) -> Result<T, FinalNotificationError> {
2796    request
2797        .params
2798        .as_ref()
2799        .ok_or(FinalNotificationError::InvalidParams {
2800            method: notification_method_literal(request),
2801        })
2802        .and_then(|params| {
2803            serde_json::from_value(params.clone()).map_err(|_| {
2804                FinalNotificationError::InvalidParams {
2805                    method: notification_method_literal(request),
2806                }
2807            })
2808        })
2809}
2810
2811fn decode_optional_final_notification_params<T: DeserializeOwned>(
2812    request: &JsonRpcRequest,
2813) -> Result<Option<T>, FinalNotificationError> {
2814    request
2815        .params
2816        .as_ref()
2817        .map(|params| {
2818            serde_json::from_value(params.clone()).map_err(|_| {
2819                FinalNotificationError::InvalidParams {
2820                    method: notification_method_literal(request),
2821                }
2822            })
2823        })
2824        .transpose()
2825}
2826
2827fn encode_final_notification_params<T: Serialize>(
2828    method: &'static str,
2829    params: &T,
2830) -> Result<Value, FinalNotificationError> {
2831    serde_json::to_value(params).map_err(|_| FinalNotificationError::EncodeFailure { method })
2832}
2833
2834fn notification_method_literal(request: &JsonRpcRequest) -> &'static str {
2835    match request.method.as_str() {
2836        NOTIFICATIONS_CANCELLED => NOTIFICATIONS_CANCELLED,
2837        NOTIFICATIONS_PROGRESS => NOTIFICATIONS_PROGRESS,
2838        NOTIFICATIONS_MESSAGE => NOTIFICATIONS_MESSAGE,
2839        NOTIFICATIONS_RESOURCES_UPDATED => NOTIFICATIONS_RESOURCES_UPDATED,
2840        NOTIFICATIONS_RESOURCES_LIST_CHANGED => NOTIFICATIONS_RESOURCES_LIST_CHANGED,
2841        NOTIFICATIONS_TOOLS_LIST_CHANGED => NOTIFICATIONS_TOOLS_LIST_CHANGED,
2842        NOTIFICATIONS_PROMPTS_LIST_CHANGED => NOTIFICATIONS_PROMPTS_LIST_CHANGED,
2843        NOTIFICATIONS_SUBSCRIPTIONS_ACKNOWLEDGED => NOTIFICATIONS_SUBSCRIPTIONS_ACKNOWLEDGED,
2844        _ => unreachable!("notification admission rejects unknown method literals"),
2845    }
2846}
2847
2848/// Completion candidates returned by the legacy protocol era.
2849#[derive(Debug, Clone, Serialize, Deserialize)]
2850pub struct CompletionValues {
2851    /// Completion values selected by the server.
2852    #[serde(
2853        serialize_with = "serialize_completion_values",
2854        deserialize_with = "deserialize_completion_values"
2855    )]
2856    pub values: Vec<String>,
2857    /// Total number of available values, if known.
2858    #[serde(default, skip_serializing_if = "Option::is_none")]
2859    pub total: Option<i64>,
2860    /// Whether further completion values are available.
2861    #[serde(rename = "hasMore", default, skip_serializing_if = "Option::is_none")]
2862    pub has_more: Option<bool>,
2863}
2864
2865/// Completion candidates returned by the final protocol era.
2866///
2867/// The final schema's `total` is a mathematical JSON integer, so it retains
2868/// [`JsonInteger`] rather than narrowing a peer value to a machine integer.
2869#[derive(Debug, Clone, Serialize, Deserialize)]
2870pub struct FinalCompletionValues {
2871    /// Completion values selected by the server.
2872    #[serde(
2873        serialize_with = "serialize_final_completion_values",
2874        deserialize_with = "deserialize_final_completion_values"
2875    )]
2876    pub values: Vec<String>,
2877    /// Exact total number of available values, when the peer supplied one.
2878    #[serde(
2879        default,
2880        skip_serializing_if = "Option::is_none",
2881        serialize_with = "serialize_optional_final_completion_total",
2882        deserialize_with = "deserialize_optional_final_completion_total"
2883    )]
2884    pub total: Option<JsonInteger>,
2885    /// Whether further completion values are available.
2886    #[serde(
2887        rename = "hasMore",
2888        default,
2889        skip_serializing_if = "Option::is_none",
2890        deserialize_with = "deserialize_optional_final_completion_has_more"
2891    )]
2892    pub has_more: Option<bool>,
2893}
2894
2895/// Bounded peer conformance diagnostics specific to final completion values.
2896#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2897pub enum FinalCompletionPeerDiagnostic {
2898    /// A peer supplied a schema-valid negative total. It is retained only for
2899    /// display and compatibility; it must not control allocation, pagination,
2900    /// or local result emission.
2901    NegativeTotal,
2902}
2903
2904fn deserialize_optional_final_completion_total<'de, D>(
2905    deserializer: D,
2906) -> Result<Option<JsonInteger>, D::Error>
2907where
2908    D: Deserializer<'de>,
2909{
2910    let total = JsonInteger::deserialize(deserializer)?;
2911    Ok(Some(total))
2912}
2913
2914fn serialize_optional_final_completion_total<S>(
2915    total: &Option<JsonInteger>,
2916    serializer: S,
2917) -> Result<S::Ok, S::Error>
2918where
2919    S: Serializer,
2920{
2921    if total
2922        .as_ref()
2923        .is_some_and(final_completion_total_is_negative)
2924    {
2925        return Err(serde::ser::Error::custom(
2926            "final completion total must be a nonnegative JSON integer",
2927        ));
2928    }
2929    total.serialize(serializer)
2930}
2931
2932fn deserialize_optional_final_completion_has_more<'de, D>(
2933    deserializer: D,
2934) -> Result<Option<bool>, D::Error>
2935where
2936    D: Deserializer<'de>,
2937{
2938    bool::deserialize(deserializer).map(Some)
2939}
2940
2941/// Maximum completion candidates allowed on the wire by either supported era.
2942pub const MAX_COMPLETION_VALUES: usize = 100;
2943/// Maximum UTF-8 bytes in one final completion candidate.
2944pub const MAX_FINAL_COMPLETION_VALUE_BYTES: usize = 16 * 1024;
2945/// Maximum aggregate UTF-8 bytes in final completion candidates.
2946pub const MAX_FINAL_COMPLETION_VALUES_BYTES: usize = 256 * 1024;
2947
2948impl FinalCompletionValues {
2949    /// Returns the bounded compatibility diagnostic for an admitted peer
2950    /// total. Locally authored values must still pass [`Self::validate`].
2951    #[must_use]
2952    pub fn peer_diagnostic(&self) -> Option<FinalCompletionPeerDiagnostic> {
2953        self.total
2954            .as_ref()
2955            .filter(|total| final_completion_total_is_negative(total))
2956            .map(|_| FinalCompletionPeerDiagnostic::NegativeTotal)
2957    }
2958
2959    /// Validates the final-only bounds and nonnegative total invariant for
2960    /// local provider output.
2961    ///
2962    /// Exact MCP 2024-11-05 completion values deliberately retain their
2963    /// schema's unconstrained signed `total`; this validation belongs only to
2964    /// the final completion surface.
2965    pub fn validate(&self) -> Result<(), &'static str> {
2966        validate_final_completion_values(&self.values)?;
2967        if self
2968            .total
2969            .as_ref()
2970            .is_some_and(final_completion_total_is_negative)
2971        {
2972            return Err("final completion total must be a nonnegative JSON integer");
2973        }
2974        Ok(())
2975    }
2976}
2977
2978fn serialize_completion_values<S>(values: &Vec<String>, serializer: S) -> Result<S::Ok, S::Error>
2979where
2980    S: Serializer,
2981{
2982    if values.len() > MAX_COMPLETION_VALUES {
2983        return Err(serde::ser::Error::custom(
2984            "completion values exceed the maximum of 100 items",
2985        ));
2986    }
2987    values.serialize(serializer)
2988}
2989
2990fn deserialize_completion_values<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
2991where
2992    D: Deserializer<'de>,
2993{
2994    let values = Vec::<String>::deserialize(deserializer)?;
2995    if values.len() > MAX_COMPLETION_VALUES {
2996        return Err(serde::de::Error::custom(
2997            "completion values exceed the maximum of 100 items",
2998        ));
2999    }
3000    Ok(values)
3001}
3002
3003fn serialize_final_completion_values<S>(
3004    values: &Vec<String>,
3005    serializer: S,
3006) -> Result<S::Ok, S::Error>
3007where
3008    S: Serializer,
3009{
3010    validate_final_completion_values(values).map_err(serde::ser::Error::custom)?;
3011    values.serialize(serializer)
3012}
3013
3014fn deserialize_final_completion_values<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
3015where
3016    D: Deserializer<'de>,
3017{
3018    let values = Vec::<String>::deserialize(deserializer)?;
3019    validate_final_completion_values(&values).map_err(serde::de::Error::custom)?;
3020    Ok(values)
3021}
3022
3023fn validate_final_completion_values(values: &[String]) -> Result<(), &'static str> {
3024    if values.len() > MAX_COMPLETION_VALUES {
3025        return Err("completion values exceed the maximum of 100 items");
3026    }
3027
3028    let mut total_bytes = 0_usize;
3029    for value in values {
3030        if value.len() > MAX_FINAL_COMPLETION_VALUE_BYTES {
3031            return Err("final completion value exceeds the maximum of 16384 bytes");
3032        }
3033        total_bytes = total_bytes
3034            .checked_add(value.len())
3035            .ok_or("final completion values exceed the maximum aggregate byte limit")?;
3036        if total_bytes > MAX_FINAL_COMPLETION_VALUES_BYTES {
3037            return Err("final completion values exceed the maximum aggregate byte limit");
3038        }
3039    }
3040    Ok(())
3041}
3042
3043fn final_completion_total_is_negative(total: &JsonInteger) -> bool {
3044    let Some(absolute) = total.as_str().strip_prefix('-') else {
3045        return false;
3046    };
3047    absolute
3048        .split(['e', 'E'])
3049        .next()
3050        .is_some_and(|mantissa| mantissa.bytes().any(|byte| matches!(byte, b'1'..=b'9')))
3051}
3052
3053/// Exact legacy `completion/complete` result payload.
3054#[derive(Debug, Clone, Serialize, Deserialize)]
3055pub struct LegacyCompletionResult {
3056    /// Completion candidates.
3057    pub completion: CompletionValues,
3058    /// Opaque legacy response metadata retained without interpretation.
3059    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
3060    pub meta: Option<LegacyOpaqueMetadata>,
3061}
3062
3063/// Ordered opaque legacy metadata.
3064///
3065/// `serde_json::Map` uses a key-sorted representation in this workspace. The
3066/// Legacy result wires promise the received `_meta` member order remains
3067/// observable on replay, so this narrow wrapper retains object-member order.
3068#[derive(Debug, Clone, Default, PartialEq)]
3069pub struct LegacyOpaqueMetadata {
3070    entries: Vec<(String, Value)>,
3071}
3072
3073impl LegacyOpaqueMetadata {
3074    /// Looks up one retained metadata value.
3075    #[must_use]
3076    pub fn get(&self, key: &str) -> Option<&Value> {
3077        self.entries
3078            .iter()
3079            .find(|(entry_key, _)| entry_key == key)
3080            .map(|(_, value)| value)
3081    }
3082
3083    /// Returns metadata entries in their original wire order.
3084    #[must_use]
3085    pub fn entries(&self) -> &[(String, Value)] {
3086        &self.entries
3087    }
3088}
3089
3090impl Serialize for LegacyOpaqueMetadata {
3091    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3092    where
3093        S: Serializer,
3094    {
3095        let mut map = serializer.serialize_map(Some(self.entries.len()))?;
3096        for (key, value) in &self.entries {
3097            map.serialize_entry(key, value)?;
3098        }
3099        map.end()
3100    }
3101}
3102
3103impl<'de> Deserialize<'de> for LegacyOpaqueMetadata {
3104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3105    where
3106        D: Deserializer<'de>,
3107    {
3108        struct MetadataVisitor;
3109
3110        impl<'de> Visitor<'de> for MetadataVisitor {
3111            type Value = LegacyOpaqueMetadata;
3112
3113            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3114                formatter.write_str("an object of legacy metadata")
3115            }
3116
3117            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
3118            where
3119                A: serde::de::MapAccess<'de>,
3120            {
3121                let mut entries = Vec::new();
3122                while let Some((key, value)) = map.next_entry::<String, Value>()? {
3123                    if entries.iter().any(|(existing, _)| existing == &key) {
3124                        return Err(serde::de::Error::custom("duplicate legacy metadata member"));
3125                    }
3126                    entries.push((key, value));
3127                }
3128                Ok(LegacyOpaqueMetadata { entries })
3129            }
3130        }
3131
3132        deserializer.deserialize_map(MetadataVisitor)
3133    }
3134}
3135
3136/// Final `completion/complete` result payload.
3137#[derive(Debug, Clone, Serialize, Deserialize)]
3138pub struct FinalCompletionResult {
3139    /// Completion candidates.
3140    pub completion: FinalCompletionValues,
3141}
3142
3143/// Empty final `subscriptions/listen` result body.
3144///
3145/// The required subscription-stream ID is carried in the common result
3146/// metadata and exposed by [`FinalCoreResult::SubscriptionsListen`].
3147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3148#[serde(deny_unknown_fields)]
3149pub struct FinalSubscriptionsListenResult {}
3150
3151/// Empty final complete-result payload used by acknowledgement methods.
3152#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3153#[serde(deny_unknown_fields)]
3154pub struct FinalEmptyResult {}
3155
3156/// Empty exact-legacy result payload used by acknowledgement methods.
3157#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3158#[serde(deny_unknown_fields)]
3159pub struct LegacyEmptyResult {}
3160
3161/// Legacy core requests remain exact uses of the existing message structs.
3162/// They are never reinterpreted as final requests.
3163#[derive(Debug, Clone)]
3164pub enum LegacyCoreRequest {
3165    /// `initialize` is unique to the legacy initialize-handshake era.
3166    Initialize(InitializeParams),
3167    /// `completion/complete`.
3168    Completion(LegacyCompletionParams),
3169    /// `sampling/createMessage`.
3170    SamplingCreateMessage(CreateMessageParams),
3171    /// `tools/list`.
3172    ToolsList(ListToolsParams),
3173    /// `tools/call`.
3174    ToolsCall(CallToolParams),
3175    /// `resources/list`.
3176    ResourcesList(ListResourcesParams),
3177    /// `resources/templates/list`.
3178    ResourceTemplatesList(ListResourceTemplatesParams),
3179    /// `resources/read`.
3180    ResourcesRead(ReadResourceParams),
3181    /// `resources/subscribe`.
3182    ResourcesSubscribe(SubscribeResourceParams),
3183    /// `resources/unsubscribe`.
3184    ResourcesUnsubscribe(UnsubscribeResourceParams),
3185    /// `prompts/list`.
3186    PromptsList(ListPromptsParams),
3187    /// `prompts/get`.
3188    PromptsGet(GetPromptParams),
3189    /// `logging/setLevel`.
3190    SetLogLevel(SetLogLevelParams),
3191    /// `ping`, which has no legacy parameter object.
3192    Ping,
3193}
3194
3195/// Final core requests use final metadata and common vocabulary throughout.
3196#[derive(Debug, Clone)]
3197pub enum FinalCoreRequest {
3198    /// `server/discover`.
3199    Discover(FinalEmptyParams),
3200    /// `completion/complete`.
3201    Completion(FinalCompletionParams),
3202    /// `tools/list`.
3203    ToolsList(FinalListParams),
3204    /// `tools/call`.
3205    ToolsCall(FinalCallToolParams),
3206    /// `resources/list`.
3207    ResourcesList(FinalListParams),
3208    /// `resources/templates/list`.
3209    ResourceTemplatesList(FinalListParams),
3210    /// `resources/read`.
3211    ResourcesRead(FinalReadResourceParams),
3212    /// `prompts/list`.
3213    PromptsList(FinalListParams),
3214    /// `prompts/get`.
3215    PromptsGet(FinalGetPromptParams),
3216    /// `subscriptions/listen`.
3217    SubscriptionsListen(FinalSubscriptionsListenParams),
3218}
3219
3220/// Public, era-aware dispatch for the currently supported core request set.
3221#[derive(Debug, Clone)]
3222pub enum CoreRequest {
3223    /// Exact MCP 2024-11-05 request vocabulary.
3224    Legacy(LegacyCoreRequest),
3225    /// Final MCP 2026-07-28 request vocabulary.
3226    Final(FinalCoreRequest),
3227}
3228
3229/// Exact legacy response payloads, intentionally disjoint from final results.
3230#[derive(Debug, Clone)]
3231pub enum LegacyCoreResult {
3232    /// `initialize`.
3233    Initialize(InitializeResult),
3234    /// `completion/complete`.
3235    Completion(LegacyCompletionResult),
3236    /// `sampling/createMessage`.
3237    SamplingCreateMessage(CreateMessageResult),
3238    /// `tools/list`.
3239    ToolsList(ListToolsResult),
3240    /// `tools/call`.
3241    ToolsCall(CallToolResult),
3242    /// `resources/list`.
3243    ResourcesList(ListResourcesResult),
3244    /// `resources/templates/list`.
3245    ResourceTemplatesList(ListResourceTemplatesResult),
3246    /// `resources/read`.
3247    ResourcesRead(ReadResourceResult),
3248    /// `resources/subscribe` acknowledgement.
3249    ResourcesSubscribe(LegacyEmptyResult),
3250    /// `resources/unsubscribe` acknowledgement.
3251    ResourcesUnsubscribe(LegacyEmptyResult),
3252    /// `prompts/list`.
3253    PromptsList(ListPromptsResult),
3254    /// `prompts/get`.
3255    PromptsGet(GetPromptResult),
3256    /// `logging/setLevel` acknowledgement.
3257    SetLogLevel(LegacyEmptyResult),
3258    /// `ping` acknowledgement.
3259    Ping(LegacyEmptyResult),
3260}
3261
3262/// Final result dispatch for the currently supported core methods.
3263///
3264/// Every complete branch carries the bounded final complete-result algebra.
3265/// `tools/call`, `resources/read`, and `prompts/get` additionally carry the
3266/// final MRTR `input_required` branch. An absent `resultType` is accepted
3267/// only by the separately selected legacy result decoder.
3268#[derive(Debug, Clone)]
3269pub enum FinalCoreResult {
3270    /// `server/discover`.
3271    Discover(crate::server_discovery::ServerDiscoverResult),
3272    /// `completion/complete`.
3273    Completion {
3274        result: CompleteResult<FinalCompletionResult>,
3275        diagnostic: Option<ResultPeerDiagnostic>,
3276    },
3277    /// `tools/list`.
3278    ToolsList {
3279        result: CompleteResult<FinalListToolsResult>,
3280        diagnostic: Option<ResultPeerDiagnostic>,
3281    },
3282    /// `tools/call`.
3283    ToolsCall {
3284        result: CompleteResult<FinalCallToolResult>,
3285        diagnostic: Option<ResultPeerDiagnostic>,
3286    },
3287    /// A Tasks-backed final `tools/call` result.
3288    #[cfg(feature = "tasks")]
3289    ToolsCallTask {
3290        result: crate::tasks_extension::CreateTaskResult,
3291    },
3292    /// `tools/call` requires client input before a retry.
3293    ToolsCallInputRequired {
3294        result: InputRequiredResult,
3295        diagnostic: Option<ResultPeerDiagnostic>,
3296    },
3297    /// `resources/list`.
3298    ResourcesList {
3299        result: CompleteResult<FinalListResourcesResult>,
3300        diagnostic: Option<ResultPeerDiagnostic>,
3301    },
3302    /// `resources/templates/list`.
3303    ResourceTemplatesList {
3304        result: CompleteResult<FinalListResourceTemplatesResult>,
3305        diagnostic: Option<ResultPeerDiagnostic>,
3306    },
3307    /// `resources/read`.
3308    ResourcesRead {
3309        result: CompleteResult<FinalReadResourceResult>,
3310        diagnostic: Option<ResultPeerDiagnostic>,
3311    },
3312    /// `resources/read` requires client input before a retry.
3313    ResourcesReadInputRequired {
3314        result: InputRequiredResult,
3315        diagnostic: Option<ResultPeerDiagnostic>,
3316    },
3317    /// `prompts/list`.
3318    PromptsList {
3319        result: CompleteResult<FinalListPromptsResult>,
3320        diagnostic: Option<ResultPeerDiagnostic>,
3321    },
3322    /// `prompts/get`.
3323    PromptsGet {
3324        result: CompleteResult<FinalGetPromptResult>,
3325        diagnostic: Option<ResultPeerDiagnostic>,
3326    },
3327    /// `prompts/get` requires client input before a retry.
3328    PromptsGetInputRequired {
3329        result: InputRequiredResult,
3330        diagnostic: Option<ResultPeerDiagnostic>,
3331    },
3332    /// `subscriptions/listen` graceful termination.
3333    SubscriptionsListen {
3334        result: CompleteResult<FinalSubscriptionsListenResult>,
3335        /// The required subscription ID extracted from the result metadata.
3336        subscription_id: RequestId,
3337        diagnostic: Option<ResultPeerDiagnostic>,
3338    },
3339}
3340
3341/// Server-owned final metadata retained across response middleware.
3342///
3343/// This is intentionally opaque outside the protocol crate: callers preserve
3344/// and compare the typed seal, rather than interpreting or reconstructing its
3345/// metadata through a raw compatibility path.
3346#[derive(Debug, Clone, PartialEq, Eq)]
3347pub struct FinalResultMetadataSeal {
3348    family: FinalResultMetadataFamily,
3349    server_info: FinalResultServerInfo,
3350    subscription_id: Option<RequestId>,
3351}
3352
3353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3354enum FinalResultMetadataFamily {
3355    Discover,
3356    Completion,
3357    ToolsList,
3358    ToolsCall,
3359    #[cfg(feature = "tasks")]
3360    ToolsCallTask,
3361    ToolsCallInputRequired,
3362    ResourcesList,
3363    ResourceTemplatesList,
3364    ResourcesRead,
3365    ResourcesReadInputRequired,
3366    PromptsList,
3367    PromptsGet,
3368    PromptsGetInputRequired,
3369    SubscriptionsListen,
3370}
3371
3372#[derive(Debug, Clone, PartialEq, Eq)]
3373enum FinalResultServerInfo {
3374    Discovery(Option<FinalDiscoveryServerInfo>),
3375    Common(Option<Implementation>),
3376}
3377
3378#[derive(Debug, Clone, PartialEq, Eq)]
3379struct FinalDiscoveryServerInfo {
3380    name: String,
3381    version: String,
3382}
3383
3384impl From<&ServerInfo> for FinalDiscoveryServerInfo {
3385    fn from(server_info: &ServerInfo) -> Self {
3386        Self {
3387            name: server_info.name.clone(),
3388            version: server_info.version.clone(),
3389        }
3390    }
3391}
3392
3393/// Public, era-aware dispatch for core results.
3394#[derive(Debug, Clone)]
3395#[allow(
3396    clippy::large_enum_variant,
3397    reason = "the public dual-era dispatch intentionally keeps each exhaustive typed result algebra inline; boxing one negotiated era would distort its direct pattern-matching API solely to reduce enum size"
3398)]
3399pub enum CoreResult {
3400    /// Exact MCP 2024-11-05 result vocabulary.
3401    Legacy(LegacyCoreResult),
3402    /// Final MCP 2026-07-28 complete-result vocabulary.
3403    Final(FinalCoreResult),
3404}
3405
3406/// Stable errors raised while selecting a typed core request or result.
3407#[derive(Debug, Clone, PartialEq, Eq)]
3408pub enum CoreDispatchError {
3409    /// A selected wire capability was compiled out of this crate build.
3410    FeatureUnavailable {
3411        /// The exact Cargo feature required for this wire capability.
3412        feature: &'static str,
3413    },
3414    /// The selected era does not support this method.
3415    UnsupportedMethod { era: ProtocolEra, method: String },
3416    /// A request's method-specific parameters could not be decoded.
3417    InvalidParams {
3418        era: ProtocolEra,
3419        method: &'static str,
3420    },
3421    /// Final request metadata was absent, malformed, or for another era.
3422    InvalidFinalMetadata { method: &'static str },
3423    /// A legacy request attempted to carry final per-request metadata.
3424    CrossEraRequestMetadata { method: &'static str },
3425    /// A legacy result attempted to carry a final `resultType` discriminator.
3426    CrossEraResultType { method: &'static str },
3427    /// A legacy result attempted to carry final result metadata.
3428    CrossEraResultMetadata { method: &'static str },
3429    /// A result did not match the selected method-specific payload.
3430    InvalidResult {
3431        era: ProtocolEra,
3432        method: &'static str,
3433    },
3434    /// A final result used another core discriminator.
3435    UnexpectedFinalResultType { method: &'static str },
3436    /// A final subscriptions/listen result did not correlate to its JSON-RPC response ID.
3437    SubscriptionIdMismatch,
3438    /// The bounded final result codec rejected the wire value.
3439    ResultCodec(ResultDecodeError),
3440}
3441
3442impl std::fmt::Display for CoreDispatchError {
3443    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3444        match self {
3445            Self::FeatureUnavailable { feature } => {
3446                write!(formatter, "the {feature:?} protocol feature is unavailable")
3447            }
3448            Self::UnsupportedMethod { era, method } => {
3449                write!(formatter, "{method} is not supported in {era:?}")
3450            }
3451            Self::InvalidParams { era, method } => {
3452                write!(formatter, "invalid {method} parameters for {era:?}")
3453            }
3454            Self::InvalidFinalMetadata { method } => {
3455                write!(formatter, "invalid final metadata for {method}")
3456            }
3457            Self::CrossEraRequestMetadata { method } => {
3458                write!(formatter, "legacy {method} cannot carry final metadata")
3459            }
3460            Self::CrossEraResultType { method } => {
3461                write!(formatter, "legacy {method} cannot carry final resultType")
3462            }
3463            Self::CrossEraResultMetadata { method } => {
3464                write!(
3465                    formatter,
3466                    "legacy {method} cannot carry final result metadata"
3467                )
3468            }
3469            Self::InvalidResult { era, method } => {
3470                write!(formatter, "invalid {method} result for {era:?}")
3471            }
3472            Self::UnexpectedFinalResultType { method } => {
3473                write!(formatter, "final {method} requires a complete result")
3474            }
3475            Self::SubscriptionIdMismatch => {
3476                formatter.write_str("subscription result metadata does not match response id")
3477            }
3478            Self::ResultCodec(error) => error.fmt(formatter),
3479        }
3480    }
3481}
3482
3483impl std::error::Error for CoreDispatchError {}
3484
3485impl From<ResultDecodeError> for CoreDispatchError {
3486    fn from(error: ResultDecodeError) -> Self {
3487        Self::ResultCodec(error)
3488    }
3489}
3490
3491impl CoreRequest {
3492    /// Decodes one core request only through the exact protocol era selected
3493    /// by the connection. The two request vocabularies are deliberately
3494    /// disjoint even where their method literals are shared.
3495    pub fn decode(
3496        era: ProtocolEra,
3497        method: &str,
3498        params: Option<&Value>,
3499    ) -> Result<Self, CoreDispatchError> {
3500        match era {
3501            ProtocolEra::Legacy2024 => {
3502                #[cfg(feature = "legacy-2024-11-05")]
3503                {
3504                    Self::decode_legacy(method, params)
3505                }
3506                #[cfg(not(feature = "legacy-2024-11-05"))]
3507                {
3508                    let _ = (method, params);
3509                    Err(CoreDispatchError::FeatureUnavailable {
3510                        feature: "legacy-2024-11-05",
3511                    })
3512                }
3513            }
3514            ProtocolEra::Modern2026 => Self::decode_final(method, params),
3515        }
3516    }
3517
3518    /// Decodes one core request while retaining the admitted raw parameter
3519    /// source for final MRTR retry maps.
3520    ///
3521    /// The ordinary [`Self::decode`] path accepts a materialized
3522    /// [`Value`], whose object representation cannot retain member order.
3523    /// Callers that admitted the original parameter source can use this form
3524    /// for the three final methods whose `inputResponses` maps have typed,
3525    /// ordered response entries. The supplied source must describe exactly the
3526    /// same parameter value as `params`; it cannot be attached to another
3527    /// admitted frame.
3528    pub fn decode_with_raw_params(
3529        era: ProtocolEra,
3530        method: &str,
3531        params: Option<&Value>,
3532        raw_params: Option<&str>,
3533    ) -> Result<Self, CoreDispatchError> {
3534        let Some(raw_params) = raw_params else {
3535            return Self::decode(era, method, params);
3536        };
3537        if era != ProtocolEra::Modern2026
3538            || !matches!(method, TOOLS_CALL | RESOURCES_READ | PROMPTS_GET)
3539        {
3540            return Self::decode(era, method, params);
3541        }
3542        let method_literal = match method {
3543            TOOLS_CALL => TOOLS_CALL,
3544            RESOURCES_READ => RESOURCES_READ,
3545            PROMPTS_GET => PROMPTS_GET,
3546            _ => unreachable!("the final MRTR raw-params guard selected a known method"),
3547        };
3548        crate::result::parse_exact_json(raw_params).map_err(|_| {
3549            CoreDispatchError::InvalidParams {
3550                era,
3551                method: method_literal,
3552            }
3553        })?;
3554        let raw_value: Value =
3555            serde_json::from_str(raw_params).map_err(|_| CoreDispatchError::InvalidParams {
3556                era,
3557                method: method_literal,
3558            })?;
3559        if params != Some(&raw_value) {
3560            return Err(CoreDispatchError::InvalidParams {
3561                era,
3562                method: method_literal,
3563            });
3564        }
3565
3566        let request = match method {
3567            TOOLS_CALL => {
3568                FinalCoreRequest::ToolsCall(serde_json::from_str(raw_params).map_err(|_| {
3569                    CoreDispatchError::InvalidParams {
3570                        era,
3571                        method: TOOLS_CALL,
3572                    }
3573                })?)
3574            }
3575            RESOURCES_READ => {
3576                FinalCoreRequest::ResourcesRead(serde_json::from_str(raw_params).map_err(|_| {
3577                    CoreDispatchError::InvalidParams {
3578                        era,
3579                        method: RESOURCES_READ,
3580                    }
3581                })?)
3582            }
3583            PROMPTS_GET => {
3584                FinalCoreRequest::PromptsGet(serde_json::from_str(raw_params).map_err(|_| {
3585                    CoreDispatchError::InvalidParams {
3586                        era,
3587                        method: PROMPTS_GET,
3588                    }
3589                })?)
3590            }
3591            _ => unreachable!("the final MRTR raw-params guard selected a known method"),
3592        };
3593        request.validate_metadata()?;
3594        Ok(Self::Final(request))
3595    }
3596
3597    /// Returns the era selected by this request.
3598    #[must_use]
3599    pub const fn era(&self) -> ProtocolEra {
3600        match self {
3601            Self::Legacy(_) => ProtocolEra::Legacy2024,
3602            Self::Final(_) => ProtocolEra::Modern2026,
3603        }
3604    }
3605
3606    /// Returns the exact core method literal selected by this request.
3607    #[must_use]
3608    pub const fn method(&self) -> &'static str {
3609        match self {
3610            Self::Legacy(request) => request.method(),
3611            Self::Final(request) => request.method(),
3612        }
3613    }
3614
3615    /// Encodes the method-owned parameter object without adding a JSON-RPC
3616    /// envelope. Final requests revalidate their common metadata before
3617    /// serialization so local callers cannot emit a cross-era request.
3618    pub fn encode_params(&self) -> Result<Option<Value>, CoreDispatchError> {
3619        match self {
3620            Self::Legacy(request) => request.encode_params(),
3621            Self::Final(request) => request.encode_params(),
3622        }
3623    }
3624
3625    /// Decodes the JSON-RPC result payload selected by this request.
3626    pub fn decode_result(&self, input: &str) -> Result<CoreResult, CoreDispatchError> {
3627        match self {
3628            Self::Legacy(request) => request.decode_result(input).map(CoreResult::Legacy),
3629            Self::Final(request) => request.decode_result(input, None).map(CoreResult::Final),
3630        }
3631    }
3632
3633    /// Decodes a successful JSON-RPC response selected by this request.
3634    ///
3635    /// This form preserves the response correlation context required by final
3636    /// `subscriptions/listen`: its result metadata subscription ID must equal
3637    /// the enclosing JSON-RPC response ID. This `Value`-only API is lossy for
3638    /// received member order and noncanonical numeric lexemes; callers with
3639    /// admitted source must use [`Self::decode_response_result`].
3640    pub fn decode_response(
3641        &self,
3642        response: &JsonRpcResponse,
3643    ) -> Result<CoreResult, CoreDispatchError> {
3644        let Some(response_id) = response.id.as_ref() else {
3645            return Err(CoreDispatchError::InvalidResult {
3646                era: self.era(),
3647                method: self.method(),
3648            });
3649        };
3650        let Some(result) = response.result.as_ref() else {
3651            return Err(CoreDispatchError::InvalidResult {
3652                era: self.era(),
3653                method: self.method(),
3654            });
3655        };
3656        let input =
3657            serde_json::to_string(result).map_err(|_| CoreDispatchError::InvalidResult {
3658                era: self.era(),
3659                method: self.method(),
3660            })?;
3661        match self {
3662            Self::Legacy(request) => {
3663                #[cfg(feature = "legacy-2024-11-05")]
3664                {
3665                    request.decode_result(&input).map(CoreResult::Legacy)
3666                }
3667                #[cfg(not(feature = "legacy-2024-11-05"))]
3668                {
3669                    let _ = request;
3670                    Err(CoreDispatchError::FeatureUnavailable {
3671                        feature: "legacy-2024-11-05",
3672                    })
3673                }
3674            }
3675            Self::Final(request) => request
3676                .decode_result(&input, Some(response_id))
3677                .map(CoreResult::Final),
3678        }
3679    }
3680
3681    /// Decodes a successful JSON-RPC response from its admitted result source.
3682    ///
3683    /// Unlike [`Self::decode_response`], this path does not serialize the
3684    /// response's [`Value`] again. The caller supplies the exact source JSON
3685    /// retained while the response frame was admitted, preserving object
3686    /// member order and number lexemes for the lossless result algebra. The
3687    /// parsed value must still equal the response's typed result so source from
3688    /// a different response cannot be attached accidentally.
3689    pub fn decode_response_result(
3690        &self,
3691        response: &JsonRpcResponse,
3692        result_source: &str,
3693    ) -> Result<CoreResult, CoreDispatchError> {
3694        let Some(response_id) = response.id.as_ref() else {
3695            return Err(CoreDispatchError::InvalidResult {
3696                era: self.era(),
3697                method: self.method(),
3698            });
3699        };
3700        let Some(result) = response.result.as_ref() else {
3701            return Err(CoreDispatchError::InvalidResult {
3702                era: self.era(),
3703                method: self.method(),
3704            });
3705        };
3706        let admitted_value: Value =
3707            serde_json::from_str(result_source).map_err(|_| CoreDispatchError::InvalidResult {
3708                era: self.era(),
3709                method: self.method(),
3710            })?;
3711        if &admitted_value != result {
3712            return Err(CoreDispatchError::InvalidResult {
3713                era: self.era(),
3714                method: self.method(),
3715            });
3716        }
3717        match self {
3718            Self::Legacy(request) => {
3719                #[cfg(feature = "legacy-2024-11-05")]
3720                {
3721                    request.decode_result(result_source).map(CoreResult::Legacy)
3722                }
3723                #[cfg(not(feature = "legacy-2024-11-05"))]
3724                {
3725                    let _ = request;
3726                    Err(CoreDispatchError::FeatureUnavailable {
3727                        feature: "legacy-2024-11-05",
3728                    })
3729                }
3730            }
3731            Self::Final(request) => request
3732                .decode_result(result_source, Some(response_id))
3733                .map(CoreResult::Final),
3734        }
3735    }
3736
3737    fn decode_legacy(method: &str, params: Option<&Value>) -> Result<Self, CoreDispatchError> {
3738        let request =
3739            match method {
3740                INITIALIZE => LegacyCoreRequest::Initialize(decode_params(
3741                    ProtocolEra::Legacy2024,
3742                    INITIALIZE,
3743                    params,
3744                )?),
3745                COMPLETION_COMPLETE => LegacyCoreRequest::Completion(decode_params(
3746                    ProtocolEra::Legacy2024,
3747                    COMPLETION_COMPLETE,
3748                    params,
3749                )?),
3750                SAMPLING_CREATE_MESSAGE => LegacyCoreRequest::SamplingCreateMessage(decode_params(
3751                    ProtocolEra::Legacy2024,
3752                    SAMPLING_CREATE_MESSAGE,
3753                    params,
3754                )?),
3755                TOOLS_LIST => LegacyCoreRequest::ToolsList(decode_params(
3756                    ProtocolEra::Legacy2024,
3757                    TOOLS_LIST,
3758                    params,
3759                )?),
3760                TOOLS_CALL => LegacyCoreRequest::ToolsCall(decode_params(
3761                    ProtocolEra::Legacy2024,
3762                    TOOLS_CALL,
3763                    params,
3764                )?),
3765                RESOURCES_LIST => LegacyCoreRequest::ResourcesList(decode_params(
3766                    ProtocolEra::Legacy2024,
3767                    RESOURCES_LIST,
3768                    params,
3769                )?),
3770                RESOURCES_TEMPLATES_LIST => LegacyCoreRequest::ResourceTemplatesList(
3771                    decode_params(ProtocolEra::Legacy2024, RESOURCES_TEMPLATES_LIST, params)?,
3772                ),
3773                RESOURCES_READ => LegacyCoreRequest::ResourcesRead(decode_params(
3774                    ProtocolEra::Legacy2024,
3775                    RESOURCES_READ,
3776                    params,
3777                )?),
3778                RESOURCES_SUBSCRIBE => LegacyCoreRequest::ResourcesSubscribe(decode_params(
3779                    ProtocolEra::Legacy2024,
3780                    RESOURCES_SUBSCRIBE,
3781                    params,
3782                )?),
3783                RESOURCES_UNSUBSCRIBE => LegacyCoreRequest::ResourcesUnsubscribe(decode_params(
3784                    ProtocolEra::Legacy2024,
3785                    RESOURCES_UNSUBSCRIBE,
3786                    params,
3787                )?),
3788                PROMPTS_LIST => LegacyCoreRequest::PromptsList(decode_params(
3789                    ProtocolEra::Legacy2024,
3790                    PROMPTS_LIST,
3791                    params,
3792                )?),
3793                PROMPTS_GET => LegacyCoreRequest::PromptsGet(decode_params(
3794                    ProtocolEra::Legacy2024,
3795                    PROMPTS_GET,
3796                    params,
3797                )?),
3798                LOGGING_SET_LEVEL => LegacyCoreRequest::SetLogLevel(decode_params(
3799                    ProtocolEra::Legacy2024,
3800                    LOGGING_SET_LEVEL,
3801                    params,
3802                )?),
3803                PING => {
3804                    require_absent_or_empty_params(ProtocolEra::Legacy2024, PING, params)?;
3805                    LegacyCoreRequest::Ping
3806                }
3807                _ => {
3808                    return Err(CoreDispatchError::UnsupportedMethod {
3809                        era: ProtocolEra::Legacy2024,
3810                        method: method.to_owned(),
3811                    });
3812                }
3813            };
3814        if legacy_params_carry_final_metadata(params) {
3815            return Err(CoreDispatchError::CrossEraRequestMetadata {
3816                method: request.method(),
3817            });
3818        }
3819        if let LegacyCoreRequest::Initialize(params) = &request
3820            && params.protocol_version != ProtocolEra::Legacy2024.version().as_str()
3821        {
3822            return Err(CoreDispatchError::InvalidParams {
3823                era: ProtocolEra::Legacy2024,
3824                method: INITIALIZE,
3825            });
3826        }
3827        Ok(Self::Legacy(request))
3828    }
3829
3830    fn decode_final(method: &str, params: Option<&Value>) -> Result<Self, CoreDispatchError> {
3831        let request = match method {
3832            SERVER_DISCOVER => {
3833                FinalCoreRequest::Discover(decode_final_params(SERVER_DISCOVER, params)?)
3834            }
3835            COMPLETION_COMPLETE => {
3836                FinalCoreRequest::Completion(decode_final_params(COMPLETION_COMPLETE, params)?)
3837            }
3838            TOOLS_LIST => FinalCoreRequest::ToolsList(decode_final_params(TOOLS_LIST, params)?),
3839            TOOLS_CALL => FinalCoreRequest::ToolsCall(decode_final_params(TOOLS_CALL, params)?),
3840            RESOURCES_LIST => {
3841                FinalCoreRequest::ResourcesList(decode_final_params(RESOURCES_LIST, params)?)
3842            }
3843            RESOURCES_TEMPLATES_LIST => FinalCoreRequest::ResourceTemplatesList(
3844                decode_final_params(RESOURCES_TEMPLATES_LIST, params)?,
3845            ),
3846            RESOURCES_READ => {
3847                FinalCoreRequest::ResourcesRead(decode_final_params(RESOURCES_READ, params)?)
3848            }
3849            PROMPTS_LIST => {
3850                FinalCoreRequest::PromptsList(decode_final_params(PROMPTS_LIST, params)?)
3851            }
3852            PROMPTS_GET => FinalCoreRequest::PromptsGet(decode_final_params(PROMPTS_GET, params)?),
3853            SUBSCRIPTIONS_LISTEN => FinalCoreRequest::SubscriptionsListen(decode_final_params(
3854                SUBSCRIPTIONS_LISTEN,
3855                params,
3856            )?),
3857            _ => {
3858                return Err(CoreDispatchError::UnsupportedMethod {
3859                    era: ProtocolEra::Modern2026,
3860                    method: method.to_owned(),
3861                });
3862            }
3863        };
3864        request.validate_metadata()?;
3865        Ok(Self::Final(request))
3866    }
3867}
3868
3869impl LegacyCoreRequest {
3870    /// Returns this request's exact method literal.
3871    #[must_use]
3872    pub const fn method(&self) -> &'static str {
3873        match self {
3874            Self::Initialize(_) => INITIALIZE,
3875            Self::Completion(_) => COMPLETION_COMPLETE,
3876            Self::SamplingCreateMessage(_) => SAMPLING_CREATE_MESSAGE,
3877            Self::ToolsList(_) => TOOLS_LIST,
3878            Self::ToolsCall(_) => TOOLS_CALL,
3879            Self::ResourcesList(_) => RESOURCES_LIST,
3880            Self::ResourceTemplatesList(_) => RESOURCES_TEMPLATES_LIST,
3881            Self::ResourcesRead(_) => RESOURCES_READ,
3882            Self::ResourcesSubscribe(_) => RESOURCES_SUBSCRIBE,
3883            Self::ResourcesUnsubscribe(_) => RESOURCES_UNSUBSCRIBE,
3884            Self::PromptsList(_) => PROMPTS_LIST,
3885            Self::PromptsGet(_) => PROMPTS_GET,
3886            Self::SetLogLevel(_) => LOGGING_SET_LEVEL,
3887            Self::Ping => PING,
3888        }
3889    }
3890
3891    fn encode_params(&self) -> Result<Option<Value>, CoreDispatchError> {
3892        match self {
3893            Self::Initialize(params) => encode_params(ProtocolEra::Legacy2024, INITIALIZE, params),
3894            Self::Completion(params) => {
3895                encode_params(ProtocolEra::Legacy2024, COMPLETION_COMPLETE, params)
3896            }
3897            Self::SamplingCreateMessage(params) => {
3898                encode_params(ProtocolEra::Legacy2024, SAMPLING_CREATE_MESSAGE, params)
3899            }
3900            Self::ToolsList(params) => encode_params(ProtocolEra::Legacy2024, TOOLS_LIST, params),
3901            Self::ToolsCall(params) => encode_params(ProtocolEra::Legacy2024, TOOLS_CALL, params),
3902            Self::ResourcesList(params) => {
3903                encode_params(ProtocolEra::Legacy2024, RESOURCES_LIST, params)
3904            }
3905            Self::ResourceTemplatesList(params) => {
3906                encode_params(ProtocolEra::Legacy2024, RESOURCES_TEMPLATES_LIST, params)
3907            }
3908            Self::ResourcesRead(params) => {
3909                encode_params(ProtocolEra::Legacy2024, RESOURCES_READ, params)
3910            }
3911            Self::ResourcesSubscribe(params) => {
3912                encode_params(ProtocolEra::Legacy2024, RESOURCES_SUBSCRIBE, params)
3913            }
3914            Self::ResourcesUnsubscribe(params) => {
3915                encode_params(ProtocolEra::Legacy2024, RESOURCES_UNSUBSCRIBE, params)
3916            }
3917            Self::PromptsList(params) => {
3918                encode_params(ProtocolEra::Legacy2024, PROMPTS_LIST, params)
3919            }
3920            Self::PromptsGet(params) => encode_params(ProtocolEra::Legacy2024, PROMPTS_GET, params),
3921            Self::SetLogLevel(params) => {
3922                encode_params(ProtocolEra::Legacy2024, LOGGING_SET_LEVEL, params)
3923            }
3924            Self::Ping => Ok(None),
3925        }
3926    }
3927
3928    fn decode_result(&self, input: &str) -> Result<LegacyCoreResult, CoreDispatchError> {
3929        match self {
3930            Self::Initialize(_) => {
3931                decode_legacy_result(INITIALIZE, input).map(LegacyCoreResult::Initialize)
3932            }
3933            Self::Completion(_) => {
3934                decode_legacy_result(COMPLETION_COMPLETE, input).map(LegacyCoreResult::Completion)
3935            }
3936            Self::SamplingCreateMessage(_) => decode_legacy_result(SAMPLING_CREATE_MESSAGE, input)
3937                .map(LegacyCoreResult::SamplingCreateMessage),
3938            Self::ToolsList(_) => {
3939                decode_legacy_result(TOOLS_LIST, input).map(LegacyCoreResult::ToolsList)
3940            }
3941            Self::ToolsCall(_) => {
3942                decode_legacy_result(TOOLS_CALL, input).map(LegacyCoreResult::ToolsCall)
3943            }
3944            Self::ResourcesList(_) => {
3945                decode_legacy_result(RESOURCES_LIST, input).map(LegacyCoreResult::ResourcesList)
3946            }
3947            Self::ResourceTemplatesList(_) => decode_legacy_result(RESOURCES_TEMPLATES_LIST, input)
3948                .map(LegacyCoreResult::ResourceTemplatesList),
3949            Self::ResourcesRead(_) => {
3950                decode_legacy_result(RESOURCES_READ, input).map(LegacyCoreResult::ResourcesRead)
3951            }
3952            Self::ResourcesSubscribe(_) => decode_legacy_result(RESOURCES_SUBSCRIBE, input)
3953                .map(LegacyCoreResult::ResourcesSubscribe),
3954            Self::ResourcesUnsubscribe(_) => decode_legacy_result(RESOURCES_UNSUBSCRIBE, input)
3955                .map(LegacyCoreResult::ResourcesUnsubscribe),
3956            Self::PromptsList(_) => {
3957                decode_legacy_result(PROMPTS_LIST, input).map(LegacyCoreResult::PromptsList)
3958            }
3959            Self::PromptsGet(_) => {
3960                decode_legacy_result(PROMPTS_GET, input).map(LegacyCoreResult::PromptsGet)
3961            }
3962            Self::SetLogLevel(_) => {
3963                decode_legacy_result(LOGGING_SET_LEVEL, input).map(LegacyCoreResult::SetLogLevel)
3964            }
3965            Self::Ping => decode_legacy_result(PING, input).map(LegacyCoreResult::Ping),
3966        }
3967    }
3968}
3969
3970impl FinalCoreRequest {
3971    /// Returns this request's exact method literal.
3972    #[must_use]
3973    pub const fn method(&self) -> &'static str {
3974        match self {
3975            Self::Discover(_) => SERVER_DISCOVER,
3976            Self::Completion(_) => COMPLETION_COMPLETE,
3977            Self::ToolsList(_) => TOOLS_LIST,
3978            Self::ToolsCall(_) => TOOLS_CALL,
3979            Self::ResourcesList(_) => RESOURCES_LIST,
3980            Self::ResourceTemplatesList(_) => RESOURCES_TEMPLATES_LIST,
3981            Self::ResourcesRead(_) => RESOURCES_READ,
3982            Self::PromptsList(_) => PROMPTS_LIST,
3983            Self::PromptsGet(_) => PROMPTS_GET,
3984            Self::SubscriptionsListen(_) => SUBSCRIPTIONS_LISTEN,
3985        }
3986    }
3987
3988    fn validate_metadata(&self) -> Result<(), CoreDispatchError> {
3989        let metadata = match self {
3990            Self::Discover(params) => &params.meta,
3991            Self::Completion(params) => &params.meta,
3992            Self::ToolsList(params)
3993            | Self::ResourcesList(params)
3994            | Self::ResourceTemplatesList(params)
3995            | Self::PromptsList(params) => &params.meta,
3996            Self::ToolsCall(params) => &params.meta,
3997            Self::ResourcesRead(params) => &params.meta,
3998            Self::PromptsGet(params) => &params.meta,
3999            Self::SubscriptionsListen(params) => &params.meta,
4000        };
4001        let valid_version = metadata.protocol_version().ok().flatten()
4002            == Some(ProtocolEra::Modern2026.version().as_str());
4003        let has_capabilities = metadata.client_capabilities().ok().flatten().is_some();
4004        if valid_version && has_capabilities {
4005            Ok(())
4006        } else {
4007            Err(CoreDispatchError::InvalidFinalMetadata {
4008                method: self.method(),
4009            })
4010        }
4011    }
4012
4013    fn encode_params(&self) -> Result<Option<Value>, CoreDispatchError> {
4014        self.validate_metadata()?;
4015        match self {
4016            Self::Discover(params) => {
4017                encode_params(ProtocolEra::Modern2026, SERVER_DISCOVER, params)
4018            }
4019            Self::Completion(params) => {
4020                encode_params(ProtocolEra::Modern2026, COMPLETION_COMPLETE, params)
4021            }
4022            Self::ToolsList(params) => encode_params(ProtocolEra::Modern2026, TOOLS_LIST, params),
4023            Self::ToolsCall(params) => encode_params(ProtocolEra::Modern2026, TOOLS_CALL, params),
4024            Self::ResourcesList(params) => {
4025                encode_params(ProtocolEra::Modern2026, RESOURCES_LIST, params)
4026            }
4027            Self::ResourceTemplatesList(params) => {
4028                encode_params(ProtocolEra::Modern2026, RESOURCES_TEMPLATES_LIST, params)
4029            }
4030            Self::ResourcesRead(params) => {
4031                encode_params(ProtocolEra::Modern2026, RESOURCES_READ, params)
4032            }
4033            Self::PromptsList(params) => {
4034                encode_params(ProtocolEra::Modern2026, PROMPTS_LIST, params)
4035            }
4036            Self::PromptsGet(params) => encode_params(ProtocolEra::Modern2026, PROMPTS_GET, params),
4037            Self::SubscriptionsListen(params) => {
4038                encode_params(ProtocolEra::Modern2026, SUBSCRIPTIONS_LISTEN, params)
4039            }
4040        }
4041    }
4042
4043    fn decode_result(
4044        &self,
4045        input: &str,
4046        response_id: Option<&RequestId>,
4047    ) -> Result<FinalCoreResult, CoreDispatchError> {
4048        match self {
4049            Self::Discover(_) => serde_json::from_str(input)
4050                .map(FinalCoreResult::Discover)
4051                .map_err(|_| CoreDispatchError::InvalidResult {
4052                    era: ProtocolEra::Modern2026,
4053                    method: SERVER_DISCOVER,
4054                }),
4055            Self::Completion(_) => {
4056                decode_final_complete(COMPLETION_COMPLETE, input, &["completion"])
4057                    .map(|(result, diagnostic)| FinalCoreResult::Completion { result, diagnostic })
4058            }
4059            Self::ToolsList(_) => decode_final_complete(
4060                TOOLS_LIST,
4061                input,
4062                &["tools", "nextCursor", "ttlMs", "cacheScope"],
4063            )
4064            .map(|(result, diagnostic)| FinalCoreResult::ToolsList { result, diagnostic }),
4065            Self::ToolsCall(_) => decode_final_tools_call(input),
4066            Self::ResourcesList(_) => decode_final_complete(
4067                RESOURCES_LIST,
4068                input,
4069                &["resources", "nextCursor", "ttlMs", "cacheScope"],
4070            )
4071            .map(|(result, diagnostic)| FinalCoreResult::ResourcesList { result, diagnostic }),
4072            Self::ResourceTemplatesList(_) => {
4073                decode_final_complete(
4074                    RESOURCES_TEMPLATES_LIST,
4075                    input,
4076                    &["resourceTemplates", "nextCursor", "ttlMs", "cacheScope"],
4077                )
4078                .map(|(result, diagnostic)| {
4079                    FinalCoreResult::ResourceTemplatesList { result, diagnostic }
4080                })
4081            }
4082            Self::ResourcesRead(_) => decode_final_complete_or_input_required(
4083                RESOURCES_READ,
4084                input,
4085                &["contents", "ttlMs", "cacheScope"],
4086            )
4087            .map(|result| match result {
4088                FinalMethodResult::Complete { result, diagnostic } => {
4089                    FinalCoreResult::ResourcesRead { result, diagnostic }
4090                }
4091                FinalMethodResult::InputRequired { result, diagnostic } => {
4092                    FinalCoreResult::ResourcesReadInputRequired { result, diagnostic }
4093                }
4094            }),
4095            Self::PromptsList(_) => decode_final_complete(
4096                PROMPTS_LIST,
4097                input,
4098                &["prompts", "nextCursor", "ttlMs", "cacheScope"],
4099            )
4100            .map(|(result, diagnostic)| FinalCoreResult::PromptsList { result, diagnostic }),
4101            Self::PromptsGet(_) => decode_final_complete_or_input_required(
4102                PROMPTS_GET,
4103                input,
4104                &["description", "messages"],
4105            )
4106            .map(|result| match result {
4107                FinalMethodResult::Complete { result, diagnostic } => {
4108                    FinalCoreResult::PromptsGet { result, diagnostic }
4109                }
4110                FinalMethodResult::InputRequired { result, diagnostic } => {
4111                    FinalCoreResult::PromptsGetInputRequired { result, diagnostic }
4112                }
4113            }),
4114            Self::SubscriptionsListen(_) => {
4115                let (result, diagnostic) = decode_final_complete(SUBSCRIPTIONS_LISTEN, input, &[])?;
4116                let subscription_id = subscription_id_from_result(&result)?;
4117                if response_id
4118                    .is_some_and(|response_id| !response_id.correlates_with(&subscription_id))
4119                {
4120                    return Err(CoreDispatchError::SubscriptionIdMismatch);
4121                }
4122                Ok(FinalCoreResult::SubscriptionsListen {
4123                    result,
4124                    subscription_id,
4125                    diagnostic,
4126                })
4127            }
4128        }
4129    }
4130}
4131
4132impl CoreResult {
4133    /// Returns the era selected by this result.
4134    #[must_use]
4135    pub const fn era(&self) -> ProtocolEra {
4136        match self {
4137            Self::Legacy(_) => ProtocolEra::Legacy2024,
4138            Self::Final(_) => ProtocolEra::Modern2026,
4139        }
4140    }
4141
4142    /// Returns the exact method literal that selected this result type.
4143    #[must_use]
4144    pub const fn method(&self) -> &'static str {
4145        match self {
4146            Self::Legacy(result) => result.method(),
4147            Self::Final(result) => result.method(),
4148        }
4149    }
4150
4151    /// Encodes this typed result without a JSON-RPC response envelope.
4152    pub fn encode(&self) -> Result<String, CoreDispatchError> {
4153        match self {
4154            Self::Legacy(result) => result.encode(),
4155            Self::Final(result) => result.encode(),
4156        }
4157    }
4158}
4159
4160impl LegacyCoreResult {
4161    /// Returns the exact method literal that selected this legacy result.
4162    #[must_use]
4163    pub const fn method(&self) -> &'static str {
4164        match self {
4165            Self::Initialize(_) => INITIALIZE,
4166            Self::Completion(_) => COMPLETION_COMPLETE,
4167            Self::SamplingCreateMessage(_) => SAMPLING_CREATE_MESSAGE,
4168            Self::ToolsList(_) => TOOLS_LIST,
4169            Self::ToolsCall(_) => TOOLS_CALL,
4170            Self::ResourcesList(_) => RESOURCES_LIST,
4171            Self::ResourceTemplatesList(_) => RESOURCES_TEMPLATES_LIST,
4172            Self::ResourcesRead(_) => RESOURCES_READ,
4173            Self::ResourcesSubscribe(_) => RESOURCES_SUBSCRIBE,
4174            Self::ResourcesUnsubscribe(_) => RESOURCES_UNSUBSCRIBE,
4175            Self::PromptsList(_) => PROMPTS_LIST,
4176            Self::PromptsGet(_) => PROMPTS_GET,
4177            Self::SetLogLevel(_) => LOGGING_SET_LEVEL,
4178            Self::Ping(_) => PING,
4179        }
4180    }
4181
4182    fn encode(&self) -> Result<String, CoreDispatchError> {
4183        match self {
4184            Self::Initialize(result) => encode_legacy_result(INITIALIZE, result),
4185            Self::Completion(result) => encode_legacy_result(COMPLETION_COMPLETE, result),
4186            Self::SamplingCreateMessage(result) => {
4187                encode_legacy_result(SAMPLING_CREATE_MESSAGE, result)
4188            }
4189            Self::ToolsList(result) => encode_legacy_result(TOOLS_LIST, result),
4190            Self::ToolsCall(result) => encode_legacy_result(TOOLS_CALL, result),
4191            Self::ResourcesList(result) => encode_legacy_result(RESOURCES_LIST, result),
4192            Self::ResourceTemplatesList(result) => {
4193                encode_legacy_result(RESOURCES_TEMPLATES_LIST, result)
4194            }
4195            Self::ResourcesRead(result) => encode_legacy_result(RESOURCES_READ, result),
4196            Self::ResourcesSubscribe(result) => encode_legacy_result(RESOURCES_SUBSCRIBE, result),
4197            Self::ResourcesUnsubscribe(result) => {
4198                encode_legacy_result(RESOURCES_UNSUBSCRIBE, result)
4199            }
4200            Self::PromptsList(result) => encode_legacy_result(PROMPTS_LIST, result),
4201            Self::PromptsGet(result) => encode_legacy_result(PROMPTS_GET, result),
4202            Self::SetLogLevel(result) => encode_legacy_result(LOGGING_SET_LEVEL, result),
4203            Self::Ping(result) => encode_legacy_result(PING, result),
4204        }
4205    }
4206}
4207
4208impl FinalCoreResult {
4209    /// Returns the exact method literal that selected this final result.
4210    #[must_use]
4211    pub const fn method(&self) -> &'static str {
4212        match self {
4213            Self::Discover(_) => SERVER_DISCOVER,
4214            Self::Completion { .. } => COMPLETION_COMPLETE,
4215            Self::ToolsList { .. } => TOOLS_LIST,
4216            Self::ToolsCall { .. } | Self::ToolsCallInputRequired { .. } => TOOLS_CALL,
4217            #[cfg(feature = "tasks")]
4218            Self::ToolsCallTask { .. } => TOOLS_CALL,
4219            Self::ResourcesList { .. } => RESOURCES_LIST,
4220            Self::ResourceTemplatesList { .. } => RESOURCES_TEMPLATES_LIST,
4221            Self::ResourcesRead { .. } | Self::ResourcesReadInputRequired { .. } => RESOURCES_READ,
4222            Self::PromptsList { .. } => PROMPTS_LIST,
4223            Self::PromptsGet { .. } | Self::PromptsGetInputRequired { .. } => PROMPTS_GET,
4224            Self::SubscriptionsListen { .. } => SUBSCRIPTIONS_LISTEN,
4225        }
4226    }
4227
4228    /// Returns the server-owned metadata that middleware must preserve for
4229    /// this selected final result family.
4230    ///
4231    /// The returned seal includes absence, so middleware cannot introduce a
4232    /// reserved server identity where the server did not emit one. It omits
4233    /// every open metadata member by design.
4234    pub fn protected_metadata_seal(&self) -> Result<FinalResultMetadataSeal, CoreDispatchError> {
4235        fn common_server_info<T>(
4236            result: &CompleteResult<T>,
4237        ) -> Result<Option<Implementation>, CoreDispatchError> {
4238            result
4239                .meta
4240                .final_server_info()
4241                .map_err(CoreDispatchError::from)
4242        }
4243
4244        fn input_required_server_info(
4245            result: &InputRequiredResult,
4246        ) -> Result<Option<Implementation>, CoreDispatchError> {
4247            result
4248                .meta
4249                .final_server_info()
4250                .map_err(CoreDispatchError::from)
4251        }
4252
4253        #[cfg(feature = "tasks")]
4254        fn task_server_info(
4255            result: &crate::tasks_extension::CreateTaskResult,
4256        ) -> Result<Option<Implementation>, CoreDispatchError> {
4257            result.meta.as_ref().map_or(Ok(None), |metadata| {
4258                metadata
4259                    .server_info()
4260                    .map_err(|_| CoreDispatchError::InvalidResult {
4261                        era: ProtocolEra::Modern2026,
4262                        method: TOOLS_CALL,
4263                    })
4264            })
4265        }
4266
4267        match self {
4268            Self::Discover(result) => Ok(FinalResultMetadataSeal {
4269                family: FinalResultMetadataFamily::Discover,
4270                server_info: FinalResultServerInfo::Discovery(
4271                    result.server_info().map(FinalDiscoveryServerInfo::from),
4272                ),
4273                subscription_id: None,
4274            }),
4275            Self::Completion { result, .. } => Ok(FinalResultMetadataSeal {
4276                family: FinalResultMetadataFamily::Completion,
4277                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4278                subscription_id: None,
4279            }),
4280            Self::ToolsList { result, .. } => Ok(FinalResultMetadataSeal {
4281                family: FinalResultMetadataFamily::ToolsList,
4282                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4283                subscription_id: None,
4284            }),
4285            Self::ToolsCall { result, .. } => Ok(FinalResultMetadataSeal {
4286                family: FinalResultMetadataFamily::ToolsCall,
4287                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4288                subscription_id: None,
4289            }),
4290            #[cfg(feature = "tasks")]
4291            Self::ToolsCallTask { result } => Ok(FinalResultMetadataSeal {
4292                family: FinalResultMetadataFamily::ToolsCallTask,
4293                server_info: FinalResultServerInfo::Common(task_server_info(result)?),
4294                subscription_id: None,
4295            }),
4296            Self::ToolsCallInputRequired { result, .. } => Ok(FinalResultMetadataSeal {
4297                family: FinalResultMetadataFamily::ToolsCallInputRequired,
4298                server_info: FinalResultServerInfo::Common(input_required_server_info(result)?),
4299                subscription_id: None,
4300            }),
4301            Self::ResourcesList { result, .. } => Ok(FinalResultMetadataSeal {
4302                family: FinalResultMetadataFamily::ResourcesList,
4303                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4304                subscription_id: None,
4305            }),
4306            Self::ResourceTemplatesList { result, .. } => Ok(FinalResultMetadataSeal {
4307                family: FinalResultMetadataFamily::ResourceTemplatesList,
4308                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4309                subscription_id: None,
4310            }),
4311            Self::ResourcesRead { result, .. } => Ok(FinalResultMetadataSeal {
4312                family: FinalResultMetadataFamily::ResourcesRead,
4313                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4314                subscription_id: None,
4315            }),
4316            Self::ResourcesReadInputRequired { result, .. } => Ok(FinalResultMetadataSeal {
4317                family: FinalResultMetadataFamily::ResourcesReadInputRequired,
4318                server_info: FinalResultServerInfo::Common(input_required_server_info(result)?),
4319                subscription_id: None,
4320            }),
4321            Self::PromptsList { result, .. } => Ok(FinalResultMetadataSeal {
4322                family: FinalResultMetadataFamily::PromptsList,
4323                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4324                subscription_id: None,
4325            }),
4326            Self::PromptsGet { result, .. } => Ok(FinalResultMetadataSeal {
4327                family: FinalResultMetadataFamily::PromptsGet,
4328                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4329                subscription_id: None,
4330            }),
4331            Self::PromptsGetInputRequired { result, .. } => Ok(FinalResultMetadataSeal {
4332                family: FinalResultMetadataFamily::PromptsGetInputRequired,
4333                server_info: FinalResultServerInfo::Common(input_required_server_info(result)?),
4334                subscription_id: None,
4335            }),
4336            Self::SubscriptionsListen {
4337                result,
4338                subscription_id,
4339                ..
4340            } => Ok(FinalResultMetadataSeal {
4341                family: FinalResultMetadataFamily::SubscriptionsListen,
4342                server_info: FinalResultServerInfo::Common(common_server_info(result)?),
4343                subscription_id: Some(subscription_id.clone()),
4344            }),
4345        }
4346    }
4347
4348    fn encode(&self) -> Result<String, CoreDispatchError> {
4349        match self {
4350            Self::Discover(result) => {
4351                serde_json::to_string(result).map_err(|_| CoreDispatchError::InvalidResult {
4352                    era: ProtocolEra::Modern2026,
4353                    method: SERVER_DISCOVER,
4354                })
4355            }
4356            Self::Completion { result, .. } => {
4357                encode_final_complete(COMPLETION_COMPLETE, result, &["completion"])
4358            }
4359            Self::ToolsList { result, .. } => encode_final_complete(
4360                TOOLS_LIST,
4361                result,
4362                &["tools", "nextCursor", "ttlMs", "cacheScope"],
4363            ),
4364            Self::ToolsCall { result, .. } => encode_final_complete(
4365                TOOLS_CALL,
4366                result,
4367                &["content", "isError", "structuredContent"],
4368            ),
4369            #[cfg(feature = "tasks")]
4370            Self::ToolsCallTask { result } => encode_final_tools_call_task(result),
4371            Self::ToolsCallInputRequired { result, .. } => {
4372                encode_final_input_required(TOOLS_CALL, result)
4373            }
4374            Self::ResourcesList { result, .. } => encode_final_complete(
4375                RESOURCES_LIST,
4376                result,
4377                &["resources", "nextCursor", "ttlMs", "cacheScope"],
4378            ),
4379            Self::ResourceTemplatesList { result, .. } => encode_final_complete(
4380                RESOURCES_TEMPLATES_LIST,
4381                result,
4382                &["resourceTemplates", "nextCursor", "ttlMs", "cacheScope"],
4383            ),
4384            Self::ResourcesRead { result, .. } => {
4385                encode_final_complete(RESOURCES_READ, result, &["contents", "ttlMs", "cacheScope"])
4386            }
4387            Self::ResourcesReadInputRequired { result, .. } => {
4388                encode_final_input_required(RESOURCES_READ, result)
4389            }
4390            Self::PromptsList { result, .. } => encode_final_complete(
4391                PROMPTS_LIST,
4392                result,
4393                &["prompts", "nextCursor", "ttlMs", "cacheScope"],
4394            ),
4395            Self::PromptsGet { result, .. } => {
4396                encode_final_complete(PROMPTS_GET, result, &["description", "messages"])
4397            }
4398            Self::PromptsGetInputRequired { result, .. } => {
4399                encode_final_input_required(PROMPTS_GET, result)
4400            }
4401            Self::SubscriptionsListen {
4402                result,
4403                subscription_id,
4404                ..
4405            } => {
4406                if !subscription_id_from_result(result)?.correlates_with(subscription_id) {
4407                    return Err(CoreDispatchError::InvalidResult {
4408                        era: ProtocolEra::Modern2026,
4409                        method: SUBSCRIPTIONS_LISTEN,
4410                    });
4411                }
4412                encode_final_complete(SUBSCRIPTIONS_LISTEN, result, &[])
4413            }
4414        }
4415    }
4416}
4417
4418fn decode_params<T: DeserializeOwned>(
4419    era: ProtocolEra,
4420    method: &'static str,
4421    params: Option<&Value>,
4422) -> Result<T, CoreDispatchError> {
4423    serde_json::from_value(
4424        params
4425            .cloned()
4426            .unwrap_or_else(|| Value::Object(serde_json::Map::default())),
4427    )
4428    .map_err(|_| CoreDispatchError::InvalidParams { era, method })
4429}
4430
4431fn decode_final_params<T: DeserializeOwned>(
4432    method: &'static str,
4433    params: Option<&Value>,
4434) -> Result<T, CoreDispatchError> {
4435    decode_params(ProtocolEra::Modern2026, method, params)
4436}
4437
4438fn encode_params<T: Serialize>(
4439    era: ProtocolEra,
4440    method: &'static str,
4441    params: &T,
4442) -> Result<Option<Value>, CoreDispatchError> {
4443    serde_json::to_value(params)
4444        .map(Some)
4445        .map_err(|_| CoreDispatchError::InvalidParams { era, method })
4446}
4447
4448fn require_absent_or_empty_params(
4449    era: ProtocolEra,
4450    method: &'static str,
4451    params: Option<&Value>,
4452) -> Result<(), CoreDispatchError> {
4453    if params.is_none_or(|params| params.as_object().is_some_and(|object| object.is_empty())) {
4454        Ok(())
4455    } else {
4456        Err(CoreDispatchError::InvalidParams { era, method })
4457    }
4458}
4459
4460fn legacy_params_carry_final_metadata(params: Option<&Value>) -> bool {
4461    params.is_some_and(has_final_only_metadata)
4462}
4463
4464fn decode_legacy_result<T: DeserializeOwned>(
4465    method: &'static str,
4466    input: &str,
4467) -> Result<T, CoreDispatchError> {
4468    let value: Value =
4469        serde_json::from_str(input).map_err(|_| CoreDispatchError::InvalidResult {
4470            era: ProtocolEra::Legacy2024,
4471            method,
4472        })?;
4473    if value
4474        .as_object()
4475        .is_some_and(|object| object.contains_key("resultType"))
4476    {
4477        return Err(CoreDispatchError::CrossEraResultType { method });
4478    }
4479    if has_final_only_metadata(&value) {
4480        return Err(CoreDispatchError::CrossEraResultMetadata { method });
4481    }
4482    serde_json::from_value(value).map_err(|_| CoreDispatchError::InvalidResult {
4483        era: ProtocolEra::Legacy2024,
4484        method,
4485    })
4486}
4487
4488fn encode_legacy_result<T: Serialize>(
4489    method: &'static str,
4490    result: &T,
4491) -> Result<String, CoreDispatchError> {
4492    let value = serde_json::to_value(result).map_err(|_| CoreDispatchError::InvalidResult {
4493        era: ProtocolEra::Legacy2024,
4494        method,
4495    })?;
4496    if value
4497        .as_object()
4498        .is_some_and(|object| object.contains_key("resultType"))
4499    {
4500        return Err(CoreDispatchError::CrossEraResultType { method });
4501    }
4502    if has_final_only_metadata(&value) {
4503        return Err(CoreDispatchError::CrossEraResultMetadata { method });
4504    }
4505    // Serialize the typed result directly: `value` exists only for the era
4506    // checks above, and emitting it would alphabetize members through the
4507    // BTreeMap-backed Value instead of keeping declaration order.
4508    serde_json::to_string(result).map_err(|_| CoreDispatchError::InvalidResult {
4509        era: ProtocolEra::Legacy2024,
4510        method,
4511    })
4512}
4513
4514enum FinalMethodResult<T> {
4515    Complete {
4516        result: CompleteResult<T>,
4517        diagnostic: Option<ResultPeerDiagnostic>,
4518    },
4519    InputRequired {
4520        result: InputRequiredResult,
4521        diagnostic: Option<ResultPeerDiagnostic>,
4522    },
4523}
4524
4525fn decode_final_tools_call(input: &str) -> Result<FinalCoreResult, CoreDispatchError> {
4526    let ExactJsonValue::Object(wire) =
4527        crate::result::parse_exact_json(input).map_err(|_| CoreDispatchError::InvalidResult {
4528            era: ProtocolEra::Modern2026,
4529            method: TOOLS_CALL,
4530        })?
4531    else {
4532        return Err(CoreDispatchError::InvalidResult {
4533            era: ProtocolEra::Modern2026,
4534            method: TOOLS_CALL,
4535        });
4536    };
4537    if wire.get("serverInfo").is_some() {
4538        return Err(CoreDispatchError::InvalidResult {
4539            era: ProtocolEra::Modern2026,
4540            method: TOOLS_CALL,
4541        });
4542    }
4543    if matches!(
4544        wire.get("resultType"),
4545        Some(ExactJsonValue::String(result_type)) if result_type == "task"
4546    ) {
4547        #[cfg(feature = "tasks")]
4548        {
4549            let result = crate::tasks_extension::CreateTaskResult::decode_exact_wire(input)
4550                .map_err(|_| CoreDispatchError::InvalidResult {
4551                    era: ProtocolEra::Modern2026,
4552                    method: TOOLS_CALL,
4553                })?;
4554            return Ok(FinalCoreResult::ToolsCallTask { result });
4555        }
4556        #[cfg(not(feature = "tasks"))]
4557        {
4558            return Err(CoreDispatchError::FeatureUnavailable { feature: "tasks" });
4559        }
4560    }
4561    decode_final_complete_or_input_required(
4562        TOOLS_CALL,
4563        input,
4564        &["content", "isError", "structuredContent"],
4565    )
4566    .map(|result| match result {
4567        FinalMethodResult::Complete { result, diagnostic } => {
4568            FinalCoreResult::ToolsCall { result, diagnostic }
4569        }
4570        FinalMethodResult::InputRequired { result, diagnostic } => {
4571            FinalCoreResult::ToolsCallInputRequired { result, diagnostic }
4572        }
4573    })
4574}
4575
4576fn decode_final_complete<T: DeserializeOwned>(
4577    method: &'static str,
4578    input: &str,
4579    known_names: &[&str],
4580) -> Result<(CompleteResult<T>, Option<ResultPeerDiagnostic>), CoreDispatchError> {
4581    let FinalMethodResult::Complete { result, diagnostic } =
4582        decode_final_complete_or_input_required(method, input, known_names)?
4583    else {
4584        return Err(CoreDispatchError::UnexpectedFinalResultType { method });
4585    };
4586    Ok((result, diagnostic))
4587}
4588
4589fn decode_final_complete_or_input_required<T: DeserializeOwned>(
4590    method: &'static str,
4591    input: &str,
4592    known_names: &[&str],
4593) -> Result<FinalMethodResult<T>, CoreDispatchError> {
4594    let wire: Value =
4595        serde_json::from_str(input).map_err(|_| CoreDispatchError::InvalidResult {
4596            era: ProtocolEra::Modern2026,
4597            method,
4598        })?;
4599    if wire
4600        .as_object()
4601        .is_some_and(|object| object.contains_key("serverInfo"))
4602    {
4603        return Err(CoreDispatchError::InvalidResult {
4604            era: ProtocolEra::Modern2026,
4605            method,
4606        });
4607    }
4608    if wire.get("resultType").and_then(Value::as_str) == Some("task") {
4609        return Err(CoreDispatchError::UnexpectedFinalResultType { method });
4610    }
4611    let metadata_role = if method == SUBSCRIPTIONS_LISTEN {
4612        FinalResultMetadataRole::SubscriptionsListen
4613    } else {
4614        FinalResultMetadataRole::Ordinary
4615    };
4616    let (decoded, diagnostic) = decode_peer_result_for_era_with_metadata_role(
4617        input,
4618        ProtocolEra::Modern2026,
4619        &CoreResultDiscriminatorPolicy,
4620        metadata_role,
4621    )?;
4622    let complete = match decoded {
4623        DecodedResult::Complete(complete) => complete,
4624        DecodedResult::InputRequired(result) => {
4625            return Ok(FinalMethodResult::InputRequired { result, diagnostic });
4626        }
4627        DecodedResult::Deferred(_) => {
4628            return Err(CoreDispatchError::UnexpectedFinalResultType { method });
4629        }
4630    };
4631    let CompleteResult { meta, extras, .. } = complete;
4632    let mut selected = Vec::new();
4633    let mut remaining = Vec::new();
4634    for member in extras.into_members() {
4635        if known_names.contains(&member.name.as_str()) {
4636            selected.push(member);
4637        } else {
4638            remaining.push(member);
4639        }
4640    }
4641    let payload =
4642        deserialize_exact_object(selected).map_err(|_| CoreDispatchError::InvalidResult {
4643            era: ProtocolEra::Modern2026,
4644            method,
4645        })?;
4646    let extras = UnknownResultMembers::try_new(remaining, known_names)?;
4647    Ok(FinalMethodResult::Complete {
4648        result: CompleteResult {
4649            payload,
4650            meta,
4651            extras,
4652        },
4653        diagnostic,
4654    })
4655}
4656
4657fn subscription_id_from_result(
4658    result: &CompleteResult<FinalSubscriptionsListenResult>,
4659) -> Result<RequestId, CoreDispatchError> {
4660    let metadata = result.meta.metadata();
4661    let Some(value) = metadata.get(FINAL_SUBSCRIPTION_ID_META_KEY) else {
4662        return Err(CoreDispatchError::InvalidResult {
4663            era: ProtocolEra::Modern2026,
4664            method: SUBSCRIPTIONS_LISTEN,
4665        });
4666    };
4667    serde_json::from_value(exact_json_to_serde(value)?).map_err(|_| {
4668        CoreDispatchError::InvalidResult {
4669            era: ProtocolEra::Modern2026,
4670            method: SUBSCRIPTIONS_LISTEN,
4671        }
4672    })
4673}
4674
4675fn encode_final_complete<T: Serialize>(
4676    method: &'static str,
4677    result: &CompleteResult<T>,
4678    known_names: &[&str],
4679) -> Result<String, CoreDispatchError> {
4680    if result.meta.server_info.is_some()
4681        || result
4682            .extras
4683            .members()
4684            .iter()
4685            .any(|member| member.name == "serverInfo")
4686    {
4687        return Err(CoreDispatchError::InvalidResult {
4688            era: ProtocolEra::Modern2026,
4689            method,
4690        });
4691    }
4692    // Stream-serialize the typed payload and reparse it exactly: routing
4693    // through serde_json::Value would alphabetize every object (BTreeMap
4694    // maps), destroying the declaration-ordered member layout and the
4695    // tag-first content blocks that the frozen final wires require.
4696    let payload_text =
4697        serde_json::to_string(&result.payload).map_err(|_| CoreDispatchError::InvalidResult {
4698            era: ProtocolEra::Modern2026,
4699            method,
4700        })?;
4701    let ExactJsonValue::Object(payload) =
4702        crate::result::parse_exact_json(&payload_text).map_err(|_| {
4703            CoreDispatchError::InvalidResult {
4704                era: ProtocolEra::Modern2026,
4705                method,
4706            }
4707        })?
4708    else {
4709        return Err(CoreDispatchError::InvalidResult {
4710            era: ProtocolEra::Modern2026,
4711            method,
4712        });
4713    };
4714    for member in payload.members() {
4715        if !known_names.contains(&member.name.as_str()) {
4716            return Err(CoreDispatchError::InvalidResult {
4717                era: ProtocolEra::Modern2026,
4718                method,
4719            });
4720        }
4721    }
4722    encode_complete_result(
4723        &result.meta,
4724        payload.members().to_vec(),
4725        known_names,
4726        &result.extras,
4727    )
4728    .map_err(CoreDispatchError::from)
4729}
4730
4731fn encode_final_input_required(
4732    method: &'static str,
4733    result: &InputRequiredResult,
4734) -> Result<String, CoreDispatchError> {
4735    if result.meta.server_info.is_some()
4736        || result
4737            .extras
4738            .members()
4739            .iter()
4740            .any(|member| member.name == "serverInfo")
4741    {
4742        return Err(CoreDispatchError::InvalidResult {
4743            era: ProtocolEra::Modern2026,
4744            method,
4745        });
4746    }
4747    Ok(encode_result(&DecodedResult::InputRequired(result.clone())))
4748}
4749
4750#[cfg(feature = "tasks")]
4751fn encode_final_tools_call_task(
4752    result: &crate::tasks_extension::CreateTaskResult,
4753) -> Result<String, CoreDispatchError> {
4754    if result.additional.contains_key("serverInfo") {
4755        return Err(CoreDispatchError::InvalidResult {
4756            era: ProtocolEra::Modern2026,
4757            method: TOOLS_CALL,
4758        });
4759    }
4760    serde_json::to_string(result).map_err(|_| CoreDispatchError::InvalidResult {
4761        era: ProtocolEra::Modern2026,
4762        method: TOOLS_CALL,
4763    })
4764}
4765
4766// ============================================================================
4767// Initialize
4768// ============================================================================
4769
4770/// Initialize request params.
4771#[derive(Debug, Clone, Serialize, Deserialize)]
4772pub struct InitializeParams {
4773    /// Protocol version requested.
4774    #[serde(rename = "protocolVersion")]
4775    pub protocol_version: String,
4776    /// Client capabilities.
4777    pub capabilities: ClientCapabilities,
4778    /// Client info.
4779    #[serde(rename = "clientInfo")]
4780    pub client_info: ClientInfo,
4781}
4782
4783/// Initialize response result.
4784#[derive(Debug, Clone, Serialize, Deserialize)]
4785pub struct InitializeResult {
4786    /// Protocol version accepted.
4787    #[serde(rename = "protocolVersion")]
4788    pub protocol_version: String,
4789    /// Server capabilities.
4790    pub capabilities: ServerCapabilities,
4791    /// Server info.
4792    #[serde(rename = "serverInfo")]
4793    pub server_info: ServerInfo,
4794    /// Optional instructions for the client.
4795    #[serde(skip_serializing_if = "Option::is_none")]
4796    pub instructions: Option<String>,
4797}
4798
4799// ============================================================================
4800// Tools
4801// ============================================================================
4802
4803/// tools/list request params.
4804#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4805pub struct ListToolsParams {
4806    /// Cursor for pagination.
4807    #[serde(skip_serializing_if = "Option::is_none")]
4808    pub cursor: Option<String>,
4809    /// Only include tools with ALL of these tags (AND logic).
4810    #[serde(
4811        rename = "includeTags",
4812        default,
4813        skip_serializing_if = "Option::is_none"
4814    )]
4815    pub include_tags: Option<Vec<String>>,
4816    /// Exclude tools with ANY of these tags (OR logic).
4817    #[serde(
4818        rename = "excludeTags",
4819        default,
4820        skip_serializing_if = "Option::is_none"
4821    )]
4822    pub exclude_tags: Option<Vec<String>>,
4823}
4824
4825/// tools/list response result.
4826#[derive(Debug, Clone, Serialize, Deserialize)]
4827pub struct ListToolsResult {
4828    /// List of available tools.
4829    pub tools: Vec<Tool>,
4830    /// Next cursor for pagination.
4831    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
4832    pub next_cursor: Option<String>,
4833}
4834
4835/// tools/call request params.
4836#[derive(Debug, Clone, Serialize, Deserialize)]
4837pub struct CallToolParams {
4838    /// Tool name to call.
4839    pub name: String,
4840    /// Tool arguments.
4841    #[serde(default, skip_serializing_if = "Option::is_none")]
4842    pub arguments: Option<serde_json::Value>,
4843    /// Request metadata (progress token, etc.).
4844    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
4845    pub meta: Option<RequestMeta>,
4846}
4847
4848/// tools/call response result.
4849#[derive(Debug, Clone, Serialize, Deserialize)]
4850pub struct CallToolResult {
4851    /// Tool output content.
4852    pub content: Vec<LegacyContent>,
4853    /// Whether the tool call errored.
4854    #[serde(
4855        rename = "isError",
4856        default,
4857        skip_serializing_if = "std::ops::Not::not"
4858    )]
4859    pub is_error: bool,
4860    /// Open legacy result metadata.
4861    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
4862    pub meta: Option<LegacyMetadata>,
4863    /// Other schema-allowed result members.
4864    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
4865    pub additional: BTreeMap<String, Value>,
4866}
4867
4868// ============================================================================
4869// Resources
4870// ============================================================================
4871
4872/// resources/list request params.
4873#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4874pub struct ListResourcesParams {
4875    /// Cursor for pagination.
4876    #[serde(skip_serializing_if = "Option::is_none")]
4877    pub cursor: Option<String>,
4878    /// Only include resources with ALL of these tags (AND logic).
4879    #[serde(
4880        rename = "includeTags",
4881        default,
4882        skip_serializing_if = "Option::is_none"
4883    )]
4884    pub include_tags: Option<Vec<String>>,
4885    /// Exclude resources with ANY of these tags (OR logic).
4886    #[serde(
4887        rename = "excludeTags",
4888        default,
4889        skip_serializing_if = "Option::is_none"
4890    )]
4891    pub exclude_tags: Option<Vec<String>>,
4892}
4893
4894/// resources/list response result.
4895#[derive(Debug, Clone, Serialize, Deserialize)]
4896pub struct ListResourcesResult {
4897    /// List of available resources.
4898    pub resources: Vec<Resource>,
4899    /// Next cursor for pagination.
4900    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
4901    pub next_cursor: Option<String>,
4902}
4903
4904/// resources/templates/list request params.
4905#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4906pub struct ListResourceTemplatesParams {
4907    /// Cursor for pagination.
4908    #[serde(skip_serializing_if = "Option::is_none")]
4909    pub cursor: Option<String>,
4910    /// Only include templates with ALL of these tags (AND logic).
4911    #[serde(
4912        rename = "includeTags",
4913        default,
4914        skip_serializing_if = "Option::is_none"
4915    )]
4916    pub include_tags: Option<Vec<String>>,
4917    /// Exclude templates with ANY of these tags (OR logic).
4918    #[serde(
4919        rename = "excludeTags",
4920        default,
4921        skip_serializing_if = "Option::is_none"
4922    )]
4923    pub exclude_tags: Option<Vec<String>>,
4924}
4925
4926/// resources/templates/list response result.
4927#[derive(Debug, Clone, Serialize, Deserialize)]
4928pub struct ListResourceTemplatesResult {
4929    /// List of resource templates.
4930    #[serde(rename = "resourceTemplates")]
4931    pub resource_templates: Vec<ResourceTemplate>,
4932    /// Next cursor for pagination.
4933    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
4934    pub next_cursor: Option<String>,
4935}
4936
4937/// resources/read request params.
4938#[derive(Debug, Clone, Serialize, Deserialize)]
4939pub struct ReadResourceParams {
4940    /// Resource URI to read.
4941    pub uri: String,
4942    /// Request metadata (progress token, etc.).
4943    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
4944    pub meta: Option<RequestMeta>,
4945}
4946
4947/// resources/read response result.
4948#[derive(Debug, Clone, Serialize, Deserialize)]
4949pub struct ReadResourceResult {
4950    /// Resource contents.
4951    #[serde(deserialize_with = "deserialize_legacy_resource_contents")]
4952    pub contents: Vec<LegacyResourceContent>,
4953    /// Open legacy result metadata.
4954    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
4955    pub meta: Option<LegacyMetadata>,
4956    /// Other schema-allowed result members.
4957    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
4958    pub additional: BTreeMap<String, Value>,
4959}
4960
4961/// Decodes the exact legacy resource-content one-of without discarding open members.
4962///
4963/// `LegacyResourceContent` is intentionally untagged because the 2024 wire
4964/// shape selects its variant by `text` or `blob`. Its flattened open members
4965/// otherwise allow an object with both (or neither) fields to match an
4966/// unintended variant, so inspect the two discriminating fields before asking
4967/// serde to preserve the typed fields, `_meta`, and additional members.
4968fn deserialize_legacy_resource_contents<'de, D>(
4969    deserializer: D,
4970) -> Result<Vec<LegacyResourceContent>, D::Error>
4971where
4972    D: Deserializer<'de>,
4973{
4974    let contents = Vec::<Value>::deserialize(deserializer)?;
4975    contents
4976        .into_iter()
4977        .map(|content| {
4978            let Value::Object(object) = &content else {
4979                return Err(serde::de::Error::custom(
4980                    "legacy resource content must be an object",
4981                ));
4982            };
4983            match (object.contains_key("text"), object.contains_key("blob")) {
4984                (true, false) | (false, true) => serde_json::from_value(content)
4985                    .map_err(|error| serde::de::Error::custom(error.to_string())),
4986                (true, true) | (false, false) => Err(serde::de::Error::custom(
4987                    "legacy resource content must contain exactly one of text or blob",
4988                )),
4989            }
4990        })
4991        .collect()
4992}
4993
4994/// resources/subscribe request params.
4995#[derive(Debug, Clone, Serialize, Deserialize)]
4996pub struct SubscribeResourceParams {
4997    /// Resource URI to subscribe to.
4998    pub uri: String,
4999}
5000
5001/// resources/unsubscribe request params.
5002#[derive(Debug, Clone, Serialize, Deserialize)]
5003pub struct UnsubscribeResourceParams {
5004    /// Resource URI to unsubscribe from.
5005    pub uri: String,
5006}
5007
5008// ============================================================================
5009// Prompts
5010// ============================================================================
5011
5012/// prompts/list request params.
5013#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5014pub struct ListPromptsParams {
5015    /// Cursor for pagination.
5016    #[serde(skip_serializing_if = "Option::is_none")]
5017    pub cursor: Option<String>,
5018    /// Only include prompts with ALL of these tags (AND logic).
5019    #[serde(
5020        rename = "includeTags",
5021        default,
5022        skip_serializing_if = "Option::is_none"
5023    )]
5024    pub include_tags: Option<Vec<String>>,
5025    /// Exclude prompts with ANY of these tags (OR logic).
5026    #[serde(
5027        rename = "excludeTags",
5028        default,
5029        skip_serializing_if = "Option::is_none"
5030    )]
5031    pub exclude_tags: Option<Vec<String>>,
5032}
5033
5034/// prompts/list response result.
5035#[derive(Debug, Clone, Serialize, Deserialize)]
5036pub struct ListPromptsResult {
5037    /// List of available prompts.
5038    pub prompts: Vec<Prompt>,
5039    /// Next cursor for pagination.
5040    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
5041    pub next_cursor: Option<String>,
5042}
5043
5044/// prompts/get request params.
5045#[derive(Debug, Clone, Serialize, Deserialize)]
5046pub struct GetPromptParams {
5047    /// Prompt name.
5048    pub name: String,
5049    /// Prompt arguments.
5050    #[serde(default, skip_serializing_if = "Option::is_none")]
5051    pub arguments: Option<std::collections::HashMap<String, String>>,
5052    /// Request metadata (progress token, etc.).
5053    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
5054    pub meta: Option<RequestMeta>,
5055}
5056
5057/// prompts/get response result.
5058#[derive(Debug, Clone, Serialize, Deserialize)]
5059pub struct GetPromptResult {
5060    /// Optional prompt description.
5061    #[serde(skip_serializing_if = "Option::is_none")]
5062    pub description: Option<String>,
5063    /// Prompt messages.
5064    pub messages: Vec<LegacyPromptMessage>,
5065    /// Open legacy result metadata.
5066    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
5067    pub meta: Option<LegacyMetadata>,
5068    /// Other schema-allowed result members.
5069    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
5070    pub additional: BTreeMap<String, Value>,
5071}
5072
5073// ============================================================================
5074// Logging
5075// ============================================================================
5076
5077/// Log level.
5078#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5079#[serde(rename_all = "lowercase")]
5080pub enum LogLevel {
5081    /// Emergency level.
5082    Emergency,
5083    /// Alert level.
5084    Alert,
5085    /// Critical level.
5086    Critical,
5087    /// Debug level.
5088    Debug,
5089    /// Info level.
5090    Info,
5091    /// Notice level.
5092    Notice,
5093    /// Warning level.
5094    Warning,
5095    /// Error level.
5096    Error,
5097}
5098
5099/// logging/setLevel request params.
5100#[derive(Debug, Clone, Serialize, Deserialize)]
5101pub struct SetLogLevelParams {
5102    /// The log level to set.
5103    pub level: LogLevel,
5104}
5105
5106// ============================================================================
5107// Notifications
5108// ============================================================================
5109
5110/// Historical cancellation-reason size used by earlier bounded profiles.
5111///
5112/// Neither supported MCP era imposes this wire limit, so exact cancellation
5113/// encoding and decoding do not enforce it.
5114pub const MAX_CANCELLATION_REASON_BYTES: usize = 4 * 1024;
5115
5116/// Cancelled notification params.
5117///
5118/// Sent by either party to request cancellation of an in-progress request.
5119#[derive(Debug, Clone, Serialize, Deserialize)]
5120#[serde(deny_unknown_fields)]
5121pub struct CancelledParams {
5122    /// The ID of the request to cancel.
5123    #[serde(rename = "requestId")]
5124    pub request_id: RequestId,
5125    /// Optional reason for cancellation.
5126    #[serde(
5127        default,
5128        skip_serializing_if = "Option::is_none",
5129        serialize_with = "serialize_cancellation_reason",
5130        deserialize_with = "deserialize_cancellation_reason"
5131    )]
5132    pub reason: Option<String>,
5133}
5134
5135/// Peer that originates a cancellation notification.
5136///
5137/// Legacy MCP permits cancellation in either direction. Final MCP permits a
5138/// client to cancel its own live request and, on stdio only, a server to end
5139/// only its live `subscriptions/listen` stream. The sender remains part of the
5140/// typed value so consumers can enforce the selected-era ownership rule;
5141/// transport and live-registry binding remain outside this protocol codec.
5142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5143pub enum CancellationSender {
5144    /// A client is cancelling one of its in-flight requests.
5145    Client,
5146    /// A server is cancelling a request-owned stream, such as a subscription.
5147    Server,
5148}
5149
5150/// An era-selected `notifications/cancelled` JSON-RPC notification.
5151///
5152/// This is intentionally separate from the broad [`ClientNotification`] and
5153/// [`ServerNotification`] unions. Those unions preserve schema-open final
5154/// extension members for generic routing. This codec is the boundary at which
5155/// FastMCP assigns cancellation semantics without inferring meaning from
5156/// schema-open extension members.
5157///
5158/// The codec is for JSON-RPC transports such as stdio. Modern Streamable HTTP
5159/// cancellation is a response-stream close and must not be translated into a
5160/// second `notifications/cancelled` POST by a transport integration.
5161#[derive(Debug, Clone)]
5162pub enum CancellationWireMessage {
5163    /// Exact MCP 2024-11-05 cancellation parameters.
5164    Legacy2024 {
5165        /// Peer that originated the notification.
5166        sender: CancellationSender,
5167        /// Closed legacy cancellation payload.
5168        params: CancelledParams,
5169    },
5170    /// MCP 2026-07-28 cancellation parameters.
5171    ///
5172    /// Notification metadata is optional for either sender and is never
5173    /// synthesized by this codec.
5174    Modern2026 {
5175        /// Peer that originated the notification.
5176        sender: CancellationSender,
5177        /// Final cancellation payload.
5178        params: FinalCancelledNotificationParams,
5179    },
5180}
5181
5182impl CancellationWireMessage {
5183    /// Decodes one cancellation notification using the already-negotiated era
5184    /// and the peer that supplied the frame.
5185    ///
5186    /// This method deliberately does not infer an era from optional fields.
5187    /// The caller must negotiate once before decoding control traffic.
5188    pub fn decode(
5189        era: ProtocolEra,
5190        sender: CancellationSender,
5191        request: &JsonRpcRequest,
5192    ) -> Result<Self, CancellationWireCodecError> {
5193        validate_cancellation_notification_envelope(era, request)?;
5194        let params = request
5195            .params
5196            .as_ref()
5197            .ok_or(CancellationWireCodecError::MissingParameters { era })?;
5198
5199        match era {
5200            ProtocolEra::Legacy2024 => {
5201                let params =
5202                    serde_json::from_value::<CancelledParams>(params.clone()).map_err(|_| {
5203                        CancellationWireCodecError::InvalidParameters {
5204                            era: ProtocolEra::Legacy2024,
5205                        }
5206                    })?;
5207                Ok(Self::Legacy2024 { sender, params })
5208            }
5209            ProtocolEra::Modern2026 => {
5210                let params =
5211                    serde_json::from_value::<FinalCancelledNotificationParams>(params.clone())
5212                        .map_err(|_| CancellationWireCodecError::InvalidParameters {
5213                            era: ProtocolEra::Modern2026,
5214                        })?;
5215                if sender == CancellationSender::Server
5216                    && !final_server_cancellation_metadata_matches_request(&params)
5217                {
5218                    return Err(CancellationWireCodecError::InvalidParameters {
5219                        era: ProtocolEra::Modern2026,
5220                    });
5221                }
5222                Ok(Self::Modern2026 { sender, params })
5223            }
5224        }
5225    }
5226
5227    /// Returns the exact selected protocol era.
5228    #[must_use]
5229    pub const fn era(&self) -> ProtocolEra {
5230        match self {
5231            Self::Legacy2024 { .. } => ProtocolEra::Legacy2024,
5232            Self::Modern2026 { .. } => ProtocolEra::Modern2026,
5233        }
5234    }
5235
5236    /// Returns the peer that originated this cancellation notification.
5237    #[must_use]
5238    pub const fn sender(&self) -> CancellationSender {
5239        match self {
5240            Self::Legacy2024 { sender, .. } | Self::Modern2026 { sender, .. } => *sender,
5241        }
5242    }
5243
5244    /// Encodes this typed cancellation as an ID-free JSON-RPC notification.
5245    ///
5246    /// Local construction receives the same era-specific validation as peer
5247    /// ingress. A schema-open extension never acquires cancellation semantics
5248    /// through this codec.
5249    pub fn encode(&self) -> Result<JsonRpcRequest, CancellationWireCodecError> {
5250        let era = self.era();
5251        let params = match self {
5252            Self::Legacy2024 { params, .. } => serde_json::to_value(params),
5253            Self::Modern2026 { sender, params } => {
5254                if *sender == CancellationSender::Server
5255                    && !final_server_cancellation_metadata_matches_request(params)
5256                {
5257                    return Err(CancellationWireCodecError::InvalidParameters { era });
5258                }
5259                serde_json::to_value(params)
5260            }
5261        }
5262        .map_err(|_| CancellationWireCodecError::EncodeFailure { era })?;
5263        Ok(JsonRpcRequest::notification(
5264            NOTIFICATIONS_CANCELLED,
5265            Some(params),
5266        ))
5267    }
5268}
5269
5270fn final_server_cancellation_metadata_matches_request(
5271    params: &FinalCancelledNotificationParams,
5272) -> bool {
5273    let Some(metadata) = params.meta.as_ref() else {
5274        return true;
5275    };
5276    let Some(subscription_id) = metadata.get(FINAL_SUBSCRIPTION_ID_META_KEY) else {
5277        return true;
5278    };
5279    serde_json::from_value::<RequestId>(subscription_id.clone())
5280        .is_ok_and(|subscription_id| params.request_id.correlates_with(&subscription_id))
5281}
5282
5283/// Stable refusal classes for [`CancellationWireMessage`] codec operations.
5284#[derive(Debug, Clone, PartialEq, Eq)]
5285pub enum CancellationWireCodecError {
5286    /// The JSON-RPC envelope has an invalid version or request ID.
5287    InvalidEnvelope {
5288        /// Era selected before the frame was decoded.
5289        era: ProtocolEra,
5290    },
5291    /// The wire frame is a JSON-RPC request instead of a notification.
5292    RequestIdPresent {
5293        /// Era selected before the frame was decoded.
5294        era: ProtocolEra,
5295    },
5296    /// The selected cancellation codec received another method.
5297    UnexpectedMethod {
5298        /// Era selected before the frame was decoded.
5299        era: ProtocolEra,
5300        /// Method literal received from the peer.
5301        method: String,
5302    },
5303    /// The cancellation notification omitted its required parameters object.
5304    MissingParameters {
5305        /// Era selected before the frame was decoded.
5306        era: ProtocolEra,
5307    },
5308    /// Parameters do not have the exact selected-era shape.
5309    InvalidParameters {
5310        /// Era selected before the frame was decoded.
5311        era: ProtocolEra,
5312    },
5313    /// A locally constructed typed payload could not be serialized.
5314    EncodeFailure {
5315        /// Era selected by the typed value.
5316        era: ProtocolEra,
5317    },
5318}
5319
5320impl std::fmt::Display for CancellationWireCodecError {
5321    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5322        match self {
5323            Self::InvalidEnvelope { era } => {
5324                write!(formatter, "invalid {era:?} cancellation JSON-RPC envelope")
5325            }
5326            Self::RequestIdPresent { era } => {
5327                write!(formatter, "{era:?} cancellation must be a notification")
5328            }
5329            Self::UnexpectedMethod { era, method } => {
5330                write!(
5331                    formatter,
5332                    "{method} is not a {era:?} cancellation notification"
5333                )
5334            }
5335            Self::MissingParameters { era } => {
5336                write!(formatter, "{era:?} cancellation requires parameters")
5337            }
5338            Self::InvalidParameters { era } => {
5339                write!(formatter, "invalid {era:?} cancellation parameters")
5340            }
5341            Self::EncodeFailure { era } => {
5342                write!(
5343                    formatter,
5344                    "unable to encode {era:?} cancellation parameters"
5345                )
5346            }
5347        }
5348    }
5349}
5350
5351impl std::error::Error for CancellationWireCodecError {}
5352
5353fn validate_cancellation_notification_envelope(
5354    era: ProtocolEra,
5355    request: &JsonRpcRequest,
5356) -> Result<(), CancellationWireCodecError> {
5357    if request.validate().is_err() {
5358        return Err(CancellationWireCodecError::InvalidEnvelope { era });
5359    }
5360    if !request.is_notification() {
5361        return Err(CancellationWireCodecError::RequestIdPresent { era });
5362    }
5363    if request.method != NOTIFICATIONS_CANCELLED {
5364        return Err(CancellationWireCodecError::UnexpectedMethod {
5365            era,
5366            method: request.method.clone(),
5367        });
5368    }
5369    Ok(())
5370}
5371
5372fn serialize_cancellation_reason<S>(
5373    reason: &Option<String>,
5374    serializer: S,
5375) -> Result<S::Ok, S::Error>
5376where
5377    S: Serializer,
5378{
5379    match reason {
5380        Some(reason) => serializer.serialize_str(reason),
5381        None => serializer.serialize_none(),
5382    }
5383}
5384
5385fn deserialize_cancellation_reason<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
5386where
5387    D: Deserializer<'de>,
5388{
5389    struct CancellationReasonVisitor;
5390
5391    impl<'de> Visitor<'de> for CancellationReasonVisitor {
5392        type Value = String;
5393
5394        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5395            formatter.write_str("a non-null cancellation reason string")
5396        }
5397
5398        fn visit_borrowed_str<E>(self, value: &'de str) -> Result<Self::Value, E>
5399        where
5400            E: serde::de::Error,
5401        {
5402            self.visit_str(value)
5403        }
5404
5405        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
5406        where
5407            E: serde::de::Error,
5408        {
5409            Ok(value.to_owned())
5410        }
5411
5412        fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
5413        where
5414            E: serde::de::Error,
5415        {
5416            Ok(value)
5417        }
5418    }
5419
5420    deserializer
5421        .deserialize_str(CancellationReasonVisitor)
5422        .map(Some)
5423}
5424
5425/// Progress notification params.
5426///
5427/// Sent from server to client to report progress on a long-running operation.
5428#[derive(Debug, Clone, Serialize, Deserialize)]
5429pub struct ProgressParams {
5430    /// Progress marker (from original request's `_meta.progress...` field).
5431    // Avoid UBS "hardcoded secrets" heuristics while keeping the on-the-wire name.
5432    #[serde(rename = "progressTo\x6ben")]
5433    pub progress_marker: ProgressMarker,
5434    /// Progress value (0.0 to 1.0, or absolute values for indeterminate progress).
5435    pub progress: f64,
5436    /// Total expected progress (optional, for determinate progress).
5437    #[serde(skip_serializing_if = "Option::is_none")]
5438    pub total: Option<f64>,
5439    /// Optional progress message describing current status.
5440    #[serde(skip_serializing_if = "Option::is_none")]
5441    pub message: Option<String>,
5442}
5443
5444impl ProgressParams {
5445    /// Creates a new progress notification.
5446    #[must_use]
5447    pub fn new(marker: impl Into<ProgressMarker>, progress: f64) -> Self {
5448        Self {
5449            progress_marker: marker.into(),
5450            progress,
5451            total: None,
5452            message: None,
5453        }
5454    }
5455
5456    /// Creates a progress notification with total (determinate progress).
5457    #[must_use]
5458    pub fn with_total(marker: impl Into<ProgressMarker>, progress: f64, total: f64) -> Self {
5459        Self {
5460            progress_marker: marker.into(),
5461            progress,
5462            total: Some(total),
5463            message: None,
5464        }
5465    }
5466
5467    /// Adds a message to the progress notification.
5468    #[must_use]
5469    pub fn with_message(mut self, message: impl Into<String>) -> Self {
5470        self.message = Some(message.into());
5471        self
5472    }
5473
5474    /// Returns the progress as a fraction (0.0 to 1.0) if total is known.
5475    #[must_use]
5476    pub fn fraction(&self) -> Option<f64> {
5477        self.total
5478            .map(|t| if t > 0.0 { self.progress / t } else { 0.0 })
5479    }
5480}
5481
5482/// Resource updated notification params.
5483///
5484/// Sent from server to client when a subscribed resource changes.
5485#[derive(Debug, Clone, Serialize, Deserialize)]
5486pub struct ResourceUpdatedNotificationParams {
5487    /// Updated resource URI.
5488    pub uri: String,
5489}
5490
5491/// Log message notification params.
5492#[derive(Debug, Clone, Serialize, Deserialize)]
5493pub struct LogMessageParams {
5494    /// Log level.
5495    pub level: LogLevel,
5496    /// Logger name.
5497    #[serde(skip_serializing_if = "Option::is_none")]
5498    pub logger: Option<String>,
5499    /// Log message data.
5500    pub data: serde_json::Value,
5501}
5502
5503// ============================================================================
5504// Background Tasks (Docket/SEP-1686)
5505// ============================================================================
5506
5507use crate::types::{TaskId, TaskResult, TaskStatus};
5508
5509/// Task status change notification params.
5510///
5511/// Sent from server to client when a task status changes.
5512#[derive(Debug, Clone, Serialize, Deserialize)]
5513pub struct TaskStatusNotificationParams {
5514    /// Task ID.
5515    pub id: TaskId,
5516    /// New task status.
5517    pub status: TaskStatus,
5518    /// Progress (0.0 to 1.0, if known).
5519    #[serde(skip_serializing_if = "Option::is_none")]
5520    pub progress: Option<f64>,
5521    /// Progress message.
5522    #[serde(skip_serializing_if = "Option::is_none")]
5523    pub message: Option<String>,
5524    /// Error message (if failed).
5525    #[serde(skip_serializing_if = "Option::is_none")]
5526    pub error: Option<String>,
5527    /// Task result (if completed successfully).
5528    #[serde(skip_serializing_if = "Option::is_none")]
5529    pub result: Option<TaskResult>,
5530}
5531
5532// ============================================================================
5533// Sampling (Server-to-Client LLM requests)
5534// ============================================================================
5535
5536use crate::types::{ModelPreferences, SamplingContent, SamplingMessage};
5537
5538/// sampling/createMessage request params.
5539///
5540/// Sent from server to client to request an LLM completion.
5541#[derive(Debug, Clone, Serialize, Deserialize)]
5542pub struct CreateMessageParams {
5543    /// Conversation messages.
5544    pub messages: Vec<SamplingMessage>,
5545    /// Maximum tokens to generate, represented as an arbitrary-width JSON integer.
5546    // Avoid UBS "hardcoded secrets" heuristics while keeping the on-the-wire name.
5547    #[serde(rename = "maxTo\x6bens")]
5548    pub max_tokens: JsonInteger,
5549    /// Optional system prompt.
5550    #[serde(rename = "systemPrompt", skip_serializing_if = "Option::is_none")]
5551    pub system_prompt: Option<String>,
5552    /// Sampling temperature (0.0 to 2.0).
5553    #[serde(skip_serializing_if = "Option::is_none")]
5554    pub temperature: Option<f64>,
5555    /// Stop sequences to end generation.
5556    #[serde(
5557        rename = "stopSequences",
5558        default,
5559        skip_serializing_if = "Vec::is_empty"
5560    )]
5561    pub stop_sequences: Vec<String>,
5562    /// Model preferences/hints.
5563    #[serde(rename = "modelPreferences", skip_serializing_if = "Option::is_none")]
5564    pub model_preferences: Option<ModelPreferences>,
5565    /// Include context from MCP servers.
5566    #[serde(rename = "includeContext", skip_serializing_if = "Option::is_none")]
5567    pub include_context: Option<IncludeContext>,
5568    /// Optional provider-specific metadata.
5569    #[serde(default, skip_serializing_if = "Option::is_none")]
5570    pub metadata: Option<serde_json::Map<String, Value>>,
5571    /// Request metadata.
5572    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
5573    pub meta: Option<RequestMeta>,
5574}
5575
5576impl CreateMessageParams {
5577    /// Creates a new sampling request with default settings.
5578    #[must_use]
5579    pub fn new(messages: Vec<SamplingMessage>, max_tokens: JsonInteger) -> Self {
5580        Self {
5581            messages,
5582            max_tokens,
5583            system_prompt: None,
5584            temperature: None,
5585            stop_sequences: Vec::new(),
5586            model_preferences: None,
5587            include_context: None,
5588            metadata: None,
5589            meta: None,
5590        }
5591    }
5592
5593    /// Sets the system prompt.
5594    #[must_use]
5595    pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
5596        self.system_prompt = Some(prompt.into());
5597        self
5598    }
5599
5600    /// Sets the sampling temperature.
5601    #[must_use]
5602    pub fn with_temperature(mut self, temp: f64) -> Self {
5603        self.temperature = Some(temp);
5604        self
5605    }
5606
5607    /// Adds stop sequences.
5608    #[must_use]
5609    pub fn with_stop_sequences(mut self, sequences: Vec<String>) -> Self {
5610        self.stop_sequences = sequences;
5611        self
5612    }
5613}
5614
5615/// Context inclusion mode for sampling.
5616#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5617#[serde(rename_all = "camelCase")]
5618pub enum IncludeContext {
5619    /// Include no MCP context.
5620    None,
5621    /// Include context from the current server only.
5622    ThisServer,
5623    /// Include context from all connected MCP servers.
5624    AllServers,
5625}
5626
5627/// sampling/createMessage response result.
5628///
5629/// Returned by the client with the LLM completion.
5630#[derive(Debug, Clone, Serialize, Deserialize)]
5631pub struct CreateMessageResult {
5632    /// Generated content.
5633    pub content: SamplingContent,
5634    /// Role of the generated message (always "assistant").
5635    pub role: crate::types::Role,
5636    /// Model that was used.
5637    pub model: String,
5638    /// Optional open provider stop reason.
5639    #[serde(
5640        rename = "stopReason",
5641        default,
5642        skip_serializing_if = "Option::is_none"
5643    )]
5644    pub stop_reason: Option<String>,
5645    /// Opaque legacy result metadata preserved in its received key order.
5646    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
5647    pub meta: Option<LegacyOpaqueMetadata>,
5648}
5649
5650impl CreateMessageResult {
5651    /// Creates a new text completion result.
5652    #[must_use]
5653    pub fn text(text: impl Into<String>, model: impl Into<String>) -> Self {
5654        Self {
5655            content: SamplingContent::Text { text: text.into() },
5656            role: crate::types::Role::Assistant,
5657            model: model.into(),
5658            stop_reason: Some("endTurn".to_owned()),
5659            meta: None,
5660        }
5661    }
5662
5663    /// Sets the stop reason.
5664    #[must_use]
5665    pub fn with_stop_reason(mut self, reason: impl Into<String>) -> Self {
5666        self.stop_reason = Some(reason.into());
5667        self
5668    }
5669
5670    /// Returns the text content if this is a text response.
5671    #[must_use]
5672    pub fn text_content(&self) -> Option<&str> {
5673        match &self.content {
5674            SamplingContent::Text { text } => Some(text),
5675            SamplingContent::Image { .. } => None,
5676        }
5677    }
5678}
5679
5680// ============================================================================
5681// Roots (Client-to-Server filesystem roots)
5682// ============================================================================
5683
5684use crate::types::Root;
5685
5686/// roots/list request params.
5687///
5688/// Sent from server to client to request the list of available filesystem roots.
5689/// This request has no parameters.
5690#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5691pub struct ListRootsParams {}
5692
5693/// roots/list response result.
5694///
5695/// Returned by the client with the list of available filesystem roots.
5696#[derive(Debug, Clone, Serialize, Deserialize)]
5697pub struct ListRootsResult {
5698    /// The list of available roots.
5699    pub roots: Vec<Root>,
5700}
5701
5702impl ListRootsResult {
5703    /// Creates a new empty result.
5704    #[must_use]
5705    pub fn empty() -> Self {
5706        Self { roots: Vec::new() }
5707    }
5708
5709    /// Creates a result with the given roots.
5710    #[must_use]
5711    pub fn new(roots: Vec<Root>) -> Self {
5712        Self { roots }
5713    }
5714}
5715
5716/// Notification params for roots/list_changed.
5717///
5718/// Sent by the client when the list of roots changes.
5719/// This notification has no parameters.
5720#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5721pub struct RootsListChangedNotificationParams {}
5722
5723// ============================================================================
5724// Elicitation (Server-to-Client user input requests)
5725// ============================================================================
5726
5727/// JSON Schema for elicitation requests.
5728///
5729/// Must be an object schema with flat properties (no nesting).
5730/// Only primitive types (string, number, integer, boolean) are allowed.
5731pub type ElicitRequestedSchema = serde_json::Value;
5732
5733/// Parameters for form mode elicitation requests.
5734///
5735/// Form mode collects non-sensitive information from the user via an in-band form
5736/// rendered by the client.
5737#[derive(Debug, Clone, Serialize, Deserialize)]
5738pub struct ElicitRequestFormParams {
5739    /// The elicitation mode (always "form" for this type).
5740    pub mode: ElicitMode,
5741    /// The message to present to the user describing what information is being requested.
5742    pub message: String,
5743    /// A restricted subset of JSON Schema defining the structure of expected response.
5744    /// Only top-level properties are allowed, without nesting.
5745    #[serde(rename = "requestedSchema")]
5746    pub requested_schema: ElicitRequestedSchema,
5747}
5748
5749impl ElicitRequestFormParams {
5750    /// Creates a new form elicitation request.
5751    #[must_use]
5752    pub fn new(message: impl Into<String>, schema: serde_json::Value) -> Self {
5753        Self {
5754            mode: ElicitMode::Form,
5755            message: message.into(),
5756            requested_schema: schema,
5757        }
5758    }
5759}
5760
5761/// Parameters for URL mode elicitation requests.
5762///
5763/// URL mode directs users to external URLs for sensitive out-of-band interactions
5764/// like OAuth flows, credential collection, or payment processing.
5765#[derive(Debug, Clone, Serialize, Deserialize)]
5766pub struct ElicitRequestUrlParams {
5767    /// The elicitation mode (always "url" for this type).
5768    pub mode: ElicitMode,
5769    /// The message to present to the user explaining why the interaction is needed.
5770    pub message: String,
5771    /// The URL that the user should navigate to.
5772    pub url: String,
5773    /// The ID of the elicitation, which must be unique within the context of the server.
5774    /// The client MUST treat this ID as an opaque value.
5775    #[serde(rename = "elicitationId")]
5776    pub elicitation_id: String,
5777}
5778
5779impl ElicitRequestUrlParams {
5780    /// Creates a new URL elicitation request.
5781    #[must_use]
5782    pub fn new(
5783        message: impl Into<String>,
5784        url: impl Into<String>,
5785        elicitation_id: impl Into<String>,
5786    ) -> Self {
5787        Self {
5788            mode: ElicitMode::Url,
5789            message: message.into(),
5790            url: url.into(),
5791            elicitation_id: elicitation_id.into(),
5792        }
5793    }
5794}
5795
5796/// Elicitation mode.
5797#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5798#[serde(rename_all = "lowercase")]
5799pub enum ElicitMode {
5800    /// Form mode - collect user input via in-band form.
5801    Form,
5802    /// URL mode - redirect user to external URL.
5803    Url,
5804}
5805
5806/// Parameters for elicitation requests (either form or URL mode).
5807#[derive(Debug, Clone, Serialize, Deserialize)]
5808#[serde(untagged)]
5809pub enum ElicitRequestParams {
5810    /// Form mode elicitation.
5811    Form(ElicitRequestFormParams),
5812    /// URL mode elicitation.
5813    Url(ElicitRequestUrlParams),
5814}
5815
5816impl ElicitRequestParams {
5817    /// Creates a form mode elicitation request.
5818    #[must_use]
5819    pub fn form(message: impl Into<String>, schema: serde_json::Value) -> Self {
5820        Self::Form(ElicitRequestFormParams::new(message, schema))
5821    }
5822
5823    /// Creates a URL mode elicitation request.
5824    #[must_use]
5825    pub fn url(
5826        message: impl Into<String>,
5827        url: impl Into<String>,
5828        elicitation_id: impl Into<String>,
5829    ) -> Self {
5830        Self::Url(ElicitRequestUrlParams::new(message, url, elicitation_id))
5831    }
5832
5833    /// Returns the mode of this elicitation request.
5834    #[must_use]
5835    pub fn mode(&self) -> ElicitMode {
5836        match self {
5837            Self::Form(_) => ElicitMode::Form,
5838            Self::Url(_) => ElicitMode::Url,
5839        }
5840    }
5841
5842    /// Returns the message for this elicitation request.
5843    #[must_use]
5844    pub fn message(&self) -> &str {
5845        match self {
5846            Self::Form(f) => &f.message,
5847            Self::Url(u) => &u.message,
5848        }
5849    }
5850}
5851
5852/// User action in response to an elicitation request.
5853#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5854#[serde(rename_all = "lowercase")]
5855pub enum ElicitAction {
5856    /// User submitted the form/confirmed the action (or consented to URL navigation).
5857    Accept,
5858    /// User explicitly declined the action.
5859    Decline,
5860    /// User dismissed without making an explicit choice.
5861    Cancel,
5862}
5863
5864/// Content type for elicitation responses.
5865///
5866/// Values can be strings, integers, floats, booleans, arrays of strings, or null.
5867///
5868/// Deserialize is manual: a derived untagged decode buffers the input into
5869/// serde's Content, where an arbitrary-precision JSON number surfaces as a
5870/// magic map that neither `JsonInteger` nor `f64` variant probing could
5871/// previously classify.
5872#[derive(Debug, Clone, PartialEq, Serialize)]
5873#[serde(untagged)]
5874pub enum ElicitContentValue {
5875    /// Null value.
5876    Null,
5877    /// Boolean value.
5878    Bool(bool),
5879    /// Arbitrary-width JSON integer value.
5880    Int(JsonInteger),
5881    /// Float value.
5882    Float(f64),
5883    /// String value.
5884    String(String),
5885    /// Array of strings (for multi-select).
5886    StringArray(Vec<String>),
5887}
5888
5889impl<'de> Deserialize<'de> for ElicitContentValue {
5890    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
5891    where
5892        D: serde::Deserializer<'de>,
5893    {
5894        struct ElicitContentValueVisitor;
5895
5896        impl<'de> serde::de::Visitor<'de> for ElicitContentValueVisitor {
5897            type Value = ElicitContentValue;
5898
5899            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5900                formatter.write_str("null, a boolean, a JSON number, a string, or a string array")
5901            }
5902
5903            fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
5904                Ok(ElicitContentValue::Null)
5905            }
5906
5907            fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
5908                Ok(ElicitContentValue::Null)
5909            }
5910
5911            fn visit_bool<E: serde::de::Error>(self, value: bool) -> Result<Self::Value, E> {
5912                Ok(ElicitContentValue::Bool(value))
5913            }
5914
5915            fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Self::Value, E> {
5916                Ok(ElicitContentValue::Int(JsonInteger::from(value)))
5917            }
5918
5919            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
5920                Ok(ElicitContentValue::Int(JsonInteger::from(value)))
5921            }
5922
5923            fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
5924                Ok(ElicitContentValue::Float(value))
5925            }
5926
5927            fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
5928                Ok(ElicitContentValue::String(value.to_owned()))
5929            }
5930
5931            fn visit_string<E: serde::de::Error>(self, value: String) -> Result<Self::Value, E> {
5932                Ok(ElicitContentValue::String(value))
5933            }
5934
5935            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
5936            where
5937                A: serde::de::SeqAccess<'de>,
5938            {
5939                let mut values = Vec::new();
5940                while let Some(value) = seq.next_element::<String>()? {
5941                    values.push(value);
5942                }
5943                Ok(ElicitContentValue::StringArray(values))
5944            }
5945
5946            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
5947            where
5948                A: serde::de::MapAccess<'de>,
5949            {
5950                // The arbitrary-precision magic map is the only object shape
5951                // an elicitation value can take; classify its number lexeme
5952                // as an exact integer first, then as a float.
5953                let Some(key) = map.next_key::<std::borrow::Cow<'_, str>>()? else {
5954                    return Err(serde::de::Error::custom(
5955                        "elicitation value cannot be an object",
5956                    ));
5957                };
5958                if key != "$serde_json::private::Number" && key != "$serde_json::private::RawValue"
5959                {
5960                    return Err(serde::de::Error::custom(
5961                        "elicitation value cannot be an object",
5962                    ));
5963                }
5964                let lexeme = map.next_value::<std::borrow::Cow<'_, str>>()?;
5965                if let Ok(integer) = lexeme.parse::<JsonInteger>() {
5966                    return Ok(ElicitContentValue::Int(integer));
5967                }
5968                lexeme
5969                    .parse::<f64>()
5970                    .map(ElicitContentValue::Float)
5971                    .map_err(|_| serde::de::Error::custom("invalid elicitation number"))
5972            }
5973        }
5974
5975        deserializer.deserialize_any(ElicitContentValueVisitor)
5976    }
5977}
5978
5979impl From<bool> for ElicitContentValue {
5980    fn from(v: bool) -> Self {
5981        Self::Bool(v)
5982    }
5983}
5984
5985impl From<i64> for ElicitContentValue {
5986    fn from(v: i64) -> Self {
5987        Self::Int(JsonInteger::from(v))
5988    }
5989}
5990
5991impl From<JsonInteger> for ElicitContentValue {
5992    fn from(v: JsonInteger) -> Self {
5993        Self::Int(v)
5994    }
5995}
5996
5997impl From<f64> for ElicitContentValue {
5998    fn from(v: f64) -> Self {
5999        Self::Float(v)
6000    }
6001}
6002
6003impl From<String> for ElicitContentValue {
6004    fn from(v: String) -> Self {
6005        Self::String(v)
6006    }
6007}
6008
6009impl From<&str> for ElicitContentValue {
6010    fn from(v: &str) -> Self {
6011        Self::String(v.to_owned())
6012    }
6013}
6014
6015impl From<Vec<String>> for ElicitContentValue {
6016    fn from(v: Vec<String>) -> Self {
6017        Self::StringArray(v)
6018    }
6019}
6020
6021impl<T: Into<ElicitContentValue>> From<Option<T>> for ElicitContentValue {
6022    fn from(v: Option<T>) -> Self {
6023        match v {
6024            Some(v) => v.into(),
6025            None => Self::Null,
6026        }
6027    }
6028}
6029
6030/// elicitation/create response result.
6031///
6032/// The client's response to an elicitation request.
6033#[derive(Debug, Clone, Serialize, Deserialize)]
6034pub struct ElicitResult {
6035    /// The user action in response to the elicitation.
6036    pub action: ElicitAction,
6037    /// The submitted form data, only present when action is "accept" in form mode.
6038    /// Contains values matching the requested schema.
6039    /// For URL mode, this field is omitted.
6040    #[serde(skip_serializing_if = "Option::is_none")]
6041    pub content: Option<std::collections::HashMap<String, ElicitContentValue>>,
6042}
6043
6044impl ElicitResult {
6045    /// Creates an accept result with form data.
6046    #[must_use]
6047    pub fn accept(content: std::collections::HashMap<String, ElicitContentValue>) -> Self {
6048        Self {
6049            action: ElicitAction::Accept,
6050            content: Some(content),
6051        }
6052    }
6053
6054    /// Creates an accept result for URL mode (no content).
6055    #[must_use]
6056    pub fn accept_url() -> Self {
6057        Self {
6058            action: ElicitAction::Accept,
6059            content: None,
6060        }
6061    }
6062
6063    /// Creates a decline result.
6064    #[must_use]
6065    pub fn decline() -> Self {
6066        Self {
6067            action: ElicitAction::Decline,
6068            content: None,
6069        }
6070    }
6071
6072    /// Creates a cancel result.
6073    #[must_use]
6074    pub fn cancel() -> Self {
6075        Self {
6076            action: ElicitAction::Cancel,
6077            content: None,
6078        }
6079    }
6080
6081    /// Returns true if the user accepted the elicitation.
6082    #[must_use]
6083    pub fn is_accepted(&self) -> bool {
6084        matches!(self.action, ElicitAction::Accept)
6085    }
6086
6087    /// Returns true if the user declined the elicitation.
6088    #[must_use]
6089    pub fn is_declined(&self) -> bool {
6090        matches!(self.action, ElicitAction::Decline)
6091    }
6092
6093    /// Returns true if the user cancelled the elicitation.
6094    #[must_use]
6095    pub fn is_cancelled(&self) -> bool {
6096        matches!(self.action, ElicitAction::Cancel)
6097    }
6098
6099    /// Gets a string value from the content.
6100    #[must_use]
6101    pub fn get_string(&self, key: &str) -> Option<&str> {
6102        self.content.as_ref().and_then(|c| {
6103            c.get(key).and_then(|v| match v {
6104                ElicitContentValue::String(s) => Some(s.as_str()),
6105                _ => None,
6106            })
6107        })
6108    }
6109
6110    /// Gets a boolean value from the content.
6111    #[must_use]
6112    pub fn get_bool(&self, key: &str) -> Option<bool> {
6113        self.content.as_ref().and_then(|c| {
6114            c.get(key).and_then(|v| match v {
6115                ElicitContentValue::Bool(b) => Some(*b),
6116                _ => None,
6117            })
6118        })
6119    }
6120
6121    /// Gets the exact JSON integer value from the content.
6122    #[must_use]
6123    pub fn get_int(&self, key: &str) -> Option<&JsonInteger> {
6124        self.content.as_ref().and_then(|c| {
6125            c.get(key).and_then(|v| match v {
6126                ElicitContentValue::Int(i) => Some(i),
6127                _ => None,
6128            })
6129        })
6130    }
6131}
6132
6133/// Elicitation complete notification params.
6134///
6135/// Sent from server to client when a URL mode elicitation has been completed.
6136#[derive(Debug, Clone, Serialize, Deserialize)]
6137pub struct ElicitCompleteNotificationParams {
6138    /// The unique identifier of the elicitation that was completed.
6139    #[serde(rename = "elicitationId")]
6140    pub elicitation_id: String,
6141}
6142
6143impl ElicitCompleteNotificationParams {
6144    /// Creates a new elicitation complete notification.
6145    #[must_use]
6146    pub fn new(elicitation_id: impl Into<String>) -> Self {
6147        Self {
6148            elicitation_id: elicitation_id.into(),
6149        }
6150    }
6151}
6152
6153/// Error data for URL elicitation required errors.
6154///
6155/// Servers return this when a request cannot be processed until one or more
6156/// URL mode elicitations are completed.
6157#[derive(Debug, Clone, Serialize, Deserialize)]
6158pub struct ElicitationRequiredErrorData {
6159    /// List of URL mode elicitations that must be completed.
6160    pub elicitations: Vec<ElicitRequestUrlParams>,
6161}
6162
6163#[cfg(test)]
6164mod tests {
6165    use super::*;
6166    use crate::ResultMeta;
6167    use crate::types::PROTOCOL_VERSION;
6168
6169    const PROGRESS_MARKER_KEY: &str = "progressTo\x6ben";
6170    const MAX_TOKENS_KEY: &str = "maxTo\x6bens";
6171
6172    // ========================================================================
6173    // ProgressMarker Tests
6174    // ========================================================================
6175
6176    #[test]
6177    fn progress_marker_string_serialization() {
6178        let progress = ProgressMarker::String("progress_value_test_1".to_string());
6179        let value = serde_json::to_value(&progress).expect("serialize");
6180        assert_eq!(value, "progress_value_test_1");
6181    }
6182
6183    #[test]
6184    fn progress_marker_number_serialization() {
6185        let progress = ProgressMarker::Number(JsonInteger::from(42_i64));
6186        let value = serde_json::to_value(&progress).expect("serialize");
6187        assert_eq!(value, 42);
6188    }
6189
6190    #[test]
6191    fn progress_marker_integer_preserves_arbitrary_width_and_rejects_fractional_values() {
6192        let accepted_wire = "922337203685477580812345678901234567890";
6193        let accepted: ProgressMarker =
6194            serde_json::from_str(accepted_wire).expect("arbitrary-width progress marker parses");
6195        assert!(matches!(
6196            &accepted,
6197            ProgressMarker::Number(value)
6198                if value.as_str() == "922337203685477580812345678901234567890"
6199        ));
6200        assert_eq!(
6201            serde_json::to_string(&accepted).expect("arbitrary-width progress marker encodes"),
6202            accepted_wire,
6203            "the exact integer progress marker lexeme round-trips"
6204        );
6205
6206        assert!(
6207            serde_json::from_str::<ProgressMarker>("922337203685477580812345678901234567890.5")
6208                .is_err(),
6209            "changing only the integer progress marker to a fractional number rejects it"
6210        );
6211    }
6212
6213    #[test]
6214    fn progress_marker_from_impls() {
6215        let from_str: ProgressMarker = "progress".into();
6216        assert!(matches!(from_str, ProgressMarker::String(_)));
6217
6218        let from_string: ProgressMarker = "progress".to_string().into();
6219        assert!(matches!(from_string, ProgressMarker::String(_)));
6220
6221        let from_i64: ProgressMarker = 99i64.into();
6222        assert!(matches!(from_i64, ProgressMarker::Number(value) if value.as_str() == "99"));
6223    }
6224
6225    #[test]
6226    fn progress_marker_display() {
6227        assert_eq!(
6228            format!(
6229                "{}",
6230                ProgressMarker::String("progress_value_test_1".to_string())
6231            ),
6232            "progress_value_test_1"
6233        );
6234        assert_eq!(
6235            format!("{}", ProgressMarker::Number(JsonInteger::from(42_i64))),
6236            "42"
6237        );
6238    }
6239
6240    #[test]
6241    fn progress_marker_equality() {
6242        assert_eq!(
6243            ProgressMarker::Number(JsonInteger::from(1_i64)),
6244            ProgressMarker::Number(JsonInteger::from(1_i64))
6245        );
6246        assert_ne!(
6247            ProgressMarker::Number(JsonInteger::from(1_i64)),
6248            ProgressMarker::Number(JsonInteger::from(2_i64))
6249        );
6250        assert_eq!(
6251            ProgressMarker::String("a".to_string()),
6252            ProgressMarker::String("a".to_string())
6253        );
6254    }
6255
6256    // ========================================================================
6257    // RequestMeta Tests
6258    // ========================================================================
6259
6260    #[test]
6261    fn request_meta_default_empty() {
6262        let meta = RequestMeta::default();
6263        let value = serde_json::to_value(&meta).expect("serialize");
6264        assert_eq!(value, serde_json::json!({}));
6265    }
6266
6267    #[test]
6268    fn request_meta_with_marker() {
6269        let meta = RequestMeta {
6270            progress_marker: Some(ProgressMarker::String("progress_value_test_2".to_string())),
6271        };
6272        let value = serde_json::to_value(&meta).expect("serialize");
6273        assert_eq!(value[PROGRESS_MARKER_KEY], "progress_value_test_2");
6274    }
6275
6276    #[test]
6277    fn final_request_meta_preserves_namespaced_capabilities_and_inert_metadata() {
6278        let mut meta = FinalRequestMeta::new(ClientCapabilities {
6279            roots: Some(crate::types::RootsCapability { list_changed: true }),
6280            ..ClientCapabilities::default()
6281        });
6282        meta.additional_metadata
6283            .insert("example.com/trace".to_owned(), serde_json::json!(null));
6284
6285        let wire = serde_json::to_value(&meta).expect("final metadata serializes");
6286        assert_eq!(
6287            wire[FINAL_PROTOCOL_VERSION_META_KEY],
6288            FINAL_PROTOCOL_VERSION
6289        );
6290        assert_eq!(
6291            wire[FINAL_CLIENT_CAPABILITIES_META_KEY]["roots"]["listChanged"],
6292            true
6293        );
6294        assert_eq!(wire["example.com/trace"], serde_json::json!(null));
6295
6296        let round_trip: FinalRequestMeta =
6297            serde_json::from_value(wire).expect("final metadata deserializes");
6298        assert_eq!(
6299            round_trip
6300                .version_metadata(Some(FINAL_PROTOCOL_VERSION))
6301                .body_version,
6302            Some(FINAL_PROTOCOL_VERSION)
6303        );
6304        assert_eq!(
6305            round_trip.additional_metadata.get("example.com/trace"),
6306            Some(&serde_json::json!(null))
6307        );
6308    }
6309
6310    #[test]
6311    fn legacy_sampling_core_and_final_mrtr_sampling_wires_remain_disjoint() {
6312        #[cfg(feature = "legacy-2024-11-05")]
6313        {
6314            let legacy_params = serde_json::json!({
6315                "messages": [{"role": "user", "content": {"type": "text", "text": "summarize"}}],
6316                "maxTokens": 32,
6317                "metadata": {"provider": "legacy"}
6318            });
6319            let legacy = CoreRequest::decode(
6320                ProtocolEra::Legacy2024,
6321                SAMPLING_CREATE_MESSAGE,
6322                Some(&legacy_params),
6323            )
6324            .expect("legacy sampling is a direct reverse RPC");
6325            assert_eq!(legacy.method(), SAMPLING_CREATE_MESSAGE);
6326            assert_eq!(
6327                legacy
6328                    .encode_params()
6329                    .expect("legacy sampling parameters encode")
6330                    .expect("legacy sampling owns parameters"),
6331                legacy_params
6332            );
6333            let legacy_result_wire = r#"{"content":{"type":"text","text":"summary"},"role":"assistant","model":"legacy-model","stopReason":"endTurn","_meta":{"trace":"legacy"}}"#;
6334            let legacy_result = legacy
6335                .decode_result(legacy_result_wire)
6336                .expect("legacy sampling result is typed");
6337            assert!(matches!(
6338                legacy_result,
6339                CoreResult::Legacy(LegacyCoreResult::SamplingCreateMessage(_))
6340            ));
6341            assert_eq!(
6342                serde_json::from_str::<Value>(
6343                    &legacy_result
6344                        .encode()
6345                        .expect("legacy sampling result encodes"),
6346                )
6347                .expect("legacy sampling encoding is JSON"),
6348                serde_json::from_str::<Value>(legacy_result_wire)
6349                    .expect("legacy sampling fixture is JSON"),
6350                "legacy sampling preserves decoded result semantics without asserting member order"
6351            );
6352        }
6353
6354        let final_params_wire = serde_json::json!({
6355            "_meta": {
6356                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
6357                "io.modelcontextprotocol/clientCapabilities": {}
6358            },
6359            "messages": [{
6360                "role": "assistant",
6361                "content": {"type": "tool_use", "id": "call-1", "name": "weather", "input": {"city": "Boston"}}
6362            }],
6363            "maxTokens": 32,
6364            "toolChoice": {"mode": "required"}
6365        });
6366        let final_params: FinalCreateMessageParams =
6367            serde_json::from_value(final_params_wire.clone())
6368                .expect("final sampling is reusable as an MRTR input request");
6369        assert_eq!(
6370            serde_json::to_value(&final_params).expect("final sampling parameters encode"),
6371            final_params_wire
6372        );
6373        let final_result_wire = serde_json::json!({
6374            "content": {"type": "tool_result", "toolUseId": "call-1", "content": [{"type": "text", "text": "sunny"}]},
6375            "model": "final-model",
6376            "role": "assistant",
6377            "stopReason": "toolUse",
6378            "_meta": {"com.example/retryTrace": {"attempt": 2}}
6379        });
6380        let final_result: FinalCreateMessageResult =
6381            serde_json::from_value(final_result_wire.clone())
6382                .expect("final sampling complete payload is typed");
6383        assert_eq!(
6384            serde_json::to_value(&final_result).expect("final sampling complete encodes"),
6385            final_result_wire
6386        );
6387        assert!(
6388            serde_json::to_value(&final_result)
6389                .expect("final sampling complete encodes")
6390                .get("resultType")
6391                .is_none(),
6392            "embedded input responses are not JSON-RPC result envelopes"
6393        );
6394        let mut planted_result_type = final_result_wire.clone();
6395        planted_result_type["resultType"] = serde_json::json!("complete");
6396        assert!(
6397            serde_json::from_value::<FinalCreateMessageResult>(planted_result_type).is_err(),
6398            "only adding an envelope resultType must reject the embedded MRTR response"
6399        );
6400        assert_eq!(
6401            serde_json::to_value(&final_result).expect("accepted embedded result is unchanged"),
6402            final_result_wire,
6403            "rejecting a resultType does not alter the admitted result value"
6404        );
6405        let input_required_wire = serde_json::json!({
6406            "resultType": "input_required",
6407            "inputRequests": {},
6408            "requestState": "retry-1"
6409        });
6410        let input_required: FinalCreateMessageInputRequiredResult =
6411            serde_json::from_value(input_required_wire.clone())
6412                .expect("final input-required discriminator is exact");
6413        input_required
6414            .validate()
6415            .expect("input-required retains at least one retry input dimension");
6416        assert_eq!(
6417            serde_json::to_value(input_required).expect("input-required encodes"),
6418            input_required_wire
6419        );
6420
6421        assert!(matches!(
6422            CoreRequest::decode(
6423                ProtocolEra::Modern2026,
6424                SAMPLING_CREATE_MESSAGE,
6425                Some(&final_params_wire)
6426            ),
6427            Err(CoreDispatchError::UnsupportedMethod {
6428                era: ProtocolEra::Modern2026,
6429                method,
6430            }) if method == SAMPLING_CREATE_MESSAGE
6431        ));
6432    }
6433
6434    #[test]
6435    fn legacy_sampling_params_preserve_huge_signed_max_tokens() {
6436        for (wire, expected_max_tokens) in [
6437            (
6438                r#"{"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":922337203685477580812345678901234567890}"#,
6439                "922337203685477580812345678901234567890",
6440            ),
6441            (
6442                r#"{"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":-922337203685477580812345678901234567890}"#,
6443                "-922337203685477580812345678901234567890",
6444            ),
6445        ] {
6446            let params: CreateMessageParams =
6447                serde_json::from_str(wire).expect("huge signed maxTokens is an exact JSON integer");
6448            assert_eq!(params.max_tokens.as_str(), expected_max_tokens);
6449            assert_eq!(
6450                serde_json::to_string(&params).expect("huge signed maxTokens serializes"),
6451                wire,
6452                "the exact {expected_max_tokens} spelling round-trips"
6453            );
6454        }
6455    }
6456
6457    #[test]
6458    fn legacy_sampling_params_reject_fractional_huge_max_tokens_one_variable_mutation() {
6459        let fractional = r#"{"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":-922337203685477580812345678901234567890.5}"#;
6460        assert!(
6461            serde_json::from_str::<CreateMessageParams>(fractional).is_err(),
6462            "changing only maxTokens from a huge signed integer to a fraction must reject"
6463        );
6464    }
6465
6466    #[test]
6467    fn final_sampling_params_preserve_huge_signed_max_tokens() {
6468        for (wire, expected_max_tokens) in [
6469            (
6470                r#"{"_meta":{},"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":922337203685477580812345678901234567890}"#,
6471                "922337203685477580812345678901234567890",
6472            ),
6473            (
6474                r#"{"_meta":{},"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":-922337203685477580812345678901234567890}"#,
6475                "-922337203685477580812345678901234567890",
6476            ),
6477        ] {
6478            let params: FinalCreateMessageParams = serde_json::from_str(wire)
6479                .expect("huge signed final maxTokens is an exact JSON integer");
6480            assert_eq!(params.max_tokens.as_str(), expected_max_tokens);
6481            assert!(
6482                serde_json::to_string(&params)
6483                    .expect("huge signed final maxTokens serializes")
6484                    .contains(&format!("\"maxTokens\":{expected_max_tokens}")),
6485                "the exact {expected_max_tokens} spelling round-trips"
6486            );
6487        }
6488
6489        for (wire, expected_max_tokens) in [
6490            (
6491                r#"{"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":922337203685477580812345678901234567890}"#,
6492                "922337203685477580812345678901234567890",
6493            ),
6494            (
6495                r#"{"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":-922337203685477580812345678901234567890}"#,
6496                "-922337203685477580812345678901234567890",
6497            ),
6498        ] {
6499            let params: FinalEmbeddedCreateMessageParams = serde_json::from_str(wire)
6500                .expect("huge signed embedded final maxTokens is an exact JSON integer");
6501            assert_eq!(params.max_tokens.as_str(), expected_max_tokens);
6502            assert!(
6503                serde_json::to_string(&params)
6504                    .expect("huge signed embedded final maxTokens serializes")
6505                    .contains(&format!("\"maxTokens\":{expected_max_tokens}")),
6506                "the exact embedded {expected_max_tokens} spelling round-trips"
6507            );
6508        }
6509    }
6510
6511    #[test]
6512    fn final_sampling_params_reject_fractional_huge_max_tokens_one_variable_mutation() {
6513        let final_fractional = r#"{"_meta":{},"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":922337203685477580812345678901234567890.5}"#;
6514        assert!(
6515            serde_json::from_str::<FinalCreateMessageParams>(final_fractional).is_err(),
6516            "changing only final maxTokens from a huge integer to a fraction must reject"
6517        );
6518
6519        let embedded_fractional = r#"{"messages":[{"role":"user","content":{"type":"text","text":"summarize"}}],"maxTokens":922337203685477580812345678901234567890.5}"#;
6520        assert!(
6521            serde_json::from_str::<FinalEmbeddedCreateMessageParams>(embedded_fractional).is_err(),
6522            "changing only embedded final maxTokens from a huge integer to a fraction must reject"
6523        );
6524    }
6525
6526    #[test]
6527    fn final_embedded_input_response_equality_covers_each_typed_response() {
6528        let sampling: FinalEmbeddedInputResponse = serde_json::from_value(serde_json::json!({
6529            "content": {"type": "text", "text": "summary"},
6530            "model": "final-model",
6531            "role": "assistant",
6532            "_meta": {"com.example/trace": {"attempt": 1}}
6533        }))
6534        .expect("final sampling response decodes");
6535        let roots: FinalEmbeddedInputResponse = serde_json::from_value(serde_json::json!({
6536            "roots": [{"uri": "file:///workspace", "name": "workspace"}]
6537        }))
6538        .expect("final roots response decodes");
6539        let elicitation: FinalEmbeddedInputResponse = serde_json::from_value(serde_json::json!({
6540            "action": "accept",
6541            "content": {"choice": "yes", "attempt": 1}
6542        }))
6543        .expect("final elicitation response decodes");
6544
6545        assert_eq!(sampling, sampling.clone());
6546        assert_eq!(roots, roots.clone());
6547        assert_eq!(elicitation, elicitation.clone());
6548        assert_ne!(sampling, roots);
6549        assert_ne!(roots, elicitation);
6550    }
6551
6552    #[test]
6553    fn final_notification_unions_round_trip_the_exact_client_and_server_members() {
6554        let client_wire = JsonRpcRequest::notification(
6555            NOTIFICATIONS_CANCELLED,
6556            Some(serde_json::json!({
6557                "requestId": "client-request-7",
6558                "reason": "client no longer needs this response",
6559                "awaitCleanup": true,
6560                "com.example/cancellationTrace": {"attempt": 2}
6561            })),
6562        );
6563        let client = ClientNotification::decode(&client_wire)
6564            .expect("the final client union admits its cancellation notification");
6565        assert_eq!(client.method(), NOTIFICATIONS_CANCELLED);
6566        assert!(client_wire.is_notification());
6567        let ClientNotification::Cancelled(params) = &client;
6568        assert_eq!(
6569            params.additional.get("awaitCleanup"),
6570            Some(&serde_json::json!(true)),
6571            "schema-open cancellation members retain legacy-looking names as opaque data"
6572        );
6573        assert_eq!(
6574            serde_json::to_value(client.encode().expect("client notification re-encodes"))
6575                .expect("client notification remains JSON"),
6576            serde_json::to_value(&client_wire).expect("client notification wire remains JSON")
6577        );
6578
6579        let server_wires = [
6580            JsonRpcRequest::notification(
6581                NOTIFICATIONS_CANCELLED,
6582                Some(serde_json::json!({
6583                    "requestId": "subscription-9",
6584                    "com.example/cancellationTrace": "stream-close"
6585                })),
6586            ),
6587            JsonRpcRequest::notification(
6588                NOTIFICATIONS_PROGRESS,
6589                Some(serde_json::json!({
6590                    "progressToken": "job-9",
6591                    "progress": 1.0,
6592                    "total": 2.0,
6593                    "message": "halfway",
6594                    "com.example/progressPhase": "indexing"
6595                })),
6596            ),
6597            JsonRpcRequest::notification(
6598                NOTIFICATIONS_MESSAGE,
6599                Some(serde_json::json!({
6600                    "level": "notice",
6601                    "logger": "discovery-server",
6602                    "data": {"event": "catalog-refreshed"},
6603                    "com.example/logTrace": 7
6604                })),
6605            ),
6606            JsonRpcRequest::notification(
6607                NOTIFICATIONS_RESOURCES_UPDATED,
6608                Some(serde_json::json!({
6609                    "uri": "file:///workspace/status",
6610                    "com.example/resourceRevision": 4
6611                })),
6612            ),
6613            JsonRpcRequest::notification(
6614                NOTIFICATIONS_RESOURCES_LIST_CHANGED,
6615                Some(serde_json::json!({"com.example/listRevision": 8})),
6616            ),
6617            JsonRpcRequest::notification(
6618                NOTIFICATIONS_TOOLS_LIST_CHANGED,
6619                Some(serde_json::json!({
6620                    "_meta": {"com.example/trace": "tools-4"},
6621                    "com.example/listRevision": 9
6622                })),
6623            ),
6624            JsonRpcRequest::notification(NOTIFICATIONS_PROMPTS_LIST_CHANGED, None),
6625            JsonRpcRequest::notification(
6626                NOTIFICATIONS_SUBSCRIPTIONS_ACKNOWLEDGED,
6627                Some(serde_json::json!({
6628                    "notifications": {"toolsListChanged": true},
6629                    "com.example/acknowledgement": {"accepted": true}
6630                })),
6631            ),
6632        ];
6633        let expected_methods = [
6634            NOTIFICATIONS_CANCELLED,
6635            NOTIFICATIONS_PROGRESS,
6636            NOTIFICATIONS_MESSAGE,
6637            NOTIFICATIONS_RESOURCES_UPDATED,
6638            NOTIFICATIONS_RESOURCES_LIST_CHANGED,
6639            NOTIFICATIONS_TOOLS_LIST_CHANGED,
6640            NOTIFICATIONS_PROMPTS_LIST_CHANGED,
6641            NOTIFICATIONS_SUBSCRIPTIONS_ACKNOWLEDGED,
6642        ];
6643
6644        for (wire, expected_method) in server_wires.iter().zip(expected_methods) {
6645            let notification = ServerNotification::decode(wire)
6646                .expect("every exact final server notification member is admitted");
6647            assert_eq!(notification.method(), expected_method);
6648            assert!(wire.is_notification());
6649            assert_eq!(
6650                serde_json::to_value(
6651                    notification
6652                        .encode()
6653                        .expect("server notification re-encodes")
6654                )
6655                .expect("server notification remains JSON"),
6656                serde_json::to_value(wire).expect("server notification wire remains JSON"),
6657                "{expected_method} preserves its exact notification parameter shape"
6658            );
6659        }
6660    }
6661
6662    #[test]
6663    fn final_log_message_omits_an_absent_logger_and_rejects_explicit_null() {
6664        let absent = JsonRpcRequest::notification(
6665            NOTIFICATIONS_MESSAGE,
6666            Some(serde_json::json!({
6667                "level": "notice",
6668                "data": {"message": "catalog refreshed"}
6669            })),
6670        );
6671        let admitted = ServerNotification::decode(&absent)
6672            .expect("a final log message without a logger is admitted");
6673        let ServerNotification::Message(params) = &admitted else {
6674            panic!("final log message decodes to the message variant");
6675        };
6676        assert_eq!(params.logger, None);
6677        assert_eq!(
6678            serde_json::to_value(admitted.encode().expect("admitted log message re-encodes"))
6679                .expect("admitted log message remains JSON"),
6680            serde_json::to_value(&absent).expect("absent-logger message remains JSON"),
6681            "an absent final logger remains absent when the notification re-encodes"
6682        );
6683
6684        let empty = JsonRpcRequest::notification(
6685            NOTIFICATIONS_MESSAGE,
6686            Some(serde_json::json!({
6687                "level": "notice",
6688                "logger": "",
6689                "data": {"message": "catalog refreshed"}
6690            })),
6691        );
6692        let empty = ServerNotification::decode(&empty)
6693            .expect("an empty final logger is a valid string value");
6694        let ServerNotification::Message(empty_params) = &empty else {
6695            panic!("empty logger decodes to the final message variant");
6696        };
6697        assert_eq!(empty_params.logger.as_deref(), Some(""));
6698        assert_eq!(
6699            serde_json::to_value(empty.encode().expect("empty logger re-encodes"))
6700                .expect("empty logger notification remains JSON")["params"]["logger"],
6701            ""
6702        );
6703
6704        let explicit_null = JsonRpcRequest::notification(
6705            NOTIFICATIONS_MESSAGE,
6706            Some(serde_json::json!({
6707                "level": "notice",
6708                "logger": null,
6709                "data": {"message": "catalog refreshed"}
6710            })),
6711        );
6712        assert!(
6713            matches!(
6714                ServerNotification::decode(&explicit_null),
6715                Err(FinalNotificationError::InvalidParams {
6716                    method: NOTIFICATIONS_MESSAGE
6717                })
6718            ),
6719            "an explicit null final logger is invalid"
6720        );
6721
6722        let outbound_missing_logger = FinalLogMessageParams {
6723            level: LoggingLevel::Notice,
6724            logger: None,
6725            data: serde_json::json!({"message": "catalog refreshed"}),
6726            meta: None,
6727            additional: BTreeMap::new(),
6728        };
6729        assert_eq!(
6730            serde_json::to_value(outbound_missing_logger)
6731                .expect("an absent logger serializes as an omitted member"),
6732            serde_json::json!({
6733                "level": "notice",
6734                "data": {"message": "catalog refreshed"}
6735            })
6736        );
6737    }
6738
6739    #[test]
6740    fn final_progress_raw_params_preserve_large_decimal_and_exponent_lexemes() {
6741        let large_wire = r#"{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"job-large","progress":123456789012345678901234567890}}"#;
6742        let large_request: JsonRpcRequest =
6743            serde_json::from_str(large_wire).expect("large exact progress notification parses");
6744        let large_params =
6745            r#"{"progressToken":"job-large","progress":123456789012345678901234567890}"#;
6746        let large_notification =
6747            ServerNotification::decode_with_raw_params(&large_request, large_params)
6748                .expect("large exact progress notification is admitted with its raw parameters");
6749        let ServerNotification::Progress(large_params) = &large_notification else {
6750            panic!("progress method decodes to the progress notification variant");
6751        };
6752        assert_eq!(
6753            large_params.progress.as_str(),
6754            "123456789012345678901234567890",
6755            "the large integer progress lexeme is retained without an IEEE-754 conversion"
6756        );
6757        assert_eq!(
6758            large_notification
6759                .encode_wire()
6760                .expect("large progress re-encodes"),
6761            large_wire,
6762            "the large integer progress lexeme round-trips exactly"
6763        );
6764
6765        let equivalent_wire = r#"{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"job-decimal","progress":1.20e+4,"total":12000.0}}"#;
6766        let equivalent_request: JsonRpcRequest = serde_json::from_str(equivalent_wire)
6767            .expect("decimal/exponent exact progress notification parses");
6768        let equivalent_params =
6769            r#"{"progressToken":"job-decimal","progress":1.20e+4,"total":12000.0}"#;
6770        let equivalent_notification =
6771            ServerNotification::decode_with_raw_params(&equivalent_request, equivalent_params)
6772                .expect("numerically equal decimal and exponent progress fields are admitted");
6773        let ServerNotification::Progress(equivalent_params) = &equivalent_notification else {
6774            panic!("progress method decodes to the progress notification variant");
6775        };
6776        assert_eq!(equivalent_params.progress.as_str(), "1.20e+4");
6777        assert_eq!(
6778            equivalent_params
6779                .total
6780                .as_ref()
6781                .map(ExactNonNegativeJsonNumber::as_str),
6782            Some("12000.0")
6783        );
6784        assert_eq!(
6785            equivalent_params.total.as_ref(),
6786            Some(&equivalent_params.progress)
6787        );
6788        assert_eq!(
6789            equivalent_notification
6790                .encode_wire()
6791                .expect("equivalent progress re-encodes"),
6792            equivalent_wire,
6793            "equivalent decimal/exponent values retain their individual wire lexemes"
6794        );
6795    }
6796
6797    #[test]
6798    fn final_progress_admits_finite_negative_and_greater_than_total_values() {
6799        let baseline_params =
6800            r#"{"progressToken":"job-ordered","progress":1.20e+4,"total":12000.0}"#;
6801        let baseline_wire = format!(
6802            r#"{{"jsonrpc":"2.0","method":"notifications/progress","params":{baseline_params}}}"#
6803        );
6804        let baseline: JsonRpcRequest =
6805            serde_json::from_str(&baseline_wire).expect("baseline progress notification parses");
6806        let admitted = ServerNotification::decode_with_raw_params(&baseline, baseline_params)
6807            .expect("equal exact progress and total values form the baseline");
6808        let baseline_wire = serde_json::to_value(&baseline).expect("baseline progress serializes");
6809
6810        let negative_params = r#"{"progressToken":"job-ordered","progress":-1,"total":12000.0}"#;
6811        let negative: JsonRpcRequest = serde_json::from_str(&format!(
6812            r#"{{"jsonrpc":"2.0","method":"notifications/progress","params":{negative_params}}}"#
6813        ))
6814        .expect("one-variable negative progress notification parses");
6815        let negative = ServerNotification::decode_with_raw_params(&negative, negative_params)
6816            .expect("negative finite progress is admitted");
6817        let ServerNotification::Progress(negative_params) = negative else {
6818            panic!("negative final progress notification decodes to the progress variant");
6819        };
6820        assert_eq!(negative_params.progress.as_str(), "-1");
6821        assert_eq!(
6822            negative_params
6823                .total
6824                .as_ref()
6825                .map(ExactNonNegativeJsonNumber::as_str),
6826            Some("12000.0")
6827        );
6828
6829        let negative_total_params =
6830            r#"{"progressToken":"job-ordered","progress":1.20e+4,"total":-2}"#;
6831        let negative_total: JsonRpcRequest = serde_json::from_str(&format!(
6832            r#"{{"jsonrpc":"2.0","method":"notifications/progress","params":{negative_total_params}}}"#
6833        ))
6834        .expect("one-variable negative total progress notification parses");
6835        let negative_total =
6836            ServerNotification::decode_with_raw_params(&negative_total, negative_total_params)
6837                .expect("negative finite total is admitted");
6838        let ServerNotification::Progress(negative_total_params) = negative_total else {
6839            panic!("negative-total final progress notification decodes to the progress variant");
6840        };
6841        assert_eq!(
6842            negative_total_params
6843                .total
6844                .as_ref()
6845                .map(ExactNonNegativeJsonNumber::as_str),
6846            Some("-2")
6847        );
6848
6849        let greater_than_total_params =
6850            r#"{"progressToken":"job-ordered","progress":1.20e+4,"total":11999.0}"#;
6851        let greater_than_total: JsonRpcRequest = serde_json::from_str(&format!(
6852            r#"{{"jsonrpc":"2.0","method":"notifications/progress","params":{greater_than_total_params}}}"#
6853        ))
6854        .expect("one-variable greater-than-total progress notification parses");
6855        let greater_than_total = ServerNotification::decode_with_raw_params(
6856            &greater_than_total,
6857            greater_than_total_params,
6858        )
6859        .expect("finite final progress greater than its total is admitted");
6860        let ServerNotification::Progress(greater_than_total_params) = greater_than_total else {
6861            panic!("greater-than-total notification decodes to the progress variant");
6862        };
6863        assert!(
6864            greater_than_total_params.progress
6865                > *greater_than_total_params
6866                    .total
6867                    .as_ref()
6868                    .expect("greater-than-total notification retains total"),
6869            "the admitted final values retain their unconstrained numeric relationship"
6870        );
6871        assert_eq!(
6872            serde_json::to_value(admitted.encode().expect("baseline progress re-encodes"))
6873                .expect("baseline progress JSON serializes"),
6874            baseline_wire,
6875            "admitting unconstrained finite values cannot mutate the exact progress baseline"
6876        );
6877    }
6878
6879    #[test]
6880    fn legacy_progress_params_remain_the_separate_2024_f64_surface() {
6881        let legacy: ProgressParams =
6882            serde_json::from_str(r#"{"progressToken":"legacy-job","progress":-1.5,"total":2.0}"#)
6883                .expect("legacy progress remains governed by its existing f64 decoder");
6884
6885        assert!((legacy.progress + 1.5).abs() < f64::EPSILON);
6886        assert!(
6887            legacy
6888                .total
6889                .is_some_and(|total| (total - 2.0).abs() < f64::EPSILON)
6890        );
6891    }
6892
6893    #[test]
6894    fn final_notification_unions_reject_wrong_direction_and_malformed_field() {
6895        let progress = JsonRpcRequest::notification(
6896            NOTIFICATIONS_PROGRESS,
6897            Some(serde_json::json!({"progressToken": "job-9", "progress": 1.0})),
6898        );
6899        let progress_wire = serde_json::to_value(&progress).expect("progress wire serializes");
6900        assert!(
6901            matches!(
6902                ClientNotification::decode(&progress),
6903                Err(FinalNotificationError::WrongDirection { method, sender: Final2026Peer::Client })
6904                    if method == NOTIFICATIONS_PROGRESS
6905            ),
6906            "only the originating peer changes: client admission rejects server-only progress"
6907        );
6908        assert_eq!(
6909            serde_json::to_value(&progress).expect("rejected progress remains serializable"),
6910            progress_wire,
6911            "wrong-direction rejection leaves the original notification wire unchanged"
6912        );
6913
6914        let cancellation = JsonRpcRequest::notification(
6915            NOTIFICATIONS_CANCELLED,
6916            Some(serde_json::json!({
6917                "requestId": "client-request-7",
6918                "awaitCleanup": true
6919            })),
6920        );
6921        let admitted = ClientNotification::decode(&cancellation)
6922            .expect("final cancellation preserves schema-open additional fields");
6923        let accepted_wire = serde_json::to_value(&cancellation).expect("accepted wire serializes");
6924        let mut planted = cancellation.clone();
6925        planted
6926            .params
6927            .as_mut()
6928            .and_then(Value::as_object_mut)
6929            .expect("cancellation owns object parameters")
6930            .insert("requestId".to_owned(), Value::Null);
6931        assert!(
6932            matches!(
6933                ClientNotification::decode(&planted),
6934                Err(FinalNotificationError::InvalidParams {
6935                    method: NOTIFICATIONS_CANCELLED
6936                })
6937            ),
6938            "changing only required requestId to null rejects the final cancellation shape"
6939        );
6940        assert_eq!(
6941            serde_json::to_value(admitted.encode().expect("accepted cancellation re-encodes"))
6942                .expect("accepted cancellation remains JSON"),
6943            accepted_wire,
6944            "the one-field malformed-field rejection leaves the admitted cancellation unchanged"
6945        );
6946    }
6947
6948    #[test]
6949    fn final_cancellation_preserves_large_integer_request_ids_and_rejects_fractional_ids() {
6950        let large_id = "922337203685477580812345678901234567890";
6951        let accepted_wire = format!(
6952            r#"{{"jsonrpc":"2.0","method":"notifications/cancelled","params":{{"requestId":{large_id}}}}}"#
6953        );
6954        let accepted: JsonRpcRequest = serde_json::from_str(&accepted_wire)
6955            .expect("arbitrary-precision integer cancellation ID decodes");
6956        let notification = ClientNotification::decode(&accepted)
6957            .expect("final cancellation retains the arbitrary-precision request ID");
6958        let ClientNotification::Cancelled(params) = &notification;
6959        assert_eq!(
6960            params.request_id,
6961            RequestId::Integer(large_id.to_owned()),
6962            "the cancellation parameter preserves the numeric ID without narrowing it"
6963        );
6964        assert_eq!(
6965            serde_json::to_string(&notification.encode().expect("cancellation re-encodes"))
6966                .expect("cancellation JSON serializes"),
6967            accepted_wire,
6968            "the final notification returns the exact large integer lexeme to the wire"
6969        );
6970
6971        let baseline = JsonRpcRequest::notification(
6972            NOTIFICATIONS_CANCELLED,
6973            Some(serde_json::json!({"requestId": 1})),
6974        );
6975        let admitted = ClientNotification::decode(&baseline)
6976            .expect("integer cancellation request IDs remain admitted");
6977        let baseline_wire = serde_json::to_value(&baseline).expect("baseline wire serializes");
6978        let mut planted = baseline.clone();
6979        planted
6980            .params
6981            .as_mut()
6982            .and_then(Value::as_object_mut)
6983            .expect("cancellation owns object parameters")
6984            .insert("requestId".to_owned(), serde_json::json!(1.5));
6985        assert!(
6986            matches!(
6987                ClientNotification::decode(&planted),
6988                Err(FinalNotificationError::InvalidParams {
6989                    method: NOTIFICATIONS_CANCELLED
6990                })
6991            ),
6992            "changing only the requestId from an integer to a fraction rejects cancellation"
6993        );
6994        assert_eq!(
6995            serde_json::to_value(admitted.encode().expect("integer cancellation re-encodes"))
6996                .expect("integer cancellation remains JSON"),
6997            baseline_wire,
6998            "fractional rejection cannot alter the admitted integer cancellation"
6999        );
7000    }
7001
7002    #[test]
7003    fn legacy_sampling_stop_reason_is_optional_and_open() {
7004        let absent_wire = serde_json::json!({
7005            "content": {"type": "text", "text": "summary"},
7006            "role": "assistant",
7007            "model": "legacy-model"
7008        });
7009        let absent: CreateMessageResult = serde_json::from_value(absent_wire.clone())
7010            .expect("exact legacy sampling permits an absent stopReason");
7011        assert_eq!(absent.stop_reason, None);
7012        assert_eq!(
7013            serde_json::to_value(&absent).expect("absent legacy stopReason re-encodes"),
7014            absent_wire
7015        );
7016
7017        let arbitrary_wire = serde_json::json!({
7018            "content": {"type": "text", "text": "summary"},
7019            "role": "assistant",
7020            "model": "legacy-model",
7021            "stopReason": "provider_safety_limit"
7022        });
7023        let arbitrary: CreateMessageResult = serde_json::from_value(arbitrary_wire.clone())
7024            .expect("exact legacy sampling retains an arbitrary provider stopReason");
7025        assert_eq!(
7026            arbitrary.stop_reason.as_deref(),
7027            Some("provider_safety_limit")
7028        );
7029        assert_eq!(
7030            serde_json::to_value(arbitrary).expect("open legacy stopReason re-encodes"),
7031            arbitrary_wire
7032        );
7033    }
7034
7035    #[cfg(feature = "legacy-2024-11-05")]
7036    #[test]
7037    fn legacy_sampling_rejects_one_final_result_field_without_mutating_its_baseline() {
7038        let request = CoreRequest::decode(
7039            ProtocolEra::Legacy2024,
7040            SAMPLING_CREATE_MESSAGE,
7041            Some(&serde_json::json!({
7042                "messages": [{"role": "user", "content": {"type": "text", "text": "hello"}}],
7043                "maxTokens": 8
7044            })),
7045        )
7046        .expect("legacy sampling baseline request");
7047        let accepted = r#"{"content":{"type":"text","text":"hello"},"role":"assistant","model":"legacy","stopReason":"endTurn"}"#;
7048        let baseline = request
7049            .decode_result(accepted)
7050            .expect("legacy sampling baseline result");
7051        let planted = r#"{"content":{"type":"text","text":"hello"},"role":"assistant","model":"legacy","stopReason":"endTurn","resultType":"complete"}"#;
7052        assert!(
7053            matches!(
7054                request.decode_result(planted),
7055                Err(CoreDispatchError::CrossEraResultType {
7056                    method: SAMPLING_CREATE_MESSAGE
7057                })
7058            ),
7059            "only the final resultType field changes the accepted legacy sampling result"
7060        );
7061        assert_eq!(
7062            serde_json::from_str::<Value>(
7063                &request
7064                    .decode_result(accepted)
7065                    .expect("legacy baseline remains admitted")
7066                    .encode()
7067                    .expect("legacy baseline encodes"),
7068            )
7069            .expect("reaccepted legacy sampling result is JSON"),
7070            serde_json::from_str::<Value>(&baseline.encode().expect("baseline encodes"))
7071                .expect("baseline legacy sampling result is JSON"),
7072            "the cross-era rejection leaves legacy sampling semantics unchanged"
7073        );
7074    }
7075
7076    #[test]
7077    fn final_catalog_results_preserve_typed_cache_hints() {
7078        let params = serde_json::json!({
7079            "_meta": {
7080                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7081                "io.modelcontextprotocol/clientCapabilities": {}
7082            }
7083        });
7084        let cases = [
7085            (
7086                TOOLS_LIST,
7087                r#"{"resultType":"complete","tools":[],"ttlMs":0,"cacheScope":"private"}"#,
7088            ),
7089            (
7090                RESOURCES_LIST,
7091                r#"{"resultType":"complete","resources":[],"ttlMs":1,"cacheScope":"public"}"#,
7092            ),
7093            (
7094                RESOURCES_TEMPLATES_LIST,
7095                r#"{"resultType":"complete","resourceTemplates":[],"ttlMs":2,"cacheScope":"private"}"#,
7096            ),
7097            (
7098                PROMPTS_LIST,
7099                r#"{"resultType":"complete","prompts":[],"ttlMs":3,"cacheScope":"public"}"#,
7100            ),
7101            (
7102                RESOURCES_READ,
7103                r#"{"resultType":"complete","contents":[],"ttlMs":4,"cacheScope":"private"}"#,
7104            ),
7105        ];
7106        for (method, wire) in cases {
7107            let request_params = if method == RESOURCES_READ {
7108                serde_json::json!({
7109                    "_meta": {
7110                        "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7111                        "io.modelcontextprotocol/clientCapabilities": {}
7112                    },
7113                    "uri": "file:///workspace/status"
7114                })
7115            } else {
7116                params.clone()
7117            };
7118            let request =
7119                CoreRequest::decode(ProtocolEra::Modern2026, method, Some(&request_params))
7120                    .expect("final catalog/read request");
7121            let result = request
7122                .decode_result(wire)
7123                .expect("required final cache fields decode");
7124            assert_eq!(
7125                serde_json::from_str::<Value>(
7126                    &result.encode().expect("final cached result encodes")
7127                )
7128                .expect("final cached result encoding is JSON"),
7129                serde_json::from_str::<Value>(wire).expect("final cached result fixture is JSON"),
7130                "{method} preserves final cache-result semantics"
7131            );
7132        }
7133
7134        let tools_request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_LIST, Some(&params))
7135            .expect("tools/list request");
7136        assert!(
7137            matches!(
7138                tools_request.decode_result(
7139                    r#"{"resultType":"complete","tools":[],"ttlMs":0,"cacheScope":"shared"}"#
7140                ),
7141                Err(CoreDispatchError::InvalidResult {
7142                    era: ProtocolEra::Modern2026,
7143                    method: TOOLS_LIST,
7144                })
7145            ),
7146            "only an invalid cacheScope changes the otherwise valid final catalog result; peer TTL omission/negativity is normalized as immediately stale at client ingress"
7147        );
7148    }
7149
7150    #[test]
7151    fn final_catalog_ttl_ms_preserves_an_unbounded_wire_integer() {
7152        let params = serde_json::json!({
7153            "_meta": {
7154                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7155                "io.modelcontextprotocol/clientCapabilities": {}
7156            }
7157        });
7158        let request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_LIST, Some(&params))
7159            .expect("final tools/list request");
7160        let accepted = r#"{"resultType":"complete","tools":[],"ttlMs":18446744073709551616,"cacheScope":"private"}"#;
7161        let decoded = request
7162            .decode_result(accepted)
7163            .expect("the unbounded nonnegative final TTL is admitted");
7164        let CoreResult::Final(FinalCoreResult::ToolsList { result, .. }) = &decoded else {
7165            panic!("final tools/list result");
7166        };
7167        assert_eq!(result.payload.ttl_ms.as_str(), "18446744073709551616");
7168        assert_eq!(
7169            result.payload.ttl_ms.try_as_millis(),
7170            Err(crate::result::CacheTtlConversionError::RuntimeOutOfRange),
7171            "only the runtime conversion rejects the one-over-u64 TTL"
7172        );
7173        assert_eq!(
7174            decoded.encode().expect("unbounded final TTL re-encodes"),
7175            accepted
7176        );
7177
7178        let fractional = r#"{"resultType":"complete","tools":[],"ttlMs":18446744073709551616.5,"cacheScope":"private"}"#;
7179        assert!(
7180            matches!(
7181                request.decode_result(fractional),
7182                Err(CoreDispatchError::InvalidResult {
7183                    era: ProtocolEra::Modern2026,
7184                    method: TOOLS_LIST,
7185                })
7186            ),
7187            "changing only ttlMs from an unbounded integer to a fraction violates the final cache schema"
7188        );
7189    }
7190
7191    #[test]
7192    fn final_retry_parameters_preserve_input_state_and_require_object_arguments() {
7193        let call_params = serde_json::json!({
7194            "_meta": {
7195                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7196                "io.modelcontextprotocol/clientCapabilities": {}
7197            },
7198            "name": "weather",
7199            "arguments": {"city": "Boston"},
7200            "inputResponses": {"request-1": {"roots": []}},
7201            "requestState": "retry-1"
7202        });
7203        let baseline = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&call_params))
7204            .expect("final call admits retry state and object arguments");
7205        assert_eq!(
7206            baseline
7207                .encode_params()
7208                .expect("call parameters encode")
7209                .expect("call owns parameters"),
7210            call_params
7211        );
7212
7213        let mut planted = call_params.clone();
7214        planted["arguments"] = serde_json::json!(["Boston"]);
7215        assert!(
7216            matches!(
7217                CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&planted)),
7218                Err(CoreDispatchError::InvalidParams {
7219                    era: ProtocolEra::Modern2026,
7220                    method: TOOLS_CALL,
7221                })
7222            ),
7223            "only a non-object arguments value changes the accepted final call"
7224        );
7225
7226        let read_params = serde_json::json!({
7227            "_meta": {
7228                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7229                "io.modelcontextprotocol/clientCapabilities": {}
7230            },
7231            "uri": "file:///workspace/status",
7232            "inputResponses": {"request-1": {"roots": []}},
7233            "requestState": "retry-1"
7234        });
7235        let get_params = serde_json::json!({
7236            "_meta": {
7237                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7238                "io.modelcontextprotocol/clientCapabilities": {}
7239            },
7240            "name": "status",
7241            "inputResponses": {"request-1": {"roots": []}},
7242            "requestState": "retry-1"
7243        });
7244        for (method, params) in [(RESOURCES_READ, read_params), (PROMPTS_GET, get_params)] {
7245            let request = CoreRequest::decode(ProtocolEra::Modern2026, method, Some(&params))
7246                .expect("final retry parameters decode");
7247            assert_eq!(
7248                request
7249                    .encode_params()
7250                    .expect("retry parameters encode")
7251                    .expect("retry-owning request has parameters"),
7252                params,
7253                "{method} retains retry input responses and request state"
7254            );
7255        }
7256    }
7257
7258    #[test]
7259    fn final_mrtr_retry_responses_are_typed_ordered_and_correlatable() {
7260        let responses_wire = r#"{"second":{"roots":[]},"first":{"roots":[]}}"#;
7261        let responses: FinalInputResponses = serde_json::from_str(responses_wire)
7262            .expect("typed final input responses decode in their wire order");
7263        assert_eq!(
7264            responses
7265                .entries()
7266                .iter()
7267                .map(|(key, _)| key.as_str())
7268                .collect::<Vec<_>>(),
7269            vec!["second", "first"],
7270            "input response key order remains observable after decoding"
7271        );
7272        assert_eq!(
7273            serde_json::to_string(&responses).expect("typed responses re-encode"),
7274            responses_wire,
7275            "the exact inputResponses object order round-trips"
7276        );
7277
7278        let raw_call_params = r#"{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}},"name":"weather","inputResponses":{"second":{"roots":[]},"first":{"roots":[]}}}"#;
7279        let materialized_call_params: Value = serde_json::from_str(raw_call_params)
7280            .expect("raw call parameters materialize for JSON-RPC envelope admission");
7281        let CoreRequest::Final(FinalCoreRequest::ToolsCall(call)) =
7282            CoreRequest::decode_with_raw_params(
7283                ProtocolEra::Modern2026,
7284                TOOLS_CALL,
7285                Some(&materialized_call_params),
7286                Some(raw_call_params),
7287            )
7288            .expect("the raw final core decoder preserves MRTR response ordering")
7289        else {
7290            panic!("raw tools/call parameters select the final request type");
7291        };
7292        assert_eq!(
7293            call.input_responses
7294                .as_ref()
7295                .expect("present retry map")
7296                .entries()
7297                .iter()
7298                .map(|(key, _)| key.as_str())
7299                .collect::<Vec<_>>(),
7300            vec!["second", "first"],
7301            "the core raw-params path does not inherit materialized-map sorting"
7302        );
7303
7304        let ExactJsonValue::Object(input_requests) = crate::result::parse_exact_json(
7305            r#"{"second":{"method":"roots/list"},"first":{"method":"roots/list"}}"#,
7306        )
7307        .expect("input request map admits exactly") else {
7308            panic!("inputRequests must be an object");
7309        };
7310        responses
7311            .validate_against(&input_requests)
7312            .expect("each typed response matches its exact input request key and kind");
7313
7314        let wrong_kind: FinalInputResponses =
7315            serde_json::from_str(r#"{"second":{"action":"decline"},"first":{"roots":[]}}"#)
7316                .expect("a differently typed embedded response is structurally valid");
7317        assert_eq!(
7318            wrong_kind.validate_against(&input_requests),
7319            Err(FinalInputResponseCorrelationError::ResponseKindMismatch),
7320            "changing only one response payload kind rejects correlation"
7321        );
7322
7323        let mismatched_raw = raw_call_params.replacen("weather", "forecast", 1);
7324        assert!(
7325            matches!(
7326                CoreRequest::decode_with_raw_params(
7327                    ProtocolEra::Modern2026,
7328                    TOOLS_CALL,
7329                    Some(&materialized_call_params),
7330                    Some(&mismatched_raw),
7331                ),
7332                Err(CoreDispatchError::InvalidParams {
7333                    era: ProtocolEra::Modern2026,
7334                    method: TOOLS_CALL,
7335                })
7336            ),
7337            "changing only the raw method-owned value cannot attach a source from another frame"
7338        );
7339
7340        let raw_resource_params = r#"{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}},"uri":"file:///workspace/status","inputResponses":{"second":{"roots":[]},"first":{"roots":[]}}}"#;
7341        let raw_prompt_params = r#"{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}},"name":"status","inputResponses":{"second":{"roots":[]},"first":{"roots":[]}}}"#;
7342        for (method, raw_params, mismatched_raw) in [
7343            (
7344                RESOURCES_READ,
7345                raw_resource_params,
7346                raw_resource_params.replacen("status", "other", 1),
7347            ),
7348            (
7349                PROMPTS_GET,
7350                raw_prompt_params,
7351                raw_prompt_params.replacen("status", "other", 1),
7352            ),
7353        ] {
7354            let materialized: Value = serde_json::from_str(raw_params)
7355                .expect("raw method parameters materialize for envelope admission");
7356            let decoded = CoreRequest::decode_with_raw_params(
7357                ProtocolEra::Modern2026,
7358                method,
7359                Some(&materialized),
7360                Some(raw_params),
7361            )
7362            .expect("raw final retry parameters preserve ordered input responses");
7363            let entry_keys = match decoded {
7364                CoreRequest::Final(FinalCoreRequest::ResourcesRead(params)) => params
7365                    .input_responses
7366                    .as_ref()
7367                    .expect("resource retry map is present")
7368                    .entries()
7369                    .iter()
7370                    .map(|(key, _)| key.clone())
7371                    .collect::<Vec<_>>(),
7372                CoreRequest::Final(FinalCoreRequest::PromptsGet(params)) => params
7373                    .input_responses
7374                    .as_ref()
7375                    .expect("prompt retry map is present")
7376                    .entries()
7377                    .iter()
7378                    .map(|(key, _)| key.clone())
7379                    .collect::<Vec<_>>(),
7380                _ => panic!("raw parameters select their method's final request type"),
7381            };
7382            assert_eq!(
7383                entry_keys,
7384                vec!["second".to_owned(), "first".to_owned()],
7385                "{method} retains inputResponses wire order"
7386            );
7387            assert!(
7388                matches!(
7389                    CoreRequest::decode_with_raw_params(
7390                        ProtocolEra::Modern2026,
7391                        method,
7392                        Some(&materialized),
7393                        Some(&mismatched_raw),
7394                    ),
7395                    Err(CoreDispatchError::InvalidParams {
7396                        era: ProtocolEra::Modern2026,
7397                        method: rejected_method,
7398                    }) if rejected_method == method
7399                ),
7400                "changing only one raw {method} value cannot attach another frame's source"
7401            );
7402        }
7403    }
7404
7405    #[test]
7406    fn final_mrtr_retry_rejects_duplicate_wire_keys_and_present_state_only_maps() {
7407        let raw_params = r#"{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}},"name":"weather","inputResponses":{"roots":{"roots":[]},"roots":{"roots":[]}},"requestState":"retry-1"}"#;
7408        let materialized: Value = serde_json::from_str(raw_params)
7409            .expect("JSON-RPC envelope materializes duplicate members as a value");
7410        assert!(
7411            matches!(
7412                CoreRequest::decode_with_raw_params(
7413                    ProtocolEra::Modern2026,
7414                    TOOLS_CALL,
7415                    Some(&materialized),
7416                    Some(raw_params),
7417                ),
7418                Err(CoreDispatchError::InvalidParams {
7419                    era: ProtocolEra::Modern2026,
7420                    method: TOOLS_CALL,
7421                })
7422            ),
7423            "a duplicate inputResponses wire key is rejected before it can collapse into a map"
7424        );
7425
7426        let request = CoreRequest::decode(
7427            ProtocolEra::Modern2026,
7428            TOOLS_CALL,
7429            Some(&serde_json::json!({
7430                "_meta": {
7431                    "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7432                    "io.modelcontextprotocol/clientCapabilities": {}
7433                },
7434                "name": "weather"
7435            })),
7436        )
7437        .expect("accepted final tool request remains available after planted rejection");
7438        let CoreResult::Final(FinalCoreResult::ToolsCallInputRequired { result, .. }) = request
7439            .decode_result(r#"{"resultType":"input_required","requestState":"state-only"}"#)
7440            .expect("state-only input-required result decodes")
7441        else {
7442            panic!("state-only result selects the final input-required branch");
7443        };
7444        assert_eq!(
7445            FinalInputResponses::default().validate_against_input_required(&result),
7446            Err(FinalInputResponseCorrelationError::StateOnlyInputResponses),
7447            "an explicit empty inputResponses object is not an absent member"
7448        );
7449        assert!(
7450            result.input_requests().is_none(),
7451            "the planted explicit-empty rejection does not add input requests"
7452        );
7453        assert_eq!(
7454            result.request_state(),
7455            Some("state-only"),
7456            "the planted explicit-empty rejection does not mutate accepted state-only state"
7457        );
7458    }
7459
7460    #[test]
7461    fn final_retry_parameters_reject_null_or_untyped_input_responses() {
7462        let accepted = serde_json::json!({
7463            "_meta": {
7464                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7465                "io.modelcontextprotocol/clientCapabilities": {}
7466            },
7467            "name": "weather",
7468            "inputResponses": {"request-1": {"roots": []}},
7469            "requestState": "retry-1"
7470        });
7471        let baseline = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&accepted))
7472            .expect("typed final retry parameters are admitted");
7473
7474        for (field, value) in [
7475            ("inputResponses", serde_json::Value::Null),
7476            ("requestState", serde_json::Value::Null),
7477            (
7478                "inputResponses",
7479                serde_json::json!({"request-1": {"approved": true}}),
7480            ),
7481        ] {
7482            let mut planted = accepted.clone();
7483            planted[field] = value;
7484            assert!(
7485                matches!(
7486                    CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&planted)),
7487                    Err(CoreDispatchError::InvalidParams {
7488                        era: ProtocolEra::Modern2026,
7489                        method: TOOLS_CALL,
7490                    })
7491                ),
7492                "changing only {field} rejects a null or untyped final retry member"
7493            );
7494        }
7495        assert_eq!(
7496            baseline.encode_params().expect("baseline encodes"),
7497            Some(accepted),
7498            "each planted rejection leaves the accepted retry parameters unchanged"
7499        );
7500    }
7501
7502    #[test]
7503    fn final_arguments_admit_absence_and_objects_but_reject_null() {
7504        let meta = serde_json::json!({
7505            "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7506            "io.modelcontextprotocol/clientCapabilities": {}
7507        });
7508
7509        let tool_absent = serde_json::json!({"_meta": meta.clone(), "name": "weather"});
7510        let CoreRequest::Final(FinalCoreRequest::ToolsCall(tool)) =
7511            CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&tool_absent))
7512                .expect("an absent final tool arguments member is admitted")
7513        else {
7514            panic!("tools/call selects its final request type");
7515        };
7516        assert!(tool.arguments.is_absent());
7517        assert_eq!(
7518            CoreRequest::Final(FinalCoreRequest::ToolsCall(tool))
7519                .encode_params()
7520                .expect("absent tool arguments encode")
7521                .expect("tools/call has params"),
7522            tool_absent,
7523        );
7524
7525        let tool_object = serde_json::json!({
7526            "_meta": meta.clone(),
7527            "name": "weather",
7528            "arguments": {"units": "metric"}
7529        });
7530        assert!(
7531            CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&tool_object)).is_ok()
7532        );
7533        let tool_null =
7534            serde_json::json!({"_meta": meta.clone(), "name": "weather", "arguments": null});
7535        assert!(
7536            CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&tool_null)).is_err(),
7537            "changing only final tool arguments from an object to null rejects"
7538        );
7539
7540        let prompt_absent = serde_json::json!({"_meta": meta.clone(), "name": "summary"});
7541        let CoreRequest::Final(FinalCoreRequest::PromptsGet(prompt)) =
7542            CoreRequest::decode(ProtocolEra::Modern2026, PROMPTS_GET, Some(&prompt_absent))
7543                .expect("an absent final prompt arguments member is admitted")
7544        else {
7545            panic!("prompts/get selects its final request type");
7546        };
7547        assert!(prompt.arguments.is_absent());
7548
7549        let prompt_null = serde_json::json!({"_meta": meta, "name": "summary", "arguments": null});
7550        assert!(
7551            CoreRequest::decode(ProtocolEra::Modern2026, PROMPTS_GET, Some(&prompt_null)).is_err(),
7552            "changing only final prompt arguments from absent to null rejects"
7553        );
7554    }
7555
7556    #[test]
7557    fn final_server_info_is_admitted_only_in_result_metadata() {
7558        let params = serde_json::json!({
7559            "_meta": {
7560                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7561                "io.modelcontextprotocol/clientCapabilities": {}
7562            }
7563        });
7564        let request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_LIST, Some(&params))
7565            .expect("final tools/list request");
7566        let accepted = r#"{"resultType":"complete","tools":[],"ttlMs":0,"cacheScope":"private","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"final-server","version":"1.0.0"}}}"#;
7567        let baseline = request
7568            .decode_result(accepted)
7569            .expect("final serverInfo is admitted in metadata");
7570        assert_eq!(
7571            baseline.encode().expect("metadata serverInfo encodes"),
7572            accepted
7573        );
7574
7575        let planted = r#"{"resultType":"complete","tools":[],"ttlMs":0,"cacheScope":"private","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"final-server","version":"1.0.0"}},"serverInfo":{"name":"legacy-location","version":"1.0.0"}}"#;
7576        assert!(
7577            matches!(
7578                request.decode_result(planted),
7579                Err(CoreDispatchError::InvalidResult {
7580                    era: ProtocolEra::Modern2026,
7581                    method: TOOLS_LIST,
7582                })
7583            ),
7584            "only a top-level serverInfo changes the final result admission"
7585        );
7586        assert_eq!(
7587            request
7588                .decode_result(accepted)
7589                .expect("baseline remains admitted")
7590                .encode()
7591                .expect("baseline encodes"),
7592            baseline.encode().expect("original baseline encodes"),
7593            "the top-level serverInfo rejection does not alter metadata server info"
7594        );
7595    }
7596
7597    #[test]
7598    fn final_result_metadata_seal_is_typed_and_excludes_open_metadata() {
7599        let server_info =
7600            Implementation::try_new("sealed-server", "1.0.0").expect("server identity is valid");
7601        let metadata = OpenMetadata::try_from_entries([
7602            (
7603                FINAL_SERVER_INFO_META_KEY.to_owned(),
7604                serde_json::to_value(&server_info).expect("server identity serializes"),
7605            ),
7606            ("com.example/trace".to_owned(), serde_json::json!("open")),
7607        ])
7608        .expect("metadata is valid");
7609        let complete = FinalCoreResult::ToolsCall {
7610            result: CompleteResult::new(
7611                FinalCallToolResult {
7612                    content: Vec::new(),
7613                    is_error: false,
7614                    structured_content: None,
7615                },
7616                ResultMeta::server_generated(server_info.clone()).with_metadata(metadata),
7617            ),
7618            diagnostic: None,
7619        };
7620        assert_eq!(
7621            complete
7622                .protected_metadata_seal()
7623                .expect("complete result seal is typed"),
7624            FinalResultMetadataSeal {
7625                family: FinalResultMetadataFamily::ToolsCall,
7626                server_info: FinalResultServerInfo::Common(Some(server_info.clone())),
7627                subscription_id: None,
7628            }
7629        );
7630
7631        let input_required = FinalCoreResult::PromptsGetInputRequired {
7632            result: InputRequiredResult::new(
7633                None,
7634                Some("retry".to_owned()),
7635                ResultMeta::server_generated(server_info.clone()),
7636            )
7637            .expect("input-required result is valid"),
7638            diagnostic: None,
7639        };
7640        assert_eq!(
7641            input_required
7642                .protected_metadata_seal()
7643                .expect("input-required result seal is typed"),
7644            FinalResultMetadataSeal {
7645                family: FinalResultMetadataFamily::PromptsGetInputRequired,
7646                server_info: FinalResultServerInfo::Common(Some(server_info.clone())),
7647                subscription_id: None,
7648            }
7649        );
7650
7651        let subscription_id = RequestId::String("subscription-7".to_owned());
7652        let subscription_metadata = OpenMetadata::try_from_entries([
7653            (
7654                FINAL_SERVER_INFO_META_KEY.to_owned(),
7655                serde_json::to_value(&server_info).expect("server identity serializes"),
7656            ),
7657            (
7658                FINAL_SUBSCRIPTION_ID_META_KEY.to_owned(),
7659                serde_json::to_value(&subscription_id).expect("subscription id serializes"),
7660            ),
7661        ])
7662        .expect("subscription metadata is valid");
7663        let subscription = FinalCoreResult::SubscriptionsListen {
7664            result: CompleteResult::new(
7665                FinalSubscriptionsListenResult {},
7666                ResultMeta::server_generated(server_info.clone())
7667                    .with_metadata(subscription_metadata),
7668            ),
7669            subscription_id: subscription_id.clone(),
7670            diagnostic: None,
7671        };
7672        assert_eq!(
7673            subscription
7674                .protected_metadata_seal()
7675                .expect("subscription result seal is typed"),
7676            FinalResultMetadataSeal {
7677                family: FinalResultMetadataFamily::SubscriptionsListen,
7678                server_info: FinalResultServerInfo::Common(Some(server_info)),
7679                subscription_id: Some(subscription_id),
7680            }
7681        );
7682    }
7683
7684    #[test]
7685    fn final_log_level_metadata_replaces_final_set_level_rpc() {
7686        let final_params = serde_json::json!({
7687            "_meta": {
7688                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7689                "io.modelcontextprotocol/clientCapabilities": {},
7690                "io.modelcontextprotocol/logLevel": "notice"
7691            }
7692        });
7693        let request = CoreRequest::decode(
7694            ProtocolEra::Modern2026,
7695            SERVER_DISCOVER,
7696            Some(&final_params),
7697        )
7698        .expect("final discovery metadata carries log level");
7699        let CoreRequest::Final(FinalCoreRequest::Discover(params)) = request else {
7700            panic!("final discovery request");
7701        };
7702        assert_eq!(
7703            params.meta.log_level().expect("typed final log level"),
7704            Some(LoggingLevel::Notice)
7705        );
7706        let notification = FinalLogMessageParams {
7707            level: LoggingLevel::Notice,
7708            logger: Some("final.server".to_owned()),
7709            data: serde_json::json!({"message": "catalog refreshed"}),
7710            meta: None,
7711            additional: BTreeMap::new(),
7712        };
7713        assert_eq!(
7714            serde_json::to_value(notification).expect("final log notification encodes"),
7715            serde_json::json!({
7716                "level": "notice",
7717                "logger": "final.server",
7718                "data": {"message": "catalog refreshed"}
7719            })
7720        );
7721        assert!(matches!(
7722            CoreRequest::decode(
7723                ProtocolEra::Modern2026,
7724                PING,
7725                Some(&serde_json::json!({
7726                    "_meta": {
7727                        "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7728                        "io.modelcontextprotocol/clientCapabilities": {}
7729                    }
7730                }))
7731            ),
7732            Err(CoreDispatchError::UnsupportedMethod {
7733                era: ProtocolEra::Modern2026,
7734                method,
7735            }) if method == PING
7736        ));
7737        #[cfg(feature = "legacy-2024-11-05")]
7738        assert!(
7739            CoreRequest::decode(ProtocolEra::Legacy2024, PING, None).is_ok(),
7740            "the exact legacy ping request remains available only in its legacy era"
7741        );
7742    }
7743
7744    #[test]
7745    fn final_discover_core_result_round_trips_typed_capabilities_and_cache_hints() {
7746        let params = serde_json::json!({
7747            "_meta": {
7748                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7749                "io.modelcontextprotocol/clientCapabilities": {}
7750            }
7751        });
7752        let request = CoreRequest::decode(ProtocolEra::Modern2026, SERVER_DISCOVER, Some(&params))
7753            .expect("final server/discover request is typed");
7754        let advertised = crate::ServerDiscoverResult::new(
7755            crate::ServerDiscoverCapabilities::from_registry(
7756                &crate::ServerBehaviorRegistry::from_behaviors([
7757                    crate::ServerBehavior::ToolsList,
7758                    crate::ServerBehavior::ToolsListChangedNotification,
7759                ]),
7760                std::collections::BTreeMap::new(),
7761            )
7762            .expect("installed server behavior derives typed discovery capabilities"),
7763            ServerInfo {
7764                name: "discovery-server".to_owned(),
7765                version: "1.0.0".to_owned(),
7766            },
7767            Some(
7768                crate::ServerInstructions::new("Use tools before answering.")
7769                    .expect("bounded discovery instructions"),
7770            ),
7771            crate::DiscoveryCacheHints::private_ttl_ms(60_000),
7772        );
7773        let accepted = serde_json::to_value(&advertised).expect("typed discovery result encodes");
7774        let result = request
7775            .decode_result(
7776                &serde_json::to_string(&accepted).expect("discovery wire serializes for dispatch"),
7777            )
7778            .expect("typed discovery result is admitted by final core dispatch");
7779        let CoreResult::Final(final_result @ FinalCoreResult::Discover(decoded)) = &result else {
7780            panic!("server/discover selects its typed final result");
7781        };
7782        assert_eq!(
7783            decoded.supported_versions(),
7784            [FINAL_PROTOCOL_VERSION.to_owned()],
7785            "the final discovery version set round-trips exactly"
7786        );
7787        assert_eq!(
7788            decoded
7789                .server_info()
7790                .map(|info| (info.name.as_str(), info.version.as_str())),
7791            Some(("discovery-server", "1.0.0")),
7792            "serverInfo remains final result metadata"
7793        );
7794        assert_eq!(
7795            final_result
7796                .protected_metadata_seal()
7797                .expect("discovery result metadata seal is typed"),
7798            FinalResultMetadataSeal {
7799                family: FinalResultMetadataFamily::Discover,
7800                server_info: FinalResultServerInfo::Discovery(Some(FinalDiscoveryServerInfo {
7801                    name: "discovery-server".to_owned(),
7802                    version: "1.0.0".to_owned(),
7803                })),
7804                subscription_id: None,
7805            }
7806        );
7807        assert_eq!(
7808            decoded
7809                .instructions()
7810                .map(crate::ServerInstructions::as_str),
7811            Some("Use tools before answering."),
7812            "instructions remain part of the typed discovery result"
7813        );
7814        assert_eq!(
7815            decoded
7816                .cache_hints()
7817                .ttl_ms()
7818                .try_as_millis()
7819                .expect("local TTL fits the runtime domain"),
7820            60_000
7821        );
7822        assert!(!decoded.cache_hints().is_public());
7823        assert_eq!(
7824            serde_json::from_str::<Value>(&result.encode().expect("typed result re-encodes"))
7825                .expect("encoded typed result remains JSON"),
7826            accepted,
7827            "capabilities, serverInfo, instructions, and cache hints all survive core dispatch"
7828        );
7829
7830        let mut planted = accepted.clone();
7831        planted["cacheScope"] = serde_json::json!("shared");
7832        assert!(
7833            matches!(
7834                request.decode_result(
7835                    &serde_json::to_string(&planted)
7836                        .expect("one-field malformed discovery wire serializes"),
7837                ),
7838                Err(CoreDispatchError::InvalidResult {
7839                    era: ProtocolEra::Modern2026,
7840                    method: SERVER_DISCOVER,
7841                })
7842            ),
7843            "changing only cacheScope to an unrecognized value rejects the typed discovery result"
7844        );
7845        assert_eq!(
7846            serde_json::from_str::<Value>(
7847                &result.encode().expect("accepted result stays immutable")
7848            )
7849            .expect("accepted result stays JSON"),
7850            accepted,
7851            "the malformed peer field cannot mutate the admitted discovery result"
7852        );
7853    }
7854
7855    #[test]
7856    fn final_discover_core_result_rejects_non_complete_result_algebra() {
7857        let params = serde_json::json!({
7858            "_meta": {
7859                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7860                "io.modelcontextprotocol/clientCapabilities": {}
7861            }
7862        });
7863        let request = CoreRequest::decode(ProtocolEra::Modern2026, SERVER_DISCOVER, Some(&params))
7864            .expect("final server/discover request is typed");
7865        let baseline = serde_json::json!({
7866            "resultType": "complete",
7867            "supportedVersions": [FINAL_PROTOCOL_VERSION],
7868            "capabilities": {},
7869            "ttlMs": 0,
7870            "cacheScope": "private"
7871        });
7872
7873        for result_type in [
7874            serde_json::json!("input_required"),
7875            serde_json::json!("task"),
7876            serde_json::json!("com.example/deferred-discovery"),
7877            Value::Null,
7878        ] {
7879            let mut planted = baseline.clone();
7880            planted["resultType"] = result_type;
7881            assert!(
7882                matches!(
7883                    request.decode_result(
7884                        &serde_json::to_string(&planted)
7885                            .expect("one-field invalid discovery result serializes"),
7886                    ),
7887                    Err(CoreDispatchError::InvalidResult {
7888                        era: ProtocolEra::Modern2026,
7889                        method: SERVER_DISCOVER,
7890                    })
7891                ),
7892                "only complete or omission can select the modern discovery result"
7893            );
7894        }
7895
7896        let mut contradictory = baseline;
7897        contradictory["requestState"] = serde_json::json!("resume-1");
7898        assert!(
7899            matches!(
7900                request.decode_result(
7901                    &serde_json::to_string(&contradictory)
7902                        .expect("contradictory discovery result serializes"),
7903                ),
7904                Err(CoreDispatchError::InvalidResult {
7905                    era: ProtocolEra::Modern2026,
7906                    method: SERVER_DISCOVER,
7907                })
7908            ),
7909            "a complete discriminator cannot make an input-required shape discovery"
7910        );
7911    }
7912
7913    #[test]
7914    fn core_dispatch_round_trips_legacy_and_final_core_payloads() {
7915        let final_params = serde_json::json!({
7916            "_meta": {
7917                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7918                "io.modelcontextprotocol/clientCapabilities": {}
7919            },
7920            "name": "echo",
7921            "arguments": {"message": "hello"}
7922        });
7923        let final_request =
7924            CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&final_params))
7925                .expect("final tools/call request is typed through common metadata");
7926        assert_eq!(final_request.era(), ProtocolEra::Modern2026);
7927        assert_eq!(final_request.method(), TOOLS_CALL);
7928        assert_eq!(
7929            final_request
7930                .encode_params()
7931                .expect("final request re-encodes")
7932                .expect("final requests always own a parameter object"),
7933            final_params
7934        );
7935
7936        let final_wire = r#"{"resultType":"complete","content":[{"type":"text","text":"ready"}],"extension":{"opaque":true}}"#;
7937        let final_result = final_request
7938            .decode_result(final_wire)
7939            .expect("final complete result selects the tools/call payload");
7940        let CoreResult::Final(FinalCoreResult::ToolsCall { result, diagnostic }) = &final_result
7941        else {
7942            panic!("final tools/call result");
7943        };
7944        assert_eq!(diagnostic, &None);
7945        assert!(matches!(
7946            result.payload.content.as_slice(),
7947            [ContentBlock::Text { text, .. }] if text == "ready"
7948        ));
7949        assert_eq!(
7950            result
7951                .extras
7952                .members()
7953                .iter()
7954                .map(|member| member.name.as_str())
7955                .collect::<Vec<_>>(),
7956            ["extension"]
7957        );
7958        assert_eq!(
7959            final_result.encode().expect("final result re-encodes"),
7960            final_wire
7961        );
7962
7963        #[cfg(feature = "legacy-2024-11-05")]
7964        {
7965            let legacy_params = serde_json::json!({"cursor": ""});
7966            let legacy_request =
7967                CoreRequest::decode(ProtocolEra::Legacy2024, TOOLS_LIST, Some(&legacy_params))
7968                    .expect("legacy tools/list keeps its exact parameter struct");
7969            assert_eq!(legacy_request.era(), ProtocolEra::Legacy2024);
7970            assert_eq!(
7971                legacy_request
7972                    .encode_params()
7973                    .expect("legacy request re-encodes")
7974                    .expect("list request owns a parameter object"),
7975                legacy_params
7976            );
7977            let legacy_wire = r#"{"tools":[],"nextCursor":""}"#;
7978            let legacy_result = legacy_request
7979                .decode_result(legacy_wire)
7980                .expect("legacy result selects the legacy payload");
7981            assert!(matches!(
7982                legacy_result,
7983                CoreResult::Legacy(LegacyCoreResult::ToolsList(_))
7984            ));
7985            assert_eq!(
7986                legacy_result.encode().expect("legacy result re-encodes"),
7987                legacy_wire
7988            );
7989        }
7990    }
7991
7992    #[test]
7993    fn final_tools_call_result_preserves_absent_and_explicit_null_structured_content() {
7994        let params = serde_json::json!({
7995            "_meta": {
7996                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
7997                "io.modelcontextprotocol/clientCapabilities": {}
7998            },
7999            "name": "nullable"
8000        });
8001        let request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&params))
8002            .expect("final tools/call request decodes");
8003
8004        let absent_wire = r#"{"resultType":"complete","content":[]}"#;
8005        let absent = request
8006            .decode_result(absent_wire)
8007            .expect("absent structuredContent is valid");
8008        let CoreResult::Final(FinalCoreResult::ToolsCall { result, .. }) = &absent else {
8009            panic!("final tools/call complete result");
8010        };
8011        assert!(result.payload.structured_content.is_none());
8012        assert_eq!(
8013            absent
8014                .encode()
8015                .expect("absent structuredContent re-encodes"),
8016            absent_wire
8017        );
8018
8019        let null_wire = r#"{"resultType":"complete","content":[],"structuredContent":null}"#;
8020        let explicit_null = request
8021            .decode_result(null_wire)
8022            .expect("explicit-null structuredContent is a present JSON value");
8023        let CoreResult::Final(FinalCoreResult::ToolsCall { result, .. }) = &explicit_null else {
8024            panic!("final tools/call complete result");
8025        };
8026        assert_eq!(result.payload.structured_content, Some(Value::Null));
8027        assert_eq!(
8028            explicit_null
8029                .encode()
8030                .expect("explicit-null structuredContent re-encodes"),
8031            null_wire
8032        );
8033    }
8034
8035    #[cfg(feature = "tasks")]
8036    #[test]
8037    fn final_tools_call_task_result_selects_a_disjoint_typed_branch() {
8038        let params = serde_json::json!({
8039            "_meta": {
8040                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
8041                "io.modelcontextprotocol/clientCapabilities": {}
8042            },
8043            "name": "long-running"
8044        });
8045        let request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&params))
8046            .expect("final tools/call request admits the task result branch");
8047        let accepted = serde_json::json!({
8048            "resultType": "task",
8049            "taskId": "task-1",
8050            "status": "working",
8051            "createdAt": "2026-07-28T12:00:00.000Z",
8052            "lastUpdatedAt": "2026-07-28T12:00:00.000Z",
8053            "ttlMs": null,
8054            "_meta": {
8055                "io.modelcontextprotocol/serverInfo": {
8056                    "name": "task-server",
8057                    "version": "1.0.0"
8058                }
8059            },
8060            "com.example/opaque": {"retained": true}
8061        });
8062        let wire = serde_json::to_string(&accepted).expect("task result serializes");
8063
8064        let decoded = request
8065            .decode_result(&wire)
8066            .expect("final tools/call task result decodes");
8067        let CoreResult::Final(final_result) = &decoded else {
8068            panic!("tools/call must select a final result branch");
8069        };
8070        let FinalCoreResult::ToolsCallTask { result } = final_result else {
8071            panic!("tools/call must select the task result branch");
8072        };
8073        assert_eq!(result.task.base().task_id.as_str(), "task-1");
8074        assert_eq!(
8075            final_result
8076                .protected_metadata_seal()
8077                .expect("task result metadata seal is typed"),
8078            FinalResultMetadataSeal {
8079                family: FinalResultMetadataFamily::ToolsCallTask,
8080                server_info: FinalResultServerInfo::Common(Some(
8081                    Implementation::try_new("task-server", "1.0.0")
8082                        .expect("task server identity is valid"),
8083                )),
8084                subscription_id: None,
8085            },
8086            "task metadata seals serverInfo while retaining unrelated open entries"
8087        );
8088        assert_eq!(
8089            result.additional.get("com.example/opaque"),
8090            Some(&serde_json::json!({"retained": true}))
8091        );
8092        assert_eq!(
8093            serde_json::from_str::<Value>(&decoded.encode().expect("task result re-encodes"))
8094                .expect("encoded task result is JSON"),
8095            accepted,
8096            "the typed task branch preserves the task result and inert siblings"
8097        );
8098    }
8099
8100    #[cfg(feature = "tasks")]
8101    #[test]
8102    fn final_tools_call_task_response_uses_admitted_raw_result_source() {
8103        let params = serde_json::json!({
8104            "_meta": {
8105                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
8106                "io.modelcontextprotocol/clientCapabilities": {}
8107            },
8108            "name": "long-running"
8109        });
8110        let request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&params))
8111            .expect("final tools/call request admits the task result branch");
8112        let frame = r#"{"jsonrpc":"2.0","result":{"resultType":"task","taskId":"task-1","status":"completed","createdAt":"2026-07-28T12:00:00.000Z","lastUpdatedAt":"2026-07-28T12:00:00.000Z","ttlMs":null,"result":{"x-first":1.20e+4,"content":[],"x-second":123456789012345678901234567890}},"id":91}"#;
8113        let admission = crate::decode_strict_jsonrpc_response(frame.as_bytes(), frame.len())
8114            .expect("bounded JSON-RPC admission retains the exact result member");
8115        let result_source = admission
8116            .raw_result()
8117            .expect("successful response has an exact result source");
8118        let decoded = request
8119            .decode_response_result(admission.response(), result_source)
8120            .expect("Tasks decoder consumes the admitted result source");
8121        assert_eq!(
8122            decoded.encode().expect("admitted task result re-encodes"),
8123            result_source,
8124            "response ingress preserves nested Tasks member order and numeric lexemes"
8125        );
8126    }
8127
8128    #[cfg(all(feature = "tasks", feature = "legacy-2024-11-05"))]
8129    #[test]
8130    fn final_tools_call_task_result_rejections_leave_decode_state_unchanged() {
8131        let call_params = serde_json::json!({
8132            "_meta": {
8133                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
8134                "io.modelcontextprotocol/clientCapabilities": {}
8135            },
8136            "name": "long-running"
8137        });
8138        let call = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_CALL, Some(&call_params))
8139            .expect("baseline final tools/call request");
8140        let accepted = serde_json::json!({
8141            "resultType": "task",
8142            "taskId": "task-1",
8143            "status": "working",
8144            "createdAt": "2026-07-28T12:00:00.000Z",
8145            "lastUpdatedAt": "2026-07-28T12:00:00.000Z",
8146            "ttlMs": null
8147        });
8148        let accepted_wire = serde_json::to_string(&accepted).expect("task result serializes");
8149        let baseline = call
8150            .decode_result(&accepted_wire)
8151            .expect("baseline task result decodes");
8152
8153        let mut wrong_result = accepted.clone();
8154        wrong_result["resultType"] = serde_json::json!("complete");
8155        assert!(
8156            matches!(
8157                call.decode_result(
8158                    &serde_json::to_string(&wrong_result)
8159                        .expect("one-field wrong result serializes")
8160                ),
8161                Err(CoreDispatchError::InvalidResult {
8162                    era: ProtocolEra::Modern2026,
8163                    method: TOOLS_CALL,
8164                })
8165            ),
8166            "changing only resultType cannot reinterpret a task result as a complete tools/call result"
8167        );
8168
8169        let mut missing_ttl = accepted.clone();
8170        missing_ttl
8171            .as_object_mut()
8172            .expect("task result is an object")
8173            .remove("ttlMs");
8174        assert!(
8175            matches!(
8176                call.decode_result(
8177                    &serde_json::to_string(&missing_ttl)
8178                        .expect("one-field missing TTL task serializes")
8179                ),
8180                Err(CoreDispatchError::InvalidResult {
8181                    era: ProtocolEra::Modern2026,
8182                    method: TOOLS_CALL,
8183                })
8184            ),
8185            "unlike cacheable catalog peers, Tasks keep ttlMs required"
8186        );
8187
8188        let mut negative_ttl = accepted.clone();
8189        negative_ttl["ttlMs"] = serde_json::json!(-1);
8190        assert!(
8191            matches!(
8192                call.decode_result(
8193                    &serde_json::to_string(&negative_ttl)
8194                        .expect("one-field negative TTL task serializes")
8195                ),
8196                Err(CoreDispatchError::InvalidResult {
8197                    era: ProtocolEra::Modern2026,
8198                    method: TOOLS_CALL,
8199                })
8200            ),
8201            "the composed Tasks profile rejects a negative ttlMs"
8202        );
8203        assert_eq!(
8204            baseline.encode().expect("baseline task re-encodes"),
8205            call.decode_result(&accepted_wire)
8206                .expect("accepted task remains decodable")
8207                .encode()
8208                .expect("accepted task re-encodes"),
8209            "rejected ttlMs mutation cannot alter accepted task state"
8210        );
8211
8212        let list_params = serde_json::json!({
8213            "_meta": {
8214                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
8215                "io.modelcontextprotocol/clientCapabilities": {}
8216            }
8217        });
8218        let wrong_method =
8219            CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_LIST, Some(&list_params))
8220                .expect("final tools/list request");
8221        assert!(
8222            matches!(
8223                wrong_method.decode_result(&accepted_wire),
8224                Err(CoreDispatchError::UnexpectedFinalResultType { method: TOOLS_LIST })
8225            ),
8226            "the task result discriminator belongs only to final tools/call"
8227        );
8228
8229        let reaccepted = call
8230            .decode_result(&accepted_wire)
8231            .expect("wrong result and method do not mutate task result decoding");
8232        assert_eq!(
8233            serde_json::from_str::<Value>(&baseline.encode().expect("baseline encodes"))
8234                .expect("baseline is JSON"),
8235            serde_json::from_str::<Value>(&reaccepted.encode().expect("reaccepted encodes"))
8236                .expect("reaccepted result is JSON"),
8237            "the rejected one-field resultType cannot mutate the admitted task result"
8238        );
8239
8240        let legacy_params = serde_json::json!({"name": "long-running"});
8241        let legacy = CoreRequest::decode(ProtocolEra::Legacy2024, TOOLS_CALL, Some(&legacy_params))
8242            .expect("legacy tools/call request");
8243        let legacy_wire = r#"{"content":[{"type":"text","text":"ready"}]}"#;
8244        let legacy_baseline = legacy
8245            .decode_result(legacy_wire)
8246            .expect("legacy tools/call result remains admitted");
8247        let legacy_planted = r#"{"resultType":"task","content":[{"type":"text","text":"ready"}]}"#;
8248        assert!(
8249            matches!(
8250                legacy.decode_result(legacy_planted),
8251                Err(CoreDispatchError::CrossEraResultType { method: TOOLS_CALL })
8252            ),
8253            "adding only the final task discriminator cannot alter legacy tools/call decoding"
8254        );
8255        assert_eq!(
8256            legacy
8257                .decode_result(legacy_wire)
8258                .expect("legacy rejection leaves baseline decoding intact")
8259                .encode()
8260                .expect("legacy reaccepted result encodes"),
8261            legacy_baseline.encode().expect("legacy baseline encodes"),
8262            "the final task branch leaves exact legacy result decoding unchanged"
8263        );
8264    }
8265
8266    #[test]
8267    fn final_core_input_required_results_round_trip_for_mrtr_methods() {
8268        let metadata = serde_json::json!({
8269            "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
8270            "io.modelcontextprotocol/clientCapabilities": {}
8271        });
8272        let requests = [
8273            (
8274                TOOLS_CALL,
8275                serde_json::json!({
8276                    "_meta": metadata,
8277                    "name": "collect-input"
8278                }),
8279            ),
8280            (
8281                RESOURCES_READ,
8282                serde_json::json!({
8283                    "_meta": metadata,
8284                    "uri": "file:///workspace/status"
8285                }),
8286            ),
8287            (
8288                PROMPTS_GET,
8289                serde_json::json!({
8290                    "_meta": metadata,
8291                    "name": "collect-input"
8292                }),
8293            ),
8294        ];
8295        let wire = r#"{"resultType":"input_required","inputRequests":{"roots":{"method":"roots/list"}},"requestState":"retry-7","ttlMs":-1,"cacheScope":"private","com.example/opaque":{"retained":true}}"#;
8296
8297        for (method, params) in requests {
8298            let request = CoreRequest::decode(ProtocolEra::Modern2026, method, Some(&params))
8299                .expect("each final MRTR-capable request decodes");
8300            let result = request
8301                .decode_result(wire)
8302                .expect("input-required result is admitted for the selected method");
8303            let input_required = match (&result, method) {
8304                (
8305                    CoreResult::Final(FinalCoreResult::ToolsCallInputRequired { result, .. }),
8306                    TOOLS_CALL,
8307                )
8308                | (
8309                    CoreResult::Final(FinalCoreResult::ResourcesReadInputRequired {
8310                        result, ..
8311                    }),
8312                    RESOURCES_READ,
8313                )
8314                | (
8315                    CoreResult::Final(FinalCoreResult::PromptsGetInputRequired { result, .. }),
8316                    PROMPTS_GET,
8317                ) => result,
8318                _ => panic!("{method} must select its final input-required branch"),
8319            };
8320            assert!(
8321                input_required
8322                    .input_requests()
8323                    .and_then(|requests| requests.get("roots"))
8324                    .is_some(),
8325                "{method} preserves the exact MRTR input request map"
8326            );
8327            assert_eq!(input_required.request_state(), Some("retry-7"));
8328            let extras = input_required.extras.members();
8329            assert_eq!(
8330                extras.len(),
8331                3,
8332                "{method} keeps complete-result cache lookalikes inert on input_required"
8333            );
8334            for name in ["ttlMs", "cacheScope", "com.example/opaque"] {
8335                assert!(
8336                    extras.iter().any(|member| member.name == name),
8337                    "{method} retains inert {name} on input_required"
8338                );
8339            }
8340            assert_eq!(
8341                serde_json::from_str::<Value>(
8342                    &result.encode().expect("input-required result re-encodes")
8343                )
8344                .expect("input-required result encoding is JSON"),
8345                serde_json::from_str::<Value>(wire).expect("input-required result fixture is JSON"),
8346                "{method} retains input-required state and inert lookalikes"
8347            );
8348        }
8349    }
8350
8351    #[test]
8352    fn final_core_rejects_input_required_for_ineligible_method_without_mutation() {
8353        let params = serde_json::json!({
8354            "_meta": {
8355                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
8356                "io.modelcontextprotocol/clientCapabilities": {}
8357            }
8358        });
8359        let request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_LIST, Some(&params))
8360            .expect("final tools/list request decodes");
8361        let accepted = r#"{"resultType":"complete","tools":[],"ttlMs":0,"cacheScope":"private","inputRequests":{"roots":{"method":"roots/list"}},"requestState":"retry-7"}"#;
8362        let baseline = request
8363            .decode_result(accepted)
8364            .expect("complete tools/list retains foreign open siblings inertly");
8365        let planted = accepted.replacen("\"complete\"", "\"input_required\"", 1);
8366
8367        assert!(
8368            matches!(
8369                request.decode_result(&planted),
8370                Err(CoreDispatchError::UnexpectedFinalResultType { method: TOOLS_LIST })
8371            ),
8372            "changing only resultType cannot make tools/list MRTR-capable"
8373        );
8374        assert_eq!(
8375            baseline
8376                .encode()
8377                .expect("accepted complete result remains encodable"),
8378            accepted,
8379            "the rejected input-required discriminator leaves the accepted result unchanged"
8380        );
8381    }
8382
8383    #[test]
8384    fn core_completion_preserves_legacy_and_final_payload_semantics() {
8385        #[cfg(feature = "legacy-2024-11-05")]
8386        {
8387            let legacy_params = serde_json::json!({
8388                "ref": {"type": "ref/prompt", "name": "deploy"},
8389                "argument": {"name": "environment", "value": "sta"}
8390            });
8391            let legacy_request = CoreRequest::decode(
8392                ProtocolEra::Legacy2024,
8393                COMPLETION_COMPLETE,
8394                Some(&legacy_params),
8395            )
8396            .expect("exact legacy completion request is typed");
8397            assert_eq!(legacy_request.era(), ProtocolEra::Legacy2024);
8398            assert_eq!(legacy_request.method(), COMPLETION_COMPLETE);
8399            assert_eq!(
8400                legacy_request
8401                    .encode_params()
8402                    .expect("legacy completion request re-encodes")
8403                    .expect("completion owns an object parameter"),
8404                legacy_params
8405            );
8406
8407            let legacy_wire = r#"{"completion":{"values":["staging"],"total":1,"hasMore":false}}"#;
8408            let legacy_result = legacy_request
8409                .decode_result(legacy_wire)
8410                .expect("exact legacy completion result is typed");
8411            let CoreResult::Legacy(LegacyCoreResult::Completion(result)) = &legacy_result else {
8412                panic!("legacy completion result");
8413            };
8414            assert_eq!(result.completion.values, vec!["staging".to_owned()]);
8415            assert_eq!(result.completion.total, Some(1));
8416            assert_eq!(result.completion.has_more, Some(false));
8417            let encoded_legacy: Value = serde_json::from_str(
8418                &legacy_result
8419                    .encode()
8420                    .expect("legacy completion re-encodes"),
8421            )
8422            .expect("legacy completion encoding is JSON");
8423            assert_eq!(
8424                encoded_legacy["completion"]["values"],
8425                serde_json::json!(["staging"])
8426            );
8427            assert_eq!(encoded_legacy["completion"]["total"], 1);
8428            assert_eq!(encoded_legacy["completion"]["hasMore"], false);
8429        }
8430
8431        let final_params = serde_json::json!({
8432            "_meta": {
8433                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
8434                "io.modelcontextprotocol/clientCapabilities": {}
8435            },
8436            "ref": {
8437                "type": "ref/prompt",
8438                "name": "deploy",
8439                "title": "Deploy application"
8440            },
8441            "argument": {"name": "environment", "value": "pro"},
8442            "context": {"arguments": {"region": "us-east-1"}}
8443        });
8444        let final_request = CoreRequest::decode(
8445            ProtocolEra::Modern2026,
8446            COMPLETION_COMPLETE,
8447            Some(&final_params),
8448        )
8449        .expect("final completion request is typed through final metadata");
8450        assert_eq!(final_request.era(), ProtocolEra::Modern2026);
8451        assert_eq!(final_request.method(), COMPLETION_COMPLETE);
8452        let CoreRequest::Final(FinalCoreRequest::Completion(params)) = &final_request else {
8453            panic!("final completion request");
8454        };
8455        assert!(matches!(
8456            &params.reference,
8457            FinalCompletionReference::PromptWithTitle { title, .. } if title == "Deploy application"
8458        ));
8459        assert_eq!(
8460            final_request
8461                .encode_params()
8462                .expect("final completion request re-encodes")
8463                .expect("completion owns an object parameter"),
8464            final_params
8465        );
8466
8467        let final_wire = r#"{"resultType":"complete","completion":{"values":["production"],"total":1,"hasMore":false},"extension":{"opaque":true}}"#;
8468        let final_result = final_request
8469            .decode_result(final_wire)
8470            .expect("final complete result selects the completion payload");
8471        let CoreResult::Final(FinalCoreResult::Completion { result, diagnostic }) = &final_result
8472        else {
8473            panic!("final completion result");
8474        };
8475        assert_eq!(diagnostic, &None);
8476        assert_eq!(
8477            result.payload.completion.values,
8478            vec!["production".to_owned()]
8479        );
8480        assert_eq!(
8481            result.payload.completion.total,
8482            Some(JsonInteger::from(1_i64))
8483        );
8484        assert_eq!(result.payload.completion.has_more, Some(false));
8485        assert_eq!(
8486            result
8487                .extras
8488                .members()
8489                .iter()
8490                .map(|member| member.name.as_str())
8491                .collect::<Vec<_>>(),
8492            ["extension"]
8493        );
8494        let encoded_final: Value =
8495            serde_json::from_str(&final_result.encode().expect("final completion re-encodes"))
8496                .expect("final completion encoding is JSON");
8497        let completion = encoded_final["completion"]
8498            .as_object()
8499            .expect("final completion remains an object");
8500        assert_eq!(encoded_final["resultType"], "complete");
8501        assert_eq!(completion["values"], serde_json::json!(["production"]));
8502        assert_eq!(
8503            completion["total"]
8504                .as_number()
8505                .map(serde_json::Number::as_str),
8506            Some("1")
8507        );
8508        assert_eq!(completion.get("hasMore"), Some(&Value::Bool(false)));
8509        assert!(
8510            completion.contains_key("total") && completion.contains_key("hasMore"),
8511            "present final completion optionals remain present after re-encoding"
8512        );
8513        assert_eq!(
8514            encoded_final["extension"],
8515            serde_json::json!({"opaque": true})
8516        );
8517    }
8518
8519    #[test]
8520    fn completion_values_enforce_the_one_hundred_value_limit() {
8521        let accepted = CompletionValues {
8522            values: (0..MAX_COMPLETION_VALUES)
8523                .map(|index| format!("value-{index}"))
8524                .collect(),
8525            total: Some(MAX_COMPLETION_VALUES as i64),
8526            has_more: Some(false),
8527        };
8528        let encoded = serde_json::to_value(&accepted).expect("one hundred values serialize");
8529        assert_eq!(
8530            encoded["values"].as_array().map(Vec::len),
8531            Some(MAX_COMPLETION_VALUES)
8532        );
8533
8534        let too_many = (0..=MAX_COMPLETION_VALUES)
8535            .map(|index| format!("value-{index}"))
8536            .collect::<Vec<_>>();
8537        assert!(
8538            serde_json::from_value::<CompletionValues>(serde_json::json!({
8539                "values": too_many
8540            }))
8541            .is_err(),
8542            "a peer cannot admit 101 completion values"
8543        );
8544        assert!(
8545            serde_json::to_value(CompletionValues {
8546                values: (0..=MAX_COMPLETION_VALUES)
8547                    .map(|index| format!("value-{index}"))
8548                    .collect(),
8549                total: None,
8550                has_more: None,
8551            })
8552            .is_err(),
8553            "locally authored results cannot emit 101 completion values"
8554        );
8555    }
8556
8557    #[test]
8558    fn final_completion_values_preserve_arbitrary_precision_totals_at_the_value_bound() {
8559        let admitted = FinalCompletionValues {
8560            values: (0..MAX_COMPLETION_VALUES)
8561                .map(|index| format!("value-{index}"))
8562                .collect(),
8563            total: Some(
8564                serde_json::from_str("922337203685477580812345678901234567890")
8565                    .expect("an arbitrary-precision JSON integer"),
8566            ),
8567            has_more: Some(false),
8568        };
8569        let wire = serde_json::to_value(&admitted).expect("100 final completion values serialize");
8570        assert_eq!(
8571            wire["values"].as_array().map(Vec::len),
8572            Some(MAX_COMPLETION_VALUES)
8573        );
8574        assert_eq!(
8575            wire["total"].as_number().map(serde_json::Number::as_str),
8576            Some("922337203685477580812345678901234567890")
8577        );
8578        assert!(
8579            serde_json::to_value(FinalCompletionValues {
8580                values: (0..=MAX_COMPLETION_VALUES)
8581                    .map(|index| format!("value-{index}"))
8582                    .collect(),
8583                total: None,
8584                has_more: None,
8585            })
8586            .is_err(),
8587            "a locally authored final result cannot emit 101 completion values"
8588        );
8589    }
8590
8591    #[test]
8592    fn final_completion_values_diagnose_negative_peer_totals_and_refuse_local_emission() {
8593        let accepted = serde_json::json!({
8594            "values": ["stable"],
8595            "total": 0,
8596            "hasMore": false,
8597        });
8598        let admitted = serde_json::from_value::<FinalCompletionValues>(accepted.clone())
8599            .expect("a nonnegative final completion total and bounded candidate are admitted");
8600        assert_eq!(
8601            serde_json::to_value(&admitted).expect("the admitted final completion re-encodes"),
8602            accepted
8603        );
8604
8605        let mut negative_total = accepted.clone();
8606        negative_total["total"] = serde_json::json!(-1);
8607        let admitted_negative = serde_json::from_value::<FinalCompletionValues>(negative_total)
8608            .expect("a schema-valid negative peer total remains decodable");
8609        assert_eq!(
8610            admitted_negative.peer_diagnostic(),
8611            Some(FinalCompletionPeerDiagnostic::NegativeTotal),
8612            "a negative peer total has bounded compatibility diagnostics"
8613        );
8614        assert!(
8615            serde_json::to_value(&admitted_negative).is_err(),
8616            "a peer-only negative total cannot be forwarded as local provider output"
8617        );
8618        assert!(
8619            serde_json::to_value(FinalCompletionValues {
8620                values: vec!["stable".to_owned()],
8621                total: Some(JsonInteger::from(-1_i64)),
8622                has_more: Some(false),
8623            })
8624            .is_err(),
8625            "a locally authored final completion cannot emit a negative total"
8626        );
8627
8628        let at_value_bound = serde_json::json!({
8629            "values": ["v".repeat(MAX_FINAL_COMPLETION_VALUE_BYTES)],
8630            "total": 1,
8631        });
8632        assert!(
8633            serde_json::from_value::<FinalCompletionValues>(at_value_bound).is_ok(),
8634            "a final completion candidate at the per-value byte limit is admitted"
8635        );
8636
8637        let one_byte_over = serde_json::json!({
8638            "values": ["v".repeat(MAX_FINAL_COMPLETION_VALUE_BYTES + 1)],
8639            "total": 1,
8640        });
8641        assert!(
8642            serde_json::from_value::<FinalCompletionValues>(one_byte_over).is_err(),
8643            "adding one candidate byte crosses the final completion limiter"
8644        );
8645
8646        let at_aggregate_bound = serde_json::json!({
8647            "values": vec!["v".repeat(MAX_FINAL_COMPLETION_VALUE_BYTES);
8648                MAX_FINAL_COMPLETION_VALUES_BYTES / MAX_FINAL_COMPLETION_VALUE_BYTES],
8649            "total": MAX_FINAL_COMPLETION_VALUES_BYTES / MAX_FINAL_COMPLETION_VALUE_BYTES,
8650        });
8651        assert!(
8652            serde_json::from_value::<FinalCompletionValues>(at_aggregate_bound.clone()).is_ok(),
8653            "final completion candidates at the aggregate byte limit are admitted"
8654        );
8655
8656        let mut aggregate_one_byte_over = at_aggregate_bound;
8657        aggregate_one_byte_over["values"]
8658            .as_array_mut()
8659            .expect("completion values array")
8660            .push(serde_json::json!("v"));
8661        assert!(
8662            serde_json::from_value::<FinalCompletionValues>(aggregate_one_byte_over).is_err(),
8663            "adding one aggregate candidate byte crosses the final completion limiter"
8664        );
8665
8666        let legacy_negative = serde_json::json!({
8667            "values": ["stable"],
8668            "total": -1,
8669        });
8670        assert!(
8671            serde_json::from_value::<CompletionValues>(legacy_negative).is_ok(),
8672            "exact MCP 2024-11-05 retains its signed completion total schema"
8673        );
8674    }
8675
8676    #[test]
8677    fn final_completion_context_preserves_presence_and_exact_bounds() {
8678        let meta = serde_json::json!({
8679            "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
8680            "io.modelcontextprotocol/clientCapabilities": {}
8681        });
8682        let request_without_context = serde_json::json!({
8683            "_meta": meta,
8684            "ref": {"type": "ref/prompt", "name": "deploy"},
8685            "argument": {"name": "environment", "value": "pro"}
8686        });
8687        let CoreRequest::Final(FinalCoreRequest::Completion(without_context)) =
8688            CoreRequest::decode(
8689                ProtocolEra::Modern2026,
8690                COMPLETION_COMPLETE,
8691                Some(&request_without_context),
8692            )
8693            .expect("an absent final completion context is valid")
8694        else {
8695            panic!("final completion request");
8696        };
8697        assert!(without_context.context.is_none());
8698
8699        let request_with_empty_context = serde_json::json!({
8700            "_meta": meta,
8701            "ref": {"type": "ref/prompt", "name": "deploy"},
8702            "argument": {"name": "environment", "value": "pro"},
8703            "context": {"arguments": {}}
8704        });
8705        let CoreRequest::Final(FinalCoreRequest::Completion(with_empty_context)) =
8706            CoreRequest::decode(
8707                ProtocolEra::Modern2026,
8708                COMPLETION_COMPLETE,
8709                Some(&request_with_empty_context),
8710            )
8711            .expect("a present empty final completion context is valid")
8712        else {
8713            panic!("final completion request");
8714        };
8715        assert!(
8716            with_empty_context
8717                .context
8718                .as_ref()
8719                .and_then(|context| context.arguments.as_ref())
8720                .is_some_and(BTreeMap::is_empty),
8721            "present empty context arguments remain distinct from an absent context"
8722        );
8723
8724        let mut bounded_arguments = serde_json::Map::new();
8725        for index in 0..MAX_COMPLETION_CONTEXT_ARGUMENTS {
8726            bounded_arguments.insert(format!("key-{index}"), Value::String("value".to_owned()));
8727        }
8728        let request_at_bound = serde_json::json!({
8729            "_meta": meta,
8730            "ref": {"type": "ref/prompt", "name": "deploy"},
8731            "argument": {"name": "environment", "value": "pro"},
8732            "context": {"arguments": bounded_arguments}
8733        });
8734        let at_bound = CoreRequest::decode(
8735            ProtocolEra::Modern2026,
8736            COMPLETION_COMPLETE,
8737            Some(&request_at_bound),
8738        )
8739        .expect("the exact completion-context entry limit is valid");
8740        assert_eq!(
8741            at_bound
8742                .encode_params()
8743                .expect("bounded final completion context re-encodes")
8744                .expect("completion has parameters"),
8745            request_at_bound,
8746            "the admitted context map retains every exact string entry"
8747        );
8748    }
8749
8750    #[test]
8751    fn final_completion_context_encoded_bytes_honor_short_escapes_and_exact_boundary() {
8752        assert_eq!(
8753            encoded_json_string_bytes("\u{0008}\t\n\u{000c}\r\u{0000}\"\\"),
8754            22,
8755            "JSON uses two-byte escapes for backspace, tab, newline, form feed, carriage return, quote, and backslash"
8756        );
8757
8758        let mut arguments = BTreeMap::new();
8759        let mut encoded_bytes = 2_usize;
8760        for index in 0..15 {
8761            let key = format!("key-{index}");
8762            let value = "v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES);
8763            encoded_bytes = next_completion_context_encoded_bytes(
8764                encoded_bytes,
8765                !arguments.is_empty(),
8766                &key,
8767                &value,
8768            )
8769            .expect("the first fifteen maximum values fit the aggregate bound");
8770            arguments.insert(key, value);
8771        }
8772        let final_key = "last".to_owned();
8773        let encoded_empty_final_value =
8774            next_completion_context_encoded_bytes(encoded_bytes, true, &final_key, "")
8775                .expect("an empty final value fits the aggregate bound");
8776        let final_value =
8777            "v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES - encoded_empty_final_value);
8778        assert!(final_value.len() <= MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES);
8779        assert_eq!(
8780            next_completion_context_encoded_bytes(encoded_bytes, true, &final_key, &final_value,),
8781            Some(MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES),
8782            "the final value reaches the aggregate bound exactly"
8783        );
8784        arguments.insert(final_key.clone(), final_value.clone());
8785        let admitted = FinalCompletionContext {
8786            arguments: Some(arguments),
8787        };
8788        let wire = serde_json::to_string(&admitted).expect("the exact aggregate bound serializes");
8789        let decoded: FinalCompletionContext =
8790            serde_json::from_str(&wire).expect("bounded string seeds admit the exact bound");
8791        assert_eq!(decoded, admitted);
8792
8793        let mut one_byte_over = admitted;
8794        one_byte_over
8795            .arguments
8796            .as_mut()
8797            .expect("context arguments are present")
8798            .get_mut(&final_key)
8799            .expect("final boundary value is present")
8800            .push('v');
8801        assert!(
8802            serde_json::to_string(&one_byte_over).is_err(),
8803            "one additional encoded byte must reject"
8804        );
8805
8806        let oversized_key_wire = format!(
8807            r#"{{"arguments":{{"{}":"value"}}}}"#,
8808            "k".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES + 1)
8809        );
8810        assert!(
8811            serde_json::from_str::<FinalCompletionContext>(&oversized_key_wire).is_err(),
8812            "the bounded key seed rejects before retaining an oversized key"
8813        );
8814
8815        let maximum_key_wire = format!(
8816            r#"{{"arguments":{{"{}":"value"}}}}"#,
8817            "k".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES)
8818        );
8819        assert!(
8820            serde_json::from_str::<FinalCompletionContext>(&maximum_key_wire).is_ok(),
8821            "the bounded key seed admits exactly 1024 key bytes"
8822        );
8823
8824        let maximum_value_wire = format!(
8825            r#"{{"arguments":{{"key":"{}"}}}}"#,
8826            "v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES)
8827        );
8828        assert!(
8829            serde_json::from_str::<FinalCompletionContext>(&maximum_value_wire).is_ok(),
8830            "the bounded value seed admits exactly 16384 value bytes"
8831        );
8832        let oversized_value_wire = format!(
8833            r#"{{"arguments":{{"key":"{}"}}}}"#,
8834            "v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES + 1)
8835        );
8836        assert!(
8837            serde_json::from_str::<FinalCompletionContext>(&oversized_value_wire).is_err(),
8838            "the bounded value seed rejects before retaining an oversized value"
8839        );
8840    }
8841
8842    #[test]
8843    fn jsonrpc_ingress_validates_final_completion_context_from_raw_params() {
8844        let final_request = format!(
8845            r#"{{"jsonrpc":"2.0","method":"completion/complete","params":{{"_meta":{{"io.modelcontextprotocol/protocolVersion":"{FINAL_PROTOCOL_VERSION}","io.modelcontextprotocol/clientCapabilities":{{}}}},"ref":{{"type":"ref/prompt","name":"deploy"}},"argument":{{"name":"environment","value":"pro"}},"context":{{"arguments":{{"key":"{}"}}}}}},"id":1}}"#,
8846            "v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES)
8847        );
8848        assert!(
8849            crate::jsonrpc::decode_strict_jsonrpc_message(
8850                final_request.as_bytes(),
8851                MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES * 2,
8852            )
8853            .is_ok(),
8854            "strict JSON-RPC ingress admits a final context value at the raw-source bound"
8855        );
8856
8857        let oversized_final_request = format!(
8858            r#"{{"jsonrpc":"2.0","method":"completion/complete","params":{{"_meta":{{"io.modelcontextprotocol/protocolVersion":"{FINAL_PROTOCOL_VERSION}","io.modelcontextprotocol/clientCapabilities":{{}}}},"ref":{{"type":"ref/prompt","name":"deploy"}},"argument":{{"name":"environment","value":"pro"}},"context":{{"arguments":{{"key":"{}"}}}}}},"id":1}}"#,
8859            "v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES + 1)
8860        );
8861        assert!(
8862            crate::jsonrpc::decode_strict_jsonrpc_message(
8863                oversized_final_request.as_bytes(),
8864                MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES * 2,
8865            )
8866            .is_err(),
8867            "strict JSON-RPC ingress rejects an oversized final context before params become Value"
8868        );
8869
8870        let legacy_request = format!(
8871            r#"{{"jsonrpc":"2.0","method":"completion/complete","params":{{"ref":{{"type":"ref/prompt","name":"deploy"}},"argument":{{"name":"environment","value":"sta"}},"context":{{"arguments":{{"key":"{}"}}}}}},"id":1}}"#,
8872            "v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES + 1)
8873        );
8874        assert!(
8875            serde_json::from_str::<JsonRpcRequest>(&legacy_request).is_ok(),
8876            "legacy completion parameters without final metadata retain their existing wire path"
8877        );
8878    }
8879
8880    #[test]
8881    fn jsonrpc_ingress_bounds_every_duplicate_final_completion_context_before_serde() {
8882        fn final_completion_request(first: &str, second: &str) -> String {
8883            format!(
8884                r#"{{"jsonrpc":"2.0","method":"completion/complete","params":{{"_meta":{{"io.modelcontextprotocol/protocolVersion":"{FINAL_PROTOCOL_VERSION}","io.modelcontextprotocol/clientCapabilities":{{}}}},"ref":{{"type":"ref/prompt","name":"deploy"}},"argument":{{"name":"environment","value":"pro"}},"context":{{"arguments":{{"key":"{first}"}}}},"context":{{"arguments":{{"key":"{second}"}}}}}},"id":1}}"#
8885            )
8886        }
8887
8888        let small = "small";
8889        let oversized = r"\/".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES / 2 + 1);
8890        assert_eq!(
8891            oversized.len(),
8892            MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES + 2,
8893            "the fixture exceeds only the received raw value-byte bound"
8894        );
8895
8896        for (first, second, case) in [
8897            (oversized.as_str(), small, "oversized-first/small-second"),
8898            (small, oversized.as_str(), "small-first/oversized-second"),
8899        ] {
8900            let error =
8901                serde_json::from_str::<JsonRpcRequest>(&final_completion_request(first, second))
8902                    .expect_err("every final context occurrence must be raw-bounded before serde");
8903            assert!(
8904                error.to_string().contains(
8905                    "completion context argument value exceeds the maximum raw JSON byte limit"
8906                ),
8907                "{case} must fail at the raw bound rather than after serde reaches a duplicate context"
8908            );
8909        }
8910    }
8911
8912    #[test]
8913    fn jsonrpc_ingress_measures_received_completion_context_json_bytes() {
8914        fn final_completion_request(arguments: &str) -> String {
8915            let mut request = format!(
8916                r#"{{"jsonrpc":"2.0","method":"completion/complete","params":{{"_meta":{{"io.modelcontextprotocol/protocolVersion":"{FINAL_PROTOCOL_VERSION}","io.modelcontextprotocol/clientCapabilities":{{}}}},"ref":{{"type":"ref/prompt","name":"deploy"}},"argument":{{"name":"environment","value":"pro"}},"context":{{"arguments":"#
8917            );
8918            request.push_str(arguments);
8919            request.push_str(r#"}},"id":1}"#);
8920            request
8921        }
8922
8923        let escaped_key_at_bound = format!("{}{}", r"\u006b".repeat(170), "k".repeat(4));
8924        assert_eq!(
8925            escaped_key_at_bound.len(),
8926            MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES
8927        );
8928        let accepted_key =
8929            final_completion_request(&format!(r#"{{"{escaped_key_at_bound}":"value"}}"#));
8930        assert!(
8931            crate::jsonrpc::decode_strict_jsonrpc_message(
8932                accepted_key.as_bytes(),
8933                accepted_key.len(),
8934            )
8935            .is_ok(),
8936            "an escaped context key at the received-byte limit is admitted"
8937        );
8938        let rejected_key =
8939            final_completion_request(&format!(r#"{{"{escaped_key_at_bound}\u006b":"value"}}"#));
8940        assert!(
8941            crate::jsonrpc::decode_strict_jsonrpc_message(
8942                rejected_key.as_bytes(),
8943                rejected_key.len(),
8944            )
8945            .is_err(),
8946            "adding only one escaped key spelling crosses the raw key-byte limit"
8947        );
8948
8949        let escaped_value_at_bound = r"\/".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES / 2);
8950        assert_eq!(
8951            escaped_value_at_bound.len(),
8952            MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES
8953        );
8954        let accepted_value =
8955            final_completion_request(&format!(r#"{{"key":"{escaped_value_at_bound}"}}"#));
8956        assert!(
8957            crate::jsonrpc::decode_strict_jsonrpc_message(
8958                accepted_value.as_bytes(),
8959                accepted_value.len(),
8960            )
8961            .is_ok(),
8962            "an escaped context value at the received-byte limit is admitted"
8963        );
8964        let rejected_value =
8965            final_completion_request(&format!(r#"{{"key":"{escaped_value_at_bound}x"}}"#));
8966        assert!(
8967            crate::jsonrpc::decode_strict_jsonrpc_message(
8968                rejected_value.as_bytes(),
8969                rejected_value.len(),
8970            )
8971            .is_err(),
8972            "adding only one raw value byte crosses the received value-byte limit"
8973        );
8974
8975        let escaped_maximum_value = r"\/".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES / 2);
8976        let entries = (0..15)
8977            .map(|index| format!(r#""key-{index}":"{escaped_maximum_value}""#))
8978            .collect::<Vec<_>>();
8979        // The tail value's OPENING quote belongs to the prefix; the suffix
8980        // carries only the closing quote and brace. Omitting it made the
8981        // fixture invalid JSON and turned both assertions vacuous.
8982        let prefix = format!(r#"{{{},"tail":""#, entries.join(","));
8983        let suffix = r#""}"#;
8984        let tail_bytes = MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES - prefix.len() - suffix.len();
8985        assert!(tail_bytes <= MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES);
8986        let tail = format!(
8987            "{}{}",
8988            r"\/".repeat(tail_bytes / 2),
8989            "x".repeat(tail_bytes % 2)
8990        );
8991        let aggregate_at_bound = format!("{prefix}{tail}{suffix}");
8992        assert_eq!(
8993            aggregate_at_bound.len(),
8994            MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES,
8995            "the fixture counts the exact received arguments-object bytes, including \\/ escapes"
8996        );
8997        let accepted_aggregate = final_completion_request(&aggregate_at_bound);
8998        assert!(
8999            crate::jsonrpc::decode_strict_jsonrpc_message(
9000                accepted_aggregate.as_bytes(),
9001                accepted_aggregate.len(),
9002            )
9003            .is_ok(),
9004            "an alternate-escape arguments object at the received-byte limit is admitted"
9005        );
9006        let rejected_aggregate = final_completion_request(&format!("{prefix}{tail}x{suffix}"));
9007        assert!(
9008            crate::jsonrpc::decode_strict_jsonrpc_message(
9009                rejected_aggregate.as_bytes(),
9010                rejected_aggregate.len(),
9011            )
9012            .is_err(),
9013            "adding only one raw JSON byte makes the arguments object too large"
9014        );
9015    }
9016
9017    #[test]
9018    fn final_completion_context_rejects_one_field_null_and_one_entry_over_bound() {
9019        let meta = serde_json::json!({
9020            "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
9021            "io.modelcontextprotocol/clientCapabilities": {}
9022        });
9023        let accepted = serde_json::json!({
9024            "_meta": meta,
9025            "ref": {"type": "ref/resource", "uri": "file:///templates/{name}"},
9026            "argument": {"name": "name", "value": "prod"},
9027            "context": {"arguments": {"region": "us-east-1"}}
9028        });
9029        let baseline = CoreRequest::decode(
9030            ProtocolEra::Modern2026,
9031            COMPLETION_COMPLETE,
9032            Some(&accepted),
9033        )
9034        .expect("baseline final completion context is valid");
9035
9036        let mut null_arguments = accepted.clone();
9037        null_arguments["context"]["arguments"] = Value::Null;
9038        assert!(
9039            CoreRequest::decode(
9040                ProtocolEra::Modern2026,
9041                COMPLETION_COMPLETE,
9042                Some(&null_arguments),
9043            )
9044            .is_err(),
9045            "changing only context.arguments to null must reject"
9046        );
9047
9048        let mut null_context = accepted.clone();
9049        null_context["context"] = Value::Null;
9050        assert!(
9051            CoreRequest::decode(
9052                ProtocolEra::Modern2026,
9053                COMPLETION_COMPLETE,
9054                Some(&null_context),
9055            )
9056            .is_err(),
9057            "changing only context to null must reject"
9058        );
9059
9060        let mut at_bound_arguments = serde_json::Map::new();
9061        for index in 0..MAX_COMPLETION_CONTEXT_ARGUMENTS {
9062            at_bound_arguments.insert(format!("key-{index}"), Value::String("value".to_owned()));
9063        }
9064        let mut one_over_bound = accepted.clone();
9065        one_over_bound["context"]["arguments"] = Value::Object(at_bound_arguments);
9066        one_over_bound["context"]["arguments"]["one-too-many"] = Value::String("value".to_owned());
9067        assert!(
9068            CoreRequest::decode(
9069                ProtocolEra::Modern2026,
9070                COMPLETION_COMPLETE,
9071                Some(&one_over_bound),
9072            )
9073            .is_err(),
9074            "adding only a 257th context argument must reject"
9075        );
9076
9077        let mut oversized_key = accepted.clone();
9078        let mut oversized_key_arguments = serde_json::Map::new();
9079        oversized_key_arguments.insert(
9080            "k".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES + 1),
9081            Value::String("value".to_owned()),
9082        );
9083        oversized_key["context"]["arguments"] = Value::Object(oversized_key_arguments);
9084        assert!(
9085            CoreRequest::decode(
9086                ProtocolEra::Modern2026,
9087                COMPLETION_COMPLETE,
9088                Some(&oversized_key),
9089            )
9090            .is_err(),
9091            "changing only the context map to contain an oversized key must reject"
9092        );
9093
9094        let mut oversized_value = accepted.clone();
9095        oversized_value["context"]["arguments"] = serde_json::json!({
9096            "key": "v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES + 1)
9097        });
9098        assert!(
9099            CoreRequest::decode(
9100                ProtocolEra::Modern2026,
9101                COMPLETION_COMPLETE,
9102                Some(&oversized_value),
9103            )
9104            .is_err(),
9105            "changing only the context map to contain an oversized value must reject"
9106        );
9107
9108        let mut aggregate_over_bound_arguments = serde_json::Map::new();
9109        for index in 0..17 {
9110            aggregate_over_bound_arguments.insert(
9111                format!("key-{index}"),
9112                Value::String("v".repeat(MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES)),
9113            );
9114        }
9115        let mut aggregate_over_bound = accepted.clone();
9116        aggregate_over_bound["context"]["arguments"] =
9117            Value::Object(aggregate_over_bound_arguments);
9118        assert!(
9119            CoreRequest::decode(
9120                ProtocolEra::Modern2026,
9121                COMPLETION_COMPLETE,
9122                Some(&aggregate_over_bound),
9123            )
9124            .is_err(),
9125            "changing only the context map to exceed its aggregate encoded-byte bound must reject"
9126        );
9127        assert_eq!(
9128            baseline
9129                .encode_params()
9130                .expect("accepted context remains encodable")
9131                .expect("completion has parameters"),
9132            accepted,
9133            "rejected context mutations leave the accepted request unchanged"
9134        );
9135    }
9136
9137    #[test]
9138    fn final_completion_resource_reference_requires_rfc6570_on_construction_decode_and_encode() {
9139        let valid = FinalCompletionReference::resource("mcp://resources/{item}{?cursor}")
9140            .expect("a valid RFC 6570 resource reference constructs");
9141        assert_eq!(
9142            serde_json::to_value(&valid).expect("valid resource reference serializes"),
9143            serde_json::json!({"type": "ref/resource", "uri": "mcp://resources/{item}{?cursor}"})
9144        );
9145
9146        let invalid = "mcp://resources/{item:0}";
9147        assert!(
9148            FinalCompletionReference::resource(invalid).is_err(),
9149            "construction rejects an RFC 6570-invalid prefix modifier"
9150        );
9151        assert!(
9152            serde_json::from_value::<FinalCompletionReference>(serde_json::json!({
9153                "type": "ref/resource",
9154                "uri": invalid,
9155            }))
9156            .is_err(),
9157            "peer decode rejects the same invalid resource reference"
9158        );
9159        let bypass = FinalCompletionReference::Resource {
9160            uri: invalid.to_owned(),
9161        };
9162        assert!(
9163            serde_json::to_value(&bypass).is_err(),
9164            "direct enum construction cannot bypass RFC 6570 admission on local emission"
9165        );
9166    }
9167
9168    #[test]
9169    fn final_completion_total_is_exact_and_rejects_one_field_invalid_forms() {
9170        let params = serde_json::json!({
9171            "_meta": {
9172                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
9173                "io.modelcontextprotocol/clientCapabilities": {}
9174            },
9175            "ref": {"type": "ref/prompt", "name": "deploy"},
9176            "argument": {"name": "environment", "value": "pro"}
9177        });
9178        let request =
9179            CoreRequest::decode(ProtocolEra::Modern2026, COMPLETION_COMPLETE, Some(&params))
9180                .expect("final completion request");
9181        let accepted = r#"{"resultType":"complete","completion":{"values":["production"],"total":922337203685477580812345678901234567890,"hasMore":false}}"#;
9182        let baseline = request
9183            .decode_result(accepted)
9184            .expect("an arbitrary-precision exact total is valid");
9185        let CoreResult::Final(FinalCoreResult::Completion { result, .. }) = &baseline else {
9186            panic!("final completion result");
9187        };
9188        assert_eq!(
9189            result
9190                .payload
9191                .completion
9192                .total
9193                .as_ref()
9194                .map(JsonInteger::as_str),
9195            Some("922337203685477580812345678901234567890")
9196        );
9197
9198        for planted_total in ["null", "1.5"] {
9199            let planted = format!(
9200                r#"{{"resultType":"complete","completion":{{"values":["production"],"total":{planted_total},"hasMore":false}}}}"#
9201            );
9202            assert!(
9203                request.decode_result(&planted).is_err(),
9204                "changing only total to {planted_total} must reject"
9205            );
9206        }
9207        let negative = r#"{"resultType":"complete","completion":{"values":["production"],"total":-1,"hasMore":false}}"#;
9208        let negative_result = request
9209            .decode_result(negative)
9210            .expect("a negative schema-valid peer total remains decodable");
9211        let CoreResult::Final(FinalCoreResult::Completion { result, .. }) = negative_result else {
9212            panic!("negative total remains a completion result");
9213        };
9214        assert_eq!(
9215            result.payload.completion.peer_diagnostic(),
9216            Some(FinalCompletionPeerDiagnostic::NegativeTotal),
9217            "negative completion totals retain a bounded peer diagnostic"
9218        );
9219        assert!(
9220            result.payload.completion.validate().is_err(),
9221            "a negative peer total is not valid for local provider emission"
9222        );
9223        let planted_has_more = r#"{"resultType":"complete","completion":{"values":["production"],"total":922337203685477580812345678901234567890,"hasMore":null}}"#;
9224        assert!(
9225            request.decode_result(planted_has_more).is_err(),
9226            "changing only hasMore to null must reject"
9227        );
9228        let encoded: Value = serde_json::from_str(
9229            &baseline
9230                .encode()
9231                .expect("accepted exact total remains encodable"),
9232        )
9233        .expect("accepted exact total encoding is JSON");
9234        let completion = encoded["completion"]
9235            .as_object()
9236            .expect("accepted exact total retains its completion object");
9237        assert_eq!(encoded["resultType"], "complete");
9238        assert_eq!(completion["values"], serde_json::json!(["production"]));
9239        assert_eq!(
9240            completion["total"]
9241                .as_number()
9242                .map(serde_json::Number::as_str),
9243            Some("922337203685477580812345678901234567890")
9244        );
9245        assert_eq!(completion.get("hasMore"), Some(&Value::Bool(false)));
9246        assert!(
9247            completion.contains_key("total") && completion.contains_key("hasMore"),
9248            "present final completion optionals remain present after re-encoding"
9249        );
9250    }
9251
9252    #[cfg(feature = "legacy-2024-11-05")]
9253    #[test]
9254    fn legacy_completion_result_retains_meta_during_round_trip() {
9255        let request = CoreRequest::decode(
9256            ProtocolEra::Legacy2024,
9257            COMPLETION_COMPLETE,
9258            Some(&serde_json::json!({
9259                "ref": {"type": "ref/prompt", "name": "deploy"},
9260                "argument": {"name": "environment", "value": "sta"}
9261            })),
9262        )
9263        .expect("legacy completion request");
9264        let wire = r#"{"completion":{"values":["staging"]},"_meta":{"trace":{"attempt":1},"cache":"private"}}"#;
9265        let result = request
9266            .decode_result(wire)
9267            .expect("legacy completion metadata remains typed");
9268        let CoreResult::Legacy(LegacyCoreResult::Completion(completion)) = &result else {
9269            panic!("legacy completion result");
9270        };
9271        let metadata = completion
9272            .meta
9273            .as_ref()
9274            .expect("legacy metadata is retained");
9275        assert_eq!(metadata.get("cache"), Some(&serde_json::json!("private")));
9276        assert_eq!(
9277            metadata.get("trace"),
9278            Some(&serde_json::json!({"attempt": 1}))
9279        );
9280        assert_eq!(
9281            serde_json::from_str::<Value>(&result.encode().expect("legacy completion re-encodes"))
9282                .expect("legacy completion encoding is JSON"),
9283            serde_json::from_str::<Value>(wire).expect("legacy completion fixture is JSON"),
9284            "legacy completion _meta is retained without asserting source member order"
9285        );
9286    }
9287
9288    #[test]
9289    fn final_subscriptions_listen_round_trips_request_result_and_acknowledgement() {
9290        let params = serde_json::json!({
9291            "_meta": {
9292                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
9293                "io.modelcontextprotocol/clientCapabilities": {}
9294            },
9295            "notifications": {
9296                "promptsListChanged": false,
9297                "resourceSubscriptions": ["file:///workspace/status"],
9298                "resourcesListChanged": true,
9299                "toolsListChanged": true,
9300                "com.example/extension": {"enabled": true}
9301            }
9302        });
9303        let request =
9304            CoreRequest::decode(ProtocolEra::Modern2026, SUBSCRIPTIONS_LISTEN, Some(&params))
9305                .expect("final subscriptions/listen request is typed");
9306        assert_eq!(request.method(), SUBSCRIPTIONS_LISTEN);
9307        let CoreRequest::Final(FinalCoreRequest::SubscriptionsListen(listen)) = &request else {
9308            panic!("final subscriptions/listen request");
9309        };
9310        assert!(matches!(
9311            listen.notifications.resource_subscriptions.as_deref(),
9312            Some([uri]) if uri == "file:///workspace/status"
9313        ));
9314        assert_eq!(listen.notifications.prompts_list_changed, Some(false));
9315        assert_eq!(
9316            listen.notifications.additional.get("com.example/extension"),
9317            Some(&serde_json::json!({"enabled": true}))
9318        );
9319        assert_eq!(
9320            request
9321                .encode_params()
9322                .expect("final subscriptions/listen re-encodes")
9323                .expect("listen owns a parameter object"),
9324            params
9325        );
9326
9327        let acknowledgement_wire = serde_json::json!({
9328            "_meta": {"io.modelcontextprotocol/subscriptionId": "subscription-7"},
9329            "notifications": {
9330                "resourceSubscriptions": ["file:///workspace/status"],
9331                "toolsListChanged": true
9332            }
9333        });
9334        let acknowledgement: FinalSubscriptionsAcknowledgedNotificationParams =
9335            serde_json::from_value(acknowledgement_wire.clone())
9336                .expect("acknowledgement notification is typed");
9337        assert_eq!(
9338            acknowledgement
9339                .meta
9340                .as_ref()
9341                .and_then(|metadata| metadata.get(FINAL_SUBSCRIPTION_ID_META_KEY)),
9342            Some(&serde_json::json!("subscription-7"))
9343        );
9344        assert_eq!(
9345            serde_json::to_value(&acknowledgement).expect("acknowledgement re-encodes"),
9346            acknowledgement_wire
9347        );
9348
9349        let result_wire = r#"{"resultType":"complete","_meta":{"io.modelcontextprotocol/subscriptionId":"subscription-7","io.modelcontextprotocol/serverInfo":{"name":"final-server","version":"1.0.0"}}}"#;
9350        let response = JsonRpcResponse::success(
9351            RequestId::from("subscription-7"),
9352            serde_json::from_str(result_wire).expect("subscription result JSON"),
9353        );
9354        let result = request
9355            .decode_response(&response)
9356            .expect("final subscriptions/listen termination result is typed");
9357        let CoreResult::Final(FinalCoreResult::SubscriptionsListen {
9358            result: listen_result,
9359            subscription_id,
9360            diagnostic,
9361        }) = &result
9362        else {
9363            panic!("final subscriptions/listen result");
9364        };
9365        assert_eq!(subscription_id, &RequestId::from("subscription-7"));
9366        assert!(diagnostic.is_none());
9367        assert!(listen_result.extras.members().is_empty());
9368        assert_eq!(
9369            serde_json::from_str::<Value>(
9370                &result
9371                    .encode()
9372                    .expect("final subscriptions/listen re-encodes"),
9373            )
9374            .expect("encoded subscription result is JSON"),
9375            serde_json::from_str::<Value>(result_wire).expect("subscription result is JSON")
9376        );
9377
9378        let legacy_subscribe = SubscribeResourceParams {
9379            uri: "file:///workspace/status".to_owned(),
9380        };
9381        let legacy_unsubscribe = UnsubscribeResourceParams {
9382            uri: "file:///workspace/status".to_owned(),
9383        };
9384        assert_eq!(
9385            serde_json::to_value(&legacy_subscribe).expect("legacy subscribe serializes"),
9386            serde_json::json!({"uri": "file:///workspace/status"})
9387        );
9388        assert_eq!(
9389            serde_json::to_value(&legacy_unsubscribe).expect("legacy unsubscribe serializes"),
9390            serde_json::json!({"uri": "file:///workspace/status"})
9391        );
9392        #[cfg(feature = "legacy-2024-11-05")]
9393        {
9394            let subscribe = CoreRequest::decode(
9395                ProtocolEra::Legacy2024,
9396                RESOURCES_SUBSCRIBE,
9397                Some(&serde_json::json!({"uri": "file:///workspace/status"})),
9398            )
9399            .expect("exact-2024 resources/subscribe is a typed core request");
9400            assert!(matches!(
9401                subscribe,
9402                CoreRequest::Legacy(LegacyCoreRequest::ResourcesSubscribe(params))
9403                    if params.uri == "file:///workspace/status"
9404            ));
9405            let unsubscribe = CoreRequest::decode(
9406                ProtocolEra::Legacy2024,
9407                RESOURCES_UNSUBSCRIBE,
9408                Some(&serde_json::json!({"uri": "file:///workspace/status"})),
9409            )
9410            .expect("exact-2024 resources/unsubscribe is a typed core request");
9411            assert!(matches!(
9412                unsubscribe,
9413                CoreRequest::Legacy(LegacyCoreRequest::ResourcesUnsubscribe(params))
9414                    if params.uri == "file:///workspace/status"
9415            ));
9416            assert!(
9417                matches!(
9418                    CoreRequest::decode(
9419                        ProtocolEra::Modern2026,
9420                        RESOURCES_SUBSCRIBE,
9421                        Some(&serde_json::json!({"uri": "file:///workspace/status"})),
9422                    ),
9423                    Err(CoreDispatchError::UnsupportedMethod {
9424                        era: ProtocolEra::Modern2026,
9425                        method,
9426                    }) if method == RESOURCES_SUBSCRIBE
9427                ),
9428                "resources/subscribe stays exact-2024-only"
9429            );
9430            assert!(
9431                matches!(
9432                    CoreRequest::decode(
9433                        ProtocolEra::Legacy2024,
9434                        RESOURCES_SUBSCRIBE,
9435                        Some(&serde_json::json!({})),
9436                    ),
9437                    Err(CoreDispatchError::InvalidParams {
9438                        era: ProtocolEra::Legacy2024,
9439                        method: RESOURCES_SUBSCRIBE,
9440                    })
9441                ),
9442                "resources/subscribe without uri is invalid"
9443            );
9444        }
9445    }
9446
9447    #[test]
9448    fn final_subscriptions_listen_rejects_one_field_response_id_mismatch() {
9449        let params = serde_json::json!({
9450            "_meta": {
9451                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
9452                "io.modelcontextprotocol/clientCapabilities": {}
9453            },
9454            "notifications": {"toolsListChanged": true}
9455        });
9456        let request =
9457            CoreRequest::decode(ProtocolEra::Modern2026, SUBSCRIPTIONS_LISTEN, Some(&params))
9458                .expect("final subscriptions/listen request");
9459        let result = serde_json::json!({
9460            "resultType": "complete",
9461            "_meta": {"io.modelcontextprotocol/subscriptionId": "subscription-7"}
9462        });
9463        let accepted = JsonRpcResponse::success(RequestId::from("subscription-7"), result.clone());
9464        request
9465            .decode_response(&accepted)
9466            .expect("matching subscription response id is admitted");
9467
9468        let mut wrong_role_result = result.clone();
9469        wrong_role_result["_meta"]["io.modelcontextprotocol/logLevel"] =
9470            serde_json::json!("notice");
9471        let wrong_role =
9472            JsonRpcResponse::success(RequestId::from("subscription-7"), wrong_role_result);
9473        assert!(
9474            matches!(
9475                request.decode_response(&wrong_role),
9476                Err(CoreDispatchError::InvalidResult {
9477                    era: ProtocolEra::Modern2026,
9478                    method: SUBSCRIPTIONS_LISTEN,
9479                })
9480            ),
9481            "only request-only logLevel changes the valid subscriptions/listen terminal result"
9482        );
9483        request
9484            .decode_response(&accepted)
9485            .expect("the wrong-role member cannot mutate the accepted subscription binding");
9486
9487        let planted = JsonRpcResponse::success(RequestId::from("subscription-8"), result);
9488        assert!(
9489            matches!(
9490                request.decode_response(&planted),
9491                Err(CoreDispatchError::SubscriptionIdMismatch)
9492            ),
9493            "only the response id differs from the otherwise valid subscription result"
9494        );
9495        request
9496            .decode_response(&accepted)
9497            .expect("the mismatched response cannot mutate the accepted binding");
9498    }
9499
9500    #[test]
9501    fn final_subscriptions_listen_correlates_equivalent_numeric_id_spellings() {
9502        let params = serde_json::json!({
9503            "_meta": {
9504                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
9505                "io.modelcontextprotocol/clientCapabilities": {}
9506            },
9507            "notifications": {"toolsListChanged": true}
9508        });
9509        let request =
9510            CoreRequest::decode(ProtocolEra::Modern2026, SUBSCRIPTIONS_LISTEN, Some(&params))
9511                .expect("final subscriptions/listen request");
9512        let accepted_frame = r#"{"jsonrpc":"2.0","id":2e0,"result":{"resultType":"complete","_meta":{"io.modelcontextprotocol/subscriptionId":2.0}}}"#;
9513        let accepted =
9514            crate::decode_strict_jsonrpc_response(accepted_frame.as_bytes(), accepted_frame.len())
9515                .expect("equivalent numeric subscription identifiers are valid JSON-RPC");
9516        let accepted_result_source = accepted
9517            .raw_result()
9518            .expect("successful subscription response retains exact result source");
9519
9520        let decoded = request
9521            .decode_response_result(accepted.response(), accepted_result_source)
9522            .expect("equivalent numeric spellings correlate");
9523        let encoded = decoded
9524            .encode()
9525            .expect("the correlated subscription result re-encodes");
9526        assert_eq!(
9527            encoded, accepted_result_source,
9528            "the exact final result path retains the subscription ID's admitted numeric lexeme"
9529        );
9530
9531        let planted_frame = r#"{"jsonrpc":"2.0","id":2e0,"result":{"resultType":"complete","_meta":{"io.modelcontextprotocol/subscriptionId":3.0}}}"#;
9532        let planted =
9533            crate::decode_strict_jsonrpc_response(planted_frame.as_bytes(), planted_frame.len())
9534                .expect("one-number mathematical-integer near-miss is valid JSON-RPC");
9535        let planted_result_source = planted
9536            .raw_result()
9537            .expect("successful planted response retains exact result source");
9538        assert!(
9539            matches!(
9540                request.decode_response_result(planted.response(), planted_result_source),
9541                Err(CoreDispatchError::SubscriptionIdMismatch)
9542            ),
9543            "only the mathematical subscription identifier changes"
9544        );
9545
9546        let missing_identifier_frame =
9547            r#"{"jsonrpc":"2.0","id":2e0,"result":{"resultType":"complete","_meta":{}}}"#;
9548        let missing_identifier = crate::decode_strict_jsonrpc_response(
9549            missing_identifier_frame.as_bytes(),
9550            missing_identifier_frame.len(),
9551        )
9552        .expect("the one-member-absent response remains valid JSON-RPC");
9553        let missing_identifier_result_source = missing_identifier
9554            .raw_result()
9555            .expect("successful response retains its exact result source");
9556        assert!(
9557            matches!(
9558                request.decode_response_result(
9559                    missing_identifier.response(),
9560                    missing_identifier_result_source,
9561                ),
9562                Err(CoreDispatchError::InvalidResult {
9563                    era: ProtocolEra::Modern2026,
9564                    method: SUBSCRIPTIONS_LISTEN,
9565                })
9566            ),
9567            "removing only the required subscriptionId rejects the terminal result"
9568        );
9569
9570        let reaccepted = request
9571            .decode_response_result(accepted.response(), accepted_result_source)
9572            .expect("the numeric near-miss cannot mutate correlation state");
9573        assert_eq!(
9574            reaccepted
9575                .encode()
9576                .expect("the reaccepted subscription result re-encodes"),
9577            accepted_result_source,
9578            "the subscriptionId-absent rejection leaves the accepted exact result unchanged"
9579        );
9580    }
9581
9582    #[test]
9583    fn final_subscriptions_listen_rejects_one_legacy_subscription_field() {
9584        let accepted = serde_json::json!({
9585            "_meta": {
9586                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
9587                "io.modelcontextprotocol/clientCapabilities": {}
9588            },
9589            "notifications": {
9590                "resourceSubscriptions": ["file:///workspace/status"]
9591            }
9592        });
9593        let baseline = CoreRequest::decode(
9594            ProtocolEra::Modern2026,
9595            SUBSCRIPTIONS_LISTEN,
9596            Some(&accepted),
9597        )
9598        .expect("baseline final subscriptions/listen request");
9599
9600        let mut planted = accepted.clone();
9601        planted["uri"] = serde_json::json!("file:///workspace/status");
9602        assert!(
9603            matches!(
9604                CoreRequest::decode(
9605                    ProtocolEra::Modern2026,
9606                    SUBSCRIPTIONS_LISTEN,
9607                    Some(&planted)
9608                ),
9609                Err(CoreDispatchError::InvalidParams {
9610                    era: ProtocolEra::Modern2026,
9611                    method: SUBSCRIPTIONS_LISTEN,
9612                })
9613            ),
9614            "only the legacy resources/subscribe uri field changes the valid final listen request"
9615        );
9616
9617        let reaccepted = CoreRequest::decode(
9618            ProtocolEra::Modern2026,
9619            SUBSCRIPTIONS_LISTEN,
9620            Some(&accepted),
9621        )
9622        .expect("cross-era rejection cannot mutate final listen decoding");
9623        assert_eq!(
9624            baseline
9625                .encode_params()
9626                .expect("baseline encodes")
9627                .expect("baseline parameters"),
9628            reaccepted
9629                .encode_params()
9630                .expect("reaccepted encodes")
9631                .expect("reaccepted parameters"),
9632            "the one-field cross-era rejection leaves the accepted final request unchanged"
9633        );
9634    }
9635
9636    #[cfg(feature = "legacy-2024-11-05")]
9637    #[test]
9638    fn core_completion_rejects_one_field_cross_era_metadata() {
9639        let accepted = serde_json::json!({
9640            "ref": {"type": "ref/prompt", "name": "deploy"},
9641            "argument": {"name": "environment", "value": "sta"}
9642        });
9643        let baseline = CoreRequest::decode(
9644            ProtocolEra::Legacy2024,
9645            COMPLETION_COMPLETE,
9646            Some(&accepted),
9647        )
9648        .expect("baseline legacy completion request");
9649
9650        let mut planted = accepted.clone();
9651        planted["_meta"] = serde_json::json!({
9652            "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION
9653        });
9654        assert!(
9655            matches!(
9656                CoreRequest::decode(ProtocolEra::Legacy2024, COMPLETION_COMPLETE, Some(&planted)),
9657                Err(CoreDispatchError::CrossEraRequestMetadata {
9658                    method: COMPLETION_COMPLETE
9659                })
9660            ),
9661            "only the final _meta field changes the otherwise valid legacy completion request"
9662        );
9663
9664        let reaccepted = CoreRequest::decode(
9665            ProtocolEra::Legacy2024,
9666            COMPLETION_COMPLETE,
9667            Some(&accepted),
9668        )
9669        .expect("cross-era rejection cannot mutate completion request decoding");
9670        assert_eq!(
9671            baseline
9672                .encode_params()
9673                .expect("baseline encodes")
9674                .expect("baseline parameters"),
9675            reaccepted
9676                .encode_params()
9677                .expect("reaccepted encodes")
9678                .expect("reaccepted parameters"),
9679            "the one-field cross-era rejection leaves the accepted legacy request unchanged"
9680        );
9681    }
9682
9683    #[test]
9684    fn core_request_envelope_admits_final_client_info() {
9685        let params = serde_json::json!({
9686            "_meta": {
9687                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
9688                "io.modelcontextprotocol/clientCapabilities": {},
9689                "io.modelcontextprotocol/clientInfo": {
9690                    "name": "final-client",
9691                    "version": "1.0.0"
9692                }
9693            }
9694        });
9695
9696        let request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_LIST, Some(&params))
9697            .expect("final clientInfo is admitted by the final request envelope");
9698        assert_eq!(request.era(), ProtocolEra::Modern2026);
9699        assert_eq!(
9700            request
9701                .encode_params()
9702                .expect("admitted final request encodes"),
9703            Some(params)
9704        );
9705    }
9706
9707    #[cfg(feature = "legacy-2024-11-05")]
9708    #[test]
9709    fn core_request_envelope_rejects_one_final_client_info_member_in_legacy_era() {
9710        let accepted = serde_json::json!({});
9711        let baseline = CoreRequest::decode(ProtocolEra::Legacy2024, TOOLS_LIST, Some(&accepted))
9712            .expect("baseline legacy list request");
9713
9714        let mut planted = accepted.clone();
9715        planted["_meta"] = serde_json::json!({
9716            "io.modelcontextprotocol/clientInfo": {
9717                "name": "final-client",
9718                "version": "1.0.0"
9719            }
9720        });
9721        assert!(
9722            matches!(
9723                CoreRequest::decode(ProtocolEra::Legacy2024, TOOLS_LIST, Some(&planted)),
9724                Err(CoreDispatchError::CrossEraRequestMetadata { method: TOOLS_LIST })
9725            ),
9726            "only the final clientInfo metadata member changes the accepted legacy request"
9727        );
9728
9729        let reaccepted = CoreRequest::decode(ProtocolEra::Legacy2024, TOOLS_LIST, Some(&accepted))
9730            .expect("cross-era rejection cannot mutate legacy request admission");
9731        assert_eq!(
9732            baseline
9733                .encode_params()
9734                .expect("baseline legacy request encodes"),
9735            reaccepted
9736                .encode_params()
9737                .expect("reaccepted legacy request encodes")
9738        );
9739    }
9740
9741    #[test]
9742    fn core_result_envelope_admits_final_server_info() {
9743        let params = serde_json::json!({
9744            "_meta": {
9745                "io.modelcontextprotocol/protocolVersion": FINAL_PROTOCOL_VERSION,
9746                "io.modelcontextprotocol/clientCapabilities": {}
9747            }
9748        });
9749        let request = CoreRequest::decode(ProtocolEra::Modern2026, TOOLS_LIST, Some(&params))
9750            .expect("final tools/list request");
9751        let result = r#"{"resultType":"complete","tools":[],"ttlMs":0,"cacheScope":"private","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"final-server","version":"1.0.0"}}}"#;
9752
9753        let decoded = request
9754            .decode_result(result)
9755            .expect("final serverInfo is admitted by the final result envelope");
9756        assert_eq!(
9757            serde_json::from_str::<Value>(
9758                &decoded.encode().expect("admitted final result encodes")
9759            )
9760            .expect("admitted final result encoding is JSON"),
9761            serde_json::from_str::<Value>(result).expect("final serverInfo fixture is JSON"),
9762            "serverInfo is admitted only through _meta on a complete valid catalog"
9763        );
9764    }
9765
9766    #[cfg(feature = "legacy-2024-11-05")]
9767    #[test]
9768    fn core_result_envelope_rejects_each_final_only_metadata_member_in_legacy_era() {
9769        let request = CoreRequest::decode(ProtocolEra::Legacy2024, TOOLS_LIST, None)
9770            .expect("baseline legacy request");
9771        let accepted = r#"{"tools":[]}"#;
9772        let baseline = request
9773            .decode_result(accepted)
9774            .expect("baseline legacy result is admitted");
9775        assert_eq!(
9776            baseline.encode().expect("baseline legacy result encodes"),
9777            accepted
9778        );
9779
9780        for final_metadata_member in [
9781            FINAL_PROTOCOL_VERSION_META_KEY,
9782            FINAL_CLIENT_CAPABILITIES_META_KEY,
9783            FINAL_CLIENT_INFO_META_KEY,
9784            FINAL_SERVER_INFO_META_KEY,
9785            FINAL_SUBSCRIPTION_ID_META_KEY,
9786        ] {
9787            let planted = serde_json::json!({
9788                "tools": [],
9789                "_meta": {final_metadata_member: true}
9790            })
9791            .to_string();
9792            assert!(
9793                matches!(
9794                    request.decode_result(&planted),
9795                    Err(CoreDispatchError::CrossEraResultMetadata { method: TOOLS_LIST })
9796                ),
9797                "adding only {final_metadata_member} rejects the otherwise valid legacy result"
9798            );
9799        }
9800
9801        let reaccepted = request
9802            .decode_result(accepted)
9803            .expect("cross-era rejection cannot mutate legacy result admission");
9804        assert_eq!(
9805            reaccepted
9806                .encode()
9807                .expect("reaccepted legacy result encodes"),
9808            accepted
9809        );
9810    }
9811
9812    #[cfg(feature = "legacy-2024-11-05")]
9813    #[test]
9814    fn core_dispatch_rejects_one_field_final_result_type_on_legacy_result() {
9815        let request = CoreRequest::decode(ProtocolEra::Legacy2024, TOOLS_LIST, None)
9816            .expect("baseline legacy request");
9817        let accepted = r#"{"tools":[]}"#;
9818        let baseline = request
9819            .decode_result(accepted)
9820            .expect("the legacy result remains accepted without a final discriminator");
9821        let planted = r#"{"tools":[],"resultType":"complete"}"#;
9822        assert!(
9823            matches!(
9824                request.decode_result(planted),
9825                Err(CoreDispatchError::CrossEraResultType { method: TOOLS_LIST })
9826            ),
9827            "only the final resultType field changes the otherwise valid legacy result"
9828        );
9829        let reaccepted = request
9830            .decode_result(accepted)
9831            .expect("rejection cannot mutate the selected legacy dispatch");
9832        assert_eq!(
9833            baseline.encode().expect("baseline encodes"),
9834            reaccepted.encode().expect("reaccepted value encodes"),
9835            "the one-field cross-era rejection leaves the accepted legacy result unchanged"
9836        );
9837    }
9838
9839    // ========================================================================
9840    // Initialize Tests
9841    // ========================================================================
9842
9843    #[test]
9844    fn initialize_params_serialization() {
9845        let params = InitializeParams {
9846            protocol_version: PROTOCOL_VERSION.to_string(),
9847            capabilities: ClientCapabilities::default(),
9848            client_info: ClientInfo {
9849                name: "test-client".to_string(),
9850                version: "1.0.0".to_string(),
9851            },
9852        };
9853        let value = serde_json::to_value(&params).expect("serialize");
9854        assert_eq!(value["protocolVersion"], PROTOCOL_VERSION);
9855        assert_eq!(value["clientInfo"]["name"], "test-client");
9856        assert_eq!(value["clientInfo"]["version"], "1.0.0");
9857    }
9858
9859    #[test]
9860    fn initialize_params_round_trip() {
9861        let json = serde_json::json!({
9862            "protocolVersion": "2024-11-05",
9863            "capabilities": {},
9864            "clientInfo": {"name": "my-client", "version": "0.1.0"}
9865        });
9866        let params: InitializeParams = serde_json::from_value(json).expect("deserialize");
9867        assert_eq!(params.protocol_version, "2024-11-05");
9868        assert_eq!(params.client_info.name, "my-client");
9869    }
9870
9871    #[test]
9872    fn initialize_result_serialization() {
9873        let result = InitializeResult {
9874            protocol_version: PROTOCOL_VERSION.to_string(),
9875            capabilities: ServerCapabilities::default(),
9876            server_info: ServerInfo {
9877                name: "test-server".to_string(),
9878                version: "1.0.0".to_string(),
9879            },
9880            instructions: Some("Welcome!".to_string()),
9881        };
9882        let value = serde_json::to_value(&result).expect("serialize");
9883        assert_eq!(value["protocolVersion"], PROTOCOL_VERSION);
9884        assert_eq!(value["serverInfo"]["name"], "test-server");
9885        assert_eq!(value["instructions"], "Welcome!");
9886    }
9887
9888    #[test]
9889    fn initialize_result_without_instructions() {
9890        let result = InitializeResult {
9891            protocol_version: PROTOCOL_VERSION.to_string(),
9892            capabilities: ServerCapabilities::default(),
9893            server_info: ServerInfo {
9894                name: "srv".to_string(),
9895                version: "0.1.0".to_string(),
9896            },
9897            instructions: None,
9898        };
9899        let value = serde_json::to_value(&result).expect("serialize");
9900        assert!(value.get("instructions").is_none());
9901    }
9902
9903    // ========================================================================
9904    // ListToolsParams Tests (with tags)
9905    // ========================================================================
9906
9907    #[test]
9908    fn list_tools_params_default() {
9909        let params = ListToolsParams::default();
9910        let value = serde_json::to_value(&params).expect("serialize");
9911        assert_eq!(value, serde_json::json!({}));
9912    }
9913
9914    #[test]
9915    fn list_tools_params_with_cursor() {
9916        let params = ListToolsParams {
9917            cursor: Some("next-page".to_string()),
9918            include_tags: None,
9919            exclude_tags: None,
9920        };
9921        let value = serde_json::to_value(&params).expect("serialize");
9922        assert_eq!(value["cursor"], "next-page");
9923    }
9924
9925    #[test]
9926    fn list_tools_params_with_tags() {
9927        let params = ListToolsParams {
9928            cursor: None,
9929            include_tags: Some(vec!["api".to_string(), "v2".to_string()]),
9930            exclude_tags: Some(vec!["deprecated".to_string()]),
9931        };
9932        let value = serde_json::to_value(&params).expect("serialize");
9933        assert_eq!(value["includeTags"], serde_json::json!(["api", "v2"]));
9934        assert_eq!(value["excludeTags"], serde_json::json!(["deprecated"]));
9935    }
9936
9937    // ========================================================================
9938    // CallToolParams Tests
9939    // ========================================================================
9940
9941    #[test]
9942    fn call_tool_params_minimal() {
9943        let params = CallToolParams {
9944            name: "greet".to_string(),
9945            arguments: None,
9946            meta: None,
9947        };
9948        let value = serde_json::to_value(&params).expect("serialize");
9949        assert_eq!(value["name"], "greet");
9950        assert!(value.get("arguments").is_none());
9951        assert!(value.get("_meta").is_none());
9952    }
9953
9954    #[test]
9955    fn call_tool_params_full() {
9956        let params = CallToolParams {
9957            name: "add".to_string(),
9958            arguments: Some(serde_json::json!({"a": 1, "b": 2})),
9959            meta: Some(RequestMeta {
9960                progress_marker: Some(ProgressMarker::Number(JsonInteger::from(100_i64))),
9961            }),
9962        };
9963        let value = serde_json::to_value(&params).expect("serialize");
9964        assert_eq!(value["name"], "add");
9965        assert_eq!(value["arguments"]["a"], 1);
9966        assert_eq!(value["_meta"][PROGRESS_MARKER_KEY], 100);
9967    }
9968
9969    // ========================================================================
9970    // CallToolResult Tests
9971    // ========================================================================
9972
9973    #[test]
9974    fn call_tool_result_success() {
9975        let result = CallToolResult {
9976            content: vec![LegacyContent::Text {
9977                text: "42".to_string(),
9978                annotations: None,
9979                additional: BTreeMap::new(),
9980            }],
9981            is_error: false,
9982            meta: None,
9983            additional: BTreeMap::new(),
9984        };
9985        let value = serde_json::to_value(&result).expect("serialize");
9986        assert_eq!(value["content"][0]["type"], "text");
9987        assert_eq!(value["content"][0]["text"], "42");
9988        // is_error=false should be omitted
9989        assert!(value.get("isError").is_none());
9990    }
9991
9992    #[test]
9993    fn call_tool_result_error() {
9994        let result = CallToolResult {
9995            content: vec![LegacyContent::Text {
9996                text: "Something went wrong".to_string(),
9997                annotations: None,
9998                additional: BTreeMap::new(),
9999            }],
10000            is_error: true,
10001            meta: None,
10002            additional: BTreeMap::new(),
10003        };
10004        let value = serde_json::to_value(&result).expect("serialize");
10005        assert_eq!(value["isError"], true);
10006    }
10007
10008    #[test]
10009    fn legacy_2024_content_results_round_trip_open_wire_members() {
10010        let tool_wire = serde_json::json!({
10011            "content": [{
10012                "type": "text",
10013                "text": "ready",
10014                "annotations": {
10015                    "audience": ["assistant"],
10016                    "priority": 0.75,
10017                    "com.example/annotation": {"retain": true}
10018                },
10019                "_meta": {"legacy": "content"},
10020                "com.example/content": {"retain": true}
10021            }],
10022            "_meta": {"legacy": "tool-result"},
10023            "com.example/result": ["retain"]
10024        });
10025        let tool_result: CallToolResult =
10026            serde_json::from_value(tool_wire.clone()).expect("legacy tool result decodes");
10027        assert_eq!(
10028            serde_json::to_value(&tool_result).expect("legacy tool result re-encodes"),
10029            tool_wire
10030        );
10031
10032        let read_wire = serde_json::json!({
10033            "contents": [{
10034                "uri": "file:///report.txt",
10035                "text": "ready",
10036                "_meta": {"legacy": "resource"},
10037                "com.example/resource": {"retain": true}
10038            }],
10039            "_meta": {"legacy": "read-result"},
10040            "com.example/result": {"retain": true}
10041        });
10042        let read_result: ReadResourceResult =
10043            serde_json::from_value(read_wire.clone()).expect("legacy read result decodes");
10044        assert_eq!(
10045            serde_json::to_value(&read_result).expect("legacy read result re-encodes"),
10046            read_wire
10047        );
10048
10049        let prompt_wire = serde_json::json!({
10050            "messages": [{
10051                "role": "user",
10052                "content": {
10053                    "type": "text",
10054                    "text": "summarize",
10055                    "_meta": {"legacy": "prompt-content"},
10056                    "com.example/content": "retain"
10057                },
10058                "_meta": {"legacy": "prompt-message"},
10059                "com.example/message": true
10060            }],
10061            "_meta": {"legacy": "prompt-result"},
10062            "com.example/result": {"retain": true}
10063        });
10064        let prompt_result: GetPromptResult =
10065            serde_json::from_value(prompt_wire.clone()).expect("legacy prompt result decodes");
10066        assert_eq!(
10067            serde_json::to_value(&prompt_result).expect("legacy prompt result re-encodes"),
10068            prompt_wire
10069        );
10070    }
10071
10072    #[test]
10073    fn legacy_2024_call_tool_rejects_only_audio_discriminator_without_mutating_baseline() {
10074        let accepted = serde_json::json!({
10075            "content": [{
10076                "type": "text",
10077                "text": "payload",
10078                "data": "UklGRg==",
10079                "mimeType": "audio/wav"
10080            }]
10081        });
10082        let accepted_result: CallToolResult =
10083            serde_json::from_value(accepted.clone()).expect("legacy text content decodes");
10084        assert_eq!(
10085            serde_json::to_value(&accepted_result).expect("legacy text content re-encodes"),
10086            accepted
10087        );
10088
10089        let baseline = accepted.clone();
10090        let mut planted = accepted.clone();
10091        planted["content"][0]["type"] = serde_json::json!("audio");
10092        assert!(
10093            serde_json::from_value::<CallToolResult>(planted).is_err(),
10094            "the exact 2024 tools/call content union excludes audio"
10095        );
10096        assert_eq!(
10097            accepted, baseline,
10098            "the one-field audio discriminator rejection cannot mutate accepted legacy wire"
10099        );
10100    }
10101
10102    // ========================================================================
10103    // ListResourcesParams Tests
10104    // ========================================================================
10105
10106    #[test]
10107    fn list_resources_params_default() {
10108        let params = ListResourcesParams::default();
10109        let value = serde_json::to_value(&params).expect("serialize");
10110        assert_eq!(value, serde_json::json!({}));
10111    }
10112
10113    #[test]
10114    fn list_resources_params_with_tags() {
10115        let params = ListResourcesParams {
10116            cursor: None,
10117            include_tags: Some(vec!["config".to_string()]),
10118            exclude_tags: None,
10119        };
10120        let value = serde_json::to_value(&params).expect("serialize");
10121        assert_eq!(value["includeTags"], serde_json::json!(["config"]));
10122    }
10123
10124    // ========================================================================
10125    // ReadResourceParams Tests
10126    // ========================================================================
10127
10128    #[test]
10129    fn read_resource_params_serialization() {
10130        let params = ReadResourceParams {
10131            uri: "file://config.json".to_string(),
10132            meta: None,
10133        };
10134        let value = serde_json::to_value(&params).expect("serialize");
10135        assert_eq!(value["uri"], "file://config.json");
10136        assert!(value.get("_meta").is_none());
10137    }
10138
10139    #[test]
10140    fn read_resource_params_with_meta() {
10141        let params = ReadResourceParams {
10142            uri: "file://data.csv".to_string(),
10143            meta: Some(RequestMeta {
10144                progress_marker: Some(ProgressMarker::String("pt-read".to_string())),
10145            }),
10146        };
10147        let value = serde_json::to_value(&params).expect("serialize");
10148        assert_eq!(value["uri"], "file://data.csv");
10149        assert_eq!(value["_meta"][PROGRESS_MARKER_KEY], "pt-read");
10150    }
10151
10152    // ========================================================================
10153    // ReadResourceResult Tests
10154    // ========================================================================
10155
10156    #[test]
10157    fn read_resource_result_serialization() {
10158        let result = ReadResourceResult {
10159            contents: vec![LegacyResourceContent::Text {
10160                uri: "file://test.txt".to_string(),
10161                mime_type: Some("text/plain".to_string()),
10162                text: "Hello!".to_string(),
10163                additional: BTreeMap::new(),
10164            }],
10165            meta: None,
10166            additional: BTreeMap::new(),
10167        };
10168        let value = serde_json::to_value(&result).expect("serialize");
10169        assert_eq!(value["contents"][0]["uri"], "file://test.txt");
10170        assert_eq!(value["contents"][0]["text"], "Hello!");
10171    }
10172
10173    #[test]
10174    fn legacy_resource_content_one_of_valid_text_and_blob_round_trip_open_members() {
10175        let wire = serde_json::json!({
10176            "contents": [
10177                {
10178                    "uri": "file:///report.txt",
10179                    "text": "ready",
10180                    "mimeType": "text/plain",
10181                    "_meta": {"legacy": "text"},
10182                    "com.example/resource": {"retain": true}
10183                },
10184                {
10185                    "uri": "file:///report.bin",
10186                    "blob": "cmVhZHk=",
10187                    "mimeType": "application/octet-stream",
10188                    "_meta": {"legacy": "blob"},
10189                    "com.example/resource": ["retain"]
10190                }
10191            ],
10192            "_meta": {"legacy": "read-result"},
10193            "com.example/result": {"retain": true}
10194        });
10195
10196        let result: ReadResourceResult =
10197            serde_json::from_value(wire.clone()).expect("exact text and blob members decode");
10198
10199        let LegacyResourceContent::Text { additional, .. } = &result.contents[0] else {
10200            panic!("text discriminator selects the text variant");
10201        };
10202        assert_eq!(additional["_meta"], serde_json::json!({"legacy": "text"}));
10203        assert_eq!(
10204            additional["com.example/resource"],
10205            serde_json::json!({"retain": true})
10206        );
10207        let LegacyResourceContent::Blob { additional, .. } = &result.contents[1] else {
10208            panic!("blob discriminator selects the blob variant");
10209        };
10210        assert_eq!(additional["_meta"], serde_json::json!({"legacy": "blob"}));
10211        assert_eq!(
10212            additional["com.example/resource"],
10213            serde_json::json!(["retain"])
10214        );
10215        assert_eq!(
10216            serde_json::to_value(&result).expect("exact legacy contents re-encode"),
10217            wire
10218        );
10219    }
10220
10221    #[test]
10222    fn legacy_resource_content_one_of_rejects_ambiguous_or_missing_payload_negative() {
10223        let accepted = serde_json::json!({
10224            "contents": [{
10225                "uri": "file:///report.txt",
10226                "text": "ready",
10227                "_meta": {"legacy": "resource"},
10228                "com.example/resource": {"retain": true}
10229            }]
10230        });
10231        assert!(
10232            serde_json::from_value::<ReadResourceResult>(accepted.clone()).is_ok(),
10233            "the one-of baseline remains valid"
10234        );
10235
10236        let mut both = accepted.clone();
10237        both["contents"][0]["blob"] = serde_json::json!("cmVhZHk=");
10238        assert!(
10239            serde_json::from_value::<ReadResourceResult>(both).is_err(),
10240            "only adding blob to a valid text resource must reject an ambiguous one-of"
10241        );
10242
10243        let mut neither = accepted;
10244        neither["contents"][0]
10245            .as_object_mut()
10246            .expect("baseline content is an object")
10247            .remove("text");
10248        assert!(
10249            serde_json::from_value::<ReadResourceResult>(neither).is_err(),
10250            "only removing text from the same resource must reject an empty one-of"
10251        );
10252    }
10253
10254    // ========================================================================
10255    // ListPromptsParams Tests
10256    // ========================================================================
10257
10258    #[test]
10259    fn list_prompts_params_default() {
10260        let params = ListPromptsParams::default();
10261        let value = serde_json::to_value(&params).expect("serialize");
10262        assert_eq!(value, serde_json::json!({}));
10263    }
10264
10265    #[test]
10266    fn list_prompts_params_with_tags() {
10267        let params = ListPromptsParams {
10268            cursor: Some("c1".to_string()),
10269            include_tags: Some(vec!["onboarding".to_string()]),
10270            exclude_tags: Some(vec!["deprecated".to_string()]),
10271        };
10272        let value = serde_json::to_value(&params).expect("serialize");
10273        assert_eq!(value["cursor"], "c1");
10274        assert_eq!(value["includeTags"], serde_json::json!(["onboarding"]));
10275        assert_eq!(value["excludeTags"], serde_json::json!(["deprecated"]));
10276    }
10277
10278    // ========================================================================
10279    // GetPromptParams Tests
10280    // ========================================================================
10281
10282    #[test]
10283    fn get_prompt_params_minimal() {
10284        let params = GetPromptParams {
10285            name: "greeting".to_string(),
10286            arguments: None,
10287            meta: None,
10288        };
10289        let value = serde_json::to_value(&params).expect("serialize");
10290        assert_eq!(value["name"], "greeting");
10291        assert!(value.get("arguments").is_none());
10292    }
10293
10294    #[test]
10295    fn get_prompt_params_with_arguments() {
10296        let mut args = std::collections::HashMap::new();
10297        args.insert("name".to_string(), "Alice".to_string());
10298        args.insert("language".to_string(), "French".to_string());
10299
10300        let params = GetPromptParams {
10301            name: "translate".to_string(),
10302            arguments: Some(args),
10303            meta: None,
10304        };
10305        let value = serde_json::to_value(&params).expect("serialize");
10306        assert_eq!(value["name"], "translate");
10307        assert_eq!(value["arguments"]["name"], "Alice");
10308        assert_eq!(value["arguments"]["language"], "French");
10309    }
10310
10311    // ========================================================================
10312    // GetPromptResult Tests
10313    // ========================================================================
10314
10315    #[test]
10316    fn get_prompt_result_serialization() {
10317        let result = GetPromptResult {
10318            description: Some("A greeting prompt".to_string()),
10319            messages: vec![LegacyPromptMessage {
10320                role: crate::types::Role::User,
10321                content: LegacyContent::Text {
10322                    text: "Say hello".to_string(),
10323                    annotations: None,
10324                    additional: BTreeMap::new(),
10325                },
10326                additional: BTreeMap::new(),
10327            }],
10328            meta: None,
10329            additional: BTreeMap::new(),
10330        };
10331        let value = serde_json::to_value(&result).expect("serialize");
10332        assert_eq!(value["description"], "A greeting prompt");
10333        assert_eq!(value["messages"][0]["role"], "user");
10334        assert_eq!(value["messages"][0]["content"]["text"], "Say hello");
10335    }
10336
10337    #[test]
10338    fn get_prompt_result_without_description() {
10339        let result = GetPromptResult {
10340            description: None,
10341            messages: vec![],
10342            meta: None,
10343            additional: BTreeMap::new(),
10344        };
10345        let value = serde_json::to_value(&result).expect("serialize");
10346        assert!(value.get("description").is_none());
10347    }
10348
10349    // ========================================================================
10350    // CancelledParams Tests
10351    // ========================================================================
10352
10353    #[test]
10354    fn cancellation_wire_codec_round_trips_selected_era_payloads() {
10355        let legacy_wire = serde_json::from_str::<JsonRpcRequest>(
10356            r#"{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":900719925474099312345,"reason":"legacy client stopped waiting"}}"#,
10357        )
10358        .expect("arbitrary-precision legacy cancellation is valid JSON-RPC");
10359        let legacy = CancellationWireMessage::decode(
10360            ProtocolEra::Legacy2024,
10361            CancellationSender::Client,
10362            &legacy_wire,
10363        )
10364        .expect("the legacy codec admits its exact cancellation payload");
10365        assert_eq!(legacy.era(), ProtocolEra::Legacy2024);
10366        assert_eq!(legacy.sender(), CancellationSender::Client);
10367        let CancellationWireMessage::Legacy2024 { params, .. } = &legacy else {
10368            panic!("the selected legacy era must construct the legacy variant");
10369        };
10370        assert_eq!(
10371            params.request_id,
10372            RequestId::Integer("900719925474099312345".to_owned())
10373        );
10374        assert_eq!(
10375            serde_json::to_value(legacy.encode().expect("legacy cancellation re-encodes"))
10376                .expect("legacy cancellation remains JSON"),
10377            serde_json::to_value(&legacy_wire).expect("legacy baseline remains JSON"),
10378            "the legacy wire preserves arbitrary-precision request IDs without awaitCleanup"
10379        );
10380
10381        let modern_wire = JsonRpcRequest::notification(
10382            NOTIFICATIONS_CANCELLED,
10383            Some(serde_json::json!({
10384                "requestId": "final-request-7",
10385                "reason": "final client stopped waiting",
10386            })),
10387        );
10388        let modern = CancellationWireMessage::decode(
10389            ProtocolEra::Modern2026,
10390            CancellationSender::Client,
10391            &modern_wire,
10392        )
10393        .expect("the final client codec admits cancellation without metadata");
10394        assert_eq!(modern.era(), ProtocolEra::Modern2026);
10395        assert_eq!(modern.sender(), CancellationSender::Client);
10396        let CancellationWireMessage::Modern2026 { params, .. } = &modern else {
10397            panic!("the selected final era must construct the final variant");
10398        };
10399        assert!(params.meta.is_none());
10400        assert!(
10401            !params.additional.contains_key("awaitCleanup"),
10402            "a final client codec emission does not synthesize the legacy-only semantic"
10403        );
10404        assert_eq!(
10405            serde_json::to_value(modern.encode().expect("final cancellation re-encodes"))
10406                .expect("final cancellation remains JSON"),
10407            serde_json::to_value(&modern_wire).expect("final baseline remains JSON"),
10408            "the final wire preserves metadata absence without adding legacy members"
10409        );
10410    }
10411
10412    #[test]
10413    fn cancellation_wire_codec_keeps_modern_metadata_optional_and_inert() {
10414        let accepted_wire = JsonRpcRequest::notification(
10415            NOTIFICATIONS_CANCELLED,
10416            Some(serde_json::json!({
10417                "requestId": "final-request-8",
10418                "reason": "final client stopped waiting",
10419            })),
10420        );
10421        let admitted = CancellationWireMessage::decode(
10422            ProtocolEra::Modern2026,
10423            CancellationSender::Client,
10424            &accepted_wire,
10425        )
10426        .expect("the metadata-free final baseline is admitted");
10427        let baseline_wire = serde_json::to_value(&accepted_wire).expect("baseline serializes");
10428
10429        let mut planted = accepted_wire.clone();
10430        planted
10431            .params
10432            .as_mut()
10433            .and_then(Value::as_object_mut)
10434            .expect("baseline owns final cancellation parameters")
10435            .insert(
10436                "_meta".to_owned(),
10437                serde_json::json!({
10438                    "io.modelcontextprotocol/protocolVersion": ProtocolEra::Legacy2024
10439                        .version()
10440                        .as_str(),
10441                    "io.modelcontextprotocol/futureCancellationHint": {
10442                        "preserved": true,
10443                    },
10444                }),
10445            );
10446        let with_metadata = CancellationWireMessage::decode(
10447            ProtocolEra::Modern2026,
10448            CancellationSender::Client,
10449            &planted,
10450        )
10451        .expect("changing only optional metadata never changes cancellation admission");
10452        assert_eq!(
10453            serde_json::to_value(with_metadata.encode().expect("metadata remains opaque"))
10454                .expect("metadata-bearing cancellation remains JSON"),
10455            serde_json::to_value(&planted).expect("planted wire remains JSON"),
10456            "optional metadata is preserved but never validated or synthesized"
10457        );
10458        assert_eq!(
10459            serde_json::to_value(admitted.encode().expect("admitted cancellation re-encodes"))
10460                .expect("admitted cancellation remains JSON"),
10461            baseline_wire,
10462            "the metadata-free baseline remains unchanged"
10463        );
10464    }
10465
10466    #[test]
10467    fn cancellation_wire_codec_rejects_present_null_modern_optional_fields() {
10468        for planted in [
10469            serde_json::json!({"requestId": 7, "reason": null}),
10470            serde_json::json!({"requestId": 7, "_meta": null}),
10471        ] {
10472            let wire = JsonRpcRequest::notification(NOTIFICATIONS_CANCELLED, Some(planted));
10473            assert!(matches!(
10474                CancellationWireMessage::decode(
10475                    ProtocolEra::Modern2026,
10476                    CancellationSender::Client,
10477                    &wire,
10478                ),
10479                Err(CancellationWireCodecError::InvalidParameters {
10480                    era: ProtocolEra::Modern2026,
10481                })
10482            ));
10483        }
10484
10485        let valid_subscription_metadata = JsonRpcRequest::notification(
10486            NOTIFICATIONS_CANCELLED,
10487            Some(serde_json::json!({
10488                "requestId": 7,
10489                "_meta": {(FINAL_SUBSCRIPTION_ID_META_KEY): 7e0},
10490            })),
10491        );
10492        assert!(
10493            CancellationWireMessage::decode(
10494                ProtocolEra::Modern2026,
10495                CancellationSender::Server,
10496                &valid_subscription_metadata,
10497            )
10498            .is_ok()
10499        );
10500
10501        let mismatched_subscription_metadata = JsonRpcRequest::notification(
10502            NOTIFICATIONS_CANCELLED,
10503            Some(serde_json::json!({
10504                "requestId": 7,
10505                "_meta": {(FINAL_SUBSCRIPTION_ID_META_KEY): 8},
10506            })),
10507        );
10508        assert!(
10509            matches!(
10510                CancellationWireMessage::decode(
10511                    ProtocolEra::Modern2026,
10512                    CancellationSender::Server,
10513                    &mismatched_subscription_metadata,
10514                ),
10515                Err(CancellationWireCodecError::InvalidParameters {
10516                    era: ProtocolEra::Modern2026,
10517                })
10518            ),
10519            "changing only the server cancellation metadata ID rejects a stream-target mismatch"
10520        );
10521        let locally_constructed_mismatch = CancellationWireMessage::Modern2026 {
10522            sender: CancellationSender::Server,
10523            params: serde_json::from_value(serde_json::json!({
10524                "requestId": 7,
10525                "_meta": {(FINAL_SUBSCRIPTION_ID_META_KEY): 8},
10526            }))
10527            .expect("the individual final fields remain structurally valid"),
10528        };
10529        assert!(
10530            matches!(
10531                locally_constructed_mismatch.encode(),
10532                Err(CancellationWireCodecError::InvalidParameters {
10533                    era: ProtocolEra::Modern2026,
10534                })
10535            ),
10536            "the same one-field mismatch cannot bypass ingress validation through local encoding"
10537        );
10538
10539        let invalid_subscription_metadata = JsonRpcRequest::notification(
10540            NOTIFICATIONS_CANCELLED,
10541            Some(serde_json::json!({
10542                "requestId": 7,
10543                "_meta": {(FINAL_SUBSCRIPTION_ID_META_KEY): null},
10544            })),
10545        );
10546        assert!(matches!(
10547            CancellationWireMessage::decode(
10548                ProtocolEra::Modern2026,
10549                CancellationSender::Server,
10550                &invalid_subscription_metadata,
10551            ),
10552            Err(CancellationWireCodecError::InvalidParameters {
10553                era: ProtocolEra::Modern2026,
10554            })
10555        ));
10556    }
10557
10558    #[test]
10559    fn cancelled_params_minimal() {
10560        let params = CancelledParams {
10561            request_id: RequestId::Number(5),
10562            reason: None,
10563        };
10564        let value = serde_json::to_value(&params).expect("serialize");
10565        assert_eq!(value["requestId"], 5);
10566        assert!(value.get("reason").is_none());
10567        assert!(value.get("awaitCleanup").is_none());
10568    }
10569
10570    #[test]
10571    fn cancelled_params_full() {
10572        let params = CancelledParams {
10573            request_id: RequestId::String("req-7".to_string()),
10574            reason: Some("User cancelled".to_string()),
10575        };
10576        let value = serde_json::to_value(&params).expect("serialize");
10577        assert_eq!(value["requestId"], "req-7");
10578        assert_eq!(value["reason"], "User cancelled");
10579        assert!(value.get("awaitCleanup").is_none());
10580        assert_eq!(
10581            serde_json::to_string(&params).expect("legacy cancellation serializes"),
10582            r#"{"requestId":"req-7","reason":"User cancelled"}"#,
10583            "the exact legacy cancellation wire has only requestId and optional reason"
10584        );
10585    }
10586
10587    #[test]
10588    fn cancelled_params_reason_is_unbounded_by_the_spec_and_shape_is_closed() {
10589        let beyond_historical_bound = "x".repeat(MAX_CANCELLATION_REASON_BYTES + 1);
10590        let admitted_json = serde_json::json!({
10591            "requestId": 1,
10592            "reason": beyond_historical_bound,
10593        });
10594        assert!(serde_json::from_value::<CancelledParams>(admitted_json).is_ok());
10595        assert!(
10596            serde_json::from_value::<CancelledParams>(serde_json::json!({
10597                "requestId": 1,
10598                "reason": null,
10599            }))
10600            .is_err()
10601        );
10602        assert!(
10603            serde_json::from_value::<CancelledParams>(serde_json::json!({
10604                "requestId": 1,
10605                "awaitCleanup": true,
10606            }))
10607            .is_err()
10608        );
10609        assert!(
10610            serde_json::from_value::<CancelledParams>(serde_json::json!({
10611                "requestId": 1,
10612                "reason": "ok",
10613                "unknown": true,
10614            }))
10615            .is_err()
10616        );
10617
10618        let outbound = CancelledParams {
10619            request_id: RequestId::Number(1),
10620            reason: Some("x".repeat(MAX_CANCELLATION_REASON_BYTES + 1)),
10621        };
10622        assert!(serde_json::to_value(outbound).is_ok());
10623    }
10624
10625    // ========================================================================
10626    // ProgressParams Tests
10627    // ========================================================================
10628
10629    #[test]
10630    fn progress_params_new() {
10631        let params = ProgressParams::new("id-1", 0.5);
10632        let value = serde_json::to_value(&params).expect("serialize");
10633        assert_eq!(value[PROGRESS_MARKER_KEY], "id-1");
10634        assert_eq!(value["progress"], 0.5);
10635        assert!(value.get("total").is_none());
10636        assert!(value.get("message").is_none());
10637    }
10638
10639    #[test]
10640    fn progress_params_with_total() {
10641        let params = ProgressParams::with_total(42i64, 50.0, 100.0);
10642        let value = serde_json::to_value(&params).expect("serialize");
10643        assert_eq!(value[PROGRESS_MARKER_KEY], 42);
10644        assert_eq!(value["progress"], 50.0);
10645        assert_eq!(value["total"], 100.0);
10646    }
10647
10648    #[test]
10649    fn progress_params_with_message() {
10650        let params = ProgressParams::new("tok", 0.75).with_message("Almost done");
10651        let value = serde_json::to_value(&params).expect("serialize");
10652        assert_eq!(value["message"], "Almost done");
10653    }
10654
10655    #[test]
10656    fn progress_params_fraction() {
10657        let params = ProgressParams::with_total("t", 25.0, 100.0);
10658        assert_eq!(params.fraction(), Some(0.25));
10659
10660        // Zero total
10661        let params = ProgressParams::with_total("t", 10.0, 0.0);
10662        assert_eq!(params.fraction(), Some(0.0));
10663
10664        // No total
10665        let params = ProgressParams::new("t", 0.5);
10666        assert_eq!(params.fraction(), None);
10667    }
10668
10669    // ========================================================================
10670    // LogLevel Tests
10671    // ========================================================================
10672
10673    #[test]
10674    fn exact_2024_log_levels_round_trip_on_logging_params() {
10675        for (level, wire) in [
10676            (LogLevel::Emergency, "emergency"),
10677            (LogLevel::Alert, "alert"),
10678            (LogLevel::Critical, "critical"),
10679            (LogLevel::Error, "error"),
10680            (LogLevel::Warning, "warning"),
10681            (LogLevel::Notice, "notice"),
10682            (LogLevel::Info, "info"),
10683            (LogLevel::Debug, "debug"),
10684        ] {
10685            assert_eq!(
10686                serde_json::to_value(level).expect("log level serializes"),
10687                wire
10688            );
10689            assert_eq!(
10690                serde_json::from_value::<LogLevel>(serde_json::json!(wire))
10691                    .expect("exact 2024 log level deserializes"),
10692                level
10693            );
10694            assert_eq!(
10695                serde_json::to_value(SetLogLevelParams { level })
10696                    .expect("set-level parameters serialize"),
10697                serde_json::json!({"level": wire})
10698            );
10699            let message = serde_json::from_value::<LogMessageParams>(serde_json::json!({
10700                "level": wire,
10701                "data": "event"
10702            }))
10703            .expect("message parameters deserialize");
10704            assert_eq!(message.level, level);
10705            assert_eq!(message.logger, None);
10706            assert_eq!(message.data, serde_json::json!("event"));
10707        }
10708        assert!(
10709            serde_json::from_value::<LogLevel>(serde_json::json!("trace")).is_err(),
10710            "the exact 2024 wire enum rejects non-MCP severity names"
10711        );
10712    }
10713
10714    // ========================================================================
10715    // Existing Tests (preserved below)
10716    // ========================================================================
10717
10718    #[test]
10719    fn list_resource_templates_params_serialization() {
10720        let params = ListResourceTemplatesParams::default();
10721        let value = serde_json::to_value(&params).expect("serialize params");
10722        assert_eq!(value, serde_json::json!({}));
10723
10724        let params = ListResourceTemplatesParams {
10725            cursor: Some("next".to_string()),
10726            ..Default::default()
10727        };
10728        let value = serde_json::to_value(&params).expect("serialize params with cursor");
10729        assert_eq!(value, serde_json::json!({ "cursor": "next" }));
10730    }
10731
10732    #[test]
10733    fn list_resource_templates_result_serialization() {
10734        let result = ListResourceTemplatesResult {
10735            resource_templates: vec![ResourceTemplate {
10736                uri_template: "resource://{id}".to_string(),
10737                name: "Resource Template".to_string(),
10738                description: Some("Template description".to_string()),
10739                mime_type: Some("text/plain".to_string()),
10740                icon: None,
10741                version: None,
10742                tags: vec![],
10743            }],
10744            next_cursor: None,
10745        };
10746
10747        let value = serde_json::to_value(&result).expect("serialize result");
10748        let templates = value
10749            .get("resourceTemplates")
10750            .expect("resourceTemplates key");
10751        let template = templates.get(0).expect("first resource template");
10752
10753        assert_eq!(template["uriTemplate"], "resource://{id}");
10754        assert_eq!(template["name"], "Resource Template");
10755        assert_eq!(template["description"], "Template description");
10756        assert_eq!(template["mimeType"], "text/plain");
10757    }
10758
10759    #[test]
10760    fn resource_updated_notification_serialization() {
10761        let params = ResourceUpdatedNotificationParams {
10762            uri: "resource://test".to_string(),
10763        };
10764        let value = serde_json::to_value(&params).expect("serialize params");
10765        assert_eq!(value, serde_json::json!({ "uri": "resource://test" }));
10766    }
10767
10768    #[test]
10769    fn subscribe_unsubscribe_resource_params_serialization() {
10770        let subscribe = SubscribeResourceParams {
10771            uri: "resource://alpha".to_string(),
10772        };
10773        let value = serde_json::to_value(&subscribe).expect("serialize subscribe params");
10774        assert_eq!(value, serde_json::json!({ "uri": "resource://alpha" }));
10775
10776        let unsubscribe = UnsubscribeResourceParams {
10777            uri: "resource://alpha".to_string(),
10778        };
10779        let value = serde_json::to_value(&unsubscribe).expect("serialize unsubscribe params");
10780        assert_eq!(value, serde_json::json!({ "uri": "resource://alpha" }));
10781    }
10782
10783    #[test]
10784    fn logging_params_serialization() {
10785        let set_level = SetLogLevelParams {
10786            level: LogLevel::Warning,
10787        };
10788        let value = serde_json::to_value(&set_level).expect("serialize setLevel");
10789        assert_eq!(value, serde_json::json!({ "level": "warning" }));
10790
10791        let log_message = LogMessageParams {
10792            level: LogLevel::Info,
10793            logger: Some("fastmcp_rust::server".to_string()),
10794            data: serde_json::Value::String("hello".to_string()),
10795        };
10796        let value = serde_json::to_value(&log_message).expect("serialize log message");
10797        assert_eq!(value["level"], "info");
10798        assert_eq!(value["logger"], "fastmcp_rust::server");
10799        assert_eq!(value["data"], "hello");
10800    }
10801
10802    #[test]
10803    fn task_status_notification_serialization() {
10804        let params = TaskStatusNotificationParams {
10805            id: TaskId::from_string("task-1"),
10806            status: TaskStatus::Running,
10807            progress: Some(0.5),
10808            message: Some("halfway".to_string()),
10809            error: None,
10810            result: None,
10811        };
10812        let value = serde_json::to_value(&params).expect("serialize task status notification");
10813        assert_eq!(
10814            value,
10815            serde_json::json!({
10816                "id": "task-1",
10817                "status": "running",
10818                "progress": 0.5,
10819                "message": "halfway"
10820            })
10821        );
10822    }
10823
10824    // ========================================================================
10825    // Sampling Tests
10826    // ========================================================================
10827
10828    #[test]
10829    fn create_message_params_minimal() {
10830        let params = CreateMessageParams::new(
10831            vec![SamplingMessage::user("Hello")],
10832            JsonInteger::from(100_i64),
10833        );
10834        let value = serde_json::to_value(&params).expect("serialize");
10835        assert_eq!(value[MAX_TOKENS_KEY], 100);
10836        assert!(value["messages"].is_array());
10837        assert!(value.get("systemPrompt").is_none());
10838        assert!(value.get("temperature").is_none());
10839    }
10840
10841    #[test]
10842    fn create_message_params_full() {
10843        let params = CreateMessageParams::new(
10844            vec![
10845                SamplingMessage::user("Hello"),
10846                SamplingMessage::assistant("Hi there!"),
10847            ],
10848            JsonInteger::from(500_i64),
10849        )
10850        .with_system_prompt("You are helpful")
10851        .with_temperature(0.7)
10852        .with_stop_sequences(vec!["END".to_string()]);
10853
10854        let value = serde_json::to_value(&params).expect("serialize");
10855        assert_eq!(value[MAX_TOKENS_KEY], 500);
10856        assert_eq!(value["systemPrompt"], "You are helpful");
10857        assert_eq!(value["temperature"], 0.7);
10858        assert_eq!(value["stopSequences"][0], "END");
10859        assert_eq!(value["messages"].as_array().unwrap().len(), 2);
10860    }
10861
10862    #[test]
10863    fn create_message_result_text() {
10864        let result = CreateMessageResult::text("Hello!", "claude-3");
10865        let value = serde_json::to_value(&result).expect("serialize");
10866        assert_eq!(value["content"]["type"], "text");
10867        assert_eq!(value["content"]["text"], "Hello!");
10868        assert_eq!(value["model"], "claude-3");
10869        assert_eq!(value["role"], "assistant");
10870        assert_eq!(value["stopReason"], "endTurn");
10871    }
10872
10873    #[test]
10874    fn create_message_result_max_tokens() {
10875        let result = CreateMessageResult::text("Truncated", "gpt-4").with_stop_reason("maxTokens");
10876        let value = serde_json::to_value(&result).expect("serialize");
10877        assert_eq!(value["stopReason"], "maxTo\x6bens");
10878    }
10879
10880    #[test]
10881    fn sampling_message_user() {
10882        let msg = SamplingMessage::user("Test message");
10883        let value = serde_json::to_value(&msg).expect("serialize");
10884        assert_eq!(value["role"], "user");
10885        assert_eq!(value["content"]["type"], "text");
10886        assert_eq!(value["content"]["text"], "Test message");
10887    }
10888
10889    #[test]
10890    fn sampling_message_assistant() {
10891        let msg = SamplingMessage::assistant("Response");
10892        let value = serde_json::to_value(&msg).expect("serialize");
10893        assert_eq!(value["role"], "assistant");
10894        assert_eq!(value["content"]["type"], "text");
10895        assert_eq!(value["content"]["text"], "Response");
10896    }
10897
10898    #[test]
10899    fn sampling_content_image() {
10900        let content = SamplingContent::Image {
10901            data: "base64data".to_string(),
10902            mime_type: "image/png".to_string(),
10903        };
10904        let value = serde_json::to_value(&content).expect("serialize");
10905        assert_eq!(value["type"], "image");
10906        assert_eq!(value["data"], "base64data");
10907        assert_eq!(value["mimeType"], "image/png");
10908    }
10909
10910    #[test]
10911    fn include_context_serialization() {
10912        let none = IncludeContext::None;
10913        let this = IncludeContext::ThisServer;
10914        let all = IncludeContext::AllServers;
10915
10916        assert_eq!(serde_json::to_value(none).unwrap(), "none");
10917        assert_eq!(serde_json::to_value(this).unwrap(), "thisServer");
10918        assert_eq!(serde_json::to_value(all).unwrap(), "allServers");
10919    }
10920
10921    #[test]
10922    fn create_message_result_text_content() {
10923        let result = CreateMessageResult::text("Hello!", "model");
10924        assert_eq!(result.text_content(), Some("Hello!"));
10925
10926        let result = CreateMessageResult {
10927            content: SamplingContent::Image {
10928                data: "data".to_string(),
10929                mime_type: "image/png".to_string(),
10930            },
10931            role: crate::types::Role::Assistant,
10932            model: "model".to_string(),
10933            stop_reason: None,
10934            meta: None,
10935        };
10936        assert_eq!(result.text_content(), None);
10937    }
10938
10939    // ========================================================================
10940    // Elicitation Tests
10941    // ========================================================================
10942
10943    #[test]
10944    fn elicit_form_params_serialization() {
10945        let params = ElicitRequestFormParams::new(
10946            "Please enter your name",
10947            serde_json::json!({
10948                "type": "object",
10949                "properties": {
10950                    "name": {"type": "string"}
10951                },
10952                "required": ["name"]
10953            }),
10954        );
10955        let value = serde_json::to_value(&params).expect("serialize");
10956        assert_eq!(value["mode"], "form");
10957        assert_eq!(value["message"], "Please enter your name");
10958        assert!(value["requestedSchema"]["properties"]["name"].is_object());
10959    }
10960
10961    #[test]
10962    fn elicit_url_params_serialization() {
10963        let params = ElicitRequestUrlParams::new(
10964            "Please authenticate",
10965            "https://auth.example.com/oauth",
10966            "elicit-12345",
10967        );
10968        let value = serde_json::to_value(&params).expect("serialize");
10969        assert_eq!(value["mode"], "url");
10970        assert_eq!(value["message"], "Please authenticate");
10971        assert_eq!(value["url"], "https://auth.example.com/oauth");
10972        assert_eq!(value["elicitationId"], "elicit-12345");
10973    }
10974
10975    #[test]
10976    fn elicit_request_params_untagged() {
10977        let form = ElicitRequestParams::form(
10978            "Enter name",
10979            serde_json::json!({"type": "object", "properties": {}}),
10980        );
10981        assert_eq!(form.mode(), ElicitMode::Form);
10982        assert_eq!(form.message(), "Enter name");
10983
10984        let url = ElicitRequestParams::url("Auth required", "https://example.com", "id-1");
10985        assert_eq!(url.mode(), ElicitMode::Url);
10986        assert_eq!(url.message(), "Auth required");
10987    }
10988
10989    #[test]
10990    fn final_embedded_form_elicitation_admits_a_flat_draft_2020_12_schema() {
10991        let request: FinalEmbeddedInputRequest = serde_json::from_value(serde_json::json!({
10992            "method": "elicitation/create",
10993            "params": {
10994                "mode": "form",
10995                "message": "Choose a display name",
10996                "requestedSchema": {
10997                    "$schema": "https://json-schema.org/draft/2020-12/schema",
10998                    "type": "object",
10999                    "properties": {
11000                        "displayName": {"type": "string", "minLength": 1}
11001                    },
11002                    "required": ["displayName"]
11003                }
11004            }
11005        }))
11006        .expect("a flat final form schema is admitted before descriptor interpretation");
11007
11008        let FinalEmbeddedInputRequest::Elicitation(FinalEmbeddedElicitationParams::Form(form)) =
11009            request
11010        else {
11011            panic!("fixture must decode as a final form elicitation descriptor");
11012        };
11013        assert_eq!(
11014            form.requested_schema.schema()["properties"]["displayName"]["type"],
11015            "string"
11016        );
11017    }
11018
11019    #[test]
11020    fn final_embedded_form_elicitation_rejects_only_a_nested_property_type() {
11021        let mut fixture = serde_json::json!({
11022            "method": "elicitation/create",
11023            "params": {
11024                "mode": "form",
11025                "message": "Choose a display name",
11026                "requestedSchema": {
11027                    "$schema": "https://json-schema.org/draft/2020-12/schema",
11028                    "type": "object",
11029                    "properties": {
11030                        "displayName": {"type": "string", "minLength": 1}
11031                    },
11032                    "required": ["displayName"]
11033                }
11034            }
11035        });
11036        fixture["params"]["requestedSchema"]["properties"]["displayName"]["type"] =
11037            serde_json::json!("object");
11038
11039        assert!(serde_json::from_value::<FinalEmbeddedInputRequest>(fixture).is_err());
11040    }
11041
11042    #[test]
11043    fn final_embedded_elicitation_variants_round_trip_their_exact_flat_wire() {
11044        for wire in [
11045            r#"{"method":"elicitation/create","params":{"message":"Choose a display name","mode":"form","requestedSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"displayName":{"minLength":1,"type":"string"}},"required":["displayName"],"type":"object"}}}"#,
11046            r#"{"method":"elicitation/create","params":{"message":"Authorize access","mode":"url","url":"https://example.com/authorize"}}"#,
11047        ] {
11048            let request: FinalEmbeddedInputRequest = serde_json::from_str(wire)
11049                .expect("a flat final elicitation descriptor is admitted");
11050            assert_eq!(
11051                serde_json::to_vec(&request).expect("admitted descriptor re-encodes"),
11052                wire.as_bytes(),
11053                "the final elicitation encoder must retain the flat mode-selected wire shape"
11054            );
11055        }
11056    }
11057
11058    #[test]
11059    fn final_embedded_url_elicitation_rejects_the_legacy_identity_field() {
11060        assert!(
11061            serde_json::from_value::<FinalEmbeddedInputRequest>(serde_json::json!({
11062                "method": "elicitation/create",
11063                "params": {
11064                    "mode": "url",
11065                    "message": "Authorize access",
11066                    "url": "https://example.com/authorize",
11067                    "elicitationId": "exact-2024-only",
11068                }
11069            }))
11070            .is_err()
11071        );
11072    }
11073
11074    #[test]
11075    fn final_embedded_elicitation_rejects_only_the_externally_tagged_shape() {
11076        let mut fixture = serde_json::json!({
11077            "method": "elicitation/create",
11078            "params": {
11079                "mode": "form",
11080                "message": "Choose a display name",
11081                "requestedSchema": {
11082                    "$schema": "https://json-schema.org/draft/2020-12/schema",
11083                    "type": "object",
11084                    "properties": {"displayName": {"type": "string"}},
11085                    "required": ["displayName"]
11086                }
11087            }
11088        });
11089        let params = fixture["params"].take();
11090        fixture["params"] = serde_json::json!({"Form": params});
11091
11092        assert!(serde_json::from_value::<FinalEmbeddedInputRequest>(fixture).is_err());
11093    }
11094
11095    #[test]
11096    fn elicit_result_accept_with_content() {
11097        let mut content = std::collections::HashMap::new();
11098        content.insert(
11099            "name".to_string(),
11100            ElicitContentValue::String("Alice".to_string()),
11101        );
11102        content.insert(
11103            "age".to_string(),
11104            ElicitContentValue::Int(JsonInteger::from(30_i64)),
11105        );
11106        content.insert("active".to_string(), ElicitContentValue::Bool(true));
11107
11108        let result = ElicitResult::accept(content);
11109        assert!(result.is_accepted());
11110        assert!(!result.is_declined());
11111        assert!(!result.is_cancelled());
11112        assert_eq!(result.get_string("name"), Some("Alice"));
11113        assert_eq!(result.get_int("age").map(JsonInteger::as_str), Some("30"));
11114        assert_eq!(result.get_bool("active"), Some(true));
11115    }
11116
11117    #[test]
11118    fn elicit_integer_content_preserves_arbitrary_width_and_distinguishes_fractional_values() {
11119        let integer_wire =
11120            r#"{"action":"accept","content":{"count":922337203685477580812345678901234567890}}"#;
11121        let integer: ElicitResult =
11122            serde_json::from_str(integer_wire).expect("arbitrary-width elicitation integer parses");
11123        assert_eq!(
11124            integer.get_int("count").map(JsonInteger::as_str),
11125            Some("922337203685477580812345678901234567890")
11126        );
11127        assert_eq!(
11128            serde_json::to_string(&integer).expect("arbitrary-width elicitation integer encodes"),
11129            integer_wire,
11130            "the exact integer elicitation value lexeme round-trips"
11131        );
11132
11133        let fractional: ElicitResult = serde_json::from_str(
11134            r#"{"action":"accept","content":{"count":922337203685477580812345678901234567890.5}}"#,
11135        )
11136        .expect("changing only the elicitation value to fractional remains a valid float");
11137        assert!(matches!(
11138            fractional
11139                .content
11140                .as_ref()
11141                .and_then(|content| content.get("count")),
11142            Some(ElicitContentValue::Float(_))
11143        ));
11144        assert!(fractional.get_int("count").is_none());
11145    }
11146
11147    #[test]
11148    fn elicit_result_serialization() {
11149        let result = ElicitResult::decline();
11150        let value = serde_json::to_value(&result).expect("serialize");
11151        assert_eq!(value["action"], "decline");
11152        assert!(value.get("content").is_none());
11153
11154        let result = ElicitResult::cancel();
11155        let value = serde_json::to_value(&result).expect("serialize");
11156        assert_eq!(value["action"], "cancel");
11157    }
11158
11159    #[test]
11160    fn elicit_content_value_conversions() {
11161        let s: ElicitContentValue = "hello".into();
11162        assert!(matches!(s, ElicitContentValue::String(_)));
11163
11164        let i: ElicitContentValue = 42i64.into();
11165        assert!(matches!(i, ElicitContentValue::Int(value) if value.as_str() == "42"));
11166
11167        let b: ElicitContentValue = true.into();
11168        assert!(matches!(b, ElicitContentValue::Bool(true)));
11169
11170        let f: ElicitContentValue = 1.23.into();
11171        assert!(matches!(f, ElicitContentValue::Float(_)));
11172
11173        let arr: ElicitContentValue = vec!["a".to_string(), "b".to_string()].into();
11174        assert!(matches!(arr, ElicitContentValue::StringArray(_)));
11175
11176        let none: ElicitContentValue = None::<String>.into();
11177        assert!(matches!(none, ElicitContentValue::Null));
11178    }
11179
11180    #[test]
11181    fn elicit_complete_notification_serialization() {
11182        let params = ElicitCompleteNotificationParams::new("elicit-12345");
11183        let value = serde_json::to_value(&params).expect("serialize");
11184        assert_eq!(value["elicitationId"], "elicit-12345");
11185    }
11186
11187    #[test]
11188    fn elicitation_capability_modes() {
11189        use crate::types::ElicitationCapability;
11190
11191        let form_only = ElicitationCapability::form();
11192        assert!(form_only.supports_form());
11193        assert!(!form_only.supports_url());
11194
11195        let url_only = ElicitationCapability::url();
11196        assert!(!url_only.supports_form());
11197        assert!(url_only.supports_url());
11198
11199        let both = ElicitationCapability::both();
11200        assert!(both.supports_form());
11201        assert!(both.supports_url());
11202    }
11203
11204    // ========================================================================
11205    // Roots tests
11206    // ========================================================================
11207
11208    #[test]
11209    fn root_new() {
11210        use crate::types::Root;
11211
11212        let root = Root::new("file:///home/user/project");
11213        assert_eq!(root.uri, "file:///home/user/project");
11214        assert!(root.name.is_none());
11215    }
11216
11217    #[test]
11218    fn root_with_name() {
11219        use crate::types::Root;
11220
11221        let root = Root::with_name("file:///home/user/project", "My Project");
11222        assert_eq!(root.uri, "file:///home/user/project");
11223        assert_eq!(root.name, Some("My Project".to_string()));
11224    }
11225
11226    #[test]
11227    fn root_serialization() {
11228        use crate::types::Root;
11229
11230        let root = Root::with_name("file:///home/user/project", "My Project");
11231        let json = serde_json::to_value(&root).expect("serialize");
11232        assert_eq!(json["uri"], "file:///home/user/project");
11233        assert_eq!(json["name"], "My Project");
11234
11235        // Without name
11236        let root_no_name = Root::new("file:///tmp");
11237        let json = serde_json::to_value(&root_no_name).expect("serialize");
11238        assert_eq!(json["uri"], "file:///tmp");
11239        assert!(json.get("name").is_none());
11240    }
11241
11242    #[test]
11243    fn list_roots_result_empty() {
11244        let result = ListRootsResult::empty();
11245        assert!(result.roots.is_empty());
11246    }
11247
11248    #[test]
11249    fn list_roots_result_serialization() {
11250        use crate::types::Root;
11251
11252        let result = ListRootsResult::new(vec![
11253            Root::with_name("file:///home/user/frontend", "Frontend"),
11254            Root::with_name("file:///home/user/backend", "Backend"),
11255        ]);
11256
11257        let json = serde_json::to_value(&result).expect("serialize");
11258        let roots = json["roots"].as_array().expect("roots array");
11259        assert_eq!(roots.len(), 2);
11260        assert_eq!(roots[0]["uri"], "file:///home/user/frontend");
11261        assert_eq!(roots[0]["name"], "Frontend");
11262        assert_eq!(roots[1]["uri"], "file:///home/user/backend");
11263        assert_eq!(roots[1]["name"], "Backend");
11264    }
11265
11266    #[test]
11267    fn roots_capability_serialization() {
11268        use crate::types::RootsCapability;
11269
11270        // With listChanged = true
11271        let cap = RootsCapability { list_changed: true };
11272        let json = serde_json::to_value(&cap).expect("serialize");
11273        assert_eq!(json["listChanged"], true);
11274
11275        // With listChanged = false (should be omitted)
11276        let cap = RootsCapability::default();
11277        let json = serde_json::to_value(&cap).expect("serialize");
11278        assert!(json.get("listChanged").is_none());
11279    }
11280
11281    // ========================================================================
11282    // Component Version Metadata Tests
11283    // ========================================================================
11284
11285    #[test]
11286    fn tool_version_serialization() {
11287        use crate::types::Tool;
11288
11289        // Tool without version (should omit version field)
11290        let tool = Tool {
11291            name: "my_tool".to_string(),
11292            description: Some("A test tool".to_string()),
11293            input_schema: serde_json::json!({"type": "object"}),
11294            output_schema: None,
11295            icon: None,
11296            version: None,
11297            tags: vec![],
11298            annotations: None,
11299        };
11300        let json = serde_json::to_value(&tool).expect("serialize");
11301        assert!(json.get("version").is_none());
11302
11303        // Tool with version
11304        let tool = Tool {
11305            name: "my_tool".to_string(),
11306            description: Some("A test tool".to_string()),
11307            input_schema: serde_json::json!({"type": "object"}),
11308            output_schema: None,
11309            icon: None,
11310            version: Some("1.2.3".to_string()),
11311            tags: vec![],
11312            annotations: None,
11313        };
11314        let json = serde_json::to_value(&tool).expect("serialize");
11315        assert_eq!(json["version"], "1.2.3");
11316    }
11317
11318    #[test]
11319    fn resource_version_serialization() {
11320        use crate::types::Resource;
11321
11322        // Resource without version
11323        let resource = Resource {
11324            uri: "file://test".to_string(),
11325            name: "Test Resource".to_string(),
11326            description: None,
11327            mime_type: Some("text/plain".to_string()),
11328            icon: None,
11329            version: None,
11330            tags: vec![],
11331        };
11332        let json = serde_json::to_value(&resource).expect("serialize");
11333        assert!(json.get("version").is_none());
11334
11335        // Resource with version
11336        let resource = Resource {
11337            uri: "file://test".to_string(),
11338            name: "Test Resource".to_string(),
11339            description: None,
11340            mime_type: Some("text/plain".to_string()),
11341            icon: None,
11342            version: Some("2.0.0".to_string()),
11343            tags: vec![],
11344        };
11345        let json = serde_json::to_value(&resource).expect("serialize");
11346        assert_eq!(json["version"], "2.0.0");
11347    }
11348
11349    #[test]
11350    fn prompt_version_serialization() {
11351        use crate::types::Prompt;
11352
11353        // Prompt without version
11354        let prompt = Prompt {
11355            name: "greeting".to_string(),
11356            description: Some("A greeting prompt".to_string()),
11357            arguments: vec![],
11358            icon: None,
11359            version: None,
11360            tags: vec![],
11361        };
11362        let json = serde_json::to_value(&prompt).expect("serialize");
11363        assert!(json.get("version").is_none());
11364
11365        // Prompt with version
11366        let prompt = Prompt {
11367            name: "greeting".to_string(),
11368            description: Some("A greeting prompt".to_string()),
11369            arguments: vec![],
11370            icon: None,
11371            version: Some("0.1.0".to_string()),
11372            tags: vec![],
11373        };
11374        let json = serde_json::to_value(&prompt).expect("serialize");
11375        assert_eq!(json["version"], "0.1.0");
11376    }
11377
11378    #[test]
11379    fn resource_template_version_serialization() {
11380        // ResourceTemplate without version
11381        let template = ResourceTemplate {
11382            uri_template: "file://{path}".to_string(),
11383            name: "Files".to_string(),
11384            description: None,
11385            mime_type: None,
11386            icon: None,
11387            version: None,
11388            tags: vec![],
11389        };
11390        let json = serde_json::to_value(&template).expect("serialize");
11391        assert!(json.get("version").is_none());
11392
11393        // ResourceTemplate with version
11394        let template = ResourceTemplate {
11395            uri_template: "file://{path}".to_string(),
11396            name: "Files".to_string(),
11397            description: None,
11398            mime_type: None,
11399            icon: None,
11400            version: Some("3.0.0".to_string()),
11401            tags: vec![],
11402        };
11403        let json = serde_json::to_value(&template).expect("serialize");
11404        assert_eq!(json["version"], "3.0.0");
11405    }
11406
11407    #[test]
11408    fn version_deserialization() {
11409        use crate::types::{Prompt, Resource, Tool};
11410
11411        // Deserialize tool without version
11412        let json = serde_json::json!({
11413            "name": "tool",
11414            "inputSchema": {"type": "object"}
11415        });
11416        let tool: Tool = serde_json::from_value(json).expect("deserialize");
11417        assert!(tool.version.is_none());
11418
11419        // Deserialize tool with version
11420        let json = serde_json::json!({
11421            "name": "tool",
11422            "inputSchema": {"type": "object"},
11423            "version": "1.0.0"
11424        });
11425        let tool: Tool = serde_json::from_value(json).expect("deserialize");
11426        assert_eq!(tool.version, Some("1.0.0".to_string()));
11427
11428        // Deserialize resource without version
11429        let json = serde_json::json!({
11430            "uri": "file://test",
11431            "name": "Test"
11432        });
11433        let resource: Resource = serde_json::from_value(json).expect("deserialize");
11434        assert!(resource.version.is_none());
11435
11436        // Deserialize prompt without version
11437        let json = serde_json::json!({
11438            "name": "prompt"
11439        });
11440        let prompt: Prompt = serde_json::from_value(json).expect("deserialize");
11441        assert!(prompt.version.is_none());
11442    }
11443
11444    // ========================================================================
11445    // Tags Serialization Tests
11446    // ========================================================================
11447
11448    #[test]
11449    fn tool_tags_serialization() {
11450        use crate::types::Tool;
11451
11452        // Tool without tags (empty vec should not appear in JSON)
11453        let tool = Tool {
11454            name: "my_tool".to_string(),
11455            description: None,
11456            input_schema: serde_json::json!({"type": "object"}),
11457            output_schema: None,
11458            icon: None,
11459            version: None,
11460            tags: vec![],
11461            annotations: None,
11462        };
11463        let json = serde_json::to_value(&tool).expect("serialize");
11464        assert!(
11465            json.get("tags").is_none(),
11466            "Empty tags should not appear in JSON"
11467        );
11468
11469        // Tool with tags
11470        let tool = Tool {
11471            name: "my_tool".to_string(),
11472            description: None,
11473            input_schema: serde_json::json!({"type": "object"}),
11474            output_schema: None,
11475            icon: None,
11476            version: None,
11477            tags: vec!["api".to_string(), "database".to_string()],
11478            annotations: None,
11479        };
11480        let json = serde_json::to_value(&tool).expect("serialize");
11481        assert_eq!(json["tags"], serde_json::json!(["api", "database"]));
11482    }
11483
11484    #[test]
11485    fn resource_tags_serialization() {
11486        use crate::types::Resource;
11487
11488        // Resource without tags
11489        let resource = Resource {
11490            uri: "file://test".to_string(),
11491            name: "Test Resource".to_string(),
11492            description: None,
11493            mime_type: None,
11494            icon: None,
11495            version: None,
11496            tags: vec![],
11497        };
11498        let json = serde_json::to_value(&resource).expect("serialize");
11499        assert!(
11500            json.get("tags").is_none(),
11501            "Empty tags should not appear in JSON"
11502        );
11503
11504        // Resource with tags
11505        let resource = Resource {
11506            uri: "file://test".to_string(),
11507            name: "Test Resource".to_string(),
11508            description: None,
11509            mime_type: None,
11510            icon: None,
11511            version: None,
11512            tags: vec!["files".to_string(), "readonly".to_string()],
11513        };
11514        let json = serde_json::to_value(&resource).expect("serialize");
11515        assert_eq!(json["tags"], serde_json::json!(["files", "readonly"]));
11516    }
11517
11518    #[test]
11519    fn prompt_tags_serialization() {
11520        use crate::types::Prompt;
11521
11522        // Prompt without tags
11523        let prompt = Prompt {
11524            name: "greeting".to_string(),
11525            description: None,
11526            arguments: vec![],
11527            icon: None,
11528            version: None,
11529            tags: vec![],
11530        };
11531        let json = serde_json::to_value(&prompt).expect("serialize");
11532        assert!(
11533            json.get("tags").is_none(),
11534            "Empty tags should not appear in JSON"
11535        );
11536
11537        // Prompt with tags
11538        let prompt = Prompt {
11539            name: "greeting".to_string(),
11540            description: None,
11541            arguments: vec![],
11542            icon: None,
11543            version: None,
11544            tags: vec!["templates".to_string(), "onboarding".to_string()],
11545        };
11546        let json = serde_json::to_value(&prompt).expect("serialize");
11547        assert_eq!(json["tags"], serde_json::json!(["templates", "onboarding"]));
11548    }
11549
11550    #[test]
11551    fn resource_template_tags_serialization() {
11552        // ResourceTemplate without tags
11553        let template = ResourceTemplate {
11554            uri_template: "file://{path}".to_string(),
11555            name: "Files".to_string(),
11556            description: None,
11557            mime_type: None,
11558            icon: None,
11559            version: None,
11560            tags: vec![],
11561        };
11562        let json = serde_json::to_value(&template).expect("serialize");
11563        assert!(
11564            json.get("tags").is_none(),
11565            "Empty tags should not appear in JSON"
11566        );
11567
11568        // ResourceTemplate with tags
11569        let template = ResourceTemplate {
11570            uri_template: "file://{path}".to_string(),
11571            name: "Files".to_string(),
11572            description: None,
11573            mime_type: None,
11574            icon: None,
11575            version: None,
11576            tags: vec!["filesystem".to_string()],
11577        };
11578        let json = serde_json::to_value(&template).expect("serialize");
11579        assert_eq!(json["tags"], serde_json::json!(["filesystem"]));
11580    }
11581
11582    #[test]
11583    fn tags_deserialization() {
11584        use crate::types::{Prompt, Resource, Tool};
11585
11586        // Deserialize tool without tags field
11587        let json = serde_json::json!({
11588            "name": "tool",
11589            "inputSchema": {"type": "object"}
11590        });
11591        let tool: Tool = serde_json::from_value(json).expect("deserialize");
11592        assert!(tool.tags.is_empty());
11593
11594        // Deserialize tool with tags
11595        let json = serde_json::json!({
11596            "name": "tool",
11597            "inputSchema": {"type": "object"},
11598            "tags": ["compute", "heavy"]
11599        });
11600        let tool: Tool = serde_json::from_value(json).expect("deserialize");
11601        assert_eq!(tool.tags, vec!["compute", "heavy"]);
11602
11603        // Deserialize resource without tags
11604        let json = serde_json::json!({
11605            "uri": "file://test",
11606            "name": "Test"
11607        });
11608        let resource: Resource = serde_json::from_value(json).expect("deserialize");
11609        assert!(resource.tags.is_empty());
11610
11611        // Deserialize resource with tags
11612        let json = serde_json::json!({
11613            "uri": "file://test",
11614            "name": "Test",
11615            "tags": ["data"]
11616        });
11617        let resource: Resource = serde_json::from_value(json).expect("deserialize");
11618        assert_eq!(resource.tags, vec!["data"]);
11619
11620        // Deserialize prompt without tags
11621        let json = serde_json::json!({
11622            "name": "prompt"
11623        });
11624        let prompt: Prompt = serde_json::from_value(json).expect("deserialize");
11625        assert!(prompt.tags.is_empty());
11626
11627        // Deserialize prompt with tags
11628        let json = serde_json::json!({
11629            "name": "prompt",
11630            "tags": ["greeting", "onboarding"]
11631        });
11632        let prompt: Prompt = serde_json::from_value(json).expect("deserialize");
11633        assert_eq!(prompt.tags, vec!["greeting", "onboarding"]);
11634    }
11635
11636    // ========================================================================
11637    // Tool Annotations Serialization Tests
11638    // ========================================================================
11639
11640    #[test]
11641    fn tool_annotations_serialization() {
11642        use crate::types::{Tool, ToolAnnotations};
11643
11644        // Tool without annotations (None should not appear in JSON)
11645        let tool = Tool {
11646            name: "my_tool".to_string(),
11647            description: None,
11648            input_schema: serde_json::json!({"type": "object"}),
11649            output_schema: None,
11650            icon: None,
11651            version: None,
11652            tags: vec![],
11653            annotations: None,
11654        };
11655        let json = serde_json::to_value(&tool).expect("serialize");
11656        assert!(
11657            json.get("annotations").is_none(),
11658            "None annotations should not appear in JSON"
11659        );
11660
11661        // Tool with annotations
11662        let tool = Tool {
11663            name: "delete_file".to_string(),
11664            description: Some("Deletes a file".to_string()),
11665            input_schema: serde_json::json!({"type": "object"}),
11666            output_schema: None,
11667            icon: None,
11668            version: None,
11669            tags: vec![],
11670            annotations: Some(
11671                ToolAnnotations::new()
11672                    .destructive(true)
11673                    .idempotent(false)
11674                    .read_only(false),
11675            ),
11676        };
11677        let json = serde_json::to_value(&tool).expect("serialize");
11678        let annotations = json.get("annotations").expect("annotations field");
11679        // MCP-spec wire names are the `*Hint` forms.
11680        assert_eq!(annotations["destructiveHint"], true);
11681        assert_eq!(annotations["idempotentHint"], false);
11682        assert_eq!(annotations["readOnlyHint"], false);
11683        assert!(annotations.get("destructive").is_none());
11684        assert!(annotations.get("readOnly").is_none());
11685        assert!(annotations.get("openWorldHint").is_none());
11686
11687        // Tool with read_only annotation
11688        let tool = Tool {
11689            name: "get_status".to_string(),
11690            description: Some("Gets status".to_string()),
11691            input_schema: serde_json::json!({"type": "object"}),
11692            output_schema: None,
11693            icon: None,
11694            version: None,
11695            tags: vec![],
11696            annotations: Some(ToolAnnotations::new().read_only(true)),
11697        };
11698        let json = serde_json::to_value(&tool).expect("serialize");
11699        let annotations = json.get("annotations").expect("annotations field");
11700        assert_eq!(annotations["readOnlyHint"], true);
11701        assert!(annotations.get("destructiveHint").is_none());
11702    }
11703
11704    #[test]
11705    fn tool_annotations_deserialization() {
11706        use crate::types::Tool;
11707
11708        // Deserialize tool without annotations
11709        let json = serde_json::json!({
11710            "name": "tool",
11711            "inputSchema": {"type": "object"}
11712        });
11713        let tool: Tool = serde_json::from_value(json).expect("deserialize");
11714        assert!(tool.annotations.is_none());
11715
11716        // Deserialize tool with annotations
11717        let json = serde_json::json!({
11718            "name": "delete_tool",
11719            "inputSchema": {"type": "object"},
11720            "annotations": {
11721                "destructiveHint": true,
11722                "idempotentHint": false,
11723                "readOnlyHint": false,
11724                "openWorldHint": true
11725            }
11726        });
11727        let tool: Tool = serde_json::from_value(json).expect("deserialize");
11728        let annotations = tool.annotations.expect("annotations present");
11729        assert_eq!(annotations.destructive, Some(true));
11730        assert_eq!(annotations.idempotent, Some(false));
11731        assert_eq!(annotations.read_only, Some(false));
11732        assert_eq!(annotations.open_world_hint, Some(true));
11733    }
11734
11735    #[test]
11736    fn tool_annotations_builder() {
11737        use crate::types::ToolAnnotations;
11738
11739        let annotations = ToolAnnotations::new()
11740            .destructive(true)
11741            .idempotent(true)
11742            .read_only(false)
11743            .open_world_hint(true);
11744
11745        assert_eq!(annotations.destructive, Some(true));
11746        assert_eq!(annotations.idempotent, Some(true));
11747        assert_eq!(annotations.read_only, Some(false));
11748        assert_eq!(annotations.open_world_hint, Some(true));
11749        assert!(!annotations.is_empty());
11750
11751        // Empty annotations
11752        let empty = ToolAnnotations::new();
11753        assert!(empty.is_empty());
11754    }
11755
11756    // ========================================================================
11757    // Tool Output Schema Serialization Tests
11758    // ========================================================================
11759
11760    #[test]
11761    fn tool_output_schema_serialization() {
11762        use crate::types::Tool;
11763
11764        // Tool without output_schema (None should not appear in JSON)
11765        let tool = Tool {
11766            name: "my_tool".to_string(),
11767            description: None,
11768            input_schema: serde_json::json!({"type": "object"}),
11769            output_schema: None,
11770            icon: None,
11771            version: None,
11772            tags: vec![],
11773            annotations: None,
11774        };
11775        let json = serde_json::to_value(&tool).expect("serialize");
11776        assert!(
11777            json.get("outputSchema").is_none(),
11778            "None output_schema should not appear in JSON"
11779        );
11780
11781        // Tool with output_schema
11782        let tool = Tool {
11783            name: "compute".to_string(),
11784            description: Some("Computes a result".to_string()),
11785            input_schema: serde_json::json!({"type": "object"}),
11786            output_schema: Some(serde_json::json!({
11787                "type": "object",
11788                "properties": {
11789                    "result": {"type": "number"},
11790                    "success": {"type": "boolean"}
11791                }
11792            })),
11793            icon: None,
11794            version: None,
11795            tags: vec![],
11796            annotations: None,
11797        };
11798        let json = serde_json::to_value(&tool).expect("serialize");
11799        let output_schema = json.get("outputSchema").expect("outputSchema field");
11800        assert_eq!(output_schema["type"], "object");
11801        assert_eq!(output_schema["properties"]["result"]["type"], "number");
11802        assert_eq!(output_schema["properties"]["success"]["type"], "boolean");
11803    }
11804
11805    #[test]
11806    fn tool_output_schema_deserialization() {
11807        use crate::types::Tool;
11808
11809        // Deserialize tool without output_schema
11810        let json = serde_json::json!({
11811            "name": "tool",
11812            "inputSchema": {"type": "object"}
11813        });
11814        let tool: Tool = serde_json::from_value(json).expect("deserialize");
11815        assert!(tool.output_schema.is_none());
11816
11817        // Deserialize tool with output_schema
11818        let json = serde_json::json!({
11819            "name": "compute",
11820            "inputSchema": {"type": "object"},
11821            "outputSchema": {
11822                "type": "object",
11823                "properties": {
11824                    "value": {"type": "integer"}
11825                }
11826            }
11827        });
11828        let tool: Tool = serde_json::from_value(json).expect("deserialize");
11829        assert!(tool.output_schema.is_some());
11830        let schema = tool.output_schema.unwrap();
11831        assert_eq!(schema["type"], "object");
11832        assert_eq!(schema["properties"]["value"]["type"], "integer");
11833    }
11834}