koala73/worldmonitor · error · McpProxySsrfError
serverUrl hostname is blocked
Error message
serverUrl hostname is blocked
What it means
assertServerUrlSafe performs SSRF checks on user-supplied serverUrl values before the proxy fetches them. If the hostname is in BLOCKED_HOSTNAMES (localhost, metadata endpoints, etc.) it throws McpProxySsrfError('serverUrl hostname is blocked'). The proxy deliberately refuses to fetch internal/reserved hosts.
Solutions
- Use a public, externally reachable URL for the MCP server (not localhost/127.0.0.1/internal hostnames)
- Expose your local MCP server via a public tunnel (e.g. ngrok-style) if you must reach a dev server
- If this is your own infrastructure, add the required hostname to the allow path via the sanctioned config mechanism rather than bypassing the check
- Treat the error as a security guardrail — do not try to evade it
Example fix
// before
registerMcpServer({ serverUrl: 'http://localhost:8080/mcp' }); // blocked
// after
registerMcpServer({ serverUrl: 'https://mcp.example.com/mcp' }); // public host Defensive patterns
Strategy: validation
Validate before calling
const BLOCKED = new Set(['localhost','127.0.0.1','0.0.0.0','169.254.169.254','metadata.google.internal']);
function serverUrlIsPublic(u) { const h = new URL(u).hostname.toLowerCase(); return !BLOCKED.has(h) && !/(^|\.)local$|\.internal$/.test(h); } Type guard
function isPublicHttpUrl(v) { try { const u = new URL(v); return (u.protocol === 'https:' || u.protocol === 'http:') && !/(^|\.)local$|\.internal$|^localhost$/.test(u.hostname.toLowerCase()); } catch { return false; } } Try / catch
try { await validateServerUrl(serverUrl); } catch (e) { if (e instanceof McpProxySsrfError) return { error: 'blocked_server_url', detail: e.message }; throw e; } Prevention
- Never point serverUrl at localhost/private hostnames in deployed environments
- Use public tunnels for local MCP development
- Keep BLOCKED_HOSTNAMES and reserved-CIDR checks current (cloud metadata hosts)
- Treat SSRF errors as security signals — log and investigate blocked attempts
When it happens
Trigger: Registering or fetching an MCP server whose serverUrl hostname resolves to a blocked literal (localhost, 127.0.0.1 names, cloud metadata hosts like 169.254.169.254's hostname forms, *.internal entries) — at validateServerUrl time or revalidateBeforeFetch time on each request.
Common situations: Developers pointing serverUrl at a local MCP server (http://localhost:3000) that works locally but is blocked in the deployed proxy; attackers probing SSRF; config copied from internal network docs.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- UNTRUSTED_SOURCE_HOST
- callbackUrl is not a valid URL
- serverUrl DNS resolution failed
- callbackUrl must use https
- callbackUrl hostname is a blocked metadata endpoint
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/50e535f7fa1db266.
Report an issue: GitHub.
Appendix: source
Thrown at api/mcp-proxy.ts:333
.filter(answer => answer?.type === expectedType && typeof answer?.data === 'string')
.map(answer => answer.data);
}
async function defaultResolveHostname(hostname, signal) {
const resolveHostnameForTest = getResolveHostnameForTest();
if (resolveHostnameForTest) return resolveHostnameForTest(hostname, signal);
const records = await Promise.all([
resolveDnsJson(hostname, 'A', signal),
resolveDnsJson(hostname, 'AAAA', signal),
]);
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');
}
View on GitHub (pinned to 7d06c8633d)