Skip to main content
Glama
spotify-client.ts4.61 kB
import type { AuthManager } from "./auth.js"; import type { Logger } from "./logger.js"; const SPOTIFY_API_BASE = "https://api.spotify.com/v1"; export class SpotifyClient { private auth: AuthManager; private logger: Logger; constructor(auth: AuthManager, logger: Logger) { this.auth = auth; this.logger = logger; } /** * Make a GET request to the Spotify API */ async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> { const url = this.buildUrl(endpoint, params); return this.request<T>(url, { method: "GET" }); } /** * Make a POST request to the Spotify API */ async post<T>( endpoint: string, body?: any, params?: Record<string, any> ): Promise<T> { const url = this.buildUrl(endpoint, params); return this.request<T>(url, { method: "POST", body: body ? JSON.stringify(body) : undefined, }); } /** * Make a PUT request to the Spotify API */ async put<T>( endpoint: string, body?: any, params?: Record<string, any> ): Promise<T> { const url = this.buildUrl(endpoint, params); return this.request<T>(url, { method: "PUT", body: body ? JSON.stringify(body) : undefined, }); } /** * Make a DELETE request to the Spotify API */ async delete<T>(endpoint: string, params?: Record<string, any>): Promise<T> { const url = this.buildUrl(endpoint, params); return this.request<T>(url, { method: "DELETE" }); } /** * Build full URL with query parameters */ private buildUrl( endpoint: string, params?: Record<string, any> ): string { // Remove leading slash if present const cleanEndpoint = endpoint.startsWith("/") ? endpoint.slice(1) : endpoint; const url = new URL(`${SPOTIFY_API_BASE}/${cleanEndpoint}`); if (params) { Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { url.searchParams.append(key, String(value)); } }); } return url.toString(); } /** * Make an authenticated request to Spotify API with retry logic */ private async request<T>( url: string, options: RequestInit ): Promise<T> { const accessToken = await this.auth.getAccessToken(); const headers: HeadersInit = { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", ...options.headers, }; await this.logger.debug(`${options.method} ${url}`); try { const response = await fetch(url, { ...options, headers, }); // Handle successful responses if (response.ok) { // Some endpoints return 204 No Content if (response.status === 204) { return {} as T; } return (await response.json()) as T; } // Handle specific error cases if (response.status === 401) { // Token might be invalid, try refreshing once await this.logger.info("Received 401, attempting to refresh token and retry"); const newToken = await this.auth.getAccessToken(); const retryResponse = await fetch(url, { ...options, headers: { ...headers, Authorization: `Bearer ${newToken}`, }, }); if (retryResponse.ok) { if (retryResponse.status === 204) { return {} as T; } return (await retryResponse.json()) as T; } const retryError = await retryResponse.text(); throw new Error( `Spotify API error (retry): ${retryResponse.status} ${retryError}` ); } // Handle rate limiting if (response.status === 429) { const retryAfter = response.headers.get("Retry-After"); throw new Error( `Rate limited. Retry after ${retryAfter || "unknown"} seconds` ); } // Handle other errors const errorText = await response.text(); let errorMessage = `Spotify API error: ${response.status}`; try { const errorJson = JSON.parse(errorText); if (errorJson.error?.message) { errorMessage += ` - ${errorJson.error.message}`; } } catch { if (errorText) { errorMessage += ` - ${errorText}`; } } throw new Error(errorMessage); } catch (error) { if (error instanceof Error) { await this.logger.error(`Request failed: ${error.message}`); throw error; } throw new Error(`Unknown error during request: ${error}`); } } }

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/nicklaustrup/mcp-spotify'

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