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
- Prefix the URL with the scheme: "http://localhost:3000" instead of "localhost:3000"
- Use https:// for remote servers over the public internet
- Replace ws:// with its HTTP(S) equivalent endpoint if the server exposes one
- 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
- Always include the scheme: "http://localhost:3000", never "localhost:3000"
- Validate configured URLs with new URL() + protocol check at config-load time
- Don't paste ws:// or file:// URLs into HTTP server URL fields
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
- OpenWork-managed OAuth requires a remote MCP URL.
- mcp_server_not_remote
- invalid_mcp_url
- OpenWork Connect MCP metadata is invalid.
- extensions.add_mcp_url_required
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/6291e31479837165.
Report an issue: GitHub.