131 lines
4.7 KiB
Markdown
131 lines
4.7 KiB
Markdown
|
|
# Spotify MCP Server — Design Spec
|
|||
|
|
|
|||
|
|
**Date:** 2026-05-22
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
A Python MCP server that exposes Spotify library and playlist management as tools for Claude. Connects via stdio transport (Claude launches it as a subprocess). Authentication uses the OAuth PKCE flow via `spotipy`; tokens are cached locally and auto-refreshed.
|
|||
|
|
|
|||
|
|
## Project Structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
mcp-spotify/
|
|||
|
|
├── server.py # MCP server entry point, tool registrations
|
|||
|
|
├── spotify_client.py # Thin wrapper around spotipy (auth + API calls)
|
|||
|
|
├── pyproject.toml # Dependencies: mcp, spotipy, python-dotenv
|
|||
|
|
├── .env.example # SPOTIPY_CLIENT_ID, SPOTIPY_CLIENT_SECRET, SPOTIPY_REDIRECT_URI
|
|||
|
|
└── README.md # Setup instructions (creating a Spotify Developer app, registering with Claude Code)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Dependencies
|
|||
|
|
|
|||
|
|
- `mcp` — official MCP Python SDK (stdio server)
|
|||
|
|
- `spotipy` — Spotify Web API client with built-in OAuth and token caching
|
|||
|
|
- `python-dotenv` — load `.env` at startup
|
|||
|
|
|
|||
|
|
## MCP Tools
|
|||
|
|
|
|||
|
|
| Tool | Description | Parameters |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `list_playlists` | List the authenticated user's playlists | — |
|
|||
|
|
| `get_playlist_tracks` | Get all tracks in a playlist with full metadata | `playlist_id: str` |
|
|||
|
|
| `list_saved_tracks` | Get the user's liked/saved tracks | `limit: int = 50` |
|
|||
|
|
| `search_tracks` | Search Spotify for tracks | `query: str`, `limit: int = 10` |
|
|||
|
|
| `create_playlist` | Create a new playlist | `name: str`, `description: str = ""`, `public: bool = False` |
|
|||
|
|
| `add_tracks_to_playlist` | Add tracks to a playlist by URI | `playlist_id: str`, `track_uris: list[str]` |
|
|||
|
|
|
|||
|
|
### Track data returned by `get_playlist_tracks`
|
|||
|
|
|
|||
|
|
Each track entry includes:
|
|||
|
|
- `name` — track title
|
|||
|
|
- `artists` — list of artist names
|
|||
|
|
- `album` — album name
|
|||
|
|
- `duration_ms` — duration in milliseconds
|
|||
|
|
- `duration` — duration formatted as `mm:ss`
|
|||
|
|
- `popularity` — Spotify popularity score (0–100)
|
|||
|
|
- `uri` — Spotify track URI (used to add tracks to playlists)
|
|||
|
|
- `added_at` — ISO timestamp of when the track was added to the playlist
|
|||
|
|
|
|||
|
|
All fields come from a single paginated API call (`playlist_items`); no additional requests needed.
|
|||
|
|
|
|||
|
|
## Authentication
|
|||
|
|
|
|||
|
|
### Setup (once per user)
|
|||
|
|
|
|||
|
|
1. Create a Spotify Developer app at [developer.spotify.com](https://developer.spotify.com)
|
|||
|
|
2. Set the redirect URI to `http://localhost:8888/callback`
|
|||
|
|
3. Copy `.env.example` to `.env` and fill in:
|
|||
|
|
```
|
|||
|
|
SPOTIPY_CLIENT_ID=<your_client_id>
|
|||
|
|
SPOTIPY_CLIENT_SECRET=<your_client_secret>
|
|||
|
|
SPOTIPY_REDIRECT_URI=http://localhost:8888/callback
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### First run
|
|||
|
|
|
|||
|
|
`spotify_client.py` initializes `spotipy.Spotify` with `SpotifyOAuth`. spotipy opens the browser to Spotify's auth page. After login, Spotify redirects to `localhost:8888/callback`. spotipy exchanges the code for tokens and caches them to `~/.cache/spotipy/<username>.cache`.
|
|||
|
|
|
|||
|
|
### Subsequent runs
|
|||
|
|
|
|||
|
|
spotipy reads the cached token. If expired, it silently refreshes using the stored refresh token. No browser interaction needed.
|
|||
|
|
|
|||
|
|
### OAuth scopes
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
playlist-read-private
|
|||
|
|
playlist-read-collaborative
|
|||
|
|
playlist-modify-public
|
|||
|
|
playlist-modify-private
|
|||
|
|
user-library-read
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Architecture & Data Flow
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Claude (MCP client)
|
|||
|
|
│ stdio (JSON-RPC)
|
|||
|
|
▼
|
|||
|
|
server.py (mcp SDK — tool definitions + request routing)
|
|||
|
|
│ Python function calls
|
|||
|
|
▼
|
|||
|
|
spotify_client.py (spotipy wrapper)
|
|||
|
|
│ HTTPS
|
|||
|
|
▼
|
|||
|
|
Spotify Web API
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`server.py` owns all MCP concerns: tool schemas, request parsing, response formatting.
|
|||
|
|
`spotify_client.py` owns all Spotify concerns: auth, API calls, pagination, data shaping.
|
|||
|
|
|
|||
|
|
## Error Handling
|
|||
|
|
|
|||
|
|
- **Startup:** If required env vars are missing, raise immediately with a descriptive message before the server accepts any connections.
|
|||
|
|
- **API errors:** `spotipy` raises `SpotifyException` for all API-level errors. These are caught in `server.py` and returned as MCP error responses containing the HTTP status code and Spotify error message.
|
|||
|
|
- **Token expiry:** Handled transparently by spotipy's `SpotifyOAuth` — no special handling needed.
|
|||
|
|
|
|||
|
|
## Claude Code Registration
|
|||
|
|
|
|||
|
|
Add to `~/.claude/settings.json` (or project `.claude/settings.json`):
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"mcpServers": {
|
|||
|
|
"spotify": {
|
|||
|
|
"command": "python",
|
|||
|
|
"args": ["/path/to/mcp-spotify/server.py"],
|
|||
|
|
"env": {
|
|||
|
|
"SPOTIPY_CLIENT_ID": "<your_client_id>",
|
|||
|
|
"SPOTIPY_CLIENT_SECRET": "<your_client_secret>",
|
|||
|
|
"SPOTIPY_REDIRECT_URI": "http://localhost:8888/callback"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Alternatively, env vars can be loaded from `.env` by `python-dotenv` and the `env` block omitted.
|
|||
|
|
|
|||
|
|
## Testing
|
|||
|
|
|
|||
|
|
No automated tests. The server is a thin adapter over `spotipy` and the Spotify Web API; mocking the API would test the wrong thing. Verification is done by running the server and calling tools from Claude.
|