nanocoai/nanoclaw · error · Error

Provide exactly one of command or url

Error message

Provide exactly one of command or url

What it means

Thrown when an MCP server entry provides BOTH `command` and `url`. The two fields select mutually exclusive transports (local process vs remote endpoint), and later code branches on url first, so the ambiguity is rejected up front at container-config.ts:147.

Source

Thrown at src/container-config.ts:147

  // A declared transport is honored; absence keeps the legacy CLI inference
  // (url → http, command → stdio). "streamable-http" is the Agent Plugins
  // spelling of the internal "http".
  const type = input.type === 'streamable-http' ? 'http' : input.type;
  if (type === 'sse') throw new Error('unsupported transport "sse"');
  if (type !== undefined && type !== 'stdio' && type !== 'http') {
    throw new Error('type must be "stdio", "http", or "streamable-http"');
  }
  if (type === 'stdio' && !command) throw new Error('type "stdio" requires command');
  if (type === 'http' && !url) throw new Error('type "http" requires url');

  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'))) {

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. Delete whichever of command/url you don't want
  2. If migrating to http, also remove args/env/cwd (they're stdio-only)
  3. Re-run ncl groups config get to confirm only one transport field remains

Example fix

// before
{"command":"npx","args":["srv"],"url":"https://mcp.example.com"}
// after
{"url":"https://mcp.example.com"}
Defensive patterns

Strategy: validation

Validate before calling

if (entry.command !== undefined && entry.url !== undefined) delete entry.url; // pick one transport explicitly

Type guard

const hasExactlyOneTransport = (e: any) => (e.command !== undefined) !== (e.url !== undefined);

Try / catch

catch (err) { if (err.message.includes('exactly one of command or url')) askUserToPickTransport(); else throw err; }

Prevention

When it happens

Trigger: An entry with {"command":"npx",...,"url":"https://..."} passed to parseMcpServerConfig via ncl add-mcp-server, validateAddMcpServer, or server config reads.

Common situations: Merging two example configs together; migrating stdio→http and forgetting to delete the old command field.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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