Skip to content

Commit 8fd9903

Browse files
rameshreddy-adutlaCopilotKehrlann
committed
Fix UTF-8 encoding for non-ASCII tool names in HTTP client transports
Both HttpClientSseClientTransport and HttpClientStreamableHttpTransport set Content-Type to 'application/json' without specifying the charset. While Java's BodyPublishers.ofString() uses UTF-8 by default, the missing charset in the header can cause the server to interpret the request body using a different encoding (e.g., ISO-8859-1), corrupting non-ASCII characters such as Chinese tool names. Explicitly set Content-Type to 'application/json; charset=utf-8' in POST requests on both client transports. Fixes modelcontextprotocol#260 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Daniel Garnier-Moiroux <git@garnier.wf> Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent 5e77762 commit 8fd9903

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ private Mono<HttpResponse<String>> sendHttpPost(final String endpoint, final Str
445445
return Mono.deferContextual(ctx -> {
446446
var builder = this.requestBuilder.copy()
447447
.uri(requestUri)
448-
.header(HttpHeaders.CONTENT_TYPE, "application/json")
448+
.header(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8")
449449
.header(MCP_PROTOCOL_VERSION_HEADER_NAME, MCP_PROTOCOL_VERSION)
450450
.POST(HttpRequest.BodyPublishers.ofString(body));
451451
var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);

mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport {
102102

103103
private static final String APPLICATION_JSON = "application/json";
104104

105+
private static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
106+
105107
private static final String TEXT_EVENT_STREAM = "text/event-stream";
106108

107109
public static int NOT_FOUND = 404;
@@ -477,7 +479,7 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage sentMessage) {
477479

478480
var builder = requestBuilder.uri(uri)
479481
.header(HttpHeaders.ACCEPT, APPLICATION_JSON + ", " + TEXT_EVENT_STREAM)
480-
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
482+
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_UTF8)
481483
.header(HttpHeaders.CACHE_CONTROL, "no-cache")
482484
.header(HttpHeaders.PROTOCOL_VERSION,
483485
ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,

mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import io.modelcontextprotocol.client.McpClient;
2525
import io.modelcontextprotocol.common.McpTransportContext;
26+
import io.modelcontextprotocol.json.McpJsonDefaults;
2627
import io.modelcontextprotocol.server.McpServer;
2728
import io.modelcontextprotocol.server.McpServerFeatures;
2829
import io.modelcontextprotocol.server.McpSyncServer;
@@ -47,6 +48,7 @@
4748
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
4849
import io.modelcontextprotocol.spec.McpSchema.TextContent;
4950
import io.modelcontextprotocol.spec.McpSchema.Tool;
51+
import io.modelcontextprotocol.util.McpJsonMapperUtils;
5052
import io.modelcontextprotocol.util.Utils;
5153
import net.javacrumbs.jsonunit.core.Option;
5254
import org.junit.jupiter.params.ParameterizedTest;
@@ -914,6 +916,62 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) {
914916
}
915917
}
916918

919+
@ParameterizedTest(name = "{0} : {displayName} ")
920+
@MethodSource("clientsForTesting")
921+
void testToolWithNonAsciiCharacters(String clientType) {
922+
var clientBuilder = clientBuilders.get(clientType);
923+
924+
String inputSchema = """
925+
{
926+
"type": "object",
927+
"properties": {
928+
"username": { "type": "string" }
929+
},
930+
"required": ["username"]
931+
}
932+
""";
933+
934+
McpServerFeatures.SyncToolSpecification nonAsciiTool = McpServerFeatures.SyncToolSpecification.builder()
935+
.tool(Tool.builder()
936+
.name("greeter")
937+
.description("打招呼")
938+
.inputSchema(McpJsonDefaults.getMapper(), inputSchema)
939+
.build())
940+
.callHandler((exchange, request) -> {
941+
String username = (String) request.arguments().get("username");
942+
return McpSchema.CallToolResult.builder()
943+
.addContent(new McpSchema.TextContent("Hello " + username))
944+
.build();
945+
})
946+
.build();
947+
948+
var mcpServer = prepareSyncServerBuilder().capabilities(ServerCapabilities.builder().tools(true).build())
949+
.tools(nonAsciiTool)
950+
.build();
951+
952+
try (var mcpClient = clientBuilder.build()) {
953+
954+
InitializeResult initResult = mcpClient.initialize();
955+
assertThat(initResult).isNotNull();
956+
957+
var tools = mcpClient.listTools().tools();
958+
assertThat(tools).hasSize(1);
959+
assertThat(tools.get(0).name()).isEqualTo("greeter");
960+
assertThat(tools.get(0).description()).isEqualTo("打招呼");
961+
962+
CallToolResult response = mcpClient
963+
.callTool(new McpSchema.CallToolRequest("greeter", Map.of("username", "测试用户")));
964+
965+
assertThat(response).isNotNull();
966+
assertThat(response.isError()).isFalse();
967+
assertThat(response.content()).hasSize(1);
968+
assertThat(((McpSchema.TextContent) response.content().get(0)).text()).isEqualTo("Hello 测试用户");
969+
}
970+
finally {
971+
mcpServer.closeGracefully();
972+
}
973+
}
974+
917975
@ParameterizedTest(name = "{0} : {displayName} ")
918976
@MethodSource("clientsForTesting")
919977
void testToolListChangeHandlingSuccess(String clientType) {

0 commit comments

Comments
 (0)