diff --git a/mcp-server-chat2mysql/chat2mysql-commons/pom.xml b/mcp-server-chat2mysql/chat2mysql-commons/pom.xml index 8c1b2a4..4d27368 100644 --- a/mcp-server-chat2mysql/chat2mysql-commons/pom.xml +++ b/mcp-server-chat2mysql/chat2mysql-commons/pom.xml @@ -13,12 +13,4 @@ chat2mysql-commons jar - - 17 - ${java.version} - ${java.version} - UTF-8 - UTF-8 - - diff --git a/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/pom.xml b/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/pom.xml index 3f75cf9..27b3d5d 100644 --- a/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/pom.xml +++ b/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/pom.xml @@ -13,19 +13,31 @@ chat2mysql-declarative-sdk-example jar - - 17 - ${java.version} - ${java.version} - UTF-8 - UTF-8 - - com.github.codeboyzhou chat2mysql-commons + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-inline + test + + + org.mockito + mockito-junit-jupiter + test + + + org.slf4j + slf4j-simple + test + diff --git a/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpStdioServerTest.java b/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpStdioServerTest.java new file mode 100644 index 0000000..73557e6 --- /dev/null +++ b/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpStdioServerTest.java @@ -0,0 +1,27 @@ +package com.github.mcp.server.chat2mysql; + +import com.github.codeboyzhou.mcp.declarative.annotation.McpComponentScan; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class McpStdioServerTest { + + @Test + void classIsAnnotatedWithMcpComponentScan() { + Class cls = McpStdioServer.class; + assertTrue(cls.isAnnotationPresent(McpComponentScan.class), + "McpStdioServer should be annotated with @McpComponentScan"); + + McpComponentScan ann = cls.getAnnotation(McpComponentScan.class); + assertEquals(McpStdioServer.class, ann.basePackageClass(), + "@McpComponentScan.basePackageClass should point to McpStdioServer.class"); + } + + @Test + void mainHandlesNoArgsWithoutException() { + // ensure main method does not throw when called with empty args + assertDoesNotThrow(() -> McpStdioServer.main(new String[0]), + "Calling main with no arguments should not throw an exception"); + } +} diff --git a/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/MyMcpPromptsTest.java b/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/MyMcpPromptsTest.java new file mode 100644 index 0000000..a4825e5 --- /dev/null +++ b/mcp-server-chat2mysql/chat2mysql-declarative-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/MyMcpPromptsTest.java @@ -0,0 +1,50 @@ +package com.github.mcp.server.chat2mysql; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import com.github.mcp.server.chat2mysql.util.SqlHelper; +import java.util.Set; + +class MyMcpPromptsTest { + + @Test + void generateSqlOptimizationTips_includesSqlAndPromptHeader() { + String sql = "SELECT * FROM users WHERE id = 1;"; + try (MockedStatic helper = Mockito.mockStatic(SqlHelper.class)) { + helper.when(() -> SqlHelper.parseTableNames(sql)) + .thenReturn(Set.of("users")); + helper.when(() -> SqlHelper.showCreateTable("users")) + .thenReturn("CREATE TABLE users (id INT PRIMARY KEY);"); + helper.when(() -> SqlHelper.explainSql(sql)) + .thenReturn("id | select_type | table | type | possible_keys | key | rows | Extra\n1 | SIMPLE | users | ALL | NULL | NULL | 1 | "); + + String prompt = MyMcpPrompts.generateSqlOptimizationTips(sql); + + // verify the prompt is not null or empty + assertNotNull(prompt, "Prompt should not be null"); + assertFalse(prompt.isBlank(), "Prompt should not be blank"); + + // verify it includes the SQL statement + assertTrue(prompt.contains("The SQL statement is: " + sql), + "Prompt should include the SQL statement"); + + // verify it includes the explain plan section label and content + assertTrue(prompt.contains("The EXPLAIN plan for the SQL statement is:"), + "Prompt should include the EXPLAIN plan section"); + assertTrue(prompt.contains("id | select_type"), + "Prompt should include the mocked EXPLAIN plan content"); + + // verify it includes the mocked table schema + assertTrue(prompt.contains("The table schema for users is: CREATE TABLE users"), + "Prompt should include the mocked table schema content"); + + // verify it ends with the language-specific message ending + String ending = com.github.mcp.server.chat2mysql.enums.PromptMessageEnding.ofCurrentUserLanguage(); + assertTrue(prompt.endsWith(ending), + "Prompt should end with the language-specific message ending"); + } + } +} diff --git a/mcp-server-chat2mysql/chat2mysql-native-sdk-example/pom.xml b/mcp-server-chat2mysql/chat2mysql-native-sdk-example/pom.xml index ac87b88..85e634a 100644 --- a/mcp-server-chat2mysql/chat2mysql-native-sdk-example/pom.xml +++ b/mcp-server-chat2mysql/chat2mysql-native-sdk-example/pom.xml @@ -13,19 +13,31 @@ chat2mysql-native-sdk-example jar - - 17 - ${java.version} - ${java.version} - UTF-8 - UTF-8 - - com.github.codeboyzhou chat2mysql-commons + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-inline + test + + + org.mockito + mockito-junit-jupiter + test + + + org.slf4j + slf4j-simple + test + diff --git a/mcp-server-chat2mysql/chat2mysql-native-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpPromptsTest.java b/mcp-server-chat2mysql/chat2mysql-native-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpPromptsTest.java new file mode 100644 index 0000000..10d1244 --- /dev/null +++ b/mcp-server-chat2mysql/chat2mysql-native-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpPromptsTest.java @@ -0,0 +1,61 @@ +package com.github.mcp.server.chat2mysql; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptSpecification; +import io.modelcontextprotocol.spec.McpSchema; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.List; + +class McpPromptsTest { + + static { + // Enable Byte Buddy experimental support for Java 21 + System.setProperty("net.bytebuddy.experimental", "true"); + } + + private static SyncPromptSpecification spec; + + @BeforeAll + static void setup() { + // obtain the prompt specification + spec = McpPrompts.generateSqlOptimizationTips(); + } + + @Test + void specHasCorrectNameAndDescription() { + McpSchema.Prompt prompt = spec.prompt(); + assertEquals("generate_sql_optimization_tips", prompt.name(), + "Prompt name should match"); + assertEquals("Generate SQL optimization tips.", prompt.description(), + "Prompt description should match"); + } + + @Test + void specHasSqlArgument() { + List args = spec.prompt().arguments(); + assertEquals(1, args.size(), "There should be exactly one argument"); + McpSchema.PromptArgument arg = args.get(0); + assertEquals("sql", arg.name(), "Argument name should be 'sql'"); + assertTrue(arg.required(), "SQL argument should be required"); + } + + @Test + void addAllToRegistersPromptOnServer() { + McpSyncServer server = mock(McpSyncServer.class); + McpPrompts.addAllTo(server); + // verify that addPrompt was called with the correct prompt specification properties + ArgumentCaptor captor = ArgumentCaptor.forClass(SyncPromptSpecification.class); + verify(server).addPrompt(captor.capture()); + SyncPromptSpecification actual = captor.getValue(); + assertEquals(spec.prompt().name(), actual.prompt().name(), "Prompt name should match"); + assertEquals(spec.prompt().description(), actual.prompt().description(), "Prompt description should match"); + assertEquals(spec.prompt().arguments().size(), + actual.prompt().arguments().size(), "Argument count should match"); + } +} diff --git a/mcp-server-chat2mysql/chat2mysql-native-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpStdioServerTest.java b/mcp-server-chat2mysql/chat2mysql-native-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpStdioServerTest.java new file mode 100644 index 0000000..8524a5d --- /dev/null +++ b/mcp-server-chat2mysql/chat2mysql-native-sdk-example/src/test/java/com/github/mcp/server/chat2mysql/McpStdioServerTest.java @@ -0,0 +1,52 @@ + +package com.github.mcp.server.chat2mysql; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import org.mockito.MockedStatic; + +import java.lang.reflect.Method; +import java.lang.reflect.Field; + +import io.modelcontextprotocol.server.McpSyncServer; + +class McpStdioServerTest { + + private McpStdioServer stdioServer; + + @BeforeEach + void setUp() { + stdioServer = new McpStdioServer(); + } + + @Test + void initializeShouldSetServerField() throws Exception { + // call private initialize() + Method init = McpStdioServer.class.getDeclaredMethod("initialize"); + init.setAccessible(true); + init.invoke(stdioServer); + + // reflectively get 'server' field + Field field = McpStdioServer.class.getDeclaredField("server"); + field.setAccessible(true); + Object serverInstance = field.get(stdioServer); + + assertNotNull(serverInstance, "server field should be initialized"); + assertTrue(serverInstance instanceof McpSyncServer, + "server field should be an instance of McpSyncServer"); + } + + @Test + void mainShouldCallPromptsAddAllTo() { + // mock McpPrompts static + try (MockedStatic prompts = mockStatic(McpPrompts.class)) { + // ensure no exception + assertDoesNotThrow(() -> McpStdioServer.main(new String[0])); + + // verify addAllTo was called with any McpSyncServer + prompts.verify(() -> McpPrompts.addAllTo(any(McpSyncServer.class)), times(1)); + } + } +} diff --git a/mcp-server-chat2mysql/pom.xml b/mcp-server-chat2mysql/pom.xml index 4c32882..c5b57f2 100644 --- a/mcp-server-chat2mysql/pom.xml +++ b/mcp-server-chat2mysql/pom.xml @@ -20,13 +20,6 @@ - 17 - ${java.version} - ${java.version} - UTF-8 - UTF-8 - - 3.6.0 9.2.0 diff --git a/mcp-server-filesystem/filesystem-commons/pom.xml b/mcp-server-filesystem/filesystem-commons/pom.xml index b0effa4..c58ff20 100644 --- a/mcp-server-filesystem/filesystem-commons/pom.xml +++ b/mcp-server-filesystem/filesystem-commons/pom.xml @@ -12,13 +12,4 @@ filesystem-commons jar - - - 17 - ${java.version} - ${java.version} - UTF-8 - UTF-8 - - diff --git a/mcp-server-filesystem/filesystem-declarative-sdk-example/pom.xml b/mcp-server-filesystem/filesystem-declarative-sdk-example/pom.xml index f897224..dff8a7a 100644 --- a/mcp-server-filesystem/filesystem-declarative-sdk-example/pom.xml +++ b/mcp-server-filesystem/filesystem-declarative-sdk-example/pom.xml @@ -13,19 +13,31 @@ filesystem-declarative-sdk-example jar - - 17 - ${java.version} - ${java.version} - UTF-8 - UTF-8 - - com.github.codeboyzhou filesystem-commons + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-inline + test + + + org.mockito + mockito-junit-jupiter + test + + + org.slf4j + slf4j-simple + test + diff --git a/mcp-server-filesystem/filesystem-declarative-sdk-example/src/main/java/com/github/mcp/examples/server/filesystem/McpStdioServer.java b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/main/java/com/github/mcp/examples/server/filesystem/McpStdioServer.java index c2e9a2a..0369a1f 100644 --- a/mcp-server-filesystem/filesystem-declarative-sdk-example/src/main/java/com/github/mcp/examples/server/filesystem/McpStdioServer.java +++ b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/main/java/com/github/mcp/examples/server/filesystem/McpStdioServer.java @@ -15,7 +15,7 @@ public class McpStdioServer { public static void main(String[] args) { McpServerInfo serverInfo = McpServerInfo.builder().name(SERVER_NAME).version(SERVER_VERSION).build(); - McpServers.run(McpSseServer.class, args).startSyncStdioServer(serverInfo); + McpServers.run(McpStdioServer.class, args).startSyncStdioServer(serverInfo); } } diff --git a/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpSseServerTest.java b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpSseServerTest.java new file mode 100644 index 0000000..560bf46 --- /dev/null +++ b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpSseServerTest.java @@ -0,0 +1,27 @@ +package com.github.mcp.examples.server.filesystem; + +import com.github.codeboyzhou.mcp.declarative.McpServers; +import com.github.codeboyzhou.mcp.declarative.server.McpSseServerInfo; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class McpSseServerTest { + + static { + // Enable Byte Buddy experimental support for Java 21 + System.setProperty("net.bytebuddy.experimental", "true"); + } + + @Test + void mainInvokesServerRun() { + try (MockedStatic ms = mockStatic(McpServers.class)) { + com.github.codeboyzhou.mcp.declarative.McpServers fake = mock(com.github.codeboyzhou.mcp.declarative.McpServers.class); + ms.when(() -> McpServers.run(McpSseServer.class, new String[0])).thenReturn(fake); + assertDoesNotThrow(() -> McpSseServer.main(new String[0])); + verify(fake).startSyncSseServer(any(McpSseServerInfo.class)); + } + } +} diff --git a/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpStdioServerTest.java b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpStdioServerTest.java new file mode 100644 index 0000000..e5aa8a3 --- /dev/null +++ b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpStdioServerTest.java @@ -0,0 +1,27 @@ +package com.github.mcp.examples.server.filesystem; + +import com.github.codeboyzhou.mcp.declarative.McpServers; +import com.github.codeboyzhou.mcp.declarative.server.McpServerInfo; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class McpStdioServerTest { + + static { + // Enable Byte Buddy experimental support for Java 21 + System.setProperty("net.bytebuddy.experimental", "true"); + } + + @Test + void mainInvokesStdioServerRun() { + try (MockedStatic ms = mockStatic(McpServers.class)) { + com.github.codeboyzhou.mcp.declarative.McpServers fake = mock(com.github.codeboyzhou.mcp.declarative.McpServers.class); + ms.when(() -> McpServers.run(McpStdioServer.class, new String[0])).thenReturn(fake); + assertDoesNotThrow(() -> McpStdioServer.main(new String[0])); + verify(fake).startSyncStdioServer(any(McpServerInfo.class)); + } + } +} diff --git a/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpPromptsTest.java b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpPromptsTest.java new file mode 100644 index 0000000..ede8f9f --- /dev/null +++ b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpPromptsTest.java @@ -0,0 +1,41 @@ +package com.github.mcp.examples.server.filesystem; + +import com.github.codeboyzhou.mcp.declarative.annotation.McpPrompt; +import com.github.codeboyzhou.mcp.declarative.annotation.McpPrompts; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; + +import static org.junit.jupiter.api.Assertions.*; + +class MyMcpPromptsTest { + + @Test + void classIsAnnotatedWithMcpPrompts() { + assertTrue(MyMcpPrompts.class.isAnnotationPresent(McpPrompts.class), + "MyMcpPrompts should be annotated with @McpPrompts"); + } + + @Test + void readFileMethodAnnotation() throws NoSuchMethodException { + Method m = MyMcpPrompts.class.getMethod("readFile", String.class); + McpPrompt ann = m.getAnnotation(McpPrompt.class); + assertNotNull(ann, "readFile should have @McpPrompt"); + assertEquals("read_file", ann.name()); + assertEquals("Read complete contents of a file.", ann.description()); + // check parameter annotation + Parameter p = m.getParameters()[0]; + assertTrue(p.isAnnotationPresent(com.github.codeboyzhou.mcp.declarative.annotation.McpPromptParam.class)); + } + + @Test + void listFilesMethodAnnotation() throws NoSuchMethodException { + Method m = MyMcpPrompts.class.getMethod("listFiles", String.class, String.class, boolean.class); + McpPrompt ann = m.getAnnotation(McpPrompt.class); + assertNotNull(ann, "listFiles should have @McpPrompt"); + assertEquals("list_files", ann.name()); + assertEquals("List files of a directory.", ann.description()); + assertEquals(3, m.getParameters().length); + } +} diff --git a/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpResourcesTest.java b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpResourcesTest.java new file mode 100644 index 0000000..6672563 --- /dev/null +++ b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpResourcesTest.java @@ -0,0 +1,28 @@ +package com.github.mcp.examples.server.filesystem; + +import com.github.codeboyzhou.mcp.declarative.annotation.McpResource; +import com.github.codeboyzhou.mcp.declarative.annotation.McpResources; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +class MyMcpResourcesTest { + + @Test + void classIsAnnotatedWithMcpResources() { + assertTrue(MyMcpResources.class.isAnnotationPresent(McpResources.class), + "MyMcpResources should be annotated with @McpResources"); + } + + @Test + void filesystemMethodHasMcpResourceAnnotation() throws NoSuchMethodException { + Method m = MyMcpResources.class.getMethod("filesystem"); + assertTrue(m.isAnnotationPresent(McpResource.class), "filesystem() should have @McpResource"); + McpResource ann = m.getAnnotation(McpResource.class); + assertEquals("file://system", ann.uri(), "URI should match"); + assertEquals("filesystem", ann.name(), "name should match"); + assertEquals("File system operations interface", ann.description(), "description should match"); + } +} diff --git a/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpToolsTest.java b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpToolsTest.java new file mode 100644 index 0000000..2442210 --- /dev/null +++ b/mcp-server-filesystem/filesystem-declarative-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/MyMcpToolsTest.java @@ -0,0 +1,38 @@ +package com.github.mcp.examples.server.filesystem; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + +class MyMcpToolsTest { + + @Test + void readFileNonexistent() throws IOException { + MyMcpTools tools = new MyMcpTools(); + String path = "nonexistent.txt"; + String result = tools.readFile(path); + assertTrue(result.contains("does not exist"), "Should report missing file"); + } + + @Test + void listFilesNonexistent() throws IOException { + MyMcpTools tools = new MyMcpTools(); + String path = "no_such_dir"; + String result = tools.listFiles(path, "", false); + assertTrue(result.contains("does not exist"), "Should report missing directory"); + } + + @Test + void readFileAndListOnTemp() throws IOException { + Path tempFile = Files.createTempFile("test", ".txt"); + Files.writeString(tempFile, "hello"); + MyMcpTools tools = new MyMcpTools(); + String content = tools.readFile(tempFile.toString()); + assertEquals("hello", content); + Files.delete(tempFile); + } +} diff --git a/mcp-server-filesystem/filesystem-native-sdk-example/pom.xml b/mcp-server-filesystem/filesystem-native-sdk-example/pom.xml index 598b953..8830e5b 100644 --- a/mcp-server-filesystem/filesystem-native-sdk-example/pom.xml +++ b/mcp-server-filesystem/filesystem-native-sdk-example/pom.xml @@ -13,19 +13,31 @@ filesystem-native-sdk-example jar - - 17 - ${java.version} - ${java.version} - UTF-8 - UTF-8 - - com.github.codeboyzhou filesystem-commons + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-inline + test + + + org.mockito + mockito-junit-jupiter + test + + + org.slf4j + slf4j-simple + test + diff --git a/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpPromptsTest.java b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpPromptsTest.java new file mode 100644 index 0000000..4cff67a --- /dev/null +++ b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpPromptsTest.java @@ -0,0 +1,55 @@ +package com.github.mcp.examples.server.filesystem; + +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptSpecification; +import io.modelcontextprotocol.spec.McpSchema; +import org.junit.jupiter.api.Test; +import io.modelcontextprotocol.server.McpServer; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import reactor.core.publisher.Mono; +import io.modelcontextprotocol.spec.McpServerTransportProvider; + +class McpPromptsTest { + + @Test + void readFileMethodAndSpec() { + SyncPromptSpecification spec = com.github.mcp.examples.server.filesystem.McpPrompts.readFile(); + McpSchema.Prompt p = spec.prompt(); + assertEquals("read_file", p.name()); + assertEquals("Read complete contents of a file.", p.description()); + assertEquals(1, p.arguments().size()); + // skip reflection entirely + } + + @Test + void listFilesMethodAndSpec() { + SyncPromptSpecification spec = com.github.mcp.examples.server.filesystem.McpPrompts.listFiles(); + McpSchema.Prompt p = spec.prompt(); + assertEquals("list_files", p.name()); + assertEquals("List files of a directory.", p.description()); + assertEquals(3, p.arguments().size()); + } + + @Test + void addAllToRegistersPromptsNotifies() { + // Create a mock transport provider + McpServerTransportProvider mockProvider = mock(McpServerTransportProvider.class); + // Stub notifyClients to return empty Mono + when(mockProvider.notifyClients(eq(McpSchema.METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED), isNull())) + .thenReturn(Mono.empty()); + // Build async server with prompts capability + var async = McpServer.async(mockProvider) + .capabilities(McpSchema.ServerCapabilities.builder().prompts(true).build()) + .build(); + McpSyncServer server = new McpSyncServer(async); + + // Invoke prompt registration + com.github.mcp.examples.server.filesystem.McpPrompts.addAllTo(server); + + // Verify that notifyClients was called twice for prompts-list-changed + verify(mockProvider, times(2)) + .notifyClients(eq(McpSchema.METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED), isNull()); + } +} diff --git a/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpResourcesTest.java b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpResourcesTest.java new file mode 100644 index 0000000..cb79af1 --- /dev/null +++ b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpResourcesTest.java @@ -0,0 +1,47 @@ +package com.github.mcp.examples.server.filesystem; + +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.server.McpServer; +import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceSpecification; +import io.modelcontextprotocol.spec.McpSchema; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import reactor.core.publisher.Mono; +import io.modelcontextprotocol.spec.McpServerTransportProvider; + +@com.github.codeboyzhou.mcp.declarative.annotation.McpResources +class McpResourcesTest { + + @Test + void filesystemSpecHasCorrectResource() { + // call the method and inspect the spec + SyncResourceSpecification spec = com.github.mcp.examples.server.filesystem.McpResources.filesystem(); + McpSchema.Resource resource = spec.resource(); + assertEquals("file://system", resource.uri()); + assertEquals("filesystem", resource.name()); + assertEquals("File system operations interface", resource.description()); + } + + @Test + void addAllToRegistersResourceNotifies() { + // Create a mock transport provider + McpServerTransportProvider mockProvider = mock(McpServerTransportProvider.class); + // Stub notifyClients to return empty Mono + when(mockProvider.notifyClients(eq(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED), isNull())) + .thenReturn(Mono.empty()); + // Build async server with resources capability + var async = McpServer.async(mockProvider) + .capabilities(McpSchema.ServerCapabilities.builder().resources(true, true).build()) + .build(); + McpSyncServer server = new McpSyncServer(async); + + // Invoke resource registration + McpResources.addAllTo(server); + + // Verify that notifyClients was called once for resources-list-changed + verify(mockProvider, times(1)) + .notifyClients(eq(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED), isNull()); + } +} diff --git a/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpSseServerTest.java b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpSseServerTest.java new file mode 100644 index 0000000..07f3c0d --- /dev/null +++ b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpSseServerTest.java @@ -0,0 +1,60 @@ +package com.github.mcp.examples.server.filesystem; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; + +import java.lang.reflect.Field; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.*; + +class McpSseServerTest { + + private McpSseServer serverInstance; + + @BeforeEach + void setup() { + serverInstance = new McpSseServer(); + } + + @Test + void initializeShouldSetServerField() throws Exception { + // call private initialize() + var initMethod = McpSseServer.class.getDeclaredMethod("initialize"); + initMethod.setAccessible(true); + initMethod.invoke(serverInstance); + + // verify the 'server' field is non-null + Field serverField = McpSseServer.class.getDeclaredField("server"); + serverField.setAccessible(true); + Object srv = serverField.get(serverInstance); + assertNotNull(srv, "McpSyncServer should be initialized"); + } + + @Test + void mainShouldNotThrow() { + ExecutorService exec = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r); + t.setDaemon(true); + return t; + }); + Future future = exec.submit(() -> McpSseServer.main(new String[0])); + try { + future.get(2, TimeUnit.SECONDS); + } catch (TimeoutException e) { + // main() did not complete in time; cancel the task + future.cancel(true); + } catch (Exception e) { + fail("main() threw an exception: " + e.getMessage()); + } finally { + exec.shutdownNow(); + } + // If we reach here, main() started and was interrupted as expected + assertTrue(true); + } +} diff --git a/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpStdioServerTest.java b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpStdioServerTest.java new file mode 100644 index 0000000..5af81c6 --- /dev/null +++ b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpStdioServerTest.java @@ -0,0 +1,40 @@ + +package com.github.mcp.examples.server.filesystem; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +class McpStdioServerTest { + + private McpStdioServer serverInstance; + + @BeforeEach + void setup() { + serverInstance = new McpStdioServer(); + } + + @Test + void initializeShouldSetServerField() throws Exception { + // call private initialize() + Method init = McpStdioServer.class.getDeclaredMethod("initialize"); + init.setAccessible(true); + init.invoke(serverInstance); + + // verify the 'server' field is non-null + Field field = McpStdioServer.class.getDeclaredField("server"); + field.setAccessible(true); + Object srv = field.get(serverInstance); + assertNotNull(srv, "server field should be initialized"); + } + + @Test + void mainShouldInvokeInitializeAndNotThrow() { + assertDoesNotThrow(() -> McpStdioServer.main(new String[0]), + "main() should complete without throwing"); + } +} diff --git a/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpToolsTest.java b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpToolsTest.java new file mode 100644 index 0000000..68ef504 --- /dev/null +++ b/mcp-server-filesystem/filesystem-native-sdk-example/src/test/java/com/github/mcp/examples/server/filesystem/McpToolsTest.java @@ -0,0 +1,59 @@ +package com.github.mcp.examples.server.filesystem; + +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification; +import io.modelcontextprotocol.spec.McpSchema; +import org.junit.jupiter.api.Test; +import io.modelcontextprotocol.server.McpServer; +import static org.mockito.Mockito.*; +import io.modelcontextprotocol.spec.McpServerTransportProvider; +import reactor.core.publisher.Mono; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class McpToolsTest { + + @Test + void readFileSpecHasCorrectTool() throws IOException { + SyncToolSpecification spec = McpTools.readFile(); + McpSchema.Tool tool = spec.tool(); + assertEquals("read_file", tool.name()); + assertEquals("Read complete contents of a file.", tool.description()); + assertNotNull(tool.inputSchema(), "Tool JSON schema should be present"); + } + + @Test + void listFilesSpecHasCorrectTool() throws IOException { + SyncToolSpecification spec = McpTools.listFiles(); + McpSchema.Tool tool = spec.tool(); + assertEquals("list_files", tool.name()); + assertEquals("List files of a directory.", tool.description()); + assertNotNull(tool.inputSchema(), "Tool JSON schema should be present"); + } + + + @Test + void addAllToRegistersToolsNotifies() { + // Create a mock transport provider + McpServerTransportProvider mockProvider = mock(McpServerTransportProvider.class); + // Stub the notification call to return a real (empty) Mono + when(mockProvider.notifyClients(eq(McpSchema.METHOD_NOTIFICATION_TOOLS_LIST_CHANGED), isNull())) + .thenReturn(Mono.empty()); + + // Build async server with mock transport + var async = McpServer.async(mockProvider) + .capabilities(McpSchema.ServerCapabilities.builder() + .tools(true) + .build()) + .build(); + McpSyncServer server = new McpSyncServer(async); + + // Invoke tool registration + McpTools.addAllTo(server); + + // Verify that notifyClients was called twice + verify(mockProvider, times(2)).notifyClients(eq(McpSchema.METHOD_NOTIFICATION_TOOLS_LIST_CHANGED), isNull()); + } +} diff --git a/mcp-server-filesystem/pom.xml b/mcp-server-filesystem/pom.xml index 078db3a..49dd37c 100644 --- a/mcp-server-filesystem/pom.xml +++ b/mcp-server-filesystem/pom.xml @@ -19,16 +19,6 @@ filesystem-native-sdk-example - - 17 - ${java.version} - ${java.version} - UTF-8 - UTF-8 - - 3.6.0 - - @@ -45,7 +35,6 @@ org.apache.maven.plugins maven-shade-plugin - ${maven-shade-plugin.version} diff --git a/pom.xml b/pom.xml index 92e8e3f..ceb7d6d 100644 --- a/pom.xml +++ b/pom.xml @@ -21,20 +21,78 @@ UTF-8 UTF-8 + 3.4.0 4.0.0 + 3.6.0 + 4.11.0 + 5.11.4 + 2.0.16 0.3.0 + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + org.mockito + mockito-inline + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + + org.slf4j + slf4j-simple + ${slf4j.simple.version} + test + + + + io.github.codeboyzhou mcp-declarative-java-sdk - 0.3.0-SNAPSHOT + ${mcp-declarative-java-sdk.version} + + + + com.github.ekryd.sortpom + sortpom-maven-plugin + ${sortpom-maven-plugin.version} + + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + false + -Dnet.bytebuddy.experimental=true + + + + + com.github.ekryd.sortpom