mvanhorn/last30days-skill · error

format must be 'text' or 'json', got %q

Error message

format must be 'text' or 'json', got %q

What it means

Thrown by preflightFormatArgument (mcp/internal/tools/preflight.go:83) when the 'preflight' MCP tool is called with a 'format' argument that is a string but not one of the allowed values. The switch accepts "" (treated as "text"), "text", and "json"; anything else — "Text", "JSON", "md", "yaml" — hits the default case. The error quotes the offending value with %q so the caller sees exactly what the server received. Non-string types are caught earlier by a separate 'format must be a string' error.

Source

Thrown at mcp/internal/tools/preflight.go:83

	return runArgs
}

func preflightFormatArgument(args map[string]any) (string, error) {
	raw, ok := args["format"]
	if !ok {
		return "text", nil
	}
	value, ok := raw.(string)
	if !ok {
		return "", errors.New("format must be a string")
	}
	switch value {
	case "", "text":
		return "text", nil
	case "json":
		return "json", nil
	default:
		return "", fmt.Errorf("format must be 'text' or 'json', got %q", value)
	}
}

View on GitHub (pinned to c7460f6114)

Solutions

  1. Pass format: "json" or format: "text" exactly (lowercase), or omit the argument entirely — omission defaults to "text".
  2. If you want structured machine-readable preflight output, use "json"; there is no markdown/html option on this tool.
  3. Check for case or whitespace typos in the argument; the comparison is exact and case-sensitive.
  4. If you're a client author, read the tool's inputSchema (declared via mcplib.WithString("format", ...)) instead of hardcoding guesses.

Example fix

// before (MCP tool call)
{"name": "preflight", "arguments": {"format": "markdown"}}
// -> error: format must be 'text' or 'json', got "markdown"

// after
{"name": "preflight", "arguments": {"format": "json"}}
Defensive patterns

Strategy: validation

Validate before calling

// Validate before calling the preflight tool.
func normalizePreflightFormat(v any) (string, error) {
	if v == nil {
		return "text", nil
	}
	s, ok := v.(string)
	if !ok {
		return "", errors.New("format must be a string")
	}
	switch s {
	case "", "text", "json":
		return s, nil
	}
	return "", fmt.Errorf("unsupported preflight format %q (want text|json)", s)
}

Type guard

func isPreflightFormat(v any) bool {
	switch v {
	case nil, "", "text", "json":
		return true
	}
	return false
}

Prevention

When it happens

Trigger: Calling the MCP 'preflight' tool with format set to any string other than '', 'text', or 'json' — e.g. {"format": "markdown"}, {"format": "Json"} (case-sensitive), or {"format": "txt"}. Common when an LLM client guesses an output format the tool doesn't offer, or when a script copies a --emit value (like 'compact' or 'html', which belong to the research tool) into preflight's format argument.

Common situations: Model-driven MCP clients inventing plausible enum values; reusing the research tool's emit vocabulary ('compact'/'html') on the preflight tool; case mismatches ('JSON' vs 'json'); stale client code written against an older or imagined schema.

Related errors


AI-assisted analysis of mvanhorn/last30days-skill@c7460f6114 (2026-08-15). Data as JSON: /api/errors/d06a729d4894ae7a. Report an issue: GitHub.