import { RegistrySource } from '../base.js'; import { OfficialRegistryResponseSchema, sanitizeString, type OfficialServerEntry, type RegistryServer, } from '../types.js'; import { withRetry } from '../retry.js'; const BASE_URL = 'https://registry.modelcontextprotocol.io/v0/servers'; export class OfficialRegistrySource extends RegistrySource { readonly name = 'official' as const; async search(query: string, limit: number): Promise { const results: RegistryServer[] = []; let cursor: string | null | undefined; while (results.length < limit) { const url = new URL(BASE_URL); url.searchParams.set('search', query); url.searchParams.set('limit', String(Math.min(limit - results.length, 100))); if (cursor !== undefined && cursor !== null) { url.searchParams.set('cursor', cursor); } const response = await withRetry(() => fetch(url.toString())); if (!response.ok) { throw new Error(`Official registry returned ${String(response.status)}`); } const raw: unknown = await response.json(); const parsed = OfficialRegistryResponseSchema.parse(raw); for (const entry of parsed.servers) { results.push(this.normalizeResult(entry)); } cursor = parsed.metadata?.nextCursor; if (cursor === null || cursor === undefined || parsed.servers.length === 0) break; } return results.slice(0, limit); } protected normalizeResult(raw: unknown): RegistryServer { const entry = raw as OfficialServerEntry; const server = entry.server; // Extract env vars from packages const envTemplate = server.packages.flatMap((pkg: { environmentVariables: Array<{ name: string; description?: string; isSecret?: boolean }> }) => pkg.environmentVariables.map((ev: { name: string; description?: string; isSecret?: boolean }) => ({ name: ev.name, description: sanitizeString(ev.description ?? ''), isSecret: ev.isSecret ?? false, })), ); // Determine transport from packages or remotes let transport: RegistryServer['transport'] = 'stdio'; if (server.packages.length > 0) { const pkgTransport = server.packages[0]?.transport?.type; if (pkgTransport === 'stdio') transport = 'stdio'; } if (server.remotes.length > 0) { const remoteType = server.remotes[0]?.type; if (remoteType === 'sse') transport = 'sse'; else if (remoteType === 'streamable-http') transport = 'streamable-http'; } // Extract npm package identifier const npmPkg = server.packages.find((p: { registryType: string }) => p.registryType === 'npm'); const dockerPkg = server.packages.find((p: { registryType: string }) => p.registryType === 'oci'); // Extract dates from _meta const meta = entry._meta as Record> | undefined; const officialMeta = meta?.['io.modelcontextprotocol.registry/official']; const updatedAt = officialMeta?.['updatedAt']; const packages: RegistryServer['packages'] = {}; if (npmPkg !== undefined) { packages.npm = npmPkg.identifier; } if (dockerPkg !== undefined) { packages.docker = dockerPkg.identifier; } const result: RegistryServer = { name: sanitizeString(server.title ?? server.name), description: sanitizeString(server.description), packages, envTemplate, transport, popularityScore: 0, // Official registry has no popularity data verified: false, // Official registry has no verified badges sourceRegistry: 'official', }; if (server.repository?.url !== undefined) { result.repositoryUrl = server.repository.url; } if (typeof updatedAt === 'string') { result.lastUpdated = new Date(updatedAt); } return result; } }