spring-projects / spring-projects/spring-ai
Support distinguishing null vs absent fields in MCP tool parameters for partial update operations
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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