# MCP Registry

The MCP module provides a unified registry that aggregates MCP server entries from 4 public sources, with deduplication, caching, and protocol version filtering. It also includes a testing toolkit for MCP server development.

## UnifiedMcpRegistry

The `UnifiedMcpRegistry` is a one-stop shop for discovering MCP servers. It queries multiple public registries (like the official MCP registry and Smithery.ai), deduplicates results by server name, and caches responses so repeated searches are fast.

```java
// Default: all 4 sources, 10-minute cache
UnifiedMcpRegistry registry = new UnifiedMcpRegistry();

// Search for servers
List<McpServerEntry> results = registry.search("filesystem");

// Look up a specific server
Optional<McpServerEntry> server = registry.getServer("filesystem");

// List all available servers (aggregated from all sources)
List<McpServerEntry> all = registry.listAll();
```

### Custom Configuration

You can pick which registry sources to use and set a custom cache TTL. This is useful if you only want results from specific sources or need fresher data.

```java
UnifiedMcpRegistry registry = new UnifiedMcpRegistry(
    List.of(
        new OfficialRegistryClient(),
        new SmitheryClient()
    ),
    Duration.ofMinutes(5)  // Custom cache TTL
);
```

### Source Priority

When the same server appears in multiple registries, the registry uses priority to decide which entry to keep. Higher-priority sources are considered more authoritative.

| Priority | Source | Class | Registry |
|----------|--------|-------|----------|
| 4 (highest) | `official` | `OfficialRegistryClient` | Official MCP Registry |
| 3 | `camel` | `CamelMcpHubClient` | Apache Camel MCP Hub |
| 2 | `smithery` | `SmitheryClient` | Smithery.ai |
| 1 (lowest) | `mcpservers.org` | `McpServersOrgClient` | mcpservers.org |

### Caching

To avoid hitting remote registries on every search, results are cached per query key with a configurable TTL (default: 10 minutes). The cache uses `ConcurrentHashMap` for thread safety. Cache entries include the timestamp of insertion and are evicted on the next access after TTL expiry.

### Resilience

The registry is designed to be fault-tolerant. If a source fails (network error, parsing error), the registry logs a warning and continues with results from the remaining sources. A partial result set is always better than a failed query.

## McpRegistrySource Interface

If you run your own internal MCP server catalog, you can add it as a custom registry source. Implement this interface and pass it to the `UnifiedMcpRegistry` constructor.

```java
public interface McpRegistrySource {
    String getSourceName();
    List<McpServerEntry> search(String query) throws IOException;
    Optional<McpServerEntry> getServer(String name) throws IOException;
    List<McpServerEntry> listAll() throws IOException;
}
```

### Built-in Sources

These four sources are included out of the box and are all enabled by default.

| Source | Description |
|--------|-------------|
| `OfficialRegistryClient` | Queries the official MCP server registry |
| `CamelMcpHubClient` | Queries the Apache Camel MCP Hub |
| `SmitheryClient` | Queries the Smithery.ai marketplace |
| `McpServersOrgClient` | Queries the mcpservers.org community directory |

## McpServerEntry

Each discovered MCP server is represented as an `McpServerEntry` record. It contains everything you need to connect to the server, including its transport type, endpoint, and protocol version.

```java
public record McpServerEntry(
    String name,             // Unique server name (required)
    String description,      // Human-readable description
    String version,          // Server version string
    String protocolVersion,  // MCP protocol version (e.g. "2025-11-05")
    String transport,        // "stdio", "sse", or "streamable-http"
    String endpoint,         // Connection URL or command
    String source,           // Registry source name
    double rating,           // Quality rating (0.0 - 5.0)
    Map<String, String> metadata  // Additional key-value pairs
) {}
```

### Protocol Version Filtering

Not all servers support the same MCP protocol version. Use protocol version filtering to ensure you only connect to servers compatible with your client's version.

```java
List<McpServerEntry> servers = registry.search("database");

// Filter to servers supporting protocol version 2025-03-26 or later
List<McpServerEntry> compatible = servers.stream()
    .filter(s -> McpProtocolVersion.isCompatible(s.protocolVersion(), "2025-03-26"))
    .toList();
```

## Testing Toolkit

The MCP module includes JUnit 5 utilities for testing MCP server implementations.

### McpServerTestKit

The `McpServerTestKit` is a JUnit 5 test harness that validates whether your MCP server implementation is compliant with the protocol. It automatically tests initialization, tool listing, tool invocation, and error handling.

```java
class MyMcpServerTest {

    @Test
    void testServerCompliance() {
        McpServerTestKit testKit = McpServerTestKit.builder()
            .transport(new StdioTransport.Builder()
                .command("node", "my-server.js")
                .build())
            .build();

        // Validates initialize, tools/list, tool invocation, and error handling
        testKit.runComplianceTests();
    }

    @Test
    void testSpecificTool() {
        McpServerTestKit testKit = McpServerTestKit.builder()
            .transport(transport)
            .build();

        // Test a specific tool
        var result = testKit.callTool("read_file", Map.of("path", "/tmp/test.txt"));
        assertNotNull(result);
    }
}
```

### MockMcpServer

When you want to test your MCP client code without a real server, use `MockMcpServer`. It runs entirely in memory with no network I/O, making tests fast and deterministic.

```java
MockMcpServer mockServer = MockMcpServer.builder()
    .tool("read_file", "Read a file", Map.of(
        "type", "object",
        "properties", Map.of("path", Map.of("type", "string"))
    ))
    .toolHandler("read_file", args -> "file content here")
    .build();

// Use with McpClient for testing
McpClient client = new McpClient(mockServer.getTransport());
client.connect();
var tools = client.listTools();
// tools contains "read_file"
```

`MockMcpServer` uses `MockMcpTransport` internally, which implements `McpTransport` with in-memory message queues instead of network I/O.

### McpSchemaValidator

The `McpSchemaValidator` checks that your JSON-RPC messages conform to the MCP specification. Use it during development to catch schema issues early, such as missing required fields or invalid types in tool definitions.

```java
McpSchemaValidator validator = new McpSchemaValidator();

// Validate a tool definition
ValidationResult result = validator.validateToolDefinition(toolJson);
if (!result.isValid()) {
    result.getErrors().forEach(System.err::println);
}

// Validate a JSON-RPC request
ValidationResult result = validator.validateRequest(requestJson);

// Validate a JSON-RPC response
ValidationResult result = validator.validateResponse(responseJson);
```

The validator checks required fields, type constraints, and MCP-specific schema rules (e.g., tool input schemas must be valid JSON Schema).
