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

Comment From: babywait

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.

感谢回复。 我同意:server 确实可以返回 returnDirect,我们也可以在 client 端做进一步的决定。

目前我想在客户端来设定 SyncMcpToolCallback 中的 returnDirect = true, 最佳方式应该是什么?

Comment From: sunyuhan1998

目前我想在客户端来设定 SyncMcpToolCallback 中的 returnDirect = true, 最佳方式应该是什么?

To be honest, there is currently no way to specify returnDirect on the client side. However, I'm very interested in understanding how you would expect it to be used if we were to support this feature.

Would we need to maintain a mapping from tool names to returnDirect on the client side in advance? I'm having trouble seeing where exactly we would use it in our current workflow.

Comment From: babywait

目前我想在客户端来设定 SyncMcpToolCallback 中的 returnDirect = true, 最佳方式应该是什么?

To be honest, there is currently no way to specify returnDirect on the client side. However, I'm very interested in understanding how you would expect it to be used if we were to support this feature.

Would we need to maintain a mapping from tool names to returnDirect on the client side in advance? I'm having trouble seeing where exactly we would use it in our current workflow.

为了表达清楚,我还是用中文了哈~ 场景举例:Text to SQL 流程:用户的自然语言输入 -> LLM 转成对应的 SQL 语句 -> 通过 MCP 调用,执行 SQL 查询 DB -> 直接将查询结果展示给用户 我觉得通过 MCP 获得的查询结果完全没有必要再调一次 LLM

Comment From: sunyuhan1998

为了表达清楚,我还是用中文了哈~ 场景举例:Text to SQL 流程:用户的自然语言输入 -> LLM 转成对应的 SQL 语句 -> 通过 MCP 调用,执行 SQL 查询 DB -> 直接将查询结果展示给用户 我觉得通过 MCP 获得的查询结果完全没有必要再调一次 LLM

Thank you for the explanation. I understand your use case now. But I'd like to clarify one key point: your MCP server may provide many tools (not just executing SQL). So, as a client, how do you expect to specify which tool results should be returned directly, and which others should follow the declarations from the MCP server?

By the way, I'm using English here just to make it easier for others reviewing this PR to understand. : )

Comment From: babywait

为了表达清楚,我还是用中文了哈~ 场景举例:Text to SQL 流程:用户的自然语言输入 -> LLM 转成对应的 SQL 语句 -> 通过 MCP 调用,执行 SQL 查询 DB -> 直接将查询结果展示给用户 我觉得通过 MCP 获得的查询结果完全没有必要再调一次 LLM

Thank you for the explanation. I understand your use case now. But I'd like to clarify one key point: your MCP server may provide many tools (not just executing SQL). So, as a client, how do you expect to specify which tool results should be returned directly, and which others should follow the declarations from the MCP server?

By the way, I'm using English here just to make it easier for others reviewing this PR to understand. : )

Thank you so much. My Mcp Server provided only one tool, so I didn't realize the problem :)

I will consider to use the tool of Spring AI rather than MCP 😊

Comment From: rlagyu0

is it ING?

Comment From: sunyuhan1998

is it ING?

Yes, the latest progress is in this PR, currently, we are waiting for the maintainers to handle it: https://github.com/spring-projects/spring-ai/pull/3787