Skip to content

Commit 1f7ef5c

Browse files
committed
chore(mcp-server-filesystem): Add unit tests for example servers
Fixes a typo in McpStdioServer to correctly run the Stdio server example.
1 parent d78f0b0 commit 1f7ef5c

File tree

13 files changed

+497
-1
lines changed

13 files changed

+497
-1
lines changed

mcp-server-filesystem/filesystem-declarative-sdk-example/pom.xml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,42 @@
2626
<groupId>com.github.codeboyzhou</groupId>
2727
<artifactId>filesystem-commons</artifactId>
2828
</dependency>
29+
<dependency>
30+
<groupId>org.junit.jupiter</groupId>
31+
<artifactId>junit-jupiter</artifactId>
32+
<version>5.11.4</version>
33+
<scope>test</scope>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.mockito</groupId>
37+
<artifactId>mockito-inline</artifactId>
38+
<version>4.11.0</version>
39+
<scope>test</scope>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.mockito</groupId>
43+
<artifactId>mockito-junit-jupiter</artifactId>
44+
<version>4.11.0</version>
45+
<scope>test</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.slf4j</groupId>
49+
<artifactId>slf4j-simple</artifactId>
50+
<version>2.0.16</version>
51+
<scope>test</scope>
52+
</dependency>
2953
</dependencies>
3054

55+
<build>
56+
<plugins>
57+
<plugin>
58+
<groupId>org.apache.maven.plugins</groupId>
59+
<artifactId>maven-surefire-plugin</artifactId>
60+
<version>3.4.0</version>
61+
<configuration>
62+
<useModulePath>false</useModulePath>
63+
</configuration>
64+
</plugin>
65+
</plugins>
66+
</build>
3167
</project>

