Summary
rmcp 3.0 unconditionally serializes resultType: "complete" on ordinary
response types, including CallToolResult, paginated list results, and
resource results.
That behavior is correct for MCP 2026-07-28, but it also changes the wire
format when a server is implementing or negotiating an older MCP version.
Consequently, upgrading rmcp can break existing legacy servers or clients even
when they do not enable the new protocol.
Background
The discriminator was introduced intentionally by:
The SDK documentation says:
Servers implementing this protocol version MUST include resultType in
every result. For backward compatibility, clients MUST treat an absent field
as "complete".
However, backward compatibility here appears to cover deserialization by new
clients, not serialization by new servers that are still speaking a legacy
protocol.
Reproduction
use rmcp::model::CallToolResult;
let result = CallToolResult::success(Vec::new());
let value = serde_json::to_value(result)?;
assert_eq!(
value,
serde_json::json!({
"resultType": "complete",
"content": [],
"isError": false,
})
);
This serialization is identical regardless of whether the peer negotiated MCP
2026-07-28 or an older version such as 2025-06-18.
For a legacy session, the historical wire representation was:
{
"content": [],
"isError": false
}
The same issue also applies to other result types to which #915 added a
result_type field.
Why this matters
Applications can upgrade rmcp globally while continuing to implement legacy MCP
server behavior. Adding a new field to their existing responses can break:
- Clients that strictly validate legacy response schemas.
- Clients with exact response-shape expectations.
- Applications promising no wire-format changes during an SDK-only upgrade.
We currently work around this by serializing the response to JSON and removing
resultType when its value is "complete" before sending responses from our
legacy MCP server.
Existing upstream signal
The SDK already acknowledges this compatibility concern for empty results:
#[test]
fn empty_result_serializes_without_result_type() {
assert_eq!(serde_json::to_value(EmptyResult {})?, serde_json::json!({}));
}
The accompanying comment explains that older or strict peers may reject
additional fields. Ordinary result types have the same issue when used in a
legacy protocol session.
Expected behavior
Serialization should respect the negotiated protocol version:
- MCP 2026-07-28: include
resultType: "complete".
- Older MCP versions: preserve the legacy representation without
resultType.
- Continue accepting absent
resultType as "complete" when deserializing
legacy responses.
A session-aware serializer, transport-level compatibility adapter, or explicit
legacy serialization API would all address this without weakening 2026-07-28
compliance.
Question
Is unconditional serialization intentional even for legacy sessions, or should
rmcp provide a supported protocol-version-aware compatibility path?
Summary
rmcp 3.0 unconditionally serializes
resultType: "complete"on ordinaryresponse types, including
CallToolResult, paginated list results, andresource results.
That behavior is correct for MCP 2026-07-28, but it also changes the wire
format when a server is implementing or negotiating an older MCP version.
Consequently, upgrading rmcp can break existing legacy servers or clients even
when they do not enable the new protocol.
Background
The discriminator was introduced intentionally by:
The SDK documentation says:
However, backward compatibility here appears to cover deserialization by new
clients, not serialization by new servers that are still speaking a legacy
protocol.
Reproduction
This serialization is identical regardless of whether the peer negotiated MCP
2026-07-28 or an older version such as 2025-06-18.
For a legacy session, the historical wire representation was:
{ "content": [], "isError": false }The same issue also applies to other result types to which #915 added a
result_typefield.Why this matters
Applications can upgrade rmcp globally while continuing to implement legacy MCP
server behavior. Adding a new field to their existing responses can break:
We currently work around this by serializing the response to JSON and removing
resultTypewhen its value is"complete"before sending responses from ourlegacy MCP server.
Existing upstream signal
The SDK already acknowledges this compatibility concern for empty results:
The accompanying comment explains that older or strict peers may reject
additional fields. Ordinary result types have the same issue when used in a
legacy protocol session.
Expected behavior
Serialization should respect the negotiated protocol version:
resultType: "complete".resultType.resultTypeas"complete"when deserializinglegacy responses.
A session-aware serializer, transport-level compatibility adapter, or explicit
legacy serialization API would all address this without weakening 2026-07-28
compliance.
Question
Is unconditional serialization intentional even for legacy sessions, or should
rmcp provide a supported protocol-version-aware compatibility path?