koala73/worldmonitor · error · McpProxySsrfError

serverUrl DNS resolution failed

Error message

serverUrl DNS resolution failed

What it means

assertServerUrlSafe resolves the serverUrl hostname via DNS before fetching, to verify no resolved address is private/reserved. If defaultResolveHostname throws (NXDOMAIN, resolver outage, transient DNS failure), it throws McpProxySsrfError('serverUrl DNS resolution failed', { cause }). A hostname that resolves to zero addresses also fails with a sibling message.

Solutions

  1. Verify the hostname resolves publicly: run dig/nslookup on it from outside your network
  2. Fix typos in serverUrl or restore the deleted DNS record
  3. If the host is internal-only, expose it publicly — the proxy requires public DNS and public addresses
  4. Check error.cause for the resolver error code (ENOTFOUND vs ETIMEOUT) to distinguish bad name from resolver outage

Example fix

// before
serverUrl: 'https://mcp.corp-internal.local/mcp' // ENOTFOUND publicly
// after
serverUrl: 'https://mcp.acme.dev/mcp' // public A/AAAA records
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight DNS check before registering
import { lookup } from 'node:dns/promises';
async function hostnameResolves(u) { try { return (await lookup(new URL(u).hostname)).address.length > 0; } catch { return false; } }

Type guard

null

Try / catch

try { await validateServerUrl(serverUrl); } catch (e) { if (e instanceof McpProxySsrfError && e.message.includes('DNS')) return { error: 'dns_failure', hostname: new URL(serverUrl).hostname }; throw e; }

Prevention

When it happens

Trigger: Registering/calling an MCP server whose serverUrl hostname does not exist (typo, deleted DNS record, split-horizon internal name visible only inside a private VPC), or where the DNS resolver itself is failing/timing out.

Common situations: Typos in serverUrl hostnames; pointing at internal-only DNS names from a public edge function; DNS provider outage; IPv6 AAAA-only records failing in an IPv4 resolver; DNS record recently deleted after a service rename.

Understand the failure class

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/2594def34a1eff24. Report an issue: GitHub.

Appendix: source

Thrown at api/mcp-proxy.ts:344

  return records.flat();
}

async function assertServerUrlSafe(url, signal) {
  signal?.throwIfAborted();
  const hostname = url.hostname.toLowerCase();
  if (BLOCKED_HOSTNAMES.has(hostname)) {
    throw new McpProxySsrfError('serverUrl hostname is blocked');
  }
  if (isBlockedResolvedAddress(hostname)) {
    throwBlockedAddress(hostname);
  }

  let resolvedAddresses;
  try {
    resolvedAddresses = await defaultResolveHostname(hostname, signal);
  } catch (error) {
    signal?.throwIfAborted();
    throw new McpProxySsrfError('serverUrl DNS resolution failed', { cause: error });
  }
  signal?.throwIfAborted();

  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

View on GitHub (pinned to 7d06c8633d)