different-ai/openwork · error · LocalManagedMcpPrivateUrlError

the address is private or reserved

Error message

the address is private or reserved

What it means

After confirming HTTPS, assertLocalManagedMcpUrl resolves the hostname and rejects addresses that are private, loopback, link-local, or otherwise reserved (checked via isLocalManagedMcpPrivateAddress). This prevents SSRF-style egress to internal infrastructure from the managed MCP runtime.

Source

Thrown at apps/server/src/local-managed-mcp-url-guard.ts:178

      }
      const first = addresses[0];
      callback(null, first.address, first.family);
    }, (error: unknown) => {
      callback(error instanceof Error ? error : new Error("Managed MCP hostname lookup failed."), []);
    });
  };
}

export async function assertLocalManagedMcpUrl(rawUrl: string): Promise<void> {
  const url = parseHttpUrl(rawUrl);
  if (allowPrivateUrls()) return;
  if (url.protocol !== "https:") {
    throw new LocalManagedMcpPrivateUrlError(rawUrl, "managed MCP egress requires HTTPS");
  }
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
  if (isIP(hostname)) {
    if (isLocalManagedMcpPrivateAddress(hostname)) {
      throw new LocalManagedMcpPrivateUrlError(rawUrl, "the address is private or reserved");
    }
    return;
  }
  let addresses: LookupAddress[];
  try {
    addresses = await resolveAddresses(hostname, { all: true, verbatim: true });
  } catch {
    throw new LocalManagedMcpPrivateUrlError(rawUrl, "the hostname does not resolve");
  }
  validateResolvedAddresses(hostname, addresses);
}

type FetchLike = (url: string | URL, init?: RequestInit) => Promise<Response>;
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
const guardedDispatcher = new Agent({
  connect: {
    lookup: createLocalManagedMcpPublicLookup(),
    // Node's 250 ms family-attempt default is too aggressive for otherwise

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Use the public DNS hostname of the MCP server instead of a private IP
  2. If the connection is intentionally local/internal, enable the allowPrivateUrls() switch for that environment
  3. Check for typos in the configured IP (e.g. 10.0.0.1 vs a public address)
  4. Verify the server publishes a public endpoint reachable over HTTPS

Example fix

// before
await createLocalManagedMcpConnection({ url: "https://192.168.1.10/mcp" });
// after
await createLocalManagedMcpConnection({ url: "https://mcp.internal-gateway.example.com/mcp" });
Defensive patterns

Strategy: validation

Validate before calling

import { isIP } from "node:net";
const host = new URL(rawUrl).hostname.replace(/^\[|\]$/g, "");
if (isIP(host) && (host === "127.0.0.1" || host.startsWith("10.") || host.startsWith("192.168.") || host === "::1")) {
  throw new Error(`Refusing private address for managed MCP: ${host}`);
}

Type guard

function isPublicLiteralIp(hostname: string): boolean {
  return isIP(hostname) === 0 || !isLocalManagedMcpPrivateAddress(hostname);
}

Try / catch

try {
  await createLocalManagedMcpConnection({ url });
} catch (error) {
  if (error instanceof LocalManagedMcpPrivateUrlError && error.message === "the address is private or reserved") {
    // prompt user to provide the public hostname of the MCP server
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling createLocalManagedMcpConnection or withRemoteClient with a URL whose hostname is a literal IP (e.g. 127.0.0.1, 10.x.x.x, 192.168.x.x, ::1) that isLocalManagedMcpPrivateAddress classifies as private/reserved, while private URLs are not allowed.

Common situations: Configuring an MCP server by internal cluster IP instead of its public hostname; pointing at 127.0.0.1 for a locally running server; IPv6 literals like [::1] (brackets are stripped before the check); Docker-internal addresses like 172.17.x.x.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/06ac11cbed05f13d. Report an issue: GitHub.