spring-projects / spring-projects/spring-ai

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

Open
#5,178 3 comments 2 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

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

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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.