agent-rules-kit
Version:
CLI tool to bootstrap AI agent rules for multiple IDEs and frameworks. Generates optimized rules for Cursor, VS Code, Claude, and 6+ other AI coding assistants.
594 lines (478 loc) • 18.2 kB
Markdown
---
globs: <root>/**/*.java,<root>/pom.xml,<root>/build.gradle
alwaysApply: false
---
# MCP Java SDK Implementation Guide
## Java SDK Features
The Java SDK provides enterprise-grade MCP server and client implementations with strong typing, comprehensive error handling, and excellent performance characteristics.
### Maven Configuration
```xml
<!-- pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mcp-server</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.modelcontextprotocol</groupId>
<artifactId>mcp-java-sdk</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.3.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
```
### Basic Server Implementation
```java
package com.example.mcp.server;
import io.modelcontextprotocol.sdk.server.MCPServer;
import io.modelcontextprotocol.sdk.server.ServerCapabilities;
import io.modelcontextprotocol.sdk.server.annotations.*;
import io.modelcontextprotocol.sdk.types.*;
import io.modelcontextprotocol.sdk.transport.StdioTransport;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
public class ExampleMCPServer {
private final Map<String, String> resourceStorage = new ConcurrentHashMap<>();
public ExampleMCPServer() {
// Initialize with sample data
resourceStorage.put("file://example.txt", "Sample content from Java MCP server");
resourceStorage.put("file://config.json", "{\"setting\": \"value\"}");
}
public CompletableFuture<List<Resource>> listResources() {
List<Resource> resources = resourceStorage.keySet().stream()
.map(uri -> Resource.builder()
.uri(uri)
.name(extractNameFromUri(uri))
.description("A sample resource")
.mimeType(determineMimeType(uri))
.build())
.toList();
return CompletableFuture.completedFuture(resources);
}
public CompletableFuture<ResourceContent> readResource( String uri) {
String content = resourceStorage.get(uri);
if (content == null) {
return CompletableFuture.failedFuture(
new ResourceNotFoundException("Resource not found: " + uri)
);
}
ResourceContent resourceContent = ResourceContent.builder()
.uri(uri)
.mimeType(determineMimeType(uri))
.text(content)
.build();
return CompletableFuture.completedFuture(resourceContent);
}
public CompletableFuture<List<Tool>> listTools() {
List<Tool> tools = List.of(
Tool.builder()
.name("echo")
.description("Echo back the input message")
.inputSchema(createEchoSchema())
.build(),
Tool.builder()
.name("calculate")
.description("Perform mathematical calculations")
.inputSchema(createCalculateSchema())
.build()
);
return CompletableFuture.completedFuture(tools);
}
("echo")
public CompletableFuture<ToolResult> executeEcho( Map<String, Object> arguments) {
String message = (String) arguments.get("message");
if (message == null) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("Message parameter is required")
);
}
TextContent content = TextContent.builder()
.type("text")
.text("Echo: " + message)
.build();
ToolResult result = ToolResult.builder()
.content(List.of(content))
.isError(false)
.build();
return CompletableFuture.completedFuture(result);
}
("calculate")
public CompletableFuture<ToolResult> executeCalculate( Map<String, Object> arguments) {
try {
String expression = (String) arguments.get("expression");
if (expression == null) {
throw new IllegalArgumentException("Expression parameter is required");
}
double result = evaluateExpression(expression);
TextContent content = TextContent.builder()
.type("text")
.text("Result: " + result)
.build();
ToolResult toolResult = ToolResult.builder()
.content(List.of(content))
.isError(false)
.build();
return CompletableFuture.completedFuture(toolResult);
} catch (Exception e) {
TextContent errorContent = TextContent.builder()
.type("text")
.text("Error: " + e.getMessage())
.build();
ToolResult errorResult = ToolResult.builder()
.content(List.of(errorContent))
.isError(true)
.build();
return CompletableFuture.completedFuture(errorResult);
}
}
// Utility methods
private String extractNameFromUri(String uri) {
return uri.substring(uri.lastIndexOf("/") + 1);
}
private String determineMimeType(String uri) {
if (uri.endsWith(".json")) return "application/json";
if (uri.endsWith(".txt")) return "text/plain";
return "application/octet-stream";
}
private JsonSchema createEchoSchema() {
return JsonSchema.builder()
.type("object")
.property("message", JsonSchema.builder()
.type("string")
.description("Message to echo back")
.build())
.required(List.of("message"))
.build();
}
private JsonSchema createCalculateSchema() {
return JsonSchema.builder()
.type("object")
.property("expression", JsonSchema.builder()
.type("string")
.description("Mathematical expression to evaluate")
.build())
.required(List.of("expression"))
.build();
}
private double evaluateExpression(String expression) {
// Simple expression evaluator - in production, use a proper library
// This is just for demonstration
return Double.parseDouble(expression);
}
public static void main(String[] args) {
ExampleMCPServer serverInstance = new ExampleMCPServer();
ServerCapabilities capabilities = ServerCapabilities.builder()
.resources(ResourceCapabilities.builder()
.subscribe(true)
.listChanged(true)
.build())
.tools(ToolCapabilities.builder()
.listChanged(true)
.build())
.build();
MCPServer server = MCPServer.builder()
.name("java-mcp-server")
.version("1.0.0")
.capabilities(capabilities)
.instance(serverInstance)
.build();
StdioTransport transport = new StdioTransport();
try {
server.start(transport);
System.err.println("Java MCP Server started on stdio");
// Keep the server running
Thread.currentThread().join();
} catch (Exception e) {
System.err.println("Server error: " + e.getMessage());
System.exit(1);
}
}
}
```
### Client Implementation
```java
package com.example.mcp.client;
import io.modelcontextprotocol.sdk.client.MCPClient;
import io.modelcontextprotocol.sdk.client.ClientCapabilities;
import io.modelcontextprotocol.sdk.types.*;
import io.modelcontextprotocol.sdk.transport.StdioTransport;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class ExampleMCPClient {
private MCPClient client;
public ExampleMCPClient() {
ClientCapabilities capabilities = ClientCapabilities.builder()
.experimental(Map.of())
.build();
this.client = MCPClient.builder()
.name("java-mcp-client")
.version("1.0.0")
.capabilities(capabilities)
.build();
}
public CompletableFuture<Void> connect(String serverCommand, String... args) {
StdioTransport transport = StdioTransport.builder()
.command(serverCommand)
.args(List.of(args))
.build();
return client.connect(transport);
}
public CompletableFuture<List<Resource>> listResources() {
return client.listResources();
}
public CompletableFuture<ResourceContent> readResource(String uri) {
return client.readResource(uri);
}
public CompletableFuture<List<Tool>> listTools() {
return client.listTools();
}
public CompletableFuture<ToolResult> callTool(String name, Map<String, Object> arguments) {
return client.callTool(name, arguments);
}
public CompletableFuture<Void> disconnect() {
return client.disconnect();
}
public static void main(String[] args) {
ExampleMCPClient client = new ExampleMCPClient();
client.connect("java", "-jar", "mcp-server.jar")
.thenCompose(v -> client.listResources())
.thenAccept(resources -> {
System.out.println("Available resources:");
resources.forEach(resource ->
System.out.println(" " + resource.getName() + " (" + resource.getUri() + ")")
);
})
.thenCompose(v -> client.listTools())
.thenAccept(tools -> {
System.out.println("Available tools:");
tools.forEach(tool ->
System.out.println(" " + tool.getName() + ": " + tool.getDescription())
);
})
.thenCompose(v -> client.callTool("echo", Map.of("message", "Hello from Java client!")))
.thenAccept(result -> {
System.out.println("Tool result:");
result.getContent().forEach(content ->
System.out.println(" " + content.getText())
);
})
.thenCompose(v -> client.disconnect())
.whenComplete((result, throwable) -> {
if (throwable != null) {
System.err.println("Client error: " + throwable.getMessage());
throwable.printStackTrace();
}
System.exit(throwable != null ? 1 : 0);
});
}
}
```
### Spring Boot Integration
```java
package com.example.mcp.spring;
import io.modelcontextprotocol.sdk.server.MCPServer;
import io.modelcontextprotocol.sdk.server.ServerCapabilities;
import io.modelcontextprotocol.sdk.transport.HttpTransport;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RestController;
public class MCPSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MCPSpringBootApplication.class, args);
}
}
public class MCPConfiguration {
public MCPServer mcpServer(MCPServerService serverService) {
ServerCapabilities capabilities = ServerCapabilities.builder()
.resources(ResourceCapabilities.builder()
.subscribe(true)
.listChanged(true)
.build())
.tools(ToolCapabilities.builder()
.listChanged(true)
.build())
.build();
return MCPServer.builder()
.name("spring-mcp-server")
.version("1.0.0")
.capabilities(capabilities)
.instance(serverService)
.build();
}
}
public class MCPServerService {
public CompletableFuture<List<Resource>> listResources() {
// Implementation using Spring services
return CompletableFuture.completedFuture(List.of());
}
("spring-tool")
public CompletableFuture<ToolResult> executeSpringTool( Map<String, Object> arguments) {
// Implementation using Spring services
return CompletableFuture.completedFuture(ToolResult.builder().build());
}
}
public class MCPController {
private final MCPServer mcpServer;
public MCPController(MCPServer mcpServer) {
this.mcpServer = mcpServer;
// Start MCP server on HTTP transport
HttpTransport transport = HttpTransport.builder()
.port(8080)
.path("/mcp")
.build();
mcpServer.start(transport);
}
}
```
### Testing with JUnit 5
```java
package com.example.mcp.server;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
(MockitoExtension.class)
class ExampleMCPServerTest {
private ExampleMCPServer server;
void setUp() {
server = new ExampleMCPServer();
}
void testListResources() {
CompletableFuture<List<Resource>> future = server.listResources();
assertDoesNotThrow(() -> {
List<Resource> resources = future.get();
assertNotNull(resources);
assertFalse(resources.isEmpty());
});
}
void testEchoTool() {
Map<String, Object> arguments = Map.of("message", "test message");
CompletableFuture<ToolResult> future = server.executeEcho(arguments);
assertDoesNotThrow(() -> {
ToolResult result = future.get();
assertNotNull(result);
assertFalse(result.isError());
assertEquals("Echo: test message",
result.getContent().get(0).getText());
});
}
void testEchoToolWithMissingMessage() {
Map<String, Object> arguments = Map.of();
CompletableFuture<ToolResult> future = server.executeEcho(arguments);
assertThrows(Exception.class, () -> future.get());
}
}
```
### Performance Optimization
```java
package com.example.mcp.performance;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
public class PerformanceOptimizedServer {
private final ExecutorService executorService;
private final Cache<String, Object> resourceCache;
private final AtomicLong requestCounter;
public PerformanceOptimizedServer() {
this.executorService = ForkJoinPool.commonPool();
this.resourceCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(5))
.recordStats()
.build();
this.requestCounter = new AtomicLong(0);
}
public CompletableFuture<ResourceContent> readResourceCached( String uri) {
return CompletableFuture.supplyAsync(() -> {
requestCounter.incrementAndGet();
return resourceCache.get(uri, key -> {
// Expensive resource loading operation
return loadResourceFromStorage(key);
});
}, executorService).thenApply(content -> {
return ResourceContent.builder()
.uri(uri)
.text(content.toString())
.build();
});
}
private Object loadResourceFromStorage(String uri) {
// Simulate expensive operation
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Loaded content for " + uri;
}
public CacheStats getCacheStats() {
return resourceCache.stats();
}
public long getRequestCount() {
return requestCounter.get();
}
}
```
This Java SDK implementation guide provides enterprise-ready patterns for building robust, scalable MCP servers and clients with Java's strong typing and performance characteristics.