koala73/worldmonitor · error · McpProxySsrfError

serverUrl DNS resolution failed: ${message}

Error message

serverUrl DNS resolution failed: ${message}

What it means

Thrown by assertServerUrlSafe when defaultResolveHostname (DoH A+AAAA against cloudflare-dns.com/dns-query, 3s timeout) raises — the DoH fetch returned non-2xx, the JSON body's Status was non-zero, or the fetch aborted/timed out. The underlying error message is interpolated so the caller can see why DNS failed. Surfaced as a McpProxySsrfError.

Source

Thrown at api/mcp-proxy.ts:168

  ]);
  return records.flat();
}

async function assertServerUrlSafe(url) {
  const hostname = url.hostname.toLowerCase();
  if (BLOCKED_HOSTNAMES.has(hostname)) {
    throw new McpProxySsrfError(`serverUrl hostname is blocked: ${hostname}`);
  }
  if (isBlockedResolvedAddress(hostname)) {
    throwBlockedAddress(hostname);
  }

  let resolvedAddresses;
  try {
    resolvedAddresses = await defaultResolveHostname(hostname);
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    throw new McpProxySsrfError(`serverUrl DNS resolution failed: ${message}`);
  }

  if (!resolvedAddresses.length) {
    throw new McpProxySsrfError('serverUrl DNS resolution returned no addresses');
  }

  const blocked = resolvedAddresses.find(isBlockedResolvedAddress);
  if (blocked) {
    throwBlockedAddress(blocked);
  }

  return { url, resolvedAddresses };
}

// Vercel Edge fetch does not expose a Node-style lookup/socket hook, so this
// proxy CANNOT pin the TLS connection to a previously vetted address. There is
// no way to guarantee that the IP we validated is the IP fetch() ultimately
// connects to; a DNS answer can change between our resolve and fetch's own

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Retry the proxy call after a short delay — DoH failures are typically transient.
  2. Confirm the serverUrl hostname resolves normally via `dig`/`host`.
  3. Check Cloudflare status for DoH incidents.
  4. Verify Edge egress to https://cloudflare-dns.com is not blocked by a deployment-level egress policy.

Example fix

// before
proxy({ serverUrl: 'https://mcp.good-but-doh-failed.example.com/mcp' })
//   -> 'serverUrl DNS resolution failed: DNS A lookup failed: HTTP 503'
// after (retry once on a transient DoH failure)
await retry(() => proxy({ serverUrl: 'https://mcp.example.com/mcp' }), { tries: 2 })
Defensive patterns

Strategy: retry

Validate before calling

async function dohReachable(): Promise<boolean> {
  try {
    const r = await fetch('https://cloudflare-dns.com/dns-query?name=example.com&type=A', {
      headers: { Accept: 'application/dns-json' },
      signal: AbortSignal.timeout(1500),
    });
    return r.ok && (await r.json()).Status === 0;
  } catch { return false; }
}

Try / catch

async function proxyWithRetry(payload, attempts = 2) {
  for (let i = 0; i < attempts; i++) {
    try { return await runProxy(payload); }
    catch (err) {
      if (err.name === 'McpProxySsrfError' && err.message.startsWith('serverUrl DNS resolution failed:') && i < attempts - 1) {
        await new Promise(r => setTimeout(r, 500)); continue;
      }
      throw err;
    }
  }
}

Prevention

When it happens

Trigger: POST /api/mcp-proxy with a serverUrl whose hostname the DoH resolver could not resolve — Cloudflare DoH endpoint returned 5xx, the 3s AbortSignal fired, or Status != 0 in the response body.

Common situations: Transient Cloudflare DoH outage; a hostname with a pathological CNAME chain timing out; Edge egress to cloudflare-dns.com blocked or degraded; rate-limiting from the DoH endpoint.

Understand the failure class

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/d5dd50f418898a25. Report an issue: GitHub.