sipeed/picoclaw · warning
${label} must be a JSON object.
Error message
${label} must be a JSON object. What it means
Thrown by parseJSONObjectField() in web/frontend/src/components/config/form-model.ts:519 when a text field meant to hold a JSON object (e.g. MCP server headers/env) parses as valid JSON but the top-level value is not an object. The function is strict: after JSON.parse succeeds it rejects arrays, null, strings, numbers, and booleans because the return type is Record<string, string>. The label prefixes the field so the user knows which textarea failed.
Source
Thrown at web/frontend/src/components/config/form-model.ts:519
export function parseJSONObjectField(
rawValue: string,
label: string,
): Record<string, string> {
const trimmed = rawValue.trim()
if (trimmed === "") {
return {}
}
let parsed: unknown
try {
parsed = JSON.parse(trimmed)
} catch {
throw new Error(`${label} must be valid JSON.`)
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error(`${label} must be a JSON object.`)
}
const entries = Object.entries(parsed as Record<string, unknown>)
const result: Record<string, string> = {}
for (const [key, value] of entries) {
if (typeof value !== "string") {
throw new Error(`${label}.${key} must be a string.`)
}
result[key] = value
}
return result
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- Change the field content to a flat JSON object of string values, e.g. {"Authorization": "Bearer tok", "X-Org": "abc"}
- Remove surrounding quotes or backticks around the object — a quoted object is parsed as a string, not an object
- If the error names a 'Saved MCP server ... headers' field, the stored config itself is bad: open the raw config page and fix that server's headers/env value there
- Leave the field empty — an empty string short-circuits to {} and is always accepted
Example fix
// before (field content)
["Authorization: Bearer tok"]
// after
{"Authorization": "Bearer tok"} Defensive patterns
Strategy: validation
Validate before calling
function isJSONObjectText(raw: string): boolean {
const trimmed = raw.trim()
if (trimmed === "") return true // empty is allowed
try {
const parsed = JSON.parse(trimmed)
return !!parsed && typeof parsed === "object" && !Array.isArray(parsed)
} catch {
return false
}
}
// before parseJSONObjectField(text, label):
if (!isJSONObjectText(text)) {
showFieldError(`${label} must be a JSON object (e.g. {"Key": "value"}).`)
return
} Type guard
function isRecord(v: unknown): v is Record<string, unknown> {
return !!v && typeof v === "object" && !Array.isArray(v)
} Try / catch
try {
const headers = parseJSONObjectField(text, "MCP server x headers")
} catch (err) {
// err.message already carries the label; surface it next to the field
setFieldError(err instanceof Error ? err.message : "Invalid JSON field")
} Prevention
- Validate JSON textareas on blur, not only at save time, so users learn the expected shape immediately
- Show a placeholder example like {"Authorization": "Bearer token"} in headers/env fields
- Lint saved configs once at load so corrupt baseline headersText fails with a named server instead of mid-save
When it happens
Trigger: Entering `["a","b"]`, `"x"`, `123`, `true`, or `null` into an MCP server 'headers' or 'env' JSON textarea in the config page and clicking Save; also triggered when parsing a saved baseline server's headersText via parseJSONObjectField(baselineServer.headersText, `Saved MCP server ${server.name} headers`) during save (config-page.tsx:489).
Common situations: Users paste a curl-style `-H 'Authorization: Bearer ...'` line instead of a JSON object; users wrap the object in quotes making it a JSON string; users paste a JSON array of header pairs; a previously saved config contains `[]` for headers.
Related errors
- ${label}.${key} must be a string.
- MCP discovery requires at least one search method (BM25 or r
- MCP server names must be unique. Duplicates: ${duplicateName
- MCP server ${server.name} requires a URL.
- MCP server ${server.name} requires a valid HTTP(S) URL.
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/99d60bc05f095cb1.
Report an issue: GitHub.