Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ public List<McpSyncClient> mcpSyncClients(McpSyncClientConfigurer mcpSyncClientC

McpSchema.Implementation clientInfo = new McpSchema.Implementation(
this.connectedClientName(commonProperties.getName(), namedTransport.name()),
commonProperties.getVersion());
namedTransport.name(), commonProperties.getVersion());

McpClient.SyncSpec spec = McpClient.sync(namedTransport.transport())
.clientInfo(clientInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ List<ToolCallback> testDuplicateToolCallbacks() {
Mockito.when(mockTool1.name()).thenReturn("duplicate-tool");
Mockito.when(mockTool1.description()).thenReturn("First Tool");
Mockito.when(mockClient1.callTool(Mockito.any(McpSchema.CallToolRequest.class))).thenReturn(mockResult1);
when(mockClient1.getClientInfo()).thenReturn(new McpSchema.Implementation("testClient1", "1.0.0"));
when(mockClient1.getClientInfo()).thenReturn(new McpSchema.Implementation("frist_client", "1.0.0"));

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

return List.of(SyncMcpToolCallback.builder().mcpClient(mockClient1).tool(mockTool1).build(),
SyncMcpToolCallback.builder().mcpClient(mockClient2).tool(mockTool2).build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ List<ToolCallback> testDuplicateToolCallbacks() {
Mockito.when(mockTool1.name()).thenReturn("duplicate-tool");
Mockito.when(mockTool1.description()).thenReturn("First Tool");
Mockito.when(mockClient1.callTool(Mockito.any(McpSchema.CallToolRequest.class))).thenReturn(mockResult1);
when(mockClient1.getClientInfo()).thenReturn(new McpSchema.Implementation("testClient1", "1.0.0"));
when(mockClient1.getClientInfo()).thenReturn(new McpSchema.Implementation("client", "server1", "1.0.0"));

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

return List.of(SyncMcpToolCallback.builder().mcpClient(mockClient1).tool(mockTool1).build(),
SyncMcpToolCallback.builder().mcpClient(mockClient2).tool(mockTool2).build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public class AsyncMcpToolCallback implements ToolCallback {
*/
@Deprecated
public AsyncMcpToolCallback(McpAsyncClient mcpClient, Tool tool) {
this(mcpClient, tool, McpToolUtils.prefixedToolName(mcpClient.getClientInfo().name(), tool.name()),
ToolContextToMcpMetaConverter.defaultConverter());
this(mcpClient, tool, McpToolUtils.prefixedToolName(mcpClient.getClientInfo().name(),
mcpClient.getClientInfo().title(), tool.name()), ToolContextToMcpMetaConverter.defaultConverter());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public interface McpToolNamePrefixGenerator {
*/
static McpToolNamePrefixGenerator defaultGenerator() {
return (mcpConnectionIfo, tool) -> McpToolUtils.prefixedToolName(mcpConnectionIfo.clientInfo().name(),
tool.name());
mcpConnectionIfo.clientInfo().title(), tool.name());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.Stream;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
Expand Down Expand Up @@ -73,28 +74,55 @@ public final class McpToolUtils {
private McpToolUtils() {
}

public static String prefixedToolName(String prefix, String toolName) {
public static String prefixedToolName(String prefix, String title, String toolName) {

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

String input = prefix + "_" + toolName;
String input = shorten(format(prefix));
if (!StringUtils.isEmpty(title)) {
input = input + "_" + format(title); // Do not shorten the title.
}

input = input + "_" + format(toolName);

// If the string is longer than 64 characters, keep the last 64 characters
if (input.length() > 64) {
input = input.substring(input.length() - 64);
}

return input;
}

public static String prefixedToolName(String prefix, String toolName) {
return prefixedToolName(prefix, null, toolName);
}

private static String format(String input) {
// Replace any character that isn't alphanumeric, underscore, or hyphen with
// concatenation. Support Han script + CJK blocks for complete Chinese character
// coverage
String formatted = input
.replaceAll("[^\\p{IsHan}\\p{InCJK_Unified_Ideographs}\\p{InCJK_Compatibility_Ideographs}a-zA-Z0-9_-]", "");

formatted = formatted.replaceAll("-", "_");
return formatted.replaceAll("-", "_");
}

// If the string is longer than 64 characters, keep the last 64 characters
if (formatted.length() > 64) {
formatted = formatted.substring(formatted.length() - 64);
/**
* Shortens a string by taking the first letter of each word separated by underscores
* @param input String in format "Word1_Word2_Word3_..."
* @return Shortened string with first letters
*/
private static String shorten(String input) {
if (input == null || input.isEmpty()) {
return "";
}

return formatted;
return Stream.of(input.toLowerCase().split("_"))
.filter(word -> !word.isEmpty())
.map(word -> String.valueOf(word.charAt(0)))
.collect(java.util.stream.Collectors.joining("_"));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ public class SyncMcpToolCallback implements ToolCallback {
*/
@Deprecated
public SyncMcpToolCallback(McpSyncClient mcpClient, Tool tool) {
this(mcpClient, tool, McpToolUtils.prefixedToolName(mcpClient.getClientInfo().name(), tool.name()),
ToolContextToMcpMetaConverter.defaultConverter());
this(mcpClient, tool, McpToolUtils.prefixedToolName(mcpClient.getClientInfo().name(),
mcpClient.getClientInfo().title(), tool.name()), ToolContextToMcpMetaConverter.defaultConverter());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ void toolFilterShouldFilterToolsByNameWhenConfigured() {
var callbacks = provider.getToolCallbacks();

assertThat(callbacks).hasSize(2);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("testClient_tool2");
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("testClient_tool3");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("t_tool2");
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("t_tool3");
}

@Test
Expand Down Expand Up @@ -266,7 +266,7 @@ void toolFilterShouldFilterToolsByClientWhenConfigured() {
var callbacks = provider.getToolCallbacks();

assertThat(callbacks).hasSize(1);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("testClient1_tool1");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("t_tool1");
}

@Test
Expand Down Expand Up @@ -299,7 +299,7 @@ void toolFilterShouldCombineClientAndToolCriteriaWhenConfigured() {
var callbacks = provider.getToolCallbacks();

assertThat(callbacks).hasSize(1);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("weather_service_weather");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("w_s_weather");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void builderShouldCreateInstanceWithRequiredFields() {
assertThat(callback).isNotNull();
assertThat(callback.getOriginalToolName()).isEqualTo("test-tool");
assertThat(callback.getToolDefinition()).isNotNull();
assertThat(callback.getToolDefinition().name()).isEqualTo("test_client_test_tool");
assertThat(callback.getToolDefinition().name()).isEqualTo("t_c_test_tool");
assertThat(callback.getToolDefinition().description()).isEqualTo("Test tool description");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void builderShouldCreateInstanceWithSingleClient() {
assertThat(provider).isNotNull();
ToolCallback[] callbacks = provider.getToolCallbacks();
assertThat(callbacks).hasSize(1);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("test_client_test_tool");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("t_c_test_tool");
}

@Test
Expand All @@ -64,8 +64,8 @@ void builderShouldCreateInstanceWithMultipleClients() {
assertThat(provider).isNotNull();
ToolCallback[] callbacks = provider.getToolCallbacks();
assertThat(callbacks).hasSize(2);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("client1_tool1");
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("client2_tool2");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("c_tool1");
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("c_tool2");
}

@Test
Expand Down Expand Up @@ -111,7 +111,7 @@ void builderShouldCreateInstanceWithCustomToolFilter() {
assertThat(provider).isNotNull();
ToolCallback[] callbacks = provider.getToolCallbacks();
assertThat(callbacks).hasSize(1);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("client_filtered_tool");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("c_filtered_tool");
}

@Test
Expand Down Expand Up @@ -230,8 +230,8 @@ void builderShouldReplaceClientsWhenSettingNewList() {
assertThat(provider).isNotNull();
ToolCallback[] callbacks = provider.getToolCallbacks();
assertThat(callbacks).hasSize(2);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("client2_tool2");
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("client3_tool3");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("c_tool2");
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("c_tool3");
}

private McpSyncClient createMockClient(String clientName, String toolName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ void getSameNameToolsButDifferentClientInfoNamesShouldProduceDifferentToolCallba
when(listToolsResult1.tools()).thenReturn(List.of(tool1));
when(mcpClient1.listTools()).thenReturn(listToolsResult1);

var clientInfo1 = new Implementation("testClient1", "1.0.0");
var clientInfo1 = new Implementation("FirstClient", "1.0.0");
when(mcpClient1.getClientInfo()).thenReturn(clientInfo1);

McpSyncClient mcpClient2 = mock(McpSyncClient.class);
ListToolsResult listToolsResult2 = mock(ListToolsResult.class);
when(listToolsResult2.tools()).thenReturn(List.of(tool2));
when(mcpClient2.listTools()).thenReturn(listToolsResult2);

var clientInfo2 = new Implementation("testClient2", "1.0.0");
var clientInfo2 = new Implementation("SecondClient", "1.0.0");
when(mcpClient2.getClientInfo()).thenReturn(clientInfo2);

SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
Expand Down Expand Up @@ -213,8 +213,8 @@ void toolFilterShouldFilterToolsByNameWhenConfigured() {
var callbacks = provider.getToolCallbacks();

assertThat(callbacks).hasSize(2);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("testClient_tool2");
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("testClient_tool3");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("t_tool2");
assertThat(callbacks[1].getToolDefinition().name()).isEqualTo("t_tool3");
}

@Test
Expand Down Expand Up @@ -253,7 +253,7 @@ void toolFilterShouldFilterToolsByClientWhenConfigured() {
var callbacks = provider.getToolCallbacks();

assertThat(callbacks).hasSize(1);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("testClient1_tool1");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("t_tool1");
}

@Test
Expand Down Expand Up @@ -286,7 +286,7 @@ void toolFilterShouldCombineClientAndToolCriteriaWhenConfigured() {
var callbacks = provider.getToolCallbacks();

assertThat(callbacks).hasSize(1);
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("weather_service_weather");
assertThat(callbacks[0].getToolDefinition().name()).isEqualTo("w_s_weather");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ void getToolDefinitionShouldReturnCorrectDefinition() {
SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
.mcpClient(this.mcpClient)
.tool(this.tool)
.prefixedToolName(McpToolUtils.prefixedToolName(clientInfo.name(), this.tool.name()))
.prefixedToolName(McpToolUtils.prefixedToolName(clientInfo.name(), clientInfo.title(), this.tool.name()))
.toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
.build();

var toolDefinition = callback.getToolDefinition();

assertThat(toolDefinition.name()).isEqualTo(clientInfo.name() + "_testTool");
assertThat(toolDefinition.name()).isEqualTo("t_testTool");
assertThat(toolDefinition.description()).isEqualTo("Test tool description");
}

Expand All @@ -89,7 +89,7 @@ void callShouldHandleJsonInputAndOutput() {
SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
.mcpClient(this.mcpClient)
.tool(this.tool)
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", this.tool.name()))
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", "server1", this.tool.name()))
.toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
.build();

Expand All @@ -107,7 +107,7 @@ void callShouldHandleToolContext() {
SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
.mcpClient(this.mcpClient)
.tool(this.tool)
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", this.tool.name()))
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", "server1", this.tool.name()))
.toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
.build();

Expand Down Expand Up @@ -145,7 +145,7 @@ void callShouldHandleNullOrEmptyInput() {
@Test
void callShouldThrowOnError() {
when(this.tool.name()).thenReturn("testTool");
var clientInfo = new Implementation("testClient", "1.0.0");
var clientInfo = new Implementation("testClient", "server1", "1.0.0");
CallToolResult callResult = mock(CallToolResult.class);
when(callResult.isError()).thenReturn(true);
when(callResult.content()).thenReturn(List.of(new McpSchema.TextContent("Some error data")));
Expand All @@ -154,7 +154,7 @@ void callShouldThrowOnError() {
SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
.mcpClient(this.mcpClient)
.tool(this.tool)
.prefixedToolName(McpToolUtils.prefixedToolName(clientInfo.name(), this.tool.name()))
.prefixedToolName(McpToolUtils.prefixedToolName(clientInfo.name(), clientInfo.title(), this.tool.name()))
.toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
.build();

Expand All @@ -167,13 +167,13 @@ void callShouldThrowOnError() {
@Test
void callShouldWrapExceptions() {
when(this.tool.name()).thenReturn("testTool");
var clientInfo = new Implementation("testClient", "1.0.0");
var clientInfo = new Implementation("testClient", "server1", "1.0.0");
when(this.mcpClient.callTool(any(CallToolRequest.class))).thenThrow(new RuntimeException("Testing tool error"));

SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
.mcpClient(this.mcpClient)
.tool(this.tool)
.prefixedToolName(McpToolUtils.prefixedToolName(clientInfo.name(), this.tool.name()))
.prefixedToolName(McpToolUtils.prefixedToolName(clientInfo.name(), clientInfo.title(), this.tool.name()))
.toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
.build();

Expand All @@ -193,7 +193,7 @@ void callShouldHandleEmptyResponse() {
SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
.mcpClient(this.mcpClient)
.tool(this.tool)
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", this.tool.name()))
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", "server1", this.tool.name()))
.toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
.build();

Expand All @@ -214,7 +214,7 @@ void callShouldHandleMultipleContentItems() {
SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
.mcpClient(this.mcpClient)
.tool(this.tool)
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", this.tool.name()))
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", "server1", this.tool.name()))
.toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
.build();

Expand All @@ -235,7 +235,7 @@ void callShouldHandleNonTextContent() {
SyncMcpToolCallback callback = SyncMcpToolCallback.builder()
.mcpClient(this.mcpClient)
.tool(this.tool)
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", this.tool.name()))
.prefixedToolName(McpToolUtils.prefixedToolName("testClient", "server1", this.tool.name()))
.toolContextToMcpMetaConverter(ToolContextToMcpMetaConverter.defaultConverter())
.build();

Expand Down
Loading