spring-projects / spring-projects/spring-ai

Support distinguishing null vs absent fields in MCP tool parameters for partial update operations

Open
#6,046 7 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

Problem

When building MCP tools that perform partial updates, there is currently no way to
distinguish between a field that was explicitly set to null (meaning "clear this value")
and a field that was simply omitted from the call (meaning "leave this value unchanged").

This is a common requirement for update operations. For example, a tool that updates
an entity with optional description and parent_id fields:

@Tool
public void updateItem(String id, String description, Long parentId) {
    // Was parentId omitted (skip) or explicitly null (clear)?
    // Impossible to tell after deserialization.
}

When the LLM calls this tool with {"id": "123", "description": "new value"},
both the omitted parentId and an explicitly passed null arrive as null in Java —
the intent is lost.

Expected Behavior

It should be possible to detect whether a parameter was explicitly set to null or
was absent from the tool call payload.

One well-established solution is jackson-databind-nullable (https://github.com/OpenAPITools/jackson-databind-nullable),
which provides a JsonNullable<T> wrapper type for exactly this distinction:

  @Tool
  public void updateItem(String id, JsonNullable<String> description, JsonNullable<Long> parentId) {
      if (JsonNullable.undefined().equals(parentId)) {
          // field was omitted — skip
      } else if (parentId.get() == null) {
          // field was explicitly null — clear it
      } else {
          // field has a value — update it
      }
  }

Workaround (partial)

It is possible to fix the deserialization side today by manually patching Spring AI's (v.1.1.4)
internal ObjectMapper at application startup:

  @PostConstruct
  public void registerJsonNullableModule() {
      JsonParser.getObjectMapper().registerModule(new JsonNullableModule());
  }

  @Bean(name = "mcpServerObjectMapper")
  public ObjectMapper mcpServerObjectMapper() {
      return JsonMapper.builder()
              .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
              .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
              .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
              .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
              .addModules(JacksonUtils.instantiateAvailableModules())
              .addModule(new JsonNullableModule()) // module registration
              .build();
  }

However, this only partially solves the problem:

  • ✅ JsonNullable fields are correctly deserialized from tool call payloads
  • ❌ JsonSchemaGenerator does not support JsonNullableModule, so the generated
    MCP input schema for JsonNullable patameters is incorrect — the LLM receives
    a wrong or misleading schema for those fields

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 by tracing MCP tool parameter deserialization through the internal ObjectMapper and then inspect JsonSchemaGenerator, since the issue identifies both as affected entry points. Check how JsonNullableModule is registered and how JsonNullable parameters are represented in generated MCP input schemas. Done means tool calls distinguish omitted, null, and valued fields while the generated schema accurately describes those parameters.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.