Skip to main content
Glama

Basecamp MCP Server

by jhliberty
token-storage.ts3.23 kB
import { promises as fs } from 'fs'; import { join } from 'path'; import type { OAuthTokens } from '../types/basecamp.js'; const TOKEN_FILE = join(process.cwd(), 'oauth_tokens.json'); interface TokenStorage { basecamp?: OAuthTokens; } export class TokenStorageManager { private static instance: TokenStorageManager; private tokenCache: TokenStorage | null = null; static getInstance(): TokenStorageManager { if (!TokenStorageManager.instance) { TokenStorageManager.instance = new TokenStorageManager(); } return TokenStorageManager.instance; } private async readTokens(): Promise<TokenStorage> { if (this.tokenCache) { return this.tokenCache; } try { const data = await fs.readFile(TOKEN_FILE, 'utf-8'); this.tokenCache = JSON.parse(data); return this.tokenCache || {}; } catch (error: any) { if (error.code === 'ENOENT') { return {}; } console.warn(`Error reading tokens: ${error.message}`); return {}; } } private async writeTokens(tokens: TokenStorage): Promise<void> { try { await fs.writeFile(TOKEN_FILE, JSON.stringify(tokens, null, 2)); // Set secure permissions (owner read/write only) await fs.chmod(TOKEN_FILE, 0o600); this.tokenCache = tokens; } catch (error: any) { console.error(`Error writing tokens: ${error.message}`); throw error; } } async storeToken( accessToken: string, refreshToken?: string, expiresIn?: number, accountId?: string ): Promise<boolean> { if (!accessToken) { return false; } const tokens = await this.readTokens(); const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000).toISOString() : undefined; tokens.basecamp = { accessToken, refreshToken, accountId: accountId || tokens.basecamp?.accountId || '', expiresAt, updatedAt: new Date().toISOString(), }; await this.writeTokens(tokens); return true; } async getToken(): Promise<OAuthTokens | null> { const tokens = await this.readTokens(); return tokens.basecamp || null; } async isTokenExpired(): Promise<boolean> { const tokenData = await this.getToken(); if (!tokenData || !tokenData.expiresAt) { return true; } try { const expiresAt = new Date(tokenData.expiresAt); // Add 5-minute buffer to account for clock differences const bufferTime = new Date(Date.now() + 5 * 60 * 1000); return expiresAt <= bufferTime; } catch { return true; } } async clearTokens(): Promise<boolean> { try { await fs.unlink(TOKEN_FILE); this.tokenCache = null; return true; } catch (error: any) { if (error.code === 'ENOENT') { return true; } console.error(`Error clearing tokens: ${error.message}`); return false; } } async hasValidToken(): Promise<boolean> { const token = await this.getToken(); if (!token?.accessToken) { return false; } const expired = await this.isTokenExpired(); return !expired; } } // Singleton instance export const tokenStorage = TokenStorageManager.getInstance();

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/jhliberty/basecamp-mcp-server'

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