sipeed/picoclaw · error
config validation failed: %w
Error message
config validation failed: %w
What it means
The normalized config document is validated against the embedded MCP schema before every save (helpers.go:154-161). It fails when a server entry violates the contract: missing `enabled`, having neither `command` nor `url`, containing an unknown property (servers set additionalProperties:false), or a `type` outside stdio/http/sse. Most often reached through `picoclaw mcp edit` saving hand-made changes.
Source
Thrown at cmd/picoclaw/internal/mcp/helpers.go:160
clone.Tools.MCP.Servers[name] = server
}
return &clone
}
func validateConfigDocument(data []byte) error {
var instance map[string]any
if err := json.Unmarshal(data, &instance); err != nil {
return fmt.Errorf("failed to decode serialized config: %w", err)
}
schema, err := loadMCPConfigSchema()
if err != nil {
return fmt.Errorf("failed to load MCP config schema: %w", err)
}
if err := schema.Validate(instance); err != nil {
return fmt.Errorf("config validation failed: %w", err)
}
return nil
}
func loadMCPConfigSchema() (*jsonschema.Resolved, error) {
mcpConfigSchemaOnce.Do(func() {
var schema jsonschema.Schema
if err := json.Unmarshal([]byte(mcpConfigSchemaJSON), &schema); err != nil {
errMcpConfigSchema = err
return
}
mcpConfigSchema, errMcpConfigSchema = schema.Resolve(nil)
})
return mcpConfigSchema, errMcpConfigSchema
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- Give every server entry "enabled" plus exactly one of "command" (stdio) or "url" (http/sse)
- Restrict each server entry to the known keys: enabled, deferred, command, args, env, env_file, type, url, headers
- Set "type" only to stdio, http, or sse, or omit it
Example fix
// before
"servers": { "s": { "command": "./srv", "timeout": 30 } }
// after
"servers": { "s": { "enabled": true, "command": "./srv" } } Defensive patterns
Strategy: validation
Validate before calling
func validServerEntry(s map[string]any) error {
if _, ok := s["enabled"]; !ok {
return fmt.Errorf("server entry missing 'enabled'")
}
_, hasCmd := s["command"]
_, hasURL := s["url"]
if hasCmd == hasURL { // neither or both
return fmt.Errorf("server entry needs exactly one of 'command' or 'url'")
}
if t, ok := s["type"].(string); ok {
switch t {
case "stdio", "http", "sse":
default:
return fmt.Errorf("type %q not in stdio/http/sse", t)
}
}
return nil
} Try / catch
if err := saveCmd.Execute(); err != nil {
if strings.Contains(err.Error(), "config validation failed") {
// the wrapped jsonschema error names the offending pointer/property;
// print it verbatim to locate the bad server entry
fmt.Fprintf(os.Stderr, "%v\n", errors.Unwrap(err))
}
} Prevention
- Every server entry: enabled:true plus exactly one of command or url
- Do not port extra keys (timeout, cwd, env by other names) from other tools' MCP formats
- Prefer `picoclaw mcp add` over hand-editing — it constructs schema-valid entries
When it happens
Trigger: Editing the config to remove enabled:true from a server; deleting both command and url; adding an undocumented key like timeout or cwd inside a server entry; setting type: grpc.
Common situations: Porting entries from other tools' MCP config formats (Claude Desktop mcpServers, Claude Code .mcp.json) that allow extra keys; pruning fields too aggressively; typos in key names.
Related errors
- local command %q is a directory
- cannot found model '%s' in config
- ${label} must be a JSON object.
- ${label}.${key} must be a string.
- Enter and confirm the new login password.
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/66d4e84614c4bd5d.
Report an issue: GitHub.