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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit f5c97f33bc5cae35e22ca48fb9869111f8e36644
Author: Andrus Adamchik <[email protected]>
AuthorDate: Thu Jun 25 18:44:08 2026 -0400

    CAY-2965 MCP Cgen should not fail on an absent "<cgen>" tag
---
 RELEASE-NOTES.txt                                  |  1 +
 ai-plugin/references/cgen-config.md                |  6 ++--
 ai-plugin/references/mcp-tools.md                  |  8 +++--
 ai-plugin/skills/cayenne-cgen/SKILL.md             | 11 +++---
 cayenne-mcp-server/PROTOCOL.md                     |  4 +--
 .../apache/cayenne/mcp/tools/cgen/CgenRunTool.java | 36 ++++++++++++++-----
 .../mcp/tools/cgen/protocol/CgenErrorCode.java     |  1 -
 .../apache/cayenne/mcp/tools/cgen/CgenRunIT.java   | 41 ++++++++++++++++++++++
 .../mcp/tools/cgen/CgenRunValidationTest.java      | 16 ---------
 9 files changed, 86 insertions(+), 38 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 67f12f917..c04c01616 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -26,6 +26,7 @@ CAY-2959 Modeler: DbRelationship dialog "Cancel" doesn't 
cancel
 CAY-2960 Undoing renamed relationship change throws
 CAY-2961 PostgreSQL "text" column is reverse-engineered as CLOB
 CAY-2964 ClassCastException for non-generated meaningful PKs
+CAY-2965 MCP Cgen should not fail on an absent "<cgen>" tag
 
 ----------------------------------
 Release: 5.0-M2
diff --git a/ai-plugin/references/cgen-config.md 
b/ai-plugin/references/cgen-config.md
index 08ab76be0..0a755c8ce 100644
--- a/ai-plugin/references/cgen-config.md
+++ b/ai-plugin/references/cgen-config.md
@@ -106,11 +106,13 @@ The MCP tool:
 
 1. Loads the project at `projectPath`.
 2. Finds the named DataMap (`dataMap` argument).
-3. Reads its embedded `<cgen>` block.
+3. Reads its embedded `<cgen>` block — or, if there is none, synthesizes a 
default config (see below).
 4. Resolves `destDir` relative to the `*.map.xml` file.
 5. Runs the generator. Returns a JSON object listing files `written`, files 
`skipped` (already up-to-date), and any `errors`.
 
