This is an automated email from the ASF dual-hosted git repository.
davsclaus 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 ae1a9db5f44e CAMEL-23853: Embed MCP server on camel run/dev and Camel
Main
ae1a9db5f44e is described below
commit ae1a9db5f44e5eb7884242ee2ed1ecaaa190a73c
Author: Omar Atie <[email protected]>
AuthorDate: Thu Aug 6 03:08:42 2026 -0700
CAMEL-23853: Embed MCP server on camel run/dev and Camel Main
Add embedded MCP (Model Context Protocol) server support to Camel Main
and JBang CLI. When enabled via --mcp flag or camel.jbang.mcp property,
the management HTTP server exposes JBang ToolRegistry tools as MCP tools
using VertxMcpServerEngine on a /mcp endpoint bound to 127.0.0.1.
The MCP server is pluggable: camel-mcp-server is downloaded on demand
(not a compile dependency) and JBang tool classes are resolved
reflectively via ClassResolver. Configuration uses
camel.management.mcpEnabled/mcpPath properties with insecure:dev
security annotation.
Closes #25203
Co-authored-by: Cursor Agent <[email protected]>
---
.../org/apache/camel/catalog/docs/main.adoc | 4 +-
.../jbang/camel-jbang-configuration-metadata.json | 1 +
.../main/camel-main-configuration-metadata.json | 2 +
components/camel-ai/camel-mcp-server/pom.xml | 4 +-
.../mcp/server/jbang/JbangDevMcpMainListener.java | 42 ++++
.../mcp/server/jbang/JbangDevMcpServer.java | 253 +++++++++++++++++++++
...entServerConfigurationPropertiesConfigurer.java | 14 ++
.../camel-main-configuration-metadata.json | 2 +
core/camel-main/src/main/docs/main.adoc | 4 +-
...ttpManagementServerConfigurationProperties.java | 39 ++++
.../ROOT/pages/camel-jbang-configuration.adoc | 3 +-
.../pages/jbang-commands/camel-jbang-debug.adoc | 1 +
.../ROOT/pages/jbang-commands/camel-jbang-dev.adoc | 1 +
.../ROOT/pages/jbang-commands/camel-jbang-run.adoc | 1 +
dsl/camel-jbang/camel-jbang-core/pom.xml | 29 +++
.../META-INF/camel-jbang-commands-metadata.json | 6 +-
.../camel-jbang-configuration-metadata.json | 1 +
.../apache/camel/dsl/jbang/core/commands/Run.java | 18 ++
.../dsl/jbang/core/common/CamelJBangConstants.java | 4 +
.../dsl/jbang/core/commands/RunMcpOptionTest.java | 41 ++++
.../core/commands/mcp/JbangDevMcpServerTest.java | 97 ++++++++
.../java/org/apache/camel/main/KameletMain.java | 15 ++
.../camel-main-known-dependencies.properties | 1 +
23 files changed, 575 insertions(+), 8 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
index ffa14c8dfd9d..e3d33c3295b0 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
@@ -230,7 +230,7 @@ The camel.server supports 26 options, which are listed
below.
=== Camel Embedded HTTP management Server (only for standalone; not Spring
Boot or Quarkus) configurations
-The camel.management supports 29 options, which are listed below.
+The camel.management supports 31 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -255,6 +255,8 @@ The camel.management supports 29 options, which are listed
below.
| *camel.management.jwtKeystorePassword* | Password from the keystore used for
JWT tokens validation. | | String
| *camel.management.jwtKeystorePath* | Path to the keystore file used for JWT
tokens validation. | | String
| *camel.management.jwtKeystoreType* | Type of the keystore used for JWT
tokens validation (jks, pkcs12, etc.). | | String
+| *camel.management.mcpEnabled* | Whether to expose dev/diagnostics MCP tools
on this management server (requires camel-mcp-server on the classpath).
Currently honored only when Camel JBang registers JbangDevMcpMainListener (for
example camel run --mcp). For route-based MCP on the main HTTP server in plain
camel-main, configure camel.server.mcpEnabled instead. Not intended for
production use. | false | boolean
+| *camel.management.mcpPath* | HTTP path where the management MCP endpoint is
served. | /mcp | String
| *camel.management.metricsEnabled* | Whether to enable metrics. If enabled
then you can access metrics on context-path: /observe/metrics | false | boolean
| *camel.management.openapiUiEnabled* | Whether to enable Swagger UI for REST
DSL OpenAPI documents. If enabled then you can browse the API at context-path:
/q/openapi. The OpenAPI document is expected at openapiUiSpecPath (by default
/q/openapi.json from REST DSL apiContextPath ). | false | boolean
| *camel.management.openapiUiSpecPath* | Path or URL of the OpenAPI document
loaded by Swagger UI at /q/openapi. | /q/openapi.json | String
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
index f4b422e1b40c..bd1bdf642d53 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
@@ -35,6 +35,7 @@
{ "name": "camel.jbang.maven-settings", "required": false, "description":
"Optional location of Maven settings.xml file to configure servers,
repositories, mirrors, and proxies. If set to false, not even the default
\/.m2\/settings.xml will be used.", "label": "maven", "type": "string",
"javaType": "String", "secret": false },
{ "name": "camel.jbang.maven-settings-security", "required": false,
"description": "Optional location of Maven settings-security.xml file to
decrypt Maven Settings (settings.xml) file", "label": "maven", "type":
"string", "javaType": "String", "secret": false },
{ "name": "camel.jbang.mavenWrapper", "required": false, "description":
"Include Maven Wrapper files in the exported project", "type": "boolean",
"javaType": "boolean", "defaultValue": true, "secret": false },
+ { "name": "camel.jbang.mcp", "required": false, "description": "Embed
dev\/diagnostics MCP tools on the local HTTP management server (\/mcp by
default)", "type": "boolean", "javaType": "boolean", "defaultValue": false,
"secret": false, "security": "insecure:dev" },
{ "name": "camel.jbang.metrics", "required": false, "description":
"Metrics (Micrometer and Prometheus) at \/observe\/metrics on local HTTP server
(port 8080 by default) when running standalone Camel", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false, "deprecated":
true },
{ "name": "camel.jbang.openApi", "required": false, "description": "File
name of open-api spec file (JSON or YAML) to generate routes from the
swagger\/openapi API spec file.", "type": "string", "javaType": "String",
"secret": false },
{ "name": "camel.jbang.openapiUi", "required": false, "description":
"Swagger UI for REST OpenAPI at \/q\/openapi on local HTTP server (port 8080 by
default)", "type": "boolean", "javaType": "boolean", "defaultValue": false,
"secret": false, "security": "insecure:dev" },
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 5630025faa29..3d8ec1317988 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -309,6 +309,8 @@
{ "name": "camel.management.jwtKeystorePassword", "required": false,
"description": "Password from the keystore used for JWT tokens validation.",
"sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": true, "security": "secret"
},
{ "name": "camel.management.jwtKeystorePath", "required": false,
"description": "Path to the keystore file used for JWT tokens validation.",
"sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.management.jwtKeystoreType", "required": false,
"description": "Type of the keystore used for JWT tokens validation (jks,
pkcs12, etc.).", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
+ { "name": "camel.management.mcpEnabled", "required": false, "description":
"Whether to expose dev\/diagnostics MCP tools on this management server
(requires camel-mcp-server on the classpath). Currently honored only when Camel
JBang registers JbangDevMcpMainListener (for example camel run --mcp). For
route-based MCP on the main HTTP server in plain camel-main, configure
camel.server.mcpEnabled instead. Not intended for production use.",
"sourceType": "org.apache.camel.main.HttpManage [...]
+ { "name": "camel.management.mcpPath", "required": false, "description":
"HTTP path where the management MCP endpoint is served.", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "defaultValue": "\/mcp", "secret":
false },
{ "name": "camel.management.metricsEnabled", "required": false,
"description": "Whether to enable metrics. If enabled then you can access
metrics on context-path: \/observe\/metrics", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.management.openapiUiEnabled", "required": false,
"description": "Whether to enable Swagger UI for REST DSL OpenAPI documents. If
enabled then you can browse the API at context-path: \/q\/openapi. The OpenAPI
document is expected at openapiUiSpecPath (by default \/q\/openapi.json from
REST DSL apiContextPath ).", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": false, "secret": [...]
{ "name": "camel.management.openapiUiSpecPath", "required": false,
"description": "Path or URL of the OpenAPI document loaded by Swagger UI at
\/q\/openapi.", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "defaultValue": "\/q\/openapi.json",
"secret": false },
diff --git a/components/camel-ai/camel-mcp-server/pom.xml
b/components/camel-ai/camel-mcp-server/pom.xml
index 2adf333c000d..feb84234815f 100644
--- a/components/camel-ai/camel-mcp-server/pom.xml
+++ b/components/camel-ai/camel-mcp-server/pom.xml
@@ -79,12 +79,12 @@
<!-- integration tests -->
<dependency>
<groupId>org.apache.camel</groupId>
- <artifactId>camel-openai</artifactId>
+ <artifactId>camel-platform-http-main</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
- <artifactId>camel-platform-http-main</artifactId>
+ <artifactId>camel-openai</artifactId>
<scope>test</scope>
</dependency>
<dependency>
diff --git
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/jbang/JbangDevMcpMainListener.java
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/jbang/JbangDevMcpMainListener.java
new file mode 100644
index 000000000000..ad701656d06e
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/jbang/JbangDevMcpMainListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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.jbang;
+
+import org.apache.camel.main.BaseMainSupport;
+import org.apache.camel.main.MainListenerSupport;
+
+/**
+ * Starts {@link JbangDevMcpServer} on the management HTTP server when {@code
camel.management.mcpEnabled} is set.
+ * Loaded at runtime when {@code camel-mcp-server} is on the classpath (e.g.
{@code camel run --mcp}).
+ */
+public class JbangDevMcpMainListener extends MainListenerSupport {
+
+ @Override
+ public void afterConfigure(BaseMainSupport main) {
+ if (!main.configure().httpManagementServer().isMcpEnabled()) {
+ return;
+ }
+ JbangDevMcpServer server = new JbangDevMcpServer();
+ server.setCamelContext(main.getCamelContext());
+ server.setPath(main.configure().httpManagementServer().getMcpPath());
+ try {
+ main.getCamelContext().addService(server, true, true);
+ } catch (Exception e) {
+ throw new IllegalStateException("Unable to register JBang dev MCP
server", e);
+ }
+ }
+}
diff --git
a/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/jbang/JbangDevMcpServer.java
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/jbang/JbangDevMcpServer.java
new file mode 100644
index 000000000000..c67836463ccc
--- /dev/null
+++
b/components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/jbang/JbangDevMcpServer.java
@@ -0,0 +1,253 @@
+/*
+ * 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.jbang;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.component.ai.tool.AiToolParameterHelper;
+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.mcp.server.vertx.VertxMcpServerEngine;
+import org.apache.camel.component.platform.http.vertx.VertxPlatformHttpRouter;
+import org.apache.camel.support.service.ServiceSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Dev/diagnostics MCP server on the management HTTP port, exposing shared
JBang {@code ToolRegistry} tools through
+ * {@link VertxMcpServerEngine}. JBang classes are resolved reflectively so
{@code camel-jbang-core} is not required at
+ * compile time.
+ */
+public class JbangDevMcpServer extends ServiceSupport implements
CamelContextAware {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(JbangDevMcpServer.class);
+ private static final String SERVER_NAME = "camel-jbang-dev-tools";
+ private static final String TOOL_REGISTRY =
"org.apache.camel.dsl.jbang.core.commands.ai.ToolRegistry";
+ private static final String TOOL_CONTEXT =
"org.apache.camel.dsl.jbang.core.commands.ai.ToolContext";
+
+ private CamelContext camelContext;
+ private String path = "/mcp";
+ private VertxMcpServerEngine engine;
+ private Object toolContext;
+
+ @Override
+ public CamelContext getCamelContext() {
+ return camelContext;
+ }
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ toolContext = createToolContext();
+
+ engine = new VertxMcpServerEngine();
+ engine.setCamelContext(camelContext);
+ engine.setTargetServerType(resolveTargetServerType());
+ String version = camelContext.getVersion();
+ if (version == null || version.isBlank()) {
+ version = "unknown";
+ }
+ engine.initialize(new McpServerInfo(SERVER_NAME, version, path));
+ engine.start();
+
+ for (Object descriptor : allToolDescriptors()) {
+ engine.toolAdded(toMcpTool(descriptor));
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ if (engine != null) {
+ engine.stop();
+ engine = null;
+ }
+ toolContext = null;
+ }
+
+ private McpServerTool toMcpTool(Object descriptor) {
+ String toolName = invokeString(descriptor, "name");
+ List<?> params = invokeList(descriptor, "params");
+ Map<String, ParameterDef> parameterDefs = buildParameterDefs(params);
+ McpToolCallHandler handler = arguments -> {
+ try {
+ Object result = executeTool(toolName,
stringArguments(arguments));
+ return new McpToolCallResult(result != null ?
result.toString() : "", false);
+ } catch (Exception e) {
+ Throwable failure = e;
+ if (e instanceof InvocationTargetException ite &&
ite.getCause() != null) {
+ failure = ite.getCause();
+ }
+ String message = failure.getMessage();
+ if (message == null || message.isBlank()) {
+ message = failure.getClass().getSimpleName();
+ }
+ return new McpToolCallResult(message, true);
+ }
+ };
+ return new McpServerTool() {
+ @Override
+ public String name() {
+ return toolName;
+ }
+
+ @Override
+ public String description() {
+ return invokeString(descriptor, "description");
+ }
+
+ @Override
+ public String inputSchemaJson() {
+ return parameterDefs.isEmpty() ? null :
AiToolParameterHelper.buildJsonSchemaFromDefs(parameterDefs);
+ }
+
+ @Override
+ public Map<String, ParameterDef> parameters() {
+ return parameterDefs;
+ }
+
+ @Override
+ public McpToolCallHandler handler() {
+ return handler;
+ }
+ };
+ }
+
+ private Object createToolContext() throws ReflectiveOperationException {
+ Class<?> contextClass = mandatoryClass(TOOL_CONTEXT);
+ Object context = contextClass.getDeclaredConstructor().newInstance();
+ Method selectProcess = contextClass.getMethod("selectProcess",
long.class);
+ selectProcess.invoke(context, ProcessHandle.current().pid());
+ return context;
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<Object> allToolDescriptors() throws
ReflectiveOperationException {
+ Class<?> registry = mandatoryClass(TOOL_REGISTRY);
+ Method allTools = registry.getMethod("allTools");
+ return (List<Object>) allTools.invoke(null);
+ }
+
+ private Object executeTool(String name, Map<String, String> args) throws
ReflectiveOperationException {
+ Class<?> registry = mandatoryClass(TOOL_REGISTRY);
+ Class<?> contextClass = mandatoryClass(TOOL_CONTEXT);
+ Method execute = registry.getMethod("execute", String.class,
contextClass, Map.class);
+ return execute.invoke(null, name, toolContext, args);
+ }
+
+ private Class<?> mandatoryClass(String name) throws ClassNotFoundException
{
+ return camelContext.getClassResolver().resolveMandatoryClass(name);
+ }
+
+ private static String invokeString(Object target, String method) {
+ try {
+ Object value = target.getClass().getMethod(method).invoke(target);
+ return value != null ? value.toString() : null;
+ } catch (ReflectiveOperationException e) {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static List<?> invokeList(Object target, String method) {
+ try {
+ return (List<?>)
target.getClass().getMethod(method).invoke(target);
+ } catch (ReflectiveOperationException e) {
+ return List.of();
+ }
+ }
+
+ private static Map<String, ParameterDef> buildParameterDefs(List<?>
params) {
+ if (params == null || params.isEmpty()) {
+ return Map.of();
+ }
+ Map<String, String> flat = new LinkedHashMap<>();
+ for (Object param : params) {
+ String name = invokeString(param, "name");
+ if (name == null || name.isBlank()) {
+ continue;
+ }
+ String type = invokeString(param, "type");
+ if (type != null && !type.isBlank()) {
+ flat.put(name, type);
+ }
+ String description = invokeString(param, "description");
+ if (description != null && !description.isBlank()) {
+ flat.put(name + ".description", description);
+ }
+ if (Boolean.TRUE.equals(invokeBoolean(param, "required"))) {
+ flat.put(name + ".required", "true");
+ }
+ }
+ return AiToolParameterHelper.parseParameterMetadata(flat);
+ }
+
+ private static Boolean invokeBoolean(Object target, String method) {
+ try {
+ return (Boolean)
target.getClass().getMethod(method).invoke(target);
+ } catch (ReflectiveOperationException e) {
+ return false;
+ }
+ }
+
+ private static Map<String, String> stringArguments(Map<String, Object>
args) {
+ Map<String, String> out = new LinkedHashMap<>();
+ if (args == null) {
+ return out;
+ }
+ for (Map.Entry<String, Object> entry : args.entrySet()) {
+ if (entry.getValue() != null) {
+ out.put(entry.getKey(), entry.getValue().toString());
+ }
+ }
+ return out;
+ }
+
+ private String resolveTargetServerType() {
+ Set<VertxPlatformHttpRouter> routers =
camelContext.getRegistry().findByType(VertxPlatformHttpRouter.class);
+ boolean hasManagementRouter = routers.stream()
+ .anyMatch(r ->
VertxPlatformHttpRouter.SERVER_TYPE_MANAGEMENT.equals(r.getServerType()));
+ if (hasManagementRouter) {
+ return VertxPlatformHttpRouter.SERVER_TYPE_MANAGEMENT;
+ }
+ LOG.warn("No management HTTP router found; attaching JBang dev MCP
endpoint to the main HTTP server. "
+ + "When management shares the main server port, MCP is served
on the main server bind address "
+ + "(for example 0.0.0.0) rather than 127.0.0.1.");
+ return VertxPlatformHttpRouter.SERVER_TYPE_SERVER;
+ }
+}
diff --git
a/core/camel-main/src/generated/java/org/apache/camel/main/HttpManagementServerConfigurationPropertiesConfigurer.java
b/core/camel-main/src/generated/java/org/apache/camel/main/HttpManagementServerConfigurationPropertiesConfigurer.java
index 22c4403b0c29..e4e88879fb45 100644
---
a/core/camel-main/src/generated/java/org/apache/camel/main/HttpManagementServerConfigurationPropertiesConfigurer.java
+++
b/core/camel-main/src/generated/java/org/apache/camel/main/HttpManagementServerConfigurationPropertiesConfigurer.java
@@ -42,6 +42,8 @@ public class
HttpManagementServerConfigurationPropertiesConfigurer extends org.a
map.put("JwtKeystorePassword", java.lang.String.class);
map.put("JwtKeystorePath", java.lang.String.class);
map.put("JwtKeystoreType", java.lang.String.class);
+ map.put("McpEnabled", boolean.class);
+ map.put("McpPath", java.lang.String.class);
map.put("MetricsEnabled", boolean.class);
map.put("OpenapiUiEnabled", boolean.class);
map.put("OpenapiUiSpecPath", java.lang.String.class);
@@ -96,6 +98,10 @@ public class
HttpManagementServerConfigurationPropertiesConfigurer extends org.a
case "jwtKeystorePath":
target.setJwtKeystorePath(property(camelContext, java.lang.String.class,
value)); return true;
case "jwtkeystoretype":
case "jwtKeystoreType":
target.setJwtKeystoreType(property(camelContext, java.lang.String.class,
value)); return true;
+ case "mcpenabled":
+ case "mcpEnabled": target.setMcpEnabled(property(camelContext,
boolean.class, value)); return true;
+ case "mcppath":
+ case "mcpPath": target.setMcpPath(property(camelContext,
java.lang.String.class, value)); return true;
case "metricsenabled":
case "metricsEnabled": target.setMetricsEnabled(property(camelContext,
boolean.class, value)); return true;
case "openapiuienabled":
@@ -162,6 +168,10 @@ public class
HttpManagementServerConfigurationPropertiesConfigurer extends org.a
case "jwtKeystorePath": return java.lang.String.class;
case "jwtkeystoretype":
case "jwtKeystoreType": return java.lang.String.class;
+ case "mcpenabled":
+ case "mcpEnabled": return boolean.class;
+ case "mcppath":
+ case "mcpPath": return java.lang.String.class;
case "metricsenabled":
case "metricsEnabled": return boolean.class;
case "openapiuienabled":
@@ -224,6 +234,10 @@ public class
HttpManagementServerConfigurationPropertiesConfigurer extends org.a
case "jwtKeystorePath": return target.getJwtKeystorePath();
case "jwtkeystoretype":
case "jwtKeystoreType": return target.getJwtKeystoreType();
+ case "mcpenabled":
+ case "mcpEnabled": return target.isMcpEnabled();
+ case "mcppath":
+ case "mcpPath": return target.getMcpPath();
case "metricsenabled":
case "metricsEnabled": return target.isMetricsEnabled();
case "openapiuienabled":
diff --git
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 5630025faa29..3d8ec1317988 100644
---
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -309,6 +309,8 @@
{ "name": "camel.management.jwtKeystorePassword", "required": false,
"description": "Password from the keystore used for JWT tokens validation.",
"sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": true, "security": "secret"
},
{ "name": "camel.management.jwtKeystorePath", "required": false,
"description": "Path to the keystore file used for JWT tokens validation.",
"sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.management.jwtKeystoreType", "required": false,
"description": "Type of the keystore used for JWT tokens validation (jks,
pkcs12, etc.).", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
+ { "name": "camel.management.mcpEnabled", "required": false, "description":
"Whether to expose dev\/diagnostics MCP tools on this management server
(requires camel-mcp-server on the classpath). Currently honored only when Camel
JBang registers JbangDevMcpMainListener (for example camel run --mcp). For
route-based MCP on the main HTTP server in plain camel-main, configure
camel.server.mcpEnabled instead. Not intended for production use.",
"sourceType": "org.apache.camel.main.HttpManage [...]
+ { "name": "camel.management.mcpPath", "required": false, "description":
"HTTP path where the management MCP endpoint is served.", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "defaultValue": "\/mcp", "secret":
false },
{ "name": "camel.management.metricsEnabled", "required": false,
"description": "Whether to enable metrics. If enabled then you can access
metrics on context-path: \/observe\/metrics", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.management.openapiUiEnabled", "required": false,
"description": "Whether to enable Swagger UI for REST DSL OpenAPI documents. If
enabled then you can browse the API at context-path: \/q\/openapi. The OpenAPI
document is expected at openapiUiSpecPath (by default \/q\/openapi.json from
REST DSL apiContextPath ).", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": false, "secret": [...]
{ "name": "camel.management.openapiUiSpecPath", "required": false,
"description": "Path or URL of the OpenAPI document loaded by Swagger UI at
\/q\/openapi.", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "defaultValue": "\/q\/openapi.json",
"secret": false },
diff --git a/core/camel-main/src/main/docs/main.adoc
b/core/camel-main/src/main/docs/main.adoc
index ffa14c8dfd9d..e3d33c3295b0 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -230,7 +230,7 @@ The camel.server supports 26 options, which are listed
below.
=== Camel Embedded HTTP management Server (only for standalone; not Spring
Boot or Quarkus) configurations
-The camel.management supports 29 options, which are listed below.
+The camel.management supports 31 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -255,6 +255,8 @@ The camel.management supports 29 options, which are listed
below.
| *camel.management.jwtKeystorePassword* | Password from the keystore used for
JWT tokens validation. | | String
| *camel.management.jwtKeystorePath* | Path to the keystore file used for JWT
tokens validation. | | String
| *camel.management.jwtKeystoreType* | Type of the keystore used for JWT
tokens validation (jks, pkcs12, etc.). | | String
+| *camel.management.mcpEnabled* | Whether to expose dev/diagnostics MCP tools
on this management server (requires camel-mcp-server on the classpath).
Currently honored only when Camel JBang registers JbangDevMcpMainListener (for
example camel run --mcp). For route-based MCP on the main HTTP server in plain
camel-main, configure camel.server.mcpEnabled instead. Not intended for
production use. | false | boolean
+| *camel.management.mcpPath* | HTTP path where the management MCP endpoint is
served. | /mcp | String
| *camel.management.metricsEnabled* | Whether to enable metrics. If enabled
then you can access metrics on context-path: /observe/metrics | false | boolean
| *camel.management.openapiUiEnabled* | Whether to enable Swagger UI for REST
DSL OpenAPI documents. If enabled then you can browse the API at context-path:
/q/openapi. The OpenAPI document is expected at openapiUiSpecPath (by default
/q/openapi.json from REST DSL apiContextPath ). | false | boolean
| *camel.management.openapiUiSpecPath* | Path or URL of the OpenAPI document
loaded by Swagger UI at /q/openapi. | /q/openapi.json | String
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/HttpManagementServerConfigurationProperties.java
b/core/camel-main/src/main/java/org/apache/camel/main/HttpManagementServerConfigurationProperties.java
index 405635887d8e..17b788f8a690 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/HttpManagementServerConfigurationProperties.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/HttpManagementServerConfigurationProperties.java
@@ -84,6 +84,10 @@ public class HttpManagementServerConfigurationProperties
implements BootstrapClo
private String jwtAudience;
@Metadata(label = "security", defaultValue = "false", security =
"insecure:dev")
private boolean jwtAllowMissingIssuerAndAudience;
+ @Metadata(security = "insecure:dev")
+ private boolean mcpEnabled;
+ @Metadata(defaultValue = "/mcp")
+ private String mcpPath = "/mcp";
public
HttpManagementServerConfigurationProperties(MainConfigurationProperties parent)
{
this.parent = parent;
@@ -573,6 +577,41 @@ public class HttpManagementServerConfigurationProperties
implements BootstrapClo
return this;
}
+ public boolean isMcpEnabled() {
+ return mcpEnabled;
+ }
+
+ /**
+ * Whether to expose dev/diagnostics MCP tools on this management server
(requires camel-mcp-server on the
+ * classpath). Currently honored only when Camel JBang registers
JbangDevMcpMainListener (for example camel run
+ * --mcp). For route-based MCP on the main HTTP server in plain
camel-main, configure camel.server.mcpEnabled
+ * instead. Not intended for production use.
+ */
+ public void setMcpEnabled(boolean mcpEnabled) {
+ this.mcpEnabled = mcpEnabled;
+ }
+
+ public String getMcpPath() {
+ return mcpPath;
+ }
+
+ /**
+ * HTTP path where the management MCP endpoint is served.
+ */
+ public void setMcpPath(String mcpPath) {
+ this.mcpPath = mcpPath;
+ }
+
+ public HttpManagementServerConfigurationProperties withMcpEnabled(boolean
mcpEnabled) {
+ this.mcpEnabled = mcpEnabled;
+ return this;
+ }
+
+ public HttpManagementServerConfigurationProperties withMcpPath(String
mcpPath) {
+ this.mcpPath = mcpPath;
+ return this;
+ }
+
/**
* Whether to enable HTTP authentication for embedded server (for
standalone applications; not Spring Boot or
* Quarkus).
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
index 58d8bc24c988..2929345395f9 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
@@ -4,7 +4,7 @@ This page covers configuring the Camel CLI — available options,
configuration
// jbang options: START
== Camel CLI configurations
-The camel.jbang supports 52 options, which are listed below.
+The camel.jbang supports 53 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -41,6 +41,7 @@ The camel.jbang supports 52 options, which are listed below.
| *camel.jbang.maven-settings* | Optional location of Maven settings.xml file
to configure servers, repositories, mirrors, and proxies. If set to false, not
even the default /.m2/settings.xml will be used. | | String
| *camel.jbang.maven-settings-security* | Optional location of Maven
settings-security.xml file to decrypt Maven Settings (settings.xml) file | |
String
| *camel.jbang.mavenWrapper* | Include Maven Wrapper files in the exported
project | true | boolean
+| *camel.jbang.mcp* | Embed dev/diagnostics MCP tools on the local HTTP
management server (/mcp by default) | false | boolean
| *camel.jbang.metrics* | Metrics (Micrometer and Prometheus) at
/observe/metrics on local HTTP server (port 8080 by default) when running
standalone Camel | false | boolean
| *camel.jbang.openApi* | File name of open-api spec file (JSON or YAML) to
generate routes from the swagger/openapi API spec file. | | String
| *camel.jbang.openapiUi* | Swagger UI for REST OpenAPI at /q/openapi on local
HTTP server (port 8080 by default) | false | boolean
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
index de73fa1b3b02..1a7f77f79cef 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
@@ -61,6 +61,7 @@ camel debug [options]
| `--max-idle-seconds` | For how long time in seconds Camel can be idle before
stopping | 0 | int
| `--max-messages` | Max number of messages to process before stopping | 0 |
int
| `--max-seconds` | Max seconds to run before stopping | 0 | int
+| `--mcp` | Embed dev/diagnostics MCP tools on the local HTTP management
server (/mcp by default). When management shares the main HTTP port, MCP is
served on the main server bind address. Also binds the management server to
127.0.0.1 (affecting health/metrics when --observe is used). | false | boolean
| `--metrics` _(deprecated)_ | Deprecated: use --observe instead. Metrics
(Micrometer and Prometheus) at /q/metrics on local HTTP server (port 8080 by
default) | false | boolean
| `--modeline` | Whether to support JBang style //DEPS to specify additional
dependencies | true | boolean
| `--name` | The name of the Camel application | CamelJBang | String
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
index fbe37951b652..4d8ce342f534 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
@@ -57,6 +57,7 @@ camel dev [options]
| `--max-idle-seconds` | For how long time in seconds Camel can be idle before
stopping | 0 | int
| `--max-messages` | Max number of messages to process before stopping | 0 |
int
| `--max-seconds` | Max seconds to run before stopping | 0 | int
+| `--mcp` | Embed dev/diagnostics MCP tools on the local HTTP management
server (/mcp by default). When management shares the main HTTP port, MCP is
served on the main server bind address. Also binds the management server to
127.0.0.1 (affecting health/metrics when --observe is used). | false | boolean
| `--metrics` _(deprecated)_ | Deprecated: use --observe instead. Metrics
(Micrometer and Prometheus) at /q/metrics on local HTTP server (port 8080 by
default) | false | boolean
| `--modeline` | Whether to support JBang style //DEPS to specify additional
dependencies | true | boolean
| `--name` | The name of the Camel application | CamelJBang | String
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
index 205ec1b76c99..864a7fad313a 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
@@ -57,6 +57,7 @@ camel run [options]
| `--max-idle-seconds` | For how long time in seconds Camel can be idle before
stopping | 0 | int
| `--max-messages` | Max number of messages to process before stopping | 0 |
int
| `--max-seconds` | Max seconds to run before stopping | 0 | int
+| `--mcp` | Embed dev/diagnostics MCP tools on the local HTTP management
server (/mcp by default). When management shares the main HTTP port, MCP is
served on the main server bind address. Also binds the management server to
127.0.0.1 (affecting health/metrics when --observe is used). | false | boolean
| `--metrics` _(deprecated)_ | Deprecated: use --observe instead. Metrics
(Micrometer and Prometheus) at /q/metrics on local HTTP server (port 8080 by
default) | false | boolean
| `--modeline` | Whether to support JBang style //DEPS to specify additional
dependencies | true | boolean
| `--name` | The name of the Camel application | CamelJBang | String
diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml
b/dsl/camel-jbang/camel-jbang-core/pom.xml
index d3066b4c5e6f..46e20103130d 100644
--- a/dsl/camel-jbang/camel-jbang-core/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-core/pom.xml
@@ -196,6 +196,35 @@
<version>${mockito-version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-platform-http-main</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-mcp-server</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.modelcontextprotocol.sdk</groupId>
+ <artifactId>mcp-core</artifactId>
+ <version>${mcp-java-sdk-version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.modelcontextprotocol.sdk</groupId>
+ <artifactId>mcp-json-jackson2</artifactId>
+ <version>${mcp-java-sdk-version}</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- MCP SDK schema validation needs networknt 2.0.1 (Dialects);
wiremock transitively pulls 1.5.x -->
+ <dependency>
+ <groupId>com.networknt</groupId>
+ <artifactId>json-schema-validator</artifactId>
+ <version>${networknt-json-schema-validator-version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index aa34ac5e55bf..e155bf6a47e2 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -6,9 +6,9 @@
{ "name": "cmd", "fullName": "cmd", "description": "Performs commands in
the running Camel integrations, such as start\/stop route, or change logging
levels.", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"browse", "fullName": "cmd browse", "description": "Browse pending messages on
endpoints [...]
{ "name": "completion", "fullName": "completion", "description": "Generate
completion script for bash\/zsh", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
{ "name": "config", "fullName": "config", "description": "Get and set user
configuration values", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get",
"fullName": "config get", "description": "Display user configuration value",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
- { "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To [...]
+ { "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To [...]
{ "name": "dependency", "fullName": "dependency", "description": "Displays
all Camel dependencies required to run", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"copy", "fullName": "dependency copy", "description": "Copies all Camel
dependencies required to run to a specific directory", "sourc [...]
- { "name": "dev", "fullName": "dev", "description": "Run in dev mode with
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, [...]
+ { "name": "dev", "fullName": "dev", "description": "Run in dev mode with
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, [...]
{ "name": "dirty", "fullName": "dirty", "description": "Check if there are
dirty files from previous Camel runs that did not terminate gracefully",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty",
"options": [ { "names": "--clean", "description": "Clean dirty files which are
no longer in use", "defaultValue": "false", "javaType": "boolean", "type":
"boolean" }, { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", " [...]
{ "name": "doc", "fullName": "doc", "description": "Shows documentation
for kamelet, component, and other Camel resources", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ {
"names": "--camel-version", "description": "To use a different Camel version
than the default version", "javaType": "java.lang.String", "type": "string" },
{ "names": "--download", "description": "Whether to allow automatic downloading
JAR dependencies (over the internet [...]
{ "name": "doctor", "fullName": "doctor", "description": "Checks the
environment and reports potential issues", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Doctor", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
@@ -26,7 +26,7 @@
{ "name": "plugin", "fullName": "plugin", "description": "Manage plugins
that add sub-commands to this CLI", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "add",
"fullName": "plugin add", "description": "Add new plugin", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginA [...]
{ "name": "ps", "fullName": "ps", "description": "List running Camel
integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.ListProcess", "options": [ {
"names": "--json", "description": "Output in JSON Format", "javaType":
"boolean", "type": "boolean" }, { "names": "--pid", "description": "List only
pid in the output", "javaType": "boolean", "type": "boolean" }, { "names":
"--remote", "description": "Break down counters into remote\/total pairs",
"javaType": [...]
{ "name": "restart", "fullName": "restart", "description": "Restarts
running Camel integrations (stop + re-launch)", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.RestartProcess", "options": [
{ "names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ] },
- { "name": "run", "fullName": "run", "description": "Run as local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, { [...]
+ { "name": "run", "fullName": "run", "description": "Run as local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, { [...]
{ "name": "sbom", "fullName": "sbom", "description": "Generate a CycloneDX
or SPDX SBOM for a specific project", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.SBOMGenerator", "options": [ {
"names": "--build-property", "description": "Maven build properties, ex.
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, {
"names": "--camel-spring-boot-version", "description": "Camel version to use
with Spring Boot", "javaType": "java.lang.String", "type" [...]
{ "name": "script", "fullName": "script", "description": "Run Camel
integration as shell script for terminal scripting", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Script", "options": [ { "names":
"--logging", "description": "Can be used to turn on logging (logs to file in
<user home>\/.camel directory)", "defaultValue": "false", "javaType":
"boolean", "type": "boolean" }, { "names": "--logging-level", "description":
"Logging level (ERROR, WARN, INFO, DEBUG, TRACE)", "d [...]
{ "name": "shell", "fullName": "shell", "description": "Interactive Camel
CLI shell.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Shell",
"options": [ { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", "type": "boolean" } ] },
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
index f4b422e1b40c..bd1bdf642d53 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
@@ -35,6 +35,7 @@
{ "name": "camel.jbang.maven-settings", "required": false, "description":
"Optional location of Maven settings.xml file to configure servers,
repositories, mirrors, and proxies. If set to false, not even the default
\/.m2\/settings.xml will be used.", "label": "maven", "type": "string",
"javaType": "String", "secret": false },
{ "name": "camel.jbang.maven-settings-security", "required": false,
"description": "Optional location of Maven settings-security.xml file to
decrypt Maven Settings (settings.xml) file", "label": "maven", "type":
"string", "javaType": "String", "secret": false },
{ "name": "camel.jbang.mavenWrapper", "required": false, "description":
"Include Maven Wrapper files in the exported project", "type": "boolean",
"javaType": "boolean", "defaultValue": true, "secret": false },
+ { "name": "camel.jbang.mcp", "required": false, "description": "Embed
dev\/diagnostics MCP tools on the local HTTP management server (\/mcp by
default)", "type": "boolean", "javaType": "boolean", "defaultValue": false,
"secret": false, "security": "insecure:dev" },
{ "name": "camel.jbang.metrics", "required": false, "description":
"Metrics (Micrometer and Prometheus) at \/observe\/metrics on local HTTP server
(port 8080 by default) when running standalone Camel", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false, "deprecated":
true },
{ "name": "camel.jbang.openApi", "required": false, "description": "File
name of open-api spec file (JSON or YAML) to generate routes from the
swagger\/openapi API spec file.", "type": "string", "javaType": "String",
"secret": false },
{ "name": "camel.jbang.openapiUi", "required": false, "description":
"Swagger UI for REST OpenAPI at \/q\/openapi on local HTTP server (port 8080 by
default)", "type": "boolean", "javaType": "boolean", "defaultValue": false,
"secret": false, "security": "insecure:dev" },
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index 29bf3e3d3b04..0cf49cf11e9f 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -602,6 +602,13 @@ public class Run extends CamelCommand {
}
}
+ private boolean isMcpEnabled(Properties profileProperties) {
+ String val = profileProperties != null
+ ? profileProperties.getProperty(MCP, serverOptions.mcp ?
"true" : "false")
+ : (serverOptions.mcp ? "true" : "false");
+ return "true".equals(val);
+ }
+
private void writeSetting(KameletMain main, Properties existing, String
key, Supplier<String> value) {
String val = existing != null ? existing.getProperty(key, value.get())
: value.get();
if (val != null) {
@@ -846,6 +853,7 @@ public class Run extends CamelCommand {
writeSetting(main, profileProperties, HEALTH, serverOptions.health ?
"true" : "false");
writeSetting(main, profileProperties, METRICS, serverOptions.metrics ?
"true" : "false");
writeSetting(main, profileProperties, CONSOLE, serverOptions.console ?
"true" : "false");
+ writeSetting(main, profileProperties, MCP, serverOptions.mcp ? "true"
: "false");
writeSetting(main, profileProperties, OPENAPI_UI,
serverOptions.openapiUi ? "true" : "false");
writeSetting(main, profileProperties, VERBOSE, verbose ? "true" :
"false");
// the runtime version of Camel is what is loaded via the catalog
@@ -1258,6 +1266,10 @@ public class Run extends CamelCommand {
dependencies.add("camel:openapi-java");
applyOpenApiUiRuntimeOptions(main);
}
+ if (isMcpEnabled(profileProperties)) {
+ dependencies.add("camel:platform-http-main");
+ dependencies.add("camel:mcp-server");
+ }
if (debugOptions.openTelemetryAgent) {
dependencies.add("camel:opentelemetry2");
boolean externalExport =
"otlp".equals(debugOptions.openTelemetryAgentExport)
@@ -3089,6 +3101,12 @@ public class Run extends CamelCommand {
description = "Developer console at /q/dev on local HTTP
server (port 8080 by default)")
boolean console;
+ @Option(names = { "--mcp" }, defaultValue = "false",
+ description = "Embed dev/diagnostics MCP tools on the local
HTTP management server (/mcp by default). "
+ + "When management shares the main HTTP port,
MCP is served on the main server bind address. "
+ + "Also binds the management server to 127.0.0.1
(affecting health/metrics when --observe is used).")
+ boolean mcp;
+
@Option(names = { "--openapi-ui" }, defaultValue = "false",
description = "Swagger UI for REST OpenAPI at /q/openapi
(OpenAPI document at /q/openapi.json; port 8080 by default)")
boolean openapiUi;
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
index b95d4668448c..6c4789d27729 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
@@ -205,6 +205,10 @@ public final class CamelJBangConstants {
javaType = "boolean")
public static final String CONSOLE = "camel.jbang.console";
+ @Metadata(description = "Embed dev/diagnostics MCP tools on the local HTTP
management server (/mcp by default)",
+ javaType = "boolean", security = "insecure:dev")
+ public static final String MCP = "camel.jbang.mcp";
+
@Metadata(description = "Swagger UI for REST OpenAPI at /q/openapi on
local HTTP server (port 8080 by default)",
javaType = "boolean", security = "insecure:dev")
public static final String OPENAPI_UI = "camel.jbang.openapiUi";
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunMcpOptionTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunMcpOptionTest.java
new file mode 100644
index 000000000000..063609f8512b
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunMcpOptionTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.dsl.jbang.core.commands;
+
+import org.junit.jupiter.api.Test;
+import picocli.CommandLine;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RunMcpOptionTest {
+
+ @Test
+ void parseMcpFlag() {
+ Run run = new Run(new CamelJBangMain());
+ CommandLine cmd = new CommandLine(run);
+ cmd.parseArgs("hello.yaml", "--mcp");
+ assertThat(run.serverOptions.mcp).isTrue();
+ }
+
+ @Test
+ void mcpDefaultsToFalse() {
+ Run run = new Run(new CamelJBangMain());
+ CommandLine cmd = new CommandLine(run);
+ cmd.parseArgs("hello.yaml");
+ assertThat(run.serverOptions.mcp).isFalse();
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/JbangDevMcpServerTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/JbangDevMcpServerTest.java
new file mode 100644
index 000000000000..36a65375ce00
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/JbangDevMcpServerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.dsl.jbang.core.commands.mcp;
+
+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.mcp.server.jbang.JbangDevMcpServer;
+import org.apache.camel.component.platform.http.main.MainHttpServer;
+import org.apache.camel.component.platform.http.main.ManagementHttpServer;
+import org.apache.camel.dsl.jbang.core.commands.ai.ToolRegistry;
+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;
+
+class JbangDevMcpServerTest {
+
+ @Test
+ void exposesToolRegistryOnManagementServer() throws Exception {
+ int mainPort = AvailablePortFinder.getNextAvailable();
+ int managementPort = AvailablePortFinder.getNextAvailable();
+
+ CamelContext camelContext = new DefaultCamelContext();
+ JbangDevMcpServer devMcp = new JbangDevMcpServer();
+ McpSyncClient client = null;
+ try {
+ MainHttpServer main = new MainHttpServer();
+ main.setCamelContext(camelContext);
+ main.setHost("127.0.0.1");
+ main.setPort(mainPort);
+ camelContext.addService(main);
+
+ ManagementHttpServer management = new ManagementHttpServer();
+ management.setCamelContext(camelContext);
+ management.setHost("127.0.0.1");
+ management.setPort(managementPort);
+ management.setPath("/");
+ camelContext.addService(management);
+
+ devMcp.setCamelContext(camelContext);
+ devMcp.setPath("/mcp");
+ camelContext.addService(devMcp);
+
+ camelContext.start();
+
+ client = McpClient.sync(
+
HttpClientStreamableHttpTransport.builder("http://127.0.0.1:" +
managementPort).build())
+ .requestTimeout(Duration.ofSeconds(10))
+ .initializationTimeout(Duration.ofSeconds(10))
+ .build();
+ McpSchema.InitializeResult init = client.initialize();
+
assertThat(init.serverInfo().name()).isEqualTo("camel-jbang-dev-tools");
+
+ assertThat(client.listTools().tools())
+ .extracting(McpSchema.Tool::name)
+ .contains(ToolRegistry.allTools().get(0).name());
+
+ McpSchema.Tool parameterizedTool =
client.listTools().tools().stream()
+ .filter(t -> "select_process".equals(t.name()))
+ .findFirst()
+ .orElseThrow();
+ assertThat(parameterizedTool.inputSchema()).isNotNull();
+
assertThat(parameterizedTool.inputSchema().toString()).contains("name").contains("required");
+
+ McpSchema.CallToolResult result = client.callTool(
+ new McpSchema.CallToolRequest("list_processes", Map.of()));
+ assertThat(result.isError()).isNotEqualTo(Boolean.TRUE);
+ assertThat(result.content().toString()).contains("processes");
+ } finally {
+ if (client != null) {
+ client.closeGracefully();
+ }
+ camelContext.stop();
+ }
+ }
+}
diff --git
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index 7d7cade212fe..419f2ae9fe1a 100644
---
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -594,6 +594,21 @@ public class KameletMain extends MainCommandLineSupport {
configure().httpManagementServer().withEnabled(true);
configure().httpManagementServer().withInfoEnabled(true);
}
+ boolean mcp =
"true".equals(getInitialProperties().get(getInstanceType() + ".mcp"));
+ if (mcp) {
+ configure().httpManagementServer().withEnabled(true);
+ configure().httpManagementServer().withMcpEnabled(true);
+ if (!isConfigured("camel.management.host")) {
+ configure().httpManagementServer().withHost("127.0.0.1");
+ }
+ String listeners =
getInitialProperties().getProperty("camel.main.mainListenerClasses");
+ String embedded =
"org.apache.camel.component.mcp.server.jbang.JbangDevMcpMainListener";
+ if (listeners == null || listeners.isBlank()) {
+ addInitialProperty("camel.main.mainListenerClasses", embedded);
+ } else if (!listeners.contains(embedded)) {
+ addInitialProperty("camel.main.mainListenerClasses", listeners
+ "," + embedded);
+ }
+ }
// Deprecated: to be replaced by observe flag
boolean health =
"true".equals(getInitialProperties().get(getInstanceType() + ".health"));
if (health) {
diff --git
a/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
b/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
index 70730cfcc430..4f98022c4f35 100644
---
a/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
+++
b/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
@@ -42,6 +42,7 @@ org.eclipse.microprofile.config.inject.ConfigProperty =
camel:microprofile-confi
camel.opentelemetry = camel:opentelemetry
camel.opentelemetry2 = camel:opentelemetry2
+org.apache.camel.component.mcp.server.jbang.JbangDevMcpMainListener =
camel:mcp-server
camel.lra = camel:lra
camel.telemetryDev = camel:telemetry-dev
camel.cluster.file = camel:file