You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-commits@axis.apache.org by ro...@apache.org on 2026/04/10 19:05:34 UTC
(axis-axis2-java-core) branch master updated (55c7e1e4d8 -> 1fcb977114)
This is an automated email from the ASF dual-hosted git repository.
robertlazarski pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
from 55c7e1e4d8 Update transport note: both C and Java now use HTTPS/HTTP2
new 9d86d49aee Switch MCP bridge from java.net.http to Apache HttpComponents 5
new 1fcb977114 Apply Gemini review: close HC5 HttpClient on shutdown
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
modules/mcp-bridge/pom.xml | 10 +++
.../org/apache/axis2/mcp/bridge/McpBridgeMain.java | 10 +--
.../apache/axis2/mcp/bridge/McpStdioServer.java | 82 +++++++++++++++-------
.../org/apache/axis2/mcp/bridge/ToolRegistry.java | 79 ++++++++++++++-------
4 files changed, 125 insertions(+), 56 deletions(-)
(axis-axis2-java-core) 02/02: Apply Gemini review: close HC5 HttpClient on shutdown
Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
commit 1fcb977114da64bcf231e7f0093b5208c5c49e6f
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Fri Apr 10 09:02:59 2026 -1000
Apply Gemini review: close HC5 HttpClient on shutdown
Both ToolRegistry and McpStdioServer now implement Closeable with
close() calling httpClient.close(). McpBridgeMain uses try-with-
resources to ensure connection pools are released when stdin closes
(Claude Desktop terminates the MCP subprocess).
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
.../main/java/org/apache/axis2/mcp/bridge/McpBridgeMain.java | 10 ++++++----
.../main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java | 7 ++++++-
.../main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java | 7 ++++++-
3 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpBridgeMain.java b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpBridgeMain.java
index f78d7103cc..02a44385ad 100644
--- a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpBridgeMain.java
+++ b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpBridgeMain.java
@@ -88,11 +88,13 @@ public class McpBridgeMain {
keystorePath, keystorePass.toCharArray(),
truststorePath, truststorePass.toCharArray());
- ToolRegistry registry = new ToolRegistry(baseUrl, mapper, sslContext);
- registry.load();
+ try (ToolRegistry registry = new ToolRegistry(baseUrl, mapper, sslContext)) {
+ registry.load();
- McpStdioServer server = new McpStdioServer(baseUrl, registry, mapper, sslContext);
- server.run();
+ try (McpStdioServer server = new McpStdioServer(baseUrl, registry, mapper, sslContext)) {
+ server.run();
+ }
+ }
} catch (Exception e) {
System.err.println("[axis2-mcp-bridge] Fatal: " + e.getMessage());
diff --git a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java
index 2d2723d7de..e94d93bbe9 100644
--- a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java
+++ b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java
@@ -57,7 +57,7 @@ import java.nio.charset.StandardCharsets;
* <p>Notifications (messages without an {@code id} field) are silently consumed
* with no response, as required by JSON-RPC 2.0.
*/
-public class McpStdioServer {
+public class McpStdioServer implements java.io.Closeable {
private static final String PROTOCOL_VERSION = "2024-11-05";
private static final String SERVER_NAME = "axis2-mcp-bridge";
@@ -281,4 +281,9 @@ public class McpStdioServer {
out.println(json);
out.flush();
}
+
+ @Override
+ public void close() throws IOException {
+ httpClient.close();
+ }
}
diff --git a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java
index 796116be13..e69f7c07b3 100644
--- a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java
+++ b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java
@@ -47,7 +47,7 @@ import java.util.Map;
* It is fetched once at startup; restart the bridge to pick up newly deployed
* services.
*/
-public class ToolRegistry {
+public class ToolRegistry implements java.io.Closeable {
private final String baseUrl;
private final ObjectMapper mapper;
@@ -140,4 +140,9 @@ public class ToolRegistry {
public List<McpTool> getTools() { return tools; }
public McpTool getTool(String name) { return toolMap.get(name); }
+
+ @Override
+ public void close() throws IOException {
+ httpClient.close();
+ }
}
(axis-axis2-java-core) 01/02: Switch MCP bridge from java.net.http to Apache HttpComponents 5
Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
commit 9d86d49aee50a85e3acfd8dd49ee2562b3ba90f6
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Fri Apr 10 08:30:45 2026 -1000
Switch MCP bridge from java.net.http to Apache HttpComponents 5
Replace JDK HttpClient with HC5 (httpclient5 5.6) to unify with the
rest of Axis2/Java which uses HC5 for all HTTP transports. Benefits:
- One HTTP stack across all Axis2/Java modules
- Built-in connection pooling (PoolingHttpClientConnectionManager)
- ToolRegistry: 10 connections (catalog fetch, startup-only)
- McpStdioServer: 20 connections (tool calls, reuses connections)
- Fine-grained timeout control (connect + response separately)
- Same debugging/wire logging patterns as HTTPSenderImpl
- mTLS: SSLContext handling unchanged (both accept javax.net.ssl)
Files changed:
- pom.xml: add httpclient5 + httpcore5 dependencies
- ToolRegistry.java: HttpGet + execute() with response handler
- McpStdioServer.java: HttpPost + StringEntity + execute()
Removed InterruptedException from callAxis2/buildToolsCallResult
signatures (HC5 classic API doesn't throw it).
Tested: mvn clean package -DskipTests succeeds. Exe JAR: 4.6 MB.
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
modules/mcp-bridge/pom.xml | 10 +++
.../apache/axis2/mcp/bridge/McpStdioServer.java | 75 ++++++++++++++--------
.../org/apache/axis2/mcp/bridge/ToolRegistry.java | 72 ++++++++++++++-------
3 files changed, 107 insertions(+), 50 deletions(-)
diff --git a/modules/mcp-bridge/pom.xml b/modules/mcp-bridge/pom.xml
index ef19382743..666def4f1f 100644
--- a/modules/mcp-bridge/pom.xml
+++ b/modules/mcp-bridge/pom.xml
@@ -54,6 +54,16 @@
<version>${jackson.version}</version>
</dependency>
+ <!-- HTTP client — same stack as all Axis2/Java transports -->
+ <dependency>
+ <groupId>org.apache.httpcomponents.client5</groupId>
+ <artifactId>httpclient5</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents.core5</groupId>
+ <artifactId>httpcore5</artifactId>
+ </dependency>
+
<!-- Test -->
<dependency>
<groupId>junit</groupId>
diff --git a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java
index 715f0f469d..2d2723d7de 100644
--- a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java
+++ b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/McpStdioServer.java
@@ -23,17 +23,24 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.util.Timeout;
+
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
-import java.time.Duration;
/**
* MCP stdio server: reads JSON-RPC 2.0 requests from stdin, writes responses
@@ -59,23 +66,44 @@ public class McpStdioServer {
private final String baseUrl;
private final ToolRegistry registry;
private final ObjectMapper mapper;
- private final HttpClient httpClient;
+ private final CloseableHttpClient httpClient;
private final PrintStream out;
public McpStdioServer(String baseUrl, ToolRegistry registry, ObjectMapper mapper, SSLContext sslContext) {
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
this.registry = registry;
this.mapper = mapper;
- HttpClient.Builder builder = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(10));
- if (sslContext != null) {
- builder.sslContext(sslContext);
- }
- this.httpClient = builder.build();
+ this.httpClient = buildHttpClient(sslContext);
// stdout must be raw bytes in UTF-8; replace the default PrintStream
this.out = new PrintStream(System.out, true, StandardCharsets.UTF_8);
}
+ private static CloseableHttpClient buildHttpClient(SSLContext sslContext) {
+ HttpClientConnectionManager connManager;
+ if (sslContext != null) {
+ connManager = PoolingHttpClientConnectionManagerBuilder.create()
+ .setSSLSocketFactory(SSLConnectionSocketFactoryBuilder.create()
+ .setSslContext(sslContext)
+ .build())
+ .setMaxConnTotal(20)
+ .setMaxConnPerRoute(20)
+ .build();
+ } else {
+ connManager = PoolingHttpClientConnectionManagerBuilder.create()
+ .setMaxConnTotal(20)
+ .setMaxConnPerRoute(20)
+ .build();
+ }
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(Timeout.ofSeconds(10))
+ .setResponseTimeout(Timeout.ofSeconds(60))
+ .build();
+ return HttpClients.custom()
+ .setConnectionManager(connManager)
+ .setDefaultRequestConfig(requestConfig)
+ .build();
+ }
+
/**
* Blocking read loop. Returns when stdin is closed (client disconnected).
*/
@@ -136,9 +164,6 @@ public class McpStdioServer {
} catch (IllegalArgumentException e) {
// Invalid params: unknown tool name, missing required param, etc.
writeError(id, -32602, "Invalid params: " + e.getMessage());
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- writeError(id, -32000, "Server error during tool call: " + e.getMessage());
} catch (IOException e) {
writeError(id, -32000, "Server error during tool call: " + e.getMessage());
} catch (Exception e) {
@@ -183,7 +208,7 @@ public class McpStdioServer {
// ── tools/call ──────────────────────────────────────────────────────────
- private ObjectNode buildToolsCallResult(JsonNode params) throws IOException, InterruptedException {
+ private ObjectNode buildToolsCallResult(JsonNode params) throws IOException {
String toolName = params.path("name").asText(null);
if (toolName == null || toolName.isEmpty()) {
throw new IllegalArgumentException("tools/call missing required param 'name'");
@@ -210,7 +235,7 @@ public class McpStdioServer {
* POST to the Axis2 endpoint. Wraps MCP arguments in the Axis2 JSON-RPC
* request envelope: {@code {operationName: [arguments]}}.
*/
- private String callAxis2(McpTool tool, JsonNode arguments) throws IOException, InterruptedException {
+ private String callAxis2(McpTool tool, JsonNode arguments) throws IOException {
String url = baseUrl + tool.getPath();
// Axis2 JSON-RPC envelope: {"operationName": [arguments]}
@@ -222,16 +247,14 @@ public class McpStdioServer {
System.err.println("[axis2-mcp-bridge] Calling: POST " + url);
System.err.println("[axis2-mcp-bridge] Body: " + requestBody);
- HttpRequest httpRequest = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .header("Content-Type", "application/json")
- .timeout(Duration.ofSeconds(60))
- .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
- .build();
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
- HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
- System.err.println("[axis2-mcp-bridge] Response: HTTP " + response.statusCode());
- return response.body();
+ return httpClient.execute(httpPost, response -> {
+ int status = response.getCode();
+ System.err.println("[axis2-mcp-bridge] Response: HTTP " + status);
+ return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+ });
}
// ── JSON-RPC 2.0 response helpers ───────────────────────────────────────
diff --git a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java
index 4f2203a0b0..796116be13 100644
--- a/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java
+++ b/modules/mcp-bridge/src/main/java/org/apache/axis2/mcp/bridge/ToolRegistry.java
@@ -21,13 +21,18 @@ package org.apache.axis2.mcp.bridge;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.util.Timeout;
+
import javax.net.ssl.SSLContext;
import java.io.IOException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -46,7 +51,7 @@ public class ToolRegistry {
private final String baseUrl;
private final ObjectMapper mapper;
- private final HttpClient httpClient;
+ private final CloseableHttpClient httpClient;
private List<McpTool> tools = Collections.emptyList();
private Map<String, McpTool> toolMap = Collections.emptyMap();
@@ -54,37 +59,56 @@ public class ToolRegistry {
public ToolRegistry(String baseUrl, ObjectMapper mapper, SSLContext sslContext) {
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
this.mapper = mapper;
- HttpClient.Builder builder = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(10));
+ this.httpClient = buildHttpClient(sslContext);
+ }
+
+ private static CloseableHttpClient buildHttpClient(SSLContext sslContext) {
+ HttpClientConnectionManager connManager;
if (sslContext != null) {
- builder.sslContext(sslContext);
+ connManager = PoolingHttpClientConnectionManagerBuilder.create()
+ .setSSLSocketFactory(SSLConnectionSocketFactoryBuilder.create()
+ .setSslContext(sslContext)
+ .build())
+ .setMaxConnTotal(10)
+ .setMaxConnPerRoute(10)
+ .build();
+ } else {
+ connManager = PoolingHttpClientConnectionManagerBuilder.create()
+ .setMaxConnTotal(10)
+ .setMaxConnPerRoute(10)
+ .build();
}
- this.httpClient = builder.build();
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(Timeout.ofSeconds(10))
+ .setResponseTimeout(Timeout.ofSeconds(15))
+ .build();
+ return HttpClients.custom()
+ .setConnectionManager(connManager)
+ .setDefaultRequestConfig(requestConfig)
+ .build();
}
/**
* Fetches {@code /openapi-mcp.json} and builds the tool registry.
* Logs to stderr; does not throw on partial failure (empty catalog is valid).
*/
- public void load() throws IOException, InterruptedException {
+ public void load() throws IOException {
String catalogUrl = baseUrl + "/openapi-mcp.json";
System.err.println("[axis2-mcp-bridge] Loading tool catalog from: " + catalogUrl);
- HttpRequest request = HttpRequest.newBuilder()
- .uri(URI.create(catalogUrl))
- .header("Accept", "application/json")
- .timeout(Duration.ofSeconds(15))
- .GET()
- .build();
-
- HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+ HttpGet httpGet = new HttpGet(catalogUrl);
+ httpGet.setHeader("Accept", "application/json");
- if (response.statusCode() != 200) {
- throw new IOException("Tool catalog fetch failed: HTTP " + response.statusCode()
- + " from " + catalogUrl);
- }
+ String responseBody = httpClient.execute(httpGet, response -> {
+ int status = response.getCode();
+ if (status != 200) {
+ throw new IOException("Tool catalog fetch failed: HTTP " + status
+ + " from " + catalogUrl);
+ }
+ return EntityUtils.toString(response.getEntity());
+ });
- JsonNode root = mapper.readTree(response.body());
+ JsonNode root = mapper.readTree(responseBody);
JsonNode toolsNode = root.path("tools");
if (!toolsNode.isArray()) {