Tencent/WeKnora · error

OAuth token response did not contain an access_token

Error message

OAuth token response did not contain an access_token

What it means

SaveToken persists a transport.Token but refuses to store a nil token or one with an empty AccessToken — such a token is unusable for authenticated requests. This error indicates the OAuth token endpoint response was malformed or the exchange produced no access token.

Solutions

  1. Inspect the token exchange response before SaveToken: log (redacted) status code and body to see whether the provider returned an error like invalid_grant.
  2. Verify client_id/client_secret, redirect_uri, and authorization code are correct and the code was not already consumed (codes are single-use).
  3. Check your provider's token response field name matches what transport.Token parsing expects (access_token).
  4. Never call SaveToken after a failed exchange — gate it on a successful, non-empty exchange result.

Example fix

// before
tok, _ := exchange(ctx, code)
store.SaveToken(ctx, tok)
// after
tok, err := exchange(ctx, code)
if err != nil { return fmt.Errorf("token exchange failed: %w", err) }
if tok == nil || tok.AccessToken == "" {
    return fmt.Errorf("exchange returned no access token; check provider error response")
}
return store.SaveToken(ctx, tok)
Defensive patterns

Strategy: validation

Validate before calling

if token == nil || token.AccessToken == "" {
    return fmt.Errorf("refusing to save token: exchange returned no access_token")
}

Type guard

func hasAccessToken(t *transport.Token) bool {
    return t != nil && t.AccessToken != ""
}

Try / catch

if err := store.SaveToken(ctx, token); err != nil {
    if strings.Contains(err.Error(), "did not contain an access_token") {
        return fmt.Errorf("token exchange failed; verify provider credentials and error response: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: SaveToken called with a nil *transport.Token, or with a token whose AccessToken field is empty (provider returned error/JSON without access_token, or the token struct was constructed without the access token).

Common situations: OAuth provider returned 4xx with a JSON body the transport parsed into an empty token; wrong token endpoint or client credentials caused an error response; provider uses a nonstandard field name for the access token; network proxy stripped the response body.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/dfbc157cfe3ce740. Report an issue: GitHub.

Appendix: source

Thrown at internal/mcp/oauth_tokenstore.go:88

	}
	if row == nil || row.AccessToken == "" {
		return nil, transport.ErrNoToken
	}
	return &transport.Token{
		AccessToken:  row.AccessToken,
		RefreshToken: row.RefreshToken,
		TokenType:    row.TokenType,
		ExpiresAt:    row.ExpiresAt,
	}, nil
}

// SaveToken persists a freshly issued or refreshed token.
func (s *dbTokenStore) SaveToken(ctx context.Context, token *transport.Token) error {
	if err := ctx.Err(); err != nil {
		return err
	}
	if token == nil || token.AccessToken == "" {
		return fmt.Errorf("OAuth token response did not contain an access_token")
	}
	if token.TokenType == "" {
		token.TokenType = "Bearer"
	}
	expiresAt := token.ExpiresAt
	if expiresAt.IsZero() && token.ExpiresIn > 0 {
		expiresAt = time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
	}
	principal := s.principal.Normalize()
	return s.repo.SaveTokenForPrincipal(ctx, &types.MCPOAuthToken{
		TenantID:      s.tenantID,
		PrincipalType: principal.Type,
		PrincipalID:   principal.ID,
		UserID:        principal.StorageID(),
		ServiceID:     s.serviceID,
		AccessToken:   token.AccessToken,
		RefreshToken:  token.RefreshToken,
		TokenType:     token.TokenType,

View on GitHub (pinned to 988cbb0330)