Skip to content

Commit 4b576dd

Browse files
committed
feat(main): Add initial project structure
1 parent 44afe59 commit 4b576dd

File tree

13 files changed

+364
-3
lines changed

13 files changed

+364
-3
lines changed

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_size = 4
7+
indent_style = space
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# IntelliJ IDEA files
2+
*.iml
3+
.idea/
4+
5+
# Microsoft VSCode files
6+
.vscode/
7+
8+
# Maven build files
9+
target/

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2025 codeboyzz
3+
Copyright (c) 2025 https://git.1-hub.cncodeboyzhou <imzhouchen@gmail.com>
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
= Annotation-driven MCP Java SDK
2+
3+
Declarative MCP (Model Context Protocol) Development with Java Annotations and No Spring Framework Required.

README.md

-2
This file was deleted.

pom.xml

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
6+
<modelVersion>4.0.0</modelVersion>
7+
8+
<groupId>com.github.codeboyzhou</groupId>
9+
<artifactId>mcp-declarative-java-sdk</artifactId>
10+
<version>0.1.0-SNAPSHOT</version>
11+
12+
<properties>
13+
<java.version>17</java.version>
14+
<maven.compiler.source>${java.version}</maven.compiler.source>
15+
<maven.compiler.target>${java.version}</maven.compiler.target>
16+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
17+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
18+
<!--==================== dependency versions ======================-->
19+
<mcp-sdk.version>0.8.1</mcp-sdk.version>
20+
<jetty.version>12.0.18</jetty.version>
21+
<reflections.version>0.10.2</reflections.version>
22+
</properties>
23+
24+
<dependencyManagement>
25+
<dependencies>
26+
<dependency>
27+
<groupId>io.modelcontextprotocol.sdk</groupId>
28+
<artifactId>mcp-bom</artifactId>
29+
<version>${mcp-sdk.version}</version>
30+
<scope>import</scope>
31+
<type>pom</type>
32+
</dependency>
33+
</dependencies>
34+
</dependencyManagement>
35+
36+
<dependencies>
37+
<dependency>
38+
<groupId>io.modelcontextprotocol.sdk</groupId>
39+
<artifactId>mcp</artifactId>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.eclipse.jetty.ee10</groupId>
43+
<artifactId>jetty-ee10-servlet</artifactId>
44+
<version>${jetty.version}</version>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.reflections</groupId>
48+
<artifactId>reflections</artifactId>
49+
<version>${reflections.version}</version>
50+
</dependency>
51+
</dependencies>
52+
53+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package com.github.codeboyzhou.mcp.declarative;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.github.codeboyzhou.mcp.declarative.annotation.*;
5+
import com.github.codeboyzhou.mcp.declarative.util.Annotations;
6+
import io.modelcontextprotocol.server.McpServer;
7+
import io.modelcontextprotocol.server.McpServerFeatures;
8+
import io.modelcontextprotocol.server.McpSyncServer;
9+
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;
10+
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
11+
import io.modelcontextprotocol.spec.McpSchema;
12+
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
13+
import org.eclipse.jetty.ee10.servlet.ServletHolder;
14+
import org.eclipse.jetty.server.Server;
15+
import org.reflections.Reflections;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
19+
import java.lang.reflect.Method;
20+
import java.lang.reflect.Parameter;
21+
import java.util.*;
22+
23+
public class McpServers {
24+
25+
private static final Logger logger = LoggerFactory.getLogger(McpServers.class);
26+
27+
private static final McpSchema.ServerCapabilities DEFAULT_SERVER_CAPABILITIES = McpSchema.ServerCapabilities
28+
.builder()
29+
.resources(true, true)
30+
.prompts(true)
31+
.tools(true)
32+
.build();
33+
34+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
35+
36+
private static final String OBJECT_TYPE_NAME = Object.class.getName().toLowerCase();
37+
38+
private static final Reflections REFLECTIONS = new Reflections();
39+
40+
private static final String DEFAULT_MESSAGE_ENDPOINT = "/message";
41+
42+
private static final String DEFAULT_SSE_ENDPOINT = "/sse";
43+
44+
private static final int DEFAULT_HTTP_SERVER_PORT = 8080;
45+
46+
public static void startSyncStdioServer(String name, String version) {
47+
McpSyncServer server = McpServer.sync(new StdioServerTransportProvider())
48+
.capabilities(DEFAULT_SERVER_CAPABILITIES)
49+
.serverInfo(name, version)
50+
.build();
51+
52+
registerResources(server);
53+
registerTools(server);
54+
}
55+
56+
public static void startSyncSseServer(String name, String version) {
57+
startSyncSseServer(name, version, DEFAULT_MESSAGE_ENDPOINT, DEFAULT_SSE_ENDPOINT, DEFAULT_HTTP_SERVER_PORT);
58+
}
59+
60+
public static void startSyncSseServer(String name, String version, int port) {
61+
startSyncSseServer(name, version, DEFAULT_MESSAGE_ENDPOINT, DEFAULT_SSE_ENDPOINT, port);
62+
}
63+
64+
public static void startSyncSseServer(String name, String version, String messageEndpoint, String sseEndpoint, int port) {
65+
HttpServletSseServerTransportProvider transport = new HttpServletSseServerTransportProvider(
66+
OBJECT_MAPPER, messageEndpoint, sseEndpoint
67+
);
68+
69+
McpSyncServer server = McpServer.sync(transport)
70+
.capabilities(DEFAULT_SERVER_CAPABILITIES)
71+
.serverInfo(name, version)
72+
.build();
73+
74+
registerResources(server);
75+
registerTools(server);
76+
77+
startHttpServer(server, transport, port);
78+
}
79+
80+
private static void startHttpServer(McpSyncServer server, HttpServletSseServerTransportProvider transport, int port) {
81+
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
82+
servletContextHandler.setContextPath("/");
83+
84+
ServletHolder servletHolder = new ServletHolder(transport);
85+
servletContextHandler.addServlet(servletHolder, "/*");
86+
87+
Server httpserver = new Server(port);
88+
httpserver.setHandler(servletContextHandler);
89+
90+
try {
91+
httpserver.start();
92+
logger.info("Jetty-based HTTP server started on http://127.0.0.1:{}", port);
93+
94+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
95+
try {
96+
logger.info("Shutting down HTTP server");
97+
httpserver.stop();
98+
server.close();
99+
} catch (Exception e) {
100+
logger.error("Error stopping HTTP server", e);
101+
}
102+
}));
103+
104+
// Wait for the HTTP server to stop
105+
httpserver.join();
106+
} catch (Exception e) {
107+
logger.error("Error starting HTTP server on http://127.0.0.1:{}", port, e);
108+
server.close();
109+
}
110+
}
111+
112+
private static void registerResources(McpSyncServer server) {
113+
Set<Class<?>> resourceClasses = REFLECTIONS.getTypesAnnotatedWith(McpResources.class);
114+
for (Class<?> resourceClass : resourceClasses) {
115+
Set<Method> methods = Annotations.getMethodsAnnotatedWith(resourceClass, McpResource.class);
116+
for (Method method : methods) {
117+
McpResource resourceMethod = method.getAnnotation(McpResource.class);
118+
McpSchema.Resource resource = new McpSchema.Resource(
119+
resourceMethod.uri(),
120+
resourceMethod.name(),
121+
resourceMethod.description(),
122+
resourceMethod.mimeType(),
123+
new McpSchema.Annotations(List.of(resourceMethod.roles()), resourceMethod.priority())
124+
);
125+
server.addResource(new McpServerFeatures.SyncResourceSpecification(resource, (exchange, request) -> {
126+
Object result;
127+
try {
128+
Object resourceObject = resourceClass.getDeclaredConstructor().newInstance();
129+
result = method.invoke(resourceObject);
130+
} catch (Exception e) {
131+
result = e + ": " + e.getMessage();
132+
}
133+
McpSchema.ResourceContents contents = new McpSchema.TextResourceContents(
134+
resource.uri(), resource.mimeType(), result.toString()
135+
);
136+
return new McpSchema.ReadResourceResult(List.of(contents));
137+
}));
138+
}
139+
}
140+
}
141+
142+
private static void registerTools(McpSyncServer server) {
143+
Set<Class<?>> toolClasses = REFLECTIONS.getTypesAnnotatedWith(McpTools.class);
144+
for (Class<?> toolClass : toolClasses) {
145+
Set<Method> methods = Annotations.getMethodsAnnotatedWith(toolClass, McpTool.class);
146+
for (Method method : methods) {
147+
McpTool toolMethod = method.getAnnotation(McpTool.class);
148+
McpSchema.JsonSchema paramSchema = createJsonSchema(method);
149+
McpSchema.Tool tool = new McpSchema.Tool(toolMethod.name(), toolMethod.description(), paramSchema);
150+
server.addTool(new McpServerFeatures.SyncToolSpecification(tool, (exchange, params) -> {
151+
Object result;
152+
boolean isError = false;
153+
try {
154+
Object toolObject = toolClass.getDeclaredConstructor().newInstance();
155+
result = method.invoke(toolObject, params.values());
156+
} catch (Exception e) {
157+
result = e + ": " + e.getMessage();
158+
isError = true;
159+
}
160+
McpSchema.Content content = new McpSchema.TextContent(result.toString());
161+
return new McpSchema.CallToolResult(List.of(content), isError);
162+
}));
163+
}
164+
}
165+
}
166+
167+
private static McpSchema.JsonSchema createJsonSchema(Method method) {
168+
Map<String, Object> properties = new HashMap<>();
169+
List<String> required = new ArrayList<>();
170+
171+
Set<Parameter> parameters = Annotations.getParametersAnnotatedWith(method, McpToolParam.class);
172+
for (Parameter parameter : parameters) {
173+
final String parameterName = parameter.getName();
174+
final String parameterType = parameter.getType().getName().toLowerCase();
175+
McpToolParam toolParam = parameter.getAnnotation(McpToolParam.class);
176+
177+
Map<String, String> parameterProperties = Map.of(
178+
"type", parameterType,
179+
"description", toolParam.description()
180+
);
181+
properties.put(parameterName, parameterProperties);
182+
183+
if (toolParam.required()) {
184+
required.add(parameterName);
185+
}
186+
}
187+
188+
return new McpSchema.JsonSchema(OBJECT_TYPE_NAME, properties, required, false);
189+
}
190+
191+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import io.modelcontextprotocol.spec.McpSchema;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
@Target(ElementType.METHOD)
11+
@Retention(RetentionPolicy.RUNTIME)
12+
public @interface McpResource {
13+
String uri();
14+
15+
String name();
16+
17+
String description();
18+
19+
String mimeType();
20+
21+
McpSchema.Role[] roles() default {McpSchema.Role.ASSISTANT, McpSchema.Role.USER};
22+
23+
double priority() default 1.0;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface McpResources {
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.METHOD)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface McpTool {
11+
12+
String name();
13+
14+
String description();
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.PARAMETER)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface McpToolParam {
11+
12+
String description();
13+
14+
boolean required() default false;
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface McpTools {
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.github.codeboyzhou.mcp.declarative.util;
2+
3+
import java.lang.annotation.Annotation;
4+
import java.lang.reflect.Method;
5+
import java.lang.reflect.Parameter;
6+
import java.util.Set;
7+
8+
import static java.util.stream.Collectors.toSet;
9+
10+
public final class Annotations {
11+
12+
public static Set<Method> getMethodsAnnotatedWith(Class<?> clazz, Class<? extends Annotation> annotation) {
13+
Method[] methods = clazz.getMethods();
14+
return Set.of(methods).stream().filter(m -> m.isAnnotationPresent(annotation)).collect(toSet());
15+
}
16+
17+
public static Set<Parameter> getParametersAnnotatedWith(Method method, Class<? extends Annotation> annotation) {
18+
Parameter[] parameters = method.getParameters();
19+
return Set.of(parameters).stream().filter(p -> p.isAnnotationPresent(annotation)).collect(toSet());
20+
}
21+
22+
}

0 commit comments

Comments
 (0)