Skip to content

ElicitationSchema serde round-trip silently drops enumNames (untagged EnumSchema matches the legacy form as Untitled) #903

Description

@tomtom215

Deserializing a legacy enum property ({"type":"string","enum":[...],"enumNames":[...]}) into ElicitationSchema and serializing it back loses enumNames. Affects rmcp = 1.7.0 and current main (52e731b). This is the separate report promised in #902.

Mechanism (crates/rmcp/src/model/elicitation_schema.rs): EnumSchema is #[serde(untagged)] with Single before Legacy (751–758); SingleSelectEnumSchema is untagged with Untitled first (600–607); UntitledSingleSelectEnumSchema (549–566) doesn't reject unknown fields, so the legacy payload matches it and enumNames is silently dropped — Legacy is never reached.

Minimal repro (panics on =1.7.0 and main; deps: rmcp = { version = "=1.7.0", features = ["elicitation"] }, serde_json = "1"):

use rmcp::model::ElicitationSchema;

fn main() {
    let input = serde_json::json!({
        "type": "object",
        "properties": {
            "legacyEnum": {
                "type": "string",
                "enum": ["opt1", "opt2", "opt3"],
                "enumNames": ["Option One", "Option Two", "Option Three"]
            }
        }
    });
    let schema: ElicitationSchema =
        serde_json::from_value(input.clone()).expect("schema deserializes");
    let output = serde_json::to_value(&schema).expect("schema serializes");
    assert_eq!(
        output["properties"]["legacyEnum"]["enumNames"],
        serde_json::json!(["Option One", "Option Two", "Option Three"]),
        "enumNames lost in ElicitationSchema serde round-trip"
    );
}

Observed: the round-tripped property comes back as {"enum":["opt1","opt2","opt3"],"type":"string"} — enumNames gone.

Where it bites in this repo: conformance/src/bin/server.rs builds the SEP-1330 legacy fixture as raw JSON with enumNames (lines 467–470) and converts it via serde_json::from_value::(...) — the official suite then fails elicitation-sep1330-enums with "Missing or invalid enumNames array for legacy titled enum" (the 38/40 run described in #902; reproducible: build conformance-server, run npx @modelcontextprotocol/conformance@0.1.16 server --url http://127.0.0.1:/mcp --spec-version 2025-11-25). Receiving rmcp clients lose the field on every legacy elicitation the same way.

Related nit worth fixing together: LegacyEnumSchema.enum_names (547) has no skip_serializing_if, so a typed Legacy with enum_names: None serializes "enumNames": null — relevant because plain variant reordering would surface that on untitled enums.

Possible fixes: a manual Deserialize for EnumSchema that prefers Legacy when enumNames is present; or deny_unknown_fields on the untitled/titled structs (needs a look at tolerance for extension fields); plus skip_serializing_if = "Option::is_none" on enum_names. Happy to PR with the repro as a regression test.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions