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 <[email protected]> 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()); - } -}
