Skip to content

Commit f5c97f3

Browse files
committed
CAY-2965 MCP Cgen should not fail on an absent "<cgen>" tag
1 parent c063ee1 commit f5c97f3

9 files changed

Lines changed: 86 additions & 38 deletions

File tree

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ CAY-2959 Modeler: DbRelationship dialog "Cancel" doesn't cancel
2626
CAY-2960 Undoing renamed relationship change throws
2727
CAY-2961 PostgreSQL "text" column is reverse-engineered as CLOB
2828
CAY-2964 ClassCastException for non-generated meaningful PKs
29+
CAY-2965 MCP Cgen should not fail on an absent "<cgen>" tag
2930

3031
----------------------------------
3132
Release: 5.0-M2

ai-plugin/references/cgen-config.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,13 @@ The MCP tool:
106106

107107
1. Loads the project at `projectPath`.
108108
2. Finds the named DataMap (`dataMap` argument).
109-
3. Reads its embedded `<cgen>` block.
109+
3. Reads its embedded `<cgen>` block — or, if there is none, synthesizes a default config (see below).
110110
4. Resolves `destDir` relative to the `*.map.xml` file.
111111
5. Runs the generator. Returns a JSON object listing files `written`, files `skipped` (already up-to-date), and any `errors`.
112112

113-
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.
113+
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.
114+
115+
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.
114116

115117
## Determining `destDir`
116118

ai-plugin/references/mcp-tools.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Verify the server is registered with `claude mcp list` — you should see an ent
3232

3333
## Tool: `cgen_run`
3434

35-
Runs Cayenne's class generator for one DataMap, using the `<cgen>` config block embedded in that DataMap.
35+
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.
3636

3737
**Arguments:**
3838

@@ -47,7 +47,7 @@ Runs Cayenne's class generator for one DataMap, using the `<cgen>` config block
4747
{
4848
"status": "ok" | "error",
4949
"summary": { "writtenCount": 3, "skippedCount": 12, "errorCount": 0 },
50-
"resolvedConfig": { "destDir": "...", "mode": "entity", ... },
50+
"resolvedConfig": { "destDir": "..." },
5151
"writtenFiles": [{ "path": "...", "size": 1234 }],
5252
"skippedFiles": [{ "path": "...", "reason": "up-to-date" }],
5353
"errors": []
@@ -56,11 +56,13 @@ Runs Cayenne's class generator for one DataMap, using the `<cgen>` config block
5656

5757
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.
5858

59+
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.
60+
5961
**Failure modes:**
6062

61-
- DataMap has no `<cgen>` block → error; user must add one (see `cgen-config.md` for the XML to insert).
6263
- `projectPath` not readable or not a valid Cayenne project → validation error.
6364
- `dataMap` doesn't match any `<map name="...">` → validation error.
65+
- `<cgen>` block present but specifies no resolvable destination directory → `destdir_not_specified`.
6466

6567
**Source:** `cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/CgenRunTool.java`.
6668

ai-plugin/skills/cayenne-cgen/SKILL.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ description: "Use this skill whenever the user wants to (re)generate Cayenne ent
2323
-->
2424
# cayenne-cgen
2525

26-
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.
26+
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.
2727

2828
## Required reading
2929

@@ -51,11 +51,9 @@ mcp__cayenne__cgen_run({
5151
})
5252
```
5353

54-
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.
55-
56-
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`
54+
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.
5755

58-
If the `destDir` can't be determined reliably (non-standard project layout, empty project, etc.), conform it with the user
56+
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.
5957

6058
## Step 3 — Surface the result
6159

@@ -64,6 +62,7 @@ The tool returns structured JSON. Report:
6462
- `summary.writtenCount`, `summary.skippedCount`, `summary.errorCount` verbatim — these are the headline.
6563
- The first few entries in `writtenFiles` (relative paths). Full list is informational; offer to dump it if the user asks.
6664
- 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.).
65+
- `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.
6766

6867
If `writtenCount` is 0 and `skippedCount` covers everything, say so — it means everything is already up-to-date and no work was needed.
6968

@@ -74,7 +73,7 @@ If `writtenCount` is 0 and `skippedCount` covers everything, say so — it means
7473

7574
## Anti-patterns
7675

77-
- **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.
76+
- **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.
7877
- **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.
7978
- **Do not** edit `_<Entity>.java` files. Generated superclasses. Edit `<Entity>.java` subclasses.
8079
- **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`.

