RefreshOAuth2Token checks whether the given MCP user token is expired (or within 10 seconds of expiry) and refreshes it using the OAuth2 credentials from the server config. If the token is still valid, no network call is made and Refreshed is false. The caller is responsible for persisting the resu
( ctx context.Context, cfg database.MCPServerConfig, tok database.MCPServerUserToken, )
| 838 | // The caller is responsible for persisting the result when |
| 839 | // Refreshed is true. |
| 840 | func RefreshOAuth2Token( |
| 841 | ctx context.Context, |
| 842 | cfg database.MCPServerConfig, |
| 843 | tok database.MCPServerUserToken, |
| 844 | ) (RefreshResult, error) { |
| 845 | oauth2Cfg := &oauth2.Config{ |
| 846 | ClientID: cfg.OAuth2ClientID, |
| 847 | ClientSecret: cfg.OAuth2ClientSecret, |
| 848 | Endpoint: oauth2.Endpoint{ |
| 849 | TokenURL: cfg.OAuth2TokenURL, |
| 850 | }, |
| 851 | } |
| 852 | |
| 853 | oldToken := &oauth2.Token{ |
| 854 | AccessToken: tok.AccessToken, |
| 855 | RefreshToken: tok.RefreshToken, |
| 856 | TokenType: tok.TokenType, |
| 857 | } |
| 858 | if tok.Expiry.Valid { |
| 859 | oldToken.Expiry = tok.Expiry.Time |
| 860 | } |
| 861 | |
| 862 | // Cap the refresh HTTP call so a stalled token endpoint |
| 863 | // cannot block the entire MCP connection phase. The timeout |
| 864 | // matches connectTimeout used for MCP server connections. |
| 865 | refreshCtx, cancel := context.WithTimeout(ctx, connectTimeout) |
| 866 | defer cancel() |
| 867 | |
| 868 | // TokenSource automatically refreshes expired tokens. It |
| 869 | // uses a 10-second expiry window, so tokens about to expire |
| 870 | // are also refreshed proactively. |
| 871 | newToken, err := oauth2Cfg.TokenSource(refreshCtx, oldToken).Token() |
| 872 | if err != nil { |
| 873 | return RefreshResult{}, xerrors.Errorf("refresh oauth2 token: %w", err) |
| 874 | } |
| 875 | |
| 876 | refreshed := newToken.AccessToken != tok.AccessToken |
| 877 | |
| 878 | // Preserve the old refresh token when the provider doesn't |
| 879 | // rotate (returns empty). |
| 880 | refreshToken := cmp.Or(newToken.RefreshToken, tok.RefreshToken) |
| 881 | |
| 882 | return RefreshResult{ |
| 883 | AccessToken: newToken.AccessToken, |
| 884 | RefreshToken: refreshToken, |
| 885 | TokenType: newToken.TokenType, |
| 886 | Expiry: newToken.Expiry, |
| 887 | Refreshed: refreshed, |
| 888 | }, nil |
| 889 | } |
no test coverage detected