-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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