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

gnodet 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 68b37ecf8db5 Add MCP tools to browse Camel JBang examples
68b37ecf8db5 is described below

commit 68b37ecf8db5a349f8d6a1806850d825c6737c56
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri May 22 11:50:10 2026 +0200

    Add MCP tools to browse Camel JBang examples
    
    - Add camel_catalog_examples tool to list/search examples by name, tag, or 
difficulty level
    - Add camel_catalog_example_file tool to read file contents from bundled 
examples
      (returns GitHub URL for non-bundled ones)
    - Reuses existing ExampleHelper and camel-jbang-example-catalog.json
    - Documents the new tools in camel-jbang-mcp.adoc
    - Javadoc with @since 4.21 tags
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang-mcp.adoc        |  37 ++++-
 .../dsl/jbang/core/commands/mcp/ExampleTools.java  | 166 +++++++++++++++++++++
 2 files changed, 202 insertions(+), 1 deletion(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index a65c413b0d96..4781c437a76c 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -34,7 +34,7 @@ over the MCP protocol.
 
 == Available Tools
 
-The server exposes 28 tools organized into eleven functional areas, plus 3 
prompts that provide structured
+The server exposes 30 tools organized into twelve functional areas, plus 3 
prompts that provide structured
 multi-step workflows.
 
 === Catalog Exploration
@@ -92,6 +92,23 @@ multi-step workflows.
   examples, and the Kamelet's Maven dependencies.
 |===
 
+=== Example Catalog
+
+[cols="1,3",options="header"]
+|===
+| Tool | Description
+
+| `camel_catalog_examples`
+| List available Camel JBang examples with filtering by name, description, or 
tag (case-insensitive substring match).
+  Supports filtering by difficulty level (`beginner`, `intermediate`, 
`advanced`) and limiting the number of results.
+  Returns name, title, description, level, tags, and file list for each 
example.
+
+| `camel_catalog_example_file`
+| Get the content of a specific file from a Camel JBang example. For bundled 
examples, returns the file content
+  directly. For non-bundled examples, returns a GitHub URL where the file can 
be found. Use `camel_catalog_examples`
+  first to discover example names and their files.
+|===
+
 === Route Understanding
 
 [cols="1,3",options="header"]
@@ -468,6 +485,24 @@ What options does the aws-s3-source kamelet accept?
 The assistant calls `camel_catalog_kamelet_doc` with `kamelet=aws-s3-source` 
and returns the complete property
 list including required fields, types, defaults, and Maven dependencies.
 
+=== Browsing Examples
+
+----
+Show me beginner-level Camel examples related to REST
+----
+
+The assistant calls `camel_catalog_examples` with `filter=rest` and 
`level=beginner` and returns matching examples
+with their name, title, description, difficulty level, tags, and file list.
+
+To read a specific file from an example:
+
+----
+Show me the route file from the rest-api example
+----
+
+The assistant calls `camel_catalog_example_file` with `example=rest-api` and 
`file=route.camel.yaml` and returns
+the file content directly for bundled examples, or a GitHub URL for 
non-bundled ones.
+
 === Validating an Endpoint URI
 
 ----
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
new file mode 100644
index 000000000000..02c8fc78f7af
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
@@ -0,0 +1,166 @@
+/*
+ * 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.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+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.common.ExampleHelper;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * MCP Tools for browsing Camel JBang examples from the
+ * <a 
href="https://github.com/apache/camel-jbang-examples";>camel-jbang-examples</a> 
repository.
+ * <p>
+ * Provides two tools:
+ * <ul>
+ * <li>{@code camel_catalog_examples} — list and filter available examples by 
name, tag, or difficulty level</li>
+ * <li>{@code camel_catalog_example_file} — read the content of a specific 
file from a bundled example, or get a GitHub
+ * URL for non-bundled ones</li>
+ * </ul>
+ * Uses {@link ExampleHelper} for catalog loading, filtering, and file 
retrieval.
+ *
+ * @since 4.21
+ */
+@ApplicationScoped
+public class ExampleTools {
+
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = "List available Camel JBang examples. "
+                        + "Returns name, title, description, difficulty level, 
and tags. "
+                        + "Use filter to search by name, description, or tag. "
+                        + "Use level to filter by difficulty (beginner, 
intermediate, advanced).")
+    public ExampleListResult camel_catalog_examples(
+            @ToolArg(description = "Filter examples by name, description, or 
tag (case-insensitive substring match)") String filter,
+            @ToolArg(description = "Filter by difficulty level: beginner, 
intermediate, or advanced") String level,
+            @ToolArg(description = "Maximum number of results to return 
(default: 50)") Integer limit) {
+
+        int maxResults = limit != null ? limit : 50;
+
+        try {
+            List<JsonObject> catalog = ExampleHelper.loadCatalog();
+            List<JsonObject> filtered = ExampleHelper.filterExamples(catalog, 
filter);
+
+            List<ExampleInfo> result = new ArrayList<>();
+            for (JsonObject entry : filtered) {
+                if (level != null && !level.isBlank()) {
+                    String entryLevel = entry.getString("level");
+                    if (entryLevel == null || 
!entryLevel.equalsIgnoreCase(level)) {
+                        continue;
+                    }
+                }
+
+                result.add(toExampleInfo(entry));
+
+                if (result.size() >= maxResults) {
+                    break;
+                }
+            }
+
+            return new ExampleListResult(result.size(), result);
+        } catch (Throwable e) {
+            throw new ToolCallException(
+                    "Failed to list examples (" + e.getClass().getName() + "): 
" + e.getMessage(), null);
+        }
+    }
+
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = "Get the content of a specific file from a Camel JBang 
example. "
+                        + "Use camel_catalog_examples first to find the 
example name and its files. "
+                        + "Only bundled examples can return file contents 
directly; "
+                        + "for non-bundled examples, a GitHub URL is returned 
instead.")
+    public ExampleFileResult camel_catalog_example_file(
+            @ToolArg(description = "Example name (e.g., timer-log, rest-api, 
circuit-breaker)") String example,
+            @ToolArg(description = "File name within the example (e.g., 
route.camel.yaml, application.properties)") String file) {
+
+        if (example == null || example.isBlank()) {
+            throw new ToolCallException("Example name is required", null);
+        }
+        if (file == null || file.isBlank()) {
+            throw new ToolCallException("File name is required", null);
+        }
+
+        try {
+            List<JsonObject> catalog = ExampleHelper.loadCatalog();
+            JsonObject entry = ExampleHelper.findExample(catalog, example);
+            if (entry == null) {
+                throw new ToolCallException("Example not found: " + example, 
null);
+            }
+
+            List<String> files = ExampleHelper.getFiles(entry);
+            if (!files.contains(file)) {
+                throw new ToolCallException(
+                        "File '" + file + "' not found in example '" + example
+                                            + "'. Available files: " + files,
+                        null);
+            }
+
+            if (ExampleHelper.isBundled(entry)) {
+                String resourcePath = "examples/" + example + "/" + file;
+                try (InputStream is = 
ExampleHelper.class.getClassLoader().getResourceAsStream(resourcePath)) {
+                    if (is != null) {
+                        String content = IOHelper.loadText(is);
+                        return new ExampleFileResult(example, file, content, 
null);
+                    }
+                }
+                throw new ToolCallException("Could not read bundled file: " + 
resourcePath, null);
+            } else {
+                String githubUrl = ExampleHelper.getGithubUrl(entry) + "/" + 
file;
+                return new ExampleFileResult(example, file, null, githubUrl);
+            }
+        } catch (ToolCallException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ToolCallException(
+                    "Failed to read example file (" + e.getClass().getName() + 
"): " + e.getMessage(), null);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private ExampleInfo toExampleInfo(JsonObject entry) {
+        Collection<String> tags = (Collection<String>) entry.get("tags");
+        return new ExampleInfo(
+                entry.getString("name"),
+                entry.getString("title"),
+                entry.getString("description"),
+                entry.getString("level"),
+                tags != null ? new ArrayList<>(tags) : List.of(),
+                ExampleHelper.isBundled(entry),
+                ExampleHelper.requiresDocker(entry),
+                ExampleHelper.getFiles(entry));
+    }
+
+    // Result records
+
+    public record ExampleListResult(int count, List<ExampleInfo> examples) {
+    }
+
+    public record ExampleInfo(String name, String title, String description, 
String level,
+            List<String> tags, boolean bundled, boolean requiresDocker, 
List<String> files) {
+    }
+
+    public record ExampleFileResult(String example, String file, String 
content, String githubUrl) {
+    }
+}

Reply via email to