oaustegard / controlling-spotify

Control Spotify playback and manage playlists via MCP server. Use when user requests playing music, controlling Spotify, creating playlists, searching songs, or managing their Spotify library.

1 views
0 installs

Skill Content

---
name: controlling-spotify
description: Control Spotify playback and manage playlists via MCP server. Use when user requests playing music, controlling Spotify, creating playlists, searching songs, or managing their Spotify library.
credentials:
  - SPOTIFY_CLIENT_ID
  - SPOTIFY_CLIENT_SECRET
  - SPOTIFY_REFRESH_TOKEN
domains:
  - api.spotify.com
  - accounts.spotify.com
  - raw.githubusercontent.com
  - github.com
---

# Controlling Spotify

Control Spotify playback, search for music, and manage playlists using the Spotify MCP Server with full user account access.

## When to Use

Invoke this skill when users request:
- Playing, pausing, or skipping music on Spotify
- Searching for songs, albums, artists, or playlists
- Creating or modifying playlists
- Viewing currently playing track or playback status
- Managing their Spotify library (saved tracks, albums)
- Queuing songs or albums

## Prerequisites

**CRITICAL**: This skill requires user-provided credentials. The user must complete a one-time setup:

### One-Time User Setup

1. **Create Spotify Developer Application**
   - Go to https://developer.spotify.com/dashboard/
   - Create an app and note the Client ID and Client Secret
   - Add redirect URI: `http://127.0.0.1:8888/callback`

2. **Obtain Refresh Token**
   - User must run the helper script locally (see `references/setup-guide.md`)
   - Script exchanges OAuth code for a long-lived refresh token
   - Refresh token is saved as credential in skill configuration

3. **Configure Credentials**
   - Add three credentials to this skill:
     - `SPOTIFY_CLIENT_ID`: From Spotify Developer Dashboard
     - `SPOTIFY_CLIENT_SECRET`: From Spotify Developer Dashboard
     - `SPOTIFY_REFRESH_TOKEN`: From helper script output

   **Alternative**: Credentials can also be provided via a **Project Knowledge** file. Ensure the file contains a `.env` style block with the keys above.

**Without these credentials, the skill cannot function.** If credentials are missing, guide the user through the setup process detailed in `references/setup-guide.md`.

## MCP Server Installation

The Spotify MCP Server must be installed in the compute environment. Use the provided installation script which handles cloning, patching, and building the server.

```bash
# Run the installation script
bash scripts/install-mcp-server.sh
```

### MCP Server Configuration

Configure the MCP client to connect to the Spotify MCP server. Note the logic to support credentials from Project Knowledge.

```python
from mcp import Client
import asyncio
import re

# 1. Try to get credentials from skill configuration
env_vars = {
    "SPOTIFY_CLIENT_ID": credentials.get("SPOTIFY_CLIENT_ID"),
    "SPOTIFY_CLIENT_SECRET": credentials.get("SPOTIFY_CLIENT_SECRET"),
    "SPOTIFY_REFRESH_TOKEN": credentials.get("SPOTIFY_REFRESH_TOKEN")
}

# 2. If missing, look in Project Knowledge / Context for .env style block
if not all(env_vars.values()):
    # Heuristic: Scan context/files for VAR=VALUE patterns
    # (Pseudo-code: Implement based on available context access)
    pass

# Server configuration
mcp_config = {
    "command": "node",
    "args": ["/home/claude/spotify-mcp-server/build/index.js"],
    "env": env_vars
}

# Initialize client
async def initialize_spotify_mcp():
    client = Client()
    await client.connect_stdio(
        mcp_config["command"],
        mcp_config["args"],
        mcp_config["env"]
    )
    return client
```

## Available Tools

### Read Operations

1. **searchSpotify** - Search for tracks, albums, artists, or playlists
   ```python
   result = await client.call_tool("searchSpotify", {
       "query": "bohemian rhapsody",
       "type": "track",
       "limit": 10
   })
   ```

2. **getNowPlaying** - Get currently playing track information
   ```python
   result = await client.call_tool("getNowPlaying", {})
   ```

3. **getMyPlaylists** - List user's playlists
   ```python
   result = await client.call_tool("getMyPlaylists", {
       "limit": 20,
       "offset": 0
   })
   ```

4. **getPlaylistTracks** - Get tracks from a playlist
   ```python
   result = await client.call_tool("getPlaylistTracks", {
       "playlistId": "37i9dQZEVXcJZyENOWUFo7"
   })
   ```

5. **getRecentlyPlayed** - Get recently played tracks
   ```python
   result = await client.call_tool("getRecentlyPlayed", {
       "limit": 10
   })
   ```

6. **getUsersSavedTracks** - Get user's liked songs
   ```python
   result = await client.call_tool("getUsersSavedTracks", {
       "limit": 50,
       "offset": 0
   })
   ```

### Playback Control

1. **playMusic** - Start playing track/album/artist/playlist
   ```python
   # Play by URI
   result = await client.call_tool("playMusic", {
       "uri": "spotify:track:6rqhFgbbKwnb9MLmUQDhG6"
   })

   # Or by type and ID
   result = await client.call_tool("playMusic", {
       "type": "track",
       "id": "6rqhFgbbKwnb9MLmUQDhG6"
   })
   ```

2. **pausePlayback** - Pause current playback
   ```python
   result = await client.call_tool("pausePlayback", {})
   ```

3. **skipToNext** - Skip to next track
   ```python
   result = await client.call_tool("skipToNext", {})
   ```

4. **skipToPrevious** - Skip to previous track
   ```python
   result = await client.call_tool("skipToPrevious", {})
   ```

