MCP registry
UnifiedMcpRegistry (in com.tnsai.mcp.registry) searches public MCP server catalogs and merges the answers into one list. Use it to find a server's endpoint; connect to the server with a transport and the client.
Example
package com.example.tnsai.docs;
import com.tnsai.mcp.registry.McpRegistrySource;
import com.tnsai.mcp.registry.McpServerEntry;
import com.tnsai.mcp.registry.OfficialRegistryClient;
import com.tnsai.mcp.registry.SmitheryClient;
import com.tnsai.mcp.registry.UnifiedMcpRegistry;
import java.time.Duration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
/** Compilation contract for mcp/registry.md. */
public final class McpRegistryExample {
private McpRegistryExample() {
}
static Optional<String> findEndpoint() {
UnifiedMcpRegistry registry = new UnifiedMcpRegistry(); // all four public sources
List<McpServerEntry> hits = registry.search("filesystem");
hits.forEach(e -> System.out.println(e.name() + " (" + e.source() + "): " + e.endpoint()));
return registry.getServer("filesystem").map(McpServerEntry::endpoint);
}
public static final class InternalCatalog implements McpRegistrySource {
private final List<McpServerEntry> entries = List.of(new McpServerEntry(
"billing", "Billing tools", "2.1.0", null, "streamable-http",
"https://mcp.internal.example.com/billing", "internal", 0.0, Map.of("team", "payments")));
@Override
public String getSourceName() {
return "internal";
}
@Override
public List<McpServerEntry> search(String query) {
String q = query == null ? "" : query.toLowerCase(Locale.ROOT);
return entries.stream().filter(e -> e.name().contains(q)).toList();
}
@Override
public Optional<McpServerEntry> getServer(String name) {
return entries.stream().filter(e -> e.name().equals(name)).findFirst();
}
@Override
public List<McpServerEntry> listAll() {
return entries;
}
}
static UnifiedMcpRegistry customSources() {
UnifiedMcpRegistry registry = new UnifiedMcpRegistry(
List.of(new OfficialRegistryClient(), new SmitheryClient()),
Duration.ofMinutes(5));
registry.addSource(new InternalCatalog());
registry.removeSource("smithery");
registry.clearCache(); // the next search asks the sources again
return registry;
}
}Sources
new UnifiedMcpRegistry() uses the four built-in sources with a 10-minute cache. new UnifiedMcpRegistry(sources, cacheTtl) takes your own list; a null TTL means 10 minutes.
| Class | Source name | Host | Priority |
|---|---|---|---|
OfficialRegistryClient | official | registry.modelcontextprotocol.io | 4 |
CamelMcpHubClient | camel | mcp.camel-ai.org | 3 |
SmitheryClient | smithery | smithery.ai | 2 |
McpServersOrgClient | mcpservers.org | mcpservers.org | 1 |
Each built-in client sends a GET with a 15-second timeout and takes an optional HttpClient in its constructor. Any other source name has priority 0.
addSource(source) and removeSource(name) change the list at run time; getSources() returns a copy of it. To add your own catalog, implement McpRegistrySource: getSourceName(), search(query), getServer(name) and listAll().
Search
search(query) asks every source in turn.
- A source that throws
IOExceptionis logged and skipped. A built-in source also skips entries it cannot parse. - A null or blank query asks each built-in source for its unfiltered list.
- Results are merged by name, ignoring case. When two sources list the same name, the higher-priority source wins.
- The list is sorted by source priority, then by rating, highest first.
getServer(name) asks the sources one at a time, highest priority first, and returns the first match. A built-in source answers empty for any error, including an unknown name.
Both methods drop entries whose protocolVersion is set and is neither 2024-11-05 nor 2025-11-05. The built-in sources leave it null, so they are never dropped.
Cache
Answers are cached per lowercased query or name for the TTL. A search is cached even when it is empty, including when every source failed, so call clearCache() to ask again before the TTL ends. getServer caches only a match. getCacheSize() returns the number of cached answers.
Server entries
McpServerEntry is a record.
| Field | Meaning |
|---|---|
name | The server name; required, not blank |
description, version | As the source lists them |
protocolVersion | The MCP version, or null when unknown |
transport | For example stdio, sse or streamable-http; the built-in sources default to stdio |
endpoint | A URL or a command; the built-in sources read endpoint, then url, then command |
source | The source name |
rating | 0 to 5; a value outside that range throws IllegalArgumentException |
metadata | Extra values: the source's metadata object plus author and homepage when present, and repository from the official and Smithery sources |
A registry entry is unverified catalog data. Check the endpoint or command before you run it.