When using ChatClient.prompt() with .entity(MyClass.class), the MCP (Model Context Protocol) tool is not invoked. However, when the same prompt is called using .content() (rather than .entity(...)), the tool invocation works as expected.

Environment:

  • JDK: 24
  • Spring AI version: 1.1.0-M4
  • Using MCP client & server integration
  • Operating System: macOS
  • ChatClient configured with tool callbacks and toolContext

Reproduction Code Snippet:

// Chat client config
@Bean(name = "workClient")
@Primary
public ChatClient workClient(List<McpSyncClient> mcpSyncClients, GoogleGenAiChatModel chatModel, ChatMemory chatMemory) {
    return ChatClient.builder(chatModel)
            .defaultToolCallbacks(SyncMcpToolCallbackProvider.syncToolCallbacks(mcpSyncClients))
            .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
            .build();
}

// Path A: Using .content(), tool invocation works
String result = chatClient.prompt("""
    # Task analysis & instruction generation
    …
    """)
    .toolContext(Map.of("token", request.getToken()))
    .user(request.getMessage())
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, userId))
    .call()
    .content();
// MCP tool is invoked, action appears, tool executes.

// Path B: Using .entity(MyResponse.class), tool invocation does *not* occur
MyResponse response = chatClient.prompt("""
    # Task analysis & instruction generation
    …
    """)
    .toolContext(Map.of("token", request.getToken()))
    .user(request.getMessage())
    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, userId))
    .call()
    .entity(MyResponse.class);
// No tool invocation, no “Action:” log, tool callback not triggered.

Expected Behavior:

When .entity(Class) is used, the tool invocation workflow should be identical to .content() path: the client should send the prompt, model may output a tool invocation (e.g., Action: …), tool callback should execute, and the final structured output (entity) should be returned.

Actual Behavior:

Using .entity(Class) bypasses the tool invocation: the model is not instructed (or not allowed) to trigger the tool, and the tool callback is never executed. The result is directly deserialized without running the MCP tool.

Additional Notes / Observations:

  • I verified the prompt and toolContext are identical between both paths.
  • Switching to .content() immediately restores correct tool invocation behavior.

Possible Impact:

Projects using structured output (via .entity(...)) together with MCP tool invocation cannot rely on tools being triggered, forcing them to use less convenient workarounds (e.g., parse content() manually or bypass structured output).

Comment From: JamesSmith888

Not sure whether it is a BUG. https://docs.spring.io/spring-ai/reference/api/structured-output-converter.html

The StructuredOutputConverter is not used for LLM Tool Calling, as this feature inherently provides structured outputs by default.