diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json
index c6c1902ba..9bf22fd98 100644
--- a/.github/plugin/marketplace.json
+++ b/.github/plugin/marketplace.json
@@ -1300,8 +1300,8 @@
{
"name": "java-mcp-development",
"source": "plugins/java-mcp-development",
- "description": "Complete toolkit for building Model Context Protocol servers in Java using the official MCP Java SDK with reactive streams and Spring Boot integration.",
- "version": "1.0.0"
+ "description": "Build Model Context Protocol (MCP) servers in Java with the official MCP Java SDK 2.x. Includes a skill that generates a server project and an expert agent for guidance.",
+ "version": "2.0.0"
},
{
"name": "java-modernization-studio",
diff --git a/agents/java-mcp-expert.agent.md b/agents/java-mcp-expert.agent.md
index 1b87c4a36..557cc8a3e 100644
--- a/agents/java-mcp-expert.agent.md
+++ b/agents/java-mcp-expert.agent.md
@@ -1,359 +1,97 @@
---
-description: "Expert assistance for building Model Context Protocol servers in Java using reactive streams, the official MCP Java SDK, and Spring Boot integration."
+description: 'Expert assistant for developing Model Context Protocol (MCP) servers in Java with the MCP Java SDK 2.x'
name: "Java MCP Expert"
-model: GPT-4.1
---
# Java MCP Expert
-I'm specialized in helping you build robust, production-ready MCP servers in Java using the official Java SDK. I can assist with:
-
-## Core Capabilities
-
-### Server Architecture
-
-- Setting up McpServer with builder pattern
-- Configuring capabilities (tools, resources, prompts)
-- Implementing stdio and HTTP transports
-- Reactive Streams with Project Reactor
-- Synchronous facade for blocking use cases
-- Spring Boot integration with starters
-
-### Tool Development
-
-- Creating tool definitions with JSON schemas
-- Implementing tool handlers with Mono/Flux
-- Parameter validation and error handling
-- Async tool execution with reactive pipelines
-- Tool list changed notifications
-
-### Resource Management
-
-- Defining resource URIs and metadata
-- Implementing resource read handlers
-- Managing resource subscriptions
-- Resource changed notifications
-- Multi-content responses (text, image, binary)
-
-### Prompt Engineering
-
-- Creating prompt templates with arguments
-- Implementing prompt get handlers
-- Multi-turn conversation patterns
-- Dynamic prompt generation
-- Prompt list changed notifications
-
-### Reactive Programming
-
-- Project Reactor operators and pipelines
-- Mono for single results, Flux for streams
-- Error handling in reactive chains
-- Context propagation for observability
-- Backpressure management
-
-## Code Assistance
-
-I can help you with:
-
-### Maven Dependencies
-
-```xml
-
- io.modelcontextprotocol.sdk
- mcp
- 0.14.1
-
-```
-
-### Server Creation
-
-```java
-McpServer server = McpServerBuilder.builder()
- .serverInfo("my-server", "1.0.0")
- .capabilities(cap -> cap
- .tools(true)
- .resources(true)
- .prompts(true))
- .build();
-```
-
-### Tool Handler
-
-```java
-server.addToolHandler("process", (args) -> {
- return Mono.fromCallable(() -> {
- String result = process(args);
- return ToolResponse.success()
- .addTextContent(result)
- .build();
- }).subscribeOn(Schedulers.boundedElastic());
-});
-```
-
-### Transport Configuration
-
-```java
-StdioServerTransport transport = new StdioServerTransport();
-server.start(transport).subscribe();
-```
-
-### Spring Boot Integration
-
-```java
-@Configuration
-public class McpConfiguration {
- @Bean
- public McpServerConfigurer mcpServerConfigurer() {
- return server -> server
- .serverInfo("spring-server", "1.0.0")
- .capabilities(cap -> cap.tools(true));
- }
-}
-```
-
-## Best Practices
-
-### Reactive Streams
-
-Use Mono for single results, Flux for streams:
-
-```java
-// Single result
-Mono result = Mono.just(
- ToolResponse.success().build()
-);
-
-// Stream of items
-Flux resources = Flux.fromIterable(getResources());
-```
-
-### Error Handling
-
-Proper error handling in reactive chains:
-
-```java
-server.addToolHandler("risky", (args) -> {
- return Mono.fromCallable(() -> riskyOperation(args))
- .map(result -> ToolResponse.success()
- .addTextContent(result)
- .build())
- .onErrorResume(ValidationException.class, e ->
- Mono.just(ToolResponse.error()
- .message("Invalid input")
- .build()))
- .doOnError(e -> log.error("Error", e));
-});
-```
-
-### Logging
-
-Use SLF4J for structured logging:
-
-```java
-private static final Logger log = LoggerFactory.getLogger(MyClass.class);
-
-log.info("Tool called: {}", toolName);
-log.debug("Processing with args: {}", args);
-log.error("Operation failed", exception);
-```
-
-### JSON Schema
-
-Use fluent builder for schemas:
-
-```java
-JsonSchema schema = JsonSchema.object()
- .property("name", JsonSchema.string()
- .description("User's name")
- .required(true))
- .property("age", JsonSchema.integer()
- .minimum(0)
- .maximum(150))
- .build();
-```
-
-## Common Patterns
-
-### Synchronous Facade
-
-For blocking operations:
-
-```java
-McpSyncServer syncServer = server.toSyncServer();
-
-syncServer.addToolHandler("blocking", (args) -> {
- String result = blockingOperation(args);
- return ToolResponse.success()
- .addTextContent(result)
- .build();
-});
-```
-
-### Resource Subscription
-
-Track subscriptions:
-
-```java
-private final Set subscriptions = ConcurrentHashMap.newKeySet();
-
-server.addResourceSubscribeHandler((uri) -> {
- subscriptions.add(uri);
- log.info("Subscribed to {}", uri);
- return Mono.empty();
-});
-```
-
-### Async Operations
-
-Use bounded elastic for blocking calls:
-
-```java
-server.addToolHandler("external", (args) -> {
- return Mono.fromCallable(() -> callExternalApi(args))
- .timeout(Duration.ofSeconds(30))
- .subscribeOn(Schedulers.boundedElastic());
-});
-```
-
-### Context Propagation
-
-Propagate observability context:
-
-```java
-server.addToolHandler("traced", (args) -> {
- return Mono.deferContextual(ctx -> {
- String traceId = ctx.get("traceId");
- log.info("Processing with traceId: {}", traceId);
- return processWithContext(args, traceId);
- });
-});
-```
-
-## Spring Boot Integration
-
-### Configuration
-
-```java
-@Configuration
-public class McpConfig {
- @Bean
- public McpServerConfigurer configurer() {
- return server -> server
- .serverInfo("spring-app", "1.0.0")
- .capabilities(cap -> cap
- .tools(true)
- .resources(true));
- }
-}
-```
-
-### Component-Based Handlers
-
-```java
-@Component
-public class SearchToolHandler implements ToolHandler {
-
- @Override
- public String getName() {
- return "search";
- }
-
- @Override
- public Tool getTool() {
- return Tool.builder()
- .name("search")
- .description("Search for data")
- .inputSchema(JsonSchema.object()
- .property("query", JsonSchema.string().required(true)))
- .build();
- }
-
- @Override
- public Mono handle(JsonNode args) {
- String query = args.get("query").asText();
- return searchService.search(query)
- .map(results -> ToolResponse.success()
- .addTextContent(results)
- .build());
- }
-}
-```
-
-## Testing
-
-### Unit Tests
-
-```java
-@Test
-void testToolHandler() {
- McpServer server = createTestServer();
- McpSyncServer syncServer = server.toSyncServer();
-
- ObjectNode args = new ObjectMapper().createObjectNode()
- .put("key", "value");
-
- ToolResponse response = syncServer.callTool("test", args);
-
- assertFalse(response.isError());
- assertEquals(1, response.getContent().size());
-}
-```
-
-### Reactive Tests
-
-```java
-@Test
-void testReactiveHandler() {
- Mono result = toolHandler.handle(args);
-
- StepVerifier.create(result)
- .expectNextMatches(response -> !response.isError())
- .verifyComplete();
-}
-```
-
-## Platform Support
-
-The Java SDK supports:
-
-- Java 17+ (LTS recommended)
-- Jakarta Servlet 5.0+
-- Spring Boot 3.0+
-- Project Reactor 3.5+
-
-## Architecture
-
-### Modules
-
-- `mcp-core` - Core implementation (stdio, JDK HttpClient, Servlet)
-- `mcp-json` - JSON abstraction layer
-- `mcp-jackson2` - Jackson implementation
-- `mcp` - Convenience bundle (core + Jackson)
-- `mcp-spring` - Spring integrations (WebClient, WebFlux, WebMVC)
-
-### Design Decisions
-
-- **JSON**: Jackson behind abstraction (`mcp-json`)
-- **Async**: Reactive Streams with Project Reactor
-- **HTTP Client**: JDK HttpClient (Java 11+)
-- **HTTP Server**: Jakarta Servlet, Spring WebFlux/WebMVC
-- **Logging**: SLF4J facade
-- **Observability**: Reactor Context
-
-## Ask Me About
-
-- Server setup and configuration
-- Tool, resource, and prompt implementations
-- Reactive Streams patterns with Reactor
-- Spring Boot integration and starters
-- JSON schema construction
-- Error handling strategies
-- Testing reactive code
-- HTTP transport configuration
-- Servlet integration
-- Context propagation for tracing
-- Performance optimization
-- Deployment strategies
-- Maven and Gradle setup
-
-I'm here to help you build efficient, scalable, and idiomatic Java MCP servers. What would you like to work on?
+You are an expert in Model Context Protocol (MCP) servers with the [MCP Java SDK](https://github.com/modelcontextprotocol/java-sdk) 2.x. You know `McpServer`, the sync and async server APIs, Project Reactor, Jackson, Maven, and Spring AI. You know the MCP specification revision [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25), which SDK 2.0.x supports. You also know the revision [2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28), which SDK 3.x will add. You help developers build reliable, production-ready MCP servers.
+
+## Your Expertise
+
+- **Java MCP SDK**: The `io.modelcontextprotocol.sdk` artifacts 2.x, `McpServer`, the tool, resource, prompt, and completion specifications, and the transports
+- **Java Development**: Java 17+, records, lambdas, Maven, and Gradle
+- **Reactive Programming**: Project Reactor (`Mono` and `Flux`) for the async server
+- **MCP Protocol**: The 2025-11-25 specification, and the features that the 2026-07-28 revision deprecates
+- **Transport Types**: stdio, Streamable HTTP (Servlet), and the stateless HTTP transport
+- **Spring Integration**: The MCP starters and the `@McpTool` annotations of Spring AI 2.0+
+- **Tool Design**: Tools with correct JSON schemas, structured output, and annotations
+- **Best Practices**: Tests, error handling, logs, packages, and security
+- **Problem Analysis**: Schema validation errors, transport errors, and stdout corruption
+
+## Your Approach
+
+- **Understand Use Case**: Find out if the server is for local (stdio) or remote (HTTP) use.
+- **Sync by Default**: Use `McpServer.sync(...)` for most cases. Use `McpServer.async(...)` only when the tool code does not block a thread.
+- **SDK 2.x Only**: Do not use code from SDK 0.x or 1.x. Use the builders that take the required fields first, for example `Tool.builder(name, inputSchema)`.
+- **Map Schemas**: Give `inputSchema` and `outputSchema` as `Map` values. The `JsonSchema` record is deprecated.
+- **Structured Output**: Set `outputSchema` and return `structuredContent` for machine-readable data. Also return the same data as text content.
+- **Error Handling**: Return `CallToolResult` with `isError(true)` and a clear message for errors that the model must read. Do not throw an exception or an `McpError` for a tool failure. The client gets a JSON-RPC error, not a tool result.
+- **Test Early**: Test with the MCP Inspector and with an SDK client in a JUnit test before integration.
+
+## Guidelines
+
+- Import the BOM `io.modelcontextprotocol.sdk:mcp-bom:2.0.1` and add the `mcp` artifact. It contains `mcp-core` and the Jackson 3 JSON mapper `mcp-json-jackson3`.
+- Make a stdio server with `McpServer.sync(new StdioServerTransportProvider(McpJsonDefaults.getMapper()))`.
+- Set `serverInfo(name, version)` and enable only the capabilities that the server uses.
+- Tool names must have 1 to 128 characters: letters, digits, `_`, `-`, and `.`. The server builder rejects an incorrect name with an `IllegalArgumentException`. `addTool()` at runtime does not check the name.
+- Write clear tool descriptions and property descriptions. The model uses them to select a tool.
+- The SDK validates the arguments against `inputSchema` and the `structuredContent` against `outputSchema`. A validation failure gives a tool result with `isError` set to `true`.
+- If a tool has an `outputSchema`, return `structuredContent` in each successful result. Without it, the SDK returns a tool result with `isError` set to `true`.
+- The server checks each schema against JSON Schema 2020-12 when you call `build()` or `addTool()`.
+- Set tool hints with `ToolAnnotations`, for example `readOnlyHint(true)` or `destructiveHint(true)`.
+- Report progress with `exchange.progressNotification(...)` only when `request.progressToken()` is not `null`.
+- Get user input with `exchange.createElicitation(ElicitFormRequest.builder(message, requestedSchema).build())`. First, make sure that `exchange.getClientCapabilities().elicitation()` is not `null`.
+- For a flow in the browser, for example OAuth or a payment, use `ElicitUrlRequest.builder(message, url, elicitationId)`. First, make sure that `exchange.getClientCapabilities().elicitation().url()` is not `null`. Do not send credentials in a form elicitation.
+- Do not use sampling (`exchange.createMessage()`). The revision 2026-07-28 deprecates it. Call the LLM provider API directly.
+- Do not use protocol logging (`exchange.loggingNotification()`). The revision 2026-07-28 deprecates it. Log with SLF4J.
+- Do not use roots (`exchange.listRoots()`). The revision 2026-07-28 deprecates it. Get paths from tool arguments, resource URIs, or the server configuration.
+- In a stdio server, do not write to stdout. Stdout is the protocol channel. Send the logs to stderr.
+- In the async server, run code that blocks with `subscribeOn(Schedulers.boundedElastic())`.
+- For a remote server, use Streamable HTTP. The SSE transports are deprecated.
+- The HTTP transports and the Spring AI starters do not check the `Host` and `Origin` headers by default. Always set a `DefaultServerTransportSecurityValidator`.
+- The SDK has no authorization. For a remote server, add authorization in a servlet filter or with Spring Security.
+- For Spring Boot, use Spring AI 2.0+ (`org.springframework.ai`). Do not use the `io.modelcontextprotocol.sdk:mcp-spring-*` artifacts.
+- For browser clients, configure CORS. Allow the `Mcp-*` request headers. Expose the `Mcp-Session-Id` response header.
+- Package a stdio server as one executable JAR with the `maven-shade-plugin`. In Gradle, use the `application` plugin and the start script from `installDist`. Do not use `./gradlew run` as the client command.
+- Test with `McpClient.sync(new StdioClientTransport(...))`. The SDK has no in-memory transport.
+- Release resources with try-with-resources or a shutdown hook.
+
+## Common Scenarios You Excel At
+
+- **New Servers**: Generate complete Maven or Gradle projects with tests
+- **Tool Development**: Write tools that process data or use APIs, files, or databases
+- **Resource Implementation**: Create static resources and resource templates
+- **Prompt Development**: Write reusable prompts with argument completion
+- **Transport Setup**: Configure stdio for local use or Streamable HTTP for remote access
+- **Spring Integration**: Build servers with the Spring AI MCP starters
+- **Problem Analysis**: Find schema validation errors, transport problems, and stdout corruption
+- **Migration**: Upgrade servers from SDK 0.x or 1.x to SDK 2.x
+- **Integration**: Connect servers to databases, APIs, or other services
+- **Tests**: Write JUnit tests that use the SDK client
+
+## Response Style
+
+- Give complete code that the user can copy and run immediately.
+- Put all necessary imports at the top.
+- Add inline comments for important or unclear code.
+- Show the complete file structure and the build file for new projects.
+- Explain the reason for design decisions.
+- Show possible problems and edge cases.
+- Suggest improvements or alternative approaches when they are relevant.
+- Include Maven or Gradle commands for build and test.
+- Format code with the correct Java conventions.
+- Give environment variable examples when necessary.
+
+## Advanced Capabilities You Know
+
+- **Async Server**: Reactive tool handlers with `Mono`
+- **Dynamic Tools**: `addTool()`, `removeTool()`, and `notifyToolsListChanged()` at runtime
+- **Resource Templates**: RFC 6570 URI templates
+- **Completion Support**: Argument completion with `SyncCompletionSpecification`
+- **Stateless Servers**: `HttpServletStatelessServerTransport` for more than one replica
+- **Transport Security**: `DefaultServerTransportSecurityValidator` for `Host` and `Origin` checks
+- **JSON Mapper**: Jackson 3 (`mcp-json-jackson3`)
+- **Spring AI**: `@McpTool`, `@McpResource`, and `@McpPrompt` annotations
+
+You help developers build Java MCP servers that are correct, reliable, well documented, and easy for LLMs to use.
diff --git a/docs/README.agents.md b/docs/README.agents.md
index e9d771ad0..936355131 100644
--- a/docs/README.agents.md
+++ b/docs/README.agents.md
@@ -119,7 +119,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-agents) for guidelines on how to
| [Idea Generator](../agents/simple-app-idea-generator.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fsimple-app-idea-generator.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fsimple-app-idea-generator.agent.md) | Brainstorm and develop new application ideas through fun, interactive questioning until ready for specification creation. |
| [Implementation Plan Generation Mode](../agents/implementation-plan.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fimplementation-plan.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fimplementation-plan.agent.md) | Generate an implementation plan for new features or refactoring existing code. |
| [Interview Prep](../agents/interview-prep.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Finterview-prep.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Finterview-prep.agent.md) | Technical interview coach for software engineers. Runs mock interviews, coaches system design, structures behavioral answers using STAR, and researches companies before interviews. |
-| [Java MCP Expert](../agents/java-mcp-expert.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fjava-mcp-expert.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fjava-mcp-expert.agent.md) | Expert assistance for building Model Context Protocol servers in Java using reactive streams, the official MCP Java SDK, and Spring Boot integration. |
+| [Java MCP Expert](../agents/java-mcp-expert.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fjava-mcp-expert.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fjava-mcp-expert.agent.md) | Expert assistant for developing Model Context Protocol (MCP) servers in Java with the MCP Java SDK 2.x |
| [JFrog Security Agent](../agents/jfrog-sec.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fjfrog-sec.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fjfrog-sec.agent.md) | The dedicated Application Security agent for automated security remediation. Verifies package and version compliance, and suggests vulnerability fixes using JFrog security intelligence. |
| [Kotlin MCP Server Development Expert](../agents/kotlin-mcp-expert.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkotlin-mcp-expert.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkotlin-mcp-expert.agent.md) | Expert assistant for building Model Context Protocol (MCP) servers in Kotlin using the official SDK. |
| [KubeStellar Console](../agents/kubestellar-console.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkubestellar-console.agent.md)
[](https://aka.ms/awesome-copilot/install/agent?url=vscode-insiders%3Achat-agent%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Fagents%2Fkubestellar-console.agent.md) | Kubernetes operations expert for KubeStellar Console — helps you set up the console, configure kc-agent (MCP server), connect clusters, deploy workloads, and query live Kubernetes data via AI chat. |
diff --git a/docs/README.instructions.md b/docs/README.instructions.md
index e3bf08fc8..f511bb0fc 100644
--- a/docs/README.instructions.md
+++ b/docs/README.instructions.md
@@ -127,7 +127,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-instructions) for guidelines on
| [Java 11 to Java 17 Upgrade Guide](../instructions/java-11-to-java-17-upgrade.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-11-to-java-17-upgrade.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-11-to-java-17-upgrade.instructions.md) | Comprehensive best practices for adopting new Java 17 features since the release of Java 11. |
| [Java 17 to Java 21 Upgrade Guide](../instructions/java-17-to-java-21-upgrade.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-17-to-java-21-upgrade.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-17-to-java-21-upgrade.instructions.md) | Comprehensive best practices for adopting new Java 21 features since the release of Java 17. |
| [Java 21 to Java 25 Upgrade Guide](../instructions/java-21-to-java-25-upgrade.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-21-to-java-25-upgrade.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-21-to-java-25-upgrade.instructions.md) | Comprehensive best practices for adopting new Java 25 features since the release of Java 21. |
-| [Java MCP Server Development Guidelines](../instructions/java-mcp-server.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-mcp-server.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-mcp-server.instructions.md) | Best practices and patterns for building Model Context Protocol (MCP) servers in Java using the official MCP Java SDK with reactive streams and Spring integration. |
+| [Java MCP Server Development](../instructions/java-mcp-server.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-mcp-server.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-mcp-server.instructions.md) | Instructions for building Model Context Protocol (MCP) servers in Java with the MCP Java SDK 2.x |
| [Joyride User Scripts Project Assistant](../instructions/joyride-user-project.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjoyride-user-project.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjoyride-user-project.instructions.md) | Expert assistance for Joyride User Script projects - REPL-driven ClojureScript and user space automation of VS Code |
| [Joyride Workspace Automation Assistant](../instructions/joyride-workspace-automation.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjoyride-workspace-automation.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjoyride-workspace-automation.instructions.md) | Expert assistance for Joyride Workspace automation - REPL-driven and user space ClojureScript automation within specific VS Code workspaces |
| [JUnit 5 Assertions Best Practices](../instructions/java-junit5-assertions.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-junit5-assertions.instructions.md)
[](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fjava-junit5-assertions.instructions.md) | Standardizes JUnit 5 (Jupiter) assertions with best practices for performance, readability, and modern features (5.8+). Covers Supplier messages, assertAll, assertThrowsExactly, and performance-critical timeouts. |
diff --git a/docs/README.plugins.md b/docs/README.plugins.md
index fa5fa3155..123e84ab6 100644
--- a/docs/README.plugins.md
+++ b/docs/README.plugins.md
@@ -74,7 +74,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-plugins) for guidelines on how t
| [git-worktree-explorer](../plugins/git-worktree-explorer/README.md) | Visualize the active Git repository through worktrees, branches, commits, and optional GitHub pull request context. | 1 items | branch-visualization, canvas, commit-history, git, repository-topology, worktrees |
| [go-mcp-development](../plugins/go-mcp-development/README.md) | Complete toolkit for building Model Context Protocol (MCP) servers in Go using the official github.com/modelcontextprotocol/go-sdk. Includes instructions for best practices, a prompt for generating servers, and an expert chat mode for guidance. | 2 items | go, golang, mcp, model-context-protocol, server-development, sdk |
| [java-development](../plugins/java-development/README.md) | Comprehensive collection of prompts and instructions for Java development including Spring Boot, Quarkus, testing, documentation, and best practices. | 4 items | java, springboot, quarkus, jpa, junit, javadoc |
-| [java-mcp-development](../plugins/java-mcp-development/README.md) | Complete toolkit for building Model Context Protocol servers in Java using the official MCP Java SDK with reactive streams and Spring Boot integration. | 2 items | java, mcp, model-context-protocol, server-development, sdk, reactive-streams, spring-boot, reactor |
+| [java-mcp-development](../plugins/java-mcp-development/README.md) | Build Model Context Protocol (MCP) servers in Java with the official MCP Java SDK 2.x. Includes a skill that generates a server project and an expert agent for guidance. | 2 items | java, mcp, model-context-protocol, mcp-java-sdk, server-development, spring-ai |
| [java-modernization-studio](../plugins/java-modernization-studio/README.md) | Drive the GitHub Copilot App Modernization for Java workflow from an interactive canvas: environment readiness, repo assessment, prioritized plan and progress, validation gates, and one-click predefined-task runs grounded in the repo's real artifacts. | 1 items | app-modernization, assessment-dashboard, azure-migration, java-modernization, legacy-java, modernization-cockpit, validation-gates |
| [jupyter-notebooks](../plugins/jupyter-notebooks/README.md) | Create, edit, run, save, and checkpoint Jupyter notebooks in an interactive Copilot canvas. | 1 items | data-analysis, interactive-notebooks, jupyter, python |
| [kotlin-mcp-development](../plugins/kotlin-mcp-development/README.md) | Complete toolkit for building Model Context Protocol (MCP) servers in Kotlin using the official io.modelcontextprotocol:kotlin-sdk library. Includes instructions for best practices, a prompt for generating servers, and an expert chat mode for guidance. | 2 items | kotlin, mcp, model-context-protocol, kotlin-multiplatform, server-development, ktor |
diff --git a/docs/README.skills.md b/docs/README.skills.md
index bb2a3e8dc..16f19e1fb 100644
--- a/docs/README.skills.md
+++ b/docs/README.skills.md
@@ -248,7 +248,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
| [java-docs](../skills/java-docs/SKILL.md)
`gh skills install github/awesome-copilot java-docs` | Ensure that Java types are documented with Javadoc comments and follow best practices for documentation. | None |
| [java-helidon](../skills/java-helidon/SKILL.md)
`gh skills install github/awesome-copilot java-helidon` | Get best practices for developing applications with Helidon 4 (SE and MP). Use when working with Helidon SE or Helidon MP, HttpService routing, Helidon DB Client, MicroProfile Config, Helidon Security, or Helidon testing in Java 21+ projects. | None |
| [java-junit](../skills/java-junit/SKILL.md)
`gh skills install github/awesome-copilot java-junit` | Get best practices for JUnit 5 unit testing, including data-driven tests | None |
-| [java-mcp-server-generator](../skills/java-mcp-server-generator/SKILL.md)
`gh skills install github/awesome-copilot java-mcp-server-generator` | Generate a complete Model Context Protocol server project in Java using the official MCP Java SDK with reactive streams and optional Spring Boot integration. | None |
+| [java-mcp-server-generator](../skills/java-mcp-server-generator/SKILL.md)
`gh skills install github/awesome-copilot java-mcp-server-generator` | Use this skill when the user wants to create a Model Context Protocol (MCP) server in Java or in a Spring Boot app. Also use it when the user wants AI agents, such as GitHub Copilot or Claude, to use Java code, an API, or a database. Use it even if the user does not say "MCP". The skill makes a Maven or Gradle project with the MCP Java SDK 2.x, tools, JUnit tests, and client configuration. Do not use it for an MCP client or for a server in another language. | `references/gradle.md`
`references/streamable-http.md` |
| [java-refactoring-extract-method](../skills/java-refactoring-extract-method/SKILL.md)
`gh skills install github/awesome-copilot java-refactoring-extract-method` | Refactoring using Extract Methods in Java Language | None |
| [java-refactoring-remove-parameter](../skills/java-refactoring-remove-parameter/SKILL.md)
`gh skills install github/awesome-copilot java-refactoring-remove-parameter` | Refactoring using Remove Parameter in Java Language | None |
| [java-springboot](../skills/java-springboot/SKILL.md)
`gh skills install github/awesome-copilot java-springboot` | Get best practices for developing applications with Spring Boot. | None |
diff --git a/instructions/java-mcp-server.instructions.md b/instructions/java-mcp-server.instructions.md
index 39f3a53d2..811d0d369 100644
--- a/instructions/java-mcp-server.instructions.md
+++ b/instructions/java-mcp-server.instructions.md
@@ -1,553 +1,315 @@
---
-description: 'Best practices and patterns for building Model Context Protocol (MCP) servers in Java using the official MCP Java SDK with reactive streams and Spring integration.'
-applyTo: "**/*.java, **/pom.xml, **/build.gradle, **/build.gradle.kts"
+description: 'Instructions for building Model Context Protocol (MCP) servers in Java with the MCP Java SDK 2.x'
+applyTo: '**/*.java, **/pom.xml, **/build.gradle, **/build.gradle.kts'
---
-# Java MCP Server Development Guidelines
-
-When building MCP servers in Java, follow these best practices and patterns using the official Java SDK.
-
-## Dependencies
-
-Add the MCP Java SDK to your Maven project:
-
-```xml
-
-
- io.modelcontextprotocol.sdk
- mcp
- 0.14.1
-
-
-```
-
-Or for Gradle:
-
-```kotlin
-dependencies {
- implementation("io.modelcontextprotocol.sdk:mcp:0.14.1")
-}
-```
-
-## Server Setup
-
-Create an MCP server using the builder pattern:
-
-```java
-import io.mcp.server.McpServer;
-import io.mcp.server.McpServerBuilder;
-import io.mcp.server.transport.StdioServerTransport;
-
-McpServer server = McpServerBuilder.builder()
- .serverInfo("my-server", "1.0.0")
- .capabilities(capabilities -> capabilities
- .tools(true)
- .resources(true)
- .prompts(true))
- .build();
-
-// Start with stdio transport
-StdioServerTransport transport = new StdioServerTransport();
-server.start(transport).subscribe();
-```
-
-## Adding Tools
-
-Register tool handlers with the server:
-
-```java
-import io.mcp.server.tool.Tool;
-import io.mcp.server.tool.ToolHandler;
-import reactor.core.publisher.Mono;
-
-// Define a tool
-Tool searchTool = Tool.builder()
- .name("search")
- .description("Search for information")
- .inputSchema(JsonSchema.object()
- .property("query", JsonSchema.string()
- .description("Search query")
- .required(true))
- .property("limit", JsonSchema.integer()
- .description("Maximum results")
- .defaultValue(10)))
- .build();
-
-// Register tool handler
-server.addToolHandler("search", (arguments) -> {
- String query = arguments.get("query").asText();
- int limit = arguments.has("limit")
- ? arguments.get("limit").asInt()
- : 10;
-
- // Perform search
- List results = performSearch(query, limit);
-
- return Mono.just(ToolResponse.success()
- .addTextContent("Found " + results.size() + " results")
- .build());
-});
-```
-
-## Adding Resources
-
-Implement resource handlers for data access:
-
-```java
-import io.mcp.server.resource.Resource;
-import io.mcp.server.resource.ResourceHandler;
-
-// Register resource list handler
-server.addResourceListHandler(() -> {
- List resources = List.of(
- Resource.builder()
- .name("Data File")
- .uri("resource://data/example.txt")
- .description("Example data file")
- .mimeType("text/plain")
- .build()
- );
- return Mono.just(resources);
-});
-
-// Register resource read handler
-server.addResourceReadHandler((uri) -> {
- if (uri.equals("resource://data/example.txt")) {
- String content = loadResourceContent(uri);
- return Mono.just(ResourceContent.text(content, uri));
- }
- throw new ResourceNotFoundException(uri);
-});
-
-// Register resource subscribe handler
-server.addResourceSubscribeHandler((uri) -> {
- subscriptions.add(uri);
- log.info("Client subscribed to {}", uri);
- return Mono.empty();
-});
-```
-
-## Adding Prompts
-
-Implement prompt handlers for templated conversations:
-
-```java
-import io.mcp.server.prompt.Prompt;
-import io.mcp.server.prompt.PromptMessage;
-import io.mcp.server.prompt.PromptArgument;
-
-// Register prompt list handler
-server.addPromptListHandler(() -> {
- List prompts = List.of(
- Prompt.builder()
- .name("analyze")
- .description("Analyze a topic")
- .argument(PromptArgument.builder()
- .name("topic")
- .description("Topic to analyze")
- .required(true)
- .build())
- .argument(PromptArgument.builder()
- .name("depth")
- .description("Analysis depth")
- .required(false)
- .build())
- .build()
- );
- return Mono.just(prompts);
-});
-
-// Register prompt get handler
-server.addPromptGetHandler((name, arguments) -> {
- if (name.equals("analyze")) {
- String topic = arguments.getOrDefault("topic", "general");
- String depth = arguments.getOrDefault("depth", "basic");
-
- List messages = List.of(
- PromptMessage.user("Please analyze this topic: " + topic),
- PromptMessage.assistant("I'll provide a " + depth + " analysis of " + topic)
- );
-
- return Mono.just(PromptResult.builder()
- .description("Analysis of " + topic + " at " + depth + " level")
- .messages(messages)
- .build());
- }
- throw new PromptNotFoundException(name);
-});
-```
-
-## Reactive Streams Pattern
-
-The Java SDK uses Reactive Streams (Project Reactor) for asynchronous processing:
-
-```java
-// Return Mono for single results
-server.addToolHandler("process", (args) -> {
- return Mono.fromCallable(() -> {
- String result = expensiveOperation(args);
- return ToolResponse.success()
- .addTextContent(result)
- .build();
- }).subscribeOn(Schedulers.boundedElastic());
-});
-
-// Return Flux for streaming results
-server.addResourceListHandler(() -> {
- return Flux.fromIterable(getResources())
- .map(r -> Resource.builder()
- .uri(r.getUri())
- .name(r.getName())
- .build())
- .collectList();
-});
-```
-
-## Synchronous Facade
-
-For blocking use cases, use the synchronous API:
-
-```java
-import io.mcp.server.McpSyncServer;
-
-McpSyncServer syncServer = server.toSyncServer();
-
-// Blocking tool handler
-syncServer.addToolHandler("greet", (args) -> {
- String name = args.get("name").asText();
- return ToolResponse.success()
- .addTextContent("Hello, " + name + "!")
- .build();
-});
-```
-
-## Transport Configuration
-
-### Stdio Transport
-
-For local subprocess communication:
-
-```java
-import io.mcp.server.transport.StdioServerTransport;
-
-StdioServerTransport transport = new StdioServerTransport();
-server.start(transport).block();
-```
-
-### HTTP Transport (Servlet)
-
-For HTTP-based servers:
-
-```java
-import io.mcp.server.transport.ServletServerTransport;
-import jakarta.servlet.http.HttpServlet;
-
-public class McpServlet extends HttpServlet {
- private final McpServer server;
- private final ServletServerTransport transport;
-
- public McpServlet() {
- this.server = createMcpServer();
- this.transport = new ServletServerTransport();
- }
-
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
- transport.handleRequest(server, req, resp).block();
- }
-}
-```
-
-## Spring Boot Integration
-
-Use the Spring Boot starter for seamless integration:
-
-```xml
-
- io.modelcontextprotocol.sdk
- mcp-spring-boot-starter
- 0.14.1
-
-```
-
-Configure the server with Spring:
-
-```java
-import org.springframework.context.annotation.Configuration;
-import io.mcp.spring.McpServerConfigurer;
-
-@Configuration
-public class McpConfiguration {
-
- @Bean
- public McpServerConfigurer mcpServerConfigurer() {
- return server -> server
- .serverInfo("spring-server", "1.0.0")
- .capabilities(cap -> cap
- .tools(true)
- .resources(true)
- .prompts(true));
- }
-}
-```
-
-Register handlers as Spring beans:
-
-```java
-import org.springframework.stereotype.Component;
-import io.mcp.spring.ToolHandler;
-
-@Component
-public class SearchToolHandler implements ToolHandler {
-
- @Override
- public String getName() {
- return "search";
- }
-
- @Override
- public Tool getTool() {
- return Tool.builder()
- .name("search")
- .description("Search for information")
- .inputSchema(JsonSchema.object()
- .property("query", JsonSchema.string().required(true)))
- .build();
- }
-
- @Override
- public Mono handle(JsonNode arguments) {
- String query = arguments.get("query").asText();
- return Mono.just(ToolResponse.success()
- .addTextContent("Search results for: " + query)
- .build());
- }
-}
-```
-
-## Error Handling
-
-Use proper error handling with MCP exceptions:
-
-```java
-server.addToolHandler("risky", (args) -> {
- return Mono.fromCallable(() -> {
- try {
- String result = riskyOperation(args);
- return ToolResponse.success()
- .addTextContent(result)
- .build();
- } catch (ValidationException e) {
- return ToolResponse.error()
- .message("Invalid input: " + e.getMessage())
- .build();
- } catch (Exception e) {
- log.error("Unexpected error", e);
- return ToolResponse.error()
- .message("Internal error occurred")
- .build();
- }
- });
-});
-```
-
-## JSON Schema Construction
-
-Use the fluent schema builder:
-
-```java
-import io.mcp.json.JsonSchema;
-
-JsonSchema schema = JsonSchema.object()
- .property("name", JsonSchema.string()
- .description("User's name")
- .minLength(1)
- .maxLength(100)
- .required(true))
- .property("age", JsonSchema.integer()
- .description("User's age")
- .minimum(0)
- .maximum(150))
- .property("email", JsonSchema.string()
- .description("Email address")
- .format("email")
- .required(true))
- .property("tags", JsonSchema.array()
- .items(JsonSchema.string())
- .uniqueItems(true))
- .additionalProperties(false)
- .build();
-```
-
-## Logging and Observability
-
-Use SLF4J for logging:
-
-```java
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-private static final Logger log = LoggerFactory.getLogger(MyMcpServer.class);
-
-server.addToolHandler("process", (args) -> {
- log.info("Tool called: process, args: {}", args);
-
- return Mono.fromCallable(() -> {
- String result = process(args);
- log.debug("Processing completed successfully");
- return ToolResponse.success()
- .addTextContent(result)
- .build();
- }).doOnError(error -> {
- log.error("Processing failed", error);
- });
-});
-```
-
-Propagate context with Reactor:
-
-```java
-import reactor.util.context.Context;
-
-server.addToolHandler("traced", (args) -> {
- return Mono.deferContextual(ctx -> {
- String traceId = ctx.get("traceId");
- log.info("Processing with traceId: {}", traceId);
-
- return Mono.just(ToolResponse.success()
- .addTextContent("Processed")
- .build());
- });
-});
-```
-
-## Testing
-
-Write tests using the synchronous API:
-
-```java
-import org.junit.jupiter.api.Test;
-import static org.assertj.core.Assertions.assertThat;
-
-class McpServerTest {
-
- @Test
- void testToolHandler() {
- McpServer server = createTestServer();
- McpSyncServer syncServer = server.toSyncServer();
-
- JsonNode args = objectMapper.createObjectNode()
- .put("query", "test");
-
- ToolResponse response = syncServer.callTool("search", args);
-
- assertThat(response.isError()).isFalse();
- assertThat(response.getContent()).hasSize(1);
- }
-}
-```
-
-## Jackson Integration
-
-The SDK uses Jackson for JSON serialization. Customize as needed:
-
-```java
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-
-ObjectMapper mapper = new ObjectMapper();
-mapper.registerModule(new JavaTimeModule());
-
-// Use custom mapper with server
-McpServer server = McpServerBuilder.builder()
- .objectMapper(mapper)
- .build();
-```
-
-## Content Types
-
-Support multiple content types in responses:
-
-```java
-import io.mcp.server.content.Content;
-
-server.addToolHandler("multi", (args) -> {
- return Mono.just(ToolResponse.success()
- .addTextContent("Plain text response")
- .addImageContent(imageBytes, "image/png")
- .addResourceContent("resource://data", "application/json", jsonData)
- .build());
-});
-```
-
-## Server Lifecycle
-
-Properly manage server lifecycle:
-
-```java
-import reactor.core.Disposable;
-
-Disposable serverDisposable = server.start(transport).subscribe();
-
-// Graceful shutdown
-Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- log.info("Shutting down MCP server");
- serverDisposable.dispose();
- server.stop().block();
-}));
-```
-
-## Common Patterns
-
-### Request Validation
-
-```java
-server.addToolHandler("validate", (args) -> {
- if (!args.has("required_field")) {
- return Mono.just(ToolResponse.error()
- .message("Missing required_field")
- .build());
- }
-
- return processRequest(args);
-});
-```
-
-### Async Operations
-
-```java
-server.addToolHandler("async", (args) -> {
- return Mono.fromCallable(() -> callExternalApi(args))
- .timeout(Duration.ofSeconds(30))
- .onErrorResume(TimeoutException.class, e ->
- Mono.just(ToolResponse.error()
- .message("Operation timed out")
- .build()))
- .subscribeOn(Schedulers.boundedElastic());
-});
-```
-
-### Resource Caching
-
-```java
-private final Map cache = new ConcurrentHashMap<>();
-
-server.addResourceReadHandler((uri) -> {
- return Mono.fromCallable(() ->
- cache.computeIfAbsent(uri, this::loadResource))
- .map(content -> ResourceContent.text(content, uri));
-});
-```
+# Java MCP Server Development
+
+These instructions apply to the [MCP Java SDK](https://github.com/modelcontextprotocol/java-sdk) 2.x. SDK 2.0.x supports the MCP specification revision [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25) and the earlier revisions. SDK 3.x will add the revision [2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28).
+
+## Instructions
+
+- Import the BOM `io.modelcontextprotocol.sdk:mcp-bom:2.0.1` and add the `io.modelcontextprotocol.sdk:mcp` artifact.
+- In Gradle, import the BOM with `implementation(platform("io.modelcontextprotocol.sdk:mcp-bom:2.0.1"))`. Also add `testRuntimeOnly("org.junit.platform:junit-platform-launcher")`. Without it, the `test` task fails.
+- The `mcp` artifact contains `mcp-core` and the Jackson 3 JSON mapper `mcp-json-jackson3`.
+- Do not use code from SDK 0.x or 1.x. Read the [migration guide](https://github.com/modelcontextprotocol/java-sdk/blob/main/MIGRATION-2.0.md) when you update a server.
+- The packages are `io.modelcontextprotocol.server`, `io.modelcontextprotocol.spec`, and `io.modelcontextprotocol.json`. The protocol types are nested in `McpSchema`.
+- Make a server with `McpServer.sync(transport)` or `McpServer.async(transport)`. Set `serverInfo(name, version)` and `capabilities(...)`, and then call `build()`.
+- Use the sync server for most servers. Use the async server with Project Reactor (`Mono`) only when the tool code does not block a thread.
+- For stdio transport, use `new StdioServerTransportProvider(McpJsonDefaults.getMapper())`.
+- Enable only the capabilities that the server uses: `ServerCapabilities.builder().tools(false).resources(false, true).prompts(true).completions().build()`. `tools(listChanged)` and `prompts(listChanged)` enable list change notifications. `resources(subscribe, listChanged)` also enables resource subscriptions.
+- Register tools with `SyncToolSpecification.builder().tool(tool).callHandler((exchange, request) -> ...)`. The handler gets the arguments as a `Map` from `request.arguments()`.
+- Use the builders that take the required fields first: `Tool.builder(name, inputSchema)`, `Resource.builder(uri, name)`, `ResourceTemplate.builder(uriTemplate, name)`, `Prompt.builder(name)`, `TextContent.builder(text)`, and `ReadResourceResult.builder(contents)`. The builders without arguments are deprecated.
+- A required field must not be `null`. A `null` value causes an `IllegalArgumentException`.
+- Tool names must have 1 to 128 characters: letters, digits, `_`, `-`, and `.`. The server builder rejects an incorrect name with an `IllegalArgumentException`. `addTool()` at runtime does not check the name.
+- Give `inputSchema` and `outputSchema` as `Map` values. The `JsonSchema` record is deprecated.
+- The server checks each schema against JSON Schema 2020-12 when you call `build()` or `addTool()`. An incorrect schema causes an `IllegalArgumentException`.
+- The SDK validates the arguments against `inputSchema` before it calls the handler. It also validates `structuredContent` against `outputSchema`. If the validation fails, the client gets a tool result with `isError` set to `true`.
+- If a tool has an `outputSchema`, return `structuredContent` in each successful result. Without it, the SDK returns a tool result with `isError` set to `true`.
+- For machine-readable output, set `outputSchema` on the tool and return `CallToolResult.builder().structuredContent(map)`. Also add the same data as text content for older clients.
+- Set tool hints with `ToolAnnotations.builder().readOnlyHint(true).build()`. Use `destructiveHint(true)` for a destructive tool.
+- Return `CallToolResult.builder().addTextContent(message).isError(true).build()` for an error that the model must read.
+- Do not throw an exception or an `McpError` for a tool failure. The client gets a JSON-RPC error, not a tool result. Many hosts do not show this error to the model.
+- Use `McpError.builder(McpSchema.ErrorCodes.INVALID_PARAMS).message(...).build()` only for a protocol error, for example an unknown resource URI.
+- Register resources with `SyncResourceSpecification`, resource templates with `SyncResourceTemplateSpecification`, prompts with `SyncPromptSpecification`, and completions with `SyncCompletionSpecification`.
+- Define resource templates with RFC 6570 URI templates, for example `users://{id}`.
+- Report progress with `exchange.progressNotification(...)` only when `request.progressToken()` is not `null`.
+- Get user input with `exchange.createElicitation(ElicitFormRequest.builder(message, requestedSchema).build())`. First, make sure that `exchange.getClientCapabilities().elicitation()` is not `null`.
+- For a flow in the browser, for example OAuth or a payment, use `ElicitUrlRequest.builder(message, url, elicitationId)`. First, make sure that `exchange.getClientCapabilities().elicitation().url()` is not `null`. Do not send credentials in a form elicitation.
+- Do not use sampling (`exchange.createMessage()`). The revision 2026-07-28 deprecates it. Call the LLM provider API directly.
+- Do not use protocol logging (`exchange.loggingNotification()`). The revision 2026-07-28 deprecates it. Log with SLF4J.
+- Do not use roots (`exchange.listRoots()`). The revision 2026-07-28 deprecates it. Get paths from tool arguments, resource URIs, or the server configuration.
+- Add or remove tools at runtime with `server.addTool(...)` and `server.removeTool(name)`. Then call `server.notifyToolsListChanged()`.
+- In a stdio server, stdout is the protocol channel. Do not call `System.out.println()`. Use `slf4j-simple` (it writes to stderr), or set the target of the Logback console appender to `System.err`.
+- For a remote server, use Streamable HTTP. The SSE transports are deprecated.
+- For a servlet container, use `HttpServletStreamableServerTransportProvider`. For a server without sessions, use `HttpServletStatelessServerTransport`.
+- The HTTP transports do not check the `Host` and `Origin` headers by default. Always set `securityValidator(DefaultServerTransportSecurityValidator.builder()...build())`. This also applies to the Spring AI starters.
+- The SDK has no authorization. For a remote server, add authorization in a servlet filter or with Spring Security.
+- For Spring Boot, use the MCP starters of [Spring AI](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html) 2.0 or later, for example `spring-ai-starter-mcp-server-webmvc`. Do not use the `io.modelcontextprotocol.sdk:mcp-spring-*` artifacts.
+- In Spring AI, write tools as bean methods with `@McpTool` and `@McpToolParam` (from `org.springframework.ai.mcp.annotation`).
+- For browser clients, configure CORS. Allow the `Mcp-*` request headers. Expose the `Mcp-Session-Id` response header.
+- Package a stdio server as one executable JAR with the `maven-shade-plugin`. In Gradle, use the `application` plugin and the start script from `installDist`. Do not use `./gradlew run` as the client command.
+- Test a server with `McpClient.sync(new StdioClientTransport(params, McpJsonDefaults.getMapper()))`. The SDK has no in-memory transport.
+- Test servers manually with the MCP Inspector.
## Best Practices
-1. **Use Reactive Streams** for async operations and backpressure
-2. **Leverage Spring Boot** starter for enterprise applications
-3. **Implement proper error handling** with specific error messages
-4. **Use SLF4J** for logging, not System.out
-5. **Validate inputs** in tool and prompt handlers
-6. **Support graceful shutdown** with proper resource cleanup
-7. **Use bounded elastic scheduler** for blocking operations
-8. **Propagate context** for observability in reactive chains
-9. **Test with synchronous API** for simplicity
-10. **Follow Java naming conventions** (camelCase for methods, PascalCase for classes)
+- Give each tool one responsibility.
+- Write clear tool descriptions. The description tells the model when to use the tool.
+- Add a `description` to each property in `inputSchema`.
+- Put each tool specification in its own method. This keeps the tools easy to test.
+- In the async server, do not block the event loop. Run code that blocks with `Mono.fromCallable(...).subscribeOn(Schedulers.boundedElastic())`.
+- Use environment variables for configuration.
+- Be careful when a tool gives access to the file system or the network.
+
+## Common Patterns
+
+The snippets do not show all imports. The protocol types are nested in `io.modelcontextprotocol.spec.McpSchema`. `Mono` is in `reactor.core.publisher`, and `Schedulers` is in `reactor.core.scheduler`.
+
+### Basic Server Setup (stdio)
+
+```java
+import java.util.List;
+import java.util.Map;
+
+import io.modelcontextprotocol.json.McpJsonDefaults;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification;
+import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
+import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
+import io.modelcontextprotocol.spec.McpSchema.Tool;
+
+public final class McpServerApp {
+
+ static SyncToolSpecification addTool() {
+ Tool tool = Tool.builder("add", Map.of(
+ "type", "object",
+ "properties", Map.of(
+ "a", Map.of("type", "integer", "description", "First number"),
+ "b", Map.of("type", "integer", "description", "Second number")),
+ "required", List.of("a", "b")))
+ .description("Add two integers.")
+ .build();
+
+ return SyncToolSpecification.builder()
+ .tool(tool)
+ .callHandler((exchange, request) -> {
+ long a = ((Number) request.arguments().get("a")).longValue();
+ long b = ((Number) request.arguments().get("b")).longValue();
+ return CallToolResult.builder().addTextContent(String.valueOf(a + b)).build();
+ })
+ .build();
+ }
+
+ public static void main(String[] args) {
+ McpServer.sync(new StdioServerTransportProvider(McpJsonDefaults.getMapper()))
+ .serverInfo("my-server", "0.1.0")
+ .capabilities(ServerCapabilities.builder().tools(false).build())
+ .tools(addTool())
+ .build();
+ }
+}
+```
+
+### Tool with Structured Output
+
+```java
+static SyncToolSpecification wordCountTool() {
+ Tool tool = Tool.builder("word_count", Map.of(
+ "type", "object",
+ "properties", Map.of("text", Map.of("type", "string")),
+ "required", List.of("text")))
+ .description("Count the words in a text.")
+ .outputSchema(Map.of(
+ "type", "object",
+ "properties", Map.of("words", Map.of("type", "integer")),
+ "required", List.of("words")))
+ .annotations(ToolAnnotations.builder().readOnlyHint(true).build())
+ .build();
+
+ return SyncToolSpecification.builder()
+ .tool(tool)
+ .callHandler((exchange, request) -> {
+ String text = (String) request.arguments().get("text");
+ int words = text.isBlank() ? 0 : text.trim().split("\\s+").length;
+ return CallToolResult.builder()
+ .addTextContent("{\"words\":" + words + "}")
+ .structuredContent(Map.of("words", words))
+ .build();
+ })
+ .build();
+}
+```
+
+### Error Handling
+
+```java
+static SyncToolSpecification divideTool() {
+ Tool tool = Tool.builder("divide", Map.of(
+ "type", "object",
+ "properties", Map.of("a", Map.of("type", "number"), "b", Map.of("type", "number")),
+ "required", List.of("a", "b")))
+ .description("Divide a by b.")
+ .build();
+
+ return SyncToolSpecification.builder()
+ .tool(tool)
+ .callHandler((exchange, request) -> {
+ double a = ((Number) request.arguments().get("a")).doubleValue();
+ double b = ((Number) request.arguments().get("b")).doubleValue();
+ if (b == 0) {
+ return CallToolResult.builder().addTextContent("b must not be zero.").isError(true).build();
+ }
+ return CallToolResult.builder().addTextContent(String.valueOf(a / b)).build();
+ })
+ .build();
+}
+```
+
+### Resources and Resource Templates
+
+```java
+static SyncResourceSpecification configResource() {
+ return new SyncResourceSpecification(
+ Resource.builder("config://app", "app-config").mimeType("application/json").build(),
+ (exchange, request) -> ReadResourceResult.builder(List.of(
+ TextResourceContents.builder(request.uri(), "{\"debug\":false}")
+ .mimeType("application/json")
+ .build()))
+ .build());
+}
+
+static SyncResourceTemplateSpecification userResource() {
+ return new SyncResourceTemplateSpecification(
+ ResourceTemplate.builder("users://{id}", "user").build(),
+ (exchange, request) -> {
+ String id = request.uri().substring("users://".length());
+ return ReadResourceResult.builder(List.of(
+ TextResourceContents.builder(request.uri(), "User " + id).build()))
+ .build();
+ });
+}
+```
+
+Register them with `.resources(configResource())` and `.resourceTemplates(userResource())`.
+
+### Prompt with Completion
+
+```java
+static SyncPromptSpecification reviewPrompt() {
+ Prompt prompt = Prompt.builder("code-review")
+ .description("Review code in a language.")
+ .arguments(List.of(PromptArgument.builder("language").required(true).build()))
+ .build();
+
+ return new SyncPromptSpecification(prompt, (exchange, request) -> GetPromptResult.builder(List.of(
+ PromptMessage.builder(Role.USER,
+ TextContent.builder("Review this " + request.arguments().get("language") + " code.").build())
+ .build()))
+ .build());
+}
+
+static SyncCompletionSpecification languageCompletion() {
+ List languages = List.of("java", "kotlin", "scala");
+ return new SyncCompletionSpecification(new PromptReference("code-review"),
+ (exchange, request) -> new CompleteResult(new CompleteResult.CompleteCompletion(
+ languages.stream().filter(l -> l.startsWith(request.argument().value())).toList(), null, false)));
+}
+```
+
+Register them with `.prompts(reviewPrompt())` and `.completions(languageCompletion())`.
+
+### Async Tool with Progress
+
+```java
+static AsyncToolSpecification lookupTool() {
+ Tool tool = Tool.builder("lookup", Map.of(
+ "type", "object",
+ "properties", Map.of("query", Map.of("type", "string")),
+ "required", List.of("query")))
+ .description("Look up a query in a slow backend.")
+ .build();
+
+ return AsyncToolSpecification.builder()
+ .tool(tool)
+ .callHandler((exchange, request) -> {
+ Object token = request.progressToken();
+ Mono progress = token == null ? Mono.empty()
+ : exchange.progressNotification(
+ ProgressNotification.builder(token, 0.5).total(1.0).message("Halfway").build());
+ return progress.then(Mono.fromCallable(() -> slowLookup((String) request.arguments().get("query")))
+ .subscribeOn(Schedulers.boundedElastic())
+ .map(text -> CallToolResult.builder().addTextContent(text).build()));
+ })
+ .build();
+}
+```
+
+Register it on `McpServer.async(transport)`.
+
+### Streamable HTTP Server (Servlet)
+
+```java
+HttpServletStreamableServerTransportProvider transport = HttpServletStreamableServerTransportProvider.builder()
+ .jsonMapper(McpJsonDefaults.getMapper())
+ .mcpEndpoint("/mcp")
+ .securityValidator(DefaultServerTransportSecurityValidator.builder()
+ .allowedHost("localhost:8080")
+ .allowedOrigin("http://localhost:*")
+ .build())
+ .build();
+
+McpServer.sync(transport)
+ .serverInfo("my-server", "0.1.0")
+ .capabilities(ServerCapabilities.builder().tools(false).build())
+ .tools(addTool())
+ .build();
+```
+
+The transport is a Jakarta Servlet. Register it in a servlet container at `/mcp` with async support.
+
+### Spring AI Tool
+
+```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;
+ }
+}
+```
+
+### Integration Test
+
+```java
+class McpServerAppTest {
+
+ private static McpSyncClient client;
+
+ @BeforeAll
+ static void startServer() {
+ String java = Path.of(System.getProperty("java.home"), "bin", "java").toString();
+ ServerParameters server = ServerParameters.builder(java)
+ .args("-cp", System.getProperty("java.class.path"), McpServerApp.class.getName())
+ .build();
+ client = McpClient.sync(new StdioClientTransport(server, McpJsonDefaults.getMapper())).build();
+ client.initialize();
+ }
+
+ @AfterAll
+ static void stopServer() {
+ client.close();
+ }
+
+ @Test
+ void add() {
+ CallToolResult result = client.callTool(
+ CallToolRequest.builder("add").arguments(Map.of("a", 2, "b", 3)).build());
+ assertEquals("5", ((TextContent) result.content().get(0)).text());
+ }
+}
+```
diff --git a/plugins/java-mcp-development/README.md b/plugins/java-mcp-development/README.md
index 17b1e4712..266fe0b3a 100644
--- a/plugins/java-mcp-development/README.md
+++ b/plugins/java-mcp-development/README.md
@@ -1,6 +1,6 @@
# Java MCP Server Development Plugin
-Complete toolkit for building Model Context Protocol servers in Java using the official MCP Java SDK with reactive streams and Spring Boot integration.
+Build Model Context Protocol (MCP) servers in Java with the official MCP Java SDK 2.x. Includes a skill that generates a server project and an expert agent for guidance.
## Installation
@@ -13,15 +13,15 @@ copilot plugin install java-mcp-development@awesome-copilot
### Commands (Slash Commands)
-| Command | Description |
-|---------|-------------|
-| `/java-mcp-development:java-mcp-server-generator` | Generate a complete Model Context Protocol server project in Java using the official MCP Java SDK with reactive streams and optional Spring Boot integration. |
+| Command | Description |
+| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
+| `/java-mcp-development:java-mcp-server-generator` | Generate a Maven or Gradle project for a Java MCP server with the MCP Java SDK 2.x, JUnit tests, and client configuration |
### Agents
-| Agent | Description |
-|-------|-------------|
-| `java-mcp-expert` | Expert assistance for building Model Context Protocol servers in Java using reactive streams, the official MCP Java SDK, and Spring Boot integration. |
+| Agent | Description |
+| ----------------- | ------------------------------------------------------------------------------------------------------ |
+| `java-mcp-expert` | Expert assistant for developing Model Context Protocol (MCP) servers in Java with the MCP Java SDK 2.x |
## Source
diff --git a/plugins/java-mcp-development/plugin.json b/plugins/java-mcp-development/plugin.json
index 46334adf6..9a4f639d9 100644
--- a/plugins/java-mcp-development/plugin.json
+++ b/plugins/java-mcp-development/plugin.json
@@ -1,8 +1,8 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "java-mcp-development",
- "description": "Complete toolkit for building Model Context Protocol servers in Java using the official MCP Java SDK with reactive streams and Spring Boot integration.",
- "version": "1.0.0",
+ "description": "Build Model Context Protocol (MCP) servers in Java with the official MCP Java SDK 2.x. Includes a skill that generates a server project and an expert agent for guidance.",
+ "version": "2.0.0",
"author": {
"name": "Awesome Copilot Community"
},
@@ -12,11 +12,9 @@
"java",
"mcp",
"model-context-protocol",
+ "mcp-java-sdk",
"server-development",
- "sdk",
- "reactive-streams",
- "spring-boot",
- "reactor"
+ "spring-ai"
],
"extensions": {
"com.github.awesome-copilot": {
diff --git a/skills/java-mcp-server-generator/SKILL.md b/skills/java-mcp-server-generator/SKILL.md
index 88b366916..0faef0bc9 100644
--- a/skills/java-mcp-server-generator/SKILL.md
+++ b/skills/java-mcp-server-generator/SKILL.md
@@ -1,756 +1,302 @@
---
name: java-mcp-server-generator
-description: 'Generate a complete Model Context Protocol server project in Java using the official MCP Java SDK with reactive streams and optional Spring Boot integration.'
+description: 'Use this skill when the user wants to create a Model Context Protocol (MCP) server in Java or in a Spring Boot app. Also use it when the user wants AI agents, such as GitHub Copilot or Claude, to use Java code, an API, or a database. Use it even if the user does not say "MCP". The skill makes a Maven or Gradle project with the MCP Java SDK 2.x, tools, JUnit tests, and client configuration. Do not use it for an MCP client or for a server in another language.'
+compatibility: 'JDK 17 or later, Maven 3.9 or later or Gradle 9 or later, and network access to download dependencies. The MCP Inspector also uses Node.js.'
---
# Java MCP Server Generator
-Generate a complete, production-ready MCP server in Java using the official Java SDK with Maven or Gradle.
+Create a Model Context Protocol (MCP) server in Java with the official [MCP Java SDK](https://github.com/modelcontextprotocol/java-sdk) 2.x. SDK 2.0.x supports the MCP specification revision [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25) and the earlier revisions. SDK 3.x will add the revision [2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28).
-## Project Generation
+## Workflow
-When asked to create a Java MCP server, generate a complete project with this structure:
+Copy this checklist and track your progress:
-```
-my-mcp-server/
-├── pom.xml (or build.gradle.kts)
-├── src/
-│ ├── main/
-│ │ ├── java/
-│ │ │ └── com/example/mcp/
-│ │ │ ├── McpServerApplication.java
-│ │ │ ├── config/
-│ │ │ │ └── ServerConfiguration.java
-│ │ │ ├── tools/
-│ │ │ │ ├── ToolDefinitions.java
-│ │ │ │ └── ToolHandlers.java
-│ │ │ ├── resources/
-│ │ │ │ ├── ResourceDefinitions.java
-│ │ │ │ └── ResourceHandlers.java
-│ │ │ └── prompts/
-│ │ │ ├── PromptDefinitions.java
-│ │ │ └── PromptHandlers.java
-│ │ └── resources/
-│ │ └── application.properties (if using Spring)
-│ └── test/
-│ └── java/
-│ └── com/example/mcp/
-│ └── McpServerTest.java
-└── README.md
+- [ ] Step 1: Create the project.
+- [ ] Step 2: Write the server.
+- [ ] Step 3: Write the tests.
+- [ ] Step 4: Build until the tests pass.
+- [ ] Step 5: Configure the client.
+
+### Step 1: Create the project
+
+Use Maven by default. If the project uses Gradle or the user asks for Gradle, read [references/gradle.md](references/gradle.md). Use it for Step 1, Step 4, and Step 5.
+
+Make this layout:
+
+```text
+project-name/
+├── pom.xml
+└── src/
+ ├── main/java/com/example/McpServerApp.java
+ └── test/java/com/example/McpServerAppTest.java
```
-## Maven pom.xml Template
+Write `pom.xml`:
```xml
- 4.0.0
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
- com.example
- my-mcp-server
- 1.0.0
- jar
+ com.example
+ mcp-server-demo
+ 0.1.0
- My MCP Server
- Model Context Protocol server implementation
-
-
- 17
- 17
- 17
- UTF-8
- 0.14.1
- 2.0.9
- 1.4.11
- 5.10.0
-
+
+ 17
+ UTF-8
+
+
-
-
- io.modelcontextprotocol.sdk
- mcp
- ${mcp.version}
-
-
-
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
-
-
- ch.qos.logback
- logback-classic
- ${logback.version}
-
-
-
-
- org.junit.jupiter
- junit-jupiter
- ${junit.version}
- test
-
-
- io.projectreactor
- reactor-test
- test
-
+
+ io.modelcontextprotocol.sdk
+ mcp-bom
+ 2.0.1
+ pom
+ import
+
+
+ org.junit
+ junit-bom
+ 6.1.3
+ pom
+ import
+
+
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.11.0
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.1.2
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 3.5.0
-
-
- package
-
- shade
-
-
-
-
- com.example.mcp.McpServerApplication
-
-
-
-
-
-
-
-
+
+
+ io.modelcontextprotocol.sdk
+ mcp
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.17
+ runtime
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.2
+
+
+ package
+
+ shade
+
+
+ false
+
+
+ com.example.McpServerApp
+
+
+
+
+
+
+
+
```
-## Gradle build.gradle.kts Template
+- The `mcp` artifact contains `mcp-core` and the Jackson 3 JSON mapper `mcp-json-jackson3`.
+- `slf4j-simple` writes the SDK logs to stderr.
+- The shade plugin makes one executable JAR with all dependencies.
-```kotlin
-plugins {
- id("java")
- id("application")
-}
+### Step 2: Write the server
-group = "com.example"
-version = "1.0.0"
-
-java {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
-}
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- // MCP Java SDK
- implementation("io.modelcontextprotocol.sdk:mcp:0.14.1")
-
- // Logging
- implementation("org.slf4j:slf4j-api:2.0.9")
- implementation("ch.qos.logback:logback-classic:1.4.11")
-
- // Testing
- testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
- testImplementation("io.projectreactor:reactor-test:3.5.0")
-}
-
-application {
- mainClass.set("com.example.mcp.McpServerApplication")
-}
-
-tasks.test {
- useJUnitPlatform()
-}
-```
-
-## McpServerApplication.java Template
+Start from this template:
```java
-package com.example.mcp;
+package com.example;
-import com.example.mcp.tools.ToolHandlers;
-import com.example.mcp.resources.ResourceHandlers;
-import com.example.mcp.prompts.PromptHandlers;
-import io.mcp.server.McpServer;
-import io.mcp.server.McpServerBuilder;
-import io.mcp.server.transport.StdioServerTransport;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import reactor.core.Disposable;
+import java.util.List;
+import java.util.Map;
+
+import io.modelcontextprotocol.json.McpJsonDefaults;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification;
+import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
+import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
+import io.modelcontextprotocol.spec.McpSchema.Tool;
+
+public final class McpServerApp {
+
+ static SyncToolSpecification divideTool() {
+ Tool tool = Tool.builder("divide", Map.of(
+ "type", "object",
+ "properties", Map.of(
+ "a", Map.of("type", "number", "description", "Dividend"),
+ "b", Map.of("type", "number", "description", "Divisor")),
+ "required", List.of("a", "b")))
+ .description("Divide a by b.")
+ .build();
+
+ return SyncToolSpecification.builder()
+ .tool(tool)
+ .callHandler((exchange, request) -> {
+ double a = ((Number) request.arguments().get("a")).doubleValue();
+ double b = ((Number) request.arguments().get("b")).doubleValue();
+ if (b == 0) {
+ return CallToolResult.builder().addTextContent("b must not be zero.").isError(true).build();
+ }
+ return CallToolResult.builder().addTextContent(String.valueOf(a / b)).build();
+ })
+ .build();
+ }
-public class McpServerApplication {
-
- private static final Logger log = LoggerFactory.getLogger(McpServerApplication.class);
-
public static void main(String[] args) {
- log.info("Starting MCP Server...");
-
- try {
- McpServer server = createServer();
- StdioServerTransport transport = new StdioServerTransport();
-
- // Start server
- Disposable serverDisposable = server.start(transport).subscribe();
-
- // Graceful shutdown
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- log.info("Shutting down MCP server");
- serverDisposable.dispose();
- server.stop().block();
- }));
-
- log.info("MCP Server started successfully");
-
- // Keep running
- Thread.currentThread().join();
-
- } catch (Exception e) {
- log.error("Failed to start MCP server", e);
- System.exit(1);
- }
- }
-
- private static McpServer createServer() {
- McpServer server = McpServerBuilder.builder()
- .serverInfo("my-mcp-server", "1.0.0")
- .capabilities(capabilities -> capabilities
- .tools(true)
- .resources(true)
- .prompts(true))
- .build();
-
- // Register handlers
- ToolHandlers.register(server);
- ResourceHandlers.register(server);
- PromptHandlers.register(server);
-
- return server;
+ McpServer.sync(new StdioServerTransportProvider(McpJsonDefaults.getMapper()))
+ .serverInfo("demo", "0.1.0")
+ .capabilities(ServerCapabilities.builder().tools(false).build())
+ .tools(divideTool())
+ .build();
}
}
```
-## ToolDefinitions.java Template
+Then change the template to match the request of the user:
+
+1. Replace `divideTool()` with the tools that the user asks for. Make one method for each tool.
+2. Give each tool a JSON Schema `inputSchema` and a `description`. The description tells the model when to use the tool.
+3. When the client must get machine-readable data, add `.outputSchema(Map)` to the tool. Return the data with `CallToolResult.builder().structuredContent(map)`. Also add the same data as text content for older clients.
+4. Mark a read-only tool with `.annotations(ToolAnnotations.builder().readOnlyHint(true).build())`. Use `destructiveHint(true)` for a destructive tool.
+5. Add resources, resource templates, and prompts only when the user asks for them. Also enable them in `ServerCapabilities`.
+
+Use stdio by default. Read [references/streamable-http.md](references/streamable-http.md) only when the user asks for a remote server, an HTTP server, or Spring Boot.
+
+### Step 3: Write the tests
+
+The test starts the server as a subprocess and connects to it with the SDK client. Write a test for each tool and for each error result:
```java
-package com.example.mcp.tools;
+package com.example;
-import io.mcp.json.JsonSchema;
-import io.mcp.server.tool.Tool;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
-import java.util.List;
-
-public class ToolDefinitions {
-
- public static List getTools() {
- return List.of(
- createGreetTool(),
- createCalculateTool()
- );
- }
-
- private static Tool createGreetTool() {
- return Tool.builder()
- .name("greet")
- .description("Generate a greeting message")
- .inputSchema(JsonSchema.object()
- .property("name", JsonSchema.string()
- .description("Name to greet")
- .required(true)))
- .build();
- }
-
- private static Tool createCalculateTool() {
- return Tool.builder()
- .name("calculate")
- .description("Perform mathematical calculations")
- .inputSchema(JsonSchema.object()
- .property("operation", JsonSchema.string()
- .description("Operation to perform")
- .enumValues(List.of("add", "subtract", "multiply", "divide"))
- .required(true))
- .property("a", JsonSchema.number()
- .description("First operand")
- .required(true))
- .property("b", JsonSchema.number()
- .description("Second operand")
- .required(true)))
- .build();
- }
-}
-```
-
-## ToolHandlers.java Template
-
-```java
-package com.example.mcp.tools;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import io.mcp.server.McpServer;
-import io.mcp.server.tool.ToolResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import reactor.core.publisher.Mono;
-
-public class ToolHandlers {
-
- private static final Logger log = LoggerFactory.getLogger(ToolHandlers.class);
-
- public static void register(McpServer server) {
- // Register tool list handler
- server.addToolListHandler(() -> {
- log.debug("Listing available tools");
- return Mono.just(ToolDefinitions.getTools());
- });
-
- // Register greet handler
- server.addToolHandler("greet", ToolHandlers::handleGreet);
-
- // Register calculate handler
- server.addToolHandler("calculate", ToolHandlers::handleCalculate);
- }
-
- private static Mono handleGreet(JsonNode arguments) {
- log.info("Greet tool called");
-
- if (!arguments.has("name")) {
- return Mono.just(ToolResponse.error()
- .message("Missing 'name' parameter")
- .build());
- }
-
- String name = arguments.get("name").asText();
- String greeting = "Hello, " + name + "! Welcome to MCP.";
-
- log.debug("Generated greeting for: {}", name);
-
- return Mono.just(ToolResponse.success()
- .addTextContent(greeting)
- .build());
- }
-
- private static Mono handleCalculate(JsonNode arguments) {
- log.info("Calculate tool called");
-
- if (!arguments.has("operation") || !arguments.has("a") || !arguments.has("b")) {
- return Mono.just(ToolResponse.error()
- .message("Missing required parameters")
- .build());
- }
-
- String operation = arguments.get("operation").asText();
- double a = arguments.get("a").asDouble();
- double b = arguments.get("b").asDouble();
-
- double result;
- switch (operation) {
- case "add":
- result = a + b;
- break;
- case "subtract":
- result = a - b;
- break;
- case "multiply":
- result = a * b;
- break;
- case "divide":
- if (b == 0) {
- return Mono.just(ToolResponse.error()
- .message("Division by zero")
- .build());
- }
- result = a / b;
- break;
- default:
- return Mono.just(ToolResponse.error()
- .message("Unknown operation: " + operation)
- .build());
- }
-
- log.debug("Calculation: {} {} {} = {}", a, operation, b, result);
-
- return Mono.just(ToolResponse.success()
- .addTextContent("Result: " + result)
- .build());
- }
-}
-```
-
-## ResourceDefinitions.java Template
-
-```java
-package com.example.mcp.resources;
-
-import io.mcp.server.resource.Resource;
-
-import java.util.List;
-
-public class ResourceDefinitions {
-
- public static List getResources() {
- return List.of(
- Resource.builder()
- .name("Example Data")
- .uri("resource://data/example")
- .description("Example resource data")
- .mimeType("application/json")
- .build(),
- Resource.builder()
- .name("Configuration")
- .uri("resource://config")
- .description("Server configuration")
- .mimeType("application/json")
- .build()
- );
- }
-}
-```
-
-## ResourceHandlers.java Template
-
-```java
-package com.example.mcp.resources;
-
-import io.mcp.server.McpServer;
-import io.mcp.server.resource.ResourceContent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import reactor.core.publisher.Mono;
-
-import java.time.Instant;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class ResourceHandlers {
-
- private static final Logger log = LoggerFactory.getLogger(ResourceHandlers.class);
- private static final Map subscriptions = new ConcurrentHashMap<>();
-
- public static void register(McpServer server) {
- // Register resource list handler
- server.addResourceListHandler(() -> {
- log.debug("Listing available resources");
- return Mono.just(ResourceDefinitions.getResources());
- });
-
- // Register resource read handler
- server.addResourceReadHandler(ResourceHandlers::handleRead);
-
- // Register resource subscribe handler
- server.addResourceSubscribeHandler(ResourceHandlers::handleSubscribe);
-
- // Register resource unsubscribe handler
- server.addResourceUnsubscribeHandler(ResourceHandlers::handleUnsubscribe);
- }
-
- private static Mono handleRead(String uri) {
- log.info("Reading resource: {}", uri);
-
- switch (uri) {
- case "resource://data/example":
- String jsonData = String.format(
- "{\"message\":\"Example resource data\",\"timestamp\":\"%s\"}",
- Instant.now()
- );
- return Mono.just(ResourceContent.text(jsonData, uri, "application/json"));
-
- case "resource://config":
- String config = "{\"serverName\":\"my-mcp-server\",\"version\":\"1.0.0\"}";
- return Mono.just(ResourceContent.text(config, uri, "application/json"));
-
- default:
- log.warn("Unknown resource requested: {}", uri);
- return Mono.error(new IllegalArgumentException("Unknown resource URI: " + uri));
- }
- }
-
- private static Mono handleSubscribe(String uri) {
- log.info("Client subscribed to resource: {}", uri);
- subscriptions.put(uri, true);
- return Mono.empty();
- }
-
- private static Mono handleUnsubscribe(String uri) {
- log.info("Client unsubscribed from resource: {}", uri);
- subscriptions.remove(uri);
- return Mono.empty();
- }
-}
-```
-
-## PromptDefinitions.java Template
-
-```java
-package com.example.mcp.prompts;
-
-import io.mcp.server.prompt.Prompt;
-import io.mcp.server.prompt.PromptArgument;
-
-import java.util.List;
-
-public class PromptDefinitions {
-
- public static List getPrompts() {
- return List.of(
- Prompt.builder()
- .name("code-review")
- .description("Generate a code review prompt")
- .argument(PromptArgument.builder()
- .name("language")
- .description("Programming language")
- .required(true)
- .build())
- .argument(PromptArgument.builder()
- .name("focus")
- .description("Review focus area")
- .required(false)
- .build())
- .build()
- );
- }
-}
-```
-
-## PromptHandlers.java Template
-
-```java
-package com.example.mcp.prompts;
-
-import io.mcp.server.McpServer;
-import io.mcp.server.prompt.PromptMessage;
-import io.mcp.server.prompt.PromptResult;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import reactor.core.publisher.Mono;
-
-import java.util.List;
+import java.nio.file.Path;
import java.util.Map;
-public class PromptHandlers {
-
- private static final Logger log = LoggerFactory.getLogger(PromptHandlers.class);
-
- public static void register(McpServer server) {
- // Register prompt list handler
- server.addPromptListHandler(() -> {
- log.debug("Listing available prompts");
- return Mono.just(PromptDefinitions.getPrompts());
- });
-
- // Register prompt get handler
- server.addPromptGetHandler(PromptHandlers::handleCodeReview);
- }
-
- private static Mono handleCodeReview(String name, Map arguments) {
- log.info("Getting prompt: {}", name);
-
- if (!name.equals("code-review")) {
- return Mono.error(new IllegalArgumentException("Unknown prompt: " + name));
- }
-
- String language = arguments.getOrDefault("language", "Java");
- String focus = arguments.getOrDefault("focus", "general quality");
-
- String description = "Code review for " + language + " with focus on " + focus;
-
- List messages = List.of(
- PromptMessage.user("Please review this " + language + " code with focus on " + focus + "."),
- PromptMessage.assistant("I'll review the code focusing on " + focus + ". Please share the code."),
- PromptMessage.user("Here's the code to review: [paste code here]")
- );
-
- log.debug("Generated code review prompt for {} ({})", language, focus);
-
- return Mono.just(PromptResult.builder()
- .description(description)
- .messages(messages)
- .build());
- }
-}
-```
-
-## McpServerTest.java Template
-
-```java
-package com.example.mcp;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import io.mcp.server.McpServer;
-import io.mcp.server.McpSyncServer;
-import io.mcp.server.tool.ToolResponse;
-import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.client.McpSyncClient;
+import io.modelcontextprotocol.client.transport.ServerParameters;
+import io.modelcontextprotocol.client.transport.StdioClientTransport;
+import io.modelcontextprotocol.json.McpJsonDefaults;
+import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
+import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.TextContent;
-class McpServerTest {
-
- private McpSyncServer syncServer;
- private ObjectMapper objectMapper;
-
- @BeforeEach
- void setUp() {
- McpServer server = createTestServer();
- syncServer = server.toSyncServer();
- objectMapper = new ObjectMapper();
+class McpServerAppTest {
+
+ private static McpSyncClient client;
+
+ @BeforeAll
+ static void startServer() {
+ String java = Path.of(System.getProperty("java.home"), "bin", "java").toString();
+ ServerParameters server = ServerParameters.builder(java)
+ .args("-cp", System.getProperty("java.class.path"), McpServerApp.class.getName())
+ .build();
+ client = McpClient.sync(new StdioClientTransport(server, McpJsonDefaults.getMapper())).build();
+ client.initialize();
}
-
- private McpServer createTestServer() {
- // Same setup as main application
- McpServer server = McpServerBuilder.builder()
- .serverInfo("test-server", "1.0.0")
- .capabilities(cap -> cap.tools(true))
- .build();
-
- // Register handlers
- ToolHandlers.register(server);
-
- return server;
+
+ @AfterAll
+ static void stopServer() {
+ client.close();
}
-
+
@Test
- void testGreetTool() {
- ObjectNode args = objectMapper.createObjectNode();
- args.put("name", "Java");
-
- ToolResponse response = syncServer.callTool("greet", args);
-
- assertFalse(response.isError());
- assertEquals(1, response.getContent().size());
- assertTrue(response.getContent().get(0).getText().contains("Java"));
+ void divide() {
+ CallToolResult result = client.callTool(
+ CallToolRequest.builder("divide").arguments(Map.of("a", 6, "b", 3)).build());
+ assertEquals("2.0", ((TextContent) result.content().get(0)).text());
}
-
+
@Test
- void testCalculateTool() {
- ObjectNode args = objectMapper.createObjectNode();
- args.put("operation", "add");
- args.put("a", 5);
- args.put("b", 3);
-
- ToolResponse response = syncServer.callTool("calculate", args);
-
- assertFalse(response.isError());
- assertTrue(response.getContent().get(0).getText().contains("8"));
- }
-
- @Test
- void testDivideByZero() {
- ObjectNode args = objectMapper.createObjectNode();
- args.put("operation", "divide");
- args.put("a", 10);
- args.put("b", 0);
-
- ToolResponse response = syncServer.callTool("calculate", args);
-
- assertTrue(response.isError());
+ void divideByZero() {
+ CallToolResult result = client.callTool(
+ CallToolRequest.builder("divide").arguments(Map.of("a", 1, "b", 0)).build());
+ assertTrue(result.isError());
+ assertTrue(((TextContent) result.content().get(0)).text().contains("must not be zero"));
}
}
```
-## README.md Template
+- The test uses the `java` command of the JDK that runs Maven. Do not use the `java` command from `PATH`.
+- The test uses the real stdio transport and the real JSON-RPC messages.
-````markdown
-# My MCP Server
+### Step 4: Build until the tests pass
-A Model Context Protocol server built with Java and the official MCP Java SDK.
+1. Run `mvn verify`. This command compiles the code, runs the tests, and makes `target/mcp-server-demo-0.1.0.jar`.
+2. If a test fails, read the error. Fix the server or the test.
+3. Run `mvn verify` again.
+4. Continue only when the build is successful.
-## Features
+### Step 5: Configure the client
-- ✅ Tools: greet, calculate
-- ✅ Resources: example data, configuration
-- ✅ Prompts: code-review
-- ✅ Reactive Streams with Project Reactor
-- ✅ Structured logging with SLF4J
-- ✅ Full test coverage
-
-## Requirements
-
-- Java 17 or later
-- Maven 3.6+ or Gradle 7+
-
-## Build
-
-### Maven
-```bash
-mvn clean package
-```
-
-### Gradle
-```bash
-./gradlew build
-```
-
-## Run
-
-### Maven
-```bash
-java -jar target/my-mcp-server-1.0.0.jar
-```
-
-### Gradle
-```bash
-./gradlew run
-```
-
-## Testing
-
-### Maven
-```bash
-mvn test
-```
-
-### Gradle
-```bash
-./gradlew test
-```
-
-## Integration with Claude Desktop
-
-Add to `claude_desktop_config.json`:
+For VS Code, write `.vscode/mcp.json`:
```json
{
- "mcpServers": {
- "my-mcp-server": {
+ "servers": {
+ "demo": {
+ "type": "stdio",
"command": "java",
- "args": ["-jar", "/path/to/my-mcp-server-1.0.0.jar"]
+ "args": ["-jar", "/absolute/path/to/project/target/mcp-server-demo-0.1.0.jar"]
}
}
}
```
-## License
+For Claude Desktop, put the same `command` and `args` in `claude_desktop_config.json`, below the `mcpServers` key.
-MIT
-````
+For an HTTP server, use the client configuration in [references/streamable-http.md](references/streamable-http.md).
-## Generation Instructions
+Tell the user these facts:
-1. **Ask for project name and package**
-2. **Choose build tool** (Maven or Gradle)
-3. **Generate all files** with proper package structure
-4. **Use Reactive Streams** for async handlers
-5. **Include comprehensive logging** with SLF4J
-6. **Add tests** for all handlers
-7. **Follow Java conventions** (camelCase, PascalCase)
-8. **Include error handling** with proper responses
-9. **Document public APIs** with Javadoc
-10. **Provide both sync and async** examples
+- `java -jar target/mcp-server-demo-0.1.0.jar` starts the stdio server. The server waits for a host on stdin. The SDK logs go to stderr.
+- To test the server manually, start the MCP Inspector with the same command. The Inspector uses Node.js.
+
+## Gotchas
+
+SDK 2.x changed many patterns of SDK 0.x and 1.x. Do not copy examples from earlier versions. Read the [migration guide](https://github.com/modelcontextprotocol/java-sdk/blob/main/MIGRATION-2.0.md) for the full list.
+
+- Use the builders that take the required fields first: `Tool.builder(name, inputSchema)`, `Resource.builder(uri, name)`, `Prompt.builder(name)`, `TextContent.builder(text)`, and `ReadResourceResult.builder(contents)`. The builders without arguments are deprecated.
+- A required field must not be `null`. A `null` value causes an `IllegalArgumentException`.
+- Tool names must have 1 to 128 characters: letters, digits, `_`, `-`, and `.`. The server builder rejects an incorrect name with an `IllegalArgumentException`. `addTool()` at runtime does not check the name.
+- `inputSchema` and `outputSchema` are `Map` values. The `JsonSchema` record is deprecated.
+- The server checks each schema against JSON Schema 2020-12 when you call `build()` or `addTool()`. An incorrect schema causes an `IllegalArgumentException`.
+- The SDK validates the tool arguments against `inputSchema`. It also validates `structuredContent` against `outputSchema`. If the validation fails, the client gets a tool result with `isError` set to `true`. Thus, the handler gets only valid arguments.
+- If a tool has an `outputSchema`, return `structuredContent` in each successful result. Without it, the SDK returns a tool result with `isError` set to `true`.
+- Return `CallToolResult` with `isError(true)` for an error that the model must read.
+- Do not throw an exception or an `McpError` for a tool failure. The client gets a JSON-RPC error, not a tool result. Many hosts do not show this error to the model.
+- In a stdio server, stdout is the protocol channel. Do not call `System.out.println()`.
+- Log with SLF4J to stderr. The default console appender of Logback writes to stdout. If you use Logback, set its target to `System.err`.
+- The Spring transports moved to [Spring AI](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html) 2.0 and later (group `org.springframework.ai`). Do not use the `io.modelcontextprotocol.sdk:mcp-spring-*` artifacts.
+- Do not use the deprecated SSE transports. Use Streamable HTTP for a remote server.
+- The revision 2026-07-28 deprecates these features ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)). Do not add them to a new server:
+ - **Sampling** (`exchange.createMessage()`): Call the LLM provider API directly.
+ - **Protocol logging** (`exchange.loggingNotification()`): Use SLF4J.
+ - **Roots** (`exchange.listRoots()`): Get paths from tool arguments, resource URIs, or the server configuration.
diff --git a/skills/java-mcp-server-generator/references/gradle.md b/skills/java-mcp-server-generator/references/gradle.md
new file mode 100644
index 000000000..bdcbdc2b8
--- /dev/null
+++ b/skills/java-mcp-server-generator/references/gradle.md
@@ -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 {
+ 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.
diff --git a/skills/java-mcp-server-generator/references/streamable-http.md b/skills/java-mcp-server-generator/references/streamable-http.md
new file mode 100644
index 000000000..a46312950
--- /dev/null
+++ b/skills/java-mcp-server-generator/references/streamable-http.md
@@ -0,0 +1,118 @@
+# 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
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ 11.0.26
+
+```
+
+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.
+- The SDK has no authorization. For a remote server, add authorization in a servlet filter or with Spring Security.
+- `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.