Skip to content

MCP Server not using custom Jackson ObjectMapper for deserialization of tool call parameters #5178

@nautnatic

Description

@nautnatic

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

  1. Setup a Spring AI MCP server with a tool that has a parameter of type List.
  2. Register a custom named Bean of type ObjectMapper with name mcpServerObjectMapper.
  3. Run the application
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions