You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/05/24 21:26:45 UTC
(cayenne) 02/06: CAY-2943 CayenneModeler MCP: open_project tool
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 ac2b74e9439b28d1654b03884992ec42ded43482
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun May 24 15:39:49 2026 -0400
CAY-2943 CayenneModeler MCP: open_project tool
do not attempt to deal with sourtce trees, only locate the Modeler within distributions
---
.../mcp/tools/openproject/McpJarLocator.java | 41 +---------
.../mcp/tools/openproject/ModelerDiscovery.java | 92 ----------------------
.../mcp/tools/openproject/OpenProjectTool.java | 58 ++++++++++----
.../protocol/OpenProjectDistribution.java | 3 +-
.../tools/openproject/ModelerDiscoveryTest.java | 51 ++----------
5 files changed, 51 insertions(+), 194 deletions(-)
diff --git a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/McpJarLocator.java b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/McpJarLocator.java
index 3d0902eac..4bdaa664b 100644
--- a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/McpJarLocator.java
+++ b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/McpJarLocator.java
@@ -25,7 +25,6 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.security.ProtectionDomain;
-import java.util.Optional;
/**
* Resolves the directory that contains the running MCP server jar. Used as the
@@ -35,46 +34,12 @@ import java.util.Optional;
*/
final class McpJarLocator {
- /** Canonical name of the MCP server jar in a distribution (outside of dev builds). */
- static final String MCP_JAR_NAME = "CayenneMCPServer.jar";
-
- record Located(Path dir, boolean isDistribution) {}
-
private McpJarLocator() {
}
/**
- * Locates the directory containing the jar (or class output dir) the given class
- * was loaded from, and whether that jar is the canonical distribution jar
- * ({@value #MCP_JAR_NAME}). Returns {@code Optional.empty()} in exotic launch
- * configurations where the protection domain has no resolvable location.
+ * Returns the directory containing the jar the given class was loaded from, or {@code null} in exotic launch configurations where the
+ * protection domain has no resolvable location.
*/
- static Optional<Located> locate(Class<?> anchor) {
- try {
- ProtectionDomain pd = anchor.getProtectionDomain();
- if (pd == null) {
- return Optional.empty();
- }
- CodeSource cs = pd.getCodeSource();
- if (cs == null) {
- return Optional.empty();
- }
- URL url = cs.getLocation();
- if (url == null) {
- return Optional.empty();
- }
- Path location = Paths.get(url.toURI());
- // For a jar: location is the jar itself; we want its parent directory.
- // For a class-file directory (IDE / surefire fork): location is the dir;
- // its parent is also a reasonable starting point (target/), but here we
- // want the dir that "would have been the jar's parent" — so use it directly.
- boolean isDistribution = Files.isRegularFile(location)
- && MCP_JAR_NAME.equals(location.getFileName() != null
- ? location.getFileName().toString() : "");
- Path dir = location.getParent() != null ? location.getParent() : location;
- return Optional.of(new Located(dir, isDistribution));
- } catch (URISyntaxException | RuntimeException e) {
- return Optional.empty();
- }
- }
+
}
diff --git a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/ModelerDiscovery.java b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/ModelerDiscovery.java
index 56a3b8590..c8ae42fe0 100644
--- a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/ModelerDiscovery.java
+++ b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/ModelerDiscovery.java
@@ -40,9 +40,6 @@ final class ModelerDiscovery {
static final String WINDOWS_EXE_NAME = "CayenneModeler.exe";
static final String GENERIC_JAR_NAME = "CayenneModeler.jar";
- /** Max levels to climb when locating the source-tree git root. */
- private static final int SOURCE_TREE_CLIMB_LIMIT = 8;
-
sealed interface DiscoveryResult permits Found, NotFound {}
record Found(OpenProjectDistribution distribution, LauncherKind launcherKind, Path launcher)
@@ -54,16 +51,6 @@ final class ModelerDiscovery {
}
static DiscoveryResult discover(Path mcpDir, OsKind osKind) {
- return discover(mcpDir, osKind, false);
- }
-
- /**
- * @param isDistributionJar {@code true} when the MCP server is running from
- * {@value McpJarLocator#MCP_JAR_NAME} — a distribution build. When {@code true},
- * the source-tree fallback is skipped: a production jar should not accidentally
- * pick up a developer Modeler build.
- */
- static DiscoveryResult discover(Path mcpDir, OsKind osKind, boolean isDistributionJar) {
List<String> notes = new ArrayList<>();
if (osKind == OsKind.MAC) {
@@ -88,16 +75,6 @@ final class ModelerDiscovery {
}
notes.add("generic: no " + GENERIC_JAR_NAME + " sibling of the MCP jar");
- if (!isDistributionJar) {
- Optional<Found> sourceTree = probeSourceTree(mcpDir, osKind);
- if (sourceTree.isPresent()) {
- return sourceTree.get();
- }
- notes.add("""
- source_tree: no built CayenneModeler under <gitRoot>/modeler/cayenne-modeler-{mac,win,generic}/target/classes/ \
- (run `mvn -pl modeler/cayenne-modeler-<kind> -am package -P<kind>`)""");
- }
-
return new NotFound(List.copyOf(notes));
}
@@ -151,73 +128,4 @@ final class ModelerDiscovery {
return Files.isRegularFile(candidate) ? Optional.of(candidate) : Optional.empty();
}
- /**
- * Source-tree probe — climbs to a {@code .git}-bearing root and looks under
- * {@code modeler/cayenne-modeler-{mac,win,generic}/target/classes/} for the
- * OS-appropriate launcher. The reported distribution is {@code source_tree}
- * so callers can see when dev-mode discovery kicked in; the launcher kind
- * still drives argv construction.
- */
- static Optional<Found> probeSourceTree(Path mcpDir, OsKind osKind) {
- Path gitRoot = findGitRoot(mcpDir);
- if (gitRoot == null) {
- return Optional.empty();
- }
-
- if (osKind == OsKind.MAC) {
- Path macClasses = gitRoot.resolve("modeler/cayenne-modeler-mac/target/classes");
- Optional<Path> bundle = findAppBundle(macClasses);
- if (bundle.isPresent()) {
- return Optional.of(new Found(
- OpenProjectDistribution.source_tree, LauncherKind.MAC_APP, bundle.get()));
- }
- }
-
- if (osKind == OsKind.WINDOWS) {
- Path winExe = gitRoot.resolve(
- "modeler/cayenne-modeler-win/target/classes/" + WINDOWS_EXE_NAME);
- if (Files.isRegularFile(winExe)) {
- return Optional.of(new Found(
- OpenProjectDistribution.source_tree, LauncherKind.WINDOWS_EXE, winExe));
- }
- }
-
- Path genericJar = gitRoot.resolve(
- "modeler/cayenne-modeler-generic/target/classes/" + GENERIC_JAR_NAME);
- if (Files.isRegularFile(genericJar)) {
- return Optional.of(new Found(
- OpenProjectDistribution.source_tree, LauncherKind.GENERIC_JAR, genericJar));
- }
-
- return Optional.empty();
- }
-
- private static Path findGitRoot(Path start) {
- if (start == null) {
- return null;
- }
- Path current = start;
- for (int i = 0; i < SOURCE_TREE_CLIMB_LIMIT && current != null; i++) {
- if (Files.isDirectory(current.resolve(".git"))) {
- return current;
- }
- current = current.getParent();
- }
- return null;
- }
-
- private static Optional<Path> findAppBundle(Path dir) {
- if (!Files.isDirectory(dir)) {
- return Optional.empty();
- }
- try (var stream = Files.list(dir)) {
- return stream
- .filter(p -> p.getFileName() != null
- && p.getFileName().toString().endsWith(".app"))
- .filter(p -> Files.isDirectory(p.resolve("Contents/MacOS")))
- .findFirst();
- } catch (java.io.IOException e) {
- return Optional.empty();
- }
- }
}
diff --git a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/OpenProjectTool.java b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/OpenProjectTool.java
index 82de61236..3f83620ba 100644
--- a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/OpenProjectTool.java
+++ b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/OpenProjectTool.java
@@ -37,12 +37,16 @@ import org.apache.cayenne.mcp.tools.openproject.protocol.OpenProjectValidation;
import org.apache.cayenne.modeler.pref.PrefsLocator;
import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.CodeSource;
+import java.security.ProtectionDomain;
import java.time.Duration;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.UUID;
/**
@@ -131,22 +135,21 @@ public class OpenProjectTool {
}
// Step 2 — locate the MCP jar's directory.
- Optional<McpJarLocator.Located> mcpLocated = McpJarLocator.locate(OpenProjectTool.class);
- if (mcpLocated.isEmpty()) {
+ Path mcpDir = mcpJarDir();
+ if (mcpDir == null) {
return validationFailed(OpenProjectErrorCode.mcp_jar_location_unresolved,
"Could not resolve the running MCP server jar's location",
new OpenProjectValidation(true, false, null));
}
- McpJarLocator.Located mcp = mcpLocated.get();
// Step 3 — discover a Modeler installation.
OsKind osKind = OsKind.detect();
- DiscoveryResult discovery = ModelerDiscovery.discover(mcp.dir(), osKind, mcp.isDistribution());
+ DiscoveryResult discovery = ModelerDiscovery.discover(mcpDir, osKind);
return switch (discovery) {
case Found f -> launchAndAwait(f, projectFile);
case NotFound nf -> validationFailed(OpenProjectErrorCode.modeler_not_found,
"No CayenneModeler installation found relative to MCP jar at %s. Probes: %s"
- .formatted(mcp.dir(), String.join("; ", nf.probeNotes())),
+ .formatted(mcpDir, String.join("; ", nf.probeNotes())),
new OpenProjectValidation(true, true, false));
};
}
@@ -214,20 +217,20 @@ public class OpenProjectTool {
"Spawned Modeler process exited before reporting handshake (%s)"
.formatted(exit)));
}
- case TIMEOUT -> {
- yield new OpenProjectResult(
- "error",
- resolved,
- allPassed,
- null,
- new OpenProjectError(OpenProjectErrorCode.launch_not_confirmed,
- "Handshake did not appear within %ds".formatted(HANDSHAKE_TIMEOUT.toSeconds())));
- }
+ case TIMEOUT -> new OpenProjectResult(
+ "error",
+ resolved,
+ allPassed,
+ null,
+ new OpenProjectError(OpenProjectErrorCode.launch_not_confirmed,
+ "Handshake did not appear within %ds".formatted(HANDSHAKE_TIMEOUT.toSeconds())));
};
}
- private static OpenProjectResult validationFailed(OpenProjectErrorCode code, String message,
- OpenProjectValidation validation) {
+ private static OpenProjectResult validationFailed(
+ OpenProjectErrorCode code,
+ String message,
+ OpenProjectValidation validation) {
return new OpenProjectResult(
"validation_failed",
null,
@@ -236,4 +239,25 @@ public class OpenProjectTool {
new OpenProjectError(code, message)
);
}
+
+ static Path mcpJarDir() {
+ try {
+ ProtectionDomain pd = OpenProjectTool.class.getProtectionDomain();
+ if (pd == null) {
+ return null;
+ }
+ CodeSource cs = pd.getCodeSource();
+ if (cs == null) {
+ return null;
+ }
+ URL url = cs.getLocation();
+ if (url == null) {
+ return null;
+ }
+ Path location = Paths.get(url.toURI());
+ return Files.isRegularFile(location) && location.getParent() != null ? location.getParent() : location;
+ } catch (URISyntaxException | RuntimeException e) {
+ return null;
+ }
+ }
}
diff --git a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/protocol/OpenProjectDistribution.java b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/protocol/OpenProjectDistribution.java
index 3f3eb0f19..9985d8f16 100644
--- a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/protocol/OpenProjectDistribution.java
+++ b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/openproject/protocol/OpenProjectDistribution.java
@@ -26,6 +26,5 @@ package org.apache.cayenne.mcp.tools.openproject.protocol;
public enum OpenProjectDistribution {
mac,
windows,
- generic,
- source_tree
+ generic
}
diff --git a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/openproject/ModelerDiscoveryTest.java b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/openproject/ModelerDiscoveryTest.java
index e4b133414..2f680e196 100644
--- a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/openproject/ModelerDiscoveryTest.java
+++ b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/openproject/ModelerDiscoveryTest.java
@@ -174,8 +174,8 @@ public class ModelerDiscoveryTest {
DiscoveryResult result = ModelerDiscovery.discover(tmp, OsKind.MAC);
NotFound nf = assertInstanceOf(NotFound.class, result);
- // Mac eligible probes: mac, generic, source_tree (3)
- assertEquals(3, nf.probeNotes().size());
+ // Mac eligible probes: mac, generic (2)
+ assertEquals(2, nf.probeNotes().size());
// None of the notes should mention .exe (Windows probe wasn't eligible).
assertTrue(nf.probeNotes().stream().noneMatch(n -> n.contains(".exe")));
}
@@ -199,47 +199,8 @@ public class ModelerDiscoveryTest {
DiscoveryResult result = ModelerDiscovery.discover(tmp, OsKind.OTHER);
NotFound nf = assertInstanceOf(NotFound.class, result);
- // OTHER eligible probes: generic, source_tree (2)
- assertEquals(2, nf.probeNotes().size());
- }
-
- // -------- Source-tree fallback --------
-
- @Test
- public void sourceTreeMatchesGenericLauncher(@TempDir Path tmp) throws IOException {
- Path root = tmp.resolve("repo");
- Files.createDirectories(root.resolve(".git"));
- Path genericClasses = root.resolve("modeler/cayenne-modeler-generic/target/classes");
- Files.createDirectories(genericClasses);
- Files.createFile(genericClasses.resolve(ModelerDiscovery.GENERIC_JAR_NAME));
-
- Path mcpDir = root.resolve("cayenne-mcp-server/target");
- Files.createDirectories(mcpDir);
-
- DiscoveryResult result = ModelerDiscovery.discover(mcpDir, OsKind.OTHER);
-
- Found found = assertInstanceOf(Found.class, result);
- assertEquals(OpenProjectDistribution.source_tree, found.distribution());
- assertEquals(LauncherKind.GENERIC_JAR, found.launcherKind());
- }
-
- @Test
- public void sourceTreeMatchesMacBundle(@TempDir Path tmp) throws IOException {
- Path root = tmp.resolve("repo");
- Files.createDirectories(root.resolve(".git"));
- Path macClasses = root.resolve("modeler/cayenne-modeler-mac/target/classes");
- Path bundle = macClasses.resolve("CayenneModeler.app");
- Files.createDirectories(bundle.resolve("Contents/MacOS"));
-
- Path mcpDir = root.resolve("cayenne-mcp-server/target");
- Files.createDirectories(mcpDir);
-
- DiscoveryResult result = ModelerDiscovery.discover(mcpDir, OsKind.MAC);
-
- Found found = assertInstanceOf(Found.class, result);
- assertEquals(OpenProjectDistribution.source_tree, found.distribution());
- assertEquals(LauncherKind.MAC_APP, found.launcherKind());
- assertEquals(bundle, found.launcher());
+ // OTHER eligible probes: generic (1)
+ assertEquals(1, nf.probeNotes().size());
}
@Test
@@ -247,8 +208,8 @@ public class ModelerDiscoveryTest {
DiscoveryResult result = ModelerDiscovery.discover(tmp, OsKind.OTHER);
NotFound nf = assertInstanceOf(NotFound.class, result);
- assertEquals(2, nf.probeNotes().size(),
- "OTHER has 2 eligible probes: generic and source_tree");
+ assertEquals(1, nf.probeNotes().size(),
+ "OTHER has 1 eligible probe: generic");
}
// -------- Helpers --------