googleapis/mcp-toolbox · error

error reading start of json array: %w

Error message

error reading start of json array: %w

What it means

getStream streams the response as a JSON array and first reads the opening delimiter token with decoder.Token(). If that fails with anything other than io.EOF (io.EOF is treated as a valid empty response), this error is returned — meaning the response body was not a parseable JSON array.

Source

Thrown at internal/tools/bigquery/bigqueryconversationalanalytics/bigqueryconversationalanalytics.go:303

		return "", fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return "", fmt.Errorf("API returned non-200 status: %d %s", resp.StatusCode, string(body))
	}

	var messages []map[string]any
	decoder := json.NewDecoder(resp.Body)
	dataMsgIdx := -1

	// The response is a JSON array, so we read the opening bracket.
	if _, err := decoder.Token(); err != nil {
		if err == io.EOF {
			return "", nil // Empty response is valid
		}
		return "", fmt.Errorf("error reading start of json array: %w", err)
	}

	for decoder.More() {
		var rawMsg json.RawMessage
		if err := decoder.Decode(&rawMsg); err != nil {
			if err == io.EOF {
				break
			}
			return "", fmt.Errorf("error decoding raw message: %w", err)
		}

		var msg map[string]any
		if err := json.Unmarshal(rawMsg, &msg); err != nil {
			return "", fmt.Errorf("error unmarshaling raw message: %w", err)
		}

		var processedMsg map[string]any
		if dataResult := extractDataResult(msg); dataResult != nil {

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Log the raw response body to inspect what was actually returned (proxy HTML, truncated JSON, etc.).
  2. Check for proxies/gateways between the toolbox and the API that may rewrite or truncate responses.
  3. Retry the request — transient stream truncation often resolves on retry.
  4. If using a mock/test endpoint, make it return a JSON array of message objects.

Example fix

// before: mock returns an object
{"messages": []}
// after: mock must return an array like the real API
[{"systemMessage": {}}]
Defensive patterns

Strategy: retry

Validate before calling

resp, err := client.Do(req)
if err != nil { return err }
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "json") {
    return fmt.Errorf("unexpected content-type %q; proxy may be rewriting response", ct)
}

Try / catch

result, err := tool.Invoke(ctx, params)
if err != nil && strings.Contains(err.Error(), "error reading start of json array") {
    log.Printf("non-array response body; retrying: %v", err)
    return retryRequest(req)
}

Prevention

When it happens

Trigger: Invoke on the tool when the GDA endpoint returns a 200 response whose body is not a JSON array — e.g. an HTML error page from a proxy/load balancer, a truncated response, or an unexpected JSON object shape.

Common situations: Intercepting proxies or API gateways rewriting the response; responses cut off mid-stream (network interruption after headers); testing against a mock server that returns a JSON object instead of an array.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/45804d4ac7205d18. Report an issue: GitHub.