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

davsclaus 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 4b061cb959aa CAMEL-26720: camel-chaos - chaos-add --dry-run and a 
check tool say what a rule would hit, without adding it (#29390)
4b061cb959aa is described below

commit 4b061cb959aaa6542c835c5a56f41f5cdebc04f8
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 7 22:05:41 2026 +0100

    CAMEL-26720: camel-chaos - chaos-add --dry-run and a check tool say what a 
rule would hit, without adding it (#29390)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/catalog/docs/chaos.adoc       |  27 +++-
 components/camel-chaos/src/main/docs/chaos.adoc    |  27 +++-
 .../apache/camel/component/chaos/ChaosManager.java |  61 +++++++++
 .../apache/camel/component/chaos/ChaosTargets.java | 104 +++++++++++++++
 .../camel/component/chaos/ChaosCheckTest.java      | 142 +++++++++++++++++++++
 design/chaos.adoc                                  |   3 +-
 .../modules/ROOT/pages/camel-jbang-managing.adoc   |   4 +-
 .../modules/ROOT/pages/camel-jbang-mcp.adoc        |  12 +-
 .../jbang-commands/camel-jbang-cmd-chaos-add.adoc  |   1 +
 .../apache/camel/cli/connector/ChaosAction.java    |  12 +-
 .../LocalCliConnectorChaosActionTest.java          |  22 ++++
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../camel/dsl/jbang/core/commands/AskTools.java    |   1 +
 .../core/commands/action/CamelChaosAddAction.java  |  73 ++++++++++-
 .../dsl/jbang/core/commands/ai/ToolGroups.java     |   8 +-
 .../dsl/jbang/core/commands/ai/ToolRegistry.java   |  33 ++++-
 .../dsl/jbang/core/commands/AskToolsTest.java      |   2 +-
 .../core/commands/action/CamelChaosActionTest.java |  36 ++++++
 .../dsl/jbang/core/commands/ai/ToolGroupsTest.java |  10 +-
 .../jbang/core/commands/ai/ToolRegistryTest.java   |   7 +-
 .../core/commands/mcp/JbangDevMcpServerTest.java   |   2 +-
 .../src/test/resources/golden/cli-help.txt         |  10 +-
 .../dsl/jbang/core/commands/mcp/RuntimeTools.java  |  14 ++
 .../mcp/RuntimeToolsChaosSecurityTest.java         |   7 +-
 .../core/commands/mcp/RuntimeToolsChaosTest.java   |   7 +
 .../jbang/core/commands/mcp/RuntimeToolsTest.java  |   2 +-
 26 files changed, 588 insertions(+), 41 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
index 3ead495e3743..ba00e70049de 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
@@ -145,6 +145,23 @@ names some of the endpoints, routes or steps there are. It 
is not an error, as t
 creates its endpoints when it sends, and a route can be added later). The 
rules of `camel.chaos.rules` are checked when
 the context has started.
 
+To see what a rule would hit before it is added, `chaos-add --dry-run` checks 
it as `chaos-add` would, and adds
+nothing. It prints the rule in plain words, then each send, poll, route or 
step it matches now, with its route (and the
+step that sends), the sends known only when they send that may match (a 
`toD`), and the warnings:
+
+[source]
+----
+$ camel cmd chaos-add --dry-run --rule='endpoint=http:* in=orders error 
status=503'
+Would add: endpoint=http:* in=orders error status=503
+  fail every send to http:* in route orders with HTTP status 503
+  send http://inventory/stock in route orders (step callStock)
+(dry run: nothing was added)
+----
+
+A rule that is not valid is refused with the same reason as for `chaos-add`. A 
condition (`when=`) is checked on each
+message as it comes, so the check lists what the rule would hit if the 
messages met it, and the send of the dead letter
+channel (`at=dlq`) is not listed, as it is the error handler's.
+
 The commands go through the `chaos` action of the CLI connector, which calls 
`ChaosManager`: a rule goes through the
 same parser as `camel.chaos.rules`, and one that is not valid is refused with 
the reason. They work only when chaos is
 switched on in the integration (see <<_switching_it_on,Switching it on>>): 
they cannot switch it on, and with profile
@@ -155,9 +172,10 @@ JSON. It only reads: a rule cannot be added, changed or 
removed over HTTP.
 
 === From an AI agent (MCP)
 
-The Camel MCP server (`camel mcp`, see xref:manual::camel-jbang-mcp.adoc[Camel 
MCP server]) has the same six operations
+The Camel MCP server (`camel mcp`, see xref:manual::camel-jbang-mcp.adoc[Camel 
MCP server]) has the same operations
 as tools, for an AI agent that tries a route against failures: 
`camel_runtime_chaos_list`, `camel_runtime_chaos_report`,
-`camel_runtime_chaos_add`, `camel_runtime_chaos_change`, 
`camel_runtime_chaos_remove` and `camel_runtime_chaos_clear`.
+`camel_runtime_chaos_check` (the dry run), `camel_runtime_chaos_add`, 
`camel_runtime_chaos_change`,
+`camel_runtime_chaos_remove` and `camel_runtime_chaos_clear`.
 Without `nameOrPid` they pick the integration as the commands do: the only one 
that runs, or else the only one with
 chaos switched on, and otherwise they fail with the list of the running 
integrations. They take the same one-line
 rules: adding, changing, removing and clearing go through the same `chaos` 
action of the CLI connector, and the list and the report read the console from 
the status of the application. So chaos must be switched
@@ -165,9 +183,10 @@ on, and a rule that is not valid is refused with the 
reason; `camel_runtime_chao
 `chaos-clear --reset` does. `camel_runtime_chaos_add` takes several rules 
separated by `;`, and the add and change
 tools return the warnings of a rule that matches nothing, so an agent can fix 
a typo with a change. Adding and changing a rule are marked destructive, so a
 client that honours `destructiveHint` asks before it runs them, and the 
`read-write` access level of the MCP security
-layer does not offer them; the list and the report are read-only. `camel ask` 
has the same tools, and asks before it
+layer does not offer them; the list, the report and the check are read-only, 
so an agent can check a rule it made from
+a loose request (what it would hit, in plain words) and show it before it asks 
to add it. `camel ask` has the same tools, and asks before it
 adds or changes a rule. The development MCP tools that `camel run --mcp` 
(`camel.jbang.mcp=true`) serves over HTTP have
-only the list and the report.
+only the list and the report (not the check either: the integration parses the 
rule, and `exception=` loads a class).
 
 == Rules
 
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc 
b/components/camel-chaos/src/main/docs/chaos.adoc
index 3ead495e3743..ba00e70049de 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -145,6 +145,23 @@ names some of the endpoints, routes or steps there are. It 
is not an error, as t
 creates its endpoints when it sends, and a route can be added later). The 
rules of `camel.chaos.rules` are checked when
 the context has started.
 
+To see what a rule would hit before it is added, `chaos-add --dry-run` checks 
it as `chaos-add` would, and adds
+nothing. It prints the rule in plain words, then each send, poll, route or 
step it matches now, with its route (and the
+step that sends), the sends known only when they send that may match (a 
`toD`), and the warnings:
+
+[source]
+----
+$ camel cmd chaos-add --dry-run --rule='endpoint=http:* in=orders error 
status=503'
+Would add: endpoint=http:* in=orders error status=503
+  fail every send to http:* in route orders with HTTP status 503
+  send http://inventory/stock in route orders (step callStock)
+(dry run: nothing was added)
+----
+
+A rule that is not valid is refused with the same reason as for `chaos-add`. A 
condition (`when=`) is checked on each
+message as it comes, so the check lists what the rule would hit if the 
messages met it, and the send of the dead letter
+channel (`at=dlq`) is not listed, as it is the error handler's.
+
 The commands go through the `chaos` action of the CLI connector, which calls 
`ChaosManager`: a rule goes through the
 same parser as `camel.chaos.rules`, and one that is not valid is refused with 
the reason. They work only when chaos is
 switched on in the integration (see <<_switching_it_on,Switching it on>>): 
they cannot switch it on, and with profile
@@ -155,9 +172,10 @@ JSON. It only reads: a rule cannot be added, changed or 
removed over HTTP.
 
 === From an AI agent (MCP)
 
-The Camel MCP server (`camel mcp`, see xref:manual::camel-jbang-mcp.adoc[Camel 
MCP server]) has the same six operations
+The Camel MCP server (`camel mcp`, see xref:manual::camel-jbang-mcp.adoc[Camel 
MCP server]) has the same operations
 as tools, for an AI agent that tries a route against failures: 
`camel_runtime_chaos_list`, `camel_runtime_chaos_report`,
-`camel_runtime_chaos_add`, `camel_runtime_chaos_change`, 
`camel_runtime_chaos_remove` and `camel_runtime_chaos_clear`.
+`camel_runtime_chaos_check` (the dry run), `camel_runtime_chaos_add`, 
`camel_runtime_chaos_change`,
+`camel_runtime_chaos_remove` and `camel_runtime_chaos_clear`.
 Without `nameOrPid` they pick the integration as the commands do: the only one 
that runs, or else the only one with
 chaos switched on, and otherwise they fail with the list of the running 
integrations. They take the same one-line
 rules: adding, changing, removing and clearing go through the same `chaos` 
action of the CLI connector, and the list and the report read the console from 
the status of the application. So chaos must be switched
@@ -165,9 +183,10 @@ on, and a rule that is not valid is refused with the 
reason; `camel_runtime_chao
 `chaos-clear --reset` does. `camel_runtime_chaos_add` takes several rules 
separated by `;`, and the add and change
 tools return the warnings of a rule that matches nothing, so an agent can fix 
a typo with a change. Adding and changing a rule are marked destructive, so a
 client that honours `destructiveHint` asks before it runs them, and the 
`read-write` access level of the MCP security
-layer does not offer them; the list and the report are read-only. `camel ask` 
has the same tools, and asks before it
+layer does not offer them; the list, the report and the check are read-only, 
so an agent can check a rule it made from
+a loose request (what it would hit, in plain words) and show it before it asks 
to add it. `camel ask` has the same tools, and asks before it
 adds or changes a rule. The development MCP tools that `camel run --mcp` 
(`camel.jbang.mcp=true`) serves over HTTP have
-only the list and the report.
+only the list and the report (not the check either: the integration parses the 
rule, and `exception=` loads a class).
 
 == Rules
 
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
index 867de4d2ffde..87c4bcdf578d 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosManager.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.Deque;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -39,6 +40,8 @@ import org.apache.camel.component.chaos.ChaosRule.Hook;
 import org.apache.camel.component.chaos.ChaosRule.State;
 import org.apache.camel.support.ScheduledPollConsumer;
 import org.apache.camel.util.TimeSource;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -284,6 +287,64 @@ public final class ChaosManager {
             }
             rule = rules.get(index);
         }
