Skip to main content
Glama

Spotify Streamable MCP Server

by iceener
tokens.ts4.11 kB
import { randomBytes } from 'node:crypto'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname } from 'node:path'; import { config } from '../config/env.ts'; export type SpotifyUserTokens = { access_token: string; refresh_token?: string; expires_at?: number; // epoch ms scopes?: string[]; }; export type RsTokenRecord = { rs_access_token: string; rs_refresh_token: string; created_at: number; // epoch ms spotify: SpotifyUserTokens; }; const rsAccessToRecord = new Map<string, RsTokenRecord>(); const rsRefreshToRecord = new Map<string, RsTokenRecord>(); export function generateOpaqueToken(bytes: number = 32): string { return randomBytes(bytes) .toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/g, ''); } function persistPath(): string | null { return config.RS_TOKENS_FILE || null; } type PersistShape = { records: Array<{ rs_access_token: string; rs_refresh_token: string; created_at: number; spotify: SpotifyUserTokens; }>; }; function loadPersisted(): void { const p = persistPath(); if (!p) return; try { if (!existsSync(p)) return; const raw = readFileSync(p, 'utf8'); const data = JSON.parse(raw) as PersistShape; if (!data || !Array.isArray(data.records)) return; for (const rec of data.records) { const record: RsTokenRecord = { rs_access_token: rec.rs_access_token, rs_refresh_token: rec.rs_refresh_token, created_at: rec.created_at, spotify: rec.spotify, }; rsAccessToRecord.set(record.rs_access_token, record); rsRefreshToRecord.set(record.rs_refresh_token, record); } } catch {} } function savePersisted(): void { const p = persistPath(); if (!p) return; try { const dir = dirname(p); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } const records = Array.from(rsAccessToRecord.values()).map((r) => ({ rs_access_token: r.rs_access_token, rs_refresh_token: r.rs_refresh_token, created_at: r.created_at, spotify: r.spotify, })); const obj: PersistShape = { records }; writeFileSync(p, JSON.stringify(obj, null, 2), 'utf8'); } catch {} } loadPersisted(); export function storeRsTokenMapping( rsAccessToken: string, spotifyTokens: SpotifyUserTokens, rsRefreshToken?: string, ): RsTokenRecord { if (rsRefreshToken) { const existing = rsRefreshToRecord.get(rsRefreshToken); if (existing) { rsAccessToRecord.delete(existing.rs_access_token); existing.rs_access_token = rsAccessToken; existing.spotify = { ...spotifyTokens }; rsAccessToRecord.set(rsAccessToken, existing); savePersisted(); return existing; } } const record: RsTokenRecord = { rs_access_token: rsAccessToken, rs_refresh_token: rsRefreshToken ?? generateOpaqueToken(), created_at: Date.now(), spotify: { ...spotifyTokens }, }; rsAccessToRecord.set(record.rs_access_token, record); rsRefreshToRecord.set(record.rs_refresh_token, record); savePersisted(); return record; } export function getSpotifyTokensByRsToken(rsToken?: string): SpotifyUserTokens | null { if (!rsToken) return null; const rec = rsAccessToRecord.get(rsToken); return rec ? rec.spotify : null; } export function getRecordByRsRefreshToken( rsRefreshToken?: string, ): RsTokenRecord | null { if (!rsRefreshToken) return null; return rsRefreshToRecord.get(rsRefreshToken) ?? null; } export function updateSpotifyTokensByRsRefreshToken( rsRefreshToken: string, newSpotify: SpotifyUserTokens, maybeNewRsAccessToken?: string, ): RsTokenRecord | null { const rec = rsRefreshToRecord.get(rsRefreshToken); if (!rec) return null; if (maybeNewRsAccessToken) { rsAccessToRecord.delete(rec.rs_access_token); rec.rs_access_token = maybeNewRsAccessToken; rec.created_at = Date.now(); } rec.spotify = { ...newSpotify }; rsAccessToRecord.set(rec.rs_access_token, rec); rsRefreshToRecord.set(rsRefreshToken, rec); savePersisted(); return rec; }

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/iceener/spotify-streamable-mcp-server'

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