#[cfg(feature = "server")]
pub(crate) mod state;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::types::elicitation::ElicitRequestParams;
use crate::types::root::ListRootsRequestParams;
use crate::types::sampling::CreateMessageRequestParams;
use crate::types::{IntoResponse, RequestId, Response};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputRequiredResult {
#[serde(rename = "resultType")]
pub result_type: InputRequiredTag,
#[serde(rename = "inputRequests", skip_serializing_if = "Option::is_none")]
pub input_requests: Option<InputRequests>,
#[serde(rename = "requestState", skip_serializing_if = "Option::is_none")]
pub request_state: Option<String>,
}
pub type InputRequests = HashMap<String, InputRequest>;
pub type InputResponses = HashMap<String, serde_json::Value>;
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "method", content = "params")]
pub enum InputRequest {
#[serde(rename = "elicitation/create")]
Elicitation(ElicitRequestParams),
#[serde(rename = "sampling/createMessage")]
#[deprecated(
note = "sampling is deprecated in MCP 2026-07-28; it returns as an MRTR input-request kind only for migration"
)]
Sampling(Box<CreateMessageRequestParams>),
#[serde(rename = "roots/list")]
#[deprecated(
note = "roots are deprecated in MCP 2026-07-28; they return as an MRTR input-request kind only for migration"
)]
Roots(Box<ListRootsRequestParams>),
}
impl<'de> Deserialize<'de> for InputRequest {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use crate::types::{elicitation, root, sampling};
use serde::de::Error as DeError;
#[derive(Deserialize)]
struct Envelope {
method: String,
#[serde(default)]
params: Option<serde_json::Value>,
}
let envelope = Envelope::deserialize(deserializer)?;
let params = envelope
.params
.filter(|params| !params.is_null())
.unwrap_or_else(|| serde_json::Value::Object(Default::default()));
fn parse<T: serde::de::DeserializeOwned, E: DeError>(
value: serde_json::Value,
) -> Result<T, E> {
serde_json::from_value(value).map_err(E::custom)
}
#[allow(deprecated)]
match envelope.method.as_str() {
elicitation::commands::CREATE => parse(params).map(Self::Elicitation),
sampling::commands::CREATE => parse(params).map(Self::Sampling),
root::commands::LIST => parse(params).map(Self::Roots),
unknown => Err(D::Error::custom(format!(
"unknown MRTR input request method `{unknown}`"
))),
}
}
}
impl InputRequest {
pub fn method(&self) -> &'static str {
#[allow(deprecated)]
match self {
Self::Elicitation(_) => crate::types::elicitation::commands::CREATE,
Self::Sampling(_) => crate::types::sampling::commands::CREATE,
Self::Roots(_) => crate::types::root::commands::LIST,
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct ClientMrtrCapabilities {
#[serde(
default,
deserialize_with = "de_elicitation",
skip_serializing_if = "Option::is_none"
)]
pub elicitation: Option<ElicitationModes>,
#[serde(
default,
deserialize_with = "de_declared",
serialize_with = "ser_declared",
skip_serializing_if = "std::ops::Not::not"
)]
pub sampling: bool,
#[serde(
default,
deserialize_with = "de_declared",
serialize_with = "ser_declared",
skip_serializing_if = "std::ops::Not::not"
)]
pub roots: bool,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct ElicitationModes {
#[serde(
default,
deserialize_with = "de_declared",
serialize_with = "ser_declared",
skip_serializing_if = "std::ops::Not::not"
)]
pub form: bool,
#[serde(
default,
deserialize_with = "de_declared",
serialize_with = "ser_declared",
skip_serializing_if = "std::ops::Not::not"
)]
pub url: bool,
}
impl ElicitationModes {
pub fn unconstrained(&self) -> bool {
!self.form && !self.url
}
pub fn allows(&self, params: &crate::types::elicitation::ElicitRequestParams) -> bool {
use crate::types::elicitation::ElicitRequestParams;
self.unconstrained()
|| match params {
ElicitRequestParams::Form(_) => self.form,
ElicitRequestParams::Url(_) => self.url,
}
}
}
#[cfg(feature = "server")]
impl ElicitationModes {
fn requiring(params: &crate::types::elicitation::ElicitRequestParams) -> Self {
use crate::types::elicitation::ElicitRequestParams;
match params {
ElicitRequestParams::Form(_) => Self {
form: true,
url: false,
},
ElicitRequestParams::Url(_) => Self {
form: false,
url: true,
},
}
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum ElicitationDeclaration {
Modes(ElicitationModes),
Flag(bool),
}
fn de_elicitation<'de, D>(deserializer: D) -> Result<Option<ElicitationModes>, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(
match Option::<ElicitationDeclaration>::deserialize(deserializer)? {
Some(ElicitationDeclaration::Modes(modes)) => Some(modes),
Some(ElicitationDeclaration::Flag(true)) => Some(ElicitationModes::default()),
Some(ElicitationDeclaration::Flag(false)) | None => None,
},
)
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Declaration {
Object(AnyObject),
Flag(bool),
}
#[derive(Deserialize)]
struct AnyObject {}
fn de_declared<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(match Option::<Declaration>::deserialize(deserializer)? {
Some(Declaration::Object(_)) => true,
Some(Declaration::Flag(flag)) => flag,
None => false,
})
}
fn ser_declared<S>(_declared: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
serializer.serialize_map(Some(0))?.end()
}
#[cfg(feature = "server")]
impl ClientMrtrCapabilities {
pub(crate) fn allows(&self, request: &InputRequest) -> bool {
#[allow(deprecated)]
match request {
InputRequest::Elicitation(params) => {
self.elicitation.is_some_and(|modes| modes.allows(params))
}
InputRequest::Sampling(_) => self.sampling,
InputRequest::Roots(_) => self.roots,
}
}
pub(crate) fn requiring(&self, request: &InputRequest) -> Self {
#[allow(deprecated)]
Self {
elicitation: match request {
InputRequest::Elicitation(params) => Some(ElicitationModes::requiring(params)),
_ => None,
},
sampling: matches!(request, InputRequest::Sampling(_)),
roots: matches!(request, InputRequest::Roots(_)),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum InputRequiredTag {
#[serde(rename = "input_required")]
InputRequired,
}
#[cfg(feature = "server")]
impl InputRequiredResult {
pub(crate) fn new(
requests: impl IntoIterator<Item = (String, InputRequest)>,
state: String,
) -> Self {
Self {
result_type: InputRequiredTag::InputRequired,
input_requests: Some(requests.into_iter().collect()),
request_state: Some(state),
}
}
}
impl IntoResponse for InputRequiredResult {
#[inline]
fn into_response(self, req_id: RequestId) -> Response {
match serde_json::to_value(self) {
Ok(v) => Response::success(req_id, v),
Err(err) => Response::error(req_id, err.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn client_capabilities_read_the_spec_object_shape() {
let caps: ClientMrtrCapabilities = serde_json::from_value(serde_json::json!({
"elicitation": { "form": {}, "url": {} },
"sampling": { "context": {}, "tools": {} },
"roots": {}
}))
.expect("object-shaped capabilities must parse");
let modes = caps.elicitation.expect("elicitation declared");
assert!(modes.form && modes.url, "both modes were named");
assert!(caps.sampling);
assert!(caps.roots);
}
#[test]
fn client_capabilities_read_the_legacy_boolean_shape() {
let caps: ClientMrtrCapabilities = serde_json::from_value(serde_json::json!({
"elicitation": true,
"sampling": false
}))
.expect("boolean-shaped capabilities must still parse");
let modes = caps.elicitation.expect("elicitation declared");
assert!(
modes.unconstrained(),
"a bare boolean names no mode, and so rules none out"
);
assert!(!caps.sampling);
assert!(!caps.roots);
}
#[test]
fn absent_and_null_client_capabilities_declare_nothing() {
let empty: ClientMrtrCapabilities =
serde_json::from_value(serde_json::json!({})).expect("empty object must parse");
assert!(empty.elicitation.is_none() && !empty.sampling && !empty.roots);
let nulls: ClientMrtrCapabilities =
serde_json::from_value(serde_json::json!({ "elicitation": null, "roots": null }))
.expect("null capabilities must parse");
assert!(nulls.elicitation.is_none() && !nulls.roots);
}
#[test]
fn client_capabilities_write_the_spec_object_shape() {
let caps = ClientMrtrCapabilities {
elicitation: Some(ElicitationModes::default()),
sampling: false,
roots: true,
};
assert_eq!(
serde_json::to_value(caps).expect("serialize"),
serde_json::json!({ "elicitation": {}, "roots": {} }),
"a capability declared without modes writes the bare object"
);
let formal = ClientMrtrCapabilities {
elicitation: Some(ElicitationModes {
form: true,
url: false,
}),
sampling: false,
roots: false,
};
assert_eq!(
serde_json::to_value(formal).expect("serialize"),
serde_json::json!({ "elicitation": { "form": {} } }),
"a named mode survives the round trip to the wire"
);
}
#[test]
fn client_capabilities_roundtrip() {
let caps = ClientMrtrCapabilities {
elicitation: Some(ElicitationModes {
form: true,
url: true,
}),
sampling: true,
roots: false,
};
let back: ClientMrtrCapabilities =
serde_json::from_value(serde_json::to_value(caps).expect("serialize"))
.expect("deserialize");
let modes = back.elicitation.expect("elicitation declared");
assert!(modes.form && modes.url);
assert!(back.sampling);
assert!(!back.roots);
}
#[test]
fn input_required_result_roundtrips_with_tag_and_envelope() {
let json = r#"{
"resultType": "input_required",
"inputRequests": {
"ask_name": {
"method": "elicitation/create",
"params": {
"message": "Your name?",
"requestedSchema": { "type": "object", "properties": {}, "required": null }
}
}
},
"requestState": "abc.def"
}"#;
let parsed: InputRequiredResult = serde_json::from_str(json).unwrap();
assert_eq!(parsed.request_state.as_deref(), Some("abc.def"));
assert!(
parsed
.input_requests
.as_ref()
.expect("requests")
.contains_key("ask_name")
);
let back = serde_json::to_value(&parsed).unwrap();
assert_eq!(back["resultType"], serde_json::json!("input_required"));
assert_eq!(
back["inputRequests"]["ask_name"]["method"],
serde_json::json!("elicitation/create")
);
let params = &back["inputRequests"]["ask_name"]["params"];
assert_eq!(params["message"], serde_json::json!("Your name?"));
assert!(params.get("Form").is_none(), "got: {params}");
assert!(params.get("mode").is_none(), "got: {params}");
}
#[test]
fn every_input_kind_roundtrips_as_a_method_params_envelope() {
#[allow(deprecated)]
let cases = [
(
InputRequest::Elicitation(ElicitRequestParams::form("Your name?").into()),
"elicitation/create",
),
(
InputRequest::Sampling(Box::default()),
"sampling/createMessage",
),
(InputRequest::Roots(Box::default()), "roots/list"),
];
for (request, method) in cases {
assert_eq!(request.method(), method);
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json["method"], serde_json::json!(method));
assert!(
json.get("params").is_some(),
"the envelope must carry `params` for {method}: {json}"
);
let back: InputRequest = serde_json::from_value(json).unwrap();
assert_eq!(back.method(), method, "kind must survive the round trip");
}
}
#[test]
fn a_roots_envelope_decodes_with_or_without_params() {
for json in [
serde_json::json!({ "method": "roots/list", "params": {} }),
serde_json::json!({ "method": "roots/list" }),
serde_json::json!({ "method": "roots/list", "params": null }),
] {
let parsed: InputRequest = serde_json::from_value(json.clone())
.unwrap_or_else(|err| panic!("{json} must decode: {err}"));
assert_eq!(parsed.method(), "roots/list");
}
}
#[test]
fn a_paramless_envelope_still_fails_for_kinds_that_need_params() {
for method in ["elicitation/create", "sampling/createMessage"] {
let json = serde_json::json!({ "method": method });
assert!(
serde_json::from_value::<InputRequest>(json).is_err(),
"{method} must not decode without params"
);
}
}
#[test]
fn an_unknown_input_kind_is_rejected_by_name() {
let json = serde_json::json!({ "method": "sorcery/summon", "params": {} });
let err = serde_json::from_value::<InputRequest>(json).unwrap_err();
assert!(
err.to_string().contains("sorcery/summon"),
"the error must name the unknown method, got: {err}"
);
}
#[cfg(feature = "server")]
#[test]
fn capabilities_gate_each_kind_independently() {
#[allow(deprecated)]
let sampling = InputRequest::Sampling(Box::default());
let elicitation = InputRequest::Elicitation(ElicitRequestParams::form("m").into());
let only_elicitation = ClientMrtrCapabilities {
elicitation: Some(ElicitationModes::default()),
..Default::default()
};
assert!(only_elicitation.allows(&elicitation));
assert!(
!only_elicitation.allows(&sampling),
"a client that only does elicitation must not be asked to sample"
);
let all = ClientMrtrCapabilities {
elicitation: Some(ElicitationModes::default()),
sampling: true,
roots: true,
};
assert!(all.allows(&sampling));
}
#[cfg(feature = "server")]
#[test]
fn a_named_elicitation_mode_is_the_only_one_allowed() {
let form = InputRequest::Elicitation(ElicitRequestParams::form("m").into());
let url = InputRequest::Elicitation(ElicitRequestParams::url("m", "https://e.io").into());
let forms_only = ClientMrtrCapabilities {
elicitation: Some(ElicitationModes {
form: true,
url: false,
}),
..Default::default()
};
assert!(forms_only.allows(&form));
assert!(
!forms_only.allows(&url),
"a client that named only `form` cannot answer a URL request"
);
let unstated = ClientMrtrCapabilities {
elicitation: Some(ElicitationModes::default()),
..Default::default()
};
assert!(unstated.allows(&form) && unstated.allows(&url));
let required = forms_only.requiring(&url).elicitation.expect("named");
assert!(required.url && !required.form);
}
#[test]
fn capabilities_decode_from_an_older_peer() {
let caps: ClientMrtrCapabilities =
serde_json::from_value(serde_json::json!({ "elicitation": true })).unwrap();
assert!(caps.elicitation.is_some());
assert!(!caps.sampling);
assert!(!caps.roots);
let json = serde_json::to_value(ClientMrtrCapabilities::default()).unwrap();
assert_eq!(json, serde_json::json!({}));
}
}