nanocoai/nanoclaw · error · Error

url must use HTTPS (plain HTTP is allowed only for localhost

Error message

url must use HTTPS (plain HTTP is allowed only for localhost and host.docker.internal)

What it means

The MCP server url parsed successfully but its protocol is not https:. Plain http is permitted only for loopback hosts (localhost, 127.0.0.1, [::1], host.docker.internal) because container-to-host traffic is the exception; everything else must be TLS-encrypted.

Source

Thrown at src/container-config.ts:159

  const instructions = input.instructions;
  if (instructions !== undefined && typeof instructions !== 'string') {
    throw new Error('MCP instructions must be a string');
  }

  if (url !== undefined) {
    if (command !== undefined) throw new Error('Provide exactly one of command or url');
    if (input.args !== undefined || input.env !== undefined || input.cwd !== undefined) {
      throw new Error('args, env, and cwd are only valid with command');
    }
    let parsed: URL;
    try {
      parsed = new URL(url);
    } catch (err) {
      throw new Error('url must be a valid HTTP(S) URL', { cause: err });
    }
    const loopback = ['localhost', '127.0.0.1', '[::1]', 'host.docker.internal'].includes(parsed.hostname);
    if (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopback)) {
      throw new Error('url must use HTTPS (plain HTTP is allowed only for localhost and host.docker.internal)');
    }
    if (parsed.username || parsed.password || parsed.hash) {
      throw new Error('url must not contain credentials or fragments; use OneCLI for authentication');
    }
    for (const key of parsed.searchParams.keys()) {
      if (SECRET_QUERY_KEY_RE.test(key.replace(CAMEL_SPLIT_RE, '$1_$2'))) {
        throw new Error(`url query parameter "${key}" looks like a credential; use OneCLI for authentication`);
      }
    }
    const headers = parseStringRecord(input.headers, 'headers');
    return {
      type: 'http',
      url,
      ...(headers === undefined ? {} : { headers }),
      ...(instructions === undefined ? {} : { instructions }),
    };
  }
  if (command === undefined) throw new Error('Provide exactly one of command or url');

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. Use an https:// url (put the server behind TLS or a reverse proxy)
  2. If the server runs on the Docker host, switch the hostname to host.docker.internal so plain http is allowed
  3. For other LAN hosts, add TLS via a local proxy (e.g. caddy/nginx) and use https

Example fix

// before
{"url":"http://192.168.1.5:8080/mcp"}
// after
{"url":"http://host.docker.internal:8080/mcp"}
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(entry.url); const loopback = ['localhost','127.0.0.1','[::1]','host.docker.internal'].includes(u.hostname); if (u.protocol !== 'https:' && !(u.protocol === 'http:' && loopback)) throw new UserError('use https or a loopback host');

Type guard

function isAllowedMcpUrl(s: string): boolean { const u = new URL(s); const lo = ['localhost','127.0.0.1','[::1]','host.docker.internal'].includes(u.hostname); return u.protocol === 'https:' || (u.protocol === 'http:' && lo); }

Try / catch

catch (err) { if (err.message.includes('must use HTTPS')) suggestTlsOrLoopback(); else throw err; }

Prevention

When it happens

Trigger: An http:// url with a non-loopback hostname, e.g. http://mcp.internal.example.com:8080/mcp; also http:// with a LAN IP like 192.168.1.5.

Common situations: Pointing at an internal HTTP-only server on the network; dev setup moved from localhost to a LAN host; port-forwarded service without TLS.

Related errors


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/6a7d1d95671816f1. Report an issue: GitHub.