Skip to content

Commit 71f6050

Browse files
authored
Merge branch 'spring-projects:main' into main
2 parents 676b630 + f5e8349 commit 71f6050

File tree

20 files changed

+244
-104
lines changed

20 files changed

+244
-104
lines changed

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpClientAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public List<McpSyncClient> mcpSyncClients(McpSyncClientConfigurer mcpSyncClientC
172172

173173
McpSchema.Implementation clientInfo = new McpSchema.Implementation(
174174
this.connectedClientName(commonProperties.getName(), namedTransport.name()),
175-
commonProperties.getVersion());
175+
namedTransport.name(), commonProperties.getVersion());
176176

177177
McpClient.SyncSpec spec = McpClient.sync(namedTransport.transport())
178178
.clientInfo(clientInfo)

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/StatelessToolCallbackConverterAutoConfigurationIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ List<ToolCallback> testDuplicateToolCallbacks() {
261261
Mockito.when(mockTool1.name()).thenReturn("duplicate-tool");
262262
Mockito.when(mockTool1.description()).thenReturn("First Tool");
263263
Mockito.when(mockClient1.callTool(Mockito.any(McpSchema.CallToolRequest.class))).thenReturn(mockResult1);
264-
when(mockClient1.getClientInfo()).thenReturn(new McpSchema.Implementation("testClient1", "1.0.0"));
264+
when(mockClient1.getClientInfo()).thenReturn(new McpSchema.Implementation("frist_client", "1.0.0"));
265265

266266
McpSyncClient mockClient2 = Mockito.mock(McpSyncClient.class);
267267
McpSchema.Tool mockTool2 = Mockito.mock(McpSchema.Tool.class);
@@ -270,7 +270,7 @@ List<ToolCallback> testDuplicateToolCallbacks() {
270270
Mockito.when(mockTool2.name()).thenReturn("duplicate-tool");
271271
Mockito.when(mockTool2.description()).thenReturn("Second Tool");
272272
Mockito.when(mockClient2.callTool(Mockito.any(McpSchema.CallToolRequest.class))).thenReturn(mockResult2);
273-
when(mockClient2.getClientInfo()).thenReturn(new McpSchema.Implementation("testClient2", "1.0.0"));
273+
when(mockClient2.getClientInfo()).thenReturn(new McpSchema.Implementation("second_client", "1.0.0"));
274274

275275
return List.of(SyncMcpToolCallback.builder().mcpClient(mockClient1).tool(mockTool1).build(),
276276
SyncMcpToolCallback.builder().mcpClient(mockClient2).tool(mockTool2).build());

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/ToolCallbackConverterAutoConfigurationIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ List<ToolCallback> testDuplicateToolCallbacks() {
261261
Mockito.when(mockTool1.name()).thenReturn("duplicate-tool");
262262
Mockito.when(mockTool1.description()).thenReturn("First Tool");
263263
Mockito.when(mockClient1.callTool(Mockito.any(McpSchema.CallToolRequest.class))).thenReturn(mockResult1);
264-
when(mockClient1.getClientInfo()).thenReturn(new McpSchema.Implementation("testClient1", "1.0.0"));
264+
when(mockClient1.getClientInfo()).thenReturn(new McpSchema.Implementation("client", "server1", "1.0.0"));
265265

266266
McpSyncClient mockClient2 = Mockito.mock(McpSyncClient.class);
267267
McpSchema.Tool mockTool2 = Mockito.mock(McpSchema.Tool.class);
@@ -270,7 +270,7 @@ List<ToolCallback> testDuplicateToolCallbacks() {
270270
Mockito.when(mockTool2.name()).thenReturn("duplicate-tool");
271271
Mockito.when(mockTool2.description()).thenReturn("Second Tool");
272272
Mockito.when(mockClient2.callTool(Mockito.any(McpSchema.CallToolRequest.class))).thenReturn(mockResult2);
273-
when(mockClient2.getClientInfo()).thenReturn(new McpSchema.Implementation("testClient2", "1.0.0"));
273+
when(mockClient2.getClientInfo()).thenReturn(new McpSchema.Implementation("client", "server2", "1.0.0"));
274274

275275
return List.of(SyncMcpToolCallback.builder().mcpClient(mockClient1).tool(mockTool1).build(),
276276
SyncMcpToolCallback.builder().mcpClient(mockClient2).tool(mockTool2).build());

mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ public class AsyncMcpToolCallback implements ToolCallback {
6666
*/
6767
@Deprecated
6868
public AsyncMcpToolCallback(McpAsyncClient mcpClient, Tool tool) {
69-
this(mcpClient, tool, McpToolUtils.prefixedToolName(mcpClient.getClientInfo().name(), tool.name()),
70-
ToolContextToMcpMetaConverter.defaultConverter());
69+
this(mcpClient, tool, McpToolUtils.prefixedToolName(mcpClient.getClientInfo().name(),
70+
mcpClient.getClientInfo().title(), tool.name()), ToolContextToMcpMetaConverter.defaultConverter());
7171
}
7272

7373
/**

mcp/common/src/main/java/org/springframework/ai/mcp/McpToolNamePrefixGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public interface McpToolNamePrefixGenerator {
4747
*/
4848
static McpToolNamePrefixGenerator defaultGenerator() {
4949
return (mcpConnectionIfo, tool) -> McpToolUtils.prefixedToolName(mcpConnectionIfo.clientInfo().name(),
50-
tool.name());
50+
mcpConnectionIfo.clientInfo().title(), tool.name());
5151
}
5252

5353
/**

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

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Map;
2121
import java.util.Optional;
2222
import java.util.function.BiFunction;
23+
import java.util.stream.Stream;
2324

2425
import com.fasterxml.jackson.annotation.JsonAlias;
2526
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -73,28 +74,62 @@ public final class McpToolUtils {
7374
private McpToolUtils() {
7475
}
7576

76-
public static String prefixedToolName(String prefix, String toolName) {
77+
/**
78+
* @param prefix Client name, combination of client info name and the 'server'
79+
* connection name.
80+
* @param title Server connection name
81+
* @param toolName original MCP server tool name.
82+
* @return the prefix to use for the tool to avoid name collisions.
83+
*/
84+
public static String prefixedToolName(String prefix, String title, String toolName) {
7785

7886
if (StringUtils.isEmpty(prefix) || StringUtils.isEmpty(toolName)) {
7987
throw new IllegalArgumentException("Prefix or toolName cannot be null or empty");
8088
}
8189

82-
String input = prefix + "_" + toolName;
90+
String input = shorten(format(prefix));
91+
if (!StringUtils.isEmpty(title)) {
92+
input = input + "_" + format(title); // Do not shorten the title.
93+
}
94+
95+
input = input + "_" + format(toolName);
8396

97+
// If the string is longer than 64 characters, keep the last 64 characters
98+
if (input.length() > 64) {
99+
input = input.substring(input.length() - 64);
100+
}
101+
102+
return input;
103+
}
104+
105+
public static String prefixedToolName(String prefix, String toolName) {
106+
return prefixedToolName(prefix, null, toolName);
107+
}
108+
109+
private static String format(String input) {
84110
// Replace any character that isn't alphanumeric, underscore, or hyphen with
85111
// concatenation. Support Han script + CJK blocks for complete Chinese character
86112
// coverage
87113
String formatted = input
88114
.replaceAll("[^\\p{IsHan}\\p{InCJK_Unified_Ideographs}\\p{InCJK_Compatibility_Ideographs}a-zA-Z0-9_-]", "");
89115

90-
formatted = formatted.replaceAll("-", "_");
116+
return formatted.replaceAll("-", "_");
117+
}
91118

92-
// If the string is longer than 64 characters, keep the last 64 characters
93-
if (formatted.length() > 64) {
94-
formatted = formatted.substring(formatted.length() - 64);
119+
/**
120+
* Shortens a string by taking the first letter of each word separated by underscores
121+
* @param input String in format "Word1_Word2_Word3_server"
122+
* @return Shortened string with first letters in lowercase "w_w_w_s"
123+
*/
124+
private static String shorten(String input) {
125+
if (input == null || input.isEmpty()) {
126+
return "";
95127
}
96128

97-
return formatted;
129+
return Stream.of(input.toLowerCase().split("_"))
130+
.filter(word -> !word.isEmpty())
131+
.map(word -> String.valueOf(word.charAt(0)))
132+
.collect(java.util.stream.Collectors.joining("_"));
98133
}
99134

100135
/**

mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ public class SyncMcpToolCallback implements ToolCallback {
6363
*/
6464
@Deprecated
6565
public SyncMcpToolCallback(McpSyncClient mcpClient, Tool tool) {
66-
this(mcpClient, tool, McpToolUtils.prefixedToolName(mcpClient.getClientInfo().name(), tool.name()),
67-
ToolContextToMcpMetaConverter.defaultConverter());
66+
this(mcpClient, tool, McpToolUtils.prefixedToolName(mcpClient.getClientInfo().name(),
67+
mcpClient.getClientInfo().title(), tool.name()), ToolContextToMcpMetaConverter.defaultConverter());
6868
}
6969

7070
/**

mcp/common/src/test/java/org/springframework/ai/mcp/AsyncMcpToolCallbackProviderTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ void toolFilterShouldFilterToolsByNameWhenConfigured() {
225225
var callbacks = provider.getToolCallbacks();
226226

227227
assertThat(callbacks).hasSize(2);
228-
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("testClient_tool2");
229-
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("testClient_tool3");
228+
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("t_tool2");
229+
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("t_tool3");
230230
}
231231

232232
@Test
@@ -266,7 +266,7 @@ void toolFilterShouldFilterToolsByClientWhenConfigured() {
266266
var callbacks = provider.getToolCallbacks();
267267

268268
assertThat(callbacks).hasSize(1);
269-
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("testClient1_tool1");
269+
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("t_tool1");
270270
}
271271

272272
@Test
@@ -299,7 +299,7 @@ void toolFilterShouldCombineClientAndToolCriteriaWhenConfigured() {
299299
var callbacks = provider.getToolCallbacks();
300300

301301
assertThat(callbacks).hasSize(1);
302-
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("weather_service_weather");
302+
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("w_s_weather");
303303
}
304304

305305
@Test

mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackBuilderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ void builderShouldCreateInstanceWithRequiredFields() {
4949
assertThat(callback).isNotNull();
5050
assertThat(callback.getOriginalToolName()).isEqualTo("test-tool");
5151
assertThat(callback.getToolDefinition()).isNotNull();
52-
assertThat(callback.getToolDefinition().name()).isEqualTo("test_client_test_tool");
52+
assertThat(callback.getToolDefinition().name()).isEqualTo("t_c_test_tool");
5353
assertThat(callback.getToolDefinition().description()).isEqualTo("Test tool description");
5454
}
5555

mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackProviderBuilderTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ void builderShouldCreateInstanceWithSingleClient() {
4747
assertThat(provider).isNotNull();
4848
ToolCallback[] callbacks = provider.getToolCallbacks();
4949
assertThat(callbacks).hasSize(1);
50-
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("test_client_test_tool");
50+
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("t_c_test_tool");
5151
}
5252

5353
@Test
@@ -64,8 +64,8 @@ void builderShouldCreateInstanceWithMultipleClients() {
6464
assertThat(provider).isNotNull();
6565
ToolCallback[] callbacks = provider.getToolCallbacks();
6666
assertThat(callbacks).hasSize(2);
67-
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("client1_tool1");
68-
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("client2_tool2");
67+
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("c_tool1");
68+
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("c_tool2");
6969
}
7070

7171
@Test
@@ -111,7 +111,7 @@ void builderShouldCreateInstanceWithCustomToolFilter() {
111111
assertThat(provider).isNotNull();
112112
ToolCallback[] callbacks = provider.getToolCallbacks();
113113
assertThat(callbacks).hasSize(1);
114-
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("client_filtered_tool");
114+
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("c_filtered_tool");
115115
}
116116

117117
@Test
@@ -230,8 +230,8 @@ void builderShouldReplaceClientsWhenSettingNewList() {
230230
assertThat(provider).isNotNull();
231231
ToolCallback[] callbacks = provider.getToolCallbacks();
232232
assertThat(callbacks).hasSize(2);
233-
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("client2_tool2");
234-
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("client3_tool3");
233+
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("c_tool2");
234+
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("c_tool3");
235235
}
236236

237237
private McpSyncClient createMockClient(String clientName, String toolName) {

0 commit comments

Comments
 (0)