java-mcp-server
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. Triggers on: **/*.java, **/pom.xml, **/build.gradle, **/build.gradle.kts
Best use case
java-mcp-server is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
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. Triggers on: **/*.java, **/pom.xml, **/build.gradle, **/build.gradle.kts
Teams using java-mcp-server should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/java-mcp-server/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How java-mcp-server Compares
| Feature / Agent | java-mcp-server | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
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. Triggers on: **/*.java, **/pom.xml, **/build.gradle, **/build.gradle.kts
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# 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
<dependencies>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.14.1</version>
</dependency>
</dependencies>
```
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<String> 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<Resource> 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<Prompt> 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<PromptMessage> 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
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-boot-starter</artifactId>
<version>0.14.1</version>
</dependency>
```
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<ToolResponse> 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<String, String> cache = new ConcurrentHashMap<>();
server.addResourceReadHandler((uri) -> {
return Mono.fromCallable(() ->
cache.computeIfAbsent(uri, this::loadResource))
.map(content -> ResourceContent.text(content, uri));
});
```
## 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)Related Skills
azure-ai-vision-imageanalysis-java
Build image analysis applications with Azure AI Vision SDK for Java. Use when implementing image captioning, OCR text extraction, object detection, tagging, or smart cropping.
azure-ai-contentsafety-java
Build content moderation applications with Azure AI Content Safety SDK for Java. Use when implementing text/image analysis, blocklist management, or harm detection for hate, violence, sexual conten...
javascript-mastery
Comprehensive JavaScript reference covering 33+ essential concepts every developer should know. From fundamentals like primitives and closures to advanced patterns like async/await and functional p...
azure-communication-callautomation-java
Build call automation workflows with Azure Communication Services Call Automation Java SDK. Use when implementing IVR systems, call routing, call recording, DTMF recognition, text-to-speech, or AI-...
solidstart-advanced-server
SolidStart advanced server: getRequestEvent for request context, static assets handling, returning responses, request events and nativeEvent access.
server-management
Server management principles and decision-making. Process management, monitoring strategy, and scaling decisions. Teaches thinking, not commands.
senior-java
World-class Java and Spring Boot development skill for enterprise applications, microservices, and cloud-native systems. Expertise in Spring Framework, Spring Boot 3.x, Spring Cloud, JPA/Hibernate, and reactive programming with WebFlux. Includes project scaffolding, dependency management, security implementation, and performance optimization.
quarkus-mcp-server-sse
Quarkus and MCP Server with HTTP SSE transport development standards and instructions Triggers on: *
php-mcp-server
Best practices for building Model Context Protocol servers in PHP using the official PHP SDK with attribute-based discovery and multiple transport options Triggers on: **/*.php
mcpserver
Migrates an MCP server with interactive widgets from the OpenAI Apps SDK (window.openai, text/html+skybridge) to the MCP Apps standard (@modelcontextprotocol/ext-apps), covering server-side and client-side changes.
MCP Server Architecture
This skill should be used when the user asks to "create an MCP server", "set up MCP server", "build ChatGPT app backend", "MCP transport type", "configure MCP endpoint", "server setup for Apps SDK", or needs guidance on MCP server architecture, transport protocols, or SDK setup for the OpenAI Apps SDK.
java-pro
Master Java 21+ with modern features like virtual threads, pattern matching, and Spring Boot 3.x. Expert in the latest Java ecosystem including GraalVM, Project Loom, and cloud-native patterns.