Skip to content

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

@tomtom215

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions