A Vert.x-based transport implementation for the Model Context Protocol (MCP) Java SDK. This project provides a lightweight, non-blocking transport layer that integrates MCP servers with Vert.x applications.
The Vert.x MCP Server provides:
- Vert.x Transport: A
VertxMcpTransportinterface that provides a Vert.x Router for integration into your Vert.x applications - SSE Transport: Server-Sent Events (SSE) implementation for real-time bidirectional communication
- Streamable HTTP Transport: New MCP 2025-06-18 Streamable HTTP transport implementation
- Verticle Support: A ready-to-use
McpVerticlefor easy deployment - Non-blocking: Built on Vert.x for high-performance, event-driven architecture
- Session Management: Automatic client session handling with graceful shutdown support
This project is currently in experimental status and has not been fully tested in production environments.
- The implementation follows the MCP specification but may contain bugs or incomplete features
- API changes are possible as the project matures
- Performance characteristics have not been thoroughly benchmarked
- Please report any bugs, issues, or unexpected behavior you encounter
- Contributions and feedback are welcome to help improve the project
<dependency>
<groupId>org.kinotic</groupId>
<artifactId>vertx-mcp</artifactId>
<version>4.5.1</version>
</dependency>dependencies {
implementation 'org.kinotic:vertx-mcp:4.5.1'
}- 4.5.x versions: Compatible with Vert.x 4.5.x
- 5.0.x versions: Will be compatible with Vert.x 5.x (not yet supported)
First, create your MCP server using the official Java MCP SDK:
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.ServerCapabilities;
import java.util.List;
import reactor.core.publisher.Mono;
// Create a simple calculator tool
var calculatorTool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(McpSchema.Tool.builder()
.name("calculator")
.description("Basic calculator")
.inputSchema("""
{
"type": "object",
"properties": {
"operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
}
""")
.build())
.callHandler((exchange, toolReq) -> {
String operation = (String) toolReq.arguments().get("operation");
double a = ((Number) toolReq.arguments().get("a")).doubleValue();
double b = ((Number) toolReq.arguments().get("b")).doubleValue();
double result = switch (operation) {
case "add" -> a + b;
case "subtract" -> a - b;
case "multiply" -> a * b;
case "divide" -> a / b;
default -> throw new IllegalArgumentException("Unknown operation: " + operation);
};
return Mono.just(McpSchema.CallToolResult.builder()
.textContent(List.of(String.valueOf(result)))
.isError(false)
.build());
})
.build();
// Create the MCP server specification (don't call .build() yet)
var mcpServerSpec = McpServer.async(transportProvider)
.serverInfo("calculator-server", "1.0.0")
.capabilities(ServerCapabilities.builder()
.tools(true)
.build())
.tools(calculatorTool);import io.vertx.ext.mcp.transport.VertxMcpSseServerTransportProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
// Create the transport
var transport = VertxMcpSseServerTransportProvider.builder()
.baseUrl("http://localhost:8080")
.messageEndpoint("/mcp/message")
.sseEndpoint("/mcp/sse")
.keepAliveInterval(Duration.ofSeconds(30))
.objectMapper(new ObjectMapper())
.vertx(vertx)
.build();import io.vertx.ext.mcp.transport.VertxMcpStreamableServerTransportProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
// Create the Streamable HTTP transport
var transport = VertxMcpStreamableServerTransportProvider.builder()
.objectMapper(new ObjectMapper())
.mcpEndpoint("/mcp")
.disallowDelete(false)
.vertx(vertx)
.build();Use the provided Verticle for easy integration:
import io.vertx.ext.mcp.McpVerticle;
// Deploy the MCP verticle (pass transport and server specification)
vertx.deployVerticle(new McpVerticle(8080, transport, mcpServerSpec), ar -> {
if (ar.succeeded()) {
System.out.println("MCP Verticle deployed successfully");
} else {
System.err.println("Failed to deploy MCP Verticle: " + ar.cause());
}
});Here's a complete working example using the legacy HTTP+SSE transport:
import io.vertx.core.Vertx;
import io.vertx.ext.mcp.McpVerticle;
import io.vertx.ext.mcp.transport.VertxMcpSseServerTransportProvider;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.ServerCapabilities;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;
import java.util.List;
import reactor.core.publisher.Mono;
public class LegacyMcpServerExample {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
// Create MCP server with a simple calculator tool
var calculatorTool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(McpSchema.Tool.builder()
.name("calculator")
.description("Basic calculator")
.inputSchema("""
{
"type": "object",
"properties": {
"operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
}
""")
.build())
.callHandler((exchange, toolReq) -> {
String operation = (String) toolReq.arguments().get("operation");
double a = ((Number) toolReq.arguments().get("a")).doubleValue();
double b = ((Number) toolReq.arguments().get("b")).doubleValue();
double result = switch (operation) {
case "add" -> a + b;
case "subtract" -> a - b;
case "multiply" -> a * b;
case "divide" -> a / b;
default -> throw new IllegalArgumentException("Unknown operation: " + operation);
};
return Mono.just(McpSchema.CallToolResult.builder()
.textContent(List.of(String.valueOf(result)))
.isError(false)
.build());
})
.build();
// Create legacy HTTP+SSE transport
var transport = VertxMcpSseServerTransportProvider.builder()
.baseUrl("http://localhost:8080")
.messageEndpoint("/mcp/message")
.sseEndpoint("/mcp/sse")
.keepAliveInterval(Duration.ofSeconds(30))
.objectMapper(new ObjectMapper())
.vertx(vertx)
.build();
// Create MCP server specification (don't call .build() yet)
var mcpServerSpec = McpServer.async(transport)
.serverInfo("calculator-server", "1.0.0")
.capabilities(ServerCapabilities.builder()
.tools(true)
.build())
.tools(calculatorTool);
// Deploy the MCP verticle (pass transport and server specification)
vertx.deployVerticle(new McpVerticle(8080, transport, mcpServerSpec), ar -> {
if (ar.succeeded()) {
System.out.println("MCP Server started on port 8080");
System.out.println("SSE endpoint: http://localhost:8080/mcp/sse");
System.out.println("Message endpoint: http://localhost:8080/mcp/message");
} else {
System.err.println("Failed to start MCP Server: " + ar.cause());
}
});
}
}Here's a complete working example using the new MCP 2025-06-18 Streamable HTTP transport:
import io.vertx.core.Vertx;
import io.vertx.ext.mcp.McpVerticle;
import io.vertx.ext.mcp.transport.VertxMcpStreamableServerTransportProvider;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.ServerCapabilities;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;
import java.util.List;
import reactor.core.publisher.Mono;
public class StreamableMcpServerExample {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
// Create MCP server with a simple calculator tool
var calculatorTool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(McpSchema.Tool.builder()
.name("calculator")
.description("Basic calculator")
.inputSchema("""
{
"type": "object",
"properties": {
"operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
}
""")
.build())
.callHandler((exchange, toolReq) -> {
String operation = (String) toolReq.arguments().get("operation");
double a = ((Number) toolReq.arguments().get("a")).doubleValue();
double b = ((Number) toolReq.arguments().get("b")).doubleValue();
double result = switch (operation) {
case "add" -> a + b;
case "subtract" -> a - b;
case "multiply" -> a * b;
case "divide" -> a / b;
default -> throw new IllegalArgumentException("Unknown operation: " + operation);
};
return Mono.just(McpSchema.CallToolResult.builder()
.textContent(List.of(String.valueOf(result)))
.isError(false)
.build());
})
.build();
// Create Streamable HTTP transport
var transport = VertxMcpStreamableServerTransportProvider.builder()
.objectMapper(new ObjectMapper())
.mcpEndpoint("/mcp")
.disallowDelete(false)
.vertx(vertx)
.keepAliveInterval(Duration.ofSeconds(30))
.build();
// Create MCP server specification (don't call .build() yet)
var mcpServerSpec = McpServer.async(transport)
.serverInfo("calculator-server", "1.0.0")
.capabilities(ServerCapabilities.builder()
.tools(true)
.build())
.tools(calculatorTool);
// Deploy the MCP verticle (pass transport and server specification)
vertx.deployVerticle(new McpVerticle(8080, transport, mcpServerSpec), ar -> {
if (ar.succeeded()) {
System.out.println("MCP Server started on port 8080");
System.out.println("Streamable HTTP endpoint: http://localhost:8080/mcp");
System.out.println("Supports: GET (SSE), POST (messages), DELETE (sessions)");
} else {
System.err.println("Failed to start MCP Server: " + ar.cause());
}
});
}
}The VertxMcpSseServerTransportProvider supports several configuration options:
baseUrl: Base URL for your server (required)messageEndpoint: Endpoint for receiving JSON-RPC messages (default:/message)sseEndpoint: Endpoint for SSE connections (default:/sse)keepAliveInterval: Interval for keep-alive ping messages (default: 30 seconds)objectMapper: Jackson ObjectMapper for JSON serializationvertx: Vert.x instance
The VertxMcpStreamableServerTransportProvider supports:
objectMapper: Jackson ObjectMapper for JSON processing (required)mcpEndpoint: The MCP endpoint path (default:/mcp)disallowDelete: Whether to disable session deletion (default:false)vertx: Vert.x instance (required)keepAliveInterval: Interval for keep-alive ping messages (default: 30 seconds)
The transport implements the MCP protocol using:
- SSE Connection (
/sse): Establishes Server-Sent Events connection for server-to-client communication - Message Endpoint (
/message): Receives JSON-RPC messages from clients - Session Management: Automatically manages client sessions and cleanup
- Keep-alive: Sends periodic ping messages to prevent connection timeouts
The new Streamable HTTP transport follows the MCP 2025-06-18 specification:
- Single Endpoint (
/mcp): Handles all HTTP methods (GET, POST, DELETE) - Session Management: Uses MCP SDK session management with unique session IDs
- Stream Resumption: Supports resuming broken connections via Last-Event-ID headers
- Protocol Compliance: Full compliance with the latest MCP specification
You can test your MCP server using any MCP client:
- SSE endpoint:
http://localhost:8080/mcp/ssefor establishing connections - Message endpoint:
http://localhost:8080/mcp/message?sessionId=<id>for sending requests
- Single endpoint:
http://localhost:8080/mcpfor all operations - GET: Establish SSE listening streams
- POST: Send JSON-RPC messages
- DELETE: Terminate sessions
The transport supports graceful shutdown:
// Close the transport gracefully
transport.closeGracefully()
.doOnSuccess(v -> System.out.println("Transport closed successfully"))
.doOnError(e -> System.err.println("Error closing transport: " + e))
.subscribe();The following features are planned for future releases:
Support for Vert.x 5.x versions, including:
- Compatibility with Vert.x 5.x APIs
- Updated transport implementations for Vert.x 5
- Performance improvements leveraging new Vert.x 5 features
Implementation of MCP Authorization capabilities including:
- OAuth 2.1 compliant authorization flow
- Dynamic client registration
- Access token validation and audience binding
- Resource parameter support for secure token issuance
Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
This project is licensed under the Apache License, Version 2.0.