This is an automated email from the ASF dual-hosted git repository.
Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 32711165126d CAMEL-24353: camel-mcp-server - selectable target server
type for VertxMcpServerEngine
32711165126d is described below
commit 32711165126dcab5b2336e58299dd1415865ceb8
Author: croway <[email protected]>
AuthorDate: Tue Aug 4 17:29:13 2026 +0200
CAMEL-24353: camel-mcp-server - selectable target server type for
VertxMcpServerEngine
The engine hardwired its router lookup to the main HTTP server. The new
targetServerType property (default 'server') lets it register the MCP
endpoint on the management HTTP server router instead, which
ManagementHttpServer already binds in the registry with
SERVER_TYPE_MANAGEMENT. An explicit management target never falls back
to the public server; the bare-VertxPlatformHttpServer single-router
fallback is kept for the default target only.
Unblocks CAMEL-23853: the JBang dev-tools MCP can drive this SDK-backed
engine directly (tools built from its ToolRegistry) instead of
hand-rolling the streamable HTTP protocol.
Co-Authored-By: Claude Fable 5 <[email protected]>
---
.../mcp/server/vertx/VertxMcpServerEngine.java | 39 ++++-
.../VertxMcpServerEngineTargetServerTypeTest.java | 174 +++++++++++++++++++++
2 files changed, 206 insertions(+), 7 deletions(-)
diff --git
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
index 2dd0e8e0d20c..2f9b21b2d548 100644
---
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
+++
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngine.java
@@ -40,8 +40,10 @@ import org.slf4j.LoggerFactory;
/**
* {@link McpServerEngine} for Camel Main / JBang: serves MCP streamable HTTP
through the Vert.x platform HTTP router
- * using the official MCP Java SDK. The MCP endpoint is registered on the main
HTTP server's router, so it serves on the
- * main server port and inherits its lifecycle, authentication and CORS
configuration.
+ * using the official MCP Java SDK. By default the MCP endpoint is registered
on the main HTTP server's router, so it
+ * serves on the main server port and inherits its lifecycle, authentication
and CORS configuration. Set
+ * {@link #setTargetServerType(String)} to {@link
VertxPlatformHttpRouter#SERVER_TYPE_MANAGEMENT} to serve on the
+ * management HTTP server router instead (e.g. for dev/diagnostics tools that
must not be publicly exposed).
*/
@JdkService(McpServerConstants.MCP_SERVER_ENGINE_FACTORY)
public class VertxMcpServerEngine extends ServiceSupport implements
McpServerEngine {
@@ -62,6 +64,7 @@ public class VertxMcpServerEngine extends ServiceSupport
implements McpServerEng
private McpJsonMapper jsonMapper;
private VertxMcpStreamableServerTransportProvider transport;
private McpSyncServer server;
+ private String targetServerType =
VertxPlatformHttpRouter.SERVER_TYPE_SERVER;
@Override
public CamelContext getCamelContext() {
@@ -78,6 +81,19 @@ public class VertxMcpServerEngine extends ServiceSupport
implements McpServerEng
this.info = info;
}
+ public String getTargetServerType() {
+ return targetServerType;
+ }
+
+ /**
+ * The server type of the {@link VertxPlatformHttpRouter} to register the
MCP endpoint on:
+ * {@link VertxPlatformHttpRouter#SERVER_TYPE_SERVER} (default) for the
main HTTP server, or
+ * {@link VertxPlatformHttpRouter#SERVER_TYPE_MANAGEMENT} for the
management HTTP server.
+ */
+ public void setTargetServerType(String targetServerType) {
+ this.targetServerType = targetServerType;
+ }
+
@Override
public boolean consumesServingConfiguration() {
return true;
@@ -154,16 +170,25 @@ public class VertxMcpServerEngine extends ServiceSupport
implements McpServerEng
}
private VertxPlatformHttpRouter lookupRouter() {
+ boolean mainTarget =
VertxPlatformHttpRouter.SERVER_TYPE_SERVER.equals(targetServerType);
Set<VertxPlatformHttpRouter> routers =
camelContext.getRegistry().findByType(VertxPlatformHttpRouter.class);
VertxPlatformHttpRouter router = routers.stream()
- .filter(VertxPlatformHttpRouter::isMainServer)
+ .filter(r -> targetServerType.equals(r.getServerType()))
.findFirst()
- .orElseGet(() -> routers.size() == 1 ?
routers.iterator().next() : null);
+ // a bare VertxPlatformHttpServer may carry no server type;
only the default target may fall
+ // back to it — an explicit management target must never
silently use the public server
+ .orElseGet(() -> mainTarget && routers.size() == 1 ?
routers.iterator().next() : null);
if (router == null) {
+ if (mainTarget) {
+ throw new IllegalStateException(
+ "The MCP server requires the Vert.x platform HTTP
server. Enable the Camel main HTTP server "
+ + "(camel.server.enabled=true
with camel-platform-http-main on the classpath) "
+ + "or add a
VertxPlatformHttpServer service to the CamelContext.");
+ }
throw new IllegalStateException(
- "The MCP server requires the Vert.x platform HTTP server.
Enable the Camel main HTTP server "
- + "(camel.server.enabled=true with
camel-platform-http-main on the classpath) "
- + "or add a
VertxPlatformHttpServer service to the CamelContext.");
+ "The MCP server requires the Vert.x platform HTTP server
with server type '" + targetServerType
+ + "' but no such router was found
in the registry. Enable the Camel management HTTP server "
+ + "(camel.management.enabled=true
with camel-platform-http-main on the classpath).");
}
return router;
}
diff --git
a/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngineTargetServerTypeTest.java
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngineTargetServerTypeTest.java
new file mode 100644
index 000000000000..e831a1f44324
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server/src/test/java/org/apache/camel/component/mcp/server/vertx/VertxMcpServerEngineTargetServerTypeTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.mcp.server.vertx;
+
+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.Map;
+
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.client.McpSyncClient;
+import
io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.ai.tool.AiToolParameterHelper.ParameterDef;
+import org.apache.camel.component.mcp.server.McpServerInfo;
+import org.apache.camel.component.mcp.server.McpServerTool;
+import org.apache.camel.component.mcp.server.McpToolCallHandler;
+import org.apache.camel.component.mcp.server.McpToolCallResult;
+import org.apache.camel.component.platform.http.main.MainHttpServer;
+import org.apache.camel.component.platform.http.main.ManagementHttpServer;
+import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpRouter;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.test.AvailablePortFinder;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Verifies the selectable target server type (CAMEL-24353): the engine can
serve the MCP endpoint on the management
+ * HTTP server router instead of the main one, and it is driven directly with
hand-built tools — no bridge and no
+ * ai-tool routes — the way an alternative tool source such as the JBang dev
tools (CAMEL-23853) uses it.
+ */
+class VertxMcpServerEngineTargetServerTypeTest {
+
+ @Test
+ void testEngineServesOnManagementServerOnly() throws Exception {
+ int mainPort = AvailablePortFinder.getNextAvailable();
+ int managementPort = AvailablePortFinder.getNextAvailable();
+
+ CamelContext camelContext = new DefaultCamelContext();
+ ManagementHttpServer management = new ManagementHttpServer();
+ VertxMcpServerEngine engine = new VertxMcpServerEngine();
+ McpSyncClient client = null;
+ try {
+ MainHttpServer main = new MainHttpServer();
+ main.setCamelContext(camelContext);
+ main.setHost("0.0.0.0");
+ main.setPort(mainPort);
+ camelContext.addService(main);
+ camelContext.start();
+
+ management.setCamelContext(camelContext);
+ management.setHost("0.0.0.0");
+ management.setPort(managementPort);
+ management.setPath("/");
+ management.start();
+
+ engine.setCamelContext(camelContext);
+
engine.setTargetServerType(VertxPlatformHttpRouter.SERVER_TYPE_MANAGEMENT);
+ engine.initialize(new McpServerInfo("dev-tools", "1.0", "/mcp"));
+ engine.start();
+ engine.toolAdded(tool("current_pid", "The pid of this process",
+ arguments -> new McpToolCallResult("pid-42", false)));
+
+ client = McpClient.sync(
+
HttpClientStreamableHttpTransport.builder("http://localhost:" +
managementPort).build())
+ .requestTimeout(Duration.ofSeconds(10))
+ .initializationTimeout(Duration.ofSeconds(10))
+ .build();
+ McpSchema.InitializeResult init = client.initialize();
+ assertThat(init.serverInfo().name()).isEqualTo("dev-tools");
+
+ assertThat(client.listTools().tools())
+ .extracting(McpSchema.Tool::name)
+ .contains("current_pid");
+
+ McpSchema.CallToolResult result = client.callTool(new
McpSchema.CallToolRequest("current_pid", Map.of()));
+ assertThat(result.isError()).isNotEqualTo(Boolean.TRUE);
+ assertThat(result.content().toString()).contains("pid-42");
+
+ // the main server must not serve the management-targeted MCP
endpoint
+ HttpResponse<String> onMainServer =
HttpClient.newHttpClient().send(
+ HttpRequest.newBuilder(URI.create("http://localhost:" +
mainPort + "/mcp"))
+ .header("Content-Type", "application/json")
+ .header("Accept", "application/json,
text/event-stream")
+ .POST(HttpRequest.BodyPublishers.ofString("{}"))
+ .build(),
+ HttpResponse.BodyHandlers.ofString());
+ assertThat(onMainServer.statusCode()).isEqualTo(404);
+ } finally {
+ if (client != null) {
+ client.closeGracefully();
+ }
+ engine.stop();
+ management.stop();
+ camelContext.stop();
+ }
+ }
+
+ @Test
+ void testManagementTargetWithoutManagementServerFailsFast() throws
Exception {
+ int mainPort = AvailablePortFinder.getNextAvailable();
+
+ CamelContext camelContext = new DefaultCamelContext();
+ VertxMcpServerEngine engine = new VertxMcpServerEngine();
+ try {
+ MainHttpServer main = new MainHttpServer();
+ main.setCamelContext(camelContext);
+ main.setHost("0.0.0.0");
+ main.setPort(mainPort);
+ camelContext.addService(main);
+ camelContext.start();
+
+ engine.setCamelContext(camelContext);
+
engine.setTargetServerType(VertxPlatformHttpRouter.SERVER_TYPE_MANAGEMENT);
+ engine.initialize(new McpServerInfo("dev-tools", "1.0", "/mcp"));
+
+ // the main server router is present but an explicit management
target must never fall back to it
+ assertThatThrownBy(engine::start)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("management");
+ } finally {
+ engine.stop();
+ camelContext.stop();
+ }
+ }
+
+ private static McpServerTool tool(String name, String description,
McpToolCallHandler handler) {
+ return new McpServerTool() {
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String description() {
+ return description;
+ }
+
+ @Override
+ public String inputSchemaJson() {
+ return null;
+ }
+
+ @Override
+ public Map<String, ParameterDef> parameters() {
+ return Map.of();
+ }
+
+ @Override
+ public McpToolCallHandler handler() {
+ return handler;
+ }
+ };
+ }
+}