MCP Spotify Server

import { SpotifyApi } from '../utils/api.js'; import { TrackArgs, RecommendationsArgs, } from '../types/tracks.js'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; export class TracksHandler { constructor(private api: SpotifyApi) {} private extractTrackId(id: string): string { return id.startsWith('spotify:track:') ? id.split(':')[2] : id; } async getTrack(args: TrackArgs) { const trackId = this.extractTrackId(args.id); return this.api.makeRequest(`/tracks/${trackId}`); } async getRecommendations(args: RecommendationsArgs) { const { seed_tracks = [], seed_artists = [], seed_genres = [], limit = 20 } = args; if (seed_tracks.length + seed_artists.length + seed_genres.length === 0) { throw new McpError( ErrorCode.InvalidParams, 'At least one seed (tracks, artists, or genres) must be provided' ); } if (limit < 1 || limit > 100) { throw new McpError( ErrorCode.InvalidParams, 'Limit must be between 1 and 100' ); } const trackIds = seed_tracks.map(this.extractTrackId); const artistIds = seed_artists.map(id => id.startsWith('spotify:artist:') ? id.split(':')[2] : id ); const params = { seed_tracks: trackIds.length ? trackIds.join(',') : undefined, seed_artists: artistIds.length ? artistIds.join(',') : undefined, seed_genres: seed_genres.length ? seed_genres.join(',') : undefined, limit }; return this.api.makeRequest(`/recommendations${this.api.buildQueryString(params)}`); } async getAvailableGenres() { return this.api.makeRequest('/recommendations/available-genre-seeds'); } }