Skip to main content
Glama

MCP Spotify Server

by fborello
spotify-tools.ts17.2 kB
import { SpotifyAuth } from '../auth/spotify-auth.js'; import { SpotifyTrack, SpotifyPlaylist, SpotifyDevice } from '../types/index.js'; import open from 'open'; export class SpotifyTools { constructor(private spotifyAuth: SpotifyAuth) {} /** * Inicia o processo de autenticação */ async authenticate() { if (this.spotifyAuth.isAuthenticated()) { return { content: [ { type: 'text', text: '✅ Já autenticado com o Spotify!', }, ], }; } const authUrl = this.spotifyAuth.getAuthorizationUrl(); // Abrir navegador automaticamente await open(authUrl); return { content: [ { type: 'text', text: `🔐 Abra este link no navegador para autenticar com o Spotify:\n\n${authUrl}\n\nApós autorizar, você receberá um código. Use o comando spotify_set_tokens com o código recebido.`, }, ], }; } /** * Define tokens após autenticação */ async setTokens(code: string) { try { const tokens = await this.spotifyAuth.exchangeCodeForTokens(code); return { content: [ { type: 'text', text: '✅ Autenticação com Spotify concluída com sucesso!', }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro na autenticação: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Busca por conteúdo no Spotify */ async search(query: string, type: 'track' | 'artist' | 'album' | 'playlist', limit: number = 20) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const response = await spotifyApi.search(query, [type], { limit }); const results = response.body; let content = `🔍 Resultados para "${query}" (${type}):\n\n`; switch (type) { case 'track': const tracks = results.tracks?.items || []; tracks.forEach((track: any, index: number) => { const artists = track.artists.map((a: any) => a.name).join(', '); content += `${index + 1}. **${track.name}** - ${artists}\n`; content += ` ID: ${track.id}\n`; content += ` Duração: ${Math.floor(track.duration_ms / 60000)}:${String(Math.floor((track.duration_ms % 60000) / 1000)).padStart(2, '0')}\n`; content += ` Link: ${track.external_urls.spotify}\n\n`; }); break; case 'artist': const artists = results.artists?.items || []; artists.forEach((artist: any, index: number) => { content += `${index + 1}. **${artist.name}**\n`; content += ` ID: ${artist.id}\n`; content += ` Seguidores: ${artist.followers.total.toLocaleString()}\n`; content += ` Gêneros: ${artist.genres.join(', ')}\n`; content += ` Link: ${artist.external_urls.spotify}\n\n`; }); break; case 'album': const albums = results.albums?.items || []; albums.forEach((album: any, index: number) => { const artists = album.artists.map((a: any) => a.name).join(', '); content += `${index + 1}. **${album.name}** - ${artists}\n`; content += ` ID: ${album.id}\n`; content += ` Lançamento: ${album.release_date}\n`; content += ` Total de músicas: ${album.total_tracks}\n`; content += ` Link: ${album.external_urls.spotify}\n\n`; }); break; case 'playlist': const playlists = results.playlists?.items || []; playlists.forEach((playlist: any, index: number) => { content += `${index + 1}. **${playlist.name}**\n`; content += ` ID: ${playlist.id}\n`; content += ` Descrição: ${playlist.description}\n`; content += ` Total de músicas: ${playlist.tracks.total}\n`; content += ` Link: ${playlist.external_urls.spotify}\n\n`; }); break; } return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro na busca: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Toca uma música específica */ async play(trackId: string, deviceId?: string) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const options: any = { uris: [`spotify:track:${trackId}`] }; if (deviceId) { options.device_id = deviceId; } await spotifyApi.play(options); return { content: [ { type: 'text', text: `▶️ Tocando música com ID: ${trackId}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao tocar música: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Pausa a reprodução */ async pause(deviceId?: string) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const options = deviceId ? { device_id: deviceId } : {}; await spotifyApi.pause(options); return { content: [ { type: 'text', text: '⏸️ Reprodução pausada', }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao pausar: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Retoma a reprodução */ async resume(deviceId?: string) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const options = deviceId ? { device_id: deviceId } : {}; await spotifyApi.play(options); return { content: [ { type: 'text', text: '▶️ Reprodução retomada', }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao retomar: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Pula para a próxima música */ async next(deviceId?: string) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const options = deviceId ? { device_id: deviceId } : {}; await spotifyApi.skipToNext(options); return { content: [ { type: 'text', text: '⏭️ Pulou para a próxima música', }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao pular música: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Volta para a música anterior */ async previous(deviceId?: string) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const options = deviceId ? { device_id: deviceId } : {}; await spotifyApi.skipToPrevious(options); return { content: [ { type: 'text', text: '⏮️ Voltou para a música anterior', }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao voltar música: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Obtém informações sobre a música atual */ async getCurrentPlaying() { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const response = await spotifyApi.getMyCurrentPlayingTrack(); const track = response.body.item; if (!track) { return { content: [ { type: 'text', text: '❌ Nenhuma música está tocando no momento', }, ], }; } const artists = track.artists.map((a: any) => a.name).join(', '); const duration = Math.floor(track.duration_ms / 60000); const durationSeconds = Math.floor((track.duration_ms % 60000) / 1000); const progress = Math.floor((response.body.progress_ms || 0) / 60000); const progressSeconds = Math.floor(((response.body.progress_ms || 0) % 60000) / 1000); let content = `🎵 **Música atual:**\n\n`; content += `**${track.name}** - ${artists}\n`; content += `Álbum: ${track.album.name}\n`; content += `Duração: ${duration}:${String(durationSeconds).padStart(2, '0')}\n`; content += `Progresso: ${progress}:${String(progressSeconds).padStart(2, '0')}\n`; content += `Link: ${track.external_urls.spotify}\n`; return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao obter música atual: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Lista dispositivos disponíveis */ async getDevices() { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const response = await spotifyApi.getMyDevices(); const devices = response.body.devices; if (devices.length === 0) { return { content: [ { type: 'text', text: '❌ Nenhum dispositivo encontrado. Certifique-se de que o Spotify está aberto em algum dispositivo.', }, ], }; } let content = `📱 **Dispositivos disponíveis:**\n\n`; devices.forEach((device: any, index: number) => { const status = device.is_active ? '🟢 Ativo' : '⚪ Inativo'; content += `${index + 1}. **${device.name}** (${device.type})\n`; content += ` ID: ${device.id}\n`; content += ` Status: ${status}\n`; content += ` Volume: ${device.volume_percent}%\n\n`; }); return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao obter dispositivos: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Lista playlists do usuário */ async getPlaylists(limit: number = 20) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const response = await spotifyApi.getUserPlaylists({ limit }); const playlists = response.body.items; if (playlists.length === 0) { return { content: [ { type: 'text', text: '❌ Nenhuma playlist encontrada', }, ], }; } let content = `📋 **Suas playlists:**\n\n`; playlists.forEach((playlist: any, index: number) => { content += `${index + 1}. **${playlist.name}**\n`; content += ` ID: ${playlist.id}\n`; content += ` Descrição: ${playlist.description || 'Sem descrição'}\n`; content += ` Total de músicas: ${playlist.tracks.total}\n`; content += ` Pública: ${playlist.public ? 'Sim' : 'Não'}\n`; content += ` Link: ${playlist.external_urls.spotify}\n\n`; }); return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao obter playlists: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Toca uma playlist específica */ async playPlaylist(playlistId: string, deviceId?: string) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const options: any = { context_uri: `spotify:playlist:${playlistId}` }; if (deviceId) { options.device_id = deviceId; } await spotifyApi.play(options); return { content: [ { type: 'text', text: `▶️ Tocando playlist com ID: ${playlistId}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao tocar playlist: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Cria uma nova playlist usando fetch direto */ async createPlaylist(name: string, description?: string, isPublic: boolean = false) { try { await this.spotifyAuth.ensureValidToken(); const spotifyApi = this.spotifyAuth.getSpotifyApi(); const accessToken = await this.spotifyAuth.ensureValidToken(); // Primeiro, obter o ID do usuário atual const userResponse = await fetch('https://api.spotify.com/v1/me', { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' } }); if (!userResponse.ok) { throw new Error(`Erro ao obter dados do usuário: ${userResponse.status}`); } const userData = await userResponse.json(); const userId = userData.id; // Criar a playlist const playlistResponse = await fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name, description: description || '', public: isPublic }) }); if (!playlistResponse.ok) { const errorData = await playlistResponse.json(); throw new Error(`Erro ao criar playlist: ${playlistResponse.status} - ${errorData.error?.message || 'Erro desconhecido'}`); } const playlist = await playlistResponse.json(); return { content: [ { type: 'text', text: `✅ Playlist "${name}" criada com sucesso!\n\n**Detalhes:**\n- Nome: ${playlist.name}\n- ID: ${playlist.id}\n- Descrição: ${playlist.description || 'Sem descrição'}\n- Pública: ${playlist.public ? 'Sim' : 'Não'}\n- Link: ${playlist.external_urls.spotify}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao criar playlist: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } /** * Adiciona músicas a uma playlist existente */ async addTracksToPlaylist(playlistId: string, trackIds: string[]) { try { await this.spotifyAuth.ensureValidToken(); const accessToken = await this.spotifyAuth.ensureValidToken(); // Adicionar músicas à playlist 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: trackIds.map(id => `spotify:track:${id}`) }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(`Erro ao adicionar músicas: ${response.status} - ${errorData.error?.message || 'Erro desconhecido'}`); } const result = await response.json(); return { content: [ { type: 'text', text: `✅ ${trackIds.length} música(s) adicionada(s) à playlist com sucesso!\n\n**Detalhes:**\n- Playlist ID: ${playlistId}\n- Músicas adicionadas: ${trackIds.length}\n- Snapshot ID: ${result.snapshot_id}`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `❌ Erro ao adicionar músicas à playlist: ${error instanceof Error ? error.message : String(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/fborello/MCPSpotify'

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