nanocoai/nanoclaw · error · Error

type must be "stdio", "http", or "streamable-http"

Error message

type must be "stdio", "http", or "streamable-http"

What it means

parseMcpServerConfig requires a declared transport type to be one of stdio, http, or streamable-http; anything else (e.g. 'websocket', 'ws', typo'd 'http2') throws. Absence of type is allowed (inferred from url/command presence).

Source

Thrown at src/container-config.ts:136

const CWD_FORM_RE = /^(?:\.\/|\$\{PLUGIN_ROOT\}(?:\/|$)|\$\{PLUGIN_DATA\}(?:\/|$))/;

/**
 * Parse one CLI or approval payload into the persisted MCP config shape.
 * Duplicated in container/agent-runner/src/mcp-tools/self-mod.ts
 * (parseMcpServerInput) — no shared modules across the host/container
 * boundary; keep the two in sync.
 */
export function parseMcpServerConfig(input: Record<string, unknown>): McpServerConfig {
  const command = typeof input.command === 'string' && input.command.trim() ? input.command : undefined;
  const url = typeof input.url === 'string' && input.url.trim() ? input.url.trim() : undefined;

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

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. Set type to "stdio" (with command) or "http"/"streamable-http" (with url)
  2. Or omit type entirely and provide either url or command for inference

Example fix

// before
{"type": "websocket", "url": "https://mcp.example.com"}
// after
{"type": "http", "url": "https://mcp.example.com/mcp"}
Defensive patterns

Strategy: type-guard

Validate before calling

const SUPPORTED = new Set(['stdio','http','streamable-http']);
if (entry.type !== undefined && !SUPPORTED.has(entry.type)) throw new Error('unsupported type');

Type guard

function isMcpTransport(t: unknown): t is 'stdio' | 'http' | 'streamable-http' {
  return t === undefined || t === 'stdio' || t === 'http' || t === 'streamable-http';
}

Prevention

When it happens

Trigger: An MCP server entry with "type": "websocket" or any string other than stdio|http|streamable-http in container config or add-mcp-server input.

Common situations: Speculative transport values copied from other tooling, or typos like 'strdio'.

Related errors


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