1use 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(untagged)]
51pub enum ProgressMarker {
52 String(String),
54 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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
108pub struct RequestMeta {
109 #[serde(rename = "progressTo\x6ben", skip_serializing_if = "Option::is_none")]
112 pub progress_marker: Option<ProgressMarker>,
113}
114
115pub const FINAL_PROTOCOL_VERSION_META_KEY: &str = "io.modelcontextprotocol/protocolVersion";
121
122pub const FINAL_CLIENT_CAPABILITIES_META_KEY: &str = "io.modelcontextprotocol/clientCapabilities";
124
125pub const FINAL_CLIENT_INFO_META_KEY: &str = "io.modelcontextprotocol/clientInfo";
127
128pub const FINAL_LOG_LEVEL_META_KEY: &str = "io.modelcontextprotocol/logLevel";
130
131pub const FINAL_SERVER_INFO_META_KEY: &str = "io.modelcontextprotocol/serverInfo";
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct FinalRequestMeta {
141 #[serde(rename = "io.modelcontextprotocol/protocolVersion")]
143 pub protocol_version: String,
144 #[serde(rename = "io.modelcontextprotocol/clientCapabilities")]
146 pub client_capabilities: ClientCapabilities,
147 #[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 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
156 pub additional_metadata: BTreeMap<String, Value>,
157}
158
159impl FinalRequestMeta {
160 #[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 #[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#[derive(Debug, Clone, Default, PartialEq)]
196pub struct FinalInputResponses {
197 entries: Vec<(String, FinalEmbeddedInputResponse)>,
198}
199
200impl FinalInputResponses {
201 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 #[must_use]
216 pub fn entries(&self) -> &[(String, FinalEmbeddedInputResponse)] {
217 &self.entries
218 }
219
220 #[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 #[must_use]
231 pub const fn len(&self) -> usize {
232 self.entries.len()
233 }
234
235 #[must_use]
237 pub const fn is_empty(&self) -> bool {
238 self.entries.is_empty()
239 }
240
241 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340pub enum FinalInputResponseCorrelationError {
341 InvalidInputRequest,
343 DuplicateResponseKey,
345 UnknownResponseKey,
347 MissingResponse,
349 ResponseKindMismatch,
351 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#[derive(Debug, Clone, Serialize, Deserialize)]
396#[serde(deny_unknown_fields)]
397pub struct FinalListParams {
398 #[serde(rename = "_meta")]
400 pub meta: OpenMetadata,
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub cursor: Option<String>,
404 #[serde(
406 rename = "includeTags",
407 default,
408 skip_serializing_if = "Option::is_none"
409 )]
410 pub include_tags: Option<Vec<String>>,
411 #[serde(
413 rename = "excludeTags",
414 default,
415 skip_serializing_if = "Option::is_none"
416 )]
417 pub exclude_tags: Option<Vec<String>>,
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
422pub enum FinalArguments<T> {
423 Absent,
425 Value(T),
427}
428
429impl<T> FinalArguments<T> {
430 #[must_use]
432 pub const fn is_absent(&self) -> bool {
433 matches!(self, Self::Absent)
434 }
435
436 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
488#[serde(deny_unknown_fields)]
489pub struct FinalCallToolParams {
490 #[serde(rename = "_meta")]
492 pub meta: OpenMetadata,
493 pub name: String,
495 #[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 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
522#[serde(deny_unknown_fields)]
523pub struct FinalReadResourceParams {
524 #[serde(rename = "_meta")]
526 pub meta: OpenMetadata,
527 pub uri: AbsoluteUri,
529 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
549#[serde(deny_unknown_fields)]
550pub struct FinalGetPromptParams {
551 #[serde(rename = "_meta")]
553 pub meta: OpenMetadata,
554 pub name: String,
556 #[serde(default, skip_serializing_if = "FinalArguments::is_absent")]
558 pub arguments: FinalArguments<BTreeMap<String, String>>,
559 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
595#[serde(tag = "type")]
596pub enum LegacyCompletionReference {
597 #[serde(rename = "ref/prompt")]
599 Prompt {
600 name: String,
602 },
603 #[serde(rename = "ref/resource")]
605 Resource {
606 uri: String,
608 },
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize)]
613pub struct LegacyCompletionArgument {
614 pub name: String,
616 pub value: String,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize)]
622pub struct LegacyCompletionParams {
623 #[serde(rename = "ref")]
625 pub reference: LegacyCompletionReference,
626 pub argument: LegacyCompletionArgument,
628 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
630 pub meta: Option<RequestMeta>,
631}
632
633#[derive(Debug, Clone)]
635pub enum FinalCompletionReference {
636 Prompt {
638 name: String,
640 },
641 PromptWithTitle {
643 name: String,
645 title: String,
647 },
648 Resource {
650 uri: String,
652 },
653}
654
655impl FinalCompletionReference {
656 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#[derive(Debug, Clone, Serialize, Deserialize)]
721#[serde(deny_unknown_fields)]
722pub struct FinalCompletionArgument {
723 pub name: String,
725 pub value: String,
727}
728
729#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
731#[serde(deny_unknown_fields)]
732pub struct FinalCompletionContext {
733 #[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
743pub const MAX_COMPLETION_CONTEXT_ARGUMENTS: usize = 256;
745pub const MAX_COMPLETION_CONTEXT_ARGUMENT_KEY_BYTES: usize = 1024;
747pub const MAX_COMPLETION_CONTEXT_ARGUMENT_VALUE_BYTES: usize = 16 * 1024;
749pub const MAX_COMPLETION_CONTEXT_ARGUMENT_BYTES: usize = 256 * 1024;
751
752pub(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 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; 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 (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)) .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#[derive(Debug, Clone, Serialize, Deserialize)]
1400#[serde(deny_unknown_fields)]
1401pub struct FinalCompletionParams {
1402 #[serde(rename = "_meta")]
1404 pub meta: OpenMetadata,
1405 #[serde(rename = "ref")]
1407 pub reference: FinalCompletionReference,
1408 pub argument: FinalCompletionArgument,
1410 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
1421#[serde(deny_unknown_fields)]
1422pub struct FinalEmptyParams {
1423 #[serde(rename = "_meta")]
1425 pub meta: OpenMetadata,
1426}
1427
1428pub const FINAL_SUBSCRIPTION_ID_META_KEY: &str = "io.modelcontextprotocol/subscriptionId";
1430
1431#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1436pub struct SubscriptionFilter {
1437 #[serde(
1439 rename = "promptsListChanged",
1440 default,
1441 skip_serializing_if = "Option::is_none"
1442 )]
1443 pub prompts_list_changed: Option<bool>,
1444 #[serde(
1446 rename = "resourceSubscriptions",
1447 default,
1448 skip_serializing_if = "Option::is_none"
1449 )]
1450 pub resource_subscriptions: Option<Vec<String>>,
1451 #[serde(
1453 rename = "resourcesListChanged",
1454 default,
1455 skip_serializing_if = "Option::is_none"
1456 )]
1457 pub resources_list_changed: Option<bool>,
1458 #[serde(
1460 rename = "toolsListChanged",
1461 default,
1462 skip_serializing_if = "Option::is_none"
1463 )]
1464 pub tools_list_changed: Option<bool>,
1465 #[serde(flatten, default)]
1468 pub additional: BTreeMap<String, Value>,
1469}
1470
1471#[derive(Debug, Clone, Serialize, Deserialize)]
1473#[serde(deny_unknown_fields)]
1474pub struct FinalSubscriptionsListenParams {
1475 #[serde(rename = "_meta")]
1477 pub meta: OpenMetadata,
1478 pub notifications: SubscriptionFilter,
1480}
1481
1482#[derive(Debug, Clone, Serialize, Deserialize)]
1484pub struct FinalSubscriptionsAcknowledgedNotificationParams {
1485 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1488 pub meta: Option<OpenMetadata>,
1489 pub notifications: SubscriptionFilter,
1491 #[serde(flatten, default)]
1493 pub additional: BTreeMap<String, Value>,
1494}
1495
1496#[derive(Debug, Clone)]
1502pub struct FinalLogMessageParams {
1503 pub level: LoggingLevel,
1505 pub logger: Option<String>,
1507 pub data: Value,
1509 pub meta: Option<OpenMetadata>,
1511 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#[derive(Debug, Clone, Serialize)]
1583pub struct FinalCancelledNotificationParams {
1584 #[serde(rename = "requestId")]
1586 pub request_id: RequestId,
1587 #[serde(default, skip_serializing_if = "Option::is_none")]
1589 pub reason: Option<String>,
1590 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1592 pub meta: Option<OpenMetadata>,
1593 #[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#[derive(Debug, Clone, Serialize)]
1652pub struct FinalProgressNotificationParams {
1653 #[serde(rename = "progressToken")]
1655 pub progress_token: ProgressMarker,
1656 pub progress: ExactNonNegativeJsonNumber,
1658 #[serde(default, skip_serializing_if = "Option::is_none")]
1660 pub total: Option<ExactNonNegativeJsonNumber>,
1661 #[serde(default, skip_serializing_if = "Option::is_none")]
1663 pub message: Option<String>,
1664 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1666 pub meta: Option<OpenMetadata>,
1667 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
1706pub struct FinalResourceUpdatedNotificationParams {
1707 pub uri: AbsoluteUri,
1709 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1711 pub meta: Option<OpenMetadata>,
1712 #[serde(flatten, default)]
1714 pub additional: BTreeMap<String, Value>,
1715}
1716
1717#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1722pub struct FinalEmptyNotificationParams {
1723 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1725 pub meta: Option<OpenMetadata>,
1726 #[serde(flatten, default)]
1728 pub additional: BTreeMap<String, Value>,
1729}
1730
1731#[derive(Debug, Clone, Serialize, Deserialize)]
1733#[serde(deny_unknown_fields)]
1734pub struct FinalCreateMessageParams {
1735 #[serde(rename = "_meta")]
1737 pub meta: OpenMetadata,
1738 pub messages: Vec<crate::types::FinalSamplingMessage>,
1740 #[serde(rename = "maxTokens")]
1742 pub max_tokens: JsonInteger,
1743 #[serde(
1745 rename = "systemPrompt",
1746 default,
1747 skip_serializing_if = "Option::is_none"
1748 )]
1749 pub system_prompt: Option<String>,
1750 #[serde(default, skip_serializing_if = "Option::is_none")]
1752 pub temperature: Option<f64>,
1753 #[serde(
1755 rename = "stopSequences",
1756 default,
1757 skip_serializing_if = "Option::is_none"
1758 )]
1759 pub stop_sequences: Option<Vec<String>>,
1760 #[serde(
1762 rename = "modelPreferences",
1763 default,
1764 skip_serializing_if = "Option::is_none"
1765 )]
1766 pub model_preferences: Option<crate::types::ModelPreferences>,
1767 #[serde(
1769 rename = "includeContext",
1770 default,
1771 skip_serializing_if = "Option::is_none"
1772 )]
1773 pub include_context: Option<IncludeContext>,
1774 #[serde(default, skip_serializing_if = "Option::is_none")]
1776 pub metadata: Option<serde_json::Map<String, Value>>,
1777 #[serde(default, skip_serializing_if = "Option::is_none")]
1779 pub tools: Option<Vec<crate::types::FinalTool>>,
1780 #[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1791#[serde(deny_unknown_fields)]
1792pub struct FinalCreateMessageResult {
1793 pub content: crate::types::FinalSamplingMessageContent,
1795 pub model: String,
1797 pub role: crate::types::Role,
1799 #[serde(
1801 rename = "stopReason",
1802 default,
1803 skip_serializing_if = "Option::is_none"
1804 )]
1805 pub stop_reason: Option<String>,
1806 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1811 pub meta: Option<OpenMetadata>,
1812}
1813
1814#[derive(Debug, Clone, Serialize, Deserialize)]
1816#[serde(deny_unknown_fields)]
1817pub struct FinalCreateMessageInputRequiredResult {
1818 #[serde(rename = "resultType")]
1820 pub result_type: FinalInputRequiredResultType,
1821 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1823 pub meta: Option<OpenMetadata>,
1824 #[serde(
1826 rename = "inputRequests",
1827 default,
1828 skip_serializing_if = "Option::is_none"
1829 )]
1830 pub input_requests: Option<BTreeMap<String, Value>>,
1831 #[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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1856pub enum FinalInputRequiredResultType {
1857 #[serde(rename = "input_required")]
1859 InputRequired,
1860}
1861
1862#[derive(Debug, Clone)]
1868pub enum FinalEmbeddedInputRequest {
1869 Sampling(FinalEmbeddedCreateMessageParams),
1871 Roots(FinalEmbeddedRootsListParams),
1873 Elicitation(FinalEmbeddedElicitationParams),
1875}
1876
1877impl FinalEmbeddedInputRequest {
1878 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1992pub enum FinalEmbeddedInputKind {
1993 Sampling,
1995 Roots,
1997 FormElicitation,
1999 UrlElicitation,
2001}
2002
2003#[derive(Debug, Clone, Serialize, Deserialize)]
2005#[serde(deny_unknown_fields)]
2006pub struct FinalEmbeddedCreateMessageParams {
2007 pub messages: Vec<crate::types::FinalSamplingMessage>,
2009 #[serde(rename = "maxTokens")]
2011 pub max_tokens: JsonInteger,
2012 #[serde(
2014 rename = "systemPrompt",
2015 default,
2016 skip_serializing_if = "Option::is_none"
2017 )]
2018 pub system_prompt: Option<String>,
2019 #[serde(default, skip_serializing_if = "Option::is_none")]
2021 pub temperature: Option<f64>,
2022 #[serde(
2024 rename = "stopSequences",
2025 default,
2026 skip_serializing_if = "Option::is_none"
2027 )]
2028 pub stop_sequences: Option<Vec<String>>,
2029 #[serde(
2031 rename = "modelPreferences",
2032 default,
2033 skip_serializing_if = "Option::is_none"
2034 )]
2035 pub model_preferences: Option<crate::types::ModelPreferences>,
2036 #[serde(
2038 rename = "includeContext",
2039 default,
2040 skip_serializing_if = "Option::is_none"
2041 )]
2042 pub include_context: Option<IncludeContext>,
2043 #[serde(default, skip_serializing_if = "Option::is_none")]
2045 pub metadata: Option<serde_json::Map<String, Value>>,
2046 #[serde(default, skip_serializing_if = "Option::is_none")]
2048 pub tools: Option<Vec<crate::types::FinalTool>>,
2049 #[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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2060#[serde(deny_unknown_fields)]
2061pub struct FinalEmbeddedRootsListParams {
2062 #[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#[derive(Debug, Clone)]
2089pub enum FinalEmbeddedElicitationParams {
2090 Form(FinalEmbeddedFormElicitationParams),
2092 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#[derive(Debug, Clone, Serialize, Deserialize)]
2139#[serde(deny_unknown_fields)]
2140pub struct FinalEmbeddedFormElicitationParams {
2141 pub mode: ElicitMode,
2143 pub message: String,
2145 #[serde(rename = "requestedSchema")]
2147 pub requested_schema: crate::schema::AdmittedFinalFormSchema,
2148}
2149
2150#[derive(Debug, Clone, Serialize, Deserialize)]
2152#[serde(deny_unknown_fields)]
2153pub struct FinalEmbeddedUrlElicitationParams {
2154 pub mode: ElicitMode,
2156 pub message: String,
2158 pub url: AbsoluteUri,
2160}
2161
2162#[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(FinalCreateMessageResult),
2171 Roots(FinalEmbeddedRootsListResult),
2173 Elicitation(FinalEmbeddedElicitationResult),
2175}
2176
2177impl FinalEmbeddedInputResponse {
2178 #[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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2248#[serde(deny_unknown_fields)]
2249pub struct FinalEmbeddedRootsListResult {
2250 pub roots: Vec<Root>,
2252}
2253
2254#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2256#[serde(deny_unknown_fields)]
2257pub struct FinalEmbeddedElicitationResult {
2258 pub action: ElicitAction,
2260 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
2280pub struct FinalListToolsResult {
2281 pub tools: Vec<crate::types::FinalTool>,
2283 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
2285 pub next_cursor: Option<String>,
2286 #[serde(rename = "ttlMs")]
2288 pub ttl_ms: crate::result::CacheTtl,
2289 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
2300pub struct FinalCallToolResult {
2301 pub content: Vec<ContentBlock>,
2303 #[serde(
2305 rename = "isError",
2306 default,
2307 skip_serializing_if = "std::ops::Not::not"
2308 )]
2309 pub is_error: bool,
2310 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
2347pub struct FinalListResourcesResult {
2348 pub resources: Vec<crate::types::FinalResource>,
2350 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
2352 pub next_cursor: Option<String>,
2353 #[serde(rename = "ttlMs")]
2355 pub ttl_ms: crate::result::CacheTtl,
2356 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
2367pub struct FinalListResourceTemplatesResult {
2368 #[serde(rename = "resourceTemplates")]
2370 pub resource_templates: Vec<crate::types::FinalResourceTemplate>,
2371 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
2373 pub next_cursor: Option<String>,
2374 #[serde(rename = "ttlMs")]
2376 pub ttl_ms: crate::result::CacheTtl,
2377 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
2388pub struct FinalReadResourceResult {
2389 pub contents: Vec<EmbeddedResourceContents>,
2391 #[serde(rename = "ttlMs")]
2393 pub ttl_ms: crate::result::CacheTtl,
2394 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
2405pub struct FinalListPromptsResult {
2406 pub prompts: Vec<crate::types::FinalPrompt>,
2408 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
2410 pub next_cursor: Option<String>,
2411 #[serde(rename = "ttlMs")]
2413 pub ttl_ms: crate::result::CacheTtl,
2414 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
2451pub struct FinalPromptMessage {
2452 pub role: crate::types::Role,
2454 pub content: ContentBlock,
2456}
2457
2458#[derive(Debug, Clone, Serialize, Deserialize)]
2460pub struct FinalGetPromptResult {
2461 #[serde(skip_serializing_if = "Option::is_none")]
2463 pub description: Option<String>,
2464 pub messages: Vec<FinalPromptMessage>,
2466}
2467
2468#[derive(Debug, Clone, PartialEq, Eq)]
2474pub enum FinalNotificationError {
2475 InvalidEnvelope { method: String },
2477 RequestIdPresent { method: String },
2479 UnsupportedMethod { method: String },
2481 WrongEnvelope { method: String },
2483 WrongDirection {
2485 method: String,
2487 sender: Final2026Peer,
2489 },
2490 InvalidParams { method: &'static str },
2492 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#[derive(Debug, Clone)]
2540pub enum ClientNotification {
2541 Cancelled(FinalCancelledNotificationParams),
2543}
2544
2545impl ClientNotification {
2546 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 #[must_use]
2562 pub const fn method(&self) -> &'static str {
2563 match self {
2564 Self::Cancelled(_) => NOTIFICATIONS_CANCELLED,
2565 }
2566 }
2567
2568 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#[derive(Debug, Clone)]
2581pub enum ServerNotification {
2582 Cancelled(FinalCancelledNotificationParams),
2584 Progress(FinalProgressNotificationParams),
2586 Message(FinalLogMessageParams),
2588 ResourceUpdated(FinalResourceUpdatedNotificationParams),
2590 ResourcesListChanged(Option<FinalEmptyNotificationParams>),
2592 ToolsListChanged(Option<FinalEmptyNotificationParams>),
2594 PromptsListChanged(Option<FinalEmptyNotificationParams>),
2596 SubscriptionsAcknowledged(FinalSubscriptionsAcknowledgedNotificationParams),
2598}
2599
2600impl ServerNotification {
2601 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 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 #[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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
2850pub struct CompletionValues {
2851 #[serde(
2853 serialize_with = "serialize_completion_values",
2854 deserialize_with = "deserialize_completion_values"
2855 )]
2856 pub values: Vec<String>,
2857 #[serde(default, skip_serializing_if = "Option::is_none")]
2859 pub total: Option<i64>,
2860 #[serde(rename = "hasMore", default, skip_serializing_if = "Option::is_none")]
2862 pub has_more: Option<bool>,
2863}
2864
2865#[derive(Debug, Clone, Serialize, Deserialize)]
2870pub struct FinalCompletionValues {
2871 #[serde(
2873 serialize_with = "serialize_final_completion_values",
2874 deserialize_with = "deserialize_final_completion_values"
2875 )]
2876 pub values: Vec<String>,
2877 #[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2897pub enum FinalCompletionPeerDiagnostic {
2898 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
2941pub const MAX_COMPLETION_VALUES: usize = 100;
2943pub const MAX_FINAL_COMPLETION_VALUE_BYTES: usize = 16 * 1024;
2945pub const MAX_FINAL_COMPLETION_VALUES_BYTES: usize = 256 * 1024;
2947
2948impl FinalCompletionValues {
2949 #[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 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#[derive(Debug, Clone, Serialize, Deserialize)]
3055pub struct LegacyCompletionResult {
3056 pub completion: CompletionValues,
3058 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
3060 pub meta: Option<LegacyOpaqueMetadata>,
3061}
3062
3063#[derive(Debug, Clone, Default, PartialEq)]
3069pub struct LegacyOpaqueMetadata {
3070 entries: Vec<(String, Value)>,
3071}
3072
3073impl LegacyOpaqueMetadata {
3074 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
3138pub struct FinalCompletionResult {
3139 pub completion: FinalCompletionValues,
3141}
3142
3143#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3148#[serde(deny_unknown_fields)]
3149pub struct FinalSubscriptionsListenResult {}
3150
3151#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3153#[serde(deny_unknown_fields)]
3154pub struct FinalEmptyResult {}
3155
3156#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3158#[serde(deny_unknown_fields)]
3159pub struct LegacyEmptyResult {}
3160
3161#[derive(Debug, Clone)]
3164pub enum LegacyCoreRequest {
3165 Initialize(InitializeParams),
3167 Completion(LegacyCompletionParams),
3169 SamplingCreateMessage(CreateMessageParams),
3171 ToolsList(ListToolsParams),
3173 ToolsCall(CallToolParams),
3175 ResourcesList(ListResourcesParams),
3177 ResourceTemplatesList(ListResourceTemplatesParams),
3179 ResourcesRead(ReadResourceParams),
3181 ResourcesSubscribe(SubscribeResourceParams),
3183 ResourcesUnsubscribe(UnsubscribeResourceParams),
3185 PromptsList(ListPromptsParams),
3187 PromptsGet(GetPromptParams),
3189 SetLogLevel(SetLogLevelParams),
3191 Ping,
3193}
3194
3195#[derive(Debug, Clone)]
3197pub enum FinalCoreRequest {
3198 Discover(FinalEmptyParams),
3200 Completion(FinalCompletionParams),
3202 ToolsList(FinalListParams),
3204 ToolsCall(FinalCallToolParams),
3206 ResourcesList(FinalListParams),
3208 ResourceTemplatesList(FinalListParams),
3210 ResourcesRead(FinalReadResourceParams),
3212 PromptsList(FinalListParams),
3214 PromptsGet(FinalGetPromptParams),
3216 SubscriptionsListen(FinalSubscriptionsListenParams),
3218}
3219
3220#[derive(Debug, Clone)]
3222pub enum CoreRequest {
3223 Legacy(LegacyCoreRequest),
3225 Final(FinalCoreRequest),
3227}
3228
3229#[derive(Debug, Clone)]
3231pub enum LegacyCoreResult {
3232 Initialize(InitializeResult),
3234 Completion(LegacyCompletionResult),
3236 SamplingCreateMessage(CreateMessageResult),
3238 ToolsList(ListToolsResult),
3240 ToolsCall(CallToolResult),
3242 ResourcesList(ListResourcesResult),
3244 ResourceTemplatesList(ListResourceTemplatesResult),
3246 ResourcesRead(ReadResourceResult),
3248 ResourcesSubscribe(LegacyEmptyResult),
3250 ResourcesUnsubscribe(LegacyEmptyResult),
3252 PromptsList(ListPromptsResult),
3254 PromptsGet(GetPromptResult),
3256 SetLogLevel(LegacyEmptyResult),
3258 Ping(LegacyEmptyResult),
3260}
3261
3262#[derive(Debug, Clone)]
3269pub enum FinalCoreResult {
3270 Discover(crate::server_discovery::ServerDiscoverResult),
3272 Completion {
3274 result: CompleteResult<FinalCompletionResult>,
3275 diagnostic: Option<ResultPeerDiagnostic>,
3276 },
3277 ToolsList {
3279 result: CompleteResult<FinalListToolsResult>,
3280 diagnostic: Option<ResultPeerDiagnostic>,
3281 },
3282 ToolsCall {
3284 result: CompleteResult<FinalCallToolResult>,
3285 diagnostic: Option<ResultPeerDiagnostic>,
3286 },
3287 #[cfg(feature = "tasks")]
3289 ToolsCallTask {
3290 result: crate::tasks_extension::CreateTaskResult,
3291 },
3292 ToolsCallInputRequired {
3294 result: InputRequiredResult,
3295 diagnostic: Option<ResultPeerDiagnostic>,
3296 },
3297 ResourcesList {
3299 result: CompleteResult<FinalListResourcesResult>,
3300 diagnostic: Option<ResultPeerDiagnostic>,
3301 },
3302 ResourceTemplatesList {
3304 result: CompleteResult<FinalListResourceTemplatesResult>,
3305 diagnostic: Option<ResultPeerDiagnostic>,
3306 },
3307 ResourcesRead {
3309 result: CompleteResult<FinalReadResourceResult>,
3310 diagnostic: Option<ResultPeerDiagnostic>,
3311 },
3312 ResourcesReadInputRequired {
3314 result: InputRequiredResult,
3315 diagnostic: Option<ResultPeerDiagnostic>,
3316 },
3317 PromptsList {
3319 result: CompleteResult<FinalListPromptsResult>,
3320 diagnostic: Option<ResultPeerDiagnostic>,
3321 },
3322 PromptsGet {
3324 result: CompleteResult<FinalGetPromptResult>,
3325 diagnostic: Option<ResultPeerDiagnostic>,
3326 },
3327 PromptsGetInputRequired {
3329 result: InputRequiredResult,
3330 diagnostic: Option<ResultPeerDiagnostic>,
3331 },
3332 SubscriptionsListen {
3334 result: CompleteResult<FinalSubscriptionsListenResult>,
3335 subscription_id: RequestId,
3337 diagnostic: Option<ResultPeerDiagnostic>,
3338 },
3339}
3340
3341#[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#[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 Legacy(LegacyCoreResult),
3402 Final(FinalCoreResult),
3404}
3405
3406#[derive(Debug, Clone, PartialEq, Eq)]
3408pub enum CoreDispatchError {
3409 FeatureUnavailable {
3411 feature: &'static str,
3413 },
3414 UnsupportedMethod { era: ProtocolEra, method: String },
3416 InvalidParams {
3418 era: ProtocolEra,
3419 method: &'static str,
3420 },
3421 InvalidFinalMetadata { method: &'static str },
3423 CrossEraRequestMetadata { method: &'static str },
3425 CrossEraResultType { method: &'static str },
3427 CrossEraResultMetadata { method: &'static str },
3429 InvalidResult {
3431 era: ProtocolEra,
3432 method: &'static str,
3433 },
3434 UnexpectedFinalResultType { method: &'static str },
3436 SubscriptionIdMismatch,
3438 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 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 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 #[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 #[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 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 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 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 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 #[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 #[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) => ¶ms.meta,
3991 Self::Completion(params) => ¶ms.meta,
3992 Self::ToolsList(params)
3993 | Self::ResourcesList(params)
3994 | Self::ResourceTemplatesList(params)
3995 | Self::PromptsList(params) => ¶ms.meta,
3996 Self::ToolsCall(params) => ¶ms.meta,
3997 Self::ResourcesRead(params) => ¶ms.meta,
3998 Self::PromptsGet(params) => ¶ms.meta,
3999 Self::SubscriptionsListen(params) => ¶ms.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 #[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 #[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 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 #[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 #[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 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
4772pub struct InitializeParams {
4773 #[serde(rename = "protocolVersion")]
4775 pub protocol_version: String,
4776 pub capabilities: ClientCapabilities,
4778 #[serde(rename = "clientInfo")]
4780 pub client_info: ClientInfo,
4781}
4782
4783#[derive(Debug, Clone, Serialize, Deserialize)]
4785pub struct InitializeResult {
4786 #[serde(rename = "protocolVersion")]
4788 pub protocol_version: String,
4789 pub capabilities: ServerCapabilities,
4791 #[serde(rename = "serverInfo")]
4793 pub server_info: ServerInfo,
4794 #[serde(skip_serializing_if = "Option::is_none")]
4796 pub instructions: Option<String>,
4797}
4798
4799#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4805pub struct ListToolsParams {
4806 #[serde(skip_serializing_if = "Option::is_none")]
4808 pub cursor: Option<String>,
4809 #[serde(
4811 rename = "includeTags",
4812 default,
4813 skip_serializing_if = "Option::is_none"
4814 )]
4815 pub include_tags: Option<Vec<String>>,
4816 #[serde(
4818 rename = "excludeTags",
4819 default,
4820 skip_serializing_if = "Option::is_none"
4821 )]
4822 pub exclude_tags: Option<Vec<String>>,
4823}
4824
4825#[derive(Debug, Clone, Serialize, Deserialize)]
4827pub struct ListToolsResult {
4828 pub tools: Vec<Tool>,
4830 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
4832 pub next_cursor: Option<String>,
4833}
4834
4835#[derive(Debug, Clone, Serialize, Deserialize)]
4837pub struct CallToolParams {
4838 pub name: String,
4840 #[serde(default, skip_serializing_if = "Option::is_none")]
4842 pub arguments: Option<serde_json::Value>,
4843 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
4845 pub meta: Option<RequestMeta>,
4846}
4847
4848#[derive(Debug, Clone, Serialize, Deserialize)]
4850pub struct CallToolResult {
4851 pub content: Vec<LegacyContent>,
4853 #[serde(
4855 rename = "isError",
4856 default,
4857 skip_serializing_if = "std::ops::Not::not"
4858 )]
4859 pub is_error: bool,
4860 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
4862 pub meta: Option<LegacyMetadata>,
4863 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
4865 pub additional: BTreeMap<String, Value>,
4866}
4867
4868#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4874pub struct ListResourcesParams {
4875 #[serde(skip_serializing_if = "Option::is_none")]
4877 pub cursor: Option<String>,
4878 #[serde(
4880 rename = "includeTags",
4881 default,
4882 skip_serializing_if = "Option::is_none"
4883 )]
4884 pub include_tags: Option<Vec<String>>,
4885 #[serde(
4887 rename = "excludeTags",
4888 default,
4889 skip_serializing_if = "Option::is_none"
4890 )]
4891 pub exclude_tags: Option<Vec<String>>,
4892}
4893
4894#[derive(Debug, Clone, Serialize, Deserialize)]
4896pub struct ListResourcesResult {
4897 pub resources: Vec<Resource>,
4899 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
4901 pub next_cursor: Option<String>,
4902}
4903
4904#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4906pub struct ListResourceTemplatesParams {
4907 #[serde(skip_serializing_if = "Option::is_none")]
4909 pub cursor: Option<String>,
4910 #[serde(
4912 rename = "includeTags",
4913 default,
4914 skip_serializing_if = "Option::is_none"
4915 )]
4916 pub include_tags: Option<Vec<String>>,
4917 #[serde(
4919 rename = "excludeTags",
4920 default,
4921 skip_serializing_if = "Option::is_none"
4922 )]
4923 pub exclude_tags: Option<Vec<String>>,
4924}
4925
4926#[derive(Debug, Clone, Serialize, Deserialize)]
4928pub struct ListResourceTemplatesResult {
4929 #[serde(rename = "resourceTemplates")]
4931 pub resource_templates: Vec<ResourceTemplate>,
4932 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
4934 pub next_cursor: Option<String>,
4935}
4936
4937#[derive(Debug, Clone, Serialize, Deserialize)]
4939pub struct ReadResourceParams {
4940 pub uri: String,
4942 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
4944 pub meta: Option<RequestMeta>,
4945}
4946
4947#[derive(Debug, Clone, Serialize, Deserialize)]
4949pub struct ReadResourceResult {
4950 #[serde(deserialize_with = "deserialize_legacy_resource_contents")]
4952 pub contents: Vec<LegacyResourceContent>,
4953 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
4955 pub meta: Option<LegacyMetadata>,
4956 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
4958 pub additional: BTreeMap<String, Value>,
4959}
4960
4961fn 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#[derive(Debug, Clone, Serialize, Deserialize)]
4996pub struct SubscribeResourceParams {
4997 pub uri: String,
4999}
5000
5001#[derive(Debug, Clone, Serialize, Deserialize)]
5003pub struct UnsubscribeResourceParams {
5004 pub uri: String,
5006}
5007
5008#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5014pub struct ListPromptsParams {
5015 #[serde(skip_serializing_if = "Option::is_none")]
5017 pub cursor: Option<String>,
5018 #[serde(
5020 rename = "includeTags",
5021 default,
5022 skip_serializing_if = "Option::is_none"
5023 )]
5024 pub include_tags: Option<Vec<String>>,
5025 #[serde(
5027 rename = "excludeTags",
5028 default,
5029 skip_serializing_if = "Option::is_none"
5030 )]
5031 pub exclude_tags: Option<Vec<String>>,
5032}
5033
5034#[derive(Debug, Clone, Serialize, Deserialize)]
5036pub struct ListPromptsResult {
5037 pub prompts: Vec<Prompt>,
5039 #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
5041 pub next_cursor: Option<String>,
5042}
5043
5044#[derive(Debug, Clone, Serialize, Deserialize)]
5046pub struct GetPromptParams {
5047 pub name: String,
5049 #[serde(default, skip_serializing_if = "Option::is_none")]
5051 pub arguments: Option<std::collections::HashMap<String, String>>,
5052 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
5054 pub meta: Option<RequestMeta>,
5055}
5056
5057#[derive(Debug, Clone, Serialize, Deserialize)]
5059pub struct GetPromptResult {
5060 #[serde(skip_serializing_if = "Option::is_none")]
5062 pub description: Option<String>,
5063 pub messages: Vec<LegacyPromptMessage>,
5065 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
5067 pub meta: Option<LegacyMetadata>,
5068 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
5070 pub additional: BTreeMap<String, Value>,
5071}
5072
5073#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5079#[serde(rename_all = "lowercase")]
5080pub enum LogLevel {
5081 Emergency,
5083 Alert,
5085 Critical,
5087 Debug,
5089 Info,
5091 Notice,
5093 Warning,
5095 Error,
5097}
5098
5099#[derive(Debug, Clone, Serialize, Deserialize)]
5101pub struct SetLogLevelParams {
5102 pub level: LogLevel,
5104}
5105
5106pub const MAX_CANCELLATION_REASON_BYTES: usize = 4 * 1024;
5115
5116#[derive(Debug, Clone, Serialize, Deserialize)]
5120#[serde(deny_unknown_fields)]
5121pub struct CancelledParams {
5122 #[serde(rename = "requestId")]
5124 pub request_id: RequestId,
5125 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5143pub enum CancellationSender {
5144 Client,
5146 Server,
5148}
5149
5150#[derive(Debug, Clone)]
5162pub enum CancellationWireMessage {
5163 Legacy2024 {
5165 sender: CancellationSender,
5167 params: CancelledParams,
5169 },
5170 Modern2026 {
5175 sender: CancellationSender,
5177 params: FinalCancelledNotificationParams,
5179 },
5180}
5181
5182impl CancellationWireMessage {
5183 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(¶ms)
5217 {
5218 return Err(CancellationWireCodecError::InvalidParameters {
5219 era: ProtocolEra::Modern2026,
5220 });
5221 }
5222 Ok(Self::Modern2026 { sender, params })
5223 }
5224 }
5225 }
5226
5227 #[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 #[must_use]
5238 pub const fn sender(&self) -> CancellationSender {
5239 match self {
5240 Self::Legacy2024 { sender, .. } | Self::Modern2026 { sender, .. } => *sender,
5241 }
5242 }
5243
5244 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#[derive(Debug, Clone, PartialEq, Eq)]
5285pub enum CancellationWireCodecError {
5286 InvalidEnvelope {
5288 era: ProtocolEra,
5290 },
5291 RequestIdPresent {
5293 era: ProtocolEra,
5295 },
5296 UnexpectedMethod {
5298 era: ProtocolEra,
5300 method: String,
5302 },
5303 MissingParameters {
5305 era: ProtocolEra,
5307 },
5308 InvalidParameters {
5310 era: ProtocolEra,
5312 },
5313 EncodeFailure {
5315 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#[derive(Debug, Clone, Serialize, Deserialize)]
5429pub struct ProgressParams {
5430 #[serde(rename = "progressTo\x6ben")]
5433 pub progress_marker: ProgressMarker,
5434 pub progress: f64,
5436 #[serde(skip_serializing_if = "Option::is_none")]
5438 pub total: Option<f64>,
5439 #[serde(skip_serializing_if = "Option::is_none")]
5441 pub message: Option<String>,
5442}
5443
5444impl ProgressParams {
5445 #[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 #[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 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
5486pub struct ResourceUpdatedNotificationParams {
5487 pub uri: String,
5489}
5490
5491#[derive(Debug, Clone, Serialize, Deserialize)]
5493pub struct LogMessageParams {
5494 pub level: LogLevel,
5496 #[serde(skip_serializing_if = "Option::is_none")]
5498 pub logger: Option<String>,
5499 pub data: serde_json::Value,
5501}
5502
5503use crate::types::{TaskId, TaskResult, TaskStatus};
5508
5509#[derive(Debug, Clone, Serialize, Deserialize)]
5513pub struct TaskStatusNotificationParams {
5514 pub id: TaskId,
5516 pub status: TaskStatus,
5518 #[serde(skip_serializing_if = "Option::is_none")]
5520 pub progress: Option<f64>,
5521 #[serde(skip_serializing_if = "Option::is_none")]
5523 pub message: Option<String>,
5524 #[serde(skip_serializing_if = "Option::is_none")]
5526 pub error: Option<String>,
5527 #[serde(skip_serializing_if = "Option::is_none")]
5529 pub result: Option<TaskResult>,
5530}
5531
5532use crate::types::{ModelPreferences, SamplingContent, SamplingMessage};
5537
5538#[derive(Debug, Clone, Serialize, Deserialize)]
5542pub struct CreateMessageParams {
5543 pub messages: Vec<SamplingMessage>,
5545 #[serde(rename = "maxTo\x6bens")]
5548 pub max_tokens: JsonInteger,
5549 #[serde(rename = "systemPrompt", skip_serializing_if = "Option::is_none")]
5551 pub system_prompt: Option<String>,
5552 #[serde(skip_serializing_if = "Option::is_none")]
5554 pub temperature: Option<f64>,
5555 #[serde(
5557 rename = "stopSequences",
5558 default,
5559 skip_serializing_if = "Vec::is_empty"
5560 )]
5561 pub stop_sequences: Vec<String>,
5562 #[serde(rename = "modelPreferences", skip_serializing_if = "Option::is_none")]
5564 pub model_preferences: Option<ModelPreferences>,
5565 #[serde(rename = "includeContext", skip_serializing_if = "Option::is_none")]
5567 pub include_context: Option<IncludeContext>,
5568 #[serde(default, skip_serializing_if = "Option::is_none")]
5570 pub metadata: Option<serde_json::Map<String, Value>>,
5571 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
5573 pub meta: Option<RequestMeta>,
5574}
5575
5576impl CreateMessageParams {
5577 #[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 #[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 #[must_use]
5602 pub fn with_temperature(mut self, temp: f64) -> Self {
5603 self.temperature = Some(temp);
5604 self
5605 }
5606
5607 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5617#[serde(rename_all = "camelCase")]
5618pub enum IncludeContext {
5619 None,
5621 ThisServer,
5623 AllServers,
5625}
5626
5627#[derive(Debug, Clone, Serialize, Deserialize)]
5631pub struct CreateMessageResult {
5632 pub content: SamplingContent,
5634 pub role: crate::types::Role,
5636 pub model: String,
5638 #[serde(
5640 rename = "stopReason",
5641 default,
5642 skip_serializing_if = "Option::is_none"
5643 )]
5644 pub stop_reason: Option<String>,
5645 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
5647 pub meta: Option<LegacyOpaqueMetadata>,
5648}
5649
5650impl CreateMessageResult {
5651 #[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 #[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 #[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
5680use crate::types::Root;
5685
5686#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5691pub struct ListRootsParams {}
5692
5693#[derive(Debug, Clone, Serialize, Deserialize)]
5697pub struct ListRootsResult {
5698 pub roots: Vec<Root>,
5700}
5701
5702impl ListRootsResult {
5703 #[must_use]
5705 pub fn empty() -> Self {
5706 Self { roots: Vec::new() }
5707 }
5708
5709 #[must_use]
5711 pub fn new(roots: Vec<Root>) -> Self {
5712 Self { roots }
5713 }
5714}
5715
5716#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5721pub struct RootsListChangedNotificationParams {}
5722
5723pub type ElicitRequestedSchema = serde_json::Value;
5732
5733#[derive(Debug, Clone, Serialize, Deserialize)]
5738pub struct ElicitRequestFormParams {
5739 pub mode: ElicitMode,
5741 pub message: String,
5743 #[serde(rename = "requestedSchema")]
5746 pub requested_schema: ElicitRequestedSchema,
5747}
5748
5749impl ElicitRequestFormParams {
5750 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
5766pub struct ElicitRequestUrlParams {
5767 pub mode: ElicitMode,
5769 pub message: String,
5771 pub url: String,
5773 #[serde(rename = "elicitationId")]
5776 pub elicitation_id: String,
5777}
5778
5779impl ElicitRequestUrlParams {
5780 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5798#[serde(rename_all = "lowercase")]
5799pub enum ElicitMode {
5800 Form,
5802 Url,
5804}
5805
5806#[derive(Debug, Clone, Serialize, Deserialize)]
5808#[serde(untagged)]
5809pub enum ElicitRequestParams {
5810 Form(ElicitRequestFormParams),
5812 Url(ElicitRequestUrlParams),
5814}
5815
5816impl ElicitRequestParams {
5817 #[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 #[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 #[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5854#[serde(rename_all = "lowercase")]
5855pub enum ElicitAction {
5856 Accept,
5858 Decline,
5860 Cancel,
5862}
5863
5864#[derive(Debug, Clone, PartialEq, Serialize)]
5873#[serde(untagged)]
5874pub enum ElicitContentValue {
5875 Null,
5877 Bool(bool),
5879 Int(JsonInteger),
5881 Float(f64),
5883 String(String),
5885 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
6034pub struct ElicitResult {
6035 pub action: ElicitAction,
6037 #[serde(skip_serializing_if = "Option::is_none")]
6041 pub content: Option<std::collections::HashMap<String, ElicitContentValue>>,
6042}
6043
6044impl ElicitResult {
6045 #[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 #[must_use]
6056 pub fn accept_url() -> Self {
6057 Self {
6058 action: ElicitAction::Accept,
6059 content: None,
6060 }
6061 }
6062
6063 #[must_use]
6065 pub fn decline() -> Self {
6066 Self {
6067 action: ElicitAction::Decline,
6068 content: None,
6069 }
6070 }
6071
6072 #[must_use]
6074 pub fn cancel() -> Self {
6075 Self {
6076 action: ElicitAction::Cancel,
6077 content: None,
6078 }
6079 }
6080
6081 #[must_use]
6083 pub fn is_accepted(&self) -> bool {
6084 matches!(self.action, ElicitAction::Accept)
6085 }
6086
6087 #[must_use]
6089 pub fn is_declined(&self) -> bool {
6090 matches!(self.action, ElicitAction::Decline)
6091 }
6092
6093 #[must_use]
6095 pub fn is_cancelled(&self) -> bool {
6096 matches!(self.action, ElicitAction::Cancel)
6097 }
6098
6099 #[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 #[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 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
6137pub struct ElicitCompleteNotificationParams {
6138 #[serde(rename = "elicitationId")]
6140 pub elicitation_id: String,
6141}
6142
6143impl ElicitCompleteNotificationParams {
6144 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
6158pub struct ElicitationRequiredErrorData {
6159 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 #[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 #[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(¶ms).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(¶ms)
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(¶ms)
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) = ¬ification;
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(¬ification.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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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 ¶ms.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 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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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(¶ms))
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 #[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(¶ms).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 #[test]
9908 fn list_tools_params_default() {
9909 let params = ListToolsParams::default();
9910 let value = serde_json::to_value(¶ms).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(¶ms).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(¶ms).expect("serialize");
9933 assert_eq!(value["includeTags"], serde_json::json!(["api", "v2"]));
9934 assert_eq!(value["excludeTags"], serde_json::json!(["deprecated"]));
9935 }
9936
9937 #[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(¶ms).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(¶ms).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 #[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 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 #[test]
10107 fn list_resources_params_default() {
10108 let params = ListResourcesParams::default();
10109 let value = serde_json::to_value(¶ms).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(¶ms).expect("serialize");
10121 assert_eq!(value["includeTags"], serde_json::json!(["config"]));
10122 }
10123
10124 #[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(¶ms).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(¶ms).expect("serialize");
10148 assert_eq!(value["uri"], "file://data.csv");
10149 assert_eq!(value["_meta"][PROGRESS_MARKER_KEY], "pt-read");
10150 }
10151
10152 #[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 #[test]
10259 fn list_prompts_params_default() {
10260 let params = ListPromptsParams::default();
10261 let value = serde_json::to_value(¶ms).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(¶ms).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 #[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(¶ms).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(¶ms).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 #[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 #[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(¶ms).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(¶ms).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(¶ms).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 #[test]
10630 fn progress_params_new() {
10631 let params = ProgressParams::new("id-1", 0.5);
10632 let value = serde_json::to_value(¶ms).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(¶ms).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(¶ms).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 let params = ProgressParams::with_total("t", 10.0, 0.0);
10662 assert_eq!(params.fraction(), Some(0.0));
10663
10664 let params = ProgressParams::new("t", 0.5);
10666 assert_eq!(params.fraction(), None);
10667 }
10668
10669 #[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 #[test]
10719 fn list_resource_templates_params_serialization() {
10720 let params = ListResourceTemplatesParams::default();
10721 let value = serde_json::to_value(¶ms).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(¶ms).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(¶ms).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(¶ms).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 #[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(¶ms).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(¶ms).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 #[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(¶ms).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(¶ms).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(¶ms).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 #[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 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 let cap = RootsCapability { list_changed: true };
11272 let json = serde_json::to_value(&cap).expect("serialize");
11273 assert_eq!(json["listChanged"], true);
11274
11275 let cap = RootsCapability::default();
11277 let json = serde_json::to_value(&cap).expect("serialize");
11278 assert!(json.get("listChanged").is_none());
11279 }
11280
11281 #[test]
11286 fn tool_version_serialization() {
11287 use crate::types::Tool;
11288
11289 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 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 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 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 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 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 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 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 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 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 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 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 #[test]
11449 fn tool_tags_serialization() {
11450 use crate::types::Tool;
11451
11452 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[test]
11641 fn tool_annotations_serialization() {
11642 use crate::types::{Tool, ToolAnnotations};
11643
11644 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 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 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 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 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 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 let empty = ToolAnnotations::new();
11753 assert!(empty.is_empty());
11754 }
11755
11756 #[test]
11761 fn tool_output_schema_serialization() {
11762 use crate::types::Tool;
11763
11764 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 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 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 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}