different-ai/openwork · error · LocalManagedMcpPrivateUrlError

protocol "${url.protocol}" is not allowed

Error message

protocol "${url.protocol}" is not allowed

What it means

LocalManagedMcpPrivateUrlError thrown by parseHttpUrl when a configured local managed MCP server URL uses a scheme other than http: or https:. The guard only permits HTTP(S) because it must fetch and validate the URL; schemes like file:, ws:, or ftp: are rejected before any network work.

Source

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

  return first === 0x2002 && isPrivateIpv4(embeddedIpv4(words, 1));
}

export function isLocalManagedMcpPrivateAddress(address: string): boolean {
  const version = isIP(address);
  if (version === 4) return isPrivateIpv4(address);
  if (version === 6) return isPrivateIpv6(address);
  return true;
}

function parseHttpUrl(rawUrl: string): URL {
  let url: URL;
  try {
    url = new URL(rawUrl);
  } catch {
    throw new LocalManagedMcpPrivateUrlError(rawUrl, "not a valid URL");
  }
  if (url.protocol !== "http:" && url.protocol !== "https:") {
    throw new LocalManagedMcpPrivateUrlError(rawUrl, `protocol "${url.protocol}" is not allowed`);
  }
  if (url.username || url.password) {
    throw new LocalManagedMcpPrivateUrlError(rawUrl, "embedded URL credentials are not allowed");
  }
  return url;
}

function allowPrivateUrls(): boolean {
  return process.env.OPENWORK_DEV_MODE === "1" || process.env.OPENWORK_ALLOW_PRIVATE_MCP_URLS === "1";
}

type ResolveAddresses = (hostname: string, options: LookupAllOptions) => Promise<LookupAddress[]>;

const resolveAddresses: ResolveAddresses = (hostname, options) => lookup(hostname, options);

function validateResolvedAddresses(hostname: string, addresses: LookupAddress[]): void {
  if (addresses.length === 0) {
    throw new LocalManagedMcpPrivateUrlError(`https://${hostname}/`, "the hostname does not resolve");

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Prefix the URL with the scheme: "http://localhost:3000" instead of "localhost:3000"
  2. Use https:// for remote servers over the public internet
  3. Replace ws:// with its HTTP(S) equivalent endpoint if the server exposes one
  4. Print/inspect the raw url in the error to spot the missing or wrong scheme

Example fix

// before
{ "mcpServers": { "tools": { "url": "localhost:8080" } } }
// after
{ "mcpServers": { "tools": { "url": "http://localhost:8080" } } }
Defensive patterns

Strategy: validation

Validate before calling

function assertHttpUrl(raw: string): URL {
  const u = new URL(raw);
  if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(`MCP url must start with http:// or https:// — got "${u.protocol}"`);
  return u;
}

Type guard

function isHttpUrl(raw: string): boolean {
  try { const u = new URL(raw); return u.protocol === "http:" || u.protocol === "https:"; } catch { return false; }
}

Try / catch

try {
  await mcp.addServer({ url: rawUrl });
} catch (e) {
  if (e instanceof LocalManagedMcpPrivateUrlError && e.message.includes("not allowed")) {
    throw new Error(`fix scheme: use http://${rawUrl} or https://... (got: ${rawUrl})`);
  } else throw e;
}

Prevention

When it happens

Trigger: Registering/starting a local managed MCP server whose url field is e.g. "file:///path", "ws://host", "localhost:8080" (URL parses protocol as "localhost:"), or a URL with trailing junk that yields an odd protocol.

Common situations: Typo like "localhost:3000" without the http:// scheme (URL() parses "localhost:" as the protocol); copying a WebSocket URL into an HTTP config; using file:// paths where an HTTP server URL is expected.

Related errors


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