Skip to content

outputSchema is generated for the deserialize contract; tool results are serialised #1374

Open

Description

@atarutin

Problem

schema_for_output::<T>() builds a tool's outputSchema through schema_for_type, which uses SchemaSettings::draft2020_12(), i.e. schemars' default Contract::Deserialize (crates/rmcp/src/handler/server/common.rs, still the case on main at ad5a284). A tool result is a value the server serialises, so its schema should describe what Serialize writes. Under the deserialize contract the advertised schema is wrong for common shapes:

  • an Option<T> field without skip_serializing_if is always written (as null), but is advertised as optional;
  • a #[serde(default)] field is always written, but is advertised as optional;
  • a type that accepts several forms and writes one (e.g. rust_decimal::Decimal: string or number in, string out) is advertised with every accepted form.

A client or model reading outputSchema is told a field may be absent when the server always sends it, or may be a number when the server sends a string.

Repro

#[derive(serde::Serialize, schemars::JsonSchema)]
struct Out {
    always: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    sometimes: Option<i32>,
}
// schema_for_output::<Out>()["required"] is absent; it should be ["always"].

Proposed fix

schema_for_output generates with SchemaSettings::draft2020_12().for_serialize(). Inputs (schema_for_type) stay on the deserialize contract. There is no public API change. For types whose two forms are equal, the output schema is byte-identical.

I have a patch against main with two tests (an always-written Option is required in an output schema; an input Option stays optional). cargo test -p rmcp --features server,client,macros,schemars,transport-io passes. I'm happy to open the PR if this direction is welcome.

Activity

  1. added
    bugSomething is not working
    P2Medium: important but non-blocking improvement
    T-handlerHandler implementation changes
    ready for workIssue is well-defined and ready to be picked up
    on Oct 17, 2026
  2. github-actions commented on Oct 17, 2026

    @github-actions
    Contributor

    @connor4312 is on triage rotation this week and is the reviewer for this one.

    Codex labeled this bug / P2 (T-handler). Please confirm or correct those labels.

    Automated triage. Labels are a first pass and the assigned maintainer has the final say.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Medium: important but non-blocking improvementT-handlerHandler implementation changesbugSomething is not workingready for workIssue is well-defined and ready to be picked up

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions