Skip to main content
Glama

Multi-MCPs

by TaylorChen
spotify.ts5.48 kB
import { BaseApiClient } from "../../core/base-api.js"; import fetch from "node-fetch"; import { loadConfig } from "../../core/config.js"; import { ToolRegistration } from "../../core/types.js"; class SpotifyClient extends BaseApiClient { private readonly clientId: string; private readonly clientSecret: string; private accessToken: string | null = null; private tokenExpiresAt = 0; constructor(clientId: string, clientSecret: string) { super("https://api.spotify.com/v1"); this.clientId = clientId; this.clientSecret = clientSecret; } private async ensureToken(): Promise<string> { const now = Date.now(); if (this.accessToken && now < this.tokenExpiresAt - 30000) { return this.accessToken; } const auth = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64"); const tokenResp = await fetch("https://accounts.spotify.com/api/token", { method: "POST", headers: { Authorization: `Basic ${auth}`, "content-type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "client_credentials" }) as any, } as any); if (!tokenResp.ok) { throw new Error(`Spotify token error: ${tokenResp.status}`); } const data: any = await tokenResp.json(); this.accessToken = data.access_token as string; this.tokenExpiresAt = Date.now() + ((data.expires_in as number) || 3600) * 1000; return this.accessToken!; } private async authHeaders() { const token = await this.ensureToken(); return { Authorization: `Bearer ${token}` }; } async searchTracks(query: string, limit?: number) { return this.request("/search", { headers: await this.authHeaders(), query: { q: query, type: "track", limit: limit ?? 10 }, }); } async getTrack(trackId: string) { return this.request(`/tracks/${trackId}`, { headers: await this.authHeaders() }); } async createPlaylist(userId: string, name: string) { return this.request(`/users/${userId}/playlists`, { method: "POST", headers: await this.authHeaders(), body: { name, public: false }, }); } async addTracksToPlaylist(playlistId: string, tracks: string[]) { return this.request(`/playlists/${playlistId}/tracks`, { method: "POST", headers: await this.authHeaders(), body: { uris: tracks }, }); } } export function registerSpotify(): ToolRegistration { const cfg = loadConfig(); const client = new SpotifyClient(cfg.spotifyClientId || "", cfg.spotifyClientSecret || ""); return { tools: [ { name: "search_tracks", description: "Search tracks on Spotify", inputSchema: { type: "object", properties: { query: { type: "string" }, limit: { type: "number" } }, required: ["query"], }, }, { name: "get_track_info", description: "Get Spotify track details", inputSchema: { type: "object", properties: { track_id: { type: "string" } }, required: ["track_id"], }, }, { name: "create_playlist", description: "Create a new playlist for a user", inputSchema: { type: "object", properties: { user_id: { type: "string" }, name: { type: "string" } }, required: ["user_id", "name"], }, }, { name: "add_tracks_to_playlist", description: "Add tracks to a playlist by URIs", inputSchema: { type: "object", properties: { playlist_id: { type: "string" }, tracks: { type: "array", items: { type: "string" } } }, required: ["playlist_id", "tracks"], }, }, ], handlers: { async search_tracks(args: Record<string, unknown>) { if (!cfg.spotifyClientId || !cfg.spotifyClientSecret) throw new Error("SPOTIFY_CLIENT_ID/SECRET are not configured"); const query = String(args.query || ""); if (!query) throw new Error("query is required"); const limit = args.limit ? Number(args.limit) : undefined; return client.searchTracks(query, limit); }, async get_track_info(args: Record<string, unknown>) { if (!cfg.spotifyClientId || !cfg.spotifyClientSecret) throw new Error("SPOTIFY_CLIENT_ID/SECRET are not configured"); const trackId = String(args.track_id || ""); if (!trackId) throw new Error("track_id is required"); return client.getTrack(trackId); }, async create_playlist(args: Record<string, unknown>) { if (!cfg.spotifyClientId || !cfg.spotifyClientSecret) throw new Error("SPOTIFY_CLIENT_ID/SECRET are not configured"); const userId = String(args.user_id || ""); const name = String(args.name || ""); if (!userId || !name) throw new Error("user_id and name are required"); return client.createPlaylist(userId, name); }, async add_tracks_to_playlist(args: Record<string, unknown>) { if (!cfg.spotifyClientId || !cfg.spotifyClientSecret) throw new Error("SPOTIFY_CLIENT_ID/SECRET are not configured"); const playlistId = String(args.playlist_id || ""); const tracks = Array.isArray(args.tracks) ? (args.tracks as string[]) : []; if (!playlistId || tracks.length === 0) throw new Error("playlist_id and tracks are required"); return client.addTracksToPlaylist(playlistId, tracks); }, }, }; }

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/TaylorChen/muti-mcps'

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