cayenne-mcp-server/PROTOCOL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ A result always carries:
99

1010
## `cgen_run`
1111

12-
Runs Cayenne class generation for a named DataMap.
12+
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.
1313

1414
### Input
1515

@@ -50,7 +50,7 @@ Both fields are required.
5050
"destDirWritable": true
5151
},
5252
"error": {
53-
"code": "project_not_found | project_parse_failed | datamap_not_found | cgen_config_missing | destdir_not_specified | destdir_not_writable | cgen_runtime_error",
53+
"code": "project_not_found | project_parse_failed | datamap_not_found | destdir_not_specified | destdir_not_writable | cgen_runtime_error",
5454
"message": "string"
5555
}
5656
}

cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/CgenRunTool.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.cayenne.gen.CgenConfiguration;
2929
import org.apache.cayenne.gen.MetadataUtils;
3030
import org.apache.cayenne.gen.ToolsUtilsFactory;
31+
import org.apache.cayenne.gen.internal.Utils;
3132
import org.apache.cayenne.map.DataMap;
3233
import org.apache.cayenne.mcp.project.McpProjectLoaderModule;
3334
import org.apache.cayenne.mcp.log.McpLoggingHandler;
@@ -157,16 +158,15 @@ public CgenRunResult run(String projectPath, String dataMapName) {
157158
new CgenValidation(true, false, null, null, null));
158159
}
159160

160-
// Step 4 — cgen configuration present?
161+
// Step 4 — cgen configuration: use the <cgen> block stored in the DataMap, or, if there is none,
162+
// synthesize a default. This mirrors CayenneModeler and the Maven/Gradle plugins, all of which
163+
// fall back to a default config rather than failing — a missing <cgen> block is not an error.
161164
DataChannelMetaData metaData = injector.getInstance(DataChannelMetaData.class);
162165
CgenConfigList configList = metaData.get(dataMap, CgenConfigList.class);
163-
if (configList == null || configList.getAll().isEmpty()) {
164-
return validationFailed(CgenErrorCode.cgen_config_missing,
165-
"DataMap '" + dataMapName + "' has no <cgen> configuration. "
166-
+ "Configure code generation for this DataMap in CayenneModeler before invoking the tool.",
167-
new CgenValidation(true, true, false, null, null));
168-
}
169-
CgenConfiguration cgenConfig = configList.getAll().getFirst();
166+
boolean usedDefaultConfig = configList == null || configList.getAll().isEmpty();
167+
CgenConfiguration cgenConfig = usedDefaultConfig
168+
? defaultConfig(dataMap)
169+
: configList.getAll().getFirst();
170170

171171
// Set the DataMap file's mtime as the timestamp so fileNeedUpdate() can detect DataMap changes correctly.
172172
if (dataMap.getConfigurationSource() != null) {
@@ -246,6 +246,26 @@ public CgenRunResult run(String projectPath, String dataMapName) {
246246
);
247247
}
248248