mcp-server-filesystem/filesystem-declarative-sdk-example/src/main/java/com/github/mcp/examples/server/filesystem/McpStdioServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class McpStdioServer {
1515

1616
public static void main(String[] args) {
1717
McpServerInfo serverInfo = McpServerInfo.builder().name(SERVER_NAME).version(SERVER_VERSION).build();
18-
McpServers.run(McpSseServer.class, args).startSyncStdioServer(serverInfo);
18+
McpServers.run(McpStdioServer.class, args).startSyncStdioServer(serverInfo);
1919
}
2020

2121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.github.mcp.examples.server.filesystem;
2+
3+
import com.github.codeboyzhou.mcp.declarative.McpServers;
4+
import com.github.codeboyzhou.mcp.declarative.server.McpSseServerInfo;
5+
import org.junit.jupiter.api.Test;
6+
import org.mockito.MockedStatic;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
import static org.mockito.Mockito.*;
10+
11+
class McpSseServerTest {
12+
13+
static {
14+
// Enable Byte Buddy experimental support for Java 21
15+
System.setProperty("net.bytebuddy.experimental", "true");
16+
}
17+
18+
@Test
19+
void mainInvokesServerRun() {
20+
try (MockedStatic<McpServers> ms = mockStatic(McpServers.class)) {
21+
com.github.codeboyzhou.mcp.declarative.McpServers fake = mock(com.github.codeboyzhou.mcp.declarative.McpServers.class);
22+
ms.when(() -> McpServers.run(McpSseServer.class, new String[0])).thenReturn(fake);
23+
assertDoesNotThrow(() -> McpSseServer.main(new String[0]));
24+
verify(fake).startSyncSseServer(any(McpSseServerInfo.class));
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.github.mcp.examples.server.filesystem;
2+
3+
import com.github.codeboyzhou.mcp.declarative.McpServers;
4+
import com.github.codeboyzhou.mcp.declarative.server.McpServerInfo;
5+
import org.junit.jupiter.api.Test;
6+
import org.mockito.MockedStatic;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
import static org.mockito.Mockito.*;
10+
11+
class McpStdioServerTest {
12+
13+
static {
14+
// Enable Byte Buddy experimental support for Java 21
15+
System.setProperty("net.bytebuddy.experimental", "true");
16+
}
17+
18+
@Test
19+
void mainInvokesStdioServerRun() {
20+
try (MockedStatic<McpServers> ms = mockStatic(McpServers.class)) {
21+
com.github.codeboyzhou.mcp.declarative.McpServers fake = mock(com.github.codeboyzhou.mcp.declarative.McpServers.class);
22+
ms.when(() -> McpServers.run(McpStdioServer.class, new String[0])).thenReturn(fake);
23+
assertDoesNotThrow(() -> McpStdioServer.main(new String[0]));
24+
verify(fake).startSyncStdioServer(any(McpServerInfo.class));
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.github.mcp.examples.server.filesystem;
2+
3+
import com.github.codeboyzhou.mcp.declarative.annotation.McpPrompt;
4+
import com.github.codeboyzhou.mcp.declarative.annotation.McpPrompts;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.lang.reflect.Method;
8+
import java.lang.reflect.Parameter;
9+
10+
import static org.junit.jupiter.api.Assertions.*;
11+
12+
@McpPrompts
13+
class MyMcpPromptsTest {
14+
15+
@Test
16+
void classIsAnnotatedWithMcpPrompts() {
17+
assertTrue(MyMcpPrompts.class.isAnnotationPresent(McpPrompts.class),
18+
"MyMcpPrompts should be annotated with @McpPrompts");
19+
}
20+
21+
@Test
22+
void readFileMethodAnnotation() throws NoSuchMethodException {
23+
Method m = MyMcpPrompts.class.getMethod("readFile", String.class);
24+
McpPrompt ann = m.getAnnotation(McpPrompt.class);
25+
assertNotNull(ann, "readFile should have @McpPrompt");
26+
assertEquals("read_file", ann.name());
27+
assertEquals("Read complete contents of a file.", ann.description());
28+
// check parameter annotation
29+
Parameter p = m.getParameters()[0];
30+
assertTrue(p.isAnnotationPresent(com.github.codeboyzhou.mcp.declarative.annotation.McpPromptParam.class));
31+
}
32+
33+
@Test
34+
void listFilesMethodAnnotation() throws NoSuchMethodException {
35+
Method m = MyMcpPrompts.class.getMethod("listFiles", String.class, String.class, boolean.class);
36+
McpPrompt ann = m.getAnnotation(McpPrompt.class);
37+
assertNotNull(ann, "listFiles should have @McpPrompt");
38+
assertEquals("list_files", ann.name());
39+
assertEquals("List files of a directory.", ann.description());
40+
assertEquals(3, m.getParameters().length);
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.github.mcp.examples.server.filesystem;
2+
3+
import com.github.codeboyzhou.mcp.declarative.annotation.McpResource;
4+
import com.github.codeboyzhou.mcp.declarative.annotation.McpResources;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.lang.reflect.Method;
8+
9+
import static org.junit.jupiter.api.Assertions.*;
10+
11+
@McpResources
12+
class MyMcpResourcesTest {
13+
14+
@Test
15+
void classIsAnnotatedWithMcpResources() {
16+
assertTrue(MyMcpResources.class.isAnnotationPresent(McpResources.class),
17+
"MyMcpResources should be annotated with @McpResources");
18+
}
19+
20+
@Test
21+
void filesystemMethodHasMcpResourceAnnotation() throws NoSuchMethodException {
22+
Method m = MyMcpResources.class.getMethod("filesystem");
23+
assertTrue(m.isAnnotationPresent(McpResource.class), "filesystem() should have @McpResource");
24+
McpResource ann = m.getAnnotation(McpResource.class);
25+
assertEquals("file://system", ann.uri(), "URI should match");
26+
assertEquals("filesystem", ann.name(), "name should match");
27+
assertEquals("File system operations interface", ann.description(), "description should match");
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.github.mcp.examples.server.filesystem;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.io.IOException;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
9+
import static org.junit.jupiter.api.Assertions.*;
10+
11+
class MyMcpToolsTest {
12+
13+
@Test
14+
void readFileNonexistent() throws IOException {
15+
MyMcpTools tools = new MyMcpTools();
16+
String path = "nonexistent.txt";
17+
String result = tools.readFile(path);
18+
assertTrue(result.contains("does not exist"), "Should report missing file");
19+
}
20+
21+
@Test
22+
void listFilesNonexistent() throws IOException {
23+
MyMcpTools tools = new MyMcpTools();
24+
String path = "no_such_dir";
25+
String result = tools.listFiles(path, "", false);
26+
assertTrue(result.contains("does not exist"), "Should report missing directory");
27+
}
28+
29+
@Test
30+
void readFileAndListOnTemp() throws IOException {
31+
Path tempFile = Files.createTempFile("test", ".txt");
32+
Files.writeString(tempFile, "hello");
33+
MyMcpTools tools = new MyMcpTools();
34+
String content = tools.readFile(tempFile.toString());
35+
assertEquals("hello", content);
36+
Files.delete(tempFile);
37+
}
38+
}

mcp-server-filesystem/filesystem-native-sdk-example/pom.xml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,42 @@
2626
<groupId>com.github.codeboyzhou</groupId>
2727
<artifactId>filesystem-commons</artifactId>
2828
</dependency>
29+
<dependency>
30+
<groupId>org.junit.jupiter</groupId>
31+
<artifactId>junit-jupiter</artifactId>
32+
<version>5.11.4</version>
33+
<scope>test</scope>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.mockito</groupId>
37+
<artifactId>mockito-inline</artifactId>
38+
<version>4.11.0</version>
39+
<scope>test</scope>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.mockito</groupId>
43+
<artifactId>mockito-junit-jupiter</artifactId>
44+
<version>4.11.0</version>
45+
<scope>test</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.slf4j</groupId>
49+
<artifactId>slf4j-simple</artifactId>
50+
<version>2.0.16</version>
51+
<scope>test</scope>
52+
</dependency>
2953
</dependencies>
3054

55+
<build>
56+
<plugins>
57+
<plugin>
58+
<groupId>org.apache.maven.plugins</groupId>
59+
<artifactId>maven-surefire-plugin</artifactId>
60+
<version>3.4.0</version>
61+
<configuration>
62+
<useModulePath>false</useModulePath>
63+
</configuration>
64+
</plugin>
65+
</plugins>
66+
</build>
3167
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.github.mcp.examples.server.filesystem;
2+
3+
import io.modelcontextprotocol.server.McpSyncServer;
4+
import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptSpecification;
5+
import io.modelcontextprotocol.spec.McpSchema;
6+
import org.junit.jupiter.api.Test;
7+
import io.modelcontextprotocol.server.McpServer;
8+
9+
import static org.junit.jupiter.api.Assertions.*;
10+
import static org.mockito.Mockito.*;
11+
import reactor.core.publisher.Mono;
12+
import io.modelcontextprotocol.spec.McpServerTransportProvider;
13+
14+
class McpPromptsTest {
15+
16+
@Test
17+
void readFileMethodAndSpec() {
18+
SyncPromptSpecification spec = com.github.mcp.examples.server.filesystem.McpPrompts.readFile();
19+
McpSchema.Prompt p = spec.prompt();
20+
assertEquals("read_file", p.name());
21+
assertEquals("Read complete contents of a file.", p.description());
22+
assertEquals(1, p.arguments().size());
23+
// skip reflection entirely
24+
}
25+
26+
@Test
27+
void listFilesMethodAndSpec() {
28+
SyncPromptSpecification spec = com.github.mcp.examples.server.filesystem.McpPrompts.listFiles();
29+
McpSchema.Prompt p = spec.prompt();
30+
assertEquals("list_files", p.name());
31+
assertEquals("List files of a directory.", p.description());
32+
assertEquals(3, p.arguments().size());
33+
}
34+
35+
@Test
36+
void addAllToRegistersPromptsNotifies() {
37+
// Create a mock transport provider
38+
McpServerTransportProvider mockProvider = mock(McpServerTransportProvider.class);
39+
// Stub notifyClients to return empty Mono
40+
when(mockProvider.notifyClients(eq(McpSchema.METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED), isNull()))
41+
.thenReturn(Mono.empty());
42+
// Build async server with prompts capability
43+
var async = McpServer.async(mockProvider)
44+
.capabilities(McpSchema.ServerCapabilities.builder().prompts(true).build())
45+
.build();
46+
McpSyncServer server = new McpSyncServer(async);
47+
48+
// Invoke prompt registration
49+
com.github.mcp.examples.server.filesystem.McpPrompts.addAllTo(server);
50+
51+
// Verify that notifyClients was called twice for prompts-list-changed
52+
verify(mockProvider, times(2))
53+
.notifyClients(eq(McpSchema.METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED), isNull());
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.github.mcp.examples.server.filesystem;
2+
3+
import io.modelcontextprotocol.server.McpSyncServer;
4+
import io.modelcontextprotocol.server.McpServer;
5+
import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceSpecification;
6+
import io.modelcontextprotocol.spec.McpSchema;
7+
import org.junit.jupiter.api.Test;
8+
9+
import static org.junit.jupiter.api.Assertions.*;
10+
import static org.mockito.Mockito.*;
11+
import reactor.core.publisher.Mono;
12+
import io.modelcontextprotocol.spec.McpServerTransportProvider;
13+
14+
@com.github.codeboyzhou.mcp.declarative.annotation.McpResources
15+
class McpResourcesTest {
16+
17+
@Test
18+
void filesystemSpecHasCorrectResource() {
19+
// call the method and inspect the spec
20+
SyncResourceSpecification spec = com.github.mcp.examples.server.filesystem.McpResources.filesystem();
21+
McpSchema.Resource resource = spec.resource();
22+
assertEquals("file://system", resource.uri());
23+
assertEquals("filesystem", resource.name());
24+
assertEquals("File system operations interface", resource.description());
25+
}
26+
27+
@Test
28+
void addAllToRegistersResourceNotifies() {
29+
// Create a mock transport provider
30+
McpServerTransportProvider mockProvider = mock(McpServerTransportProvider.class);
31+
// Stub notifyClients to return empty Mono
32+
when(mockProvider.notifyClients(eq(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED), isNull()))
33+
.thenReturn(Mono.empty());
34+
// Build async server with resources capability
35+
var async = McpServer.async(mockProvider)
36+
.capabilities(McpSchema.ServerCapabilities.builder().resources(true, true).build())
37+
.build();
38+
McpSyncServer server = new McpSyncServer(async);
39+
40+
// Invoke resource registration
41+
McpResources.addAllTo(server);
42+
43+
// Verify that notifyClients was called once for resources-list-changed
44+
verify(mockProvider, times(1))
45+
.notifyClients(eq(McpSchema.METHOD_NOTIFICATION_RESOURCES_LIST_CHANGED), isNull());
46+
}
47+
}

0 commit comments

Comments
 (0)