UNPKG

mcp-turso-cloud

Version:

MCP server for integrating Turso with LLMs

103 lines (102 loc) 3.72 kB
/** * Token management for the Turso MCP server */ import { TursoApiError } from '../common/errors.js'; import { get_config } from '../config.js'; // In-memory token cache const token_cache = {}; /** * Parse a JWT token to extract its expiration date */ function get_token_expiration(jwt) { try { // JWT tokens consist of three parts separated by dots const parts = jwt.split('.'); if (parts.length !== 3) { throw new Error('Invalid JWT format'); } // The second part contains the payload, which is base64 encoded const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString('utf8')); // The exp claim contains the expiration timestamp in seconds if (typeof payload.exp !== 'number') { throw new Error('JWT missing expiration'); } // Convert to milliseconds and create a Date object return new Date(payload.exp * 1000); } catch (error) { // If parsing fails, set a default expiration of 1 hour from now console.error('Error parsing JWT expiration:', error); const expiration = new Date(); expiration.setHours(expiration.getHours() + 1); return expiration; } } /** * Generate a new token for a database using the organization token */ export async function generate_database_token(database_name, permission = 'full-access') { const config = get_config(); const url = `https://api.turso.tech/v1/organizations/${config.TURSO_ORGANIZATION}/databases/${database_name}/auth/tokens`; try { const response = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${config.TURSO_API_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ expiration: config.TOKEN_EXPIRATION, permission, }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); const errorMessage = errorData.error || response.statusText; throw new TursoApiError(`Failed to generate token for database ${database_name}: ${errorMessage}`, response.status); } const data = await response.json(); return data.jwt; } catch (error) { if (error instanceof TursoApiError) { throw error; } throw new TursoApiError(`Failed to generate token for database ${database_name}: ${error.message}`, 500); } } /** * Get a token for a database, generating a new one if necessary */ export async function get_database_token(database_name, permission = 'full-access') { // Check if we have a valid token in the cache const cached_token = token_cache[database_name]; if (cached_token && cached_token.permission === permission) { // Check if the token is still valid (not expired) if (cached_token.expiresAt > new Date()) { return cached_token.jwt; } } // Generate a new token const jwt = await generate_database_token(database_name, permission); // Cache the token token_cache[database_name] = { jwt, expiresAt: get_token_expiration(jwt), permission, }; return jwt; } /** * Remove expired tokens from the cache */ export function cleanup_expired_tokens() { const now = new Date(); for (const [database_name, token] of Object.entries(token_cache)) { if (token.expiresAt <= now) { delete token_cache[database_name]; } } } // Set up a periodic cleanup of expired tokens (every hour) setInterval(cleanup_expired_tokens, 60 * 60 * 1000);