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
JavaScript
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;
}
}
}