multica-ai/multica · error
config must be a JSON object
Error message
config must be a JSON object
What it means
validateWorkspaceMcpServerEntry checks the shape of one MCP server config entry before storage and rejects input that is empty after trimming (empty bytes, whitespace-only raw JSON). The validation is deliberately shallow — shape only — because entries carry runtime secrets the server refuses to echo in errors. This is the empty-raw branch, distinct from the unparseable branch at line 170.
Source
Thrown at server/internal/handler/workspace_mcp.go:164
servers, err := unmarshalServerMap(doc[container])
if err != nil {
return nil, nil, fmt.Errorf("%s: %w", container, err)
}
for name := range servers {
names[name] = struct{}{}
}
}
return doc, names, nil
}
// validateWorkspaceMcpServerEntry checks the shape of ONE server entry before
// it is stored. Deliberately shallow — shape only, never the contents, which
// are runtime-specific and carry secrets we do not want to inspect or echo
// back in an error.
func validateWorkspaceMcpServerEntry(raw json.RawMessage) error {
trimmed := bytes.TrimSpace(raw)
if len(trimmed) == 0 {
return errors.New("config must be a JSON object")
}
var entry map[string]json.RawMessage
if err := json.Unmarshal(trimmed, &entry); err != nil {
// Never wrap: the underlying error can echo fragments of an entry that
// routinely embeds API tokens.
return errors.New("config must be a JSON object")
}
if len(entry) == 0 {
return errors.New("config must not be empty")
}
return nil
}
// validateWorkspaceMcpServerName checks a server name. The name is what the
// runtime mounts the server under and what an agent's own config collides
// with, so it follows the same rule the agent settings dialog enforces.
func validateWorkspaceMcpServerName(name string) error {
if name == "" {View on GitHub (pinned to 2c0912b6ec)
Solutions
- Remove empty server entries before submitting: filter entries whose value trims to empty
- Make the client form require at least a minimal object ({"command": "..."}) per row before submit
- If a server should be disabled, delete its entry rather than blanking it
Example fix
// before
servers: { github: "" }
// after
servers: { github: { command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] } } Defensive patterns
Strategy: validation
Validate before calling
for (const [name, entry] of Object.entries(servers)) {
const raw = typeof entry === 'string' ? entry.trim() : entry;
if (!raw || (typeof raw === 'string' && raw === '')) delete servers[name];
} Type guard
function isNonEmptyJson(entry: unknown): boolean {
return entry !== null && entry !== undefined
&& !(typeof entry === 'string' && entry.trim() === '');
} Prevention
- Filter empty server rows before submit
- Make empty form rows local-only until minimally filled
- Blank an entry by removing the key, not by emptying it
When it happens
Trigger: PUT/POST workspace MCP servers with a servers map where one entry is "", " ", or null-ish raw bytes — commonly a client sending {"servers": {"foo": ""}} because the UI added an empty card that was never filled.
Common situations: Dynamic form rows that submit placeholder empty entries; template-driven configs where an optional server variable interpolates to nothing; JSON built with conditional spreads that leave empty strings.
Related errors
- config must not be empty
- entry must be a JSON object
- local server missing required field `command`
- remote server missing required field `url`
- missing required field `type` (must be "local" or "remote",
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/033cf8ff8effcc06.
Report an issue: GitHub.