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


##########
kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java:
##########
@@ -30,22 +49,220 @@ 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.
+
+        McpInventory inventory = readMcpInventory(ctx);
+        Set<String> seen = new LinkedHashSet<>();
+        boolean collisionDetected = false;
+        StringBuilder denials = new StringBuilder();
+        for (String server : inventory.servers) {
+            if (SUBSTITUTION_PATTERN.matcher(server).find()) {
+                inventory = new McpInventory(inventory.servers, true);
+                continue;
+            }
+            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 && 
!inventory.hasUnresolvedSubstitution);
+    }
+
+    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 == '-';
+    }
+
+    record McpInventory(List<String> servers, boolean 
hasUnresolvedSubstitution) {
+    }
+
+    private McpInventory readMcpInventory(InitContext ctx) throws IOException {
+        Set<String> names = new LinkedHashSet<>();
+        boolean hasSubstitution = false;
+        for (Path configFile : 
OpenCodeProjectConfig.existingFiles(ctx.projectDir())) {
+            hasSubstitution |= 
collectFromFileWithSubstitutionCheck(configFile, names);
+        }
+        for (Path ancestorFile : ancestorConfigFiles(ctx.projectDir())) {
+            hasSubstitution |= 
collectFromFileWithSubstitutionCheck(ancestorFile, names);
+        }
+        for (Path globalFile : globalConfigFiles()) {
+            hasSubstitution |= 
collectFromFileWithSubstitutionCheck(globalFile, names);
+        }
+        hasSubstitution |= 
collectFromDirWithSubstitutionCheck(customConfigDir(), names);
+        hasSubstitution |= 
collectFromDirWithSubstitutionCheck(managedConfigDir(), names);
+        Path explicitConfig = explicitConfigFile();
+        if (explicitConfig != null) {
+            hasSubstitution |= 
collectFromFileWithSubstitutionCheck(explicitConfig, names);
+        }
+        String inlineContent = inlineConfigContent();
+        if (inlineContent != null && !inlineContent.isBlank()) {
+            if (SUBSTITUTION_PATTERN.matcher(inlineContent).find()) {
+                hasSubstitution = true;
+            }
+            collectMcpServersFromContent(inlineContent, names);
+        }
+        return new McpInventory(List.copyOf(names), hasSubstitution);
+    }
+
+    private static boolean collectFromFileWithSubstitutionCheck(Path 
configFile, Set<String> names)
+            throws IOException {
+        String rawContent = Files.readString(configFile);
+        if (rawContent.startsWith("\uFEFF")) {
+            rawContent = rawContent.substring(1);
+        }
+        boolean hasSubstitution = 
SUBSTITUTION_PATTERN.matcher(rawContent).find();
+        collectMcpServersFromContent(rawContent, names);
+        return hasSubstitution;
+    }
+
+    private static boolean collectFromDirWithSubstitutionCheck(Path dir, 
Set<String> names)
+            throws IOException {
+        if (dir == null) {
+            return false;
+        }
+        boolean hasSubstitution = false;
+        for (String fileName : GLOBAL_CONFIG_FILES) {
+            Path candidate = dir.resolve(fileName);
+            if (Files.exists(candidate)) {
+                hasSubstitution |= 
collectFromFileWithSubstitutionCheck(candidate, names);
+            }
+        }
+        return hasSubstitution;
+    }
+
+    static void collectMcpServersFromContent(String jsonContent, Set<String> 
names) throws IOException {
+        collectMcpServersFromNode(
+                OpenCodeProjectConfig.newJsonMapper().readTree(jsonContent), 
names);
+    }
+
+    private static void collectMcpServersFromNode(JsonNode config, Set<String> 
names) {
+        if (config == null) {
+            return;
+        }
+        JsonNode mcp = config.path("mcp");
+        if (!mcp.isObject()) {
+            return;
+        }
+        JsonNode servers = mcp.path("servers");
+        boolean hasNativeContainer = servers.isObject() && 
isNativeServersContainer(servers);
+        if (hasNativeContainer) {
+            servers.fields().forEachRemaining(entry -> {
+                if (entry.getValue().isObject()) {
+                    names.add(entry.getKey());
+                }
+            });
+        }
+        mcp.fields().forEachRemaining(entry -> {
+            if (entry.getValue().isObject()
+                    && !(hasNativeContainer && 
"servers".equals(entry.getKey()))) {
+                names.add(entry.getKey());
+            }
+        });
+    }
+
+    private static boolean isNativeServersContainer(JsonNode servers) {
+        JsonNode type = servers.path("type");
+        return !type.isTextual();
+    }
+
+    static List<Path> ancestorConfigFiles(Path projectDir) throws IOException {
+        Path realProject = projectDir.toRealPath();
+        Path parent = realProject.getParent();
+        List<Path> result = new java.util.ArrayList<>();
+        while (parent != null) {
+            for (String name : ANCESTOR_CONFIG_FILES) {
+                Path candidate = parent.resolve(name);
+                if (Files.exists(candidate)) {
+                    result.add(candidate);
+                }
+            }
+            parent = parent.getParent();
+        }
+        return result;
+    }
+
+    List<Path> globalConfigFiles() {
+        Path globalDir = globalConfigDir();
+        return GLOBAL_CONFIG_FILES.stream()
+                .map(globalDir::resolve)
+                .filter(Files::exists)
+                .toList();
+    }
+
+    Path globalConfigDir() {
+        String xdgConfigHome = System.getenv("XDG_CONFIG_HOME");
+        if (xdgConfigHome != null && !xdgConfigHome.isBlank()) {
+            return Path.of(xdgConfigHome, "opencode");
+        }
+        return Path.of(System.getProperty("user.home"), ".config", "opencode");
+    }
+
+    Path explicitConfigFile() {
+        String explicitPath = System.getenv("OPENCODE_CONFIG");
+        if (explicitPath != null && !explicitPath.isBlank()) {
+            Path path = Path.of(explicitPath);
+            if (Files.exists(path)) {
+                return path;
+            }
+        }
+        return null;
+    }
+
+    Path customConfigDir() {
+        String dir = System.getenv("OPENCODE_CONFIG_DIR");
+        if (dir != null && !dir.isBlank()) {
+            return Path.of(dir);
+        }
+        return null;
+    }
+
+    Path managedConfigDir() {
+        return managedConfigDirForOs(System.getProperty("os.name", ""));
+    }
+
+    static Path managedConfigDirForOs(String osName) {
+        String os = osName.toLowerCase(Locale.ROOT);
+        if (os.contains("mac") || os.contains("darwin")) {
+            return Path.of("/Library/Application Support/opencode");
+        } else if (os.contains("win")) {
+            return Path.of("C:\\ProgramData", "opencode");

Review Comment:
   [P1] Complete the platform's managed MCP inventory
   
   The default directories are now selected correctly, but supported managed 
sources remain omitted. OpenCode 2.2.5 [uses `ProgramData` when 
set](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/core/src/config/discovery.ts#L24-L35);
 this always uses `C:\ProgramData`. With `ProgramData=D:\CompanyData`, the 
resolver probe still returns the C: path, so managed servers under the host's 
actual directory receive no denial. On macOS, OpenCode also [loads 
managed-preference 
plists](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/core/src/config.ts#L232-L249),
 from the per-user or system `/Library/Managed Preferences` location; neither 
is inventoried here.
   
   Honor the effective Windows directory and macOS managed preferences, or 
disable/remove the native worker when those sources cannot be bounded. Add 
generation-path coverage: replacing `managedConfigDir()` with a constant Linux 
path still passes all 70 generator/resource tests, because the new OS tests 
only call the helper. This platform finding is based on the pinned host source 
and Java resolver probe; no live macOS/Windows host run was performed.
   
   _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