This is an automated email from the ASF dual-hosted git repository.
acosentino 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 88d6605ac977 CAMEL-22886 - Camel-Jbang: Add an MCP module (#20976)
88d6605ac977 is described below
commit 88d6605ac977d34d84bb81106dbf152e801e799e
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Jan 22 13:36:33 2026 +0100
CAMEL-22886 - Camel-Jbang: Add an MCP module (#20976)
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../jbang/core/commands/version/VersionList.java | 34 +-
dsl/camel-jbang/camel-jbang-mcp/pom.xml | 133 +++++++
.../dsl/jbang/core/commands/mcp/CatalogTools.java | 404 +++++++++++++++++++++
.../dsl/jbang/core/commands/mcp/ExplainTools.java | 167 +++++++++
.../jbang/core/commands/mcp/TransformTools.java | 216 +++++++++++
.../dsl/jbang/core/commands/mcp/VersionTools.java | 122 +++++++
.../src/main/resources/application.properties | 31 ++
dsl/camel-jbang/pom.xml | 1 +
8 files changed, 1091 insertions(+), 17 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionList.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionList.java
index ed595831bca1..e5a60eee2523 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionList.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionList.java
@@ -83,65 +83,65 @@ public class VersionList extends CamelCommand {
completionCandidates =
RuntimeCompletionCandidates.class,
converter = RuntimeTypeConverter.class,
description = "Runtime (${COMPLETION-CANDIDATES})")
- RuntimeType runtime = RuntimeType.main;
+ public RuntimeType runtime = RuntimeType.main;
@CommandLine.Option(names = { "--from-version" },
description = "Filter by Camel version (inclusive).
Will start from 4.0 if no version ranges provided.")
- String fromVersion;
+ public String fromVersion;
@CommandLine.Option(names = { "--to-version" },
description = "Filter by Camel version (exclusive)")
- String toVersion;
+ public String toVersion;
@CommandLine.Option(names = { "--from-date" },
description = "Filter by release date (inclusive)")
- String fromDate;
+ public String fromDate;
@CommandLine.Option(names = { "--to-date" },
description = "Filter by release date (exclusive)")
- String toDate;
+ public String toDate;
@CommandLine.Option(names = { "--sort" },
description = "Sort by (version, date, or days)",
defaultValue = "version")
- String sort;
+ public String sort;
@CommandLine.Option(names = { "--repo", "--repos" },
description = "Additional maven repositories (Use
commas to separate multiple repositories)")
- String repositories;
+ public String repositories;
@CommandLine.Option(names = { "--lts" }, description = "Only show LTS
supported releases", defaultValue = "false")
- boolean lts;
+ public boolean lts;
@CommandLine.Option(names = { "--eol" }, description = "Include releases
that are end-of-life", defaultValue = "true")
- boolean eol = true;
+ public boolean eol = true;
@CommandLine.Option(names = { "--patch" }, description = "Whether to
include patch releases (x.y.z)", defaultValue = "true")
- boolean patch = true;
+ public boolean patch = true;
@CommandLine.Option(names = { "--rc" }, description = "Include also
milestone or RC releases", defaultValue = "false")
- boolean rc;
+ public boolean rc;
@CommandLine.Option(names = { "--days" }, description = "Whether to
include days since release", defaultValue = "true")
- boolean days;
+ public boolean days;
@CommandLine.Option(names = { "--date-format" }, description = "The format
to show the date (such as dd-MM-yyyy)",
defaultValue = DEFAULT_DATE_FORMAT)
- String dateFormat;
+ public String dateFormat;
@CommandLine.Option(names = { "--tail" },
description = "The number of lines from the end of the
table to show.")
- int tail;
+ public int tail;
@CommandLine.Option(names = { "--fresh" }, description = "Make sure we use
fresh (i.e. non-cached) resources",
defaultValue = "false")
- boolean fresh;
+ public boolean fresh;
@CommandLine.Option(names = { "--download" }, defaultValue = "true",
description = "Whether to allow automatic downloading
JAR dependencies (over the internet)")
- boolean download = true;
+ public boolean download = true;
@CommandLine.Option(names = { "--json" }, description = "Output in JSON
Format", defaultValue = "false")
- boolean jsonOutput;
+ public boolean jsonOutput;
public VersionList(CamelJBangMain main) {
super(main);
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..052ad258c5ee
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-mcp/pom.xml
@@ -0,0 +1,133 @@
+<?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</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>
+
+ <!-- Camel JBang Core for catalog and utilities -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-jbang-core</artifactId>
+ <version>${project.version}</version>
+ </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/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..29f813da9014
--- /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,404 @@
+/*
+ * 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 java.util.stream.Collectors;
+
+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.dsl.jbang.core.common.CatalogLoader;
+import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+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;
+
+/**
+ * MCP Tools for querying the Camel Catalog using Quarkus MCP Server.
+ */
+@ApplicationScoped
+public class CatalogTools {
+
+ private CamelCatalog catalog;
+
+ public CatalogTools() {
+ this.catalog = new DefaultCamelCatalog(true);
+ }
+
+ /**
+ * 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 ComponentListResult 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,
+ @ToolArg(description = "Runtime type: main, spring-boot, or
quarkus (default: main)") String runtime,
+ @ToolArg(description = "Specific Camel version to query (e.g.,
4.4.0). If not specified, uses the default catalog version.") String
camelVersion) {
+
+ int maxResults = limit != null ? limit : 50;
+
+ try {
+ CamelCatalog cat = loadCatalog(runtime, camelVersion);
+
+ List<ComponentInfo> components = findComponentNames(cat).stream()
+ .map(cat::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::toComponentInfo)
+ .collect(Collectors.toList());
+
+ return new ComponentListResult(components.size(),
cat.getCatalogVersion(), components);
+ } 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 ComponentDetailResult camel_catalog_component_doc(
+ @ToolArg(description = "Component name (e.g., kafka, http, file,
timer)") String component,
+ @ToolArg(description = "Runtime type: main, spring-boot, or
quarkus (default: main)") String runtime,
+ @ToolArg(description = "Specific Camel version to query (e.g.,
4.4.0). If not specified, uses the default catalog version.") String
camelVersion) {
+
+ if (component == null || component.isBlank()) {
+ throw new ToolCallException("Component name is required", null);
+ }
+
+ try {
+ CamelCatalog cat = loadCatalog(runtime, camelVersion);
+ ComponentModel model = cat.componentModel(component);
+ if (model == null) {
+ throw new ToolCallException("Component not found: " +
component, null);
+ }
+
+ return toComponentDetailResult(model);
+ } catch (ToolCallException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ToolCallException("Failed to get component doc: " +
e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Tool to list data formats.
+ */
+ @Tool(description = "List available Camel data formats for
marshalling/unmarshalling " +
+ "(e.g., json, xml, csv, avro, protobuf).")
+ public DataFormatListResult 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<DataFormatInfo> 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::toDataFormatInfo)
+ .collect(Collectors.toList());
+
+ return new DataFormatListResult(dataFormats.size(), dataFormats);
+ } 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 LanguageListResult camel_catalog_languages(
+ @ToolArg(description = "Filter by name") String filter) {
+
+ try {
+ List<LanguageInfo> languages = catalog.findLanguageNames().stream()
+ .map(catalog::languageModel)
+ .filter(m -> m != null)
+ .filter(m -> matchesFilter(m.getName(), m.getTitle(),
m.getDescription(), filter))
+ .map(this::toLanguageInfo)
+ .collect(Collectors.toList());
+
+ return new LanguageListResult(languages.size(), languages);
+ } 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 EipListResult 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<EipInfo> 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::toEipInfo)
+ .collect(Collectors.toList());
+
+ return new EipListResult(eips.size(), eips);
+ } 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 EipDetailResult 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);
+ }
+
+ return toEipDetailResult(model);
+ }
+
+ // Catalog loading
+
+ private CamelCatalog loadCatalog(String runtime, String camelVersion)
throws Exception {
+ // If a specific version is requested, load that version's catalog
+ if (camelVersion != null && !camelVersion.isBlank()) {
+ RuntimeType runtimeType = resolveRuntime(runtime);
+ if (runtimeType == RuntimeType.springBoot) {
+ return CatalogLoader.loadSpringBootCatalog(null, camelVersion,
true);
+ } else if (runtimeType == RuntimeType.quarkus) {
+ return CatalogLoader.loadQuarkusCatalog(null, camelVersion,
null, true);
+ } else {
+ return CatalogLoader.loadCatalog(null, camelVersion, true);
+ }
+ }
+
+ // No specific version, use runtime-specific catalog or default
+ if (runtime == null || runtime.isBlank() ||
"main".equalsIgnoreCase(runtime)) {
+ return catalog;
+ }
+
+ RuntimeType runtimeType = RuntimeType.fromValue(runtime);
+ if (runtimeType == RuntimeType.springBoot) {
+ return CatalogLoader.loadSpringBootCatalog(null, null, true);
+ } else if (runtimeType == RuntimeType.quarkus) {
+ return CatalogLoader.loadQuarkusCatalog(null,
RuntimeType.QUARKUS_VERSION, null, true);
+ }
+
+ return catalog;
+ }
+
+ private RuntimeType resolveRuntime(String runtime) {
+ if (runtime == null || runtime.isBlank() ||
"main".equalsIgnoreCase(runtime)) {
+ return RuntimeType.main;
+ }
+ return RuntimeType.fromValue(runtime);
+ }
+
+ private static List<String> findComponentNames(CamelCatalog catalog) {
+ List<String> answer = catalog.findComponentNames();
+ List<String> copy = new ArrayList<>(answer);
+ copy.removeIf(String::isBlank);
+ return copy;
+ }
+
+ // 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());
+ }
+
+ // Mapping methods
+
+ private ComponentInfo toComponentInfo(ComponentModel model) {
+ return new ComponentInfo(
+ model.getScheme(),
+ model.getTitle(),
+ model.getDescription(),
+ model.getLabel(),
+ model.isDeprecated(),
+ model.getSupportLevel() != null ?
model.getSupportLevel().name() : null);
+ }
+
+ private ComponentDetailResult toComponentDetailResult(ComponentModel
model) {
+ List<OptionInfo> componentOptions = new ArrayList<>();
+ if (model.getComponentOptions() != null) {
+ model.getComponentOptions().forEach(opt ->
componentOptions.add(new OptionInfo(
+ opt.getName(),
+ opt.getDescription(),
+ opt.getType(),
+ opt.isRequired(),
+ opt.getDefaultValue() != null ?
opt.getDefaultValue().toString() : null,
+ null)));
+ }
+
+ List<OptionInfo> endpointOptions = new ArrayList<>();
+ if (model.getEndpointOptions() != null) {
+ model.getEndpointOptions().forEach(opt -> endpointOptions.add(new
OptionInfo(
+ opt.getName(),
+ opt.getDescription(),
+ opt.getType(),
+ opt.isRequired(),
+ opt.getDefaultValue() != null ?
opt.getDefaultValue().toString() : null,
+ opt.getGroup())));
+ }
+
+ return new ComponentDetailResult(
+ model.getScheme(),
+ model.getTitle(),
+ model.getDescription(),
+ model.getLabel(),
+ model.isDeprecated(),
+ model.getSupportLevel() != null ?
model.getSupportLevel().name() : null,
+ model.getGroupId(),
+ model.getArtifactId(),
+ model.getVersion(),
+ model.getSyntax(),
+ model.isAsync(),
+ model.isConsumerOnly(),
+ model.isProducerOnly(),
+ componentOptions,
+ endpointOptions);
+ }
+
+ private DataFormatInfo toDataFormatInfo(DataFormatModel model) {
+ return new DataFormatInfo(
+ model.getName(),
+ model.getTitle(),
+ model.getDescription(),
+ model.isDeprecated());
+ }
+
+ private LanguageInfo toLanguageInfo(LanguageModel model) {
+ return new LanguageInfo(
+ model.getName(),
+ model.getTitle(),
+ model.getDescription());
+ }
+
+ private EipInfo toEipInfo(EipModel model) {
+ return new EipInfo(
+ model.getName(),
+ model.getTitle(),
+ model.getDescription(),
+ model.getLabel());
+ }
+
+ private EipDetailResult toEipDetailResult(EipModel model) {
+ List<OptionInfo> options = new ArrayList<>();
+ if (model.getOptions() != null) {
+ model.getOptions().forEach(opt -> options.add(new OptionInfo(
+ opt.getName(),
+ opt.getDescription(),
+ opt.getType(),
+ opt.isRequired(),
+ opt.getDefaultValue() != null ?
opt.getDefaultValue().toString() : null,
+ null)));
+ }
+
+ return new EipDetailResult(
+ model.getName(),
+ model.getTitle(),
+ model.getDescription(),
+ model.getLabel(),
+ options);
+ }
+
+ // Result record classes for Jackson serialization
+
+ public record ComponentListResult(int count, String camelVersion,
List<ComponentInfo> components) {
+ }
+
+ public record ComponentInfo(String name, String title, String description,
String label,
+ boolean deprecated, String supportLevel) {
+ }
+
+ public record ComponentDetailResult(String name, String title, String
description, String label,
+ boolean deprecated, String supportLevel, String groupId, String
artifactId,
+ String version, String syntax, boolean async, boolean
consumerOnly, boolean producerOnly,
+ List<OptionInfo> componentOptions, List<OptionInfo>
endpointOptions) {
+ }
+
+ public record OptionInfo(String name, String description, String type,
boolean required,
+ String defaultValue, String group) {
+ }
+
+ public record DataFormatListResult(int count, List<DataFormatInfo>
dataFormats) {
+ }
+
+ public record DataFormatInfo(String name, String title, String
description, boolean deprecated) {
+ }
+
+ public record LanguageListResult(int count, List<LanguageInfo> languages) {
+ }
+
+ public record LanguageInfo(String name, String title, String description) {
+ }
+
+ public record EipListResult(int count, List<EipInfo> eips) {
+ }
+
+ public record EipInfo(String name, String title, String description,
String label) {
+ }
+
+ public record EipDetailResult(String name, String title, String
description, String label,
+ List<OptionInfo> options) {
+ }
+}
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..6e24a7da9d13
--- /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,167 @@
+/*
+ * 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.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.EipModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * MCP Tool for providing enriched context about Camel routes.
+ * <p>
+ * This tool extracts components and EIPs used in a route and returns their
documentation from the Camel Catalog. The
+ * calling LLM can use this context to formulate its own explanation of the
route.
+ */
+@ApplicationScoped
+public class ExplainTools {
+
+ private final CamelCatalog catalog;
+
+ public ExplainTools() {
+ this.catalog = new DefaultCamelCatalog();
+ }
+
+ /**
+ * Tool to get enriched context for a Camel route.
+ */
+ @Tool(description = "Get enriched context for a Camel route including
documentation for all components and EIPs used. "
+ +
+ "Returns structured data with component descriptions,
EIP explanations, and route structure. " +
+ "Use this context to understand and explain the
route.")
+ public String camel_route_context(
+ @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) {
+
+ if (route == null || route.isBlank()) {
+ throw new ToolCallException("Route content is required", null);
+ }
+
+ String resolvedFormat = format != null && !format.isBlank() ?
format.toLowerCase() : "yaml";
+
+ JsonObject result = new JsonObject();
+ result.put("format", resolvedFormat);
+ result.put("route", route);
+
+ // Extract and document components
+ List<String> componentNames = extractComponents(route);
+ JsonArray components = new JsonArray();
+ for (String comp : componentNames) {
+ ComponentModel model = catalog.componentModel(comp);
+ if (model != null) {
+ JsonObject compJson = new JsonObject();
+ compJson.put("name", comp);
+ compJson.put("title", model.getTitle());
+ compJson.put("description", model.getDescription());
+ compJson.put("label", model.getLabel());
+ compJson.put("syntax", model.getSyntax());
+ compJson.put("producerOnly", model.isProducerOnly());
+ compJson.put("consumerOnly", model.isConsumerOnly());
+ components.add(compJson);
+ }
+ }
+ result.put("components", components);
+
+ // Extract and document EIPs
+ List<String> eipNames = extractEips(route);
+ JsonArray eips = new JsonArray();
+ for (String eip : eipNames) {
+ EipModel model = catalog.eipModel(eip);
+ if (model != null) {
+ JsonObject eipJson = new JsonObject();
+ eipJson.put("name", eip);
+ eipJson.put("title", model.getTitle());
+ eipJson.put("description", model.getDescription());
+ eipJson.put("label", model.getLabel());
+ eips.add(eipJson);
+ }
+ }
+ result.put("eips", eips);
+
+ // Add summary counts
+ JsonObject summary = new JsonObject();
+ summary.put("componentCount", components.size());
+ summary.put("eipCount", eips.size());
+ result.put("summary", summary);
+
+ return result.toJson();
+ }
+
+ /**
+ * Extract component names from route content.
+ */
+ private List<String> extractComponents(String route) {
+ List<String> found = new ArrayList<>();
+ String lowerRoute = route.toLowerCase();
+
+ for (String comp : catalog.findComponentNames()) {
+ if (containsComponent(lowerRoute, comp)) {
+ found.add(comp);
+ }
+ }
+
+ return found;
+ }
+
+ /**
+ * Extract EIP names from route content.
+ */
+ private List<String> extractEips(String route) {
+ List<String> found = new ArrayList<>();
+ String lowerRoute = route.toLowerCase();
+
+ for (String eip : catalog.findModelNames()) {
+ EipModel model = catalog.eipModel(eip);
+ if (model != null) {
+ String eipLower = eip.toLowerCase();
+ String eipDash = camelCaseToDash(eip);
+ if (lowerRoute.contains(eipLower) ||
lowerRoute.contains(eipDash)) {
+ found.add(eip);
+ }
+ }
+ }
+
+ return found;
+ }
+
+ 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..19ee6a216386
--- /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,216 @@
+/*
+ * 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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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;
+
+/**
+ * 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(true);
+ }
+
+ /**
+ * 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 ValidationResult 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);
+ }
+
+ ValidationResult result = new ValidationResult();
+
+ if (uri != null) {
+ result.uri = uri;
+ EndpointValidationResult validation =
catalog.validateEndpointProperties(uri);
+ result.valid = validation.isSuccess();
+
+ if (!validation.isSuccess()) {
+ ValidationErrors errors = new ValidationErrors();
+ if (validation.getUnknown() != null &&
!validation.getUnknown().isEmpty()) {
+ errors.unknownOptions = String.join(", ",
validation.getUnknown());
+ }
+ if (validation.getRequired() != null &&
!validation.getRequired().isEmpty()) {
+ errors.missingRequired = String.join(", ",
validation.getRequired());
+ }
+ if (validation.getInvalidEnum() != null &&
!validation.getInvalidEnum().isEmpty()) {
+ errors.invalidEnumValues =
validation.getInvalidEnum().toString();
+ }
+ if (validation.getInvalidInteger() != null &&
!validation.getInvalidInteger().isEmpty()) {
+ errors.invalidIntegers =
validation.getInvalidInteger().toString();
+ }
+ if (validation.getInvalidBoolean() != null &&
!validation.getInvalidBoolean().isEmpty()) {
+ errors.invalidBooleans =
validation.getInvalidBoolean().toString();
+ }
+ if (validation.getSyntaxError() != null) {
+ errors.syntaxError = validation.getSyntaxError();
+ }
+ result.errors = errors;
+
+ if (validation.getUnknown() != null &&
validation.getUnknownSuggestions() != null) {
+ Map<String, String> suggestions = new HashMap<>();
+ 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()) {
+ result.suggestions = suggestions;
+ }
+ }
+ }
+ }
+
+ if (route != null) {
+ result.routeProvided = true;
+ result.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()) {
+ Map<String, Boolean> uriValidations = new HashMap<>();
+ boolean allValid = true;
+ for (String extractedUri : uris) {
+ EndpointValidationResult validation =
catalog.validateEndpointProperties(extractedUri);
+ uriValidations.put(extractedUri, validation.isSuccess());
+ if (!validation.isSuccess()) {
+ allValid = false;
+ }
+ }
+ result.uriValidations = uriValidations;
+ result.valid = allValid;
+ } else {
+ result.valid = true;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * 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 TransformResult 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);
+ }
+
+ TransformResult result = new TransformResult();
+ result.fromFormat = fromFormat;
+ result.toFormat = toFormat;
+ result.note = "Route transformation requires the full Camel route
parser. " +
+ "Use 'camel transform route' CLI command for complete
transformation.";
+ result.supported = true;
+ result.recommendation = "Use 'camel transform route --format=" +
toFormat + " <file>' for DSL transformation";
+
+ return result;
+ }
+
+ /**
+ * 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;
+ }
+
+ // Result classes for Jackson serialization
+
+ public static class ValidationResult {
+ public String uri;
+ public boolean valid;
+ public boolean routeProvided;
+ public String note;
+ public ValidationErrors errors;
+ public Map<String, String> suggestions;
+ public Map<String, Boolean> uriValidations;
+ }
+
+ public static class ValidationErrors {
+ public String unknownOptions;
+ public String missingRequired;
+ public String invalidEnumValues;
+ public String invalidIntegers;
+ public String invalidBooleans;
+ public String syntaxError;
+ }
+
+ public static class TransformResult {
+ public String fromFormat;
+ public String toFormat;
+ public String note;
+ public boolean supported;
+ public String recommendation;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
new file mode 100644
index 000000000000..eaced47c0bba
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
@@ -0,0 +1,122 @@
+/*
+ * 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.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.commands.version.VersionList;
+import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+
+/**
+ * MCP Tools for querying available Camel versions using Quarkus MCP Server.
+ */
+@ApplicationScoped
+public class VersionTools {
+
+ /**
+ * Tool to list available Camel versions for a specific runtime.
+ */
+ @Tool(description = "List available Camel versions for a specific runtime
(main, spring-boot, quarkus). " +
+ "Returns version information including release date,
JDK requirements, and LTS status.")
+ public VersionListResult camel_version_list(
+ @ToolArg(description = "Runtime type: main, spring-boot, or
quarkus (default: main)") String runtime,
+ @ToolArg(description = "Only show LTS (Long Term Support) releases
(default: false)") Boolean lts,
+ @ToolArg(description = "Minimum Camel version to include (e.g.,
4.0)") String fromVersion,
+ @ToolArg(description = "Maximum number of versions to return
(default: 10)") Integer limit) {
+
+ try {
+ StringPrinter printer = new StringPrinter();
+ CamelJBangMain main = new CamelJBangMain().withPrinter(printer);
+
+ VersionList versionList = new VersionList(main);
+ versionList.runtime = resolveRuntime(runtime);
+ versionList.jsonOutput = true;
+ versionList.lts = lts != null && lts;
+ versionList.fromVersion = fromVersion != null ? fromVersion :
"4.0";
+ versionList.sort = "-version"; // newest first
+ versionList.download = true;
+ versionList.fresh = false;
+
+ int exitCode = versionList.doCall();
+ if (exitCode != 0) {
+ throw new ToolCallException("Failed to list versions, exit
code: " + exitCode, null);
+ }
+
+ String jsonOutput = printer.getOutput();
+ JsonArray versions = (JsonArray) Jsoner.deserialize(jsonOutput);
+
+ List<VersionInfo> versionInfos = new ArrayList<>();
+ int maxResults = limit != null ? limit : 10;
+ int count = 0;
+
+ for (Object obj : versions) {
+ if (count >= maxResults) {
+ break;
+ }
+ JsonObject v = (JsonObject) obj;
+ versionInfos.add(new VersionInfo(
+ v.getString("camelVersion"),
+ v.getString("runtime"),
+ v.getString("runtimeVersion"),
+ v.getString("jdkVersion"),
+ v.getString("kind"),
+ v.getString("releaseDate"),
+ v.getString("eolDate")));
+ count++;
+ }
+
+ return new VersionListResult(versionInfos.size(), versionInfos);
+ } catch (ToolCallException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ToolCallException("Failed to list versions: " +
e.getMessage(), e);
+ }
+ }
+
+ private RuntimeType resolveRuntime(String runtime) {
+ if (runtime == null || runtime.isBlank() ||
"main".equalsIgnoreCase(runtime)) {
+ return RuntimeType.main;
+ }
+ return RuntimeType.fromValue(runtime);
+ }
+
+ // Result classes for Jackson serialization
+
+ public record VersionListResult(int count, List<VersionInfo> versions) {
+ }
+
+ public record VersionInfo(
+ String camelVersion,
+ String runtime,
+ String runtimeVersion,
+ String jdkVersion,
+ String kind,
+ String releaseDate,
+ String eolDate) {
+ }
+}
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..16a9e79de4f8
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
@@ -0,0 +1,31 @@
+## ---------------------------------------------------------------------------
+## 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
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>