An open API service indexing awesome lists of open source software.

https://github.com/kinotic-foundation/vertx-mcp

A Vert.x MCP Server built on top of MCP Java SDK
https://github.com/kinotic-foundation/vertx-mcp

java llms mcp mcp-server vertx

Last synced: 7 months ago
JSON representation

A Vert.x MCP Server built on top of MCP Java SDK

Awesome Lists containing this project

README

          

# Vert.x MCP Server

A Vert.x-based transport implementation for the [Model Context Protocol (MCP) Java SDK](https://modelcontextprotocol.io/sdk/java/mcp-server). This project provides a lightweight, non-blocking transport layer that integrates MCP servers with Vert.x applications.

## Overview

The Vert.x MCP Server provides:

- **Vert.x Transport**: A `VertxMcpTransport` interface 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 `McpVerticle` for easy deployment
- **Non-blocking**: Built on Vert.x for high-performance, event-driven architecture
- **Session Management**: Automatic client session handling with graceful shutdown support

## ⚠️ Experimental Status

**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

## Installation

### Maven
```xml

org.kinotic
vertx-mcp
4.5.1

```

### Gradle
```gradle
dependencies {
implementation 'org.kinotic:vertx-mcp:4.5.1'
}
```

### Version Compatibility

- **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)

## Quick Start

### 1. Create an MCP Server

First, create your MCP server using the official [Java MCP SDK](https://modelcontextprotocol.io/sdk/java/mcp-server):

```java
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);
```

### 2. Create the Vert.x Transport

#### Option A: Use the Legacy HTTP+SSE Transport

```java
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();
```

#### Option B: Use the New Streamable HTTP Transport

```java
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();
```

### 3. Integrate with Your Vert.x Application

Use the provided Verticle for easy integration:

```java
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());
}
});
```

### 4. Complete Examples

#### Example A: Legacy HTTP+SSE Transport

Here's a complete working example using the legacy HTTP+SSE transport:

```java
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());
}
});
}
}
```

#### Example B: New Streamable HTTP Transport

Here's a complete working example using the new MCP 2025-06-18 Streamable HTTP transport:

```java
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());
}
});
}
}
```

## Configuration Options

### Legacy HTTP+SSE Transport

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 serialization
- **`vertx`**: Vert.x instance

### Streamable HTTP Transport

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)

## Architecture

### Legacy HTTP+SSE Transport

The transport implements the MCP protocol using:

1. **SSE Connection** (`/sse`): Establishes Server-Sent Events connection for server-to-client communication
2. **Message Endpoint** (`/message`): Receives JSON-RPC messages from clients
3. **Session Management**: Automatically manages client sessions and cleanup
4. **Keep-alive**: Sends periodic ping messages to prevent connection timeouts

### Streamable HTTP Transport

The new Streamable HTTP transport follows the MCP 2025-06-18 specification:

1. **Single Endpoint** (`/mcp`): Handles all HTTP methods (GET, POST, DELETE)
2. **Session Management**: Uses MCP SDK session management with unique session IDs
3. **Stream Resumption**: Supports resuming broken connections via Last-Event-ID headers
4. **Protocol Compliance**: Full compliance with the latest MCP specification

## Testing

You can test your MCP server using any MCP client:

### Legacy Transport
- **SSE endpoint**: `http://localhost:8080/mcp/sse` for establishing connections
- **Message endpoint**: `http://localhost:8080/mcp/message?sessionId=` for sending requests

### Streamable Transport
- **Single endpoint**: `http://localhost:8080/mcp` for all operations
- **GET**: Establish SSE listening streams
- **POST**: Send JSON-RPC messages
- **DELETE**: Terminate sessions

## Graceful Shutdown

The transport supports graceful shutdown:

```java
// Close the transport gracefully
transport.closeGracefully()
.doOnSuccess(v -> System.out.println("Transport closed successfully"))
.doOnError(e -> System.err.println("Error closing transport: " + e))
.subscribe();
```

## Roadmap

The following features are planned for future releases:

### Vert.x 5 Support
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

### Authorization Support
Implementation of [MCP Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/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

## Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.

## License

This project is licensed under the Apache License, Version 2.0.