pub enum LowerError {
FeatureUnavailable {
feature: Feature,
negotiated: ProtocolVersion,
},
NoRuleForFeature {
feature: Feature,
},
FieldNotRepresentable {
field: String,
expected: &'static str,
detail: String,
target: ProtocolVersion,
},
}Expand description
Why a value could not be expressed at the target revision.
Variants§
The value needs a feature the negotiated revision cannot express, and no policy authorises a downgrade.
NoRuleForFeature
The policy names a feature it can degrade, but names no rule for it.
Distinct from LowerError::FeatureUnavailable because the fix is
different: the caller asked for a downgrade and did not say how. Failing
here rather than picking a rule is what keeps a policy from quietly
choosing the destructive option.
FieldNotRepresentable
A field the target revision types as a struct does not fit it.
This is the 2026-07-28 meta case, and it is deliberately not a
LowerError::FeatureUnavailable. That variant says a revision cannot
express a shape at all, so the fix is to negotiate something else or
authorise a downgrade. This one says a value does not fit a shape: the
same value lowers cleanly at 2025-06-18, where meta is an untyped
serde_json::Value, and fails at every revision that types it. Reporting
it as a missing feature would put a false claim in the error — that the
field is unavailable at the older revision — and would refuse messages
that lower perfectly well.
§Why no DegradeRule applies
Every other variant here can be talked out of by a policy, and three options were considered for this one. All three are worse than refusing:
- Synthesise the missing member. A fabricated
versionreads as authoritative and was never sent by anyone. - Drop the field, keep the rest of
meta. Silent data loss, andmetais exactly where a sender puts something it meant to be understood. - Move the value into the struct’s
extrabag. This one is a trap.ResultMetaObjectreally does carry#[serde(flatten)] extra, so puttingio.modelcontextprotocol/serverInfoin there serialises straight back out and looks lossless. It is not: the reader treats that key as a known member and tries to parse it as anImplementation, which is the parse that just failed here. So the document is unreadable by its intended reader, and building it anyway launders the rejection out of this layer and onto the peer — where the bridge looks healthy and the error lands a long way from its cause.
Refusing is the only option that leaves the failure where it can be
diagnosed. Note this leaves nothing for a DegradePolicy to do, and
that is the intent rather than an omission: the only rule available would
discard the sender’s data.
§The blast radius is narrower than it looks
The IR holds meta as a raw value, so a meta lifted from a
2026-07-28 peer has already been through that peer’s deserialiser and
will always re-parse. Only a value from a revision that treats meta as
untyped, or one built by hand, can reach this.
Trait Implementations§
Source§impl Clone for LowerError
impl Clone for LowerError
Source§impl Debug for LowerError
impl Debug for LowerError
Source§impl Display for LowerError
impl Display for LowerError
impl Eq for LowerError
Source§impl Error for LowerError
impl Error for LowerError
1.30.0 · Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()