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.

  3. connor4312 commented on Oct 17, 2026

    @connor4312

    Thanks for the issue, this seems like a good fix to make. You mentioned you already have some patches available; would you like to submit a PR?

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