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.
|