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 da27b60eec48 CAMEL-23126 - Camel-jbang-mcp: Add MCP Prompts for
structured multi-step workflows to camel-jbang-mcp (#21702)
da27b60eec48 is described below
commit da27b60eec48c19d0e9572429eb71586013f437e
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Mar 5 00:05:47 2026 +0100
CAMEL-23126 - Camel-jbang-mcp: Add MCP Prompts for structured multi-step
workflows to camel-jbang-mcp (#21702)
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../jbang/core/commands/mcp/PromptDefinitions.java | 224 +++++++++++++++++++++
.../core/commands/mcp/PromptDefinitionsTest.java | 208 +++++++++++++++++++
2 files changed, 432 insertions(+)
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitions.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitions.java
new file mode 100644
index 000000000000..9bd4c345724e
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitions.java
@@ -0,0 +1,224 @@
+/*
+ * 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 jakarta.enterprise.context.ApplicationScoped;
+
+import io.quarkiverse.mcp.server.Prompt;
+import io.quarkiverse.mcp.server.PromptArg;
+import io.quarkiverse.mcp.server.PromptMessage;
+
+/**
+ * MCP Prompt definitions that provide structured multi-step workflows for
LLMs.
+ * <p>
+ * Prompts guide the LLM through orchestrating multiple existing tools in the
correct sequence, rather than requiring it
+ * to discover the workflow on its own.
+ */
+@ApplicationScoped
+public class PromptDefinitions {
+
+ /**
+ * Guided workflow for building a Camel integration from requirements.
+ */
+ @Prompt(name = "camel_build_integration",
+ description = "Guided workflow to build a Camel integration: "
+ + "analyze requirements, discover components and
EIPs, "
+ + "generate a YAML route, validate it, and run a
security check.")
+ public List<PromptMessage> buildIntegration(
+ @PromptArg(name = "requirements",
+ description = "Natural-language description of what the
integration should do") String requirements,
+ @PromptArg(name = "runtime", description = "Target runtime: main,
spring-boot, or quarkus (default: main)",
+ required = false) String runtime) {
+
+ String resolvedRuntime = runtime != null && !runtime.isBlank() ?
runtime : "main";
+
+ String instructions = """
+ You are building a Camel integration for the "%s" runtime.
+
+ ## Requirements
+ %s
+
+ ## Workflow
+
+ Follow these steps in order:
+
+ ### Step 1: Identify components
+ Analyze the requirements above and identify the Camel
components needed.
+ Call `camel_catalog_components` with a relevant filter and
runtime="%s" to find matching components.
+
+ ### Step 2: Identify EIPs
+ Determine which Enterprise Integration Patterns are needed
(e.g., split, aggregate, filter, choice).
+ Call `camel_catalog_eips` with a relevant filter to find
matching patterns.
+
+ ### Step 3: Get component details
+ For each component you selected, call
`camel_catalog_component_doc` with the component name \
+ and runtime="%s" to get its endpoint options, required
parameters, and URI syntax.
+
+ ### Step 4: Build the route
+ Using the gathered information, write a complete YAML route
definition. \
+ Use correct component URI syntax and required options from the
documentation.
+
+ ### Step 5: Validate
+ Call `camel_validate_yaml_dsl` with the generated YAML route
to check for syntax errors.
+ If validation fails, fix the issues and re-validate.
+
+ ### Step 6: Security review
+ Call `camel_route_harden_context` with the generated route and
format="yaml" \
+ to identify security concerns. Address any critical or
high-severity findings.
+
+ ### Step 7: Present result
+ Present the final YAML route along with:
+ - A brief explanation of each component and EIP used
+ - Any security recommendations from Step 6
+ - Instructions for running the route (e.g., with camel-jbang)
+ """.formatted(resolvedRuntime, requirements, resolvedRuntime,
resolvedRuntime);
+
+ return List.of(PromptMessage.withUserRole(instructions));
+ }
+
+ /**
+ * Guided workflow for migrating a Camel project to a new version.
+ */
+ @Prompt(name = "camel_migrate_project",
+ description = "Guided workflow to migrate a Camel project: "
+ + "analyze the pom.xml, check compatibility, "
+ + "get OpenRewrite recipes, search migration guides,
"
+ + "and produce a migration summary.")
+ public List<PromptMessage> migrateProject(
+ @PromptArg(name = "pomContent", description = "The project's
pom.xml file content") String pomContent,
+ @PromptArg(name = "targetVersion", description = "Target Camel
version to migrate to (e.g., 4.18.0)",
+ required = false) String targetVersion) {
+
+ String versionNote = targetVersion != null && !targetVersion.isBlank()
+ ? "Target version: " + targetVersion
+ : "Target version: latest stable (determine from
camel_version_list)";
+
+ String instructions = """
+ You are migrating a Camel project to a newer version.
+
+ ## %s
+
+ ## Project pom.xml
+ ```xml
+ %s
+ ```
+
+ ## Workflow
+
+ Follow these steps in order:
+
+ ### Step 1: Analyze the project
+ Call `camel_migration_analyze` with the pom.xml content above.
+ This detects the current runtime, Camel version, Java version,
and component dependencies.
+
+ ### Step 2: Determine target version
+ If no target version was specified, call `camel_version_list`
with the detected runtime \
+ to find the latest stable version. For LTS releases, filter
with lts=true.
+
+ ### Step 3: Check compatibility
+ Based on the detected runtime from Step 1:
+ - For **wildfly** or **karaf** runtimes: call
`camel_migration_wildfly_karaf` with the pom.xml \
+ content, target runtime, and target version.
+ - For **main**, **spring-boot**, or **quarkus** runtimes: call
`camel_migration_compatibility` \
+ with the detected components, current version, target version,
runtime, and Java version.
+
+ Review any blockers (e.g., Java version too old) and warnings.
+
+ ### Step 4: Get migration recipes
+ Call `camel_migration_recipes` with the runtime, current
version, target version, \
+ Java version, and dryRun=true to get the OpenRewrite Maven
commands.
+
+ ### Step 5: Search for breaking changes
+ For each component detected in Step 1, call
`camel_migration_guide_search` \
+ with the component name to find relevant breaking changes and
rename mappings.
+
+ ### Step 6: Produce migration summary
+ Present a structured summary:
+ - **Current state**: runtime, Camel version, Java version,
component count
+ - **Target state**: target version, required Java version
+ - **Blockers**: issues that must be resolved before migration
+ - **Breaking changes**: component renames, API changes found
in guides
+ - **Migration commands**: the OpenRewrite commands from Step 4
+ - **Manual steps**: any changes that OpenRewrite cannot
automate
+ """.formatted(versionNote, pomContent);
+
+ return List.of(PromptMessage.withUserRole(instructions));
+ }
+
+ /**
+ * Guided workflow for a security review of a Camel route.
+ */
+ @Prompt(name = "camel_security_review",
+ description = "Guided workflow to perform a security audit of a
Camel route: "
+ + "analyze security-sensitive components, check for
vulnerabilities, "
+ + "and produce an actionable audit checklist.")
+ public List<PromptMessage> securityReview(
+ @PromptArg(name = "route", description = "The Camel route content
to review") String route,
+ @PromptArg(name = "format", description = "Route format: yaml,
xml, or java (default: yaml)",
+ required = false) String format) {
+
+ String resolvedFormat = format != null && !format.isBlank() ? format :
"yaml";
+
+ String instructions = """
+ You are performing a security audit of a Camel route.
+
+ ## Route (format: %s)
+ ```
+ %s
+ ```
+
+ ## Workflow
+
+ Follow these steps in order:
+
+ ### Step 1: Analyze security
+ Call `camel_route_harden_context` with the route above and
format="%s".
+ This returns security-sensitive components, vulnerabilities,
and risk levels.
+
+ ### Step 2: Understand route structure
+ Call `camel_route_context` with the route and format="%s".
+ This returns the components and EIPs used, helping you
understand the full data flow.
+
+ ### Step 3: Produce audit checklist
+ Using the results from Steps 1 and 2, produce a structured
security audit report:
+
+ **Critical Issues** (must fix before production):
+ - List all critical-severity concerns from the security
analysis
+ - For each: describe the issue, the affected component, and
the specific fix
+
+ **Warnings** (should fix):
+ - List all high and medium-severity concerns
+ - For each: describe the risk and the recommended mitigation
+
+ **Positive Findings** (already secured):
+ - List all positive security findings (TLS enabled, property
placeholders used, etc.)
+
+ **Recommendations**:
+ - Provide actionable, prioritized recommendations based on the
specific components used
+ - Reference the relevant security best practices for each
component
+ - Include specific configuration examples where applicable
+
+ **Compliance Notes**:
+ - Note any components that handle PII or sensitive data
+ - Flag any components that communicate over the network
without encryption
+ """.formatted(resolvedFormat, route, resolvedFormat,
resolvedFormat);
+
+ return List.of(PromptMessage.withUserRole(instructions));
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitionsTest.java
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitionsTest.java
new file mode 100644
index 000000000000..64fd0da9eea2
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitionsTest.java
@@ -0,0 +1,208 @@
+/*
+ * 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 io.quarkiverse.mcp.server.PromptMessage;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class PromptDefinitionsTest {
+
+ private final PromptDefinitions prompts = new PromptDefinitions();
+
+ // ---- camel_build_integration ----
+
+ @Test
+ void buildIntegrationReturnsNonEmptyMessages() {
+ List<PromptMessage> result = prompts.buildIntegration("Read from Kafka
and write to S3", "spring-boot");
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void buildIntegrationContainsRequirements() {
+ List<PromptMessage> result = prompts.buildIntegration("Read from Kafka
and write to S3", null);
+
+ String text = extractText(result);
+ assertThat(text).contains("Read from Kafka and write to S3");
+ }
+
+ @Test
+ void buildIntegrationReferencesTools() {
+ List<PromptMessage> result = prompts.buildIntegration("poll an FTP
server", null);
+
+ String text = extractText(result);
+ assertThat(text).contains("camel_catalog_components");
+ assertThat(text).contains("camel_catalog_eips");
+ assertThat(text).contains("camel_catalog_component_doc");
+ assertThat(text).contains("camel_validate_yaml_dsl");
+ assertThat(text).contains("camel_route_harden_context");
+ }
+
+ @Test
+ void buildIntegrationDefaultsRuntimeToMain() {
+ List<PromptMessage> result = prompts.buildIntegration("timer to log",
null);
+
+ String text = extractText(result);
+ assertThat(text).contains("\"main\" runtime");
+ }
+
+ @Test
+ void buildIntegrationUsesSpecifiedRuntime() {
+ List<PromptMessage> result = prompts.buildIntegration("timer to log",
"quarkus");
+
+ String text = extractText(result);
+ assertThat(text).contains("\"quarkus\" runtime");
+ }
+
+ @Test
+ void buildIntegrationBlankRuntimeDefaultsToMain() {
+ List<PromptMessage> result = prompts.buildIntegration("timer to log",
" ");
+
+ String text = extractText(result);
+ assertThat(text).contains("\"main\" runtime");
+ }
+
+ // ---- camel_migrate_project ----
+
+ @Test
+ void migrateProjectReturnsNonEmptyMessages() {
+ List<PromptMessage> result = prompts.migrateProject("<project/>",
"4.18.0");
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void migrateProjectContainsPomContent() {
+ String pom = "<project><version>3.20.0</version></project>";
+ List<PromptMessage> result = prompts.migrateProject(pom, null);
+
+ String text = extractText(result);
+ assertThat(text).contains(pom);
+ }
+
+ @Test
+ void migrateProjectReferencesTools() {
+ List<PromptMessage> result = prompts.migrateProject("<project/>",
"4.18.0");
+
+ String text = extractText(result);
+ assertThat(text).contains("camel_migration_analyze");
+ assertThat(text).contains("camel_migration_compatibility");
+ assertThat(text).contains("camel_migration_wildfly_karaf");
+ assertThat(text).contains("camel_migration_recipes");
+ assertThat(text).contains("camel_migration_guide_search");
+ }
+
+ @Test
+ void migrateProjectIncludesTargetVersion() {
+ List<PromptMessage> result = prompts.migrateProject("<project/>",
"4.18.0");
+
+ String text = extractText(result);
+ assertThat(text).contains("Target version: 4.18.0");
+ }
+
+ @Test
+ void migrateProjectNullVersionSuggestsLatest() {
+ List<PromptMessage> result = prompts.migrateProject("<project/>",
null);
+
+ String text = extractText(result);
+ assertThat(text).contains("camel_version_list");
+ }
+
+ @Test
+ void migrateProjectBlankVersionSuggestsLatest() {
+ List<PromptMessage> result = prompts.migrateProject("<project/>", "
");
+
+ String text = extractText(result);
+ assertThat(text).contains("camel_version_list");
+ }
+
+ // ---- camel_security_review ----
+
+ @Test
+ void securityReviewReturnsNonEmptyMessages() {
+ List<PromptMessage> result = prompts.securityReview("from:
timer:tick", "yaml");
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void securityReviewContainsRoute() {
+ String route = "from:\n uri: kafka:topic\n steps:\n - to:
log:out";
+ List<PromptMessage> result = prompts.securityReview(route, null);
+
+ String text = extractText(result);
+ assertThat(text).contains(route);
+ }
+
+ @Test
+ void securityReviewReferencesTools() {
+ List<PromptMessage> result = prompts.securityReview("from:
timer:tick", null);
+
+ String text = extractText(result);
+ assertThat(text).contains("camel_route_harden_context");
+ assertThat(text).contains("camel_route_context");
+ }
+
+ @Test
+ void securityReviewDefaultsFormatToYaml() {
+ List<PromptMessage> result = prompts.securityReview("from:
timer:tick", null);
+
+ String text = extractText(result);
+ assertThat(text).contains("format: yaml");
+ }
+
+ @Test
+ void securityReviewUsesSpecifiedFormat() {
+ List<PromptMessage> result = prompts.securityReview("<route/>", "xml");
+
+ String text = extractText(result);
+ assertThat(text).contains("format: xml");
+ }
+
+ @Test
+ void securityReviewBlankFormatDefaultsToYaml() {
+ List<PromptMessage> result = prompts.securityReview("from:
timer:tick", " ");
+
+ String text = extractText(result);
+ assertThat(text).contains("format: yaml");
+ }
+
+ @Test
+ void securityReviewContainsAuditSections() {
+ List<PromptMessage> result = prompts.securityReview("from:
timer:tick", null);
+
+ String text = extractText(result);
+ assertThat(text).contains("Critical Issues");
+ assertThat(text).contains("Warnings");
+ assertThat(text).contains("Positive Findings");
+ assertThat(text).contains("Recommendations");
+ }
+
+ // ---- helper ----
+
+ private String extractText(List<PromptMessage> messages) {
+ StringBuilder sb = new StringBuilder();
+ for (PromptMessage msg : messages) {
+ sb.append(msg.content().asText().text());
+ }
+ return sb.toString();
+ }
+}