Model Context Protocol (MCP) and Integration with Quarkus
Introduction
The Model Context Protocol (MCP) has emerged as a critical standard for enabling AI models and agents to safely access and interact with external systems, data, and tools. For Java developers building enterprise applications with Quarkus, understanding how to implement MCP endpoints and integrate them with your microservices is essential. This article explores MCP concepts and demonstrates practical integration with Quarkus.
What is Model Context Protocol (MCP)?
The Model Context Protocol is a standardized specification for connecting AI models to external resources. It provides:
- Standardized Integration: Consistent interfaces for AI-model-to-system communication
- Resource Abstraction: Unified way to expose tools, resources, and prompts to AI systems
- Security and Control: Well-defined permission boundaries and access control
- Interoperability: Language-agnostic specification enabling diverse implementations
Core Concepts
Resources: Data or information accessible to the model
- Read-only resources for data access
- Versioning support for tracking changes
- Template-based URI patterns
Tools: Functions the model can invoke
- Semantic descriptions of tool capabilities
- Input schemas and validation
- Execution with proper error handling
Prompts: Pre-crafted instruction templates
- Context-aware prompt generation
- Dynamic parameter injection
- Reusable prompt patterns
Why MCP Matters for Enterprise Applications
Safety and Control
MCP enables you to:
- Define exactly what data and operations an AI model can access
- Implement fine-grained permission controls
- Maintain audit trails of AI model interactions
- Prevent unauthorized access to sensitive resources
Integration Simplicity
Rather than custom integrations for each AI service:
- Implement once, use with multiple AI platforms
- Standardized request/response formats
- Predictable error handling
- Easier maintenance and evolution
Scalability
MCP supports:
- Multiple concurrent client connections
- Efficient resource sharing
- Clear separation between business logic and integration
- Easy horizontal scaling
Implementing MCP with Quarkus
Project Setup
Create a Quarkus project with necessary extensions:
quarkus create app mcp-quarkus \
-x resteasy-reactive-jackson
MCP Server Implementation
Here’s a practical example of an MCP server exposing database resources:
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.json.Json;
import jakarta.json.JsonObject;
@ApplicationScoped
public class MCPServerConfig {
@GET
@Path("/mcp/initialize")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject initialize() {
return Json.createObjectBuilder()
.add("protocolVersion", "2024-11-05")
.add("capabilities", Json.createObjectBuilder()
.add("resources", true)
.add("tools", true)
.add("prompts", true)
.build())
.add("serverInfo", Json.createObjectBuilder()
.add("name", "Quarkus MCP Server")
.add("version", "1.0.0")
.build())
.build();
}
}
Exposing Resources via MCP
Expose your application’s data as MCP resources:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.persistence.EntityManager;
import jakarta.json.JsonObject;
@Path("/mcp/resources")
@ApplicationScoped
public class MCPResourcesEndpoint {
@Inject
EntityManager em;
@GET
@Path("/list")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject listResources() {
return Json.createObjectBuilder()
.add("resources", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("uri", "quarkus://database/users")
.add("name", "Users Database")
.add("description", "Access to user profiles and information")
.add("mimeType", "application/json")
.build())
.build())
.build();
}
@POST
@Path("/read")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public JsonObject readResource(JsonObject request) {
String resourceUri = request.getString("uri");
if (resourceUri.equals("quarkus://database/users")) {
// Query user data
var users = em.createQuery(
"SELECT u FROM User u",
User.class)
.getResultList();
return Json.createObjectBuilder()
.add("resourceUri", resourceUri)
.add("contents", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("mimeType", "application/json")
.add("text", users.toString())
.build())
.build())
.build();
}
throw new BadRequestException("Unknown resource");
}
}
Implementing MCP Tools
Expose application functions as MCP tools:
@Path("/mcp/tools")
@ApplicationScoped
public class MCPToolsEndpoint {
@Inject
UserService userService;
@GET
@Path("/list")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject listTools() {
return Json.createObjectBuilder()
.add("tools", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("name", "get_user_by_email")
.add("description", "Retrieve user information by email address")
.add("inputSchema", Json.createObjectBuilder()
.add("type", "object")
.add("properties", Json.createObjectBuilder()
.add("email", Json.createObjectBuilder()
.add("type", "string")
.add("description", "User's email address")
.build())
.build())
.add("required", Json.createArrayBuilder()
.add("email")
.build())
.build())
.build())
.build())
.build();
}
@POST
@Path("/call")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public JsonObject callTool(JsonObject request) {
String toolName = request.getString("name");
JsonObject arguments = request.getJsonObject("arguments");
return switch (toolName) {
case "get_user_by_email" -> {
String email = arguments.getString("email");
var user = userService.findByEmail(email);
yield Json.createObjectBuilder()
.add("content", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("type", "text")
.add("text", user.toString())
.build())
.build())
.add("isError", false)
.build();
}
default -> Json.createObjectBuilder()
.add("isError", true)
.add("content", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("type", "text")
.add("text", "Unknown tool: " + toolName)
.build())
.build())
.build();
};
}
}
MCP Prompts Implementation
Create reusable prompts for AI models:
@Path("/mcp/prompts")
@ApplicationScoped
public class MCPPromptsEndpoint {
@GET
@Path("/list")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject listPrompts() {
return Json.createObjectBuilder()
.add("prompts", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("name", "user_analysis")
.add("description", "Analyze user activity and engagement patterns")
.add("arguments", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("name", "timeframe")
.add("description", "Analysis period (e.g., 'last_30_days')")
.build())
.build())
.build())
.build())
.build();
}
@POST
@Path("/get")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public JsonObject getPrompt(JsonObject request) {
String promptName = request.getString("name");
JsonObject arguments = request.getJsonObject("arguments");
if ("user_analysis".equals(promptName)) {
String timeframe = arguments.getString("timeframe", "last_30_days");
return Json.createObjectBuilder()
.add("messages", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("role", "user")
.add("content", "Analyze user engagement for " + timeframe +
". Focus on activity patterns, retention, and feature usage.")
.build())
.build())
.build();
}
throw new BadRequestException("Unknown prompt: " + promptName);
}
}
Security Best Practices
Authentication
@ApplicationScoped
public class MCPAuthFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
String token = requestContext.getHeaderString("Authorization");
if (!validateToken(token)) {
requestContext.abortWith(Response.status(401).build());
}
}
private boolean validateToken(String token) {
// Implement your authentication logic
return token != null && !token.isEmpty();
}
}
Input Validation
@ApplicationScoped
public class InputValidator {
public void validateToolCall(String toolName, JsonObject args) {
if (!isAllowedTool(toolName)) {
throw new SecurityException("Tool not allowed: " + toolName);
}
// Validate argument types and values
// Prevent injection attacks
// Check rate limits
}
}
Testing MCP Endpoints
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.http.ContentType;
@QuarkusTest
public class MCPEndpointTest {
@Test
public void testInitialize() {
given()
.when()
.get("/mcp/initialize")
.then()
.statusCode(200)
.body("serverInfo.name", equalTo("Quarkus MCP Server"));
}
@Test
public void testToolCall() {
given()
.contentType(ContentType.JSON)
.body("{\"name\":\"get_user_by_email\",\"arguments\":{\"email\":\"test@example.com\"}}")
.when()
.post("/mcp/tools/call")
.then()
.statusCode(200)
.body("isError", equalTo(false));
}
}
Best Practices for MCP with Quarkus
1. Resource Versioning
Include version information in resource URIs to handle schema evolution:
// Good: versioned resource
"uri", "quarkus://database/users/v2"
// Allows maintaining backward compatibility
2. Pagination for Large Resources
@POST
@Path("/read")
public JsonObject readResource(JsonObject request) {
int offset = request.getInt("offset", 0);
int limit = request.getInt("limit", 100);
// Return paginated results
}
3. Rate Limiting
Implement rate limiting on tool calls:
@ApplicationScoped
public class RateLimiter {
private final Map<String, Integer> callCounts = new ConcurrentHashMap<>();
public boolean isAllowed(String clientId) {
int count = callCounts.getOrDefault(clientId, 0);
if (count > 100) { // 100 calls per minute
return false;
}
callCounts.put(clientId, count + 1);
return true;
}
}
4. Comprehensive Logging
@ApplicationScoped
public class MCPAuditLogger {
public void logToolCall(String toolName, JsonObject args,
JsonObject result, String clientId) {
LOGGER.info("MCP Tool Call - Tool: {}, Client: {}, Args: {}, Result: {}",
toolName, clientId, args, result);
}
}
Conclusion
The Model Context Protocol provides a robust, standardized way to integrate AI capabilities into enterprise Java applications. Quarkus, with its lightweight nature and excellent performance characteristics, is an ideal platform for implementing MCP servers. By following the patterns and practices outlined in this article, you can build secure, scalable MCP integrations that enable AI models to safely access your business data and operations.
As AI integration becomes increasingly common in enterprise systems, MCP will play a vital role in ensuring these integrations remain secure, auditable, and maintainable.