Skip to main content
Glama

Spotify MCP Server

by hrishi0102
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { z } from "zod"; import fetch from "node-fetch"; import express from "express"; import cors from "cors"; const app = express(); app.use(cors({ origin: "*", methods: ["GET", "POST"], allowedHeaders: ["Content-Type"], })); const server = new McpServer({ name: "SpotifyServer", version: "1.0.0", capabilities: { tools: {}, }, }); const transports = {}; let spotifyAuthInfo = { accessToken: "", refreshToken: "", clientId: "", clientSecret: "", }; // Refresh token when needed async function getValidAccessToken() { if (!spotifyAuthInfo.accessToken || !spotifyAuthInfo.refreshToken) { throw new Error("No access token available. Please set credentials first using the set-spotify-credentials tool."); } try { // Try using current token const response = await fetch("https://api.spotify.com/v1/me", { headers: { Authorization: `Bearer ${spotifyAuthInfo.accessToken}`, }, }); // If token works, return it if (response.ok) { return spotifyAuthInfo.accessToken; } console.error("Access token expired, refreshing..."); // If token doesn't work, refresh it const refreshResponse = await fetch("https://accounts.spotify.com/api/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Authorization: "Basic " + Buffer.from(spotifyAuthInfo.clientId + ":" + spotifyAuthInfo.clientSecret).toString("base64"), }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: spotifyAuthInfo.refreshToken, }), }); const data = (await refreshResponse.json()); if (data.access_token) { console.error("Successfully refreshed access token"); spotifyAuthInfo.accessToken = data.access_token; return spotifyAuthInfo.accessToken; } throw new Error("Failed to refresh access token"); } catch (error) { throw new Error("Error with access token: " + (error instanceof Error ? error.message : String(error))); } } // Set credentials tool server.tool("set-spotify-credentials", { clientId: z.string().describe("The Spotify Client ID"), clientSecret: z.string().describe("The Spotify Client Secret"), accessToken: z.string().describe("The Spotify Access Token"), refreshToken: z.string().describe("The Spotify Refresh Token"), }, async ({ clientId, clientSecret, accessToken, refreshToken }) => { spotifyAuthInfo.clientId = clientId; spotifyAuthInfo.clientSecret = clientSecret; spotifyAuthInfo.accessToken = accessToken; spotifyAuthInfo.refreshToken = refreshToken; return { content: [ { type: "text", text: "Spotify credentials set successfully. You can now use other Spotify tools.", }, ], }; }); // Check credentials tool server.tool("check-credentials-status", {}, async () => { if (!spotifyAuthInfo.accessToken || !spotifyAuthInfo.refreshToken || !spotifyAuthInfo.clientId || !spotifyAuthInfo.clientSecret) { return { content: [ { type: "text", text: "Spotify credentials are not set. Please use the set-spotify-credentials tool.", }, ], }; } try { const accessToken = await getValidAccessToken(); const response = await fetch("https://api.spotify.com/v1/me", { headers: { Authorization: `Bearer ${accessToken}`, }, }); if (response.ok) { const userData = (await response.json()); return { content: [ { type: "text", text: `Spotify credentials are valid.\nLogged in as: ${userData.display_name} (${userData.email || "email not available"})`, }, ], }; } else { return { content: [ { type: "text", text: `Spotify credentials may be invalid. Status code: ${response.status}`, }, ], isError: true, }; } } catch (error) { return { content: [ { type: "text", text: `Error checking credentials: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); // Search tracks server.tool("search-tracks", { query: z.string().describe("Search query for tracks"), limit: z .number() .min(1) .max(50) .default(10) .describe("Number of results to return"), }, async ({ query, limit }) => { try { const accessToken = await getValidAccessToken(); const response = await fetch(`https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=track&limit=${limit}`, { headers: { Authorization: `Bearer ${accessToken}`, }, }); const data = (await response.json()); if (!response.ok) { return { content: [ { type: "text", text: `Error searching tracks: ${JSON.stringify(data)}`, }, ], isError: true, }; } const tracks = data.tracks.items.map((track) => ({ id: track.id, name: track.name, artist: track.artists.map((artist) => artist.name).join(", "), album: track.album.name, uri: track.uri, })); return { content: [ { type: "text", text: JSON.stringify(tracks, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `Failed to search tracks: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); // Get current user server.tool("get-current-user", {}, async () => { try { const accessToken = await getValidAccessToken(); const response = await fetch("https://api.spotify.com/v1/me", { headers: { Authorization: `Bearer ${accessToken}`, }, }); const data = (await response.json()); if (!response.ok) { return { content: [ { type: "text", text: `Error getting user profile: ${JSON.stringify(data)}`, }, ], isError: true, }; } return { content: [ { type: "text", text: JSON.stringify({ id: data.id, name: data.display_name, email: data.email, country: data.country, }, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `Failed to get user profile: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); // Create playlist server.tool("create-playlist", { name: z.string().describe("Name of the playlist"), description: z.string().optional().describe("Description of the playlist"), }, async ({ name, description = "" }) => { try { const accessToken = await getValidAccessToken(); // Get user ID const userResponse = await fetch("https://api.spotify.com/v1/me", { headers: { Authorization: `Bearer ${accessToken}`, }, }); const userData = (await userResponse.json()); const userId = userData.id; // Create playlist const response = await fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ name, description, public: false, }), }); const data = (await response.json()); if (!response.ok) { return { content: [ { type: "text", text: `Error creating playlist: ${JSON.stringify(data)}`, }, ], isError: true, }; } return { content: [ { type: "text", text: `Playlist created successfully!\nName: ${data.name}\nID: ${data.id}\nURL: ${data.external_urls.spotify}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Failed to create playlist: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); // Add tracks to playlist server.tool("add-tracks-to-playlist", { playlistId: z.string().describe("The Spotify playlist ID"), trackUris: z .array(z.string()) .describe("Array of Spotify track URIs to add"), }, async ({ playlistId, trackUris }) => { try { const accessToken = await getValidAccessToken(); const response = await fetch(`https://api.spotify.com/v1/playlists/${playlistId}/tracks`, { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ uris: trackUris, }), }); const data = (await response.json()); if (!response.ok) { return { content: [ { type: "text", text: `Error adding tracks: ${JSON.stringify(data)}`, }, ], isError: true, }; } return { content: [ { type: "text", text: `Successfully added ${trackUris.length} track(s) to playlist!`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Failed to add tracks: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); // Get recommendations server.tool("get-recommendations", { seedTracks: z .array(z.string()) .max(5) .describe("Spotify track IDs to use as seeds (max 5)"), limit: z .number() .min(1) .max(100) .default(20) .describe("Number of recommendations to return"), }, async ({ seedTracks, limit }) => { try { const accessToken = await getValidAccessToken(); if (seedTracks.length === 0) { return { content: [ { type: "text", text: "Error: At least one seed track is required", }, ], isError: true, }; } const response = await fetch(`https://api.spotify.com/v1/recommendations?seed_tracks=${seedTracks.join(",")}&limit=${limit}`, { headers: { Authorization: `Bearer ${accessToken}`, }, }); const data = (await response.json()); if (!response.ok) { return { content: [ { type: "text", text: `Error getting recommendations: ${JSON.stringify(data)}`, }, ], isError: true, }; } const tracks = data.tracks.map((track) => ({ id: track.id, name: track.name, artist: track.artists.map((artist) => artist.name).join(", "), album: track.album.name, uri: track.uri, })); return { content: [ { type: "text", text: JSON.stringify(tracks, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: `Failed to get recommendations: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); let transport = null; app.get("/sse", (req, res) => { transport = new SSEServerTransport("/messages", res); server.connect(transport); }); app.post("/messages", (req, res) => { if (transport) { transport.handlePostMessage(req, res); } else { res.status(503).send("No active transport"); } }); app.listen(3001, () => { console.log("Listening on port 3001"); });

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

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