mcp-turso-cloud
Version:
MCP server for integrating Turso with LLMs
103 lines (102 loc) • 3.72 kB
JavaScript
/**
* 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);