Describe the bug
rmcp deserializes an elicitation requestedSchema into typed structs and silently discards fields those structs do not model.
I found two related cases:
- The top-level
$schema field, which is present in the MCP protocol schemas starting with 2025-11-25:
requestedSchema: {
$schema?: string;
type: "object";
properties: { [key: string]: PrimitiveSchemaDefinition };
required?: string[];
};
See [ElicitRequestFormParams](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts).
This is particularly significant when a schema explicitly declares a non-default dialect. [SEP-1613](modelcontextprotocol/modelcontextprotocol#1613) allows embedded schemas to declare an alternative dialect through $schema, and [rust-sdk issue #525](#525) explicitly includes elicitation requestedSchema in that requirement.
[PR #549](#549) changed generated tool schemas to JSON Schema 2020-12, but it did not add $schema support to ElicitationSchema. It looks like that part of issue #525 was missed.
- Unknown keywords inside individual property schemas.
PrimitiveSchemaDefinition is a fixed, untagged enum of typed schema variants. A property containing an additional keyword is accepted, but that keyword is silently discarded and cannot be recovered through the decoded model.
These cases are slightly different: $schema is a missing protocol field that can be modeled directly, while preserving unrecognized property keywords would require a catch-all or another lossless representation.
To reproduce
Using rmcp 3.1.2, deserialize this request into rmcp::model::InputRequest and serialize it again:
let decoded: rmcp::model::InputRequest =
serde_json::from_value(wire.clone()).unwrap();
println!("IN : {}", serde_json::to_string(&wire).unwrap());
println!("OUT: {}", serde_json::to_string(&decoded).unwrap());
Input:
{
"method": "elicitation/create",
"params": {
"message": "attach the report",
"mode": "form",
"requestedSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"report": {
"description": "the quarterly report",
"format": "uri",
"type": "string",
"x-mcp-file": {
"accept": ["application/pdf"],
"transferModes": ["upload"]
}
}
},
"required": ["report"],
"type": "object"
}
}
}
Output:
{
"method": "elicitation/create",
"params": {
"message": "attach the report",
"mode": "form",
"requestedSchema": {
"properties": {
"report": {
"description": "the quarterly report",
"format": "uri",
"type": "string"
}
},
"required": ["report"],
"type": "object"
}
}
}
description, format, type, and required survive. $schema and x-mcp-file do not.
I originally reproduced this on 3.1.0 and have confirmed the same behavior on 3.1.2. The current main branch still lacks fields for $schema and unrecognized property keywords.
Expected behavior
At minimum, the spec-defined $schema field should remain available after deserialization.
Separately, preserving unmodeled property keywords in an extra map or another lossless raw representation would make inbound decoding forward-compatible with JSON Schema annotations and newer MCP extension keywords.
For example, draft [SEP-2631](modelcontextprotocol/modelcontextprotocol#2631) carries forward the proposed x-mcp-file inline schema annotation. An older rmcp version does not need to understand the keyword’s semantics, but silently discarding it prevents consumers that do understand the extension from accessing it without intercepting the raw JSON before rmcp decodes the request.
The current MCP specification does not prescribe a particular SDK representation for unknown keywords, so I see this as a forward-compatibility improvement rather than the same normative omission as $schema.
Affected paths
The same ElicitationSchema and InputRequest models are used by:
- Direct
elicitation/create requests, including legacy form requests and the current form mode
- Task input steering through
TaskPayload::InputRequired
- MRTR through
InputRequiredResult.inputRequests
I confirmed the loss through all three paths on rmcp 3.1.2.
Inbound clients and gateways lose the fields during decoding. Outbound servers also cannot express $schema or extension keywords through the typed ElicitationSchema API.
Tool schemas do not have this problem. Tool::input_schema and output_schema retain their underlying JsonObject, so JSON Schema keywords survive a round trip. That behavior is discussed in [#874](#874) and exercised by the keyword-preservation work in [#1003](#1003) and [#1018](#1018).
Practical impact
I’m building a gateway that brokers MCP file transfer while keeping file bytes out of model context. When an upstream server pauses for elicitation, the gateway relays that request downstream. On retry, it needs to know which response fields may contain files.
[SEP-2356](modelcontextprotocol/modelcontextprotocol#2356) originally proposed the x-mcp-file declaration. It was closed in favor of draft [SEP-2631](modelcontextprotocol/modelcontextprotocol#2631), which carries that declaration forward and builds its transfer flow around it. rmcp drops the declaration before the gateway can inspect it.
I cannot safely rely on a schema copy echoed by the client during retry. The [MRTR requirements](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr) require servers to treat requestState as attacker-controlled and protect its integrity when it affects authorization, resource access, or business logic.
Describe the bug
rmcp deserializes an elicitation
requestedSchemainto typed structs and silently discards fields those structs do not model.I found two related cases:
$schemafield, which is present in the MCP protocol schemas starting with 2025-11-25:See
[ElicitRequestFormParams](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts).This is particularly significant when a schema explicitly declares a non-default dialect. [SEP-1613](modelcontextprotocol/modelcontextprotocol#1613) allows embedded schemas to declare an alternative dialect through
$schema, and [rust-sdk issue #525](#525) explicitly includes elicitationrequestedSchemain that requirement.[PR #549](#549) changed generated tool schemas to JSON Schema 2020-12, but it did not add
$schemasupport toElicitationSchema. It looks like that part of issue #525 was missed.PrimitiveSchemaDefinitionis a fixed, untagged enum of typed schema variants. A property containing an additional keyword is accepted, but that keyword is silently discarded and cannot be recovered through the decoded model.These cases are slightly different:
$schemais a missing protocol field that can be modeled directly, while preserving unrecognized property keywords would require a catch-all or another lossless representation.To reproduce
Using rmcp 3.1.2, deserialize this request into
rmcp::model::InputRequestand serialize it again:Input:
{ "method": "elicitation/create", "params": { "message": "attach the report", "mode": "form", "requestedSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "report": { "description": "the quarterly report", "format": "uri", "type": "string", "x-mcp-file": { "accept": ["application/pdf"], "transferModes": ["upload"] } } }, "required": ["report"], "type": "object" } } }Output:
{ "method": "elicitation/create", "params": { "message": "attach the report", "mode": "form", "requestedSchema": { "properties": { "report": { "description": "the quarterly report", "format": "uri", "type": "string" } }, "required": ["report"], "type": "object" } } }description,format,type, andrequiredsurvive.$schemaandx-mcp-filedo not.I originally reproduced this on 3.1.0 and have confirmed the same behavior on 3.1.2. The current
mainbranch still lacks fields for$schemaand unrecognized property keywords.Expected behavior
At minimum, the spec-defined
$schemafield should remain available after deserialization.Separately, preserving unmodeled property keywords in an
extramap or another lossless raw representation would make inbound decoding forward-compatible with JSON Schema annotations and newer MCP extension keywords.For example, draft [SEP-2631](modelcontextprotocol/modelcontextprotocol#2631) carries forward the proposed
x-mcp-fileinline schema annotation. An older rmcp version does not need to understand the keyword’s semantics, but silently discarding it prevents consumers that do understand the extension from accessing it without intercepting the raw JSON before rmcp decodes the request.The current MCP specification does not prescribe a particular SDK representation for unknown keywords, so I see this as a forward-compatibility improvement rather than the same normative omission as
$schema.Affected paths
The same
ElicitationSchemaandInputRequestmodels are used by:elicitation/createrequests, including legacy form requests and the current form modeTaskPayload::InputRequiredInputRequiredResult.inputRequestsI confirmed the loss through all three paths on rmcp 3.1.2.
Inbound clients and gateways lose the fields during decoding. Outbound servers also cannot express
$schemaor extension keywords through the typedElicitationSchemaAPI.Tool schemas do not have this problem.
Tool::input_schemaandoutput_schemaretain their underlyingJsonObject, so JSON Schema keywords survive a round trip. That behavior is discussed in [#874](#874) and exercised by the keyword-preservation work in [#1003](#1003) and [#1018](#1018).Practical impact
I’m building a gateway that brokers MCP file transfer while keeping file bytes out of model context. When an upstream server pauses for elicitation, the gateway relays that request downstream. On retry, it needs to know which response fields may contain files.
[SEP-2356](modelcontextprotocol/modelcontextprotocol#2356) originally proposed the
x-mcp-filedeclaration. It was closed in favor of draft [SEP-2631](modelcontextprotocol/modelcontextprotocol#2631), which carries that declaration forward and builds its transfer flow around it. rmcp drops the declaration before the gateway can inspect it.I cannot safely rely on a schema copy echoed by the client during retry. The [MRTR requirements](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr) require servers to treat
requestStateas attacker-controlled and protect its integrity when it affects authorization, resource access, or business logic.