5. **addToQueue** - Add track/album to playback queue
   ```python
   result = await client.call_tool("addToQueue", {
       "uri": "spotify:track:6rqhFgbbKwnb9MLmUQDhG6"
   })
   ```

### Playlist Management

1. **createPlaylist** - Create new playlist
   ```python
   result = await client.call_tool("createPlaylist", {
       "name": "My Workout Mix",
       "description": "High energy tracks",
       "public": False
   })
   ```

2. **addTracksToPlaylist** - Add tracks to existing playlist
   ```python
   result = await client.call_tool("addTracksToPlaylist", {
       "playlistId": "3cEYpjA9oz9GiPac4AsH4n",
       "trackUris": [
           "spotify:track:4iV5W9uYEdYUVa79Axb7Rh",
           "spotify:track:6rqhFgbbKwnb9MLmUQDhG6"
       ]
   })
   ```

### Album Operations

1. **getAlbums** - Get album details
   ```python
   result = await client.call_tool("getAlbums", {
       "albumIds": ["4aawyAB9vmqN3uQ7FjRGTy"]
   })
   ```

2. **getAlbumTracks** - Get tracks from album
   ```python
   result = await client.call_tool("getAlbumTracks", {
       "albumId": "4aawyAB9vmqN3uQ7FjRGTy"
   })
   ```

3. **saveOrRemoveAlbumForUser** - Save/remove albums
   ```python
   result = await client.call_tool("saveOrRemoveAlbumForUser", {
       "albumIds": ["4aawyAB9vmqN3uQ7FjRGTy"],
       "action": "save"
   })
   ```

## Workflow Examples

### Example 1: Play User's Favorite Song

```python
# 1. Search for the song
search_result = await client.call_tool("searchSpotify", {
    "query": "user's favorite song name",
    "type": "track",
    "limit": 1
})

# 2. Extract track URI from results
track_uri = search_result["tracks"][0]["uri"]

# 3. Play the track
await client.call_tool("playMusic", {
    "uri": track_uri
})
```

### Example 2: Create Playlist from Genre

```python
# 1. Search for tracks in genre
search_result = await client.call_tool("searchSpotify", {
    "query": "genre:rock year:2020-2024",
    "type": "track",
    "limit": 20
})

# 2. Create new playlist
playlist_result = await client.call_tool("createPlaylist", {
    "name": "Modern Rock Mix",
    "description": "Recent rock tracks",
    "public": False
})

# 3. Extract track URIs
track_uris = [track["uri"] for track in search_result["tracks"]]

# 4. Add tracks to playlist
await client.call_tool("addTracksToPlaylist", {
    "playlistId": playlist_result["id"],
    "trackUris": track_uris
})
```

### Example 3: Show What's Playing

```python
# Get current playback state
now_playing = await client.call_tool("getNowPlaying", {})

# Format and display
print(f"Now Playing: {now_playing['track']['name']}")
print(f"Artist: {now_playing['track']['artists'][0]['name']}")
print(f"Album: {now_playing['track']['album']['name']}")
print(f"Progress: {now_playing['progress_ms']} / {now_playing['duration_ms']} ms")
```

## Important Notes

### Spotify Premium Required

Playback control operations (play, pause, skip, queue) **require Spotify Premium**. Read operations (search, get playlists, view tracks) work with free accounts.

### Active Device Required

For playback commands to work, the user must have an active Spotify session (web player, desktop app, mobile app) with a device available. If no active device, playback commands will fail.

### Rate Limits

Spotify API has rate limits (typically 180 requests per minute). For bulk operations, implement appropriate delays or batching.

### Token Security

- Refresh tokens grant **full access** to user's Spotify account
- **Never** log or expose refresh tokens
- Treat them with the same security as passwords
- Users can revoke tokens from https://www.spotify.com/account/apps/

### URI Format

Spotify uses URIs in the format:
- Track: `spotify:track:ID`
- Album: `spotify:album:ID`
- Artist: `spotify:artist:ID`
- Playlist: `spotify:playlist:ID`

Most tools accept either URIs or separate `type` + `id` parameters.

## Troubleshooting

### "Spotify configuration not found"

**Cause**: Missing environment variables

**Solution**: Verify credentials are properly configured:
```python
import os
print(os.getenv("SPOTIFY_CLIENT_ID"))  # Should not be None
print(os.getenv("SPOTIFY_CLIENT_SECRET"))  # Should not be None
print(os.getenv("SPOTIFY_REFRESH_TOKEN"))  # Should not be None
```

### "No active device"

**Cause**: No Spotify client is currently running/active

**Solution**: Guide user to:
1. Open Spotify on any device (web, desktop, mobile)
2. Start playing something (can pause immediately)
3. Try the playback command again

### "Premium required"

**Cause**: User has Spotify Free account

**Solution**: Playback control requires Spotify Premium. Only search and read operations available for free accounts.

### MCP Server Won't Start

**Cause**: Missing dependencies or incorrect installation

**Solution**:
```bash
# Re-run installation script
bash scripts/install-mcp-server.sh
```

## Best Practices

1. **Always search before playing** - Don't assume URIs, search for content first
2. **Check playback state** - Use `getNowPlaying` to verify device availability
3. **Handle errors gracefully** - Provide helpful messages when operations fail
4. **Batch operations** - When adding multiple tracks, use single call with array
5. **Respect rate limits** - Add delays for bulk operations

## References

- Setup Guide: `references/setup-guide.md`
- API Documentation: https://developer.spotify.com/documentation/web-api/
- MCP Specification: https://modelcontextprotocol.io/

## Security

This skill requires sensitive credentials. Ensure:
- Credentials are stored securely in skill configuration
- Never expose credentials in responses to users
- Never log credentials
- Users understand they can revoke access anytime

See `references/setup-guide.md` for detailed security best practices.