luigidemasi commented on code in PR #149:
URL: https://github.com/apache/camel-kit/pull/149#discussion_r4493066641


##########
kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java:
##########
@@ -30,22 +45,164 @@ public void generate(InitContext ctx) throws Exception {
         Map<String, Object> data = new HashMap<>(
                 Map.of(
                         "COMMAND_PREFIX", ctx.commandPrefix()));
-        // AGENTS.md already generated by the shared generator flow.
+
+        List<String> mcpServers = readMcpServerNames(ctx);
+        Set<String> seen = new LinkedHashSet<>();
+        boolean collisionDetected = false;
+        StringBuilder denials = new StringBuilder();
+        for (String server : mcpServers) {
+            String normalized = normalizeServerName(server);
+            if (BUILTIN_PERMISSION_PREFIXES.contains(normalized)) {
+                collisionDetected = true;
+            } else if (seen.add(normalized)) {
+                denials.append("  ").append(normalized).append("_*: deny\n");
+            }
+        }
+        data.put("MCP_TOOL_DENIALS", denials.toString());
 
         // OpenCode-specific: generate agent definitions
-        generateAgents(ctx, data);
+        generateAgents(ctx, data, !collisionDetected);
+    }
+
+    static String normalizeServerName(String name) {
+        StringBuilder sb = new StringBuilder(name.length());
+        for (int i = 0; i < name.length(); i++) {
+            char c = name.charAt(i);
+            sb.append(isAsciiAlphanumericOrHyphen(c) ? c : '_');
+        }
+        return sb.toString();
+    }
+
+    private static boolean isAsciiAlphanumericOrHyphen(char c) {
+        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' 
&& c <= '9') || c == '-';
+    }
+
+    private List<String> readMcpServerNames(InitContext ctx) throws 
IOException {
+        Set<String> names = new LinkedHashSet<>();
+        for (Path configFile : 
OpenCodeProjectConfig.existingFiles(ctx.projectDir())) {
+            collectMcpServersFromFile(configFile, names);
+        }
+        for (Path ancestorFile : ancestorConfigFiles(ctx.projectDir())) {
+            collectMcpServersFromFile(ancestorFile, names);
+        }
+        for (Path globalFile : globalConfigFiles()) {
+            collectMcpServersFromFile(globalFile, names);
+        }
+        Path explicitConfig = explicitConfigFile();
+        if (explicitConfig != null) {
+            collectMcpServersFromFile(explicitConfig, names);
+        }
+        String inlineContent = inlineConfigContent();
+        if (inlineContent != null && !inlineContent.isBlank()) {
+            collectMcpServersFromContent(inlineContent, names);
+        }
+        return List.copyOf(names);
+    }
+
+    static void collectMcpServersFromFile(Path configFile, Set<String> names) 
throws IOException {
+        collectMcpServersFromNode(
+                
OpenCodeProjectConfig.newJsonMapper().readTree(configFile.toFile()), names);
+    }
+
+    static void collectMcpServersFromContent(String jsonContent, Set<String> 
names) throws IOException {
+        collectMcpServersFromNode(
+                OpenCodeProjectConfig.newJsonMapper().readTree(jsonContent), 
names);

Review Comment:
   [P1] Resolve config substitutions before deriving server denial names
   
   Reading the raw JSON does not yield the names OpenCode actually registers. 
For example, with `CK_REVIEW_MCP_NAME=probe` and an MCP entry named 
`{env:CK_REVIEW_MCP_NAME}` in `OPENCODE_CONFIG_CONTENT`, this emits a denial 
for the normalized placeholder rather than `probe_*`. OpenCode [substitutes 
environment/file references before 
parsing](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/core/src/config/variable.ts),
 so its worker receives `probe.mark` instead. I reproduced actual child canary 
writes with this inline env reference, an inline `{file:server-name.txt}` 
reference containing `probe`, and an env reference in project config. All 
inputs existed before generation; adding `probe_*: deny` blocked each matching 
control.
   
   Use the resolved host names for both glob generation and collision checks, 
or refuse native generation for unresolved dynamic names. Cover substitutions 
through both the file and inline paths; simply parsing them with the shared 
raw-JSON extractor leaves the read-only MCP boundary bypassable.
   
   _AI-generated by Codex on behalf of @luigidemasi._



##########
kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java:
##########
@@ -30,22 +45,164 @@ public void generate(InitContext ctx) throws Exception {
         Map<String, Object> data = new HashMap<>(
                 Map.of(
                         "COMMAND_PREFIX", ctx.commandPrefix()));
-        // AGENTS.md already generated by the shared generator flow.
+
+        List<String> mcpServers = readMcpServerNames(ctx);
+        Set<String> seen = new LinkedHashSet<>();
+        boolean collisionDetected = false;
+        StringBuilder denials = new StringBuilder();
+        for (String server : mcpServers) {
+            String normalized = normalizeServerName(server);
+            if (BUILTIN_PERMISSION_PREFIXES.contains(normalized)) {
+                collisionDetected = true;
+            } else if (seen.add(normalized)) {
+                denials.append("  ").append(normalized).append("_*: deny\n");
+            }
+        }
+        data.put("MCP_TOOL_DENIALS", denials.toString());
 
         // OpenCode-specific: generate agent definitions
-        generateAgents(ctx, data);
+        generateAgents(ctx, data, !collisionDetected);
+    }
+
+    static String normalizeServerName(String name) {
+        StringBuilder sb = new StringBuilder(name.length());
+        for (int i = 0; i < name.length(); i++) {
+            char c = name.charAt(i);
+            sb.append(isAsciiAlphanumericOrHyphen(c) ? c : '_');
+        }
+        return sb.toString();
+    }
+
+    private static boolean isAsciiAlphanumericOrHyphen(char c) {
+        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' 
&& c <= '9') || c == '-';
+    }
+
+    private List<String> readMcpServerNames(InitContext ctx) throws 
IOException {
+        Set<String> names = new LinkedHashSet<>();
+        for (Path configFile : 
OpenCodeProjectConfig.existingFiles(ctx.projectDir())) {
+            collectMcpServersFromFile(configFile, names);
+        }
+        for (Path ancestorFile : ancestorConfigFiles(ctx.projectDir())) {
+            collectMcpServersFromFile(ancestorFile, names);
+        }
+        for (Path globalFile : globalConfigFiles()) {
+            collectMcpServersFromFile(globalFile, names);
+        }
+        Path explicitConfig = explicitConfigFile();
+        if (explicitConfig != null) {
+            collectMcpServersFromFile(explicitConfig, names);
+        }
+        String inlineContent = inlineConfigContent();
+        if (inlineContent != null && !inlineContent.isBlank()) {
+            collectMcpServersFromContent(inlineContent, names);
+        }
+        return List.copyOf(names);

Review Comment:
   [P1] Include custom-directory and managed configuration in the MCP inventory
   
   The source list still omits `OPENCODE_CONFIG_DIR/opencode.json[c]` and the 
platform's managed config directory (`/etc/opencode` on Linux). Both are 
supported [OpenCode config 
sources](https://opencode.ai/docs/config/#locations). On OpenCode 2.2.5 I put 
the same harmless `probe` MCP server exclusively in each source before **both 
init generation and host startup**. In both cases the generated worker lacked 
`probe_*: deny`, and the actual `camel-ship-worker` child invoked `probe.mark` 
through `execute` and wrote its canary. Adding only that denial blocked each 
call. The managed-path reproduction used a private mount namespace, leaving the 
host's `/etc` untouched.
   
   Please obtain the effective host MCP inventory, or decline native worker 
generation when an active source cannot be bounded. Add generation/runtime 
regression cases for the custom directory and platform managed directory; the 
plain inline-config fix does not close these inherited-tool paths.
   
   _AI-generated by Codex on behalf of @luigidemasi._



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to