multica-ai/multica · error

entry must be a JSON object

Error message

entry must be a JSON object

What it means

Thrown by validateOpenCodeNativeMCPEntry when a per-server entry in the agent's opencode mcp_config is not a JSON object. The validator's discriminator probe and strict decoders assume an object, so a primitive (string, number, array, null, or empty raw) is rejected up front with this clear message instead of a confusing decoder error. The message is wrapped as `opencode mcp_config: server "<name>": ...`.

Source

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

// validateOpenCodeNativeMCPEntry strict-decodes one native-shape entry
// against OpenCode's schema and returns the equivalent map[string]any
// representation. The decode is intentionally strict
// (DisallowUnknownFields) — any field outside the McpLocalConfig /
// McpRemoteConfig / `{enabled: bool}` shapes is rejected, matching the
// schema's `additionalProperties: false` and surfacing user typos as
// errors before they reach OpenCode.
func validateOpenCodeNativeMCPEntry(name string, raw json.RawMessage) (map[string]any, error) {
	wrap := func(err error) error {
		return fmt.Errorf("opencode mcp_config: server %q: %w", name, err)
	}

	// JSON-object guard: the discriminator probe and strict decoders
	// below assume an object; without this guard a primitive (string,
	// number, array, null) would surface a confusing decoder error.
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 || trimmed[0] != '{' {
		return nil, wrap(errors.New("entry must be a JSON object"))
	}

	// Discriminator probe: peek `type` to choose the right strict
	// decode target. This first decode is intentionally permissive so
	// "type: 5" surfaces a clear "type must be a string" error rather
	// than the strict-decode generic "json: cannot unmarshal number".
	var probe struct {
		Type *json.RawMessage `json:"type,omitempty"`
	}
	if err := json.Unmarshal(raw, &probe); err != nil {
		return nil, wrap(fmt.Errorf("parse: %w", err))
	}
	var typeStr string
	if probe.Type != nil {
		if err := json.Unmarshal(*probe.Type, &typeStr); err != nil {
			return nil, wrap(fmt.Errorf("`type` must be a string, got %s", strings.TrimSpace(string(*probe.Type))))
		}
	}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Wrap each server entry in a JSON object with an explicit type: {"type":"remote","url":"..."} or {"type":"local","command":[...]}
  2. Remove null entries for servers you do not want to configure.
  3. Re-check the OpenCode mcp_config schema for the supported variants (local, remote, bare {"enabled": bool}).

Example fix

// before
"mcp_config": { "github": "https://mcp.github.com/sse" }

// after
"mcp_config": { "github": { "type": "remote", "url": "https://mcp.github.com/sse" } }
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate before submitting mcp_config
for name, raw := range mcpConfig {
    if !isObject(raw) {
        return fmt.Errorf("mcp_config entry %q must be a JSON object", name)
    }
}

Type guard

function isMCPEntryObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Prevention

When it happens

Trigger: Setting mcp_config.<name> to a scalar or array, e.g. "mcp_config": {"github": "https://mcp.github.com"} or {"github": ["npx", "-y", "server"]} or {"github": null}, via the agent config API or config file.

Common situations: Copying an SSH-style or Claude-style config line (bare command string / argv array) into OpenCode's native mcp_config; YAML/JSON config authoring mistakes; passing a URL string where a {"type":"remote","url":...} object is required.

Related errors


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