koala73/worldmonitor · error · McpProxyUpstreamError

MCP server request failed

Error message

MCP server request failed

What it means

fetchMcpUpstream wraps the proxy's upstream fetch and converts network-level rejections into McpProxyUpstreamError('MCP server request failed', { cause }). Timeouts are rethrown as-is (isTimeout check) so timeout handling stays distinct; everything else — DNS failure, connection refused, TLS error, abort mid-connect — is wrapped with a generic message.

Solutions

  1. Check the error.cause to identify the root cause (ECONNREFUSED, ENOTFOUND, TLS alert, etc.)
  2. Verify the MCP server is running and reachable at the configured serverUrl/port from the proxy's network
  3. Test with curl from the same network; fix DNS, firewall, or TLS configuration
  4. If it is a timeout, handle it via the timeout path (the original error is preserved)

Example fix

// before
const upstream = await fetchMcpUpstream(url, init); // throws opaque McpProxyUpstreamError
// after
try { return await fetchMcpUpstream(url, init); }
catch (e) { if (e instanceof McpProxyUpstreamError) log.error('mcp upstream', e.cause); throw e; }
Defensive patterns

Strategy: try-catch

Validate before calling

// before configuring the proxy
const u = new URL(serverUrl); if (u.protocol !== 'https:' && u.hostname !== 'localhost') throw new Error('https required for remote MCP servers');

Type guard

null

Try / catch

try { return await fetchMcpUpstream(url, init); } catch (e) { if (e instanceof McpProxyUpstreamError) { log('mcp upstream failed', { cause: e.cause }); return new Response(JSON.stringify({ error: 'upstream_unreachable' }), { status: 502 }); } throw e; }

Prevention

When it happens

Trigger: POST/GET through /api/mcp-proxy where the configured serverUrl host is unreachable: wrong port, server down, TLS misconfiguration, IPv6-only host on IPv4 network, or the connection resets during the request. Inspect error.cause for the underlying reason.

Common situations: MCP server not running or crashed; serverUrl pointing at localhost from a deployed edge function; firewall blocking egress; self-signed/expired certificates.

Related errors


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

Appendix: source

Thrown at api/mcp-proxy.ts:271

  const isTimeout = (error instanceof Error && error.name === 'TimeoutError')
    || message.includes('TimeoutError')
    || message.includes('timed out');
  const isExpectedExternal = error instanceof McpProxyUpstreamError
    || error instanceof McpProxySsrfError
    || error instanceof ResponseBodyTooLargeError
    || error instanceof McpProxyJsonDepthError;
  return {
    isTimeout,
    level: isTimeout || isExpectedExternal ? 'warning' : 'error',
  };
}

async function fetchMcpUpstream(input, init) {
  try {
    return await fetch(input, init);
  } catch (error) {
    if (proxyFailureFor(error).isTimeout) throw error;
    throw new McpProxyUpstreamError('MCP server request failed', { cause: error });
  }
}

// Generic message surfaced to the caller when a serverUrl resolves to a
// private/reserved address. The specific blocked IP is deliberately NOT echoed
// back: returning it turns the proxy into an address oracle (the caller could
// enumerate internal IPs by observing which hostnames get blocked). SSRF review
// finding — log the concrete IP server-side for debugging, tell the caller only
// that the host is disallowed.
const SSRF_BLOCKED_PUBLIC_MESSAGE = 'serverUrl host is not allowed';

function throwBlockedAddress(blockedAddress) {
  // Server-side audit/debug log with the concrete blocked address. This is the
  // only place the resolved internal IP appears; it never reaches the response.
  console.error('[mcp-proxy]', {
    event: 'mcp_proxy_ssrf_blocked',
    ts: new Date().toISOString(),
    blocked_address: blockedAddress,

View on GitHub (pinned to 7d06c8633d)