refresh_access_token
Refresh access tokens for Google Ads API sessions in multi-tenant mode to maintain authentication and continue API operations.
Instructions
Refresh the access token for a session (multi-tenant mode). Requires GOOGLE_OAUTH_CLIENT_ID/SECRET.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| session_key | Yes | UUID v4 session key |
Implementation Reference
- src/utils/connection-manager.ts:186-214 (handler)Core handler function that refreshes the access token for the given session key by calling the OAuth2 refresh and updating the session credentials.export async function refreshAccessTokenForSession(sessionKey: string): Promise<GoogleCredential> { const ctx = connections.get(sessionKey); if (!ctx) throw new Error('No session found'); const cred = ctx.credentials; if (!cred.refresh_token) throw new Error('No refresh token available'); if (ctx.refreshPromise) return ctx.refreshPromise; ctx.refreshPromise = refreshTokenWithOAuth2Client(cred) .then((updated) => { ctx.credentials = updated; ctx.refreshPromise = undefined; refreshCount++; try { emitMcpEvent({ timestamp: nowIso(), tool: 'token_refresh', session_key: sessionKey, response_time_ms: 0 }); } catch (e) { void e; } return updated; }) .catch((err) => { ctx.refreshPromise = undefined; // Purge on invalid_grant per security model if (String(err?.message || '').includes('invalid_grant')) { connections.delete(sessionKey); try { emitMcpEvent({ timestamp: nowIso(), tool: 'session_ended', session_key: sessionKey, response_time_ms: 0, reason: 'invalid_grant' }); } catch (e) { void e; } } refreshFailureCount++; try { emitMcpEvent({ timestamp: nowIso(), tool: 'token_refresh', session_key: sessionKey, response_time_ms: 0, error: { code: 'ERR_REFRESH_FAILED', message: String(err?.message || err) } }); } catch (e) { void e; } throw err; }); return ctx.refreshPromise; }
- src/server-tools.ts:847-879 (registration)Registration of the 'refresh_access_token' tool, including input validation, environment checks, and wrapper handler that delegates to refreshAccessTokenForSession.addTool( server, 'refresh_access_token', 'Refresh the access token for a session (multi-tenant mode). Requires GOOGLE_OAUTH_CLIENT_ID/SECRET.', RefreshAccessTokenZ, async (input: any) => { if (process.env.ENABLE_RUNTIME_CREDENTIALS !== 'true') { return { content: [{ type: 'text', text: 'Multi-tenant mode not enabled' }] }; } const clientId = (process.env.GOOGLE_OAUTH_CLIENT_ID || '').trim(); const clientSecret = (process.env.GOOGLE_OAUTH_CLIENT_SECRET || '').trim(); if (!clientId || !clientSecret) { return { content: [{ type: 'text', text: 'OAuth client credentials not set. Set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET to enable token refresh.' }] }; } try { validateSessionKey(String(input?.session_key || '')); } catch (e: any) { return { content: [{ type: 'text', text: `Error: ${e?.message || String(e)}` }] }; } try { const updated = await refreshAccessTokenForSession(String(input.session_key)); const masked = updated.access_token && updated.access_token.length > 8 ? `${updated.access_token.slice(0,4)}****${updated.access_token.slice(-4)}` : '****'; const expiresIn = Math.max(0, Math.floor(((updated.expires_at || (Date.now()+3600000)) - Date.now())/1000)); return { content: [{ type: 'text', text: JSON.stringify({ status: 'refreshed', expires_in: expiresIn, masked_token: masked }) }] }; } catch (e: any) { const msg = String(e?.message || e); if (msg.includes('invalid_grant')) { return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_INVALID_GRANT', message: 'Refresh token invalid or revoked. Re-authentication required.' } }) }] }; } return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_REFRESH_FAILED', message: msg } }) }] }; } } );
- src/schemas.ts:94-96 (schema)Zod schema for the refresh_access_token tool input: requires session_key.export const RefreshAccessTokenZ = z.object({ session_key: z.string().describe('UUID v4 session key'), });
- Helper function that performs the actual OAuth2 access token refresh using google-auth-library.async function refreshTokenWithOAuth2Client(cred: GoogleCredential): Promise<GoogleCredential> { const clientId = (process.env.GOOGLE_OAUTH_CLIENT_ID || '').trim(); const clientSecret = (process.env.GOOGLE_OAUTH_CLIENT_SECRET || '').trim(); if (!clientId || !clientSecret) throw new Error('OAuth client credentials not set'); const { OAuth2Client } = await import('google-auth-library'); const oauth2Client = new OAuth2Client({ clientId, clientSecret }); oauth2Client.setCredentials({ refresh_token: cred.refresh_token }); try { const { credentials } = await (oauth2Client as any).refreshAccessToken(); if (!credentials?.access_token) throw new Error('No access token in refresh response'); return { ...cred, access_token: credentials.access_token, expires_at: credentials.expiry_date || (Date.now() + 3600 * 1000), }; } catch (e: any) { const msg = String(e?.message || e); if (msg.includes('invalid_grant')) { throw new Error('invalid_grant: Refresh token is invalid or revoked'); } throw e; } }