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 a5d3b96f8503 CAMEL-23131 - Camel-jbang-mcp: Add camel_error_diagnose
MCP tool for stack trace diagnosis (#21717)
a5d3b96f8503 is described below
commit a5d3b96f8503416c42ca01aa126e072b0086f84f
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Mar 5 11:31:39 2026 +0100
CAMEL-23131 - Camel-jbang-mcp: Add camel_error_diagnose MCP tool for stack
trace diagnosis (#21717)
Add a new DiagnoseTools MCP tool that accepts Camel stack traces or error
messages and returns structured diagnosis including the identified
component/EIP, common causes, documentation links, and suggested fixes.
Covers 17 known Camel exceptions (NoSuchEndpointException,
ResolveEndpointFailedException, FailedToCreateRouteException, etc.).
Signed-off-by: Andrea Cosentino <[email protected]>
---
.../modules/ROOT/pages/camel-jbang-mcp.adoc | 105 +++-
.../dsl/jbang/core/commands/mcp/DiagnoseTools.java | 580 +++++++++++++++++++++
.../jbang/core/commands/mcp/DiagnoseToolsTest.java | 312 +++++++++++
3 files changed, 996 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 50ee05be58ec..1270c9ff3cd9 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -24,7 +24,8 @@ By default, the HTTP server is disabled. To enable it, set
`quarkus.http.host-en
== Available Tools
-The server exposes 24 tools organized into eight functional areas.
+The server exposes 25 tools organized into nine functional areas, plus 3
prompts that provide structured
+multi-step workflows.
=== Catalog Exploration
@@ -99,6 +100,19 @@ The server exposes 24 tools organized into eight functional
areas.
best practices.
|===
+=== Error Diagnosis
+
+[cols="1,3",options="header"]
+|===
+| Tool | Description
+
+| `camel_error_diagnose`
+| Diagnoses Camel errors from stack traces or error messages. Identifies the
exception type against 17 known
+ Camel exceptions (such as `NoSuchEndpointException`,
`ResolveEndpointFailedException`,
+ `FailedToCreateRouteException`, `PropertyBindingException`, and others),
extracts the components and EIPs
+ involved, and returns common causes, suggested fixes, and links to relevant
Camel documentation.
+|===
+
=== Validation and Transformation
[cols="1,3",options="header"]
@@ -187,6 +201,40 @@ help validate, scaffold, and provide mock guidance for
that workflow.
| Lists available Camel versions for a given runtime, including release dates,
JDK requirements, and LTS status.
|===
+== Available Prompts
+
+Prompts are structured multi-step workflows that guide the LLM through
orchestrating multiple tools in the correct
+sequence. Instead of the LLM having to discover which tools to call and in
what order, a prompt provides the complete
+workflow as a step-by-step plan.
+
+MCP clients that support prompts (such as Claude Desktop) expose them as
selectable workflows. The LLM receives the
+instructions and executes each step by calling the referenced tools.
+
+[cols="1,1,3",options="header"]
+|===
+| Prompt | Arguments | Description
+
+| `camel_build_integration`
+| `requirements` (required), `runtime` (optional)
+| Guided workflow to build a Camel integration from natural-language
requirements. Walks through seven steps:
+ identify components, identify EIPs, get component documentation, build the
YAML route, validate it with the
+ YAML DSL schema, run a security review, and present the final result with
explanations and run instructions.
+
+| `camel_migrate_project`
+| `pomContent` (required), `targetVersion` (optional)
+| Guided workflow to migrate a Camel project to a newer version. Walks through
six steps: analyze the project's
+ `pom.xml`, determine the target version, check compatibility (including
WildFly/Karaf detection), get
+ OpenRewrite migration recipes, search migration guides for breaking changes
per component, and produce a
+ structured migration summary with blockers, breaking changes, commands, and
manual steps.
+
+| `camel_security_review`
+| `route` (required), `format` (optional)
+| Guided workflow to perform a security audit of a Camel route. Walks through
three steps: analyze the route
+ for security-sensitive components and vulnerabilities, understand the route
structure and data flow, and
+ produce an actionable audit checklist organized into critical issues,
warnings, positive findings,
+ recommendations, and compliance notes.
+|===
+
== Setup
The MCP server requires https://www.jbang.dev/[JBang] to be installed and
available on your PATH.
@@ -376,6 +424,22 @@ The assistant calls `camel_route_harden_context` which
analyzes the route for se
detects issues (hardcoded credentials, HTTP instead of HTTPS, plain FTP,
etc.), assigns risk levels, and
returns structured findings with remediation recommendations.
+=== Diagnosing an Error
+
+Paste a stack trace or error message and ask:
+
+----
+I'm getting this error when starting my Camel route. What's wrong?
+
+org.apache.camel.FailedToCreateRouteException: Failed to create route route1
at: >>> To[kafka:myTopic] <<<
+Caused by: org.apache.camel.ResolveEndpointFailedException: Failed to resolve
endpoint: kafka:myTopic
+Caused by: org.apache.camel.NoSuchEndpointException: No endpoint could be
found for: kafka:myTopic
+----
+
+The assistant calls `camel_error_diagnose` which identifies all three
exceptions in the chain, extracts the
+`kafka` component, and returns common causes (missing `camel-kafka`
dependency, typo in URI scheme), suggested
+fixes (add the dependency, verify the URI), and links to the relevant Camel
documentation.
+
=== Checking Camel Versions
----
@@ -466,3 +530,42 @@ with mock mode so I can prototype quickly.
The assistant first calls `camel_openapi_validate` to check for issues, then
calls `camel_openapi_scaffold`
to generate the route scaffold. This gives you a validated spec and a complete
starting point where you can
implement routes one at a time while Camel auto-mocks the rest.
+
+=== Using the Build Integration Prompt
+
+In MCP clients that support prompts, select the `camel_build_integration`
prompt and provide your requirements:
+
+----
+Requirements: Read messages from a Kafka topic, filter them by a JSON field,
and write matching messages to an AWS S3 bucket.
+Runtime: quarkus
+----
+
+The prompt guides the assistant through a structured seven-step workflow:
discovering the right components
+(`kafka`, `aws2-s3`), selecting EIPs (`filter`), retrieving their
documentation, building a YAML route,
+validating it against the YAML DSL schema, and running a security review — all
in the correct order.
+
+=== Using the Migrate Project Prompt
+
+Select the `camel_migrate_project` prompt and provide your project's `pom.xml`:
+
+----
+pomContent: <paste your pom.xml here>
+targetVersion: 4.18.0
+----
+
+The prompt orchestrates the full migration workflow: analyzing your project,
checking component compatibility,
+retrieving OpenRewrite recipes, searching migration guides for per-component
breaking changes, and producing a
+structured migration summary with blockers, manual steps, and the exact
commands to run.
+
+=== Using the Security Review Prompt
+
+Select the `camel_security_review` prompt and provide a route:
+
+----
+Route: <paste your route here>
+Format: yaml
+----
+
+The prompt guides the assistant through a security audit: analyzing the route
for vulnerabilities and
+security-sensitive components, understanding the data flow, and producing a
structured audit checklist with
+critical issues, warnings, positive findings, and actionable recommendations.
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DiagnoseTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DiagnoseTools.java
new file mode 100644
index 000000000000..261370e549e8
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DiagnoseTools.java
@@ -0,0 +1,580 @@
+/*
+ * 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.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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 diagnosing Camel errors from stack traces or error messages.
+ * <p>
+ * Accepts a Camel stack trace or error message and returns the likely
component/EIP involved, common causes, links to
+ * relevant documentation, and suggested fixes.
+ */
+@ApplicationScoped
+public class DiagnoseTools {
+
+ private static final String CAMEL_DOC_BASE = "https://camel.apache.org/";
+ private static final String CAMEL_COMPONENT_DOC = CAMEL_DOC_BASE +
"components/next/";
+ private static final String CAMEL_MANUAL_DOC = CAMEL_DOC_BASE + "manual/";
+ private static final String CAMEL_EIP_DOC = CAMEL_COMPONENT_DOC + "eips/";
+
+ /**
+ * Pattern to extract component names from endpoint URIs in error messages
(e.g., "kafka:myTopic", "http://host").
+ */
+ private static final Pattern ENDPOINT_URI_PATTERN = Pattern
+
.compile("(?:endpoint|uri)[:\\s]+['\"]?([a-zA-Z][a-zA-Z0-9+.-]*):(?://)?[^\\s'\"]*",
Pattern.CASE_INSENSITIVE);
+
+ /**
+ * Pattern to extract component scheme from common error contexts.
+ */
+ private static final Pattern COMPONENT_SCHEME_PATTERN
+ =
Pattern.compile("(?:component|scheme)[:\\s]+['\"]?([a-zA-Z][a-zA-Z0-9+.-]*)['\"]?",
Pattern.CASE_INSENSITIVE);
+
+ /**
+ * Pattern to extract route IDs from error messages.
+ */
+ private static final Pattern ROUTE_ID_PATTERN
+ = Pattern.compile("route[:\\s]+['\"]?([a-zA-Z0-9_-]+)['\"]?",
Pattern.CASE_INSENSITIVE);
+
+ private final CamelCatalog catalog;
+ private final Map<String, ExceptionInfo> knownExceptions;
+
+ public DiagnoseTools() {
+ this.catalog = new DefaultCamelCatalog();
+ this.knownExceptions = buildKnownExceptions();
+ }
+
+ /**
+ * Tool to diagnose Camel errors from stack traces or error messages.
+ */
+ @Tool(description = "Diagnose a Camel error from a stack trace or error
message. "
+ + "Returns the identified component/EIP involved,
common causes for the error, "
+ + "links to relevant Camel documentation, and
suggested fixes. "
+ + "Covers the most common Camel exceptions including
NoSuchEndpointException, "
+ + "ResolveEndpointFailedException,
FailedToCreateRouteException, and more.")
+ public String camel_error_diagnose(
+ @ToolArg(description = "The Camel stack trace or error message to
diagnose") String error) {
+
+ if (error == null || error.isBlank()) {
+ throw new ToolCallException("Error message or stack trace is
required", null);
+ }
+
+ try {
+ JsonObject result = new JsonObject();
+
+ // Identify matching exceptions
+ List<String> matchedExceptions = identifyExceptions(error);
+ JsonArray exceptionsJson = new JsonArray();
+ for (String exceptionName : matchedExceptions) {
+ ExceptionInfo info = knownExceptions.get(exceptionName);
+ if (info != null) {
+ JsonObject exJson = new JsonObject();
+ exJson.put("exception", exceptionName);
+ exJson.put("description", info.description);
+
+ JsonArray causesJson = new JsonArray();
+ for (String cause : info.commonCauses) {
+ causesJson.add(cause);
+ }
+ exJson.put("commonCauses", causesJson);
+
+ JsonArray fixesJson = new JsonArray();
+ for (String fix : info.suggestedFixes) {
+ fixesJson.add(fix);
+ }
+ exJson.put("suggestedFixes", fixesJson);
+
+ JsonArray docsJson = new JsonArray();
+ for (String doc : info.documentationLinks) {
+ docsJson.add(doc);
+ }
+ exJson.put("documentationLinks", docsJson);
+
+ exceptionsJson.add(exJson);
+ }
+ }
+ result.put("identifiedExceptions", exceptionsJson);
+
+ // Identify components from the error
+ List<String> componentNames = extractComponentNames(error);
+ JsonArray componentsJson = 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("documentationUrl", CAMEL_COMPONENT_DOC +
comp + "-component.html");
+ componentsJson.add(compJson);
+ }
+ }
+ result.put("identifiedComponents", componentsJson);
+
+ // Identify EIPs from the error
+ List<String> eipNames = extractEipNames(error);
+ JsonArray eipsJson = 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("documentationUrl", CAMEL_EIP_DOC + eip +
"-eip.html");
+ eipsJson.add(eipJson);
+ }
+ }
+ result.put("identifiedEips", eipsJson);
+
+ // Extract route ID if present
+ Matcher routeMatcher = ROUTE_ID_PATTERN.matcher(error);
+ if (routeMatcher.find()) {
+ result.put("routeId", routeMatcher.group(1));
+ }
+
+ // Summary
+ JsonObject summary = new JsonObject();
+ summary.put("exceptionCount", exceptionsJson.size());
+ summary.put("componentCount", componentsJson.size());
+ summary.put("eipCount", eipsJson.size());
+ summary.put("diagnosed", !exceptionsJson.isEmpty());
+ result.put("summary", summary);
+
+ return result.toJson();
+ } catch (ToolCallException e) {
+ throw e;
+ } catch (Throwable e) {
+ throw new ToolCallException(
+ "Failed to diagnose error (" + e.getClass().getName() +
"): " + e.getMessage(), null);
+ }
+ }
+
+ /**
+ * Identify known Camel exceptions in the error text.
+ */
+ private List<String> identifyExceptions(String error) {
+ List<String> matched = new ArrayList<>();
+ for (String exceptionName : knownExceptions.keySet()) {
+ if (error.contains(exceptionName)) {
+ matched.add(exceptionName);
+ }
+ }
+ return matched;
+ }
+
+ /**
+ * Extract component names from endpoint URIs and other patterns in the
error text.
+ */
+ private List<String> extractComponentNames(String error) {
+ List<String> found = new ArrayList<>();
+
+ // Try endpoint URI pattern
+ Matcher uriMatcher = ENDPOINT_URI_PATTERN.matcher(error);
+ while (uriMatcher.find()) {
+ String scheme = uriMatcher.group(1).toLowerCase();
+ if (catalog.componentModel(scheme) != null &&
!found.contains(scheme)) {
+ found.add(scheme);
+ }
+ }
+
+ // Try component scheme pattern
+ Matcher schemeMatcher = COMPONENT_SCHEME_PATTERN.matcher(error);
+ while (schemeMatcher.find()) {
+ String scheme = schemeMatcher.group(1).toLowerCase();
+ if (catalog.componentModel(scheme) != null &&
!found.contains(scheme)) {
+ found.add(scheme);
+ }
+ }
+
+ // Scan for known component names in the error text
+ String lowerError = error.toLowerCase();
+ for (String comp : catalog.findComponentNames()) {
+ if (!found.contains(comp) && containsComponent(lowerError, comp)) {
+ found.add(comp);
+ }
+ }
+
+ return found;
+ }
+
+ /**
+ * Extract EIP names from the error text.
+ */
+ private List<String> extractEipNames(String error) {
+ List<String> found = new ArrayList<>();
+ String lowerError = error.toLowerCase();
+
+ for (String eip : catalog.findModelNames()) {
+ EipModel model = catalog.eipModel(eip);
+ if (model != null) {
+ String eipLower = eip.toLowerCase();
+ if (lowerError.contains(eipLower) && !found.contains(eip)) {
+ found.add(eip);
+ }
+ }
+ }
+
+ return found;
+ }
+
+ private boolean containsComponent(String content, String comp) {
+ return content.contains(comp + ":")
+ || content.contains("\"" + comp + "\"")
+ || content.contains("'" + comp + "'");
+ }
+
+ /**
+ * Build the registry of known Camel exceptions with their causes, fixes,
and documentation.
+ */
+ private Map<String, ExceptionInfo> buildKnownExceptions() {
+ Map<String, ExceptionInfo> exceptions = new LinkedHashMap<>();
+
+ exceptions.put("NoSuchEndpointException", new ExceptionInfo(
+ "The specified endpoint URI could not be resolved to any known
Camel component.",
+ Arrays.asList(
+ "Typo in the endpoint URI scheme (e.g., 'kafak:'
instead of 'kafka:')",
+ "Missing component dependency in pom.xml or
build.gradle",
+ "Component not on the classpath",
+ "Using a component scheme that does not exist"),
+ Arrays.asList(
+ "Verify the endpoint URI scheme is spelled correctly",
+ "Add the required camel-<component> dependency to your
project",
+ "Check available components with 'camel-catalog' or
the Camel documentation",
+ "Ensure the component JAR is on the classpath"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "component.html",
+ CAMEL_MANUAL_DOC +
"faq/why-is-my-message-body-empty.html")));
+
+ exceptions.put("ResolveEndpointFailedException", new ExceptionInfo(
+ "Failed to resolve or create an endpoint from the given URI.
The URI syntax may be invalid or required options may be missing.",
+ Arrays.asList(
+ "Invalid endpoint URI syntax",
+ "Missing required endpoint options",
+ "Unknown or misspelled endpoint options",
+ "Invalid option values (wrong type or format)",
+ "Special characters in URI not properly encoded"),
+ Arrays.asList(
+ "Check the endpoint URI syntax against the component
documentation",
+ "Ensure all required options are provided",
+ "Verify option names are spelled correctly",
+ "URL-encode special characters in the URI",
+ "Use the endpoint DSL for type-safe endpoint
configuration"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "endpoint.html",
+ CAMEL_MANUAL_DOC + "uris.html")));
+
+ exceptions.put("FailedToCreateRouteException", new ExceptionInfo(
+ "A route could not be created. This is typically a
configuration or wiring issue.",
+ Arrays.asList(
+ "Invalid endpoint URI in from() or to()",
+ "Missing required component dependency",
+ "Bean reference that cannot be resolved",
+ "Invalid route configuration or DSL syntax",
+ "Circular route dependencies"),
+ Arrays.asList(
+ "Check the full exception chain for the root cause",
+ "Verify all endpoint URIs in the route are valid",
+ "Ensure all referenced beans are available in the
registry",
+ "Validate the route DSL syntax",
+ "Check for missing component dependencies"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "routes.html",
+ CAMEL_MANUAL_DOC + "route-configuration.html")));
+
+ exceptions.put("FailedToStartRouteException", new ExceptionInfo(
+ "A route was created but could not be started. This often
indicates a connectivity or resource issue.",
+ Arrays.asList(
+ "Cannot connect to external service (broker, database,
etc.)",
+ "Port already in use for server-side components",
+ "Authentication/authorization failure",
+ "Missing or invalid SSL/TLS configuration",
+ "Resource not available (queue, topic, table, etc.)"),
+ Arrays.asList(
+ "Verify network connectivity to external services",
+ "Check credentials and authentication configuration",
+ "Ensure the target resource exists (queue, topic,
etc.)",
+ "Review SSL/TLS configuration if using secure
connections",
+ "Check if the port is already in use"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "routes.html",
+ CAMEL_MANUAL_DOC + "lifecycle.html")));
+
+ exceptions.put("NoTypeConversionAvailableException", new ExceptionInfo(
+ "Camel could not find a type converter to convert between the
required types.",
+ Arrays.asList(
+ "Trying to convert a message body to an incompatible
type",
+ "Missing type converter on the classpath",
+ "Custom type without a registered converter",
+ "Null body when a non-null type is expected"),
+ Arrays.asList(
+ "Check the source and target types in the conversion",
+ "Add appropriate data format or converter dependency",
+ "Use explicit marshal/unmarshal instead of implicit
conversion",
+ "Register a custom TypeConverter if needed",
+ "Check if the message body is null"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "type-converter.html",
+ CAMEL_MANUAL_DOC + "data-format.html")));
+
+ exceptions.put("CamelExecutionException", new ExceptionInfo(
+ "A wrapper exception thrown during route execution. The root
cause is in the nested exception.",
+ Arrays.asList(
+ "Exception thrown by a processor or bean in the route",
+ "External service failure (HTTP error, broker
disconnect, etc.)",
+ "Data transformation error",
+ "Timeout during synchronous processing"),
+ Arrays.asList(
+ "Inspect the nested/caused-by exception for the actual
error",
+ "Add error handling (onException, errorHandler,
doTry/doCatch) to the route",
+ "Check the processor or bean that failed",
+ "Review the full stack trace for the root cause"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "exception-clause.html",
+ CAMEL_MANUAL_DOC + "error-handler.html",
+ CAMEL_MANUAL_DOC + "try-catch-finally.html")));
+
+ exceptions.put("ExchangeTimedOutException", new ExceptionInfo(
+ "An exchange did not complete within the configured timeout
period.",
+ Arrays.asList(
+ "Slow downstream service or endpoint",
+ "Network latency or connectivity issues",
+ "Timeout value too low for the operation",
+ "Deadlock or resource contention",
+ "Direct/SEDA consumer not available"),
+ Arrays.asList(
+ "Increase the timeout value if appropriate",
+ "Add circuit breaker pattern for unreliable services",
+ "Check network connectivity to the target service",
+ "Use async processing for long-running operations",
+ "Add timeout error handling in the route"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "request-reply.html",
+ CAMEL_EIP_DOC + "circuitBreaker-eip.html")));
+
+ exceptions.put("DirectConsumerNotAvailableException", new
ExceptionInfo(
+ "No consumer is available for a direct endpoint. The direct
component requires an active consumer.",
+ Arrays.asList(
+ "Target route with the direct endpoint is not started",
+ "Typo in the direct endpoint name",
+ "Route with the direct consumer was stopped or
removed",
+ "Timing issue during startup — producer route started
before consumer route"),
+ Arrays.asList(
+ "Ensure a route with from(\"direct:name\") exists and
is started",
+ "Verify the direct endpoint name matches between
producer and consumer",
+ "Use SEDA instead of direct if startup ordering is
uncertain",
+ "Configure route startup ordering if needed"),
+ Arrays.asList(
+ CAMEL_COMPONENT_DOC + "direct-component.html",
+ CAMEL_COMPONENT_DOC + "seda-component.html")));
+
+ exceptions.put("CamelExchangeException", new ExceptionInfo(
+ "A general exception related to exchange processing.",
+ Arrays.asList(
+ "Processor failure during exchange handling",
+ "Invalid exchange pattern (InOnly vs InOut mismatch)",
+ "Missing required headers or properties",
+ "Exchange body cannot be processed"),
+ Arrays.asList(
+ "Check the exchange pattern matches the endpoint
requirements",
+ "Verify required headers are set on the exchange",
+ "Add error handling to catch and process the
exception",
+ "Inspect the exchange body type and content"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "exchange.html",
+ CAMEL_MANUAL_DOC + "exchange-pattern.html")));
+
+ exceptions.put("InvalidPayloadException", new ExceptionInfo(
+ "The message payload (body) is not of the expected type and
cannot be converted.",
+ Arrays.asList(
+ "Message body is null when a value is expected",
+ "Message body type does not match the expected type",
+ "Missing type converter for the body type",
+ "Upstream processor produced unexpected output"),
+ Arrays.asList(
+ "Check the message body type before the failing
processor",
+ "Use convertBodyTo() to explicitly convert the body
type",
+ "Add a null check or default value for the body",
+ "Add the appropriate data format dependency for
marshalling/unmarshalling"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "message.html",
+ CAMEL_MANUAL_DOC + "type-converter.html")));
+
+ exceptions.put("PropertyBindingException", new ExceptionInfo(
+ "Failed to bind a property or option to a Camel component,
endpoint, or bean.",
+ Arrays.asList(
+ "Property name does not exist on the target object",
+ "Property value has wrong type (e.g., string for a
boolean)",
+ "Misspelled property or option name",
+ "Property placeholder could not be resolved"),
+ Arrays.asList(
+ "Check the property name spelling against the
component documentation",
+ "Verify the property value type matches the expected
type",
+ "Use property placeholders correctly:
{{property.name}}",
+ "Check application.properties or YAML configuration
for correct keys"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "using-propertyplaceholder.html",
+ CAMEL_MANUAL_DOC + "component.html")));
+
+ exceptions.put("NoSuchBeanException", new ExceptionInfo(
+ "A referenced bean could not be found in the Camel registry.",
+ Arrays.asList(
+ "Bean not registered in the Spring/CDI/Camel registry",
+ "Typo in the bean name reference",
+ "Bean class not on the classpath",
+ "Missing @Named or @Component annotation on the bean
class",
+ "Bean definition not scanned by component scan"),
+ Arrays.asList(
+ "Verify the bean is registered with the correct name",
+ "Check spelling of the bean reference",
+ "Ensure the bean class has proper annotations (@Named,
@Component, etc.)",
+ "Verify component scanning includes the bean's
package",
+ "Register the bean manually in RouteBuilder
configure() if needed"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "registry.html",
+ CAMEL_MANUAL_DOC + "bean-binding.html")));
+
+ exceptions.put("NoSuchHeaderException", new ExceptionInfo(
+ "A required header was not found on the message.",
+ Arrays.asList(
+ "Header not set by upstream processors",
+ "Header name is misspelled",
+ "Header was removed by a previous processor",
+ "Using wrong header constant name"),
+ Arrays.asList(
+ "Verify the header name matches what upstream
processors set",
+ "Use header constants from the component's class
(e.g., KafkaConstants)",
+ "Add a setHeader() before the processor that requires
it",
+ "Add a null check or default value for the header"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "message.html")));
+
+ exceptions.put("PredicateValidationException", new ExceptionInfo(
+ "A predicate validation failed. This is typically thrown by
the validate() DSL or a filter condition.",
+ Arrays.asList(
+ "Message did not match the expected validation
predicate",
+ "Invalid or unexpected message content",
+ "Predicate expression has a syntax error",
+ "Null values in the expression evaluation"),
+ Arrays.asList(
+ "Review the predicate expression for correctness",
+ "Check the message content matches the expected
format",
+ "Add error handling for validation failures",
+ "Use a more lenient predicate or add default values"),
+ Arrays.asList(
+ CAMEL_EIP_DOC + "validate-eip.html",
+ CAMEL_MANUAL_DOC + "predicate.html")));
+
+ exceptions.put("NoSuchLanguageException", new ExceptionInfo(
+ "The specified expression language is not available.",
+ Arrays.asList(
+ "Missing language dependency (e.g., camel-jsonpath,
camel-xpath)",
+ "Typo in the language name",
+ "Using a language that does not exist"),
+ Arrays.asList(
+ "Add the required camel-<language> dependency",
+ "Verify the language name is spelled correctly",
+ "Use 'simple' language which is included in
camel-core"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "languages.html")));
+
+ exceptions.put("FailedToCreateConsumerException", new ExceptionInfo(
+ "A consumer could not be created for the endpoint.",
+ Arrays.asList(
+ "Cannot connect to the source system (broker, server,
etc.)",
+ "Invalid consumer configuration options",
+ "Authentication failure",
+ "Missing required consumer options",
+ "Resource does not exist (topic, queue, file path,
etc.)"),
+ Arrays.asList(
+ "Verify connectivity to the source system",
+ "Check the consumer configuration options",
+ "Ensure credentials are correct",
+ "Verify the target resource exists",
+ "Check the full exception chain for the root cause"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "component.html")));
+
+ exceptions.put("FailedToCreateProducerException", new ExceptionInfo(
+ "A producer could not be created for the endpoint.",
+ Arrays.asList(
+ "Cannot connect to the target system",
+ "Invalid producer configuration options",
+ "Authentication failure",
+ "Missing required producer options"),
+ Arrays.asList(
+ "Verify connectivity to the target system",
+ "Check the producer configuration options",
+ "Ensure credentials are correct",
+ "Check the full exception chain for the root cause"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "component.html")));
+
+ exceptions.put("CamelAuthorizationException", new ExceptionInfo(
+ "An authorization check failed. The current identity does not
have permission to perform the operation.",
+ Arrays.asList(
+ "Insufficient permissions for the user or service
account",
+ "Missing or expired authentication token",
+ "Security policy denying the operation",
+ "Incorrect RBAC or ACL configuration"),
+ Arrays.asList(
+ "Check the user/service account permissions",
+ "Verify the authentication token is valid and not
expired",
+ "Review the security policies and ACLs",
+ "Ensure the correct security provider is configured"),
+ Arrays.asList(
+ CAMEL_MANUAL_DOC + "security.html")));
+
+ return exceptions;
+ }
+
+ /**
+ * Holds diagnostic information about a known Camel exception.
+ */
+ static class ExceptionInfo {
+ final String description;
+ final List<String> commonCauses;
+ final List<String> suggestedFixes;
+ final List<String> documentationLinks;
+
+ ExceptionInfo(String description, List<String> commonCauses,
List<String> suggestedFixes,
+ List<String> documentationLinks) {
+ this.description = description;
+ this.commonCauses = commonCauses;
+ this.suggestedFixes = suggestedFixes;
+ this.documentationLinks = documentationLinks;
+ }
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/DiagnoseToolsTest.java
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/DiagnoseToolsTest.java
new file mode 100644
index 000000000000..92486b03c223
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/DiagnoseToolsTest.java
@@ -0,0 +1,312 @@
+/*
+ * 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 io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class DiagnoseToolsTest {
+
+ private final DiagnoseTools tools = new DiagnoseTools();
+
+ // ---- Input validation ----
+
+ @Test
+ void nullErrorThrows() {
+ assertThatThrownBy(() -> tools.camel_error_diagnose(null))
+ .isInstanceOf(ToolCallException.class)
+ .hasMessageContaining("required");
+ }
+
+ @Test
+ void blankErrorThrows() {
+ assertThatThrownBy(() -> tools.camel_error_diagnose(" "))
+ .isInstanceOf(ToolCallException.class)
+ .hasMessageContaining("required");
+ }
+
+ // ---- Exception identification ----
+
+ @Test
+ void identifiesNoSuchEndpointException() throws Exception {
+ String error = "org.apache.camel.NoSuchEndpointException: No endpoint
could be found for: kafak:myTopic";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions).isNotEmpty();
+
assertThat(exceptions.getMap(0).get("exception")).isEqualTo("NoSuchEndpointException");
+ }
+
+ @Test
+ void identifiesResolveEndpointFailedException() throws Exception {
+ String error = "org.apache.camel.ResolveEndpointFailedException: "
+ + "Failed to resolve endpoint:
kafka:myTopic?unknownOption=value due to: "
+ + "There are 1 parameters that couldn't be set on the
endpoint.";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions).isNotEmpty();
+
assertThat(exceptions.getMap(0).get("exception")).isEqualTo("ResolveEndpointFailedException");
+ }
+
+ @Test
+ void identifiesFailedToCreateRouteException() throws Exception {
+ String error = "org.apache.camel.FailedToCreateRouteException: "
+ + "Failed to create route route1:
Route(route1)[From[direct:start] -> [To[log:out]]]";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions).isNotEmpty();
+ JsonObject first = (JsonObject) exceptions.get(0);
+
assertThat(first.getString("exception")).isEqualTo("FailedToCreateRouteException");
+ }
+
+ @Test
+ void identifiesMultipleExceptions() throws Exception {
+ String error = "org.apache.camel.FailedToCreateRouteException: Failed
to create route\n"
+ + "Caused by:
org.apache.camel.ResolveEndpointFailedException: Failed to resolve endpoint";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions.size()).isGreaterThanOrEqualTo(2);
+ }
+
+ @Test
+ void identifiesNoTypeConversionAvailableException() throws Exception {
+ String error = "org.apache.camel.NoTypeConversionAvailableException: "
+ + "No type converter available to convert from type:
java.lang.String "
+ + "to the required type: java.io.InputStream";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions).isNotEmpty();
+
assertThat(exceptions.getMap(0).get("exception")).isEqualTo("NoTypeConversionAvailableException");
+ }
+
+ @Test
+ void identifiesExchangeTimedOutException() throws Exception {
+ String error = "org.apache.camel.ExchangeTimedOutException: "
+ + "The OUT message was not received within: 30000
millis";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions).isNotEmpty();
+
assertThat(exceptions.getMap(0).get("exception")).isEqualTo("ExchangeTimedOutException");
+ }
+
+ @Test
+ void identifiesDirectConsumerNotAvailableException() throws Exception {
+ String error =
"org.apache.camel.component.direct.DirectConsumerNotAvailableException: "
+ + "No consumers available on endpoint:
direct://myEndpoint";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions).isNotEmpty();
+
assertThat(exceptions.getMap(0).get("exception")).isEqualTo("DirectConsumerNotAvailableException");
+ }
+
+ @Test
+ void identifiesPropertyBindingException() throws Exception {
+ String error = "org.apache.camel.PropertyBindingException: "
+ + "Error binding property (brokerz=localhost:9092) with
name: brokerz on bean";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions).isNotEmpty();
+
assertThat(exceptions.getMap(0).get("exception")).isEqualTo("PropertyBindingException");
+ }
+
+ @Test
+ void identifiesNoSuchBeanException() throws Exception {
+ String error = "org.apache.camel.NoSuchBeanException: "
+ + "No bean could be found in the registry for:
myProcessor";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ assertThat(exceptions).isNotEmpty();
+
assertThat(exceptions.getMap(0).get("exception")).isEqualTo("NoSuchBeanException");
+ }
+
+ // ---- Component identification ----
+
+ @Test
+ void identifiesKafkaComponent() throws Exception {
+ String error = "org.apache.camel.ResolveEndpointFailedException: "
+ + "Failed to resolve endpoint:
kafka:myTopic?brokers=localhost:9092";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray components = result.getCollection("identifiedComponents");
+
+ assertThat(components).isNotEmpty();
+ assertThat(components.stream()
+ .map(c -> ((JsonObject) c).getString("name"))
+ .toList())
+ .contains("kafka");
+ }
+
+ @Test
+ void identifiesDirectComponent() throws Exception {
+ String error = "No consumers available on endpoint: direct://start";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray components = result.getCollection("identifiedComponents");
+
+ assertThat(components.stream()
+ .map(c -> ((JsonObject) c).getString("name"))
+ .toList())
+ .contains("direct");
+ }
+
+ // ---- Result structure ----
+
+ @Test
+ void resultContainsCommonCauses() throws Exception {
+ String error = "org.apache.camel.NoSuchEndpointException: No endpoint
could be found for: xyz:test";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+ JsonObject first = (JsonObject) exceptions.get(0);
+
+ JsonArray causes = (JsonArray) first.get("commonCauses");
+ assertThat(causes).isNotEmpty();
+ }
+
+ @Test
+ void resultContainsSuggestedFixes() throws Exception {
+ String error = "org.apache.camel.NoSuchEndpointException: No endpoint
could be found for: xyz:test";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+ JsonObject first = (JsonObject) exceptions.get(0);
+
+ JsonArray fixes = (JsonArray) first.get("suggestedFixes");
+ assertThat(fixes).isNotEmpty();
+ }
+
+ @Test
+ void resultContainsDocumentationLinks() throws Exception {
+ String error = "org.apache.camel.NoSuchEndpointException: No endpoint
could be found for: xyz:test";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+ JsonObject first = (JsonObject) exceptions.get(0);
+
+ JsonArray docs = first.getCollection("documentationLinks");
+ assertThat(docs).isNotEmpty();
+
assertThat(docs.get(0).toString()).startsWith("https://camel.apache.org/");
+ }
+
+ @Test
+ void resultContainsSummary() throws Exception {
+ String error = "org.apache.camel.NoSuchEndpointException: No endpoint";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonObject summary = result.getMap("summary");
+
+ assertThat(summary).isNotNull();
+ assertThat(summary.getBoolean("diagnosed")).isTrue();
+ assertThat(summary.getInteger("exceptionCount")).isGreaterThan(0);
+ }
+
+ @Test
+ void componentDocumentationUrlPresent() throws Exception {
+ String error = "Failed to resolve endpoint: kafka:myTopic";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray components = result.getCollection("identifiedComponents");
+
+ if (!components.isEmpty()) {
+ JsonObject comp = (JsonObject) components.get(0);
+
assertThat(comp.getString("documentationUrl")).contains("camel.apache.org");
+ }
+ }
+
+ // ---- Unrecognized errors ----
+
+ @Test
+ void unrecognizedErrorReturnsDiagnosedFalse() throws Exception {
+ String error = "Some random error that is not a Camel exception";
+
+ String json = tools.camel_error_diagnose(error);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonObject summary = result.getMap("summary");
+
+ assertThat(summary.getBoolean("diagnosed")).isFalse();
+ assertThat(summary.getInteger("exceptionCount")).isEqualTo(0);
+ }
+
+ // ---- Full stack trace ----
+
+ @Test
+ void handlesFullStackTrace() throws Exception {
+ String stackTrace
+ = """
+ org.apache.camel.FailedToCreateRouteException: Failed
to create route route1 at: >>> To[kafka:myTopic] <<< in route:
Route(route1)[From[timer:tick] -> [To[kafka:myTopic]]] because of Failed to
resolve endpoint: kafka:myTopic due to: No component found with scheme: kafka
+ \tat
org.apache.camel.reifier.RouteReifier.doCreateRoute(RouteReifier.java:230)
+ \tat
org.apache.camel.reifier.RouteReifier.createRoute(RouteReifier.java:71)
+ \tat
org.apache.camel.impl.DefaultCamelContext.startRouteDefinitions(DefaultCamelContext.java:852)
+ Caused by:
org.apache.camel.ResolveEndpointFailedException: Failed to resolve endpoint:
kafka:myTopic due to: No component found with scheme: kafka
+ \tat
org.apache.camel.impl.engine.AbstractCamelContext.getEndpoint(AbstractCamelContext.java:893)
+ Caused by: org.apache.camel.NoSuchEndpointException:
No endpoint could be found for: kafka:myTopic
+ \tat
org.apache.camel.component.direct.DirectComponent.createEndpoint(DirectComponent.java:62)
+ """;
+
+ String json = tools.camel_error_diagnose(stackTrace);
+ JsonObject result = (JsonObject) Jsoner.deserialize(json);
+ JsonArray exceptions = result.getCollection("identifiedExceptions");
+
+ // Should identify all three exceptions in the chain
+ assertThat(exceptions.size()).isGreaterThanOrEqualTo(3);
+
+ JsonArray components = result.getCollection("identifiedComponents");
+ assertThat(components.stream()
+ .map(c -> ((JsonObject) c).getString("name"))
+ .toList())
+ .contains("kafka");
+ }
+}