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

oscerd 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 2b5be619f9fe CAMEL-24218: Add Security-First execution layer to Camel 
JBang MCP Server (#25020)
2b5be619f9fe is described below

commit 2b5be619f9fe233e91f67098f996267bae16c518
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Jul 23 15:20:36 2026 +0200

    CAMEL-24218: Add Security-First execution layer to Camel JBang MCP Server 
(#25020)
    
    * CAMEL-24218: Add Security-First execution layer to Camel JBang MCP Server
    
    Adds an opt-in security execution layer for the MCP server with:
    - Tool-level authorization (read-only/read-write/admin access levels)
      derived from existing MCP tool annotations (readOnlyHint/destructiveHint)
    - Structured audit logging of tool invocations via dedicated logger category
    - Input sanitization (argument length limits, control character stripping)
    - Secret redaction in tool responses (passwords, API keys, tokens, AWS keys,
      connection strings)
    - Configuration via Quarkus properties (camel.mcp.security.*)
    
    Uses Quarkus MCP Server 1.13.1 guardrail/filter APIs:
    - McpAccessFilter (ToolFilter) hides tools from tools/list
    - McpAccessGuardrail (ToolInputGuardrail) enforces authorization
    - McpOutputGuardrail (ToolOutputGuardrail) redacts secrets
    
    All features disabled by default for backward compatibility.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * CAMEL-24218: Address review feedback - replace guardrails with CDI 
interceptor
    
    Fixes the critical issue where ToolInputGuardrail/ToolOutputGuardrail
    were never invoked at runtime (they require per-tool @ToolGuardrails
    annotation, not auto-discovered as global CDI beans).
    
    Changes:
    - Replace McpAccessGuardrail and McpOutputGuardrail with 
McpSecurityInterceptor
      (CDI @AroundInvoke interceptor bound via @McpSecured annotation)
    - Add @McpSecured to all 19 tool classes for automatic interception
    - Create McpSecured interceptor binding annotation
    - Fix binary test file: use \0 escape sequence instead of literal null byte
    - Fix McpAuditLogger.escape() to handle \b and \f per RFC 8259
    - Rename test to McpSecurityInterceptorTest
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * CAMEL-24218: Address review feedback - fix audit logging and binary test 
file
    
    - Fix redacted flag always false in audit log (track wasRedacted variable)
    - Wire logAccessDenied() in McpAccessFilter for denied tool audit trail
    - Add PatternSyntaxException handling for custom redaction patterns
    - Replace literal control chars in test file with Java Unicode escapes
    - Add TODO for CDI interceptor connection context limitation
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    ---------
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../dsl/jbang/core/commands/mcp/AdvisoryTools.java |   1 +
 .../dsl/jbang/core/commands/mcp/CatalogTools.java  |   1 +
 .../commands/mcp/ComponentPropertiesTools.java     |   1 +
 .../commands/mcp/ConfigurationValidateTools.java   |   1 +
 .../core/commands/mcp/DependencyCheckTools.java    |   1 +
 .../dsl/jbang/core/commands/mcp/DiagnoseTools.java |   1 +
 .../dsl/jbang/core/commands/mcp/ExampleTools.java  |   1 +
 .../dsl/jbang/core/commands/mcp/ExplainTools.java  |   1 +
 .../dsl/jbang/core/commands/mcp/HardenTools.java   |   1 +
 .../dsl/jbang/core/commands/mcp/KameletTools.java  |   1 +
 .../jbang/core/commands/mcp/McpAccessFilter.java   |  68 ++++++++
 .../jbang/core/commands/mcp/McpAuditLogger.java    |  86 ++++++++++
 .../jbang/core/commands/mcp/McpSecretRedactor.java |  75 +++++++++
 .../dsl/jbang/core/commands/mcp/McpSecured.java    |  38 +++++
 .../jbang/core/commands/mcp/McpSecurityConfig.java | 141 +++++++++++++++++
 .../core/commands/mcp/McpSecurityInterceptor.java  | 175 +++++++++++++++++++++
 .../jbang/core/commands/mcp/MigrationTools.java    |   1 +
 .../commands/mcp/MigrationWildflyKarafTools.java   |   1 +
 .../dsl/jbang/core/commands/mcp/OpenApiTools.java  |   1 +
 .../commands/mcp/PropertiesTranslateTools.java     |   1 +
 .../jbang/core/commands/mcp/RouteDiagramTools.java |   1 +
 .../dsl/jbang/core/commands/mcp/RuntimeTools.java  |   1 +
 .../jbang/core/commands/mcp/TestScaffoldTools.java |   1 +
 .../jbang/core/commands/mcp/TransformTools.java    |   1 +
 .../dsl/jbang/core/commands/mcp/VersionTools.java  |   1 +
 .../src/main/resources/application.properties      |  14 ++
 .../core/commands/mcp/McpAccessFilterTest.java     |  76 +++++++++
 .../core/commands/mcp/McpAuditLoggerTest.java      |  55 +++++++
 .../core/commands/mcp/McpSecretRedactorTest.java   | 136 ++++++++++++++++
 .../core/commands/mcp/McpSecurityConfigTest.java   | 134 ++++++++++++++++
 .../commands/mcp/McpSecurityInterceptorTest.java   |  67 ++++++++
 31 files changed, 1084 insertions(+)

diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AdvisoryTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AdvisoryTools.java
index 454873b1942d..1fd76914592c 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AdvisoryTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AdvisoryTools.java
@@ -33,6 +33,7 @@ import org.apache.camel.tooling.model.SecurityAdvisoryModel;
  * Lets an LLM answer questions such as "is my Camel 4.10.1 project affected 
by known CVEs?" or "which CVEs were
  * published for camel-kafka and in which versions are they fixed?".
  */
+@McpSecured
 @ApplicationScoped
 public class AdvisoryTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
index 6358023f74ae..22298f6ee71f 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
@@ -37,6 +37,7 @@ import org.apache.camel.tooling.model.LanguageModel;
 /**
  * MCP Tools for querying the Camel Catalog using Quarkus MCP Server.
  */
+@McpSecured
 @ApplicationScoped
 public class CatalogTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ComponentPropertiesTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ComponentPropertiesTools.java
index 596c8bca1403..3b14f515aee2 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ComponentPropertiesTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ComponentPropertiesTools.java
@@ -33,6 +33,7 @@ import org.apache.camel.tooling.model.ComponentModel;
  * MCP Tool that lists valid {@code application.properties} keys for a Camel 
component, so AI agents can build
  * configuration without having to parse the full component documentation.
  */
+@McpSecured
 @ApplicationScoped
 public class ComponentPropertiesTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ConfigurationValidateTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ConfigurationValidateTools.java
index d575e5a84e41..8bff50d125c9 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ConfigurationValidateTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ConfigurationValidateTools.java
@@ -36,6 +36,7 @@ import 
org.apache.camel.catalog.ConfigurationPropertiesValidationResult;
  * Boot or Quarkus). Wraps {@link 
CamelCatalog#validateConfigurationProperty(String)} so that misspelled option 
names,
  * invalid values and suggested corrections can be surfaced to LLMs.
  */
+@McpSecured
 @ApplicationScoped
 public class ConfigurationValidateTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DependencyCheckTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DependencyCheckTools.java
index d9fbfda06a1c..bc5b92da8616 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DependencyCheckTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DependencyCheckTools.java
@@ -34,6 +34,7 @@ import org.apache.camel.tooling.model.ComponentModel;
  * Analyzes a project's pom.xml (and optionally route definitions) to detect 
outdated Camel dependencies, missing
  * dependencies for components used in routes, and version conflicts between 
the Camel BOM and explicit overrides.
  */
+@McpSecured
 @ApplicationScoped
 public class DependencyCheckTools {
 
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
index 6a90580c87b8..6a473a662c5d 100644
--- 
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
@@ -37,6 +37,7 @@ import org.apache.camel.tooling.model.EipModel;
  * Accepts a Camel stack trace or error message and returns the likely 
component/EIP involved, common causes, links to
  * relevant documentation, and suggested fixes.
  */
+@McpSecured
 @ApplicationScoped
 public class DiagnoseTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
index 9f1410275d2c..c2c2d3d5c6e6 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExampleTools.java
@@ -33,6 +33,7 @@ import org.apache.camel.util.json.JsonObject;
 /**
  * MCP Tools for browsing Camel CLI examples.
  */
+@McpSecured
 @ApplicationScoped
 public class ExampleTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExplainTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExplainTools.java
index db3b1bce230f..46cbe959d89d 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExplainTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/ExplainTools.java
@@ -35,6 +35,7 @@ import org.apache.camel.tooling.model.EipModel;
  * This tool extracts components and EIPs used in a route and returns their 
documentation from the Camel Catalog. The
  * calling LLM can use this context to formulate its own explanation of the 
route.
  */
+@McpSecured
 @ApplicationScoped
 public class ExplainTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/HardenTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/HardenTools.java
index ef7768dcc4fa..7f7bd5213ec1 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/HardenTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/HardenTools.java
@@ -39,6 +39,7 @@ import org.apache.camel.tooling.model.SecurityAdvisoryModel;
  * This tool analyzes routes for security-sensitive components, identifies 
potential vulnerabilities, and provides
  * structured context that an LLM can use to formulate security hardening 
recommendations.
  */
+@McpSecured
 @ApplicationScoped
 public class HardenTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/KameletTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/KameletTools.java
index 0610376a1156..3167220bf593 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/KameletTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/KameletTools.java
@@ -34,6 +34,7 @@ import org.apache.camel.dsl.jbang.core.common.VersionHelper;
 /**
  * MCP Tools for querying the Kamelet Catalog using Quarkus MCP Server.
  */
+@McpSecured
 @ApplicationScoped
 public class KameletTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAccessFilter.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAccessFilter.java
new file mode 100644
index 000000000000..542b46ee2532
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAccessFilter.java
@@ -0,0 +1,68 @@
+/*
+ * 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 jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import io.quarkiverse.mcp.server.FilterContext;
+import io.quarkiverse.mcp.server.ToolFilter;
+import io.quarkiverse.mcp.server.ToolManager;
+
+/**
+ * Filters tool visibility in {@code tools/list} responses based on the 
configured access level.
+ * <p>
+ * When security is enabled, tools whose access tier exceeds the configured 
level are hidden from discovery. This
+ * prevents LLM clients from attempting to call tools they cannot execute.
+ */
+@ApplicationScoped
+public class McpAccessFilter implements ToolFilter {
+
+    @Inject
+    McpSecurityConfig config;
+
+    @Inject
+    McpAuditLogger auditLogger;
+
+    @Override
+    public boolean test(ToolManager.ToolInfo tool, FilterContext context) {
+        if (!config.isEnabled()) {
+            return true;
+        }
+
+        boolean readOnlyHint = 
tool.annotations().map(ToolManager.ToolAnnotations::readOnlyHint).orElse(true);
+        boolean destructiveHint = 
tool.annotations().map(ToolManager.ToolAnnotations::destructiveHint).orElse(false);
+        boolean permitted = config.getAccessLevel().permits(readOnlyHint, 
destructiveHint);
+
+        if (!permitted && config.isAuditEnabled()) {
+            String requiredLevel = destructiveHint ? "admin" : (!readOnlyHint 
? "read-write" : "read-only");
+            String connectionId = "";
+            String clientName = "";
+            try {
+                connectionId = context.connection().id();
+                clientName = 
context.connection().initialRequest().implementation().name();
+            } catch (Exception e) {
+                // connection context may not be fully available during 
filtering
+            }
+            auditLogger.logAccessDenied(
+                    tool.name(), connectionId, clientName,
+                    requiredLevel, 
config.getAccessLevel().name().toLowerCase().replace("_", "-"));
+        }
+
+        return permitted;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAuditLogger.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAuditLogger.java
new file mode 100644
index 000000000000..6e85fdb92a8e
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAuditLogger.java
@@ -0,0 +1,86 @@
+/*
+ * 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.time.Instant;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import org.jboss.logging.Logger;
+
+/**
+ * Structured audit logger for MCP tool invocations.
+ * <p>
+ * Produces single-line JSON log entries to a dedicated logger category, 
allowing operators to route audit logs
+ * independently via Quarkus logging configuration.
+ */
+@ApplicationScoped
+public class McpAuditLogger {
+
+    static final String AUDIT_LOGGER_NAME = 
"org.apache.camel.mcp.security.audit";
+
+    private static final Logger AUDIT_LOG = 
Logger.getLogger(AUDIT_LOGGER_NAME);
+
+    public void logToolCall(
+            String tool, String connectionId, String clientName,
+            String accessLevel, String arguments) {
+        AUDIT_LOG.infof(
+                
"{\"event\":\"tool_call\",\"tool\":\"%s\",\"connectionId\":\"%s\","
+                        + 
"\"clientName\":\"%s\",\"accessLevel\":\"%s\",\"arguments\":%s,"
+                        + "\"timestamp\":\"%s\"}",
+                escape(tool), escape(connectionId), escape(clientName),
+                escape(accessLevel), arguments != null ? "\"" + 
escape(arguments) + "\"" : "null",
+                Instant.now().toString());
+    }
+
+    public void logToolResult(
+            String tool, String connectionId, boolean isError,
+            boolean redacted, long durationMs) {
+        AUDIT_LOG.infof(
+                
"{\"event\":\"tool_result\",\"tool\":\"%s\",\"connectionId\":\"%s\","
+                        + 
"\"outcome\":\"%s\",\"redacted\":%s,\"durationMs\":%d,"
+                        + "\"timestamp\":\"%s\"}",
+                escape(tool), escape(connectionId),
+                isError ? "error" : "success", redacted, durationMs,
+                Instant.now().toString());
+    }
+
+    public void logAccessDenied(
+            String tool, String connectionId, String clientName,
+            String requiredLevel, String currentLevel) {
+        AUDIT_LOG.warnf(
+                
"{\"event\":\"access_denied\",\"tool\":\"%s\",\"connectionId\":\"%s\","
+                        + 
"\"clientName\":\"%s\",\"requiredLevel\":\"%s\",\"currentLevel\":\"%s\","
+                        + "\"timestamp\":\"%s\"}",
+                escape(tool), escape(connectionId), escape(clientName),
+                escape(requiredLevel), escape(currentLevel),
+                Instant.now().toString());
+    }
+
+    static String escape(String value) {
+        if (value == null) {
+            return "";
+        }
+        return value.replace("\\", "\\\\")
+                .replace("\"", "\\\"")
+                .replace("\n", "\\n")
+                .replace("\r", "\\r")
+                .replace("\t", "\\t")
+                .replace("\b", "\\b")
+                .replace("\f", "\\f");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecretRedactor.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecretRedactor.java
new file mode 100644
index 000000000000..d9df90aa21ed
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecretRedactor.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.mcp;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+/**
+ * Regex-based secret redaction engine for MCP tool responses.
+ * <p>
+ * Applies configurable patterns to detect and replace credentials, tokens, 
API keys, and connection strings in tool
+ * output text.
+ */
+@ApplicationScoped
+public class McpSecretRedactor {
+
+    static final String REDACTED = "***REDACTED***";
+
+    static final List<Pattern> DEFAULT_PATTERNS = List.of(
+            // password/passwd/pwd key-value pairs
+            
Pattern.compile("(?i)(password|passwd|pwd)\\s*[=:]\\s*[^\\s,;}'\"\\]]+"),
+            // API keys, secret keys, access keys
+            
Pattern.compile("(?i)(api[_-]?key|apikey|secret[_-]?key|access[_-]?key)\\s*[=:]\\s*[^\\s,;}'\"\\]]+"),
+            // tokens and bearer authorization
+            
Pattern.compile("(?i)(token|bearer|authorization)\\s*[=:]\\s*[^\\s,;}'\"\\]]+"),
+            // AWS Access Key IDs (AKIA prefix + 16 alphanumeric)
+            Pattern.compile("AKIA[0-9A-Z]{16}"),
+            // Connection strings with embedded credentials
+            
Pattern.compile("(?i)(mongodb(\\+srv)?://|amqp://|redis://|jdbc:)[^\\s\"']+@[^\\s\"']+"));
+
+    @Inject
+    McpSecurityConfig config;
+
+    public String redact(String text) {
+        if (text == null || text.isEmpty()) {
+            return text;
+        }
+        List<Pattern> patterns = config.getRedactionPatterns();
+        String result = text;
+        for (Pattern pattern : patterns) {
+            result = pattern.matcher(result).replaceAll(REDACTED);
+        }
+        return result;
+    }
+
+    public boolean containsSecret(String text) {
+        if (text == null || text.isEmpty()) {
+            return false;
+        }
+        List<Pattern> patterns = config.getRedactionPatterns();
+        for (Pattern pattern : patterns) {
+            if (pattern.matcher(text).find()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecured.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecured.java
new file mode 100644
index 000000000000..9097bf0179a4
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecured.java
@@ -0,0 +1,38 @@
+/*
+ * 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.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import jakarta.interceptor.InterceptorBinding;
+
+/**
+ * CDI interceptor binding for the MCP security execution layer.
+ * <p>
+ * Apply to {@code @ApplicationScoped} tool classes to enable security 
interception (authorization, audit logging, input
+ * sanitization, secret redaction) on all {@code @Tool}-annotated methods.
+ */
+@Inherited
+@InterceptorBinding
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface McpSecured {
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityConfig.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityConfig.java
new file mode 100644
index 000000000000..25f21a5bf2f1
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityConfig.java
@@ -0,0 +1,141 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+/**
+ * Central configuration for the MCP security execution layer.
+ * <p>
+ * All settings are opt-in and disabled by default to preserve backward 
compatibility. Enable via properties or
+ * environment variables (e.g., {@code CAMEL_MCP_SECURITY_ENABLED=true}).
+ */
+@ApplicationScoped
+public class McpSecurityConfig {
+
+    private static final Logger LOG = 
Logger.getLogger(McpSecurityConfig.class);
+
+    static final int DEFAULT_MAX_ARGUMENT_LENGTH = 10_000;
+
+    @ConfigProperty(name = "camel.mcp.security.enabled", defaultValue = 
"false")
+    boolean enabled;
+
+    @ConfigProperty(name = "camel.mcp.security.access-level", defaultValue = 
"admin")
+    String accessLevel;
+
+    @ConfigProperty(name = "camel.mcp.security.audit.enabled", defaultValue = 
"false")
+    boolean auditEnabled;
+
+    @ConfigProperty(name = "camel.mcp.security.audit.include-arguments", 
defaultValue = "true")
+    boolean auditIncludeArguments;
+
+    @ConfigProperty(name = "camel.mcp.security.redaction.enabled", 
defaultValue = "false")
+    boolean redactionEnabled;
+
+    @ConfigProperty(name = "camel.mcp.security.redaction.patterns")
+    Optional<String> redactionPatterns;
+
+    @ConfigProperty(name = "camel.mcp.security.max-argument-length", 
defaultValue = "10000")
+    int maxArgumentLength;
+
+    private volatile List<Pattern> compiledPatterns;
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public AccessLevel getAccessLevel() {
+        return AccessLevel.parse(accessLevel);
+    }
+
+    public boolean isAuditEnabled() {
+        return auditEnabled;
+    }
+
+    public boolean isAuditIncludeArguments() {
+        return auditIncludeArguments;
+    }
+
+    public boolean isRedactionEnabled() {
+        return redactionEnabled;
+    }
+
+    public int getMaxArgumentLength() {
+        return maxArgumentLength > 0 ? maxArgumentLength : 
DEFAULT_MAX_ARGUMENT_LENGTH;
+    }
+
+    public List<Pattern> getRedactionPatterns() {
+        List<Pattern> cached = compiledPatterns;
+        if (cached != null) {
+            return cached;
+        }
+        List<Pattern> patterns = new 
ArrayList<>(McpSecretRedactor.DEFAULT_PATTERNS);
+        if (redactionPatterns.isPresent()) {
+            for (String p : redactionPatterns.get().split(",")) {
+                String trimmed = p.trim();
+                if (!trimmed.isEmpty()) {
+                    try {
+                        patterns.add(Pattern.compile(trimmed));
+                    } catch (PatternSyntaxException e) {
+                        LOG.warnf("Ignoring invalid redaction pattern '%s': 
%s", trimmed, e.getMessage());
+                    }
+                }
+            }
+        }
+        compiledPatterns = Collections.unmodifiableList(patterns);
+        return compiledPatterns;
+    }
+
+    /**
+     * Access levels derived from MCP tool annotations.
+     */
+    public enum AccessLevel {
+        READ_ONLY,
+        READ_WRITE,
+        ADMIN;
+
+        public boolean permits(boolean readOnlyHint, boolean destructiveHint) {
+            return switch (this) {
+                case READ_ONLY -> readOnlyHint;
+                case READ_WRITE -> !destructiveHint;
+                case ADMIN -> true;
+            };
+        }
+
+        static AccessLevel parse(String value) {
+            if (value == null || value.isBlank()) {
+                return ADMIN;
+            }
+            return switch (value.trim().toLowerCase().replace("_", "-")) {
+                case "read-only", "readonly" -> READ_ONLY;
+                case "read-write", "readwrite" -> READ_WRITE;
+                case "admin" -> ADMIN;
+                default -> ADMIN;
+            };
+        }
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityInterceptor.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityInterceptor.java
new file mode 100644
index 000000000000..2d0fc19f3e1b
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityInterceptor.java
@@ -0,0 +1,175 @@
+/*
+ * 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.lang.reflect.Method;
+
+import jakarta.annotation.Priority;
+import jakarta.inject.Inject;
+import jakarta.interceptor.AroundInvoke;
+import jakarta.interceptor.Interceptor;
+import jakarta.interceptor.InvocationContext;
+
+import io.quarkiverse.mcp.server.Tool;
+
+/**
+ * CDI interceptor for the MCP security execution layer.
+ * <p>
+ * Intercepts {@code @Tool}-annotated methods on classes marked with {@link 
McpSecured} to provide input sanitization,
+ * audit logging, and secret redaction. Authorization is handled by {@link 
McpAccessFilter} which is a global
+ * {@code ToolFilter}.
+ * <p>
+ * This replaces the ToolInputGuardrail/ToolOutputGuardrail approach since 
those require per-tool
+ * {@code @ToolGuardrails} annotations and are not auto-discovered as global 
CDI beans.
+ */
+@McpSecured
+@Interceptor
+@Priority(Interceptor.Priority.APPLICATION)
+public class McpSecurityInterceptor {
+
+    @Inject
+    McpSecurityConfig config;
+
+    @Inject
+    McpAuditLogger auditLogger;
+
+    @Inject
+    McpSecretRedactor redactor;
+
+    @AroundInvoke
+    Object intercept(InvocationContext ctx) throws Exception {
+        if (!config.isEnabled()) {
+            return ctx.proceed();
+        }
+
+        Method method = ctx.getMethod();
+        if (!method.isAnnotationPresent(Tool.class)) {
+            return ctx.proceed();
+        }
+
+        String toolName = method.getName();
+
+        // Input sanitization
+        sanitizeParameters(ctx);
+
+        // TODO: CDI interceptors do not have access to MCP connection context 
(connectionId, clientName).
+        // These will be populated once global guardrail support is available 
in Quarkus MCP Server.
+
+        // Audit: log tool call
+        if (config.isAuditEnabled()) {
+            String arguments = null;
+            if (config.isAuditIncludeArguments()) {
+                arguments = summarizeParameters(ctx);
+            }
+            auditLogger.logToolCall(toolName, "", "", 
config.getAccessLevel().name(), arguments);
+        }
+
+        long start = System.nanoTime();
+        boolean isError = false;
+        boolean wasRedacted = false;
+        try {
+            Object result = ctx.proceed();
+
+            // Secret redaction on string results
+            if (config.isRedactionEnabled() && result instanceof String s) {
+                if (redactor.containsSecret(s)) {
+                    result = redactor.redact(s);
+                    wasRedacted = true;
+                }
+            }
+
+            return result;
+        } catch (Exception e) {
+            isError = true;
+            throw e;
+        } finally {
+            if (config.isAuditEnabled()) {
+                long durationMs = (System.nanoTime() - start) / 1_000_000;
+                auditLogger.logToolResult(toolName, "", isError, wasRedacted, 
durationMs);
+            }
+        }
+    }
+
+    private void sanitizeParameters(InvocationContext ctx) {
+        Object[] params = ctx.getParameters();
+        if (params == null) {
+            return;
+        }
+
+        int maxLen = config.getMaxArgumentLength();
+        boolean modified = false;
+
+        for (int i = 0; i < params.length; i++) {
+            if (params[i] instanceof String s) {
+                String clean = stripControlChars(s);
+                if (clean.length() > maxLen) {
+                    clean = clean.substring(0, maxLen);
+                }
+                if (!clean.equals(s)) {
+                    params[i] = clean;
+                    modified = true;
+                }
+            }
+        }
+
+        if (modified) {
+            ctx.setParameters(params);
+        }
+    }
+
+    static String stripControlChars(String input) {
+        if (input == null) {
+            return null;
+        }
+        StringBuilder sb = null;
+        for (int i = 0; i < input.length(); i++) {
+            char c = input.charAt(i);
+            if (c < 0x20 && c != '\n' && c != '\r' && c != '\t') {
+                if (sb == null) {
+                    sb = new StringBuilder(input.length());
+                    sb.append(input, 0, i);
+                }
+            } else if (sb != null) {
+                sb.append(c);
+            }
+        }
+        return sb != null ? sb.toString() : input;
+    }
+
+    private String summarizeParameters(InvocationContext ctx) {
+        Object[] params = ctx.getParameters();
+        if (params == null || params.length == 0) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < params.length; i++) {
+            if (params[i] == null) {
+                continue;
+            }
+            String value = params[i].toString();
+            if (value.length() > 200) {
+                value = value.substring(0, 200) + "...";
+            }
+            if (!sb.isEmpty()) {
+                sb.append(", ");
+            }
+            sb.append("arg").append(i).append("=").append(value);
+        }
+        return sb.toString();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/MigrationTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/MigrationTools.java
index 503705f906df..23b1ec5365fb 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/MigrationTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/MigrationTools.java
@@ -34,6 +34,7 @@ import io.quarkiverse.mcp.server.ToolCallException;
  * returns a {@code nextStep} hint that guides the LLM to the next action in 
the workflow. For migration summaries, use
  * {@code git diff --shortstat} directly.
  */
+@McpSecured
 @ApplicationScoped
 public class MigrationTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/MigrationWildflyKarafTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/MigrationWildflyKarafTools.java
index 8d001a258755..5d11a943f7be 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/MigrationWildflyKarafTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/MigrationWildflyKarafTools.java
@@ -33,6 +33,7 @@ import io.quarkiverse.mcp.server.ToolCallException;
  * Handles the special case where projects running on legacy runtimes need to 
be replatformed to Spring Boot or Quarkus.
  * Uses Maven archetypes for new project creation and migration guides for 
component mapping details.
  */
+@McpSecured
 @ApplicationScoped
 public class MigrationWildflyKarafTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java
index 5b7f00ae423d..8e50784a4beb 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/OpenApiTools.java
@@ -43,6 +43,7 @@ import io.swagger.v3.parser.core.models.SwaggerParseResult;
  * Since Camel 4.6, the recommended approach is contract-first: referencing 
the OpenAPI spec directly at runtime via
  * rest:openApi. These tools help validate, scaffold, and provide mock 
guidance for that workflow.
  */
+@McpSecured
 @ApplicationScoped
 public class OpenApiTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PropertiesTranslateTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PropertiesTranslateTools.java
index 0c6cd955ddad..17a4d2fbc768 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PropertiesTranslateTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PropertiesTranslateTools.java
@@ -38,6 +38,7 @@ import io.quarkiverse.mcp.server.ToolCallException;
  * ({@code camel.server.*} / {@code camel.management.*} are jbang-only) and 
the legacy {@code camel.springboot.*}
  * configuration that was harmonized to {@code camel.main.*} in Camel 4.5 and 
removed in 4.13.
  */
+@McpSecured
 @ApplicationScoped
 public class PropertiesTranslateTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
index f51a9cca1c55..e7a5bd920ab6 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
@@ -34,6 +34,7 @@ import org.apache.camel.dsl.jbang.core.common.Printer;
  * MCP Tool for generating a visual diagram of Camel routes from a source file 
(non-running integration). Wraps the
  * {@code camel route-diagram} jbang command. Supports both PNG image output 
and plain-text ASCII/Unicode output.
  */
+@McpSecured
 @ApplicationScoped
 public class RouteDiagramTools {
 
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 d0affc50fc51..e89bfa66ce23 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
@@ -42,6 +42,7 @@ import org.apache.camel.util.json.Jsoner;
  * The {@code @Tool} and {@code @ToolArg} annotations are required by the 
Quarkus MCP server for protocol discovery but
  * all tool logic lives in {@link ToolRegistry}, ensuring a single source of 
truth shared with the Agent REPL.
  */
+@McpSecured
 @ApplicationScoped
 public class RuntimeTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldTools.java
index 457ea6db86d6..35ec249b8f87 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TestScaffoldTools.java
@@ -41,6 +41,7 @@ import org.apache.camel.util.json.JsonObject;
  * {@code @CamelSpringBootTest} for Spring Boot), mock endpoints for 
producers, and {@code @RegisterExtension} stubs for
  * infrastructure components like Kafka, JMS, MongoDB, etc.
  */
+@McpSecured
 @ApplicationScoped
 public class TestScaffoldTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
index 604f7339b7da..37eb945716a3 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
@@ -51,6 +51,7 @@ import org.apache.camel.yaml.out.YamlModelWriter;
 /**
  * MCP Tools for validating and transforming Camel routes using Quarkus MCP 
Server.
  */
+@McpSecured
 @ApplicationScoped
 public class TransformTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
index 3460af5c8fb8..38bbed64491d 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/VersionTools.java
@@ -35,6 +35,7 @@ import org.apache.camel.util.json.Jsoner;
 /**
  * MCP Tools for querying available Camel versions using Quarkus MCP Server.
  */
+@McpSecured
 @ApplicationScoped
 public class VersionTools {
 
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
index 934c9a801297..c6ac18f277e4 100644
--- a/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
+++ b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
@@ -36,3 +36,17 @@ quarkus.http.host-enabled=false
 # Strip null fields from JSON responses to reduce LLM token consumption.
 # Applies to @Tool result records serialized via JsonTextContentEncoder.
 quarkus.jackson.serialization-inclusion=non-null
+
+## MCP Security Configuration (opt-in, disabled by default)
+## Enable the security execution layer for tool-level authorization,
+## audit logging, input sanitization, and secret redaction.
+# camel.mcp.security.enabled=true
+# camel.mcp.security.access-level=admin
+# camel.mcp.security.audit.enabled=true
+# camel.mcp.security.audit.include-arguments=true
+# camel.mcp.security.redaction.enabled=true
+# camel.mcp.security.redaction.patterns=
+# camel.mcp.security.max-argument-length=10000
+
+## Audit log routing (optional — route to a separate file or syslog)
+# quarkus.log.category."org.apache.camel.mcp.security.audit".level=INFO
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAccessFilterTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAccessFilterTest.java
new file mode 100644
index 000000000000..4a7d78b85ae6
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAccessFilterTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Optional;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class McpAccessFilterTest {
+
+    @Test
+    void securityDisabledAllowsAllAccessLevels() {
+        McpSecurityConfig config = createConfig(false, "read-only");
+
+        // Even read-only level permits everything when security is disabled
+        assertThat(config.isEnabled()).isFalse();
+    }
+
+    @Test
+    void readOnlyLevelBlocksWriteAndDestructive() {
+        McpSecurityConfig config = createConfig(true, "read-only");
+        McpSecurityConfig.AccessLevel level = config.getAccessLevel();
+
+        assertThat(level.permits(true, false)).as("read-only tool should be 
visible").isTrue();
+        assertThat(level.permits(false, false)).as("write tool should be 
hidden").isFalse();
+        assertThat(level.permits(false, true)).as("destructive tool should be 
hidden").isFalse();
+    }
+
+    @Test
+    void readWriteLevelBlocksDestructiveOnly() {
+        McpSecurityConfig config = createConfig(true, "read-write");
+        McpSecurityConfig.AccessLevel level = config.getAccessLevel();
+
+        assertThat(level.permits(true, false)).as("read-only tool should be 
visible").isTrue();
+        assertThat(level.permits(false, false)).as("write tool should be 
visible").isTrue();
+        assertThat(level.permits(false, true)).as("destructive tool should be 
hidden").isFalse();
+    }
+
+    @Test
+    void adminLevelAllowsEverything() {
+        McpSecurityConfig config = createConfig(true, "admin");
+        McpSecurityConfig.AccessLevel level = config.getAccessLevel();
+
+        assertThat(level.permits(true, false)).as("read-only tool").isTrue();
+        assertThat(level.permits(false, false)).as("write tool").isTrue();
+        assertThat(level.permits(false, true)).as("destructive tool").isTrue();
+    }
+
+    private static McpSecurityConfig createConfig(boolean enabled, String 
accessLevel) {
+        McpSecurityConfig config = new McpSecurityConfig();
+        config.enabled = enabled;
+        config.accessLevel = accessLevel;
+        config.auditEnabled = false;
+        config.auditIncludeArguments = true;
+        config.redactionEnabled = false;
+        config.redactionPatterns = Optional.empty();
+        config.maxArgumentLength = 
McpSecurityConfig.DEFAULT_MAX_ARGUMENT_LENGTH;
+        return config;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAuditLoggerTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAuditLoggerTest.java
new file mode 100644
index 000000000000..994786e180ec
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpAuditLoggerTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class McpAuditLoggerTest {
+
+    @Test
+    void escapeHandlesSpecialCharacters() {
+        
assertThat(McpAuditLogger.escape("line1\nline2")).isEqualTo("line1\\nline2");
+        assertThat(McpAuditLogger.escape("tab\there")).isEqualTo("tab\\there");
+        
assertThat(McpAuditLogger.escape("quote\"here")).isEqualTo("quote\\\"here");
+        
assertThat(McpAuditLogger.escape("back\\slash")).isEqualTo("back\\\\slash");
+        
assertThat(McpAuditLogger.escape("return\rhere")).isEqualTo("return\\rhere");
+        
assertThat(McpAuditLogger.escape("back\bspace")).isEqualTo("back\\bspace");
+        
assertThat(McpAuditLogger.escape("form\ffeed")).isEqualTo("form\\ffeed");
+    }
+
+    @Test
+    void escapeHandlesNull() {
+        assertThat(McpAuditLogger.escape(null)).isEmpty();
+    }
+
+    @Test
+    void escapePreservesNormalText() {
+        assertThat(McpAuditLogger.escape("normal text")).isEqualTo("normal 
text");
+    }
+
+    @Test
+    void loggerCanBeInstantiated() {
+        McpAuditLogger logger = new McpAuditLogger();
+        // Verify the logger doesn't throw on invocation — actual log output
+        // goes to JBoss LogManager which is not captured in unit tests
+        logger.logToolCall("test_tool", "conn-1", "claude", "ADMIN", 
"{\"arg\":\"val\"}");
+        logger.logToolResult("test_tool", "conn-1", false, false, 42);
+        logger.logAccessDenied("test_tool", "conn-1", "claude", "admin", 
"read-only");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecretRedactorTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecretRedactorTest.java
new file mode 100644
index 000000000000..f958b45bc81e
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecretRedactorTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.Optional;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class McpSecretRedactorTest {
+
+    private McpSecretRedactor createRedactor(String extraPatterns) {
+        McpSecurityConfig config = new McpSecurityConfig();
+        config.enabled = true;
+        config.redactionEnabled = true;
+        config.redactionPatterns = Optional.ofNullable(extraPatterns);
+        config.maxArgumentLength = 
McpSecurityConfig.DEFAULT_MAX_ARGUMENT_LENGTH;
+        config.accessLevel = "admin";
+        config.auditEnabled = false;
+        config.auditIncludeArguments = true;
+
+        McpSecretRedactor redactor = new McpSecretRedactor();
+        redactor.config = config;
+        return redactor;
+    }
+
+    @Test
+    void redactsPasswordKeyValue() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        assertThat(redactor.redact("password=secret123"))
+                .isEqualTo(McpSecretRedactor.REDACTED);
+        assertThat(redactor.redact("Password: myP@ssw0rd"))
+                .isEqualTo(McpSecretRedactor.REDACTED);
+        assertThat(redactor.redact("pwd=abc"))
+                .isEqualTo(McpSecretRedactor.REDACTED);
+    }
+
+    @Test
+    void redactsApiKeys() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        assertThat(redactor.redact("api_key=sk-12345"))
+                .isEqualTo(McpSecretRedactor.REDACTED);
+        assertThat(redactor.redact("apiKey: abc123"))
+                .isEqualTo(McpSecretRedactor.REDACTED);
+        assertThat(redactor.redact("secret-key=xyz789"))
+                .isEqualTo(McpSecretRedactor.REDACTED);
+    }
+
+    @Test
+    void redactsTokensAndBearer() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        assertThat(redactor.redact("token=eyJhbGciOiJIUzI1NiJ9"))
+                .isEqualTo(McpSecretRedactor.REDACTED);
+        assertThat(redactor.redact("bearer: some-bearer-token"))
+                .isEqualTo(McpSecretRedactor.REDACTED);
+    }
+
+    @Test
+    void redactsAwsAccessKeyIds() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        assertThat(redactor.redact("key is AKIAIOSFODNN7EXAMPLE"))
+                .isEqualTo("key is " + McpSecretRedactor.REDACTED);
+    }
+
+    @Test
+    void redactsConnectionStringsWithCredentials() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        assertThat(redactor.redact("uri: mongodb://user:pass@host:27017/db"))
+                .isEqualTo("uri: " + McpSecretRedactor.REDACTED);
+        
assertThat(redactor.redact("url=amqp://admin:secret@broker:5672/vhost"))
+                .isEqualTo("url=" + McpSecretRedactor.REDACTED);
+    }
+
+    @Test
+    void doesNotRedactNormalText() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        String normalText = "Route started successfully with 3 endpoints";
+        assertThat(redactor.redact(normalText)).isEqualTo(normalText);
+    }
+
+    @Test
+    void handlesMultipleSecretsInSameString() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        String input = "password=secret, apiKey=abc123";
+        String result = redactor.redact(input);
+        assertThat(result).doesNotContain("secret").doesNotContain("abc123");
+    }
+
+    @Test
+    void handlesNullAndEmpty() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        assertThat(redactor.redact(null)).isNull();
+        assertThat(redactor.redact("")).isEmpty();
+    }
+
+    @Test
+    void containsSecretDetectsSecrets() {
+        McpSecretRedactor redactor = createRedactor(null);
+
+        assertThat(redactor.containsSecret("password=secret")).isTrue();
+        assertThat(redactor.containsSecret("normal text")).isFalse();
+        assertThat(redactor.containsSecret(null)).isFalse();
+        assertThat(redactor.containsSecret("")).isFalse();
+    }
+
+    @Test
+    void customPatternsAreApplied() {
+        McpSecretRedactor redactor = createRedactor("CUSTOM_\\d+");
+
+        assertThat(redactor.redact("value: CUSTOM_12345 here"))
+                .isEqualTo("value: " + McpSecretRedactor.REDACTED + " here");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityConfigTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityConfigTest.java
new file mode 100644
index 000000000000..b1a9e793b4fe
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityConfigTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.mcp;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class McpSecurityConfigTest {
+
+    // ---- AccessLevel.permits() ----
+
+    @Test
+    void readOnlyPermitsOnlyReadOnlyTools() {
+        McpSecurityConfig.AccessLevel level = 
McpSecurityConfig.AccessLevel.READ_ONLY;
+
+        assertThat(level.permits(true, false)).as("read-only tool").isTrue();
+        assertThat(level.permits(false, false)).as("read-write 
tool").isFalse();
+        assertThat(level.permits(false, true)).as("destructive 
tool").isFalse();
+    }
+
+    @Test
+    void readWritePermitsNonDestructiveTools() {
+        McpSecurityConfig.AccessLevel level = 
McpSecurityConfig.AccessLevel.READ_WRITE;
+
+        assertThat(level.permits(true, false)).as("read-only tool").isTrue();
+        assertThat(level.permits(false, false)).as("read-write tool").isTrue();
+        assertThat(level.permits(false, true)).as("destructive 
tool").isFalse();
+    }
+
+    @Test
+    void adminPermitsAllTools() {
+        McpSecurityConfig.AccessLevel level = 
McpSecurityConfig.AccessLevel.ADMIN;
+
+        assertThat(level.permits(true, false)).as("read-only tool").isTrue();
+        assertThat(level.permits(false, false)).as("read-write tool").isTrue();
+        assertThat(level.permits(false, true)).as("destructive tool").isTrue();
+    }
+
+    // ---- AccessLevel.parse() ----
+
+    @Test
+    void parseAccessLevelVariants() {
+        
assertThat(McpSecurityConfig.AccessLevel.parse("read-only")).isEqualTo(McpSecurityConfig.AccessLevel.READ_ONLY);
+        
assertThat(McpSecurityConfig.AccessLevel.parse("readonly")).isEqualTo(McpSecurityConfig.AccessLevel.READ_ONLY);
+        
assertThat(McpSecurityConfig.AccessLevel.parse("READ_ONLY")).isEqualTo(McpSecurityConfig.AccessLevel.READ_ONLY);
+        
assertThat(McpSecurityConfig.AccessLevel.parse("read-write")).isEqualTo(McpSecurityConfig.AccessLevel.READ_WRITE);
+        
assertThat(McpSecurityConfig.AccessLevel.parse("readwrite")).isEqualTo(McpSecurityConfig.AccessLevel.READ_WRITE);
+        
assertThat(McpSecurityConfig.AccessLevel.parse("admin")).isEqualTo(McpSecurityConfig.AccessLevel.ADMIN);
+        
assertThat(McpSecurityConfig.AccessLevel.parse("ADMIN")).isEqualTo(McpSecurityConfig.AccessLevel.ADMIN);
+    }
+
+    @Test
+    void parseUnknownDefaultsToAdmin() {
+        
assertThat(McpSecurityConfig.AccessLevel.parse("unknown")).isEqualTo(McpSecurityConfig.AccessLevel.ADMIN);
+        
assertThat(McpSecurityConfig.AccessLevel.parse("")).isEqualTo(McpSecurityConfig.AccessLevel.ADMIN);
+        
assertThat(McpSecurityConfig.AccessLevel.parse(null)).isEqualTo(McpSecurityConfig.AccessLevel.ADMIN);
+    }
+
+    // ---- Config defaults ----
+
+    @Test
+    void defaultConfigIsDisabled() {
+        McpSecurityConfig config = createConfig(false, "admin", false, true, 
false, null);
+
+        assertThat(config.isEnabled()).isFalse();
+        
assertThat(config.getAccessLevel()).isEqualTo(McpSecurityConfig.AccessLevel.ADMIN);
+        assertThat(config.isAuditEnabled()).isFalse();
+        assertThat(config.isAuditIncludeArguments()).isTrue();
+        assertThat(config.isRedactionEnabled()).isFalse();
+    }
+
+    @Test
+    void maxArgumentLengthFallsBackToDefault() {
+        McpSecurityConfig config = createConfig(false, "admin", false, true, 
false, null);
+        config.maxArgumentLength = 0;
+
+        
assertThat(config.getMaxArgumentLength()).isEqualTo(McpSecurityConfig.DEFAULT_MAX_ARGUMENT_LENGTH);
+    }
+
+    // ---- Redaction patterns ----
+
+    @Test
+    void defaultRedactionPatternsIncludeBuiltins() {
+        McpSecurityConfig config = createConfig(true, "admin", false, true, 
true, null);
+
+        List<Pattern> patterns = config.getRedactionPatterns();
+
+        
assertThat(patterns).hasSizeGreaterThanOrEqualTo(McpSecretRedactor.DEFAULT_PATTERNS.size());
+    }
+
+    @Test
+    void customRedactionPatternsAppended() {
+        McpSecurityConfig config = createConfig(true, "admin", false, true, 
true, "SECRET_\\d+,TOKEN_[A-Z]+");
+
+        List<Pattern> patterns = config.getRedactionPatterns();
+
+        
assertThat(patterns.size()).isEqualTo(McpSecretRedactor.DEFAULT_PATTERNS.size() 
+ 2);
+    }
+
+    // ---- Helper ----
+
+    private static McpSecurityConfig createConfig(
+            boolean enabled, String accessLevel, boolean auditEnabled,
+            boolean auditIncludeArguments, boolean redactionEnabled, String 
redactionPatterns) {
+        McpSecurityConfig config = new McpSecurityConfig();
+        config.enabled = enabled;
+        config.accessLevel = accessLevel;
+        config.auditEnabled = auditEnabled;
+        config.auditIncludeArguments = auditIncludeArguments;
+        config.redactionEnabled = redactionEnabled;
+        config.redactionPatterns = Optional.ofNullable(redactionPatterns);
+        config.maxArgumentLength = 
McpSecurityConfig.DEFAULT_MAX_ARGUMENT_LENGTH;
+        return config;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityInterceptorTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityInterceptorTest.java
new file mode 100644
index 000000000000..64c7eb586072
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpSecurityInterceptorTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class McpSecurityInterceptorTest {
+
+    @Test
+    void stripControlCharsRemovesNullBytes() {
+        assertThat(McpSecurityInterceptor.stripControlChars("hello\0world"))
+                .isEqualTo("helloworld");
+    }
+
+    @Test
+    void stripControlCharsPreservesNewlinesTabsReturns() {
+        
assertThat(McpSecurityInterceptor.stripControlChars("line1\nline2\ttab\rreturn"))
+                .isEqualTo("line1\nline2\ttab\rreturn");
+    }
+
+    @Test
+    void stripControlCharsRemovesBellAndOtherControls() {
+        assertThat(McpSecurityInterceptor.stripControlChars("a\u0007b\u0001c"))
+                .isEqualTo("abc");
+    }
+
+    @Test
+    void stripControlCharsPreservesNormalText() {
+        String normal = "normal text with spaces and symbols!@#$%^&*()";
+        
assertThat(McpSecurityInterceptor.stripControlChars(normal)).isEqualTo(normal);
+    }
+
+    @Test
+    void stripControlCharsHandlesNull() {
+        assertThat(McpSecurityInterceptor.stripControlChars(null)).isNull();
+    }
+
+    @Test
+    void stripControlCharsHandlesEmptyString() {
+        assertThat(McpSecurityInterceptor.stripControlChars("")).isEmpty();
+    }
+
+    @Test
+    void accessDenialMessageFormat() {
+        McpSecurityConfig.AccessLevel readOnly = 
McpSecurityConfig.AccessLevel.READ_ONLY;
+
+        assertThat(readOnly.permits(false, true)).isFalse();
+        assertThat(readOnly.permits(false, false)).isFalse();
+        assertThat(readOnly.permits(true, false)).isTrue();
+    }
+}

Reply via email to