-If the DataMap has no `<cgen>` block, the tool returns an error and the user 
must add one — typically via the `cayenne-modeling` skill or the Modeler GUI.
+If the DataMap has no `<cgen>` block, the tool does **not** fail. It 
synthesizes a default config — all entities and embeddables, `makePairs=true`, 
with `destDir` derived from the Maven layout (`src/main/resources` → 
`src/main/java`, likewise for `test`; non-Maven layouts fall back to the 
DataMap's own directory). This mirrors what the Maven plugin and CayenneModeler 
do.
+
+The synthesized config is **not** written back to the DataMap — it is used for 
that one run only. If the user wants to persist or customize it (different 
destination, templates, entity filtering), add a `<cgen>` block via the 
`cayenne-modeling` skill or the Modeler GUI. The starting config below is a 
good template for that.
 
 ## Determining `destDir`
 
diff --git a/ai-plugin/references/mcp-tools.md 
b/ai-plugin/references/mcp-tools.md
index 2be65650d..cad3c0c8a 100644
--- a/ai-plugin/references/mcp-tools.md
+++ b/ai-plugin/references/mcp-tools.md
@@ -32,7 +32,7 @@ Verify the server is registered with `claude mcp list` — you 
should see an ent
 
 ## Tool: `cgen_run`
 
-Runs Cayenne's class generator for one DataMap, using the `<cgen>` config 
block embedded in that DataMap.
+Runs Cayenne's class generator for one DataMap. Uses the `<cgen>` config block 
embedded in that DataMap if present; if the DataMap has no `<cgen>` block, the 
tool synthesizes a default config (all entities and embeddables, 
`makePairs=true`, destination derived from the standard Maven layout) and 
generates anyway — a missing block is **not** an error.
 
 **Arguments:**
 
@@ -47,7 +47,7 @@ Runs Cayenne's class generator for one DataMap, using the 
`<cgen>` config block
 {
   "status": "ok" | "error",
   "summary": { "writtenCount": 3, "skippedCount": 12, "errorCount": 0 },
-  "resolvedConfig": { "destDir": "...", "mode": "entity", ... },
+  "resolvedConfig": { "destDir": "..." },
   "writtenFiles": [{ "path": "...", "size": 1234 }],
   "skippedFiles": [{ "path": "...", "reason": "up-to-date" }],
   "errors": []
@@ -56,11 +56,13 @@ Runs Cayenne's class generator for one DataMap, using the 
`<cgen>` config block
 
 Surface the `summary` to the user verbatim. List the first few `writtenFiles` 
paths; full list is informational. If `errors` is non-empty, those are blocking 
— show them.
 
+When the DataMap had no `<cgen>` block, the tool generated from a synthesized 
default config. The default destination is derived from the Maven layout 
(`src/main/resources` → `src/main/java`); for non-Maven layouts it falls back 
to the DataMap's own directory, so check `resolvedConfig.destDir` and confirm 
with the user if it looks wrong. Offer to persist a `<cgen>` block (via 
`cayenne-modeling` or the Modeler) if they want to customize destination, 
templates, or filtering.
+
 **Failure modes:**
 
-- DataMap has no `<cgen>` block → error; user must add one (see 
`cgen-config.md` for the XML to insert).
 - `projectPath` not readable or not a valid Cayenne project → validation error.
 - `dataMap` doesn't match any `<map name="...">` → validation error.
+- `<cgen>` block present but specifies no resolvable destination directory → 
`destdir_not_specified`.
 
 **Source:** 
`cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/CgenRunTool.java`.
 
diff --git a/ai-plugin/skills/cayenne-cgen/SKILL.md 
b/ai-plugin/skills/cayenne-cgen/SKILL.md
index 5a7ed1912..73b833ee9 100644
--- a/ai-plugin/skills/cayenne-cgen/SKILL.md
+++ b/ai-plugin/skills/cayenne-cgen/SKILL.md
@@ -23,7 +23,7 @@ description: "Use this skill whenever the user wants to 
(re)generate Cayenne ent
 -->
 # cayenne-cgen
 
-Run Cayenne's class generator on a DataMap via the `mcp__cayenne__cgen_run` 
MCP tool. The tool reads the embedded `<cgen>` block in the DataMap to 
determine destination, mode, templates, etc.; if no block is present the tool 
reports an error and the skill adds a default one then retries.
+Run Cayenne's class generator on a DataMap via the `mcp__cayenne__cgen_run` 
MCP tool. The tool reads the embedded `<cgen>` block in the DataMap to 
determine destination, mode, templates, etc.; **if no block is present the tool 
generates anyway using a sensible default config** (all entities, 
`makePairs=true`, destination derived from the Maven layout). A missing 
`<cgen>` block is **not** an error and must never stop you from running the 
tool.
 
 ## Required reading
 
@@ -51,11 +51,9 @@ mcp__cayenne__cgen_run({
 })
 ```
 
-If the tool is not available (MCP server not registered), surface 
`cayenne-mcp-server/README.md` and stop. **Do not** suggest `mvn cayenne:cgen` 
or the Gradle cgen task.
-
-If the tool errors because no `<cgen>` block is present in the DataMap, add a 
minimal one using the starting config approach from `cgen-config.md`
+Call the tool directly for each DataMap — never inspect the DataMap for a 
`<cgen>` block first, and never treat its absence as a blocker. The tool 
generates with a default config when no block exists.
 
-If the `destDir` can't be determined reliably (non-standard project layout, 
empty project, etc.), conform it with the user
+If the tool is not available (MCP server not registered), surface 
`cayenne-mcp-server/README.md` and stop. **Do not** suggest `mvn cayenne:cgen` 
or the Gradle cgen task.
 
 ## Step 3 — Surface the result
 
@@ -64,6 +62,7 @@ The tool returns structured JSON. Report:
 - `summary.writtenCount`, `summary.skippedCount`, `summary.errorCount` 
verbatim — these are the headline.
 - The first few entries in `writtenFiles` (relative paths). Full list is 
informational; offer to dump it if the user asks.
 - Any entries in `errors` — these are blocking. Read the messages and explain 
in user terms (a missing entity class name, a bad template path, an invalid 
`<destDir>`, etc.).
+- `resolvedConfig.destDir` — the absolute output directory. If the DataMap had 
no `<cgen>` block, the tool generated from a synthesized default; report this 
destination so the user can confirm it's right, and offer to persist a `<cgen>` 
block (via `cayenne-modeling`) if they want to customize destination, 
templates, or entity filtering.
 
 If `writtenCount` is 0 and `skippedCount` covers everything, say so — it means 
everything is already up-to-date and no work was needed.
 
@@ -74,7 +73,7 @@ If `writtenCount` is 0 and `skippedCount` covers everything, 
say so — it means
 
 ## Anti-patterns
 
-- **Do not** pre-check the DataMap for a `<cgen>` block before calling 
`cgen_run`. Call the tool first; add the default config only if the tool 
reports it is missing.
+- **Do not** pre-check the DataMap for a `<cgen>` block before calling 
`cgen_run`, and **do not** add a block before running. A missing block is not 
an error — the tool generates with defaults. Only add a `<cgen>` block 
afterward if the user wants to persist or customize the config.
 - **Do not** suggest Maven (`mvn cayenne:cgen`) or Gradle (`cayenneCgen`) 
goals when MCP is unavailable. Those build plugins are out of scope. Point at 
MCP setup instead.
 - **Do not** edit `_<Entity>.java` files. Generated superclasses. Edit 
`<Entity>.java` subclasses.
 - **Do not** confuse `projectPath` and `dataMap` arguments. `projectPath` is a 
file system path to `cayenne-*.xml`. `dataMap` is a logical name (e.g. `mydb`), 
not a path to `mydb.map.xml`.
diff --git a/cayenne-mcp-server/PROTOCOL.md b/cayenne-mcp-server/PROTOCOL.md
index b5d78d764..3d3e7b4e9 100644
--- a/cayenne-mcp-server/PROTOCOL.md
+++ b/cayenne-mcp-server/PROTOCOL.md
@@ -9,7 +9,7 @@ A result always carries:
 
 ## `cgen_run`
 
-Runs Cayenne class generation for a named DataMap.
+Runs Cayenne class generation for a named DataMap. If the DataMap has an 
embedded `<cgen>` block it is used; otherwise the tool synthesizes a default 
config (all entities and embeddables, `makePairs=true`, destination derived 
from the Maven layout) and generates anyway. A missing `<cgen>` block is not an 
error.
 
 ### Input
 
@@ -50,7 +50,7 @@ Both fields are required.
     "destDirWritable":   true
   },
   "error": {
-    "code":    "project_not_found | project_parse_failed | datamap_not_found | 
cgen_config_missing | destdir_not_specified | destdir_not_writable | 
cgen_runtime_error",
+    "code":    "project_not_found | project_parse_failed | datamap_not_found | 
destdir_not_specified | destdir_not_writable | cgen_runtime_error",
     "message": "string"
   }
 }
diff --git 
a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/CgenRunTool.java
 
b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/CgenRunTool.java
index 4b027f81b..bbf45f51f 100644
--- 
a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/CgenRunTool.java
+++ 
b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/CgenRunTool.java
@@ -28,6 +28,7 @@ import org.apache.cayenne.gen.CgenConfigList;
 import org.apache.cayenne.gen.CgenConfiguration;
 import org.apache.cayenne.gen.MetadataUtils;
 import org.apache.cayenne.gen.ToolsUtilsFactory;
+import org.apache.cayenne.gen.internal.Utils;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.mcp.project.McpProjectLoaderModule;
 import org.apache.cayenne.mcp.log.McpLoggingHandler;
@@ -157,16 +158,15 @@ public class CgenRunTool {
                     new CgenValidation(true, false, null, null, null));
         }
 
-        // Step 4 — cgen configuration present?
+        // Step 4 — cgen configuration: use the <cgen> block stored in the 
DataMap, or, if there is none,
+        // synthesize a default. This mirrors CayenneModeler and the 
Maven/Gradle plugins, all of which
+        // fall back to a default config rather than failing — a missing 
<cgen> block is not an error.
         DataChannelMetaData metaData = 
injector.getInstance(DataChannelMetaData.class);
         CgenConfigList configList = metaData.get(dataMap, 
CgenConfigList.class);
-        if (configList == null || configList.getAll().isEmpty()) {
-            return validationFailed(CgenErrorCode.cgen_config_missing,
-                    "DataMap '" + dataMapName + "' has no <cgen> 
configuration. "
-                            + "Configure code generation for this DataMap in 
CayenneModeler before invoking the tool.",
-                    new CgenValidation(true, true, false, null, null));
-        }
-        CgenConfiguration cgenConfig = configList.getAll().getFirst();
+        boolean usedDefaultConfig = configList == null || 
configList.getAll().isEmpty();
+        CgenConfiguration cgenConfig = usedDefaultConfig
+                ? defaultConfig(dataMap)
+                : configList.getAll().getFirst();
 
         // Set the DataMap file's mtime as the timestamp so fileNeedUpdate() 
can detect DataMap changes correctly.
         if (dataMap.getConfigurationSource() != null) {
@@ -246,6 +246,26 @@ public class CgenRunTool {
         );
     }
 
+    /**
+     * Builds a default cgen configuration for a DataMap that has no embedded 
{@code <cgen>} block.
+     * Mirrors {@code CgenPanel.createDefaultCgenConfiguration} in 
CayenneModeler: generate every
+     * entity and embeddable, with the destination derived from the standard 
Maven source layout
+     * ({@code src/main/resources} → {@code src/main/java}, likewise for 
{@code test}). For projects
+     * that don't follow the Maven layout the destination falls back to the 
DataMap's own directory.
+     */
+    private static CgenConfiguration defaultConfig(DataMap dataMap) {
+        Path mapDir = Utils.getRootPathForDataMap(dataMap);
+        Path outputPath = 
Utils.getMavenSrcPathForPath(mapDir).map(Path::of).orElse(mapDir);
+
+        CgenConfiguration config = new CgenConfiguration();
+        config.setDataMap(dataMap);
+        dataMap.getObjEntities().forEach(config::loadEntity);
+        dataMap.getEmbeddables().forEach(config::loadEmbeddable);
+        config.setRootPath(mapDir);
+        config.updateOutputPath(outputPath);
+        return config;
+    }
+
     private static CgenRunResult validationFailed(CgenErrorCode code, String 
message, CgenValidation validation) {
         return new CgenRunResult(
                 "validation_failed",
diff --git 
a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/protocol/CgenErrorCode.java
 
b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/protocol/CgenErrorCode.java
index e2fde135d..2beface80 100644
--- 
a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/protocol/CgenErrorCode.java
+++ 
b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/protocol/CgenErrorCode.java
@@ -28,7 +28,6 @@ public enum CgenErrorCode {
     project_not_found,
     project_parse_failed,
     datamap_not_found,
-    cgen_config_missing,
     destdir_not_specified,
     destdir_not_writable,
     cgen_runtime_error
diff --git 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunIT.java
 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunIT.java
index f02c14e15..3401918c2 100644
--- 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunIT.java
+++ 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunIT.java
@@ -142,6 +142,47 @@ public class CgenRunIT {
         assertTrue(second.summary().filesWritten() > 0);
     }
 
+    @Test
+    public void generatesWithDefaultConfigWhenNoCgenBlock() throws IOException 
{
+        // A DataMap with no <cgen> block, laid out in a standard Maven 
structure. The tool must
+        // synthesize a default config rather than failing, deriving destDir 
from src/main/resources
+        // → src/main/java.
+        Path resources = tempDir.resolve("src/main/resources");
+        Path mapFile = resources.resolve("DefaultMap.map.xml");
+        Files.createDirectories(resources);
+        Files.writeString(resources.resolve("cayenne-project.xml"), """
+                <?xml version="1.0" encoding="utf-8"?>
+                <domain xmlns="http://cayenne.apache.org/schema/12/domain"; 
project-version="12">
+                    <map name="DefaultMap"/>
+                </domain>
+                """);
+        Files.writeString(mapFile, """
+                <?xml version="1.0" encoding="utf-8"?>
+                <data-map xmlns="http://cayenne.apache.org/schema/12/modelMap";
+                          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                          project-version="12">
+                    <property name="defaultPackage" value="com.example"/>
+                    <obj-entity name="Person" className="com.example.Person"/>
+                </data-map>
+                """);
+
+        CgenRunResult result = 
tool.run(resources.resolve("cayenne-project.xml").toString(), "DefaultMap");
+
+        assertEquals("generated", result.status(), "Missing <cgen> block must 
not block generation");
+        assertNull(result.error());
+        assertNotNull(result.resolved());
+
+        // src/main/resources → src/main/java
+        Path expectedDest = tempDir.resolve("src/main/java");
+        assertEquals(expectedDest.toAbsolutePath().toString(), 
result.resolved().destDir());
+
+        // makePairs defaults to true → superclass under the ".auto" package + 
user subclass
+        
assertTrue(Files.exists(expectedDest.resolve("com/example/auto/_Person.java")),
+                "Expected generated superclass");
+        
assertTrue(Files.exists(expectedDest.resolve("com/example/Person.java")),
+                "Expected generated subclass");
+    }
+
     private Path writeFixture(String mapName, String pkg, Path destDir, 
boolean makePairs) throws IOException {
 
         // Project descriptor
diff --git 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunValidationTest.java
 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunValidationTest.java
index 716495dda..32318b67a 100644
--- 
a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunValidationTest.java
+++ 
b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunValidationTest.java
@@ -96,22 +96,6 @@ public class CgenRunValidationTest {
         assertNull(result.validation().destDirWritable());
     }
 
-    @Test
-    public void cgenConfigMissing() throws URISyntaxException {
-        String projectPath = fixtureProject("no-cgen");
-
-        CgenRunResult result = tool.run(projectPath, "TestMap");
-
-        assertEquals("validation_failed", result.status());
-        assertEquals(CgenErrorCode.cgen_config_missing, result.error().code());
-
-        assertTrue(result.validation().projectFound());
-        assertTrue(result.validation().dataMapFound());
-        assertFalse(result.validation().cgenConfigPresent());
-        assertNull(result.validation().destDirSpecified());
-        assertNull(result.validation().destDirWritable());
-    }
-
     @Test
     public void destDirNotSpecified() throws URISyntaxException {
         String projectPath = fixtureProject("no-destdir");

Reply via email to