luigidemasi commented on code in PR #149:
URL: https://github.com/apache/camel-kit/pull/149#discussion_r4510875329
##########
kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java:
##########
@@ -30,22 +48,195 @@ 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;
+ boolean unresolvedSubstitution = false;
+ StringBuilder denials = new StringBuilder();
+ for (String server : mcpServers) {
+ if (SUBSTITUTION_PATTERN.matcher(server).find()) {
Review Comment:
[P1] Fail closed before substitutions can hide the MCP section
The guard only examines server names already found under the literal `mcp`
key, but OpenCode 2.2.5 [substitutes the entire config text before
parsing](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/core/src/config/variable.ts).
For example, set `MCP_SECTION=mcp` and put
`{"{env:MCP_SECTION}":{"probe":{"type":"local","command":["<local MCP
fixture>"]}}}` in `OPENCODE_CONFIG_CONTENT` before initialization. The
generator sees no `mcp` object, leaves the worker installed without `probe_*:
deny`, and OpenCode resolves the key and loads `probe`.
I reproduced this at this head with an actual OpenCode 2.2.5
`camel-ship-worker` child: `execute` calls `probe.mark` and writes the harmless
canary. A `{file:section-name.txt}` key containing `mcp` does the same; adding
`probe_*: deny` blocks both matching controls. Resolve the effective
configuration before enumeration, or disable/remove the worker whenever an
unresolved substitution can affect discovery, including enclosing keys. Add
generation/reinitialization coverage for these cases.
_AI-generated by Codex on behalf of @luigidemasi._
##########
kit/camel-kit-core/src/main/java/org/apache/camel/kit/generator/OpenCodeGenerator.java:
##########
@@ -30,22 +48,195 @@ 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;
+ boolean unresolvedSubstitution = false;
+ StringBuilder denials = new StringBuilder();
+ for (String server : mcpServers) {
+ if (SUBSTITUTION_PATTERN.matcher(server).find()) {
+ unresolvedSubstitution = 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 &&
!unresolvedSubstitution);
+ }
+
+ 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);
+ }
+ collectMcpServersFromDir(customConfigDir(), names);
+ collectMcpServersFromDir(managedConfigDir(), 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);
+ }
+
+ private static void collectMcpServersFromDir(Path dir, Set<String> names)
throws IOException {
+ if (dir == null) {
+ return;
+ }
+ for (String fileName : GLOBAL_CONFIG_FILES) {
+ Path candidate = dir.resolve(fileName);
+ if (Files.exists(candidate)) {
+ collectMcpServersFromFile(candidate, 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);
+ }
+
+ 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 Path.of("/etc/opencode");
Review Comment:
[P1] Select the managed configuration directory for the host platform
`/etc/opencode` is only OpenCode's Linux managed directory. OpenCode 2.2.5's
[systemDirectory
implementation](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/core/src/config/discovery.ts#L24-L29)
uses `/Library/Application Support/opencode` on macOS and
`%ProgramData%\opencode` on Windows. This implementation returns
`/etc/opencode` on every platform, so a pre-existing managed MCP server on
macOS or Windows is absent from the generated denial list while the host still
loads it. The read-only worker then inherits its tools; `mcp: deny` alone does
not block individual MCP actions.
Use the host's platform-specific discovery rules, or suppress native worker
generation where the effective managed inventory cannot be established. On
macOS, that also includes [managed-preference
plists](https://github.com/anomalyco/opencode/blob/v2.2.5/packages/core/src/config.ts#L283-L290),
not just the system directory. The new test overrides `managedConfigDir()`, so
it does not test this selection. I verified the Linux managed-config fix with
the real host and checked the non-Linux mismatch against the pinned host source
and this method; I have not run OpenCode on macOS or Windows.
_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]