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 <[email protected]> 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 --------
