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;
+            }
+        };
+    }
+}

Reply via email to