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
