koala73/worldmonitor · error · McpProxySsrfError
serverUrl hostname is blocked
Error message
serverUrl hostname is blocked: ${hostname} What it means
SSRF guard error in the MCP proxy's assertServerUrlSafe: the serverUrl hostname (lowercased) matches the BLOCKED_HOSTNAMES denylist verbatim (e.g. 'localhost' or metadata-service names). Unlike resolved-IP blocks, the hostname itself is echoed because the caller already knows it.
Solutions
- Use the service's public hostname instead of a blocked literal name
- If a legitimate service shares a blocked name, expose it via an allowlisted public alias
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at api/mcp-proxy.ts:157 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-08-21).
Data as JSON: /api/errors/6bb817e301945a8d.
Report an issue: GitHub.
Appendix: source
Thrown at api/mcp-proxy.ts:159
* Emit one wm_api_usage RequestEvent per proxied call.
*
* Before this, `logProxyCall` was the ONLY record of a proxy request and it is
* `console.log` — Vercel runtime logs are a live tail with no historical query,
* so `/api/mcp-proxy` had ZERO rows in Axiom and its failure rate could not be
* asked about after the fact. That is the same hole #4866 closed for `/mcp`;
* this reuses the gateway's builders so rows are byte-compatible and joinable
* on customer_id. `logProxyCall` stays: it carries target_host/header_names,
* which the usage envelope has no field for, and existing log-ingest tooling
* parses its shape.
*
* OPTIONS preflights are deliberately NOT emitted — a static 204 that cannot
* fail would double row volume for no diagnostic value. /mcp skips them too
* (McpUsage.skip).
*/
function emitProxyUsage(req, status: number, durationMs: number, ctx, callerIdentity = null): void {
if (!ctx) return;
try {
const usageIdentity = proxyUsageIdentityFor(req, callerIdentity);
emitUsageEvents(ctx, [buildRequestEvent({
requestId: deriveRequestId(req),
domain: 'mcp',
route: '/api/mcp-proxy',
method: req.method,
status,
// Measured from handler entry, so this INCLUDES the auth/rate-limit
// gates — unlike logProxyCall's `started`, which begins after auth.
// The usage row is the end-to-end caller-visible latency.
durationMs,
reqBytes: deriveReqBytes(req),
// Not tracked: the proxy streams upstream bodies through bounded readers
// and jsonResponse Content-Length reflects only the local denial/error
// envelopes — never the proxied upstream size. Size questions belong to
// MAX_MCP_PROXY_RESPONSE_BYTES, not to this row. null (not 0) so unknown
// is not confused with an empty body (#8403).
resBytes: null,
customerId: usageIdentity.customer_id,View on GitHub (pinned to e586b8b4b8)