+        return warnings(rule);
+    }
+
+    /**
+     * Checks rules without adding them ({@code chaos-add --dry-run}, the 
chaos_check tool): each is parsed and checked
+     * as {@link #addRules(String)} does, so a rule that is not valid is 
refused with the same reason, and nothing
+     * changes in the context. For each rule, in their order: the rule, the 
rule in plain words at the hooks where it
+     * can fire now, those hooks, the warnings of a rule that matches nothing 
now, and what it would hit now (the sends,
+     * polls, routes or steps, each with its route; see {@link 
ChaosTargets#hits}).
+     *
+     * @param  rules                    the rules in their one-line text form, 
separated by {@code ;}
+     * @return                          {@code {"rules": [...]}}, as JSON
+     * @throws IllegalArgumentException when a rule is not valid, with the 
reason
+     */
+    public Map<String, Object> checkRules(String rules) {
+        List<String> texts = ChaosRuleParser.splitRules(rules);
+        if (texts.isEmpty()) {
+            throw new IllegalArgumentException("The chaos rule is empty");
+        }
+        List<ChaosRule.Builder> definitions = new ArrayList<>();
+        for (String text : texts) {
+            ChaosRule.Builder definition = parse(text);
+            checkRoute(definition);
+            definitions.add(definition);
+        }
+        long now = TimeSource.currentTimeMillis();
+        ChaosTargets.Reach reach = ChaosTargets.reach(camelContext);
+        JsonArray checked = new JsonArray();
+        for (ChaosRule.Builder definition : definitions) {
+            // a rule of its own, which no hook sees: its id is only for the 
words of the warnings
+            ChaosRule rule = new ChaosRule(definition, "check", seed, now, 
maxDuration);
+            Set<Hook> hooks = ChaosTargets.hooksThatCanFire(reach, rule);
+            JsonObject jo = new JsonObject();
+            jo.put("rule", rule.text);
+            jo.put("description", rule.describe(hooks));
+            JsonArray names = new JsonArray();
+            hooks.forEach(h -> names.add(h.key));
+            jo.put("hooks", names);
+            List<String> warnings = warnings(rule);
+            if (!warnings.isEmpty()) {
+                jo.put("warnings", new JsonArray(warnings));
+            }
+            if (camelContext.isStarted()) {
+                jo.put("hits", ChaosTargets.hits(camelContext, rule));
+            }
+            if (rule.when != null) {
+                // the condition is checked on each message as it comes
+                jo.put("when", rule.when);
+            }
+            checked.add(jo);
+        }
+        JsonObject answer = new JsonObject();
+        answer.put("rules", checked);
+        return answer;
+    }
+
+    /** The warnings of a rule that matches nothing in the context now (none 
before the context started). */
+    private List<String> warnings(ChaosRule rule) {
         List<String> answer = new ArrayList<>();
         if (!camelContext.isStarted()) {
             return answer;
diff --git 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosTargets.java
 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosTargets.java
index 6cb85d17c154..8d9bc3631f12 100644
--- 
a/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosTargets.java
+++ 
b/components/camel-chaos/src/main/java/org/apache/camel/component/chaos/ChaosTargets.java
@@ -48,6 +48,8 @@ import org.apache.camel.model.language.ExpressionDefinition;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.support.ScheduledPollConsumer;
 import org.apache.camel.util.URISupport;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -81,6 +83,108 @@ final class ChaosTargets {
         return answer == null && rule.in != null ? checkIn(camelContext, rule) 
: answer;
     }
 
+    /**
+     * What a rule would hit in the context now, for a check before it is 
added ({@code chaos-add --dry-run}): the sends
+     * of the routes to a matching endpoint (with the route and the step that 
sends), the sends known only when they
+     * send that may match, the polls of a matching endpoint, the routes of a 
{@code route=} target, or the steps of a
+     * {@code step=} target, each in the routes of {@code in=} when it has 
one. At most {@link #HITS} of each, with
+     * {@code more} for the rest. The send of the dead letter channel is not 
listed, as it is the error handler's.
+     */
+    static JsonObject hits(CamelContext camelContext, ChaosRule rule) {
+        JsonObject answer = new JsonObject();
+        List<Route> routes;
+        try {
+            routes = new ArrayList<>(camelContext.getRoutes());
+        } catch (RuntimeException e) {
+            return answer;
+        }
+        int[] more = new int[1];
+        JsonArray sends = new JsonArray();
+        JsonArray dynamic = new JsonArray();
+        JsonArray polls = new JsonArray();
+        JsonArray routeHits = new JsonArray();
+        JsonArray steps = new JsonArray();
+        boolean endpoints = rule.target == ChaosRule.Target.ENDPOINT || 
rule.target == ChaosRule.Target.COMPONENT;
+        boolean atSends = rule.hooks.contains(Hook.SEND) || 
rule.hooks.contains(Hook.REPLY);
+        for (Route route : routes) {
+            String routeId = route.getRouteId();
+            if (rule.target == ChaosRule.Target.ROUTE) {
+                if (rule.matchesId(routeId)) {
+                    add(routeHits, more, hit("route", routeId, "from", 
withoutQuery(endpointUri(route.getEndpoint()))));
+                }
+                continue;
+            }
+            if (!rule.matchesIn(routeId)) {
+                continue;
+            }
+            if (endpoints && rule.hooks.contains(Hook.POLL) && 
route.getConsumer() instanceof ScheduledPollConsumer
+                    && rule.matchesUri(endpointUri(route.getEndpoint()))) {
+                add(polls, more, hit("route", routeId, "endpoint", 
withoutQuery(endpointUri(route.getEndpoint()))));
+            }
+            for (Processor processor : route.filter("*")) {
+                String id = processor instanceof IdAware idAware ? 
idAware.getId() : null;
+                if (rule.target == ChaosRule.Target.STEP) {
+                    if (id != null && rule.matchesId(id)) {
+                        add(steps, more, hit("route", routeId, "step", id));
+                    }
+                } else if (endpoints && atSends && processor instanceof 
EndpointAware aware && aware.getEndpoint() != null
+                        && 
rule.matchesUri(ChaosPatterns.endpointUri(aware.getEndpoint()))) {
+                    JsonObject send = hit("route", routeId, "endpoint", 
withoutQuery(endpointUri(aware.getEndpoint())));
+                    if (id != null) {
+                        send.put("step", id);
+                    }
+                    add(sends, more, send);
+                }
+            }
+            if (endpoints && atSends && route.getRoute() instanceof 
RouteDefinition definition) {
+                List<String> uris = new ArrayList<>();
+                dynamicSends(definition.getOutputs(), uris);
+                for (String uri : uris) {
+                    if (mayMatch(rule, uri)) {
+                        add(dynamic, more, hit("route", routeId, "uri", 
withoutQuery(uri)));
+                    }
+                }
+            }
+        }
+        putIfAny(answer, "sends", sends);
+        putIfAny(answer, "dynamicSends", dynamic);
+        putIfAny(answer, "polls", polls);
+        putIfAny(answer, "routes", routeHits);
+        putIfAny(answer, "steps", steps);
+        if (more[0] > 0) {
+            answer.put("more", more[0]);
+        }
+        return answer;
+    }
+
+    // the most hits of each kind a check lists
+    static final int HITS = 25;
+
+    private static String endpointUri(Endpoint endpoint) {
+        return endpoint != null ? ChaosPatterns.endpointUri(endpoint) : "";
+    }
+
+    private static JsonObject hit(String k1, String v1, String k2, String v2) {
+        JsonObject jo = new JsonObject();
+        jo.put(k1, v1);
+        jo.put(k2, v2);
+        return jo;
+    }
+
+    private static void add(JsonArray hits, int[] more, JsonObject hit) {
+        if (hits.size() < HITS) {
+            hits.add(hit);
+        } else {
+            more[0]++;
+        }
+    }
+
+    private static void putIfAny(JsonObject answer, String key, JsonArray 
hits) {
+        if (!hits.isEmpty()) {
+            answer.put(key, hits);
+        }
+    }
+
     /**
      * Why a rule with {@code in=} finds nothing to fire at in its routes: no 
route matches {@code in=}, or its target
      * matches nothing inside those routes (a send, a step, or the poll of the 
consumer of the route). A send inside a
diff --git 
a/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosCheckTest.java
 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosCheckTest.java
new file mode 100644
index 000000000000..47dd3397510b
--- /dev/null
+++ 
b/components/camel-chaos/src/test/java/org/apache/camel/component/chaos/ChaosCheckTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.component.chaos;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * {@link ChaosManager#checkRules(String)}: a rule is checked as it would be 
added, and what it would hit now is listed,
+ * without adding it ({@code chaos-add --dry-run}, the chaos_check tool).
+ */
+class ChaosCheckTest extends ChaosTestSupport {
+
+    @BindToRegistry("testpoll")
+    private final TestPollComponent testPoll = new TestPollComponent();
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:orders").routeId("orders")
+                        .to("direct:stock").id("callStock")
+                        .to("mock:shipping?retainFirst=10").id("ship")
+                        .toD("mock:dyn-${header.n}").id("dyn");
+                from("direct:stock").routeId("stock")
+                        .log("stock").id("logStock")
+                        .to("mock:shipping").id("stockShip");
+                
from("testpoll:inbox?initialDelay=3600000").routeId("poller").to("mock:in").id("toIn");
+            }
+        };
+    }
+
+    @SuppressWarnings("unchecked")
+    private JsonObject check(String rules) {
+        return new JsonObject((Map<String, Object>) chaos().checkRules(rules));
+    }
+
+    private JsonObject only(String rule) {
+        List<Object> rules = check(rule).getCollection("rules");
+        assertThat(rules).hasSize(1);
+        return (JsonObject) rules.get(0);
+    }
+
+    private static List<Object> hits(JsonObject checked, String kind) {
+        return ((JsonObject) checked.get("hits")).getCollection(kind);
+    }
+
+    @Test
+    void theSendsItWouldHitWithTheirRouteAndStep() {
+        JsonObject checked = only("endpoint=mock:shipping* error rate=0.5");
+
+        
assertThat(checked.getString("rule")).isEqualTo("endpoint=mock:shipping* error 
rate=0.5");
+        assertThat(checked.getString("description")).isEqualTo("fail half of 
the sends to mock:shipping*");
+        assertThat((List<Object>) 
checked.getCollection("hooks")).containsExactly("send");
+        // the query of an endpoint is left out (it can hold secrets)
+        assertThat(hits(checked, "sends")).containsExactly(
+                Map.of("route", "orders", "endpoint", "mock://shipping", 
"step", "ship"),
+                Map.of("route", "stock", "endpoint", "mock://shipping", 
"step", "stockShip"));
+        // nothing was added
+        assertThat((JsonArray) 
chaos().report().toJson().get("rules")).isEmpty();
+    }
+
+    @Test
+    void inNarrowsTheHitsToTheRoute() {
+        JsonObject checked = only("endpoint=mock:shipping* in=stock error");
+        assertThat(hits(checked, "sends")).singleElement()
+                .isEqualTo(Map.of("route", "stock", "endpoint", 
"mock://shipping", "step", "stockShip"));
+    }
+
+    @Test
+    void aSendKnownOnlyWhenItSendsMayMatch() {
+        JsonObject checked = only("endpoint=mock:dyn-* delay duration=1ms");
+        assertThat(hits(checked, "dynamicSends")).singleElement()
+                .isEqualTo(Map.of("route", "orders", "uri", 
"mock:dyn-${header.n}"));
+    }
+
+    @Test
+    void thePollsTheRoutesAndTheSteps() {
+        assertThat(hits(only("component=testpoll drop at=poll"), 
"polls")).singleElement()
+                .isEqualTo(Map.of("route", "poller", "endpoint", 
"testpoll://inbox"));
+        assertThat(hits(only("route=sto* error"), "routes")).singleElement()
+                .isEqualTo(Map.of("route", "stock", "from", "direct://stock"));
+        assertThat(hits(only("step=*Stock error in=stock"), 
"steps")).singleElement()
+                .isEqualTo(Map.of("route", "stock", "step", "logStock"));
+        assertThat(hits(only("route=poller stall duration=10s"), 
"routes")).singleElement()
+                .isEqualTo(Map.of("route", "poller", "from", 
"testpoll://inbox"));
+    }
+
+    @Test
+    void aRuleThatHitsNothingHasItsWarning() {
+        JsonObject checked = only("endpoint=mock:shiping error");
+        assertThat((JsonObject) checked.get("hits")).isEmpty();
+        assertThat((List<Object>) 
checked.getCollection("warnings")).singleElement().asString()
+                .startsWith("endpoint=mock:shiping matches no endpoint of the 
context now");
+    }
+
+    @Test
+    void severalRulesAndACondition() {
+        List<Object> rules = check("endpoint=mock:shipping error 
at=send;route=orders error when=\"${header.a} == 'b'\"")
+                .getCollection("rules");
+        assertThat(rules).hasSize(2);
+        JsonObject second = (JsonObject) rules.get(1);
+        assertThat(second.getString("when")).isEqualTo("${header.a} == 'b'");
+        assertThat(second.getString("description")).isEqualTo("fail every 
message entering route orders, when"
+                                                              + " ${header.a} 
== 'b'");
+    }
+
+    @Test
+    void aRuleThatIsNotValidIsRefusedAsAddWouldRefuseIt() {
+        assertThatThrownBy(() -> chaos().checkRules("endpoint=mock:x explode"))
+                
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("unknown 
fault explode");
+        assertThatThrownBy(() -> chaos().checkRules("route=nothere stall 
duration=1s"))
+                
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("there is no 
route nothere");
+        assertThatThrownBy(() -> chaos().checkRules(" "))
+                .isInstanceOf(IllegalArgumentException.class).hasMessage("The 
chaos rule is empty");
+        assertThat((JsonArray) 
chaos().report().toJson().get("rules")).isEmpty();
+    }
+}
diff --git a/design/chaos.adoc b/design/chaos.adoc
index e43ee7cb79d4..7f8d504d67a0 100644
--- a/design/chaos.adoc
+++ b/design/chaos.adoc
@@ -409,7 +409,8 @@ pending is not final.
 
 |`camel_runtime_chaos_add`, `camel_runtime_chaos_change` |`false` |`true`
 |`camel_runtime_chaos_remove`, `camel_runtime_chaos_clear` |`false` |`false`
-|`camel_runtime_chaos_list`, `camel_runtime_chaos_report` |`true` |`false`
+|`camel_runtime_chaos_list`, `camel_runtime_chaos_report`, 
`camel_runtime_chaos_check` (stage 2, CAMEL-26720)
+|`true` |`false`
 |===
 +
 With `camel.mcp.security.enabled=true` and 
`camel.mcp.security.access-level=read-write`, add and change are refused,
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index 8e40ef2d04b7..92063c7bfa29 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -902,7 +902,9 @@ camel cmd chaos-clear
 
 `chaos-add`, `chaos-change` and `chaos-remove` take the name or pid of the 
integration; without it they act on the
 only integration that runs, or the only one with chaos switched on, and 
otherwise fail with the list of the running
-integrations. `chaos-add` prints the rule in plain words too (`fail 30% of the 
sends to kafka:orders*, for 1 minute`).
+integrations. `chaos-add` prints the rule in plain words too (`fail 30% of the 
sends to kafka:orders*, for 1 minute`),
+and with `--dry-run` it adds nothing, and prints what the rule would hit now: 
the sends, polls, routes or steps, with
+their route.
 `camel get chaos` lists the current rules with their id, version, state, 
matches and the faults that fired, and
 `--report` shows the report: the verdict, the seed, every version of every 
rule and the counts per route. Chaos is for
 development only: it stays off with profile `prod`, and the commands cannot 
switch it on.
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 eeb434db5638..9de8f7d3463f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -1087,6 +1087,12 @@ that changes rules is refused. The list and the report 
read the chaos console fr
 | The report: the verdict and whether it is final, the seed, every version of 
every rule with its live state, the
   expectations, the counts per route and the last 20 faults.
 
+| `camel_runtime_chaos_check`
+| `true` | `false`
+| Checks a rule (`rule`), or several separated by `;`, without adding it, as 
`camel cmd chaos-add --dry-run` does:
+  each in plain words, where it can fire, `warnings`, and what it would hit 
now (`hits`: the sends, polls, routes or
+  steps, each with its route). A rule that is not valid is refused with the 
reason.
+
 | `camel_runtime_chaos_add`
 | `false` | `true`
 | Adds a rule (`rule`), or several separated by `;` (all or none), and returns 
the ids (`r1`, `r2`, ...), with
@@ -1110,11 +1116,11 @@ that changes rules is refused. The list and the report 
read the chaos console fr
 Adding and changing a rule are marked destructive: a rule makes messages fail, 
wait or get lost, and its
 `exception=` can name any exception class. So a client that honours 
`destructiveHint` asks before it runs them, and
 the `read-write` access level of the security layer 
(`camel.mcp.security.access-level`) does not offer them, as it does
-not offer `camel_runtime_route_control` or `camel_runtime_sql`; `read-only` 
offers the list and the report. Removing
+not offer `camel_runtime_route_control` or `camel_runtime_sql`; `read-only` 
offers the list, the report and the check. Removing
 and clearing only take faults away, and the report keeps the removed rules, so 
they are not destructive. `camel ask`
 has the same tools (`chaos_add` and the others), and asks the user before it 
adds or changes a rule. The development
-MCP tools that `camel run --mcp` serves over HTTP have only the list and the 
report: a rule is never added, changed or
-removed over the HTTP server of the application.
+MCP tools that `camel run --mcp` serves over HTTP have only the list and the 
report: a rule is never added, changed,
+removed or checked over the HTTP server of the application.
 
 ==== Tool groups for local models
 
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-add.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-add.adoc
index fff9e87e943e..6b27ddcf0a67 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-add.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-add.adoc
@@ -19,6 +19,7 @@ camel cmd chaos-add [options]
 [cols="2,5,1,2",options="header"]
 |===
 | Option | Description | Default | Type
+| `--dry-run` | Check the rules without adding them: each in plain words, and 
what it would hit in the integration now (the sends, polls, routes or steps, 
with their route) |  | boolean
 | `--rule` | A rule in its one-line text form, as an entry of 
camel.chaos.rules; can be given more than once |  | List
 | `--timeout` | How long to wait for the integration to answer, in millis or 
as a duration such as 30s | 10000 | long
 | `-h,--help` | Display the help and sub-commands |  | boolean
diff --git 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
index 9ff87bf81b58..b20646fa0ee1 100644
--- 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
+++ 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/ChaosAction.java
@@ -20,6 +20,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.util.json.JsonArray;
@@ -80,6 +81,15 @@ final class ChaosAction {
                 answer.put("version", 1);
                 descriptions(type, chaos, ids, answer);
                 warnings(type, chaos, ids, answer);
+            } else if ("check".equals(command)) {
+                if (isBlank(rule)) {
+                    output.error("A rule is required");
+                    return;
+                }
+                // checked as add checks it, and not added: what it would hit 
now, in plain words
+                @SuppressWarnings("unchecked")
+                Map<String, Object> checked = (Map<String, Object>) call(type, 
chaos, "checkRules", rule);
+                answer.putAll(checked);
             } else if ("change".equals(command)) {
                 if (isBlank(id) || isBlank(rule)) {
                     output.error("The id of the rule and the new rule are 
required");
@@ -112,7 +122,7 @@ final class ChaosAction {
                     answer.put("reset", true);
                 }
             } else {
-                output.error("Unknown chaos command: " + command + " (add, 
change, remove or clear)");
+                output.error("Unknown chaos command: " + command + " (add, 
check, change, remove or clear)");
                 return;
             }
         } catch (IllegalArgumentException | IllegalStateException e) {
diff --git 
a/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/LocalCliConnectorChaosActionTest.java
 
b/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/LocalCliConnectorChaosActionTest.java
index b4ec117a932d..3de5ff5fd273 100644
--- 
a/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/LocalCliConnectorChaosActionTest.java
+++ 
b/dsl/camel-cli-connector/src/test/java/org/apache/camel/cli/connector/LocalCliConnectorChaosActionTest.java
@@ -155,6 +155,28 @@ class LocalCliConnectorChaosActionTest {
         assertThat(reportRules()).isEmpty();
     }
 
+    @Test
+    void checkSaysWhatARuleWouldHitWithoutAddingIt() throws Exception {
+        start(null, Map.of("camel.chaos.enabled", "true"));
+
+        CliActionOutcome checked = chaos("check", null, "endpoint=mock:target 
error rate=0.5");
+        assertThat(checked.error()).isNull();
+        JsonObject rule = (JsonObject) 
checked.result().getCollection("rules").iterator().next();
+        assertThat(rule.getString("description")).isEqualTo("fail half of the 
sends to mock:target");
+        JsonObject send = (JsonObject) ((JsonObject) 
rule.get("hits")).getCollection("sends").iterator().next();
+        assertThat(send.getString("route")).isEqualTo("start");
+        assertThat(send.getString("endpoint")).isEqualTo("mock://target");
+        // nothing was added, and the messages go through
+        assertThat(reportRules()).isEmpty();
+        assertThat(context.createProducerTemplate().send("direct:start", e -> 
e.getMessage().setBody("x"))
+                .getException()).isNull();
+
+        // refused with the reason, as add would refuse it
+        assertThat(chaos("check", null, "endpoint=mock:target error 
rate=2").error())
+                .contains("rate=2 is not a probability above 0 and at most 1");
+        assertThat(chaos("check", null, " ").error()).isEqualTo("A rule is 
required");
+    }
+
     @Test
     void rulesAreAddedTogetherWithWarningsForTheOnesThatMatchNothing() throws 
Exception {
         start(null, Map.of("camel.chaos.enabled", "true"));
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 39463c0717ac..0739cd2af369 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -3,7 +3,7 @@
     { "name": "ask", "fullName": "ask", "description": "Ask a question about a 
running Camel application using AI", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Ask", "options": [ { "names": 
"--api-key", "description": "API key. Also reads ANTHROPIC_API_KEY, 
OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--api-type", "description": 
"API type: 'ollama', 'openai', 'anthropic', or 'watsonx'", "javaType":  [...]
     { "name": "bind", "fullName": "bind", "description": "DEPRECATED: Bind 
source and sink Kamelets as a new Camel integration", "deprecated": true, 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.bind.Bind", "options": 
[ { "names": "--error-handler", "description": "Add error handler 
(none|log|sink:<endpoint>). Sink endpoints are expected in the format 
\"[[apigroup\/]version:]kind:[namespace\/]name\", plain Camel URIs or Kamelet 
name.", "javaType": "java.lang.String", "type": " [...]
     { "name": "catalog", "fullName": "catalog", "description": "List artifacts 
from Camel Catalog", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand", "options": [ 
{ "names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"component", "fullName": "catalog component", "description": "List components 
from the Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.co [...]
-    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
+    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
     { "name": "completion", "fullName": "completion", "description": "Generate 
completion script for bash\/zsh", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ] },
     { "name": "config", "fullName": "config", "description": "Get and set user 
configuration values", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "config get", "description": "Display user configuration value", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
     { "name": "debug", "fullName": "debug", "description": "Debug local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", 
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd 
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background", "description": "Run in the background", "defaultValue": 
"false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "How [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AskTools.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AskTools.java
index 4cab9fb57077..14de64363aa4 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AskTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AskTools.java
@@ -71,6 +71,7 @@ public class AskTools {
             Map.entry(ToolGroups.HTTP_REQUEST_TOOL, "http_request"),
             Map.entry(ToolGroups.CHAOS_LIST_TOOL, "chaos_list"),
             Map.entry(ToolGroups.CHAOS_REPORT_TOOL, "chaos_report"),
+            Map.entry(ToolGroups.CHAOS_CHECK_TOOL, "chaos_check"),
             Map.entry(ToolGroups.CHAOS_ADD_TOOL, "chaos_add"),
             Map.entry(ToolGroups.CHAOS_CHANGE_TOOL, "chaos_change"),
             Map.entry(ToolGroups.CHAOS_REMOVE_TOOL, "chaos_remove"),
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosAddAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosAddAction.java
index 6873634335e3..be103a2bbcce 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosAddAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosAddAction.java
@@ -45,7 +45,8 @@ import picocli.CommandLine;
                              "  camel cmd chaos-add myApp 
--rule='endpoint=http:* in=orders error status=503 rate=0.1'",
                              "  camel cmd chaos-add myApp --rule='route=orders 
error rate=0.05'",
                              "  camel cmd chaos-add myApp 
--rule='endpoint=http:stock* error when=\"$${header.country} == \\\"ES\\\"\"'",
-                             "  camel cmd chaos-add myApp 
--rule='endpoint=seda:dlq error at=dlq' --rule='endpoint=http:stock* error'" })
+                             "  camel cmd chaos-add myApp 
--rule='endpoint=seda:dlq error at=dlq' --rule='endpoint=http:stock* error'",
+                             "  camel cmd chaos-add myApp --dry-run 
--rule='endpoint=http:* in=orders error status=503'" })
 public class CamelChaosAddAction extends CamelChaosAction {
 
     @CommandLine.Parameters(description = "Name or pid of the running Camel 
integration (must match one); without it,"
@@ -58,6 +59,11 @@ public class CamelChaosAddAction extends CamelChaosAction {
                                       + " than once")
     List<String> rules;
 
+    @CommandLine.Option(names = { "--dry-run" },
+                        description = "Check the rules without adding them: 
each in plain words, and what it would hit in"
+                                      + " the integration now (the sends, 
polls, routes or steps, with their route)")
+    boolean dryRun;
+
     public CamelChaosAddAction(CamelJBangMain main) {
         super(main);
     }
@@ -68,10 +74,75 @@ public class CamelChaosAddAction extends CamelChaosAction {
         CamelProcess process = findChaosTarget(name);
         // the rules go together, separated by ; as in camel.chaos.rules (a ; 
inside a rule is quoted)
         String text = String.join(";", rules);
+        if (dryRun) {
+            printer().println(describeCheck(chaos(process, "check", root -> 
root.put("rule", text))));
+            return 0;
+        }
         printer().println(describe(chaos(process, "add", root -> 
root.put("rule", text))));
         return 0;
     }
 
+    /**
+     * The rules of a dry run: each in plain words, then what it would hit 
now, by kind, and its warnings.
+     */
+    static String describeCheck(JsonObject answer) {
+        StringBuilder sb = new StringBuilder();
+        List<Object> checked = answer.getCollection("rules");
+        if (checked == null) {
+            return "The integration did not check the rules";
+        }
+        for (Object o : checked) {
+            JsonObject rule = (JsonObject) o;
+            if (!sb.isEmpty()) {
+                sb.append('\n');
+            }
+            sb.append("Would add: ").append(rule.getString("rule"));
+            String words = rule.getString("description");
+            if (words != null) {
+                sb.append("\n  ").append(words);
+            }
+            JsonObject hits = (JsonObject) rule.get("hits");
+            if (hits != null) {
+                hits(sb, hits, "sends", "send", "endpoint");
+                hits(sb, hits, "dynamicSends", "send (known when it sends)", 
"uri");
+                hits(sb, hits, "polls", "poll", "endpoint");
+                hits(sb, hits, "routes", "route", "from");
+                hits(sb, hits, "steps", "step", "step");
+                Integer more = hits.getInteger("more");
+                if (more != null && more > 0) {
+                    sb.append("\n  and ").append(more).append(" more");
+                }
+                if (hits.isEmpty()) {
+                    sb.append("\n  hits nothing in the integration now");
+                }
+            }
+            List<Object> warnings = rule.getCollection("warnings");
+            if (warnings != null) {
+                warnings.forEach(w -> sb.append("\n  Warning: ").append(w));
+            }
+        }
+        return sb + "\n(dry run: nothing was added)";
+    }
+
+    private static void hits(StringBuilder sb, JsonObject hits, String key, 
String kind, String field) {
+        List<Object> list = hits.getCollection(key);
+        if (list == null) {
+            return;
+        }
+        for (Object o : list) {
+            JsonObject hit = (JsonObject) o;
+            sb.append("\n  ").append(kind).append(' ');
+            if ("route".equals(kind)) {
+                sb.append(hit.getString("route")).append(" (from 
").append(hit.getString(field)).append(')');
+            } else {
+                sb.append(hit.getString(field)).append(" in route 
").append(hit.getString("route"));
+                if (hit.getString("step") != null && !"step".equals(kind)) {
+                    sb.append(" (step 
").append(hit.getString("step")).append(')');
+                }
+            }
+        }
+    }
+
     @Override
     protected String describe(JsonObject answer) {
         List<Object> ids = answer.getCollection("ids");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolGroups.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolGroups.java
index 6aaf456178f5..8d402a6bb73c 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolGroups.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolGroups.java
@@ -41,6 +41,7 @@ public final class ToolGroups {
     public static final String CHAOS_LIST_TOOL = "camel_runtime_chaos_list";
     public static final String CHAOS_REPORT_TOOL = 
"camel_runtime_chaos_report";
     public static final String CHAOS_ADD_TOOL = "camel_runtime_chaos_add";
+    public static final String CHAOS_CHECK_TOOL = "camel_runtime_chaos_check";
     public static final String CHAOS_CHANGE_TOOL = 
"camel_runtime_chaos_change";
     public static final String CHAOS_REMOVE_TOOL = 
"camel_runtime_chaos_remove";
     public static final String CHAOS_CLEAR_TOOL = "camel_runtime_chaos_clear";
@@ -138,8 +139,8 @@ public final class ToolGroups {
                 case CHAOS -> groups.add(new Group(
                         group,
                         f.chaosOn()
-                                ? List.of(CHAOS_LIST_TOOL, CHAOS_REPORT_TOOL, 
CHAOS_ADD_TOOL, CHAOS_CHANGE_TOOL,
-                                        CHAOS_REMOVE_TOOL, CHAOS_CLEAR_TOOL)
+                                ? List.of(CHAOS_LIST_TOOL, CHAOS_REPORT_TOOL, 
CHAOS_CHECK_TOOL, CHAOS_ADD_TOOL,
+                                        CHAOS_CHANGE_TOOL, CHAOS_REMOVE_TOOL, 
CHAOS_CLEAR_TOOL)
                                 : List.of(CHAOS_LIST_TOOL, CHAOS_REPORT_TOOL),
                         describeChaos(f)));
             }
@@ -236,7 +237,8 @@ public final class ToolGroups {
         }
         return "Chaos: camel-chaos is on, so failures may be injected on 
purpose; " + CHAOS_LIST_TOOL + " and "
                + CHAOS_REPORT_TOOL + " show the rules and what they did; " + 
CHAOS_ADD_TOOL
-               + " takes a rule such as endpoint=kafka:orders* error rate=0.1 
until=5m.";
+               + " takes a rule such as endpoint=kafka:orders* error rate=0.1 
until=5m, and " + CHAOS_CHECK_TOOL
+               + " says what it would hit without adding it.";
     }
 
     /** {@code Circuit breakers in routes a, b}, or without the routes when 
they are not known. */
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
index 35d7936cbeaa..fceacc0efc98 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
@@ -81,7 +81,10 @@ public final class ToolRegistry {
      * over HTTP chaos only lists and reports, as its developer console does 
(design/chaos.adoc).
      */
     public static List<ToolDescriptor> httpTools() {
-        return TOOLS.stream().filter(t -> !t.name().startsWith("chaos_") || 
t.isReadOnly()).toList();
+        // chaos_check is read-only, but the integration parses the rule, and 
exception= loads a class: off HTTP too
+        return TOOLS.stream()
+                .filter(t -> !t.name().startsWith("chaos_") || t.isReadOnly() 
&& !"chaos_check".equals(t.name()))
+                .toList();
     }
 
     public static ToolDescriptor findTool(String name) {
@@ -731,12 +734,12 @@ public final class ToolRegistry {
               + " with chaos switched on)";
 
     /**
-     * The chaos tools, the six operations of camel cmd chaos-add, 
chaos-change, chaos-remove, chaos-clear and camel get
-     * chaos [--report]. The list and the report read the chaos console in the 
status of the integration; the others
-     * send the chaos action of the CLI connector, which works only when chaos 
is switched on and answers a rule that is
-     * not valid with the reason of the parser of camel.chaos.rules. Adding 
and changing a rule is destructive: a rule
-     * makes messages fail, wait or get lost, and exception= can name any 
exception class. Removing and clearing only
-     * take faults away, and the report keeps the removed rules.
+     * The chaos tools, the seven operations of camel cmd chaos-add 
[--dry-run], chaos-change, chaos-remove, chaos-clear
+     * and camel get chaos [--report]. The list and the report read the chaos 
console in the status of the integration;
+     * the others send the chaos action of the CLI connector, which works only 
when chaos is switched on and answers a
+     * rule that is not valid with the reason of the parser of 
camel.chaos.rules. Adding and changing a rule is
+     * destructive: a rule makes messages fail, wait or get lost, and 
exception= can name any exception class. Removing
+     * and clearing only take faults away, and the report keeps the removed 
rules.
      */
     private static void registerChaosTools() {
         register(tool("chaos_list",
@@ -783,6 +786,22 @@ public final class ToolRegistry {
                     return chaos(ctx, "add", null, rule);
                 }));
 
+        register(tool("chaos_check",
+                "Check chaos rules without adding them (as camel cmd chaos-add 
--dry-run): each rule in plain words, "
+                                     + "where it can fire, warnings, and what 
it would hit now (hits: the sends, polls, "
+                                     + "routes or steps, each with its route). 
A rule that is not valid is refused "
+                                     + "with the reason, as chaos_add would; 
nothing changes.")
+                .param("rule", "string",
+                        "The rule in its one-line text form, as an entry of 
camel.chaos.rules; several rules separated by ;",
+                        true)
+                .param("name", "string", CHAOS_NAME, false)
+                .readOnly(true).destructive(false)
+                .executor((ctx, args) -> {
+                    String rule = AuthoringTools.required(args, "rule");
+                    selectChaosProcess(ctx, args);
+                    return chaos(ctx, "check", null, rule);
+                }));
+
         register(tool("chaos_change",
                 "Change a chaos rule of the integration: the rule with the id 
gets the new one-line rule and one more "
                                       + "version; the report keeps the version 
it replaces. Returns the id, the new "
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/AskToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/AskToolsTest.java
index 617516b29b66..7c06a083e250 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/AskToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/AskToolsTest.java
@@ -232,7 +232,7 @@ class AskToolsTest {
         assertThat(change.title()).isEqualTo("change chaos rule r1 of the 
selected integration");
         assertThat(change.target()).isEqualTo("endpoint=* drop");
         // the others only read or take faults away
-        for (String name : List.of("chaos_remove", "chaos_clear", 
"chaos_list", "chaos_report")) {
+        for (String name : List.of("chaos_check", "chaos_remove", 
"chaos_clear", "chaos_list", "chaos_report")) {
             assertThat(tools.confirmation(name, Map.of("id", 
"r1"))).as(name).isNull();
         }
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
index 7baf786508ed..e0477eca66e2 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
@@ -122,6 +122,42 @@ class CamelChaosActionTest extends 
ActionCommandTestSupport {
                 "Added chaos rule r1: fail half of the sends to http:stock* 
with HTTP status 503, for 5 minutes");
     }
 
+    @Test
+    void dryRunChecksTheRuleAndPrintsWhatItWouldHit() throws Exception {
+        writeStatusFile(TEST_PID, "orders");
+        JsonObject hits = new JsonObject(
+                Map.of(
+                        "sends", new JsonArray(
+                                List.of(new JsonObject(
+                                        Map.of("route", "orders", "endpoint", 
"http://stock";, "step", "callStock")))),
+                        "dynamicSends", new JsonArray(
+                                List.of(new JsonObject(
+                                        Map.of("route", "orders", "uri", 
"http:stock/${header.id}"))))));
+        JsonObject rule = new JsonObject(
+                Map.of("rule", "endpoint=http:stock* error status=503",
+                        "description", "fail every send to http:stock* with 
HTTP status 503", "hits", hits));
+        JsonObject nothing = new JsonObject(
+                Map.of("rule", "step=tpyo error",
+                        "description", "fail every run of step tpyo", "hits", 
new JsonObject(),
+                        "warnings", new JsonArray(List.of("step=tpyo matches 
no step of the context now"))));
+        JsonObject answer = new JsonObject(Map.of("rules", new 
JsonArray(List.of(rule, nothing))));
+
+        assertThat(callWithResponse(command(new CamelChaosAddAction(main()), 
"orders", "--dry-run",
+                "--rule=endpoint=http:stock* error status=503", 
"--rule=step=tpyo error"), answer)).isZero();
+
+        
assertThat(readActionFile(TEST_PID).getString("command")).isEqualTo("check");
+        assertThat(printer.getStdout()).isEqualTo("""
+                Would add: endpoint=http:stock* error status=503
+                  fail every send to http:stock* with HTTP status 503
+                  send http://stock in route orders (step callStock)
+                  send (known when it sends) http:stock/${header.id} in route 
orders
+                Would add: step=tpyo error
+                  fail every run of step tpyo
+                  hits nothing in the integration now
+                  Warning: step=tpyo matches no step of the context now
+                (dry run: nothing was added)""");
+    }
+
     @Test
     void addOfSeveralRulesPrintsEachInPlainWords() throws Exception {
         writeStatusFile(TEST_PID, "orders");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolGroupsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolGroupsTest.java
index f2830b7b4d4a..da9c37b4302d 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolGroupsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolGroupsTest.java
@@ -144,15 +144,17 @@ class ToolGroupsTest {
     }
 
     @Test
-    void chaosLoadsItsSixToolsWhenOnAndOnlyTheReadsWhenOff() {
+    void chaosLoadsItsSevenToolsWhenOnAndOnlyTheReadsWhenOff() {
         AppFeatures on = AppFeatures.fromStatus(new JsonObject(Map.of("chaos", 
Map.of("enabled", true))));
         ToolGroups.Selection s = ToolGroups.select(on);
         assertEquals(List.of(ToolGroup.CHAOS), s.toolGroups());
-        assertEquals(List.of("camel_runtime_chaos_list", 
"camel_runtime_chaos_report", "camel_runtime_chaos_add",
-                "camel_runtime_chaos_change", "camel_runtime_chaos_remove", 
"camel_runtime_chaos_clear"), s.mcpTools());
+        assertEquals(List.of("camel_runtime_chaos_list", 
"camel_runtime_chaos_report", "camel_runtime_chaos_check",
+                "camel_runtime_chaos_add", "camel_runtime_chaos_change", 
"camel_runtime_chaos_remove",
+                "camel_runtime_chaos_clear"), s.mcpTools());
         assertEquals("Chaos: camel-chaos is on, so failures may be injected on 
purpose; camel_runtime_chaos_list and"
                      + " camel_runtime_chaos_report show the rules and what 
they did; camel_runtime_chaos_add takes a rule"
-                     + " such as endpoint=kafka:orders* error rate=0.1 
until=5m.",
+                     + " such as endpoint=kafka:orders* error rate=0.1 
until=5m, and camel_runtime_chaos_check says what it"
+                     + " would hit without adding it.",
                 s.guidance().get(0));
         assertEquals("chaos|", s.fingerprint());
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistryTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistryTest.java
index 10d7185c5b9e..7db1a1e6744d 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistryTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistryTest.java
@@ -316,7 +316,7 @@ class ToolRegistryTest {
     @Test
     void theChaosToolsHaveTheMarksOfTheMcpTools() {
         // the same marks as camel_runtime_chaos_* (design/chaos.adoc, 
"Control")
-        for (String name : List.of("chaos_list", "chaos_report")) {
+        for (String name : List.of("chaos_list", "chaos_report", 
"chaos_check")) {
             ToolDescriptor t = ToolRegistry.findTool(name);
             assertTrue(t.isReadOnly(), name);
             assertFalse(t.isDestructive(), name);
@@ -382,8 +382,9 @@ class ToolRegistryTest {
         List<String> http = 
ToolRegistry.httpTools().stream().map(ToolDescriptor::name).toList();
         List<String> all = 
ToolRegistry.allTools().stream().map(ToolDescriptor::name).toList();
         List<String> left = all.stream().filter(n -> 
!http.contains(n)).toList();
-        // only the chaos tools that change the rules stay off HTTP; the other 
tools keep what they offered
-        assertEquals(List.of("chaos_add", "chaos_change", "chaos_remove", 
"chaos_clear"), left);
+        // only the chaos tools that change the rules, and the check (the 
integration parses the rule), stay off HTTP;
+        // the other tools keep what they offered
+        assertEquals(List.of("chaos_add", "chaos_check", "chaos_change", 
"chaos_remove", "chaos_clear"), left);
         assertTrue(http.containsAll(List.of("chaos_list", "chaos_report", 
"stop_route", "execute_sql")));
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/JbangDevMcpServerTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/JbangDevMcpServerTest.java
index 99e8e6590248..98d0e3e8c750 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/JbangDevMcpServerTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/JbangDevMcpServerTest.java
@@ -77,7 +77,7 @@ class JbangDevMcpServerTest {
                     .contains(ToolRegistry.allTools().get(0).name())
                     // over HTTP chaos only lists and reports: a rule is never 
added, changed or removed over HTTP
                     .contains("chaos_list", "chaos_report")
-                    .doesNotContain("chaos_add", "chaos_change", 
"chaos_remove", "chaos_clear");
+                    .doesNotContain("chaos_check", "chaos_add", 
"chaos_change", "chaos_remove", "chaos_clear");
 
             McpSchema.Tool parameterizedTool = 
client.listTools().tools().stream()
                     .filter(t -> "select_process".equals(t.name()))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt 
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
index 2db0b0231723..5816e13eb9ad 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
@@ -640,8 +640,8 @@ Examples:
   camel cmd browse --endpoint=seda:foo --limit=10
 
 == camel cmd chaos-add --help
-Usage: camel cmd chaos-add [-h] [--timeout=<timeout>] --rule=<rules>
-                           [--rule=<rules>]... [<name>]
+Usage: camel cmd chaos-add [-h] [--dry-run] [--timeout=<timeout>]
+                           --rule=<rules> [--rule=<rules>]... [<name>]
 Add chaos rules to a running integration (camel-chaos, for development only)
       [<name>]              Name or pid of the running Camel integration (must
                               match one); without it, the only one running, or
@@ -652,6 +652,10 @@ Add chaos rules to a running integration (camel-chaos, for 
development only)
                               Default: 10000
       --rule=<rules>        A rule in its one-line text form, as an entry of
                               camel.chaos.rules; can be given more than once
+      --dry-run             Check the rules without adding them: each in plain
+                              words, and what it would hit in the integration
+                              now (the sends, polls, routes or steps, with
+                              their route)
 
 The integration must have camel-chaos switched on (camel run --chaos, profile
 dev). The rule is checked as camel.chaos.rules is; a rule that is not valid is
@@ -679,6 +683,8 @@ rate=0.1'
 country} == \"ES\""'
   camel cmd chaos-add myApp --rule='endpoint=seda:dlq error at=dlq'
 --rule='endpoint=http:stock* error'
+  camel cmd chaos-add myApp --dry-run --rule='endpoint=http:* in=orders error
+status=503'
 
 == camel cmd chaos-change --help
 Usage: camel cmd chaos-change [-h] --id=<id> --rule=<rule>
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
index 7fd1123eabf8..a4553d8ac4df 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
@@ -1136,6 +1136,20 @@ public class RuntimeTools {
         return delegateChaos("chaos_report", nameOrPid, Map.of());
     }
 
+    @ToolGuardrails(input = McpArgumentGuardrail.class, output = 
McpArgumentGuardrail.class)
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = """
+                  Check chaos rules without adding them (as camel cmd 
chaos-add --dry-run): each rule in plain words, \
+                  where it can fire, warnings, and what it would hit now 
(hits: the sends, polls, routes or steps, \
+                  each with its route). A rule that is not valid is refused 
with the reason; nothing changes.""")
+    public JsonObject camel_runtime_chaos_check(
+            @ToolArg(description = CHAOS_NAME_OR_PID_DESC, required = false) 
String nameOrPid,
+            @ToolArg(description = "The rule in its one-line text form, as an 
entry of camel.chaos.rules; several"
+                                   + " rules separated by ;") String rule) {
+        requireArg("rule", rule);
+        return delegateChaos("chaos_check", nameOrPid, Map.of("rule", rule));
+    }
+
     @ToolGuardrails(input = McpArgumentGuardrail.class, output = 
McpArgumentGuardrail.class)
     @Tool(annotations = @Tool.Annotations(readOnlyHint = false, 
destructiveHint = true, openWorldHint = false),
           description = """
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsChaosSecurityTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsChaosSecurityTest.java
index faa54e1810f3..b240abd3f67b 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsChaosSecurityTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsChaosSecurityTest.java
@@ -45,6 +45,7 @@ class RuntimeToolsChaosSecurityTest {
     static {
         MARKS.put("camel_runtime_chaos_list", new boolean[] { true, false });
         MARKS.put("camel_runtime_chaos_report", new boolean[] { true, false });
+        MARKS.put("camel_runtime_chaos_check", new boolean[] { true, false });
         MARKS.put("camel_runtime_chaos_add", new boolean[] { false, true });
         MARKS.put("camel_runtime_chaos_change", new boolean[] { false, true });
         MARKS.put("camel_runtime_chaos_remove", new boolean[] { false, false 
});
@@ -98,11 +99,11 @@ class RuntimeToolsChaosSecurityTest {
     @Test
     void theAccessLevelsOfferTheToolsByTheirMarks() {
         assertThat(offered(McpSecurityConfig.AccessLevel.READ_ONLY))
-                .containsExactly("camel_runtime_chaos_list", 
"camel_runtime_chaos_report");
+                .containsExactly("camel_runtime_chaos_list", 
"camel_runtime_chaos_report", "camel_runtime_chaos_check");
         // as for route_control, sql, send and receive: no destructive tool at 
read-write
         
assertThat(offered(McpSecurityConfig.AccessLevel.READ_WRITE)).containsExactly(
-                "camel_runtime_chaos_list", "camel_runtime_chaos_report", 
"camel_runtime_chaos_remove",
-                "camel_runtime_chaos_clear");
+                "camel_runtime_chaos_list", "camel_runtime_chaos_report", 
"camel_runtime_chaos_check",
+                "camel_runtime_chaos_remove", "camel_runtime_chaos_clear");
         
assertThat(offered(McpSecurityConfig.AccessLevel.ADMIN)).containsExactlyElementsOf(MARKS.keySet());
         // with the security layer on, the interceptor of @McpSecured audits 
each call (and redacts the answer when
         // camel.mcp.security.redaction.enabled=true)
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsChaosTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsChaosTest.java
index 3ab4cb22ebf8..913206909627 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsChaosTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsChaosTest.java
@@ -162,6 +162,13 @@ class RuntimeToolsChaosTest {
         start(null, Map.of("camel.chaos.enabled", "true"));
         assertThat(awaitList(l -> l.getBoolean("enabled")).<List<Object>> 
getCollection("rules")).isEmpty();
 
+        // the check says what the rule would hit, and adds nothing
+        JsonObject checked = tools.camel_runtime_chaos_check("shop", 
RULE_ERROR);
+        Map<String, Object> rule = checked.<List<Map<String, Object>>> 
getCollection("rules").get(0);
+        assertThat(rule.get("description")).isEqualTo("fail every send to 
direct:target");
+        
assertThat(rule.get("hits").toString()).contains("direct://target").contains("start");
+        assertThat(send()).isNull();
+
         JsonObject added = tools.camel_runtime_chaos_add("shop", RULE_ERROR);
         assertThat(added.getString("id")).isEqualTo("r1");
         assertThat(added.getInteger("version")).isEqualTo(1);
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsTest.java
index ce032ccd2524..4283db6239ce 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeToolsTest.java
@@ -125,7 +125,7 @@ class RuntimeToolsTest {
                 "get_sql_trace",
                 "get_circuit_breakers", "get_metrics",
                 "get_eip_stats", "get_spans", "get_startup_steps", 
"get_route_analysis", "detect_config_drift",
-                "chaos_list", "chaos_report", "chaos_add", "chaos_change", 
"chaos_remove", "chaos_clear")) {
+                "chaos_list", "chaos_report", "chaos_check", "chaos_add", 
"chaos_change", "chaos_remove", "chaos_clear")) {
             assertThat(ToolRegistry.findTool(name)).as(name).isNotNull();
         }
     }

Reply via email to