Skip to main content
Glama
client.ts3.93 kB
const API_URL = process.env.AUTHN8_API_URL || "https://api.authn8.com"; const API_KEY = process.env.AUTHN8_API_KEY; const USER_AGENT = "authn8-mcp/1.0.0"; export interface TokenInfo { businessName: string; tokenName: string; scopedGroups: Array<{ id: string; name: string }>; accountCount: number; expiresAt: string; } export interface Account { id: string; name: string; issuerDomain: string | null; } interface AccountsResponse { accounts: Account[]; } export interface OtpResponse { name: string; code: string; length: number; } class Authn8Error extends Error { constructor( message: string, public statusCode?: number ) { super(message); this.name = "Authn8Error"; } } let accountsCache: { accounts: Account[]; timestamp: number } | null = null; const CACHE_TTL_MS = 60 * 1000; // 60 seconds async function apiRequest<T>( endpoint: string, method: string = "GET" ): Promise<T> { if (!API_KEY) { throw new Authn8Error( "AUTHN8_API_KEY environment variable is not set. Please set it to your PAT token from the Authn8 dashboard." ); } const url = `${API_URL}${endpoint}`; const response = await fetch(url, { method, headers: { Authorization: `Bearer ${API_KEY}`, "User-Agent": USER_AGENT, "Content-Type": "application/json", }, }); if (!response.ok) { if (response.status === 401) { throw new Authn8Error( "Token is invalid or expired. Please check your token in the Authn8 dashboard.", 401 ); } if (response.status === 403) { throw new Authn8Error( "Token does not have permission to access this resource.", 403 ); } if (response.status === 404) { throw new Authn8Error("Resource not found.", 404); } if (response.status === 429) { const retryAfter = response.headers.get("Retry-After"); throw new Authn8Error( `Rate limited. ${retryAfter ? `Retry after ${retryAfter} seconds.` : "Please try again later."}`, 429 ); } throw new Authn8Error( `API request failed with status ${response.status}`, response.status ); } return response.json() as Promise<T>; } export async function getTokenInfo(): Promise<TokenInfo> { return apiRequest<TokenInfo>("/api/pat/me"); } export async function listAccounts(): Promise<Account[]> { // Check cache if (accountsCache && Date.now() - accountsCache.timestamp < CACHE_TTL_MS) { return accountsCache.accounts; } const response = await apiRequest<AccountsResponse>("/api/pat/accounts"); // Update cache accountsCache = { accounts: response.accounts, timestamp: Date.now(), }; return response.accounts; } export async function getOtp(accountId: string): Promise<OtpResponse> { return apiRequest<OtpResponse>(`/api/pat/otp/${accountId}`); } export async function findAccountByName( name: string ): Promise<{ account: Account } | { matches: Account[] }> { const accounts = await listAccounts(); const lowerName = name.toLowerCase(); const matches = accounts.filter( (acc) => acc.name?.toLowerCase().includes(lowerName) || acc.issuerDomain?.toLowerCase().includes(lowerName) ); if (matches.length === 0) { throw new Authn8Error( `No account found matching "${name}". Available accounts:\n${accounts.map((a) => ` - ${a.name} (${a.issuerDomain || "unknown"})`).join("\n")}` ); } if (matches.length === 1) { return { account: matches[0] }; } return { matches }; } export async function validateToken(): Promise<TokenInfo> { try { return await getTokenInfo(); } catch (error) { if (error instanceof Authn8Error) { throw error; } throw new Authn8Error( `Failed to connect to Authn8 API. Please check AUTHN8_API_URL (${API_URL}). Error: ${error instanceof Error ? error.message : String(error)}` ); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/authn8/Authn8'

If you have feedback or need assistance with the MCP directory API, please join our Discord server