This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch 22886
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 1c5e4e2b505bfca821be635061a0baa0fef7ba39
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Jan 22 09:24:05 2026 +0100

    CAMEL-22886 - Camel-Jbang: Add an MCP module
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 dsl/camel-jbang/camel-jbang-mcp/pom.xml            | 143 ++++++++
 .../camel-jbang-plugin/camel-jbang-plugin-mcp      |   2 +
 .../dsl/jbang/core/commands/mcp/CatalogTools.java  | 363 +++++++++++++++++++++
 .../dsl/jbang/core/commands/mcp/ExplainTools.java  | 212 ++++++++++++
 .../jbang/core/commands/mcp/TransformTools.java    | 188 +++++++++++
 .../src/main/resources/application.properties      |  41 +++
 dsl/camel-jbang/pom.xml                            |   1 +
 7 files changed, 950 insertions(+)

diff --git a/dsl/camel-jbang/camel-jbang-mcp/pom.xml 
b/dsl/camel-jbang/camel-jbang-mcp/pom.xml
new file mode 100644
index 000000000000..a66bfdbb5721
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-mcp/pom.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.camel</groupId>
+        <artifactId>camel-jbang-parent</artifactId>
+        <version>4.18.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>camel-jbang-mcp</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Camel :: JBang :: MCP</name>
+    <description>Camel JBang MCP (Model Context Protocol) Server using 
Quarkus</description>
+
+    <properties>
+        <firstVersion>4.18.0</firstVersion>
+        <label>jbang,mcp,ai</label>
+        <supportLevel>Preview</supportLevel>
+        <camel-prepare-component>false</camel-prepare-component>
+        <quarkus-mcp-server-version>1.2.0</quarkus-mcp-server-version>
+        <quarkus.platform.version>3.30.6</quarkus.platform.version>
+        <!-- Build uber-jar for easy JBang execution -->
+        <quarkus.package.jar.type>uber-jar</quarkus.package.jar.type>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>io.quarkus.platform</groupId>
+                <artifactId>quarkus-bom</artifactId>
+                <version>${quarkus.platform.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <!-- Quarkus MCP Server with STDIO transport -->
+        <dependency>
+            <groupId>io.quarkiverse.mcp</groupId>
+            <artifactId>quarkus-mcp-server-stdio</artifactId>
+            <version>${quarkus-mcp-server-version}</version>
+        </dependency>
+
+        <!-- Quarkus core dependencies -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-arc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-jackson</artifactId>
+        </dependency>
+
+        <!-- Quarkus LangChain4j for LLM integration -->
+        <dependency>
+            <groupId>io.quarkiverse.langchain4j</groupId>
+            <artifactId>quarkus-langchain4j-ollama</artifactId>
+            <version>1.6.0.CR1</version>
+        </dependency>
+
+        <!-- camel catalog for component/eip metadata -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-catalog</artifactId>
+       </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-route-parser</artifactId>
+        </dependency>
+
+        <!-- test dependencies -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>io.quarkus.platform</groupId>
+                <artifactId>quarkus-maven-plugin</artifactId>
+                <version>${quarkus.platform.version}</version>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                            <goal>generate-code</goal>
+                            <goal>generate-code-tests</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <parameters>true</parameters>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <systemPropertyVariables>
+                        
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/generated/resources/META-INF/services/org/apache/camel/camel-jbang-plugin/camel-jbang-plugin-mcp
 
b/dsl/camel-jbang/camel-jbang-mcp/src/generated/resources/META-INF/services/org/apache/camel/camel-jbang-plugin/camel-jbang-plugin-mcp
new file mode 100644
index 000000000000..d57b288500ea
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/generated/resources/META-INF/services/org/apache/camel/camel-jbang-plugin/camel-jbang-plugin-mcp
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.dsl.jbang.core.commands.mcp.McpPlugin
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
new file mode 100644
index 000000000000..dcd315d620a0
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
@@ -0,0 +1,363 @@
+/*
+ * 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.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.DataFormatModel;
+import org.apache.camel.tooling.model.EipModel;
+import org.apache.camel.tooling.model.LanguageModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * MCP Tools for querying the Camel Catalog using Quarkus MCP Server.
+ */
+@ApplicationScoped
+public class CatalogTools {
+
+    private final CamelCatalog catalog;
+
+    @Inject
+    ObjectMapper mapper;
+
+    public CatalogTools() {
+        this.catalog = new DefaultCamelCatalog();
+    }
+
+    /**
+     * Tool to list available Camel components.
+     */
+    @Tool(description = "List available Camel components from the catalog. " +
+                        "Returns component name, description, and labels. " +
+                        "Use filter to search by name, label to filter by 
category.")
+    public String camel_catalog_components(
+            @ToolArg(description = "Filter components by name 
(case-insensitive substring match)") String filter,
+            @ToolArg(description = "Filter by category label (e.g., cloud, 
messaging, database, file)") String label,
+            @ToolArg(description = "Maximum number of results to return 
(default: 50)") Integer limit) {
+
+        int maxResults = limit != null ? limit : 50;
+
+        try {
+            List<Map<String, Object>> components = 
catalog.findComponentNames().stream()
+                    .map(catalog::componentModel)
+                    .filter(m -> m != null)
+                    .filter(m -> matchesFilter(m.getScheme(), m.getTitle(), 
m.getDescription(), filter))
+                    .filter(m -> matchesLabel(m.getLabel(), label))
+                    .limit(maxResults)
+                    .map(this::componentToMap)
+                    .collect(Collectors.toList());
+
+            JsonObject resultJson = new JsonObject();
+            resultJson.put("count", components.size());
+            resultJson.put("camelVersion", catalog.getCatalogVersion());
+            JsonArray arr = new JsonArray();
+            components.forEach(c -> {
+                JsonObject jo = new JsonObject();
+                jo.putAll(c);
+                arr.add(jo);
+            });
+            resultJson.put("components", arr);
+
+            return resultJson.toJson();
+        } catch (Exception e) {
+            throw new ToolCallException("Failed to list components: " + 
e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Tool to get detailed documentation for a specific component.
+     */
+    @Tool(description = "Get detailed documentation for a Camel component 
including all options, " +
+                        "endpoint parameters, and usage examples.")
+    public String camel_catalog_component_doc(
+            @ToolArg(description = "Component name (e.g., kafka, http, file, 
timer)") String component) {
+
+        if (component == null || component.isBlank()) {
+            throw new ToolCallException("Component name is required", null);
+        }
+
+        ComponentModel model = catalog.componentModel(component);
+        if (model == null) {
+            throw new ToolCallException("Component not found: " + component, 
null);
+        }
+
+        JsonObject resultJson = componentToDetailedJson(model);
+        return resultJson.toJson();
+    }
+
+    /**
+     * Tool to list data formats.
+     */
+    @Tool(description = "List available Camel data formats for 
marshalling/unmarshalling " +
+                        "(e.g., json, xml, csv, avro, protobuf).")
+    public String camel_catalog_dataformats(
+            @ToolArg(description = "Filter by name") String filter,
+            @ToolArg(description = "Maximum results (default: 50)") Integer 
limit) {
+
+        int maxResults = limit != null ? limit : 50;
+
+        try {
+            List<Map<String, Object>> dataFormats = 
catalog.findDataFormatNames().stream()
+                    .map(catalog::dataFormatModel)
+                    .filter(m -> m != null)
+                    .filter(m -> matchesFilter(m.getName(), m.getTitle(), 
m.getDescription(), filter))
+                    .limit(maxResults)
+                    .map(this::dataFormatToMap)
+                    .collect(Collectors.toList());
+
+            JsonObject resultJson = new JsonObject();
+            resultJson.put("count", dataFormats.size());
+            JsonArray arr = new JsonArray();
+            dataFormats.forEach(df -> {
+                JsonObject jo = new JsonObject();
+                jo.putAll(df);
+                arr.add(jo);
+            });
+            resultJson.put("dataFormats", arr);
+
+            return resultJson.toJson();
+        } catch (Exception e) {
+            throw new ToolCallException("Failed to list data formats: " + 
e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Tool to list expression languages.
+     */
+    @Tool(description = "List available Camel expression languages " +
+                        "(e.g., simple, jsonpath, xpath, groovy, jq).")
+    public String camel_catalog_languages(
+            @ToolArg(description = "Filter by name") String filter) {
+
+        try {
+            List<Map<String, Object>> languages = 
catalog.findLanguageNames().stream()
+                    .map(catalog::languageModel)
+                    .filter(m -> m != null)
+                    .filter(m -> matchesFilter(m.getName(), m.getTitle(), 
m.getDescription(), filter))
+                    .map(this::languageToMap)
+                    .collect(Collectors.toList());
+
+            JsonObject resultJson = new JsonObject();
+            resultJson.put("count", languages.size());
+            JsonArray arr = new JsonArray();
+            languages.forEach(l -> {
+                JsonObject jo = new JsonObject();
+                jo.putAll(l);
+                arr.add(jo);
+            });
+            resultJson.put("languages", arr);
+
+            return resultJson.toJson();
+        } catch (Exception e) {
+            throw new ToolCallException("Failed to list languages: " + 
e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Tool to list EIPs (Enterprise Integration Patterns).
+     */
+    @Tool(description = "List Camel Enterprise Integration Patterns (EIPs) 
like split, aggregate, " +
+                        "filter, choice, multicast, circuit-breaker, etc.")
+    public String camel_catalog_eips(
+            @ToolArg(description = "Filter by name") String filter,
+            @ToolArg(description = "Filter by category (e.g., routing, 
transformation, error handling)") String label) {
+
+        try {
+            List<Map<String, Object>> eips = catalog.findModelNames().stream()
+                    .map(catalog::eipModel)
+                    .filter(m -> m != null)
+                    .filter(m -> matchesFilter(m.getName(), m.getTitle(), 
m.getDescription(), filter))
+                    .filter(m -> matchesLabel(m.getLabel(), label))
+                    .map(this::eipToMap)
+                    .collect(Collectors.toList());
+
+            JsonObject resultJson = new JsonObject();
+            resultJson.put("count", eips.size());
+            JsonArray arr = new JsonArray();
+            eips.forEach(e -> {
+                JsonObject jo = new JsonObject();
+                jo.putAll(e);
+                arr.add(jo);
+            });
+            resultJson.put("eips", arr);
+
+            return resultJson.toJson();
+        } catch (Exception e) {
+            throw new ToolCallException("Failed to list EIPs: " + 
e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Tool to get detailed documentation for a specific EIP.
+     */
+    @Tool(description = "Get detailed documentation for a Camel EIP 
(Enterprise Integration Pattern).")
+    public String camel_catalog_eip_doc(
+            @ToolArg(description = "EIP name (e.g., split, aggregate, choice, 
filter)") String eip) {
+
+        if (eip == null || eip.isBlank()) {
+            throw new ToolCallException("EIP name is required", null);
+        }
+
+        EipModel model = catalog.eipModel(eip);
+        if (model == null) {
+            throw new ToolCallException("EIP not found: " + eip, null);
+        }
+
+        JsonObject resultJson = eipToDetailedJson(model);
+        return resultJson.toJson();
+    }
+
+    // Helper methods
+
+    private boolean matchesFilter(String name, String title, String 
description, String filter) {
+        if (filter == null || filter.isBlank()) {
+            return true;
+        }
+        String lowerFilter = filter.toLowerCase();
+        return (name != null && name.toLowerCase().contains(lowerFilter))
+                || (title != null && title.toLowerCase().contains(lowerFilter))
+                || (description != null && 
description.toLowerCase().contains(lowerFilter));
+    }
+
+    private boolean matchesLabel(String labels, String labelFilter) {
+        if (labelFilter == null || labelFilter.isBlank()) {
+            return true;
+        }
+        if (labels == null) {
+            return false;
+        }
+        return labels.toLowerCase().contains(labelFilter.toLowerCase());
+    }
+
+    private Map<String, Object> componentToMap(ComponentModel model) {
+        return Map.of(
+                "name", model.getScheme(),
+                "title", model.getTitle() != null ? model.getTitle() : "",
+                "description", model.getDescription() != null ? 
model.getDescription() : "",
+                "label", model.getLabel() != null ? model.getLabel() : "",
+                "deprecated", model.isDeprecated(),
+                "supportLevel", model.getSupportLevel() != null ? 
model.getSupportLevel().name() : "");
+    }
+
+    private JsonObject componentToDetailedJson(ComponentModel model) {
+        JsonObject jo = new JsonObject();
+        jo.put("name", model.getScheme());
+        jo.put("title", model.getTitle());
+        jo.put("description", model.getDescription());
+        jo.put("label", model.getLabel());
+        jo.put("deprecated", model.isDeprecated());
+        jo.put("supportLevel", model.getSupportLevel() != null ? 
model.getSupportLevel().name() : null);
+        jo.put("groupId", model.getGroupId());
+        jo.put("artifactId", model.getArtifactId());
+        jo.put("version", model.getVersion());
+        jo.put("syntax", model.getSyntax());
+        jo.put("async", model.isAsync());
+        jo.put("consumerOnly", model.isConsumerOnly());
+        jo.put("producerOnly", model.isProducerOnly());
+
+        JsonArray options = new JsonArray();
+        if (model.getComponentOptions() != null) {
+            model.getComponentOptions().forEach(opt -> {
+                JsonObject optJson = new JsonObject();
+                optJson.put("name", opt.getName());
+                optJson.put("description", opt.getDescription());
+                optJson.put("type", opt.getType());
+                optJson.put("required", opt.isRequired());
+                optJson.put("defaultValue", opt.getDefaultValue());
+                options.add(optJson);
+            });
+        }
+        jo.put("componentOptions", options);
+
+        JsonArray endpointOptions = new JsonArray();
+        if (model.getEndpointOptions() != null) {
+            model.getEndpointOptions().forEach(opt -> {
+                JsonObject optJson = new JsonObject();
+                optJson.put("name", opt.getName());
+                optJson.put("description", opt.getDescription());
+                optJson.put("type", opt.getType());
+                optJson.put("required", opt.isRequired());
+                optJson.put("defaultValue", opt.getDefaultValue());
+                optJson.put("group", opt.getGroup());
+                endpointOptions.add(optJson);
+            });
+        }
+        jo.put("endpointOptions", endpointOptions);
+
+        return jo;
+    }
+
+    private Map<String, Object> dataFormatToMap(DataFormatModel model) {
+        return Map.of(
+                "name", model.getName(),
+                "title", model.getTitle() != null ? model.getTitle() : "",
+                "description", model.getDescription() != null ? 
model.getDescription() : "",
+                "deprecated", model.isDeprecated());
+    }
+
+    private Map<String, Object> languageToMap(LanguageModel model) {
+        return Map.of(
+                "name", model.getName(),
+                "title", model.getTitle() != null ? model.getTitle() : "",
+                "description", model.getDescription() != null ? 
model.getDescription() : "");
+    }
+
+    private Map<String, Object> eipToMap(EipModel model) {
+        return Map.of(
+                "name", model.getName(),
+                "title", model.getTitle() != null ? model.getTitle() : "",
+                "description", model.getDescription() != null ? 
model.getDescription() : "",
+                "label", model.getLabel() != null ? model.getLabel() : "");
+    }
+
+    private JsonObject eipToDetailedJson(EipModel model) {
+        JsonObject jo = new JsonObject();
+        jo.put("name", model.getName());
+        jo.put("title", model.getTitle());
+        jo.put("description", model.getDescription());
+        jo.put("label", model.getLabel());
+
+        JsonArray options = new JsonArray();
+        if (model.getOptions() != null) {
+            model.getOptions().forEach(opt -> {
+                JsonObject optJson = new JsonObject();
+                optJson.put("name", opt.getName());
+                optJson.put("description", opt.getDescription());
+                optJson.put("type", opt.getType());
+                optJson.put("required", opt.isRequired());
+                optJson.put("defaultValue", opt.getDefaultValue());
+                options.add(optJson);
+            });
+        }
+        jo.put("options", options);
+
+        return jo;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExplainTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExplainTools.java
new file mode 100644
index 000000000000..0ea1ffd5364d
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExplainTools.java
@@ -0,0 +1,212 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import dev.langchain4j.model.chat.ChatModel;
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.EipModel;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * MCP Tool for explaining Camel routes using AI/LLM services.
+ * <p>
+ * The LLM is configured at the application level via application.properties. 
Supported configurations include:
+ * <ul>
+ * <li>Ollama: quarkus.langchain4j.ollama.*</li>
+ * <li>OpenAI: quarkus.langchain4j.openai.*</li>
+ * </ul>
+ */
+@ApplicationScoped
+public class ExplainTools {
+
+    private static final List<String> COMMON_COMPONENTS = Arrays.asList(
+            "kafka", "http", "https", "file", "timer", "direct", "seda",
+            "log", "mock", "rest", "rest-api", "sql", "jms", "activemq",
+            "aws2-s3", "aws2-sqs", "aws2-sns", "aws2-kinesis",
+            "azure-storage-blob", "azure-storage-queue",
+            "google-pubsub", "google-storage",
+            "mongodb", "couchdb", "cassandraql",
+            "elasticsearch", "opensearch",
+            "ftp", "sftp", "ftps",
+            "smtp", "imap", "pop3",
+            "websocket", "netty-http", "vertx-http",
+            "bean", "process", "script");
+
+    private static final List<String> COMMON_EIPS = Arrays.asList(
+            "split", "aggregate", "filter", "choice", "when", "otherwise",
+            "multicast", "recipientList", "routingSlip", "dynamicRouter",
+            "circuitBreaker", "throttle", "delay", "resequence",
+            "idempotentConsumer", "loadBalance", "saga",
+            "onException", "doTry", "doCatch", "doFinally",
+            "transform", "setBody", "setHeader", "removeHeader",
+            "marshal", "unmarshal", "convertBodyTo",
+            "enrich", "pollEnrich", "wireTap", "pipeline");
+
+    private final CamelCatalog catalog;
+
+    @Inject
+    ChatModel chatModel;
+
+    public ExplainTools() {
+        this.catalog = new DefaultCamelCatalog();
+    }
+
+    /**
+     * Tool to explain a Camel route using AI/LLM.
+     */
+    @Tool(description = "Explain what a Camel route does using AI/LLM. " +
+                        "Returns a human-readable explanation of the route's 
purpose and flow. " +
+                        "The LLM is configured at the server level via 
application.properties.")
+    public String camel_explain_route(
+            @ToolArg(description = "The Camel route content (YAML, XML, or 
Java DSL)") String route,
+            @ToolArg(description = "Route format: yaml, xml, or java (default: 
yaml)") String format,
+            @ToolArg(description = "Include Camel Catalog context in prompt 
(default: true)") Boolean catalogContext,
+            @ToolArg(description = "Include verbose technical details 
(default: false)") Boolean verbose) {
+
+        if (route == null || route.isBlank()) {
+            throw new ToolCallException("Route content is required", null);
+        }
+
+        if (chatModel == null) {
+            throw new ToolCallException(
+                    "No LLM configured. Please configure an LLM provider in 
application.properties " +
+                                        "(e.g., quarkus.langchain4j.ollama.* 
or quarkus.langchain4j.openai.*)",
+                    null);
+        }
+
+        // Set defaults
+        String resolvedFormat = format != null && !format.isBlank() ? 
format.toLowerCase() : "yaml";
+        boolean useCatalogContext = catalogContext == null || catalogContext;
+        boolean useVerbose = verbose != null && verbose;
+
+        // Build the prompt
+        String systemPrompt = buildSystemPrompt(useVerbose);
+        String userPrompt = buildUserPrompt(route, resolvedFormat, 
useCatalogContext);
+        String fullPrompt = systemPrompt + "\n\n" + userPrompt;
+
+        // Call the injected LLM
+        String explanation;
+        try {
+            explanation = chatModel.chat(fullPrompt);
+        } catch (Exception e) {
+            throw new ToolCallException("Error calling LLM: " + 
e.getMessage(), e);
+        }
+
+        if (explanation == null || explanation.isBlank()) {
+            throw new ToolCallException("Failed to get explanation from LLM", 
null);
+        }
+
+        // Build result
+        JsonObject result = new JsonObject();
+        result.put("explanation", explanation);
+        result.put("format", resolvedFormat);
+
+        return result.toJson();
+    }
+
+    private String buildSystemPrompt(boolean verbose) {
+        StringBuilder prompt = new StringBuilder();
+        prompt.append("You are an Apache Camel integration expert. ");
+        prompt.append("Your task is to explain Camel routes in plain 
English.\n\n");
+        prompt.append("Guidelines:\n");
+        prompt.append("- Start with a one-sentence summary\n");
+        prompt.append("- Describe step by step what the route does\n");
+        prompt.append("- Explain each component and EIP used\n");
+        prompt.append("- Mention error handling if present\n");
+        prompt.append("- Use bullet points for clarity\n");
+        prompt.append("- Format output as Markdown\n");
+
+        if (verbose) {
+            prompt.append("- Include technical details about options and 
expressions\n");
+        }
+
+        return prompt.toString();
+    }
+
+    private String buildUserPrompt(String routeContent, String fileExtension, 
boolean includeCatalogContext) {
+        StringBuilder prompt = new StringBuilder();
+
+        if (includeCatalogContext) {
+            String catalogInfo = buildCatalogContext(routeContent);
+            if (!catalogInfo.isEmpty()) {
+                prompt.append("Reference 
information:\n").append(catalogInfo).append("\n");
+            }
+        }
+
+        prompt.append("Format: 
").append(fileExtension.toUpperCase()).append("\n\n");
+        prompt.append("Route 
definition:\n```").append(fileExtension).append("\n");
+        prompt.append(routeContent).append("\n```\n\n");
+        prompt.append("Please explain this Camel route:");
+
+        return prompt.toString();
+    }
+
+    private String buildCatalogContext(String routeContent) {
+        StringBuilder context = new StringBuilder();
+        String lowerContent = routeContent.toLowerCase();
+
+        // Add component descriptions
+        for (String comp : COMMON_COMPONENTS) {
+            if (containsComponent(lowerContent, comp)) {
+                ComponentModel model = catalog.componentModel(comp);
+                if (model != null && model.getDescription() != null) {
+                    context.append("- ").append(comp).append(": 
").append(model.getDescription()).append("\n");
+                }
+            }
+        }
+
+        // Add EIP descriptions
+        for (String eip : COMMON_EIPS) {
+            if (lowerContent.contains(eip.toLowerCase()) || 
lowerContent.contains(camelCaseToDash(eip))) {
+                EipModel model = catalog.eipModel(eip);
+                if (model != null && model.getDescription() != null) {
+                    context.append("- ").append(eip).append(" (EIP): 
").append(model.getDescription()).append("\n");
+                }
+            }
+        }
+
+        return context.toString();
+    }
+
+    private boolean containsComponent(String content, String comp) {
+        return content.contains(comp + ":")
+                || content.contains("\"" + comp + "\"")
+                || content.contains("'" + comp + "'");
+    }
+
+    private String camelCaseToDash(String text) {
+        StringBuilder sb = new StringBuilder();
+        for (char c : text.toCharArray()) {
+            if (Character.isUpperCase(c) && sb.length() > 0) {
+                sb.append('-');
+            }
+            sb.append(Character.toLowerCase(c));
+        }
+        return sb.toString();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
new file mode 100644
index 000000000000..d80ba6ea10d6
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
@@ -0,0 +1,188 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkiverse.mcp.server.Tool;
+import io.quarkiverse.mcp.server.ToolArg;
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+import org.apache.camel.catalog.EndpointValidationResult;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * MCP Tools for validating and transforming Camel routes using Quarkus MCP 
Server.
+ */
+@ApplicationScoped
+public class TransformTools {
+
+    private final CamelCatalog catalog;
+
+    public TransformTools() {
+        this.catalog = new DefaultCamelCatalog();
+    }
+
+    /**
+     * Tool to validate a Camel route or endpoint URI.
+     */
+    @Tool(description = "Validate a Camel endpoint URI or route definition. " +
+                        "Checks syntax, required options, and valid parameter 
names.")
+    public String camel_validate_route(
+            @ToolArg(description = "Camel endpoint URI to validate (e.g., 
'kafka:myTopic?brokers=localhost:9092')") String uri,
+            @ToolArg(description = "YAML route definition to validate") String 
route) {
+
+        if (uri == null && route == null) {
+            throw new ToolCallException("Either 'uri' or 'route' is required", 
null);
+        }
+
+        JsonObject resultJson = new JsonObject();
+
+        if (uri != null) {
+            EndpointValidationResult validation = 
catalog.validateEndpointProperties(uri);
+            resultJson.put("uri", uri);
+            resultJson.put("valid", validation.isSuccess());
+
+            if (!validation.isSuccess()) {
+                JsonObject errors = new JsonObject();
+                if (validation.getUnknown() != null && 
!validation.getUnknown().isEmpty()) {
+                    errors.put("unknownOptions", String.join(", ", 
validation.getUnknown()));
+                }
+                if (validation.getRequired() != null && 
!validation.getRequired().isEmpty()) {
+                    errors.put("missingRequired", String.join(", ", 
validation.getRequired()));
+                }
+                if (validation.getInvalidEnum() != null && 
!validation.getInvalidEnum().isEmpty()) {
+                    errors.put("invalidEnumValues", 
validation.getInvalidEnum().toString());
+                }
+                if (validation.getInvalidInteger() != null && 
!validation.getInvalidInteger().isEmpty()) {
+                    errors.put("invalidIntegers", 
validation.getInvalidInteger().toString());
+                }
+                if (validation.getInvalidBoolean() != null && 
!validation.getInvalidBoolean().isEmpty()) {
+                    errors.put("invalidBooleans", 
validation.getInvalidBoolean().toString());
+                }
+                if (validation.getSyntaxError() != null) {
+                    errors.put("syntaxError", validation.getSyntaxError());
+                }
+                resultJson.put("errors", errors);
+
+                if (validation.getUnknown() != null && 
validation.getUnknownSuggestions() != null) {
+                    JsonObject suggestions = new JsonObject();
+                    for (String unknown : validation.getUnknown()) {
+                        String[] suggestionArr = 
validation.getUnknownSuggestions().get(unknown);
+                        if (suggestionArr != null && suggestionArr.length > 0) 
{
+                            suggestions.put(unknown, String.join(", ", 
suggestionArr));
+                        }
+                    }
+                    if (!suggestions.isEmpty()) {
+                        resultJson.put("suggestions", suggestions);
+                    }
+                }
+            }
+        }
+
+        if (route != null) {
+            resultJson.put("routeProvided", true);
+            resultJson.put("valid", true);
+            resultJson.put("note",
+                    "Full route validation requires loading the route into a 
CamelContext. " +
+                                   "Use 'camel run --validate' for complete 
validation.");
+
+            List<String> uris = extractUrisFromRoute(route);
+            if (!uris.isEmpty()) {
+                JsonObject uriValidations = new JsonObject();
+                boolean allValid = true;
+                for (String extractedUri : uris) {
+                    EndpointValidationResult validation = 
catalog.validateEndpointProperties(extractedUri);
+                    uriValidations.put(extractedUri, validation.isSuccess());
+                    if (!validation.isSuccess()) {
+                        allValid = false;
+                    }
+                }
+                resultJson.put("uriValidations", uriValidations);
+                resultJson.put("valid", allValid);
+            }
+        }
+
+        return resultJson.toJson();
+    }
+
+    /**
+     * Tool to transform routes between DSL formats.
+     */
+    @Tool(description = "Transform a Camel route between different DSL formats 
(YAML, XML). " +
+                        "Note: Java to YAML/XML transformation has 
limitations.")
+    public String camel_transform_route(
+            @ToolArg(description = "Route definition to transform") String 
route,
+            @ToolArg(description = "Source format (yaml, xml, java)") String 
fromFormat,
+            @ToolArg(description = "Target format (yaml, xml)") String 
toFormat) {
+
+        if (route == null || fromFormat == null || toFormat == null) {
+            throw new ToolCallException("route, fromFormat, and toFormat are 
required", null);
+        }
+
+        JsonObject resultJson = new JsonObject();
+        resultJson.put("fromFormat", fromFormat);
+        resultJson.put("toFormat", toFormat);
+        resultJson.put("note",
+                "Route transformation requires the full Camel route parser. " +
+                               "Use 'camel transform route' CLI command for 
complete transformation.");
+        resultJson.put("supported", true);
+        resultJson.put("recommendation",
+                "Use 'camel transform route --format=" + toFormat + " <file>' 
for DSL transformation");
+
+        return resultJson.toJson();
+    }
+
+    /**
+     * Extract endpoint URIs from a YAML route definition.
+     */
+    private List<String> extractUrisFromRoute(String route) {
+        List<String> uris = new ArrayList<>();
+
+        String[] lines = route.split("\n");
+        for (String line : lines) {
+            line = line.trim();
+            if (line.contains(":") && !line.startsWith("#")) {
+                int colonPos = line.indexOf(":");
+                if (colonPos > 0 && colonPos < line.length() - 1) {
+                    String key = line.substring(0, colonPos).trim();
+                    String value = line.substring(colonPos + 1).trim();
+
+                    if (value.startsWith("\"") && value.endsWith("\"")) {
+                        value = value.substring(1, value.length() - 1);
+                    } else if (value.startsWith("'") && value.endsWith("'")) {
+                        value = value.substring(1, value.length() - 1);
+                    }
+
+                    if ((key.equals("uri") || key.equals("from") || 
key.equals("to"))
+                            && value.contains(":") && !value.startsWith("$")) {
+                        String scheme = value.split(":")[0];
+                        if (catalog.findComponentNames().contains(scheme)) {
+                            uris.add(value);
+                        }
+                    }
+                }
+            }
+        }
+
+        return uris;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
new file mode 100644
index 000000000000..170fb968b2aa
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
@@ -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.
+## ---------------------------------------------------------------------------
+# Camel MCP Server Configuration
+# This file configures the Quarkus MCP Server for Apache Camel
+
+# Server name and version for MCP protocol
+quarkus.application.name=camel-mcp-server
+quarkus.application.version=${camel.version:4.18.0-SNAPSHOT}
+
+# Disable banner for cleaner stdio output
+quarkus.banner.enabled=false
+
+# Logging configuration - stderr only to avoid interfering with stdio transport
+quarkus.log.console.stderr=true
+quarkus.log.level=WARN
+quarkus.log.category."org.apache.camel".level=INFO
+quarkus.log.category."io.quarkiverse.mcp".level=INFO
+
+# LLM Configuration for Camel route explanation
+# Configure Ollama (default local LLM)
+quarkus.langchain4j.ollama.base-url=${OLLAMA_BASE_URL:http://localhost:11434}
+quarkus.langchain4j.ollama.chat-model.model-id=${OLLAMA_MODEL:llama3.2}
+quarkus.langchain4j.ollama.timeout=120s
+
+# To use OpenAI instead, set these environment variables:
+# QUARKUS_LANGCHAIN4J_OPENAI_API_KEY=your-api-key
+# QUARKUS_LANGCHAIN4J_OPENAI_CHAT_MODEL_MODEL_NAME=gpt-4o-mini
diff --git a/dsl/camel-jbang/pom.xml b/dsl/camel-jbang/pom.xml
index 539a068e2295..20b03bf31eef 100644
--- a/dsl/camel-jbang/pom.xml
+++ b/dsl/camel-jbang/pom.xml
@@ -37,6 +37,7 @@
         <module>camel-jbang-console</module>
         <module>camel-jbang-core</module>
         <module>camel-jbang-main</module>
+        <module>camel-jbang-mcp</module>
         <module>camel-jbang-plugin-generate</module>
         <module>camel-jbang-plugin-edit</module>
         <module>camel-jbang-plugin-kubernetes</module>


Reply via email to