spring-projects / spring-projects/spring-ai

MCP generateOutputSchema produces an unsatisfiable schema for optional/nullable fields (non-nullable type + required)

Open
#6,950 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
Dominant language
Java
Stars
9.5k
Forks
2.9k
Avg merge
1d 7h
Merged PRs (30d)
6

Description

Summary

@McpTool(generateOutputSchema = true) generates an output schema that no
response can satisfy
when the return type has an optional (nullable) member.
Every property is typed non-nullable and listed in required, so a null value
fails the type check and omitting the field fails the required check. The MCP SDK
then rejects a correct response at call time.

This is not a nullability-annotation or serialization problem: there is no
serialization of an optional field that validates against the generated schema.

Environment
  • Spring AI 2.0.1 (spring-ai-starter-mcp-server-webmvc)
  • MCP Java SDK 2.0.0
  • Spring Boot 4.1.1, Java 21
  • Transport: Streamable HTTP, SYNC
Minimal reproduction

One tool, one record, one optional field:

@Component
public class OptionalFieldTool {

    // endedAt is null whenever the item has not ended — an ordinary shape for
    // anything with a validity period, a completion time or an upper bound.
    public record Item(String id, String endedAt) { }

    @McpTool(name = "fixture.get_item",
             description = "Returns an item whose endedAt is null when it has not ended.",
             generateOutputSchema = true)
    public Item getItem(@McpToolParam(description = "Anything.", required = true) String id) {
        return new Item(id, null);
    }
}
Generated outputSchema from tools/list
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "endedAt": { "type": "string" },
    "id":      { "type": "string" }
  },
  "required": [ "endedAt", "id" ]
}

endedAt is typed string (not ["string","null"]) and is in required.

Actual behaviour

tools/call on fixture.get_item returns isError: true:

Tool (fixture.get_item) output validation failed: Validation failed:
JSON schema validation errors: [/endedAt: null found, string expected]
Expected behaviour

A nullable/optional component should be generated as optional — omitted from
required, and/or typed to admit null (e.g. "type": ["string", "null"]) — so
that a valid response validates.

Why the obvious workarounds do not help
  • Annotating the field @org.springframework.lang.Nullable — no effect on
    the generated schema.
  • Omitting nulls from serialization (including via the SDK's
    McpJsonMapperSupplier ServiceLoader hook) — moves the failure from the type
    check to the required check, because the field is marked required as well.

Because both halves are present, an optional member has no valid representation
at all.

Impact

Any tool whose response type has an optional member cannot use
generateOutputSchema. In our case enabling it across a 16-tool server broke the
primary read tool on the first call containing a null; it fails at neither build
time nor startup, only on the first real response. We have disabled the flag and
are tracking it as interoperability debt.

Reproduction project

The reproduction above is self-contained: a Boot 4.1.1 app with the one component
shown, spring.ai.mcp.server.protocol=STREAMABLE, type=SYNC, and a test that
performs initializetools/listtools/call. Happy to attach or link a
repo if that is useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the @McpTool(generateOutputSchema = true) output-schema generation path and run the self-contained reproduction through initialize, tools/list, and tools/call. Verify that a nullable endedAt value can satisfy the generated schema without failing either type validation or the required-field check.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring-boot
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.