multica-ai/multica · error

missing required field `type` (must be "local" or "remote",

Error message

missing required field `type` (must be "local" or "remote", or use bare {"enabled": bool} to override an inherited server)

What it means

Returned when a mcp_config entry has no `type` field and is not the bare {"enabled": bool} override shape that OpenCode supports for disabling an inherited server. The validator deliberately collapses two failure modes (strict-decode failure on unknown fields, or a missing/nullable `enabled`) into one friendly message, because the user usually tried to write a local/remote server and forgot `type`.

Source

Thrown at server/pkg/agent/opencode_mcp.go:260

		}
		if entry.Timeout != nil && *entry.Timeout <= 0 {
			return nil, wrap(fmt.Errorf("`timeout` must be a positive integer, got %d", *entry.Timeout))
		}
		if len(entry.OAuth) > 0 {
			if err := validateOpenCodeOAuth(entry.OAuth); err != nil {
				return nil, wrap(fmt.Errorf("`oauth`: %w", err))
			}
		}
	case "":
		// No `type` field. The bare `{"enabled": bool}` override shape
		// is OpenCode's third native variant; anything else without a
		// type is a malformed local/remote attempt. Surface a single
		// friendly "missing type" error instead of the strict-decode
		// "json: unknown field" leak — the user usually didn't realise
		// they were mis-using the override shape.
		var entry opencodeMCPEnabledOnly
		if err := strictDecode(raw, &entry); err != nil || entry.Enabled == nil {
			return nil, wrap(errors.New("missing required field `type` (must be \"local\" or \"remote\", or use bare {\"enabled\": bool} to override an inherited server)"))
		}
	default:
		return nil, wrap(fmt.Errorf("invalid type %q (must be \"local\" or \"remote\")", typeStr))
	}

	// Validation passed; re-decode the raw bytes into map[string]any for
	// the output. Identical observable representation, just typed as a
	// generic map for the caller.
	var out map[string]any
	if err := json.Unmarshal(raw, &out); err != nil {
		return nil, wrap(fmt.Errorf("parse: %w", err))
	}
	return out, nil
}

// strictDecode runs a json.Decoder with DisallowUnknownFields so any
// field outside the target struct's tags is rejected, enforcing the
// schema's `additionalProperties: false`.

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Add "type": "local" (with `command`) or "type": "remote" (with `url`) to the entry.
  2. For pure enable/disable of an inherited server, use exactly {"enabled": true} or {"enabled": false} with no other keys.
  3. Move any transport fields (command/url/env) into the typed form rather than mixing them with the override shape.

Example fix

// before
"mcp_config": { "fs": { "command": ["npx", "-y", "server-fs"] } }

// after
"mcp_config": { "fs": { "type": "local", "command": ["npx", "-y", "server-fs"] } }
Defensive patterns

Strategy: validation

Validate before calling

function validateMCPEntryShape(e: Record<string, unknown>): string | null {
  const keys = Object.keys(e);
  if (!('type' in e)) {
    const bare = keys.length === 1 && typeof e['enabled'] === 'boolean';
    if (!bare) return 'entry needs `type` (local/remote) or must be exactly {"enabled": boolean}';
  }
  return null;
}

Type guard

function isMCPOverrideEntry(e: Record<string, unknown>): e is { enabled: boolean } {
  return Object.keys(e).length === 1 && typeof e['enabled'] === 'boolean';
}

Prevention

When it happens

Trigger: Writing {"command": ["npx", ...]} or {"url": "https://..."} without "type"; writing {"enabled": "true"} (string, fails strict decode); writing {} (empty object, `enabled` is nil).

Common situations: Assuming the server kind is inferred from which fields are present; copying configs from tools that don't require a discriminator; attempting the override shape but with extra fields or a non-boolean enabled.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/0811335995545cc5. Report an issue: GitHub.