- 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.
5.0 KiB
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:
<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:
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:
{
"servers": {
"demo": {
"type": "http",
"url": "http://localhost:8080/mcp"
}
}
}
Spring Boot server
Use the MCP starters of Spring AI 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:
@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:
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
HostandOriginheaders 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 setsecurityValidator(DefaultServerTransportSecurityValidator.builder()...build()). The validator rejects an unknownOriginwith HTTP 403 and an unknownHostwith HTTP 421. -
In Spring AI, declare your own
WebMvcStreamableServerTransportProviderbean to set the validator. The starter then does not make its own bean:@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
WebFluxStreamableServerTransportProviderbean in the same way. Its builder usesmessageEndpoint("/mcp"), notmcpEndpoint. -
Embedded Tomcat listens on all network interfaces. For a public host, add its name to
allowedHost. Put authentication in front of the server. -
HttpServletStatelessServerTransportmakes a server without sessions. Use it withMcpServer.sync(transport)when more than one replica must answer the requests. A stateless server cannot send requests to the client. Also setsecurityValidatoron its builder. -
For browser clients, add CORS to the servlet container. Allow the
Mcp-*request headers. Expose theMcp-Session-Idresponse header.