googleapis/mcp-toolbox · error

failed to unmarshal response: %v

Error message

failed to unmarshal response: %v

What it means

doLogin fails to parse the body returned by the Dgraph /login endpoint as JSON into the expected {data:{accessJWT,refreshJWT}} shape. This means the server (or an intermediary) responded with malformed JSON, an empty body, or non-JSON content such as an HTML error page. The underlying parse error is wrapped with %v.

Source

Thrown at internal/sources/dgraph/dgraph.go:327

			!strings.Contains(err.Error(), "unable to authenticate the refresh token") {
			return hc.loginWithToken()
		}
		return err
	}

	if err := checkError(resp); err != nil {
		return err
	}

	var r struct {
		Data struct {
			AccessJWT  string `json:"accessJWT"`
			RefreshJWT string `json:"refreshJWT"`
		} `json:"data"`
	}

	if err := json.Unmarshal(resp, &r); err != nil {
		return fmt.Errorf("failed to unmarshal response: %v", err)
	}

	if r.Data.AccessJWT == "" {
		return fmt.Errorf("no access JWT found in the response")
	}
	if r.Data.RefreshJWT == "" {
		return fmt.Errorf("no refresh JWT found in the response")
	}

	hc.AccessJwt = r.Data.AccessJWT
	hc.RefreshToken = r.Data.RefreshJWT
	return nil
}

func (hc *DgraphClient) healthCheck() error {
	url, err := getUrl(hc.baseUrl, "/health", nil)
	if err != nil {
		return err

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Verify hc.baseUrl points to Dgraph Alpha's HTTP port (default 8080), not the gRPC port 9080.
  2. Log or print the raw response body to see whether it is HTML, empty, or JSON with an unexpected shape.
  3. Bypass intermediaries: curl the /login endpoint directly to rule out proxy/load-balancer error pages.
  4. Confirm the Dgraph version's /login response uses {"data":{"accessJWT":...,"refreshJWT":...}} and update the parsing struct if not.
  5. Check TLS configuration if connecting through HTTPS — certificate errors can yield non-JSON responses.

Example fix

// debugging: inspect what /login actually returned
fmt.Printf("login response: %s\n", string(resp))
// before
// (no diagnostics — opaque unmarshal failure)
// after
// raw body is logged before unmarshal, revealing HTML proxy pages or empty bodies
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight: ensure the endpoint speaks JSON before logging in
resp, err := http.Get(baseURL + "/health")
if err != nil || resp.StatusCode != 200 {
  return fmt.Errorf("dgraph endpoint not reachable/healthy at %s", baseURL)
}

Type guard

func isJSONResponse(body []byte) bool {
  t := bytes.TrimSpace(body)
  return len(t) > 0 && (t[0] == '{' || t[0] == '[')
}

Try / catch

if err := doLogin(...); err != nil {
  if strings.Contains(err.Error(), "failed to unmarshal response") {
    log.Printf("non-JSON login response; verify baseURL port (8080 vs 9080) and proxies: %v", err)
    return
  }
}

Prevention

When it happens

Trigger: json.Unmarshal(resp, &r) errors after the login POST completes — response body is empty, truncated, HTML (proxy/load-balancer error page), or JSON with a different structure than expected.

Common situations: Wrong port (hitting Dgraph Alpha's gRPC port 9080 over HTTP), a reverse proxy or API gateway returning an HTML 502/403 page, TLS termination issues, or a Dgraph version whose login response shape differs.

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/5c416057d897793c. Report an issue: GitHub.