UNPKG

mcp-audiense-demand-test

Version:

MCP server for Claude that provides demand intelligence and search volume analytics from Audiense Demand reports.

148 lines (147 loc) 5.37 kB
const AUTH0_DOMAIN = 'auth.audiense.com'; const AUTH0_CLIENT_ID = 'CCpWa7nn17ETsbxHI6kncIIiUiy7S5f3'; const AUTH0_AUDIENCE = 'ebwYT5kCCQ2ty2OVIRErgv01xhW3tc9c'; export class AuthClient { static instance = null; tokenCache = null; deviceCodeResponse; constructor() { this.tokenCache = null; this.deviceCodeResponse = null; } static getInstance() { if (!AuthClient.instance) { AuthClient.instance = new AuthClient(); } return AuthClient.instance; } async getAccessToken() { // If we have a device code response but no valid token, try to get the token if (this.deviceCodeResponse && !this.tokenCache) { await this.saveAccessTokenWithDeviceCode(); return this.getAccessTokenFromCache(); } // If token has expired, try to refresh it if (this.tokenHasExpired()) { await this.refreshTokenCache(); } return this.getAccessTokenFromCache(); } resetTokenCache() { this.tokenCache = null; } async saveAccessTokenWithDeviceCode() { try { const response = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ grant_type: 'urn:ietf:params:oauth:grant-type:device_code', client_id: AUTH0_CLIENT_ID, device_code: this.getDeviceCodeFromCache(), }), }); if (!response.ok) { const error = await response.text(); throw new Error(`Save access token with device code failed: ${error}`); } const data = (await response.json()); this.tokenCache = { access_token: data.access_token, refresh_token: data.refresh_token, expires_at: Date.now() + data.expires_in * 1000 - 60000, // Subtract 1 minute for safety }; } catch (error) { throw error; } } async refreshTokenCache() { try { const response = await fetch(`https://${AUTH0_DOMAIN}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ grant_type: 'refresh_token', client_id: AUTH0_CLIENT_ID, refresh_token: this.getRefreshTokenFromCache(), }), }); if (!response.ok) { const error = await response.text(); throw new Error(`Auth0 token refresh failed: ${error}`); } const data = (await response.json()); this.tokenCache = { access_token: data.access_token, refresh_token: data.refresh_token, expires_at: Date.now() + data.expires_in * 1000 - 60000, // Subtract 1 minute for safety }; } catch (error) { console.error(`[AuthClient] Error refreshing token:`, error); this.resetTokenCache(); } } getAccessTokenFromCache() { if (!this.tokenCache) { throw new Error('No token cache available'); } return this.tokenCache.access_token; } getDeviceCodeFromCache() { if (!this.deviceCodeResponse) { throw new Error('No device code response available'); } return this.deviceCodeResponse.device_code; } getRefreshTokenFromCache() { if (!this.tokenCache) { throw new Error('No token cache available'); } return this.tokenCache.refresh_token; } tokenHasExpired() { if (!this.tokenCache) { return true; } return this.tokenCache.expires_at < Date.now(); } /** * Initiates the Device Authorization Flow by requesting a device code * @returns Promise containing the device code response with user_code, device_code, and verification_uri */ async requestDeviceCode() { if (!AUTH0_DOMAIN || !AUTH0_CLIENT_ID || !AUTH0_AUDIENCE) { throw new Error('Missing required environment variables for device authorization'); } try { const response = await fetch(`https://${AUTH0_DOMAIN}/oauth/device/code`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: AUTH0_CLIENT_ID, scope: 'offline_access', audience: AUTH0_AUDIENCE, }), }); if (!response.ok) { const error = await response.text(); throw new Error(`Device code request failed: ${error}`); } const data = (await response.json()); this.deviceCodeResponse = data; return data; } catch (error) { console.error(`[AuthClient] Error requesting device code:`, error); throw error; } } }