Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confusing interaction between additionalProperties and optional fields. #779

Open
witchof0x20 opened this issue Feb 21, 2025 · 1 comment

Comments

@witchof0x20
Copy link

given this schema:
https://github.com/modelcontextprotocol/specification/blob/main/schema/2024-11-05/schema.json#L144

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "definitions": {
"ClientCapabilities": {
            "description": "Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.",
            "properties": {
                "experimental": {
                    "additionalProperties": {
                        "additionalProperties": true,
                        "properties": {},
                        "type": "object"
                    },
                    "description": "Experimental, non-standard capabilities that the client supports.",
                    "type": "object"
                },
                "roots": {
                    "description": "Present if the client supports listing roots.",
                    "properties": {
                        "listChanged": {
                            "description": "Whether the client supports notifications for changes to the roots list.",
                            "type": "boolean"
                        }
                    },
                    "type": "object"
                },
                "sampling": {
                    "additionalProperties": true,
                    "description": "Present if the client supports sampling from an LLM.",
                    "properties": {},
                    "type": "object"
                }
            },
            "type": "object"
        }
}
}

My understanding (I could be incorrect) is that all three properties should be optional, as none are specified as required. However, typify produces

pub struct ClientCapabilities {
    #[doc = "Experimental, non-standard capabilities that the client supports."]
    #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
    pub experimental: std::collections::HashMap<String, serde_json::Map<String, serde_json::Value>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub roots: Option<ClientCapabilitiesRoots>,
    #[doc = "Present if the client supports sampling from an LLM."]
    #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
    pub sampling: serde_json::Map<String, serde_json::Value>,
}

On the serialization side, everything looks good, since these fields will not be present if empty. On the deserialization side, there's some unintended behavior, since the human-language specification seems to intend the presence of the sampling field to have meaning.

If Option<Map> becomes Map is a deliberate design choice, I could probably get away with using a custom typify::TypeSpaceSettings::with_map_type, but I figured I would ask here to be sure.

@ahl
Copy link
Collaborator

ahl commented Feb 24, 2025

This is interesting. For the roots field, we say "we need a type; the field may not be present; make it optional". For both experimental and sampling we say "we're using the map type; it's optional so take the shortcut of default and skip_serializing_if". In other words, we're not trying to distinguish "field is absent" from "field is present and has the default value".

That is intentional in that it's a design choice for ergonomics, but it would be interesting to consider alternatives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants