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/10 15:52:53 UTC
(cayenne) 02/02: CAY-2942 CayenneModeler MCP: cgen_run 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 029de077fe3d0d4ff7ea80ee2167e59a77aba78e
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun May 10 11:39:40 2026 -0400
CAY-2942 CayenneModeler MCP: cgen_run tool
removing hello tool now that we have a real one
cleanup
---
.../org/apache/cayenne/mcp/CayenneMcpServer.java | 3 +-
.../apache/cayenne/mcp/tools/hello/HelloTool.java | 51 ----------
.../org/apache/cayenne/mcp/InProcessMcpServer.java | 67 +++++++++++++
.../java/org/apache/cayenne/mcp/McpHandle.java | 42 --------
.../java/org/apache/cayenne/mcp/McpStarter.java | 27 -----
.../apache/cayenne/mcp/tools/cgen/CgenRunIT.java | 13 ++-
.../cayenne/mcp/tools/cgen/CgenRunMcpIT.java | 13 ++-
.../apache/cayenne/mcp/tools/hello/HelloMcpIT.java | 111 ---------------------
.../cayenne/mcp/tools/hello/HelloToolTest.java | 48 ---------
9 files changed, 82 insertions(+), 293 deletions(-)
diff --git a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/CayenneMcpServer.java b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/CayenneMcpServer.java
index 9178f6e78..90fe457c4 100644
--- a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/CayenneMcpServer.java
+++ b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/CayenneMcpServer.java
@@ -25,7 +25,6 @@ import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;
import org.apache.cayenne.mcp.tools.cgen.CgenRunTool;
-import org.apache.cayenne.mcp.tools.hello.HelloTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,7 +52,7 @@ public class CayenneMcpServer {
McpSyncServer server = McpServer.sync(transport)
.serverInfo("cayenne-mcp-server", version)
.capabilities(McpSchema.ServerCapabilities.builder().tools(true).build())
- .tools(HelloTool.spec(), CgenRunTool.spec(jsonMapper))
+ .tools(CgenRunTool.spec(jsonMapper))
.build();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
diff --git a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/hello/HelloTool.java b/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/hello/HelloTool.java
deleted file mode 100644
index 81f3756c7..000000000
--- a/cayenne-mcp-server/src/main/java/org/apache/cayenne/mcp/tools/hello/HelloTool.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.mcp.tools.hello;
-
-import io.modelcontextprotocol.server.McpServerFeatures;
-import io.modelcontextprotocol.spec.McpSchema;
-
-import java.util.List;
-
-/**
- * Placeholder tool that returns "hello world". Verifies the MCP wiring is functional.
- */
-public class HelloTool {
-
- public static final String NAME = "hello";
-
- public static McpServerFeatures.SyncToolSpecification spec() {
- McpSchema.Tool tool = new McpSchema.Tool(
- NAME,
- null,
- "Returns a greeting. Use this to verify the Cayenne MCP server is running.",
- new McpSchema.JsonSchema("object", null, null, null, null, null),
- null,
- null,
- null
- );
-
- return new McpServerFeatures.SyncToolSpecification(tool, (exchange, request) ->
- McpSchema.CallToolResult.builder()
- .content(List.of(new McpSchema.TextContent("hello world")))
- .isError(false)
- .build()
- );
- }
-}
diff --git a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/InProcessMcpServer.java b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/InProcessMcpServer.java
new file mode 100644
index 000000000..2fd346ca6
--- /dev/null
+++ b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/InProcessMcpServer.java
@@ -0,0 +1,67 @@
+package org.apache.cayenne.mcp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Handles the lifecycle of a test in-process MCP server.
+ */
+public class InProcessMcpServer {
+
+ private final OutputStream outputStream;
+ private final InputStream inputStream;
+ private final Thread serverThread;
+
+ public static InProcessMcpServer start() {
+ try {
+ PipedOutputStream clientOut = new PipedOutputStream();
+ PipedInputStream serverIn = new PipedInputStream(clientOut);
+ PipedOutputStream serverOut = new PipedOutputStream();
+ PipedInputStream clientIn = new PipedInputStream(serverOut);
+
+ Thread serverThread = new Thread(
+ () -> new CayenneMcpServer().run("test", serverIn, serverOut),
+ "mcp-server");
+ serverThread.setDaemon(true);
+ serverThread.start();
+
+ return new InProcessMcpServer(clientOut, clientIn, serverThread);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to start in-process MCP server", e);
+ }
+ }
+
+ private InProcessMcpServer(OutputStream outputStream, InputStream inputStream, Thread serverThread) {
+ this.outputStream = outputStream;
+ this.inputStream = inputStream;
+ this.serverThread = serverThread;
+ }
+
+ /**
+ * Stream the test writes JSON-RPC requests to (→ server stdin).
+ */
+ public OutputStream getOutputStream() {
+ return outputStream;
+ }
+
+ /**
+ * Stream the test reads JSON-RPC responses from (← server stdout).
+ */
+ public InputStream getInputStream() {
+ return inputStream;
+ }
+
+ /**
+ * Waits for the server thread to finish, returning {@code true} if it stops
+ * within {@code timeout}. Closing {@link #getOutputStream()} signals EOF to
+ * the server and causes it to shut down.
+ */
+ public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
+ serverThread.join(unit.toMillis(timeout));
+ return !serverThread.isAlive();
+ }
+}
diff --git a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/McpHandle.java b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/McpHandle.java
deleted file mode 100644
index 14df0a075..000000000
--- a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/McpHandle.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.apache.cayenne.mcp;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Handle to an in-process MCP server started by {@link McpStarter}.
- * Exposes the piped streams the test uses to send and receive JSON-RPC messages.
- */
-public class McpHandle {
-
- private final OutputStream outputStream;
- private final InputStream inputStream;
- private final Thread serverThread;
-
- McpHandle(OutputStream outputStream, InputStream inputStream, Thread serverThread) {
- this.outputStream = outputStream;
- this.inputStream = inputStream;
- this.serverThread = serverThread;
- }
-
- /** Stream the test writes JSON-RPC requests to (→ server stdin). */
- public OutputStream getOutputStream() {
- return outputStream;
- }
-
- /** Stream the test reads JSON-RPC responses from (← server stdout). */
- public InputStream getInputStream() {
- return inputStream;
- }
-
- /**
- * Waits for the server thread to finish, returning {@code true} if it stops
- * within {@code timeout}. Closing {@link #getOutputStream()} signals EOF to
- * the server and causes it to shut down.
- */
- public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException {
- serverThread.join(unit.toMillis(timeout));
- return !serverThread.isAlive();
- }
-}
diff --git a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/McpStarter.java b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/McpStarter.java
deleted file mode 100644
index 4cc6d8cbe..000000000
--- a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/McpStarter.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.apache.cayenne.mcp;
-
-import java.io.IOException;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-
-public class McpStarter {
-
- public static McpHandle start() {
- try {
- PipedOutputStream clientOut = new PipedOutputStream();
- PipedInputStream serverIn = new PipedInputStream(clientOut);
- PipedOutputStream serverOut = new PipedOutputStream();
- PipedInputStream clientIn = new PipedInputStream(serverOut);
-
- Thread serverThread = new Thread(
- () -> new CayenneMcpServer().run("test", serverIn, serverOut),
- "mcp-server");
- serverThread.setDaemon(true);
- serverThread.start();
-
- return new McpHandle(clientOut, clientIn, serverThread);
- } catch (IOException e) {
- throw new RuntimeException("Failed to start in-process MCP server", e);
- }
- }
-}
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 0243ca34d..687353a44 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
@@ -49,8 +49,7 @@ public class CgenRunIT {
void setUp() throws IOException {
tool = new CgenRunTool();
destDir = tempDir.resolve("generated");
- writeFixture("PersonMap", "com.example", destDir, true);
- projectFile = tempDir.resolve("cayenne-project.xml");
+ projectFile = writeFixture("PersonMap", "com.example", destDir, true);
}
@Test
@@ -117,10 +116,12 @@ public class CgenRunIT {
int skipped = result.summary().filesConsidered() - result.summary().filesWritten();
assertTrue(skipped >= 1, "At least one file (the existing subclass) should have been skipped");
}
-
- private void writeFixture(String mapName, String pkg, Path destDir, boolean makePairs) throws IOException {
+
+ private Path writeFixture(String mapName, String pkg, Path destDir, boolean makePairs) throws IOException {
+
// Project descriptor
- Files.writeString(tempDir.resolve("cayenne-project.xml"), String.format("""
+ Path projectDescriptor = tempDir.resolve("cayenne-project.xml");
+ Files.writeString(projectDescriptor, String.format("""
<?xml version="1.0" encoding="utf-8"?>
<domain xmlns="http://cayenne.apache.org/schema/12/domain" project-version="12">
<map name="%s"/>
@@ -144,5 +145,7 @@ public class CgenRunIT {
</cgen>
</data-map>
""", pkg, pkg, destDir.toAbsolutePath(), makePairs));
+
+ return projectDescriptor;
}
}
diff --git a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunMcpIT.java b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunMcpIT.java
index b2a58fa56..225a29889 100644
--- a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunMcpIT.java
+++ b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/cgen/CgenRunMcpIT.java
@@ -18,8 +18,7 @@
****************************************************************/
package org.apache.cayenne.mcp.tools.cgen;
-import org.apache.cayenne.mcp.McpHandle;
-import org.apache.cayenne.mcp.McpStarter;
+import org.apache.cayenne.mcp.InProcessMcpServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -52,7 +51,7 @@ public class CgenRunMcpIT {
@TempDir
Path tempDir;
- private McpHandle handle;
+ private InProcessMcpServer server;
private BufferedWriter writer;
private BufferedReader reader;
private Path projectFile;
@@ -64,9 +63,9 @@ public class CgenRunMcpIT {
writeFixture("PersonMap", "com.example", destDir, true);
projectFile = tempDir.resolve("cayenne-project.xml");
- handle = McpStarter.start();
- writer = new BufferedWriter(new OutputStreamWriter(handle.getOutputStream()));
- reader = new BufferedReader(new InputStreamReader(handle.getInputStream()));
+ server = InProcessMcpServer.start();
+ writer = new BufferedWriter(new OutputStreamWriter(server.getOutputStream()));
+ reader = new BufferedReader(new InputStreamReader(server.getInputStream()));
send("""
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{\
@@ -83,7 +82,7 @@ public class CgenRunMcpIT {
@AfterEach
void stopServer() throws Exception {
writer.close();
- assertTrue(handle.waitFor(10, TimeUnit.SECONDS), "Server thread did not stop after stdin was closed");
+ assertTrue(server.waitFor(10, TimeUnit.SECONDS), "Server thread did not stop after stdin was closed");
}
@Test
diff --git a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/hello/HelloMcpIT.java b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/hello/HelloMcpIT.java
deleted file mode 100644
index eaeab552e..000000000
--- a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/hello/HelloMcpIT.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.mcp.tools.hello;
-
-import org.apache.cayenne.mcp.McpHandle;
-import org.apache.cayenne.mcp.McpStarter;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Integration tests for the hello tool over the MCP stdio protocol.
- * Requires the shaded jar to be built before this test runs (failsafe executes after package).
- */
-public class HelloMcpIT {
-
- private McpHandle handle;
- private BufferedWriter writer;
- private BufferedReader reader;
-
- @BeforeEach
- void startServer() throws Exception {
- handle = McpStarter.start();
- writer = new BufferedWriter(new OutputStreamWriter(handle.getOutputStream()));
- reader = new BufferedReader(new InputStreamReader(handle.getInputStream()));
-
- sendMessage("""
- {"jsonrpc":"2.0","id":1,"method":"initialize","params":{\
- "protocolVersion":"2024-11-05",\
- "capabilities":{},\
- "clientInfo":{"name":"test","version":"1.0"}}}""");
- String initResponse = readLine();
- assertTrue(initResponse.contains("\"id\":1"), "initialize response missing: " + initResponse);
-
- sendMessage("""
- {"jsonrpc":"2.0","method":"notifications/initialized","params":{}}""");
- }
-
- @AfterEach
- void stopServer() throws Exception {
- writer.close();
- assertTrue(handle.waitFor(10, TimeUnit.SECONDS), "Server thread did not stop after stdin was closed");
- }
-
- @Test
- public void toolsListIncludesHello() throws Exception {
- sendMessage("""
- {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}""");
-
- String listResponse = readLine();
- assertTrue(listResponse.contains("\"hello\""), "tools/list missing 'hello' tool: " + listResponse);
- }
-
- @Test
- public void helloToolReturnsGreeting() throws Exception {
- sendMessage("""
- {"jsonrpc":"2.0","id":3,"method":"tools/call",\
- "params":{"name":"hello","arguments":{}}}""");
-
- String callResponse = readLine();
- assertTrue(callResponse.contains("hello world"), "tools/call missing 'hello world': " + callResponse);
- assertTrue(callResponse.contains("\"id\":3"), "tools/call response id mismatch: " + callResponse);
- }
-
- private void sendMessage(String json) throws IOException {
- writer.write(json);
- writer.newLine();
- writer.flush();
- }
-
- private String readLine() throws Exception {
- long deadline = System.currentTimeMillis() + 5000L;
- while (System.currentTimeMillis() < deadline) {
- if (reader.ready()) {
- String line = reader.readLine();
- if (line != null && !line.isBlank()) {
- return line;
- }
- }
- Thread.sleep(20);
- }
- throw new AssertionError("No response within " + 5000L + "ms");
- }
-
-
-}
diff --git a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/hello/HelloToolTest.java b/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/hello/HelloToolTest.java
deleted file mode 100644
index a3a993334..000000000
--- a/cayenne-mcp-server/src/test/java/org/apache/cayenne/mcp/tools/hello/HelloToolTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ****************************************************************/
-package org.apache.cayenne.mcp.tools.hello;
-
-import io.modelcontextprotocol.server.McpServerFeatures;
-import io.modelcontextprotocol.spec.McpSchema;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-
-public class HelloToolTest {
-
- @Test
- public void toolNameIsHello() {
- McpServerFeatures.SyncToolSpecification spec = HelloTool.spec();
- assertEquals("hello", spec.tool().name());
- }
-
- @Test
- public void callReturnsHelloWorld() {
- McpServerFeatures.SyncToolSpecification spec = HelloTool.spec();
- McpSchema.CallToolResult result = spec.callHandler().apply(null, null);
-
- assertFalse(result.isError());
- assertEquals(1, result.content().size());
-
- McpSchema.TextContent first = assertInstanceOf(McpSchema.TextContent.class, result.content().get(0));
- assertEquals("hello world", first.text());
- }
-}