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

luigidemasi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kit.git

commit b255386413535c4fc4ac315320c215debfd7c351
Author: Jinal Sarvaiya <[email protected]>
AuthorDate: Fri Oct 23 22:47:00 2026 +0100

    fix #109: address review findings — plugin parity, citrus removal, 
malformed validation, docs
---
 CHANGELOG.md                                       |  4 +-
 .../luigidemasi/camelkit/jbang/KitInitCommand.java |  5 +++
 .../camelkit/CamelKitCommandParityTest.java        | 49 ++++++++++++++++++++++
 .../camelkit/generator/McpConfigGenerator.java     | 34 +++++++++------
 .../camelkit/generator/OpenCodeConfigMerger.java   | 26 +++++++++---
 .../camelkit/service/DoctorService.java            |  2 +-
 .../src/main/resources/skills/shared/mcp-setup.md  | 16 +++----
 .../generator/AntigravityGeneratorTest.java        | 23 +++++++++-
 .../camelkit/generator/OpenCodeGeneratorTest.java  | 26 +++++++++++-
 .../camelkit/service/DoctorServiceTest.java        | 30 +++++++++++++
 docs/agent-architectures.md                        |  2 +-
 docs/antigravity.md                                |  4 +-
 docs/architecture.md                               |  2 +-
 docs/commands.md                                   |  5 ++-
 docs/modules/ROOT/pages/agents.adoc                | 12 +++---
 docs/modules/ROOT/pages/architecture.adoc          |  6 +--
 docs/modules/ROOT/pages/commands.adoc              |  4 +-
 docs/modules/ROOT/pages/greenfield.adoc            |  2 +-
 docs/modules/ROOT/pages/mcp.adoc                   |  2 +-
 docs/user-guide.md                                 |  2 +-
 20 files changed, 208 insertions(+), 48 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9084f3e5..84ff02a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,7 +22,9 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
 - **Make Citrus MCP server opt-in (#109)** — `camel-kit init` no longer writes 
Citrus MCP server
   configuration by default. Pass `--with-citrus-mcp` to include it. Workspaces 
created before this
   change that have `citrus.mcp.version` but no `citrus.mcp.enabled` are 
treated as implicitly opted
-  in for backward compatibility. `camel-kit doctor` skips Citrus health checks 
when not opted in.
+  in for backward compatibility. `camel-kit doctor` permits an absent Citrus 
server when not opted in,
+  but validates any present Citrus server regardless of the opt-in setting. 
Regeneration without
+  `--with-citrus-mcp` removes previously generated Citrus entries from 
OpenCode and Antigravity configs.
 - **Claude Code native skill discovery (#243)** — skills are now installed to 
`.claude/skills/` with
   `user-invocable` and `description` frontmatter so Claude Code discovers them 
natively without command stubs.
   Legacy `.claude/commands/camel-*.md` stubs are cleaned up on regeneration; 
user-created commands are preserved.
diff --git 
a/camel-jbang-plugin-kit/src/main/java/io/github/luigidemasi/camelkit/jbang/KitInitCommand.java
 
b/camel-jbang-plugin-kit/src/main/java/io/github/luigidemasi/camelkit/jbang/KitInitCommand.java
index ef095b30..d87e6655 100644
--- 
a/camel-jbang-plugin-kit/src/main/java/io/github/luigidemasi/camelkit/jbang/KitInitCommand.java
+++ 
b/camel-jbang-plugin-kit/src/main/java/io/github/luigidemasi/camelkit/jbang/KitInitCommand.java
@@ -53,6 +53,10 @@ public class KitInitCommand extends CamelCommand {
             description = "Override config property: -p key=value 
(repeatable)")
     List<String> properties;
 
+    @Option(names = {"--with-citrus-mcp"},
+            description = "Include the Citrus MCP server in agent 
configuration (default: off)")
+    boolean withCitrusMcp;
+
     @Option(names = {"--source-platform"},
             description = "Source platform for migration graph analysis: 
mulesoft, camel, biztalk, auto (default: auto)")
     String sourcePlatform;
@@ -81,6 +85,7 @@ public class KitInitCommand extends CamelCommand {
         initCommand.silent = this.silent;
         initCommand.configFile = this.configFile;
         initCommand.properties = this.properties;
+        initCommand.withCitrusMcp = this.withCitrusMcp;
         initCommand.sourcePlatform = this.sourcePlatform;
 
         return initCommand.call();
diff --git 
a/camel-jbang-plugin-kit/src/test/java/io/github/luigidemasi/camelkit/CamelKitCommandParityTest.java
 
b/camel-jbang-plugin-kit/src/test/java/io/github/luigidemasi/camelkit/CamelKitCommandParityTest.java
index 285587e9..39f9edfc 100644
--- 
a/camel-jbang-plugin-kit/src/test/java/io/github/luigidemasi/camelkit/CamelKitCommandParityTest.java
+++ 
b/camel-jbang-plugin-kit/src/test/java/io/github/luigidemasi/camelkit/CamelKitCommandParityTest.java
@@ -58,6 +58,55 @@ class CamelKitCommandParityTest {
         }
     }
 
+    @Test
+    void pluginForwardsWithCitrusMcpOption() throws Exception {
+        CamelKitMain standaloneMain = new CamelKitMain();
+        standaloneMain.disableTui();
+        CommandLine standalone = CamelKitMain.commandLine(standaloneMain);
+        CamelJBangMain main = new CamelJBangMain();
+        CommandLine plugin = new CommandLine(main);
+        new CamelKitPlugin().customize(plugin, main);
+
+        Path standaloneDefault = tempDir.resolve("standalone-default");
+        Path pluginDefault = tempDir.resolve("plugin-default");
+        Path standaloneCitrus = tempDir.resolve("standalone-citrus");
+        Path pluginCitrus = tempDir.resolve("plugin-citrus");
+
+        assertEquals(0, standalone.execute("init", 
standaloneDefault.toString(), "--ai", "bob2",
+                "--silent", "--no-fetch"));
+        assertEquals(0, plugin.execute("kit", "init", 
pluginDefault.toString(), "--ai", "bob2",
+                "--silent", "--no-fetch"));
+        assertEquals(0, standalone.execute("init", 
standaloneCitrus.toString(), "--ai", "bob2",
+                "--silent", "--no-fetch", "--with-citrus-mcp"));
+        assertEquals(0, plugin.execute("kit", "init", pluginCitrus.toString(), 
"--ai", "bob2",
+                "--silent", "--no-fetch", "--with-citrus-mcp"));
+
+        Properties defaultProps = loadConfig(standaloneDefault);
+        Properties pluginDefaultProps = loadConfig(pluginDefault);
+        Properties citrusProps = loadConfig(standaloneCitrus);
+        Properties pluginCitrusProps = loadConfig(pluginCitrus);
+
+        assertEquals("false", defaultProps.getProperty("citrus.mcp.enabled"));
+        assertEquals("false", 
pluginDefaultProps.getProperty("citrus.mcp.enabled"));
+        assertEquals("true", citrusProps.getProperty("citrus.mcp.enabled"));
+        assertEquals("true", 
pluginCitrusProps.getProperty("citrus.mcp.enabled"));
+
+        String defaultMcp = 
Files.readString(standaloneDefault.resolve(".bob/mcp.json"));
+        String citrusMcp = 
Files.readString(standaloneCitrus.resolve(".bob/mcp.json"));
+        String pluginCitrusMcp = 
Files.readString(pluginCitrus.resolve(".bob/mcp.json"));
+        assertFalse(defaultMcp.contains("\"citrus\""), "Default init must not 
include citrus MCP server");
+        assertTrue(citrusMcp.contains("\"citrus\""), "Standalone 
--with-citrus-mcp must include citrus");
+        assertTrue(pluginCitrusMcp.contains("\"citrus\""), "Plugin 
--with-citrus-mcp must include citrus");
+    }
+
+    private Properties loadConfig(Path workspace) throws Exception {
+        Properties config = new Properties();
+        try (var input = 
Files.newInputStream(workspace.resolve(".camel-kit/config.properties"))) {
+            config.load(input);
+        }
+        return config;
+    }
+
     @ParameterizedTest
     @CsvSource({"false,bob2", "true,bob2", "false,copilot", "true,copilot", 
"false,claude", "true,claude"})
     void nativeInitAndRegenerationDetectThePrefixInSeparateProcesses(boolean 
plugin, String agent) throws Exception {
diff --git 
a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/McpConfigGenerator.java
 
b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/McpConfigGenerator.java
index 4a83adce..17d84f31 100644
--- 
a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/McpConfigGenerator.java
+++ 
b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/McpConfigGenerator.java
@@ -36,24 +36,34 @@ class McpConfigGenerator {
             if ("toml".equals(ctx.agent().mcpConfigFormat())) {
                 new CodexConfigMerger().merge(configFile, processed, 
ctx.withCitrusMcp());
             } else if ("opencode".equals(ctx.agentName())) {
-                new OpenCodeConfigMerger().merge(configFile, processed);
+                new OpenCodeConfigMerger().merge(configFile, processed, 
ctx.withCitrusMcp());
             } else if ("antigravity".equals(ctx.agentName())) {
                 ObjectNode config = readAntigravityConfig(configFile);
                 ObjectNode generated = (ObjectNode) 
JSON_MAPPER.readTree(processed).path("mcpServers");
                 if (!Files.exists(configFile)) {
                     AtomicFileWriter.write(configFile, processed);
-                } else if (!generated.properties().stream()
-                        .allMatch(server -> 
server.getValue().equals(config.path("mcpServers").get(server.getKey())))) {
-                    String original = Files.readString(configFile);
-                    String bom = original.startsWith("\uFEFF") ? "\uFEFF" : "";
-                    JsoncObjectEditor editor = new 
JsoncObjectEditor(original.substring(bom.length()));
-                    if (!config.has("mcpServers")) {
-                        editor.upsertRootMember("mcpServers", generated);
-                    } else {
-                        editor.removeObjectMembers("mcpServers", 
generated.fieldNames());
-                        editor.appendObjectMembers("mcpServers", generated);
+                } else {
+                    boolean needsUpdate = !generated.properties().stream()
+                            .allMatch(server -> server.getValue()
+                                    
.equals(config.path("mcpServers").get(server.getKey())));
+                    boolean needsCitrusRemoval = !ctx.withCitrusMcp()
+                            && config.path("mcpServers").has("citrus");
+                    if (needsUpdate || needsCitrusRemoval) {
+                        String original = Files.readString(configFile);
+                        String bom = original.startsWith("\uFEFF") ? "\uFEFF" 
: "";
+                        JsoncObjectEditor editor = new 
JsoncObjectEditor(original.substring(bom.length()));
+                        if (!config.has("mcpServers")) {
+                            editor.upsertRootMember("mcpServers", generated);
+                        } else {
+                            editor.removeObjectMembers("mcpServers", 
generated.fieldNames());
+                            if (needsCitrusRemoval) {
+                                editor.removeObjectMembers("mcpServers",
+                                        
java.util.Collections.singletonList("citrus").iterator());
+                            }
+                            editor.appendObjectMembers("mcpServers", 
generated);
+                        }
+                        AtomicFileWriter.write(configFile, bom + 
editor.content());
                     }
-                    AtomicFileWriter.write(configFile, bom + editor.content());
                 }
             } else {
                 Files.writeString(configFile, processed);
diff --git 
a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/OpenCodeConfigMerger.java
 
b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/OpenCodeConfigMerger.java
index 27a485d0..847396d8 100644
--- 
a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/OpenCodeConfigMerger.java
+++ 
b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/generator/OpenCodeConfigMerger.java
@@ -27,7 +27,7 @@ final class OpenCodeConfigMerger {
         }
     }
 
-    void merge(Path configFile, String renderedConfig) throws IOException {
+    void merge(Path configFile, String renderedConfig, boolean withCitrusMcp) 
throws IOException {
         ObjectNode generated = readObject(renderedConfig, "generated OpenCode 
configuration", false);
         requireGeneratedObject(generated, "permission");
         requireGeneratedObject(generated, "mcp");
@@ -47,21 +47,23 @@ final class OpenCodeConfigMerger {
         List<PendingWrite> writes = new ArrayList<>();
         for (int i = 0; i < configurations.size() - 1; i++) {
             ExistingConfiguration configuration = configurations.get(i);
-            String updated = removeManagedEntries(configuration.content(), 
generated);
+            String updated = removeManagedEntries(configuration.content(), 
generated, withCitrusMcp);
             if (!updated.equals(configuration.content())) {
                 writes.add(new PendingWrite(configuration.path(), updated));
             }
         }
 
         ExistingConfiguration highest = 
configurations.get(configurations.size() - 1);
-        String merged = mergeGenerated(highest, generated);
+        String merged = mergeGenerated(highest, generated, withCitrusMcp);
         if (!merged.equals(highest.content())) {
             writes.add(new PendingWrite(highest.path(), merged));
         }
         writeAll(writes);
     }
 
-    private String removeManagedEntries(String content, ObjectNode generated) 
throws IOException {
+    private String removeManagedEntries(
+            String content, ObjectNode generated, boolean withCitrusMcp)
+            throws IOException {
         String body = contentWithoutBom(content);
         if (body.isBlank()) {
             return content;
@@ -69,6 +71,9 @@ final class OpenCodeConfigMerger {
         JsoncObjectEditor editor = new JsoncObjectEditor(body);
         removeObjectEntries(editor, generated, "permission");
         removeObjectEntries(editor, generated, "mcp");
+        if (!withCitrusMcp) {
+            removeCitrusEntries(editor);
+        }
         return bom(content) + editor.content();
     }
 
@@ -77,7 +82,15 @@ final class OpenCodeConfigMerger {
         editor.removeObjectMembers(field, generatedObject.fieldNames());
     }
 
-    private String mergeGenerated(ExistingConfiguration existing, ObjectNode 
generated) throws IOException {
+    private void removeCitrusEntries(JsoncObjectEditor editor) {
+        editor.removeObjectMembers("mcp", 
java.util.Collections.singletonList("citrus").iterator());
+        editor.removeObjectMembers("permission",
+                List.of("citrus_*").iterator());
+    }
+
+    private String mergeGenerated(
+            ExistingConfiguration existing, ObjectNode generated, boolean 
withCitrusMcp)
+            throws IOException {
         if (isCurrentAndOrdered(existing, generated)) {
             return existing.content();
         }
@@ -100,6 +113,9 @@ final class OpenCodeConfigMerger {
         });
         mergeObject(editor, existing.config(), generated, "permission");
         mergeObject(editor, existing.config(), generated, "mcp");
+        if (!withCitrusMcp) {
+            removeCitrusEntries(editor);
+        }
         return prefix + editor.content();
     }
 
diff --git 
a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/service/DoctorService.java
 
b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/service/DoctorService.java
index d6facb95..ffc2f57b 100644
--- 
a/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/service/DoctorService.java
+++ 
b/camel-kit-core/src/main/java/io/github/luigidemasi/camelkit/service/DoctorService.java
@@ -735,7 +735,7 @@ public class DoctorService {
             codexCitrusEnabled = true;
         }
         boolean citrusOk;
-        if (codexCitrusEnabled || servers.get("citrus") instanceof TomlTable) {
+        if (codexCitrusEnabled || servers.contains("citrus")) {
             citrusOk = checkCodexMcpServer(
                     root, mcpFile, servers, "citrus", 
expectations.citrusMcpTools(), findings);
         } else {
diff --git a/camel-kit-core/src/main/resources/skills/shared/mcp-setup.md 
b/camel-kit-core/src/main/resources/skills/shared/mcp-setup.md
index 66a2c2d3..4903d2b7 100644
--- a/camel-kit-core/src/main/resources/skills/shared/mcp-setup.md
+++ b/camel-kit-core/src/main/resources/skills/shared/mcp-setup.md
@@ -29,13 +29,15 @@ The target-specific JSON or TOML schema differs, but the 
Camel server invocation
 }
 ```
 
-All three generated servers use their configured repository lists rather than 
an extracted local artifact:
-
-| Server | JBang runner coordinate | Repository setting |
-|--------|--------------------------|--------------------|
-| Camel Catalog | 
`org.apache.camel:camel-jbang-mcp:{CAMEL_MCP_VERSION}:runner` | 
`{CAMEL_MCP_REPOS}` |
-| Camel-Kit Knowledge | 
`io.github.luigidemasi:camel-kit-knowledge-mcp:{KNOWLEDGE_VERSION}:runner` | 
`{KNOWLEDGE_MCP_REPOS}` |
-| Citrus | `org.citrusframework:citrus-mcp-server:{CITRUS_MCP_VERSION}:runner` 
| `{CITRUS_MCP_REPOS}` |
+By default, `camel-kit init` generates two MCP servers: `camel` (Catalog) and 
`camel-knowledge` (Knowledge). The
+Citrus server is opt-in — pass `--with-citrus-mcp` to include it. All 
generated servers use their configured repository
+lists rather than an extracted local artifact:
+
+| Server | JBang runner coordinate | Repository setting | Generated by 
default? |
+|--------|--------------------------|--------------------|----------------------|
+| Camel Catalog | 
`org.apache.camel:camel-jbang-mcp:{CAMEL_MCP_VERSION}:runner` | 
`{CAMEL_MCP_REPOS}` | Yes |
+| Camel-Kit Knowledge | 
`io.github.luigidemasi:camel-kit-knowledge-mcp:{KNOWLEDGE_VERSION}:runner` | 
`{KNOWLEDGE_MCP_REPOS}` | Yes |
+| Citrus | `org.citrusframework:citrus-mcp-server:{CITRUS_MCP_VERSION}:runner` 
| `{CITRUS_MCP_REPOS}` | Only with `--with-citrus-mcp` |
 
 Use the generated JBang configuration unchanged; do not substitute a local 
launch mechanism.
 
diff --git 
a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/AntigravityGeneratorTest.java
 
b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/AntigravityGeneratorTest.java
index bd83092a..f9642f84 100644
--- 
a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/AntigravityGeneratorTest.java
+++ 
b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/AntigravityGeneratorTest.java
@@ -12,6 +12,7 @@ import io.github.luigidemasi.camelkit.output.Printer;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -24,11 +25,15 @@ class AntigravityGeneratorTest {
     Path tempDir;
 
     private InitContext context(String prefix) {
+        return context(prefix, true);
+    }
+
+    private InitContext context(String prefix, boolean withCitrusMcp) {
         var agent = AgentRegistry.get("antigravity");
         Path skills = tempDir.resolve(agent.skillsDirectory());
         return new InitContext(
                 agent, "antigravity", skills, skills, tempDir, prefix,
-                DistributionConfig.loadFromClasspathOrDefaults(), true, 
Printer.noop());
+                DistributionConfig.loadFromClasspathOrDefaults(), 
withCitrusMcp, Printer.noop());
     }
 
     @ParameterizedTest
@@ -166,4 +171,20 @@ class AntigravityGeneratorTest {
 
         assertEquals(kept, Files.readString(context));
     }
+
+    @Test
+    void regenerationWithoutCitrusRemovesPreviousCitrusServer() throws 
Exception {
+        new AntigravityGenerator().generate(context("camel-kit", true));
+        Path mcpFile = tempDir.resolve(".agents/mcp_config.json");
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode withCitrus = mapper.readTree(Files.readString(mcpFile));
+        assertTrue(withCitrus.path("mcpServers").has("citrus"), "citrus must 
be present after opt-in init");
+
+        new AntigravityGenerator().generate(context("camel-kit", false));
+        JsonNode withoutCitrus = mapper.readTree(Files.readString(mcpFile));
+        assertFalse(withoutCitrus.path("mcpServers").has("citrus"),
+                "citrus must be removed after opt-out regeneration");
+        assertTrue(withoutCitrus.path("mcpServers").has("camel"), "camel must 
remain");
+        assertTrue(withoutCitrus.path("mcpServers").has("camel-knowledge"), 
"camel-knowledge must remain");
+    }
 }
diff --git 
a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/OpenCodeGeneratorTest.java
 
b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/OpenCodeGeneratorTest.java
index ec678c5a..28cdcaf4 100644
--- 
a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/OpenCodeGeneratorTest.java
+++ 
b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/generator/OpenCodeGeneratorTest.java
@@ -45,6 +45,10 @@ class OpenCodeGeneratorTest {
     }
 
     private InitContext createContext(String commandPrefix) {
+        return createContext(commandPrefix, true);
+    }
+
+    private InitContext createContext(String commandPrefix, boolean 
withCitrusMcp) {
         AgentConfig agent = AgentRegistry.get("opencode");
         String agentBaseFolder = agent.folder().substring(0, 
agent.folder().lastIndexOf("/"));
         Path commandsDir = tempDir.resolve(agent.folder());
@@ -52,7 +56,7 @@ class OpenCodeGeneratorTest {
         return new InitContext(
                 agent, "opencode", commandsDir, skillsDir, tempDir,
                 commandPrefix, 
DistributionConfig.loadFromClasspathOrDefaults(),
-                true, Printer.noop());
+                withCitrusMcp, Printer.noop());
     }
 
     @Test
@@ -653,7 +657,7 @@ class OpenCodeGeneratorTest {
                       "permission": {"camel_*": "ask"},
                       "mcp": {"camel": {"type": "local"}}
                     }
-                    """));
+                    """, true));
         } finally {
             Files.setPosixFilePermissions(higherDirectory, 
originalPermissions);
         }
@@ -816,4 +820,22 @@ class OpenCodeGeneratorTest {
         // MCP config exists
         assertTrue(Files.exists(tempDir.resolve("opencode.json")));
     }
+
+    @Test
+    void regenerationWithoutCitrusRemovesPreviousCitrusEntries() throws 
Exception {
+        new OpenCodeGenerator().generate(createContext("camel-kit", true));
+        Path configFile = tempDir.resolve("opencode.json");
+        String withCitrus = Files.readString(configFile);
+        assertTrue(withCitrus.contains("\"citrus\""), "citrus mcp must be 
present after opt-in init");
+        assertTrue(withCitrus.contains("\"citrus_*\""), "citrus_* permission 
must be present after opt-in init");
+
+        new OpenCodeGenerator().generate(createContext("camel-kit", false));
+        String withoutCitrus = Files.readString(configFile);
+        assertFalse(withoutCitrus.contains("\"citrus\""),
+                "citrus mcp must be removed after opt-out regeneration");
+        assertFalse(withoutCitrus.contains("\"citrus_*\""),
+                "citrus_* permission must be removed after opt-out 
regeneration");
+        assertTrue(withoutCitrus.contains("\"camel\""), "camel mcp must 
remain");
+        assertTrue(withoutCitrus.contains("\"camel_*\""), "camel_* permission 
must remain");
+    }
 }
diff --git 
a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/service/DoctorServiceTest.java
 
b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/service/DoctorServiceTest.java
index 59a5ee76..edef523b 100644
--- 
a/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/service/DoctorServiceTest.java
+++ 
b/camel-kit-core/src/test/java/io/github/luigidemasi/camelkit/service/DoctorServiceTest.java
@@ -1320,6 +1320,36 @@ class DoctorServiceTest {
                 "update the allowlist"));
     }
 
+    @Test
+    void malformedCodexCitrusEntryFailsWhenOptInAbsent() throws Exception {
+        createHealthyWorkspace(tempDir, CODEX);
+        Path config = tempDir.resolve(".codex/config.toml");
+        Files.writeString(config, Files.readString(config) + 
"\n[mcp_servers]\ncitrus = \"invalid-server\"\n");
+
+        DoctorResult result = new DoctorService().inspect(new 
DoctorRequest(tempDir));
+
+        assertTrue(result.hasFailures(), result.findings().toString());
+        assertTrue(hasFinding(result, DoctorFinding.Status.FAIL, "mcp",
+                "Codex MCP server 'citrus' is missing",
+                "camel-kit init --here --ai codex --force"));
+    }
+
+    @Test
+    void malformedCodexCitrusEntryFailsWhenOptInExplicitlyFalse() throws 
Exception {
+        createHealthyWorkspace(tempDir, CODEX);
+        Path props = tempDir.resolve(".camel-kit/config.properties");
+        Files.writeString(props, Files.readString(props) + 
"citrus.mcp.enabled=false\n");
+        Path config = tempDir.resolve(".codex/config.toml");
+        Files.writeString(config, Files.readString(config) + 
"\n[mcp_servers]\ncitrus = \"invalid-server\"\n");
+
+        DoctorResult result = new DoctorService().inspect(new 
DoctorRequest(tempDir));
+
+        assertTrue(result.hasFailures(), result.findings().toString());
+        assertTrue(hasFinding(result, DoctorFinding.Status.FAIL, "mcp",
+                "Codex MCP server 'citrus' is missing",
+                "camel-kit init --here --ai codex --force"));
+    }
+
     @Test
     void missingCodexCustomAgentProducesActionableFailure() throws Exception {
         createHealthyWorkspace(tempDir, CODEX);
diff --git a/docs/agent-architectures.md b/docs/agent-architectures.md
index f21a3380..3fca4a1c 100644
--- a/docs/agent-architectures.md
+++ b/docs/agent-architectures.md
@@ -293,7 +293,7 @@ questions, arguments, and phase handoffs. Users can also 
invoke bounded subagent
 |------|---------|
 | `templates/shared/agents-md.md` | Shared `AGENTS.md` bootstrap rendered by 
`AgentsMdGenerator` (includes Camel 4.22 YAML DSL rules) |
 | `templates/opencode/agents/*.md` | 9 agent definitions with per-type 
permissions and agent-owned step limits |
-| `templates/mcp-configs/opencode-mcp.json` | Supported MCP server fields plus 
top-level `ask` patterns for all three server tool namespaces |
+| `templates/mcp-configs/opencode-mcp.json` | Supported MCP server fields plus 
top-level `ask` patterns for all configured server tool namespaces |
 
 Camel-Kit generates `AGENTS.md` through the shared generator flow. Its compact 
bootstrap points to `/camel-start`, which loads the full context through 
progressive skill loading.
 When any supported project configuration already exists (`opencode.json`, 
`opencode.jsonc`,
diff --git a/docs/antigravity.md b/docs/antigravity.md
index eecc1354..062a37fc 100644
--- a/docs/antigravity.md
+++ b/docs/antigravity.md
@@ -18,8 +18,8 @@ Open the initialized project root in Antigravity. Invoke 
`/camel-start` in the C
 name skills; some clients expose a different picker or support activation by 
name. Codex uses `$camel-start`.
 
 Camel-Kit installs shared skills under `.agents/skills/`, two custom agents 
under `.agents/agents/`, complete role
-personas under `.agents/camel-kit-personas/`, project routing in `AGENTS.md`, 
and three MCP servers in
-`.agents/mcp_config.json`: Camel, Camel Knowledge, and Citrus. Inspect 
connections in Antigravity's MCP settings
+personas under `.agents/camel-kit-personas/`, project routing in `AGENTS.md`, 
and MCP servers in
+`.agents/mcp_config.json`: Camel and Camel Knowledge by default, plus Citrus 
when `--with-citrus-mcp` is passed. Inspect connections in Antigravity's MCP 
settings
 or `/mcp` in the CLI. Existing unrelated MCP servers, settings, and comments 
are preserved on reinitialization. Tool calls
 remain subject to Antigravity's permissions; Camel-Kit does not generate MCP 
auto-approval policies.
 
diff --git a/docs/architecture.md b/docs/architecture.md
index 9540d68d..0d3dcf1f 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -515,7 +515,7 @@ Most supported agents use native **sub-agent dispatch** or 
custom-agent isolatio
 
 - **GitHub Copilot CLI** -- project skills live under `.github/skills/` and 
custom agents live under `.github/agents/`. Camel-Kit generates planner, 
implementer, tester, validator, migrator, catalog researcher, security 
reviewer, and read-only Ship worker agents with Copilot tool aliases and MCP 
server prefixes. Internal guide skills copied for custom-agent use are marked 
`user-invocable: false` and `disable-model-invocation: true` using 
Copilot-readable metadata. MCP servers are committ [...]
 
-- **OpenAI Codex CLI** -- native project skills live under `.agents/skills/`, 
project instructions live in `AGENTS.md`, and eight custom roles live under 
`.codex/agents/` (including a Ship worker that declares `sandbox_mode = 
"read-only"` but relies on instruction-level restrictions until the host 
enforces per-agent sandboxing). Codex receives no command-stub directory; 
`$camel-start` and `/skills` are the entry points. The generated 
`.codex/config.toml` preserves unrelated valid project [...]
+- **OpenAI Codex CLI** -- native project skills live under `.agents/skills/`, 
project instructions live in `AGENTS.md`, and eight custom roles live under 
`.codex/agents/` (including a Ship worker that declares `sandbox_mode = 
"read-only"` but relies on instruction-level restrictions until the host 
enforces per-agent sandboxing). Codex receives no command-stub directory; 
`$camel-start` and `/skills` are the entry points. The generated 
`.codex/config.toml` preserves unrelated valid project [...]
 
 - **Pi** -- project skills live under `.pi/skills/` and command stubs are 
generated as `.pi/prompts/` prompt templates. Pi reads `AGENTS.md` natively. 
MCP servers are committed in `.mcp.json` for `pi-mcp-adapter` using the 
adapter's `directTools` allowlist schema. Internal guide skills are marked 
`user-invocable: false` and `disable-model-invocation: true`; Pi currently 
honors only the model-invocation flag. A static 
`.pi/extensions/camel-kit-guard.ts` extension interprets `.pi/camel-kit [...]
 
diff --git a/docs/commands.md b/docs/commands.md
index ff9df024..a0b61ded 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -66,6 +66,7 @@ camel kit init --here [options]
 | `--no-fetch` | `false` | Skip external catalog fetching |
 | `-p`, `--property` | -- | Override a config property (repeatable). Example: 
`-p "camel.main.version=4.18.4"` |
 | `-c`, `--config` | `~/.camel-kit/config.properties` | Path to a custom 
config properties file |
+| `--with-citrus-mcp` | `false` | Include the Citrus MCP server in agent 
configuration. When omitted, no Citrus server is generated; use this flag if 
your project needs Citrus test integration. |
 | `--source-platform` | `auto` | Source platform for migration: `mulesoft`, 
`camel`, `biztalk`, `auto` |
 | `--force` | `false` | Overwrite existing project without prompting (skips 
overwrite detection) |
 | `--silent` | `false` | Suppress all output (no banner, no TUI, no progress, 
no summary) -- useful for CI/scripted environments |
@@ -232,7 +233,7 @@ my-integration/
 
 The MCP configuration file created depends on the `--ai` option chosen.
 
-For OpenAI Codex CLI, Camel-Kit generates `AGENTS.md`, native project skills 
under `.agents/skills/`, eight custom agents under `.codex/agents/`, and the 
`camel`, `camel-knowledge`, and `citrus` MCP servers in `.codex/config.toml`. 
It does not create `.codex/commands/`; after trusting the repository, start 
with `$camel-start`, use `/skills` to inspect skills, and use `/mcp` to inspect 
servers. Codex skips project config and any project hooks until the repository 
is trusted. The three gen [...]
+For OpenAI Codex CLI, Camel-Kit generates `AGENTS.md`, native project skills 
under `.agents/skills/`, eight custom agents under `.codex/agents/`, and the 
`camel` and `camel-knowledge` MCP servers in `.codex/config.toml` (add 
`--with-citrus-mcp` to also include the `citrus` server). It does not create 
`.codex/commands/`; after trusting the repository, start with `$camel-start`, 
use `/skills` to inspect skills, and use `/mcp` to inspect servers. Codex skips 
project config and any project h [...]
 
 For GitHub Copilot CLI, Camel-Kit also generates 
`.github/copilot-instructions.md`, project skills under `.github/skills/`, 
custom agents under `.github/agents/`, and a conservative 
`.github/hooks/camel-kit-safety.json` hook that denies destructive or 
secret-sensitive shell commands while leaving normal Copilot permission prompts 
intact. Copilot users should start by asking Copilot to "Use the `/camel-start` 
skill." Run `/skills list` to inspect available project skills.
 
@@ -280,7 +281,7 @@ For Bob 2, `doctor` reports a `FAIL` for each public skill 
hidden by an effectiv
 It honors `metadata.user-invocable` before the top-level field, as Bob does; 
the four
 internal helpers remain hidden. Empty public skill files and unparseable 
metadata also fail validation.
 
-`doctor` validates `.camel-kit/config.properties`, selected agent command 
stubs when that target uses them, skill directories and `SKILL.md` files, 
registered workspace templates, user-invocable command exposure, 
runtime-supported MCP filtering and permission fields, graph availability, 
command prefix configuration, Java/Maven/JBang prerequisites, and common stale 
references in active generated files. For Codex, it parses 
`.codex/config.toml`, checks all three exact MCP allowlists and pr [...]
+`doctor` validates `.camel-kit/config.properties`, selected agent command 
stubs when that target uses them, skill directories and `SKILL.md` files, 
registered workspace templates, user-invocable command exposure, 
runtime-supported MCP filtering and permission fields, graph availability, 
command prefix configuration, Java/Maven/JBang prerequisites, and common stale 
references in active generated files. For Codex, it parses 
`.codex/config.toml`, checks the exact MCP allowlists and prompt a [...]
 
 Findings are printed as `PASS`, `WARN`, or `FAIL`. Missing external 
prerequisites are warnings so automated checks do not depend on the local 
machine having every optional tool installed. Broken generated workspace 
artifacts are failures and produce a non-zero exit code.
 
diff --git a/docs/modules/ROOT/pages/agents.adoc 
b/docs/modules/ROOT/pages/agents.adoc
index 694f1485..423259ab 100644
--- a/docs/modules/ROOT/pages/agents.adoc
+++ b/docs/modules/ROOT/pages/agents.adoc
@@ -19,9 +19,9 @@ workflow is the same everywhere and only the way you invoke 
it differs.
 |===
 
 The generated assets and the MCP configuration file of each target are listed 
in
-xref:index.adoc#agents[the overview]. Every target gets the same three MCP 
servers, `camel` (catalog, route
-validation, migration and security analysis), `camel-knowledge` 
(documentation, CVE, release and component search)
-and `citrus` (test actions, endpoints and schemas), configured as local JBang 
processes whose versions come from the
+xref:index.adoc#agents[the overview]. Every target gets two MCP servers by 
default — `camel` (catalog, route
+validation, migration and security analysis) and `camel-knowledge` 
(documentation, CVE, release and component search)
+— with an optional third, `citrus` (test actions, endpoints and schemas), 
enabled via `--with-citrus-mcp`. All are configured as local JBang processes 
whose versions come from the
 Camel Kit distribution. See xref:mcp.adoc[].
 
 The pipeline skills delegate independent implementation and review tasks to 
fresh subagent contexts where the agent
@@ -72,7 +72,7 @@ camel-kit init my-integration --ai antigravity
 `init` writes `AGENTS.md`, the skills under `.agents/skills/`, two custom 
agents under `.agents/agents/`
 (`camel-worker` with file and sandboxed command tools, `camel-reviewer` with 
read and search tools plus the
 inherited MCP access), the role
-personas under `.agents/camel-kit-personas/` and the three MCP servers in 
`.agents/mcp_config.json`. Open the
+personas under `.agents/camel-kit-personas/` and the MCP servers in 
`.agents/mcp_config.json` (two by default; three with `--with-citrus-mcp`). 
Open the
 project root in Antigravity and invoke `/camel-start` in the CLI, or ask it to 
use the `camel-start` skill by name;
 `/skills` lists the native skills and `/mcp` the connections.
 
@@ -93,7 +93,7 @@ codex
 
 `init` writes `AGENTS.md`, the skills under `.agents/skills/`, the custom 
agent roles under `.codex/agents/`
 (planner, implementer, tester, validator, migrator, catalog researcher and 
security reviewer; the development
-snapshot adds a Ship worker) and the three MCP servers in 
`.codex/config.toml`. It does not create `.codex/commands/`: Codex discovers 
the skills
+snapshot adds a Ship worker) and the MCP servers in `.codex/config.toml` (two 
by default; three with `--with-citrus-mcp`). It does not create 
`.codex/commands/`: Codex discovers the skills
 directly, so you start with `$camel-start` after trusting the repository, and 
inspect skills with `/skills` and
 servers with `/mcp`.
 
@@ -105,7 +105,7 @@ is blocked: repository trust lets the project configuration 
load, it is not a re
 `default_tools_approval_mode = "prompt"`. If `.codex/config.toml` already 
exists with valid unrelated settings they
 are preserved; invalid TOML or a conflicting server table makes `init` fail 
without changing the file.
 
-`camel-kit doctor` validates the TOML, the three server tables and their 
allowlists, the prompt approval defaults,
+`camel-kit doctor` validates the TOML, the server tables and their allowlists, 
the prompt approval defaults,
 the skills, the required custom-agent fields and the read-only sandbox of the 
research and security agents.
 
 [[copilot]]
diff --git a/docs/modules/ROOT/pages/architecture.adoc 
b/docs/modules/ROOT/pages/architecture.adoc
index 7efd5103..d1228703 100644
--- a/docs/modules/ROOT/pages/architecture.adoc
+++ b/docs/modules/ROOT/pages/architecture.adoc
@@ -31,8 +31,8 @@ front matter against it.
 | `camel-kit-core/src/main/resources/skills/`, copied into the agent's skills 
directory by `init`
 
 | 3. MCP servers
-| Three local servers queried on demand: the Camel catalog server for 
verification and validation, the Knowledge
-  server for documentation search, the Citrus server for test guidance
+| Two local servers by default (Camel catalog for verification and validation, 
Knowledge for documentation search),
+  with an optional third (Citrus for test guidance, enabled via 
`--with-citrus-mcp`)
 | The agent's MCP configuration file, generated by `init`
 
 | 4. Graph CLI (optional)
@@ -134,4 +134,4 @@ xref:constitution.adoc[] and 
xref:pipeline.adoc#iron-laws[Iron Laws].
 
 Graph queries in the CLI, not in MCP::
 Graph analysis moved out of an MCP server into `camel-kit graph` because a 
local process call is faster than a
-round-trip, and a generated project then needs only the three MCP servers.
+round-trip, and a generated project then needs only its MCP servers (two by 
default; three with `--with-citrus-mcp`).
diff --git a/docs/modules/ROOT/pages/commands.adoc 
b/docs/modules/ROOT/pages/commands.adoc
index c5044df3..969ee2c4 100644
--- a/docs/modules/ROOT/pages/commands.adoc
+++ b/docs/modules/ROOT/pages/commands.adoc
@@ -31,6 +31,8 @@ camel-kit init --here [options]
 | `--source-platform` | `auto` | Source platform of a migration: `mulesoft`, 
`biztalk`, `camel` or `auto`
 | `--citrus-version` | the configured `citrus.version` ({citrus-version}) | 
Citrus version for the test schemas
   and the generated test dependencies
+| `--with-citrus-mcp` | `false` | Include the Citrus MCP server in agent 
configuration. When omitted, no Citrus
+  server is generated; use this flag if your project needs Citrus test 
integration.
 | `--no-fetch` | `false` | Skip the external catalog fetch
 | `-p`, `--property` | | Override a configuration property, repeatable: `-p 
"camel.main.version=4.18.4"`
 | `-c`, `--config` | `~/.camel-kit/config.properties` | Configuration 
properties file
@@ -61,7 +63,7 @@ camel-kit doctor [--project-dir <path>] [--json]
 It checks `.camel-kit/config.properties`, the command stubs where the target 
uses them, the skill directories and
 manifests, the registered workspace templates, the exposure of user-invocable 
commands, the MCP filtering and
 permission fields the runtime supports, graph availability, the command 
prefix, the prerequisites and common stale
-references in generated files. Target specifics: for Codex it parses 
`.codex/config.toml`, the three exact MCP
+references in generated files. Target specifics: for Codex it parses 
`.codex/config.toml`, the exact MCP
 allowlists, the prompt approval modes and the required fields of every 
custom-agent file; for Bob 2 it fails each
 public skill hidden by `user-invocable: false`; for Qwen it checks 
`includeTools` and `trust`; for OpenCode it
 evaluates all configuration layers as one and names the file that defines each 
rule. Configurations that predate the
diff --git a/docs/modules/ROOT/pages/greenfield.adoc 
b/docs/modules/ROOT/pages/greenfield.adoc
index 8fd74da9..1e56fe44 100644
--- a/docs/modules/ROOT/pages/greenfield.adoc
+++ b/docs/modules/ROOT/pages/greenfield.adoc
@@ -20,7 +20,7 @@ camel-kit init order-processing --ai claude
 cd order-processing
 ----
 
-The agent now has the Camel Kit skills and the three MCP servers. The first 
pipeline run needs an active pipeline
+The agent now has the Camel Kit skills and the MCP servers (two by default; 
three with `--with-citrus-mcp`). The first pipeline run needs an active pipeline
 ID, which `init` does not create; `/camel-brainstorm` asks you to create it 
when none exists:
 
 [source,bash]
diff --git a/docs/modules/ROOT/pages/mcp.adoc b/docs/modules/ROOT/pages/mcp.adoc
index 72a7d8b5..32febbcb 100644
--- a/docs/modules/ROOT/pages/mcp.adoc
+++ b/docs/modules/ROOT/pages/mcp.adoc
@@ -1,7 +1,7 @@
 = MCP servers
 
 Camel Kit uses the Model Context Protocol to verify Camel configuration, to 
guide Citrus test generation and to
-search documentation without loading whole catalogs into the agent's context. 
`init` configures three local servers
+search documentation without loading whole catalogs into the agent's context. 
`init` configures two local servers by default (three with `--with-citrus-mcp`)
 in the agent's MCP configuration file (see xref:agents.adoc[] for the file of 
each target); the agent starts each
 one with JBang when a skill first needs it, and JBang resolves the runner and 
the catalog artifacts from the
 configured repositories.
diff --git a/docs/user-guide.md b/docs/user-guide.md
index eb4acea4..a7dc9ad4 100644
--- a/docs/user-guide.md
+++ b/docs/user-guide.md
@@ -263,7 +263,7 @@ order-processing/
 
 The init command checks prerequisites (Java, JBang, Camel JBang, test plugin), 
copies skill files, configures MCP, and sets up the Maven wrapper so you can 
start designing immediately. If the target directory already contains a 
camel-kit project (`AGENTS.md` or `.camel-kit/`), init warns and exits — use 
`--force` to overwrite.
 
-With `--ai codex`, both `camel-kit init` and the Camel JBang plugin form 
`camel kit init` generate repository-scoped Codex resources. Trust the 
repository so Codex can load `.codex/config.toml`; Codex also skips any 
user-added project hooks until trust. Then start with `$camel-start`; `/skills` 
lists project skills and `/mcp` shows the three configured servers. Camel-Kit 
preserves unrelated valid project config, does not create `.codex/commands/`, 
and does not change global Codex setting [...]
+With `--ai codex`, both `camel-kit init` and the Camel JBang plugin form 
`camel kit init` generate repository-scoped Codex resources. Trust the 
repository so Codex can load `.codex/config.toml`; Codex also skips any 
user-added project hooks until trust. Then start with `$camel-start`; `/skills` 
lists project skills and `/mcp` shows the configured servers (two by default; 
three with `--with-citrus-mcp`). Camel-Kit preserves unrelated valid project 
config, does not create `.codex/commands/ [...]
 
 ### Validating the Workspace
 

Reply via email to