spring-projects / spring-projects/spring-ai
MCP Server not using custom Jackson ObjectMapper for deserialization of tool call parameters
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.5k
- Forks
- 2.9k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 6
Description
Bug description
While one can register a custom Jackson ObjectMapper through McpServerAutoConfiguration#stdioServerTransport, during the construction of the method arguments in AbstractMcpToolMethodCallback#buildTypedArgument, a static JsonParser with a static ObjectMapper (JsonParser#OBJECT_MAPPER) is used.
This mapper doesn't use the Jackson Kotlin module, so if I call a tool with a parameter of type List the call fails with a Cannot construct instance of ... error.
One solution seems to make the JsonParser non-static and then inject and use the custom ObjectMapper in the JsonParser.
However, I noticed, that there is 2 implementations of the JsonParser that are both used in Spring AI:
- org.springframework.ai.util.json.JsonParser
- org.springaicommunity.mcp.method.tool.utils.JsonParser (this is the one that is used in my case but not part of the Spring AI project)
Can sb give me some context on the idea behind the duplicate implementations of the JsonParser and why it was setup in a static manner without using the custom ObjectMapper? If you give me some hints maybe I can propose a solution. Thanks so much.
Environment
- Kotlin version: 2.2.0
- JVM version: 21
- Gradle 8.14
Steps to reproduce
- Setup a Spring AI MCP server with a tool that has a parameter of type List.
- Register a custom named Bean of type ObjectMapper with name mcpServerObjectMapper.
- Run the application
- Call the tool with a non-empty list of parameters
Expected behavior
The expectation is that the registered custom ObjectMapper (mcpServerObjectMapper) is used for deserialization of the tool call parameters and that the tool call works.
Minimal Complete Reproducible example
Run the following application to start the MCP server. Then connect using the MCP inspector and call the provided tool with a non-empty list.
package com.test
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springaicommunity.mcp.annotation.McpTool
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Service
@SpringBootApplication
class Main
fun main(args: Array<String>) {
runApplication<Main>(*args)
}
data class Person(
val name: String,
val age: Int
)
@Configuration
class JacksonObjectMapper {
@Bean(name = ["mcpServerObjectMapper"])
fun objectMapper(): ObjectMapper {
val tmp = jacksonObjectMapper()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
return tmp
}
}
@Service
class PersonsToolProvider {
@McpTool
fun passPersons(
persons: List<Person>) {
println(persons)
}
}
Dependencies:
dependencies {
implementation("org.springframework.ai:spring-ai-starter-mcp-server-webmvc:1.1.2")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.20.0")
}
Application properties:
spring.ai.mcp.server.protocol=streamable
spring.ai.mcp.server.type=sync
spring.ai.mcp.server.base-url=http://localhost:8080/mcp
spring.ai.mcp.server.request-timeout=200s
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 at McpServerAutoConfiguration#stdioServerTransport and AbstractMcpToolMethodCallback#buildTypedArgument, then compare the two JsonParser implementations named in the report. Verify how the custom mcpServerObjectMapper is passed during tool-parameter deserialization, using the Kotlin List reproduction. Done means the registered mapper handles the non-empty tool call successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 57/100