249+
/**
250+
* Builds a default cgen configuration for a DataMap that has no embedded {@code <cgen>} block.
251+
* Mirrors {@code CgenPanel.createDefaultCgenConfiguration} in CayenneModeler: generate every
252+
* entity and embeddable, with the destination derived from the standard Maven source layout
253+
* ({@code src/main/resources} → {@code src/main/java}, likewise for {@code test}). For projects
254+
* that don't follow the Maven layout the destination falls back to the DataMap's own directory.
255+
*/
256+
private static CgenConfiguration defaultConfig(DataMap dataMap) {
257+
Path mapDir = Utils.getRootPathForDataMap(dataMap);
258+
Path outputPath = Utils.getMavenSrcPathForPath(mapDir).map(Path::of).orElse(mapDir);
259+
260+
CgenConfiguration config = new CgenConfiguration();
261+
config.setDataMap(dataMap);
262+
dataMap.getObjEntities().forEach(config::loadEntity);
263+
dataMap.getEmbeddables().forEach(config::loadEmbeddable);
264+
config.setRootPath(mapDir);
265+
config.updateOutputPath(outputPath);
266+
return config;
267+
}
268+
249269
private static CgenRunResult validationFailed(CgenErrorCode code, String message, CgenValidation validation) {
250270
return new CgenRunResult(
251271
"validation_failed",

cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/cgen/protocol/CgenErrorCode.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public enum CgenErrorCode {
2828
project_not_found,
2929
project_parse_failed,
3030
datamap_not_found,
31-
cgen_config_missing,
3231
destdir_not_specified,
3332
destdir_not_writable,
3433
cgen_runtime_error

cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunIT.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,47 @@ public void regeneratesAfterDataMapChange() throws IOException {
142142
assertTrue(second.summary().filesWritten() > 0);
143143
}
144144

145+
@Test
146+
public void generatesWithDefaultConfigWhenNoCgenBlock() throws IOException {
147+
// A DataMap with no <cgen> block, laid out in a standard Maven structure. The tool must
148+
// synthesize a default config rather than failing, deriving destDir from src/main/resources
149+
// → src/main/java.
150+
Path resources = tempDir.resolve("src/main/resources");
151+
Path mapFile = resources.resolve("DefaultMap.map.xml");
152+
Files.createDirectories(resources);
153+
Files.writeString(resources.resolve("cayenne-project.xml"), """
154+
<?xml version="1.0" encoding="utf-8"?>
155+
<domain xmlns="http://cayenne.apache.org/schema/12/domain" project-version="12">
156+
<map name="DefaultMap"/>
157+
</domain>
158+
""");
159+
Files.writeString(mapFile, """
160+
<?xml version="1.0" encoding="utf-8"?>
161+
<data-map xmlns="http://cayenne.apache.org/schema/12/modelMap"
162+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
163+
project-version="12">
164+
<property name="defaultPackage" value="com.example"/>
165+
<obj-entity name="Person" className="com.example.Person"/>
166+
</data-map>
167+
""");
168+
169+
CgenRunResult result = tool.run(resources.resolve("cayenne-project.xml").toString(), "DefaultMap");
170+
171+
assertEquals("generated", result.status(), "Missing <cgen> block must not block generation");
172+
assertNull(result.error());
173+
assertNotNull(result.resolved());
174+
175+
// src/main/resources → src/main/java
176+
Path expectedDest = tempDir.resolve("src/main/java");
177+
assertEquals(expectedDest.toAbsolutePath().toString(), result.resolved().destDir());
178+
179+
// makePairs defaults to true → superclass under the ".auto" package + user subclass
180+
assertTrue(Files.exists(expectedDest.resolve("com/example/auto/_Person.java")),
181+
"Expected generated superclass");
182+
assertTrue(Files.exists(expectedDest.resolve("com/example/Person.java")),
183+
"Expected generated subclass");
184+
}
185+
145186
private Path writeFixture(String mapName, String pkg, Path destDir, boolean makePairs) throws IOException {
146187

147188
// Project descriptor

cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunValidationTest.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,22 +96,6 @@ public void dataMapNotFound() throws URISyntaxException {
9696
assertNull(result.validation().destDirWritable());
9797
}
9898

99-
@Test
100-
public void cgenConfigMissing() throws URISyntaxException {
101-
String projectPath = fixtureProject("no-cgen");
102-
103-
CgenRunResult result = tool.run(projectPath, "TestMap");
104-
105-
assertEquals("validation_failed", result.status());
106-
assertEquals(CgenErrorCode.cgen_config_missing, result.error().code());
107-
108-
assertTrue(result.validation().projectFound());
109-
assertTrue(result.validation().dataMapFound());
110-
assertFalse(result.validation().cgenConfigPresent());
111-
assertNull(result.validation().destDirSpecified());
112-
assertNull(result.validation().destDirWritable());
113-
}
114-
11599
@Test
116100
public void destDirNotSpecified() throws URISyntaxException {
117101
String projectPath = fixtureProject("no-destdir");

0 commit comments

Comments
 (0)