@Tool returnDirect Attribute Ignored by DefaultToolCallingManager in MCP Integration

Bug description When integrating an MCP service with Spring AI, the returnDirect attribute in @Tool annotations is being ignored during execution. The DefaultToolCallingManager fails to retain this parameter in method metadata, causing MCP services to ​always perform an extra LLM call even when returnDirect = true.

Comment From: sunyuhan1998

In my opinion, this attribute currently cannot take effect on the MCP server side, for the following reasons:

The normal workflow of the returnDirect attribute is as follows:

  1. The model parses the list of tools and decides whether to invoke a tool and which one to invoke based on the user's prompt.
  2. Execute the tool call.
  3. All model implementations in Spring AI support the returnDirect attribute. If this attribute is set to true, the tool call will not be sent to the LLM, but instead returned directly to the user; otherwise, the result of the tool execution will be sent to the model.

This workflow presents some issues in the MCP scenario:

All tools are defined on the MCP server side, and the MCP client obtains the tool definitions from the server and requests the server to invoke the corresponding tool.
However, according to the MCP specification for tools: listing-tools, the tool itself does not have an attribute like returnDirect, It has only a few attributes, such as name, description, and inputSchema:

{
  "name": "get_weather",
  "description": "Get current weather information for a location",
  "inputSchema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City name or zip code"
      }
    },
    "required": ["location"]
  }
}

As a result, the client has no way of knowing whether the tool should be returned directly to the user without being sent to the LLM.

In the following code, we can see that when the MCP server exposes the tool definitions, it converts ToolCallback into the MCP-standard McpSchema.Tool, at which point the returnDirect property is lost:

https://github.com/spring-projects/spring-ai/blob/161c437556a3f79761c8d8991f1fa22f68cbc78e/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java#L167-L188

Then, when the client attempts to execute SyncMcpToolCallback or AsyncMcpToolCallback, since it does not (and cannot) implement the ToolMetadata getToolMetadata() method, it invokes the default implementation of the ToolCallback interface:

https://github.com/spring-projects/spring-ai/blob/161c437556a3f79761c8d8991f1fa22f68cbc78e/spring-ai-model/src/main/java/org/springframework/ai/tool/ToolCallback.java#L40-L42

In this default implementation, the returnDirect attribute is set to false:

https://github.com/spring-projects/spring-ai/blob/161c437556a3f79761c8d8991f1fa22f68cbc78e/spring-ai-model/src/main/java/org/springframework/ai/tool/metadata/DefaultToolMetadata.java#L31-L34

Therefore, I believe that at this point in time, unless the MCP protocol itself supports this attribute and the Java SDK for MCP also provides support for it, this capability cannot be supported in the MCP scenario.

What do you think? I'm looking forward to your thoughts. @tzolov

Comment From: sunyuhan1998

the same issue: #3400

Comment From: chinaxwl

Image 可以看到MCP的tool还有一个annotations的扩展属性,是否可以将returnDirect赋值到这个属性里呢?

Comment From: sunyuhan1998

可以看到MCP的tool还有一个annotations的扩展属性,是否可以将returnDirect赋值到这个属性里呢?

So far, I'm afraid not, because Spring AI's support for MCP is based on the Java SDK implementation of MCP. As of now, this SDK does not support the annotations attribute in Tools.

Comment From: sunyuhan1998

I've submitted an issue and PR in MCP's Java-sdk to try to move this forward: https://github.com/modelcontextprotocol/java-sdk/issues/312

Comment From: sunyuhan1998

Hi @mylater @chinaxwl ! There's good news, the previous issue and PR about expecting support for Tool's annotations property in MCP's java-sdk: https://github.com/modelcontextprotocol/java-sdk/issues/312 has been passed and merged, I think When the next version of MCP's java-sdk is released (maybe v0.11.0?), we will be able to integrate it in Spring AI and use it to solve the problem that we are discussing in the current issue, I will keep an eye on MCP's java-sdk release, and submit a PR to Spring AI as soon as it is available!

