mirror of
https://github.com/github/awesome-copilot.git
synced 2026-10-15 00:01:58 +00:00
docs(java-mcp-server-generator): update skill for MCP Java SDK 2.x
- Target MCP Java SDK 2.0.1 and specification revision 2025-11-25. - Rewrite the description with user intent triggers and exclusions. - Add the compatibility field. - Add a five-step workflow checklist with a build loop. - Add a Maven template, a stdio server template, and a JUnit test that starts the server as a subprocess with the SDK client. - Add Gotchas for SDK 2.x changes, schema validation, error results, stdout in stdio servers, and the features that SEP-2577 deprecates. - Add references/gradle.md for Gradle projects. - Add references/streamable-http.md for Servlet, Spring AI, and transport security. - Update the skill description in docs/README.skills.md.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# Gradle Projects
|
||||
|
||||
Use this file only when the project uses Gradle or the user asks for Gradle. The Java code and the tests do not change.
|
||||
|
||||
## Create the project
|
||||
|
||||
Make this layout. Do not write `pom.xml`:
|
||||
|
||||
```text
|
||||
project-name/
|
||||
├── settings.gradle.kts
|
||||
├── build.gradle.kts
|
||||
└── src/
|
||||
├── main/java/com/example/McpServerApp.java
|
||||
└── test/java/com/example/McpServerAppTest.java
|
||||
```
|
||||
|
||||
Write `settings.gradle.kts`:
|
||||
|
||||
```kotlin
|
||||
rootProject.name = "mcp-server-demo"
|
||||
```
|
||||
|
||||
Write `build.gradle.kts`:
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
application
|
||||
}
|
||||
|
||||
group = "com.example"
|
||||
version = "0.1.0"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform("io.modelcontextprotocol.sdk:mcp-bom:2.0.1"))
|
||||
implementation("io.modelcontextprotocol.sdk:mcp")
|
||||
runtimeOnly("org.slf4j:slf4j-simple:2.0.17")
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:6.1.3"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.release = 17
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass = "com.example.McpServerApp"
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
```
|
||||
|
||||
## Build until the tests pass
|
||||
|
||||
1. If the project has no `gradlew` file, run `gradle wrapper` one time.
|
||||
2. Run `./gradlew build installDist`. This command compiles the code and runs the tests. It also makes the start script `build/install/mcp-server-demo/bin/mcp-server-demo`.
|
||||
3. If a test fails, read the error. Fix the server or the test.
|
||||
4. Run `./gradlew build installDist` again.
|
||||
5. Continue only when the build is successful.
|
||||
|
||||
## Configure the client
|
||||
|
||||
For VS Code, write `.vscode/mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"demo": {
|
||||
"type": "stdio",
|
||||
"command": "/absolute/path/to/project/build/install/mcp-server-demo/bin/mcp-server-demo"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
On Windows, use `mcp-server-demo.bat`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Gradle 9 does not add the JUnit Platform launcher. Without `junit-platform-launcher`, the `test` task fails.
|
||||
- The `application` plugin does not make an executable JAR. The JAR in `build/libs` has no `Main-Class` and no dependencies. Use the start script from `installDist`.
|
||||
- Do not use `./gradlew run` as the client command. The `run` task does not send stdin to the server. Gradle also writes its own output to stdout.
|
||||
@@ -0,0 +1,117 @@
|
||||
# Streamable HTTP Servers
|
||||
|
||||
Use this transport only for a remote server. A local server uses stdio.
|
||||
|
||||
## Servlet server
|
||||
|
||||
`HttpServletStreamableServerTransportProvider` is a Jakarta Servlet. It is in the `mcp` artifact. Add a servlet container to `pom.xml`, for example embedded Tomcat:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-core</artifactId>
|
||||
<version>11.0.26</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
In Gradle, add `implementation("org.apache.tomcat.embed:tomcat-embed-core:11.0.26")`.
|
||||
|
||||
Replace the `main` method in `McpServerApp.java`:
|
||||
|
||||
```java
|
||||
public static void main(String[] args) throws Exception {
|
||||
int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
|
||||
|
||||
HttpServletStreamableServerTransportProvider transport = HttpServletStreamableServerTransportProvider.builder()
|
||||
.jsonMapper(McpJsonDefaults.getMapper())
|
||||
.mcpEndpoint("/mcp")
|
||||
.securityValidator(DefaultServerTransportSecurityValidator.builder()
|
||||
.allowedHost("localhost:" + port)
|
||||
.allowedHost("127.0.0.1:" + port)
|
||||
.allowedOrigin("http://localhost:*")
|
||||
.build())
|
||||
.build();
|
||||
|
||||
McpServer.sync(transport)
|
||||
.serverInfo("demo", "0.1.0")
|
||||
.capabilities(ServerCapabilities.builder().tools(false).build())
|
||||
.tools(divideTool())
|
||||
.build();
|
||||
|
||||
Tomcat tomcat = new Tomcat();
|
||||
tomcat.setPort(port);
|
||||
tomcat.getConnector();
|
||||
Context context = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(context, "mcp", transport).setAsyncSupported(true);
|
||||
context.addServletMappingDecoded("/mcp", "mcp");
|
||||
tomcat.start();
|
||||
tomcat.getServer().await();
|
||||
}
|
||||
```
|
||||
|
||||
Add these imports: `org.apache.catalina.Context`, `org.apache.catalina.startup.Tomcat`, `io.modelcontextprotocol.server.transport.DefaultServerTransportSecurityValidator`, and `io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider`. Remove the import of `StdioServerTransportProvider`.
|
||||
|
||||
Clients connect to `http://HOST:PORT/mcp`. For VS Code, write `.vscode/mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"demo": {
|
||||
"type": "http",
|
||||
"url": "http://localhost:8080/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Spring Boot server
|
||||
|
||||
Use the MCP starters of [Spring AI](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) 2.0 or later. Spring AI 2.0 uses Spring Boot 4. Use `spring-boot-starter-parent` 4.0.x. Import `org.springframework.ai:spring-ai-bom` version 2.0.1 and add `spring-ai-starter-mcp-server-webmvc`. For WebFlux, add `spring-ai-starter-mcp-server-webflux`.
|
||||
|
||||
Write the tools as annotated methods on a Spring bean:
|
||||
|
||||
```java
|
||||
@Component
|
||||
class CalculatorTools {
|
||||
|
||||
@McpTool(name = "add", description = "Add two integers")
|
||||
public int add(@McpToolParam(description = "First number") int a,
|
||||
@McpToolParam(description = "Second number") int b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The annotations are in `org.springframework.ai.mcp.annotation`. Set the server identity and the transport in `application.properties`:
|
||||
|
||||
```properties
|
||||
spring.ai.mcp.server.name=demo
|
||||
spring.ai.mcp.server.version=0.1.0
|
||||
spring.ai.mcp.server.protocol=STREAMABLE
|
||||
```
|
||||
|
||||
The starter serves the endpoint at `/mcp`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The HTTP transports do not check the `Host` and `Origin` headers by default. This also applies to the Spring AI starters. Without a check, a malicious web page can use DNS rebinding to call a local server. Always set `securityValidator(DefaultServerTransportSecurityValidator.builder()...build())`. The validator rejects an unknown `Origin` with HTTP 403 and an unknown `Host` with HTTP 421.
|
||||
- In Spring AI, declare your own `WebMvcStreamableServerTransportProvider` bean to set the validator. The starter then does not make its own bean:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportProvider() {
|
||||
return WebMvcStreamableServerTransportProvider.builder()
|
||||
.jsonMapper(McpJsonDefaults.getMapper())
|
||||
.mcpEndpoint("/mcp")
|
||||
.securityValidator(DefaultServerTransportSecurityValidator.builder()
|
||||
.allowedHost("localhost:8080")
|
||||
.allowedOrigin("http://localhost:*")
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
- For WebFlux, declare a `WebFluxStreamableServerTransportProvider` bean in the same way. Its builder uses `messageEndpoint("/mcp")`, not `mcpEndpoint`.
|
||||
- Embedded Tomcat listens on all network interfaces. For a public host, add its name to `allowedHost`. Put authentication in front of the server.
|
||||
- `HttpServletStatelessServerTransport` makes a server without sessions. Use it with `McpServer.sync(transport)` when more than one replica must answer the requests. A stateless server cannot send requests to the client. Also set `securityValidator` on its builder.
|
||||
- For browser clients, add CORS to the servlet container. Allow the `Mcp-*` request headers. Expose the `Mcp-Session-Id` response header.
|
||||
Reference in New Issue
Block a user