From 4a36aafa030e4f5ce44e16ae481e68445f00811b Mon Sep 17 00:00:00 2001 From: Burak Kaygusuz <17873574+burakkaygusuz@users.noreply.github.com> Date: Fri, 9 Oct 2026 07:10:01 +0300 Subject: [PATCH 1/5] docs(java-mcp-server-generator): update skill for MCP Java SDK 2.x - Target MCP Java SDK 2.0.1 and specification revision 2025-11-25. - Rewrite the description with user intent triggers and exclusions. - Add the compatibility field. - Add a five-step workflow checklist with a build loop. - Add a Maven template, a stdio server template, and a JUnit test that starts the server as a subprocess with the SDK client. - Add Gotchas for SDK 2.x changes, schema validation, error results, stdout in stdio servers, and the features that SEP-2577 deprecates. - Add references/gradle.md for Gradle projects. - Add references/streamable-http.md for Servlet, Spring AI, and transport security. - Update the skill description in docs/README.skills.md. --- docs/README.skills.md | 2 +- skills/java-mcp-server-generator/SKILL.md | 917 +++++------------- .../references/gradle.md | 90 ++ .../references/streamable-http.md | 117 +++ 4 files changed, 439 insertions(+), 687 deletions(-) create mode 100644 skills/java-mcp-server-generator/references/gradle.md create mode 100644 skills/java-mcp-server-generator/references/streamable-http.md diff --git a/docs/README.skills.md b/docs/README.skills.md index 2e3530a45..210304d67 100644 --- a/docs/README.skills.md +++ b/docs/README.skills.md @@ -247,7 +247,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/skills/java-mcp-server-generator/SKILL.md b/skills/java-mcp-server-generator/SKILL.md index 88b366916..35d85ad63 100644 --- a/skills/java-mcp-server-generator/SKILL.md +++ b/skills/java-mcp-server-generator/SKILL.md @@ -1,756 +1,301 @@ --- 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 the SDK core and the Jackson 3 JSON mapper. For Jackson 2, use `mcp-core` and `mcp-json-jackson2`. +- `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`. +- `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..9aee16a70 --- /dev/null +++ b/skills/java-mcp-server-generator/references/streamable-http.md @@ -0,0 +1,117 @@ +# Streamable HTTP Servers + +Use this transport only for a remote server. A local server uses stdio. + +## Servlet server + +`HttpServletStreamableServerTransportProvider` is a Jakarta Servlet. It is in the `mcp` artifact. Add a servlet container to `pom.xml`, for example embedded Tomcat: + +```xml + + 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. +- `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. From 957edc6f329b1c5fc0c74addc5ed799ec17105a3 Mon Sep 17 00:00:00 2001 From: Burak Kaygusuz <17873574+burakkaygusuz@users.noreply.github.com> Date: Fri, 9 Oct 2026 07:10:01 +0300 Subject: [PATCH 2/5] docs(java-mcp-server): update instructions for MCP Java SDK 2.x - Replace SDK 0.14.1 patterns with SDK 2.0.1 builders and Map schemas. - Add the BOM, Gradle, and packaging rules. - Document schema validation, isError results, and structuredContent. - Add transport security, CORS, and Spring AI 2.0 guidance. - Replace sampling, protocol logging, and roots with alternatives. - Rewrite the Common Patterns with code that compiles on SDK 2.0.1. - Update the description in docs/README.instructions.md. --- docs/README.instructions.md | 2 +- instructions/java-mcp-server.instructions.md | 853 +++++++------------ 2 files changed, 307 insertions(+), 548 deletions(-) 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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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/instructions/java-mcp-server.instructions.md b/instructions/java-mcp-server.instructions.md index 39f3a53d2..93f7b6c78 100644 --- a/instructions/java-mcp-server.instructions.md +++ b/instructions/java-mcp-server.instructions.md @@ -1,553 +1,312 @@ --- -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 the SDK core and the Jackson 3 JSON mapper. For Jackson 2, use `mcp-core` and `mcp-json-jackson2`. +- 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`. +- 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`. +- 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. +- 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()); + } +} +``` From 35c3b7dc21605a74899c03f4524698283e9d964b Mon Sep 17 00:00:00 2001 From: Burak Kaygusuz <17873574+burakkaygusuz@users.noreply.github.com> Date: Fri, 9 Oct 2026 07:10:01 +0300 Subject: [PATCH 3/5] docs(java-mcp-expert): update agent for MCP Java SDK 2.x - Remove the retired GPT-4.1 model. - Replace SDK 0.x guidance with SDK 2.x rules. - Add schema validation, error results, transport security, and Gradle packaging rules. - Add SDK 0.x and 1.x to 2.x migration as a supported scenario. - Update the description in docs/README.agents.md. --- agents/java-mcp-expert.agent.md | 441 +++++++------------------------- docs/README.agents.md | 2 +- 2 files changed, 89 insertions(+), 354 deletions(-) diff --git a/agents/java-mcp-expert.agent.md b/agents/java-mcp-expert.agent.md index 1b87c4a36..48ec78270 100644 --- a/agents/java-mcp-expert.agent.md +++ b/agents/java-mcp-expert.agent.md @@ -1,359 +1,94 @@ --- -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. For Jackson 2, use `mcp-core` and `mcp-json-jackson2`. +- Make a stdio server with `McpServer.sync(new StdioServerTransportProvider(McpJsonDefaults.getMapper()))`. +- Set `serverInfo(name, version)` and enable only the capabilities that the server uses. +- 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`. +- 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`. +- 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 Mappers**: Jackson 3 (default) or Jackson 2 +- **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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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. | From c93b398ab37be92a8e5d816287161c618beeab27 Mon Sep 17 00:00:00 2001 From: Burak Kaygusuz <17873574+burakkaygusuz@users.noreply.github.com> Date: Fri, 9 Oct 2026 07:10:01 +0300 Subject: [PATCH 4/5] docs(java-mcp-development): update plugin for MCP Java SDK 2.x - Describe the actual contents: one skill and one agent. - Replace outdated keywords with mcp-java-sdk and spring-ai. - Bump the version to 2.0.0. - Update the plugin README, docs/README.plugins.md, and .github/plugin/marketplace.json. --- .github/plugin/marketplace.json | 4 ++-- docs/README.plugins.md | 2 +- plugins/java-mcp-development/README.md | 14 +++++++------- plugins/java-mcp-development/plugin.json | 10 ++++------ 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 1ed267d93..fc40b500d 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -1298,8 +1298,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/docs/README.plugins.md b/docs/README.plugins.md index 949763924..89a8023de 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/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": { From 908458b050bfd959eaf72050081fa72d40f1875f Mon Sep 17 00:00:00 2001 From: Burak Kaygusuz <17873574+burakkaygusuz@users.noreply.github.com> Date: Fri, 9 Oct 2026 07:25:08 +0300 Subject: [PATCH 5/5] docs(java-mcp): document tool name rules, URL elicitation, authorization, and Jackson 3 mapper - Add the tool name rules of specification revision 2025-11-25 (SEP-986). The server builder rejects an incorrect name. addTool() does not check it. - Add URL mode elicitation with ElicitUrlRequest (SEP-1036). - State that the SDK has no authorization for remote servers. - State that the mcp artifact contains mcp-core and mcp-json-jackson3. Remove the Jackson 2 option. --- agents/java-mcp-expert.agent.md | 7 +++++-- instructions/java-mcp-server.instructions.md | 5 ++++- skills/java-mcp-server-generator/SKILL.md | 3 ++- .../references/streamable-http.md | 1 + 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/agents/java-mcp-expert.agent.md b/agents/java-mcp-expert.agent.md index 48ec78270..557cc8a3e 100644 --- a/agents/java-mcp-expert.agent.md +++ b/agents/java-mcp-expert.agent.md @@ -31,9 +31,10 @@ You are an expert in Model Context Protocol (MCP) servers with the [MCP Java SDK ## Guidelines -- Import the BOM `io.modelcontextprotocol.sdk:mcp-bom:2.0.1` and add the `mcp` artifact. For Jackson 2, use `mcp-core` and `mcp-json-jackson2`. +- 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`. @@ -41,6 +42,7 @@ You are an expert in Model Context Protocol (MCP) servers with the [MCP Java SDK - 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. @@ -48,6 +50,7 @@ You are an expert in Model Context Protocol (MCP) servers with the [MCP Java SDK - 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. @@ -88,7 +91,7 @@ You are an expert in Model Context Protocol (MCP) servers with the [MCP Java SDK - **Completion Support**: Argument completion with `SyncCompletionSpecification` - **Stateless Servers**: `HttpServletStatelessServerTransport` for more than one replica - **Transport Security**: `DefaultServerTransportSecurityValidator` for `Host` and `Origin` checks -- **JSON Mappers**: Jackson 3 (default) or Jackson 2 +- **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/instructions/java-mcp-server.instructions.md b/instructions/java-mcp-server.instructions.md index 93f7b6c78..811d0d369 100644 --- a/instructions/java-mcp-server.instructions.md +++ b/instructions/java-mcp-server.instructions.md @@ -11,7 +11,7 @@ These instructions apply to the [MCP Java SDK](https://github.com/modelcontextpr - 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 the SDK core and the Jackson 3 JSON mapper. For Jackson 2, use `mcp-core` and `mcp-json-jackson2`. +- 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()`. @@ -21,6 +21,7 @@ These instructions apply to the [MCP Java SDK](https://github.com/modelcontextpr - 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`. @@ -34,6 +35,7 @@ These instructions apply to the [MCP Java SDK](https://github.com/modelcontextpr - 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. @@ -42,6 +44,7 @@ These instructions apply to the [MCP Java SDK](https://github.com/modelcontextpr - 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. diff --git a/skills/java-mcp-server-generator/SKILL.md b/skills/java-mcp-server-generator/SKILL.md index 35d85ad63..0faef0bc9 100644 --- a/skills/java-mcp-server-generator/SKILL.md +++ b/skills/java-mcp-server-generator/SKILL.md @@ -115,7 +115,7 @@ Write `pom.xml`: ``` -- The `mcp` artifact contains the SDK core and the Jackson 3 JSON mapper. For Jackson 2, use `mcp-core` and `mcp-json-jackson2`. +- 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. @@ -285,6 +285,7 @@ SDK 2.x changed many patterns of SDK 0.x and 1.x. Do not copy examples from earl - 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. diff --git a/skills/java-mcp-server-generator/references/streamable-http.md b/skills/java-mcp-server-generator/references/streamable-http.md index 9aee16a70..a46312950 100644 --- a/skills/java-mcp-server-generator/references/streamable-http.md +++ b/skills/java-mcp-server-generator/references/streamable-http.md @@ -113,5 +113,6 @@ The starter serves the endpoint at `/mcp`. - 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.