Comment From: babywait

In my opinion, this attribute currently cannot take effect on the MCP server side, for the following reasons:

The normal workflow of the returnDirect attribute is as follows:

  1. The model parses the list of tools and decides whether to invoke a tool and which one to invoke based on the user's prompt.
  2. Execute the tool call.
  3. All model implementations in Spring AI support the returnDirect attribute. If this attribute is set to true, the tool call will not be sent to the LLM, but instead returned directly to the user; otherwise, the result of the tool execution will be sent to the model.

This workflow presents some issues in the MCP scenario:

All tools are defined on the MCP server side, and the MCP client obtains the tool definitions from the server and requests the server to invoke the corresponding tool. However, according to the MCP specification for tools: listing-tools, the tool itself does not have an attribute like returnDirect, It has only a few attributes, such as name, description, and inputSchema:

{ "name": "get_weather", "description": "Get current weather information for a location", "inputSchema": { "type": "object", "properties": { "location": { "type": "string", "description": "City name or zip code" } }, "required": ["location"] } }

As a result, the client has no way of knowing whether the tool should be returned directly to the user without being sent to the LLM.

In the following code, we can see that when the MCP server exposes the tool definitions, it converts ToolCallback into the MCP-standard McpSchema.Tool, at which point the returnDirect property is lost:

spring-ai/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java

Lines 167 to 188 in 161c437

public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(ToolCallback toolCallback, MimeType mimeType) {

var tool = new McpSchema.Tool(toolCallback.getToolDefinition().name(), toolCallback.getToolDefinition().description(), toolCallback.getToolDefinition().inputSchema());

return new McpServerFeatures.SyncToolSpecification(tool, (exchange, request) -> { try { String callResult = toolCallback.call(ModelOptionsUtils.toJsonString(request), new ToolContext(Map.of(TOOL_CONTEXT_MCP_EXCHANGE_KEY, exchange))); if (mimeType != null && mimeType.toString().startsWith("image")) { return new McpSchema.CallToolResult(List .of(new McpSchema.ImageContent(List.of(Role.ASSISTANT), null, callResult, mimeType.toString())), false); } return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(callResult)), false); } catch (Exception e) { return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(e.getMessage())), true); } }); } Then, when the client attempts to execute SyncMcpToolCallback or AsyncMcpToolCallback, since it does not (and cannot) implement the ToolMetadata getToolMetadata() method, it invokes the default implementation of the ToolCallback interface:

spring-ai/spring-ai-model/src/main/java/org/springframework/ai/tool/ToolCallback.java

Lines 40 to 42 in 161c437

default ToolMetadata getToolMetadata() { return ToolMetadata.builder().build(); } In this default implementation, the returnDirect attribute is set to false:

spring-ai/spring-ai-model/src/main/java/org/springframework/ai/tool/metadata/DefaultToolMetadata.java

Lines 31 to 34 in 161c437

public static final class Builder {

private boolean returnDirect = false;

Therefore, I believe that at this point in time, unless the MCP protocol itself supports this attribute and the Java SDK for MCP also provides support for it, this capability cannot be supported in the MCP scenario.

What do you think? I'm looking forward to your thoughts. @tzolov

I am facing the problem: I want the value of returnDirect attribute in SyncMcpToolCallback is true. My opinion: the value of returnDirect attribute should be determined by the mcp client. because: a mcp server can be used by anyone in any application, different application scenario has different purpose.

Comment From: sunyuhan1998

I am facing the problem: I want the value of returnDirect attribute in SyncMcpToolCallback is true. My opinion: the value of returnDirect attribute should be determined by the mcp client. because: a mcp server can be used by anyone in any application, different application scenario has different purpose.

I think, regardless of whether the client decides to directly return the result of a tool call to the user, the server should still correctly return the returnDirect attribute to the client. This is because returnDirect is one of the inherent properties of the tool itself. As for the behavior after it is returned to the client, we can make further decisions on the client side.