/ services / mcp / officialRegistry.ts
officialRegistry.ts
 1  import axios from 'axios'
 2  import { logForDebugging } from '../../utils/debug.js'
 3  import { errorMessage } from '../../utils/errors.js'
 4  
 5  type RegistryServer = {
 6    server: {
 7      remotes?: Array<{ url: string }>
 8    }
 9  }
10  
11  type RegistryResponse = {
12    servers: RegistryServer[]
13  }
14  
15  // URLs stripped of query string and trailing slash — matches the normalization
16  // done by getLoggingSafeMcpBaseUrl so direct Set.has() lookup works.
17  let officialUrls: Set<string> | undefined = undefined
18  
19  function normalizeUrl(url: string): string | undefined {
20    try {
21      const u = new URL(url)
22      u.search = ''
23      return u.toString().replace(/\/$/, '')
24    } catch {
25      return undefined
26    }
27  }
28  
29  /**
30   * Fire-and-forget fetch of the official MCP registry.
31   * Populates officialUrls for isOfficialMcpUrl lookups.
32   */
33  export async function prefetchOfficialMcpUrls(): Promise<void> {
34    if (process.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC) {
35      return
36    }
37  
38    try {
39      const response = await axios.get<RegistryResponse>(
40        'https://api.anthropic.com/mcp-registry/v0/servers?version=latest&visibility=commercial',
41        { timeout: 5000 },
42      )
43  
44      const urls = new Set<string>()
45      for (const entry of response.data.servers) {
46        for (const remote of entry.server.remotes ?? []) {
47          const normalized = normalizeUrl(remote.url)
48          if (normalized) {
49            urls.add(normalized)
50          }
51        }
52      }
53      officialUrls = urls
54      logForDebugging(`[mcp-registry] Loaded ${urls.size} official MCP URLs`)
55    } catch (error) {
56      logForDebugging(`Failed to fetch MCP registry: ${errorMessage(error)}`, {
57        level: 'error',
58      })
59    }
60  }
61  
62  /**
63   * Returns true iff the given (already-normalized via getLoggingSafeMcpBaseUrl)
64   * URL is in the official MCP registry. Undefined registry → false (fail-closed).
65   */
66  export function isOfficialMcpUrl(normalizedUrl: string): boolean {
67    return officialUrls?.has(normalizedUrl) ?? false
68  }
69  
70  export function resetOfficialMcpUrlsForTesting(): void {
71    officialUrls = undefined
72  }