使用说明
spotify-mcp
Local Spotify MCP server for Codex focused on personal playlist management.
This project wraps Spotify's Web API in a small MCP tool surface for managing playlists from Codex. It is designed for local, authenticated use on a single Spotify account.
What It Does
The server exposes these tools:
spotify_get_workflowspotify_list_artifactsspotify_read_artifactspotify_write_artifactspotify_check_playlist_constraintsspotify_get_my_profilespotify_list_playlistsspotify_get_playlistspotify_get_playlist_itemsspotify_get_trackspotify_search_tracksspotify_create_playlistspotify_change_playlist_detailsspotify_unfollow_playlistspotify_archive_playlistspotify_add_playlist_itemsspotify_replace_playlist_itemsspotify_merge_playlistsspotify_dedupe_playlistspotify_remove_playlist_itemsspotify_reorder_playlist_itemsspotify_clone_playlistspotify_refresh_personalization_statespotify_get_personalization_contextspotify_get_personalization_statespotify_record_personalization_feedbackspotify_record_playlist_evaluationspotify_create_person_profilespotify_update_person_profilespotify_list_person_profilesspotify_get_person_profilespotify_get_person_profile_contextspotify_record_person_playlistspotify_record_person_feedback
What It Does Not Do
- It does not support true playlist deletion. Spotify's Web API does not expose a delete-playlist endpoint.
- It does not support local-file playlist items for clone, replace, or URI-based remove flows.
- It is not intended to be a hosted multi-user service in its current form.
Requirements
- Node.js 22+
pnpm- Python 3 for the bundled playlist-builder constraint checker
- A Spotify app in the Spotify Developer Dashboard
Quick Start
- Create a Spotify app in the Spotify Developer Dashboard.
- Add
http://127.0.0.1:8787/callbackas an allowed redirect URI. - Copy
.env.exampleto.env. - Set
SPOTIFY_CLIENT_IDin.env. - Install dependencies:
pnpm install
- Authenticate once:
pnpm auth
- Build the server:
pnpm build
- Point Codex at the built server.
Most users should use the built server path above. Use pnpm dev only when actively developing on this repository.
Spotify App Setup
This project uses PKCE and requests these scopes:
playlist-read-privateplaylist-read-collaborativeplaylist-modify-privateplaylist-modify-publicuser-library-readuser-follow-read
If you authenticated before the personalization layer was added, run pnpm auth again so the stored token includes the new read scopes.
The auth command:
- generates a PKCE verifier/challenge
- prints the Spotify authorization URL
- starts a temporary callback server on your local redirect URI
- exchanges the returned code for tokens
- stores tokens in
~/.config/spotify-mcp/auth.json
Copy the printed URL into your browser to complete login.
Running
Stable built-server setup
Recommended for normal use:
pnpm build
pnpm start
dist/ is build output. Do not assume a fresh clone already has the right compiled files. Run pnpm build after cloning and whenever source changes need to be picked up by the built server.
Example Codex config:
[mcp_servers.spotify]
command = "node"
args = ["--env-file=/absolute/path/to/spotify-mcp/.env", "/absolute/path/to/spotify-mcp/dist/server.js"]
Replace /absolute/path/to/spotify-mcp with your local clone path.
Contributor setup
Useful only when editing this repo:
pnpm dev
This runs tsx watch src/server.ts so source edits hot reload automatically.
Example Codex config for contributor mode:
[mcp_servers.spotify]
command = "pnpm"
args = ["--dir", "/absolute/path/to/spotify-mcp", "dev"]
Codex Skills
The repository includes five Codex skills under skills/ for playlist prompting, building, seed-radio generation, review, and builder-quality evaluation. These repository copies are canonical and contain workflow instructions and deterministic validation code only; credentials, personalization, listener profiles, histories, and generated artifacts stay outside Git.
Preview installation into ~/.codex/skills:
pnpm skills:install
Apply the installation:
pnpm skills:install -- --apply
Set CODEX_HOME to install into another absolute Codex directory. Run pnpm skills:check before publishing skill changes to scan for personal paths, identifiers, credentials, runtime-state files, and hardcoded artifact paths. See skills/README.md for the package list and storage rules.
Tool Notes
- New playlists request
public: falseby default, which asks Spotify not to publish them on the owner's profile. This does not make playlist access private: Spotify's Web API cannot control that setting. Itspublicreadback can reporttrueeven when a playlist is not listed on the owner's profile, so verify profile visibility in Spotify's UI when it matters. spotify_unfollow_playlistremoves a playlist from the current user's library. It does not delete the playlist from Spotify.spotify_archive_playlistis an opinionated cleanup flow for owned playlists: it requests removal from the owner's profile, disables collaboration, prefixes the name with[Archived]by default, and can optionally clear all items. It does not make playlist access private; verify profile visibility in Spotify's UI when it matters.spotify_replace_playlist_items,spotify_remove_playlist_items,spotify_reorder_playlist_items,spotify_merge_playlists,spotify_dedupe_playlist,spotify_unfollow_playlist, andspotify_archive_playlistrequireconfirm: true.spotify_merge_playlistsrejects duplicate sources and a source that matches its target.- Remove and reorder fetch the latest playlist snapshot before mutating.
- Clone copies items in batches and requests that the destination is not published on the owner's profile unless you explicitly opt into
public: true. It does not make playlist access private; verify profile visibility in Spotify's UI when it matters. - Personalization state is stored outside the repo in
~/.config/spotify-mcp/personalization/. - Friend/family listener profiles are stored separately in
~/.config/spotify-mcp/people/. - Generated user-specific artifacts should also stay outside the repo in
~/.config/spotify-mcp/artifacts/. - The personalization layer keeps four files separate on purpose:
profile-snapshot.jsonfor refreshable Spotify-derived stateuser-preferences.jsonfor durable explicit preferencesinteraction-log.ndjsonfor append-only MCP historypersonalization-context.mdfor future-agent context
- The people-profile layer keeps one directory per saved listener under
~/.config/spotify-mcp/people/<profile-id>/:profile.jsonfor canonical listener context and taste cuesplaylist-history.ndjsonfor structured playlist history and outcomesprofile-context.mdfor the compact future-agent summary for that person
- Human-readable playlist writeups are optional sidecar artifacts, not canonical personalization memory. Keep the reusable learning in personalization state and store the writeups under the artifacts directory when you want to preserve them.
- Human-readable writeups for people profiles are also optional sidecars. Keep canonical listener state in
profile.jsonand structured playlist outcomes inplaylist-history.ndjson, then store review notes under~/.config/spotify-mcp/artifacts/people/<profile-id>/. spotify_record_personalization_feedbackalso supports scopeduse_casefeedback so future agents can retain lessons like "focused work prefers steady instrumental music and avoids abrupt vocals."- Scoped use-case preferences can also store playback mode and ideal track-count range.
spotify_record_playlist_evaluationcaptures evidence about specific playlist outputs, including score, verdict, winning traits, losing traits, and workflow learnings.- Friend/family profiles are manual-context-first in v1. Create or update the person profile, read their generated context, build the playlist with the normal Spotify tools, then record the resulting playlist back against the profile.
Sharing durable state between Macs
Shared mode is opt-in. Keep authentication and generated state local while putting durable preferences, people profiles, history, and artifacts in iCloud:
SPOTIFY_MCP_DATA_DIR=~/.config/spotify-mcp
SPOTIFY_MCP_SHARED_DATA_DIR=~/Library/Mobile Documents/com~apple~CloudDocs/App Data/spotify-mcp
SPOTIFY_MCP_MACHINE_ID=desktop
Use a different stable lowercase id such as neo on the other Mac. On first use, Spotify MCP creates a private local installation-id and reserves the machine id under the shared machines/ directory. A machine id can belong to only one installation, so do not copy installation-id between Macs or reuse an id after configuring another Mac.
Never move or symlink the entire ~/.config/spotify-mcp directory: auth.json, Spotify snapshots, generated contexts, .env, and Codex configuration must remain machine-local. Spotify MCP rechecks the iCloud root and machine reservation before each shared read or write. If the configured root disappears, the operation fails instead of reporting empty durable state, recreating a local shadow directory, or falling back to local storage.
Migrate the desktop first after iCloud is available:
pnpm data:migrate
pnpm data:migrate -- --apply
The first command is read-only. Create the configured directory in iCloud before applying the migration. Apply first validates a private immutable snapshot of the local migration inputs, then reserves the machine id and writes shared state from that snapshot. The migration preserves the original local files, excludes credentials and generated state, and can be rerun without duplicating records. Allow iCloud to finish syncing before configuring or migrating Neo. If Neo has different preferences or profiles, migration imports them as explicit revision forks while still migrating its histories and artifacts; resolve those forks afterward.
Preferences and person profiles retain immutable revisions. If offline edits create multiple tips, normal access refuses to choose one. Inspect and resolve the conflict explicitly:
pnpm data:resolve -- --document preferences
pnpm data:resolve -- --document preferences --from-revision <revision-id> --apply
pnpm data:resolve -- --document people/<profile-id> --from-file /absolute/path/to/merged-profile.json --apply
Resolution preserves all earlier revisions. Removing the shared variables restores legacy local behavior using the untouched local files; newer shared activity is not copied back automatically. Give shared artifacts unique or timestamped names because differing files at the same relative path are treated as collisions during migration. In shared mode, durable playlist history stores artifact references relative to the shared root (for example, artifacts/people/sample-listener/review.md) so they remain valid when each Mac mounts iCloud at a different absolute path.
Example Calls
Create a playlist:
{
"name": "Morning Focus",
"description": "Low-distraction electronic and ambient tracks"
}
Add tracks:
{
"playlistId": "37i9dQZF1DX...",
"uris": [
"spotify:track:4iV5W9uYEdYUVa79Axb7Rh",
"spotify:track:1301WleyT98MSxVHPZCA6M"
]
}
Archive a playlist you own:
{
"playlistId": "37i9dQZF1DX...",
"clearItems": true,
"confirm": true
}
Replace the full playlist body:
{
"playlistId": "37i9dQZF1DX...",
"uris": [
"spotify:track:4iV5W9uYEdYUVa79Axb7Rh",
"spotify:track:1301WleyT98MSxVHPZCA6M"
],
"confirm": true
}
Merge playlists into a target:
{
"targetPlaylistId": "37i9dQZF1DXTARGET",
"sourcePlaylistIds": ["37i9dQZF1DXSOURCEA", "37i9dQZF1DXSOURCEB"],
"dedupe": true,
"confirm": true
}
Refresh the personalization snapshot:
{
"playlistLimit": 250,
"savedTracksLimit": 200,
"savedAlbumsLimit": 100,
"followedArtistsLimit": 100
}
Record explicit personalization feedback:
{
"kind": "artist",
"sentiment": "prefer",
"value": "Artist A",
"context": "Example durable artist preference"
}
Record use-case-specific trait feedback:
{
"kind": "trait",
"sentiment": "prefer",
"value": "steady instrumental electronic",
"use_case": "focused work",
"context": "Keeps attention steady during focused work"
}
Record a use-case playback preference:
{
"kind": "playback_mode",
"use_case": "focused work",
"playback_mode": "shuffle",
"context": "Shuffle works for long background sessions"
}
Record an ideal use-case track-count range:
{
"kind": "ideal_track_count_range",
"use_case": "focused work",
"min_count": 55,
"max_count": 65,
"context": "This range balances variety and repeatability"
}
Record a playlist evaluation:
{
"playlistId": "37i9dQZF1DX...",
"use_case": "focused work",
"verdict": "default",
"score": 9.2,
"winning_traits": [
"steady instrumental electronic",
"consistent tempo",
"low distraction"
],
"losing_traits": ["abrupt vocals", "redundant texture"],
"workflow_learning": "A broad draft followed by a focused trim improves consistency"
}
Create a saved listener profile:
{
"name": "Sample Listener",
"relationship": "friend",
"age": 30,
"life_context": ["prefers morning listening", "often listens while cooking"],
"preferred_traits": ["bright", "warm", "upbeat indie-pop"],
"avoided_traits": ["harsh drops", "abrasive bass"],
"playlist_goals": ["upbeat background music"],
"notes": ["Keep recommendations easy and replayable"]
}
Read the generated context for one saved listener:
{
"profileId": "sample-listener"
}
Record a playlist made for one saved listener:
{
"profileId": "sample-listener",
"playlist_id": "37i9dQZF1DX...",
"playlist_name": "Sample Listener - Upbeat Background",
"playlist_url": "https://open.spotify.com/playlist/37i9dQZF1DX...",
"use_case": "upbeat background music",
"track_count": 22,
"runtime_minutes": 78,
"score": 9,
"verdict": "success",
"winning_traits": ["bright", "warm", "comforting"],
"workflow_learning": "Tightening to roughly 22 tracks improved hit rate and replayability",
"artifact_paths": ["artifacts/people/sample-listener/review.md"]
}
Record one new durable learning for a saved listener:
{
"profileId": "sample-listener",
"kind": "trait",
"sentiment": "avoid",
"value": "festival emotional"
}
Verification
Local verification:
pnpm format:check
pnpm lint
pnpm typecheck
pnpm test
pnpm build
Live-account smoke test:
pnpm smoke
The smoke test:
- uses your real authenticated Spotify account
- creates temporary playlists that request not to be published on the owner's profile; it does not make them access-private
- exercises create, change-details, add, replace, clear, dedupe, merge, reorder, remove, and clone
- prints the created playlist IDs so you can clean them up manually afterward
Detailed smoke polling logs are off by default. Enable them with:
SPOTIFY_SMOKE_VERBOSE=1 pnpm smoke
Refresh the local personalization files:
pnpm personalize:refresh
That command refreshes liked-track, saved-album, playlist, and followed-artist state and rebuilds the generated personalization summary future agents can use as context.
CI
GitHub Actions runs this verification stack on pushes to main and on pull requests:
pnpm lintpnpm typecheckpnpm testpnpm build
Private ChatGPT plugin
Use an OpenAI Secure MCP Tunnel to run the built stdio server on your Mac, like a local Codex connection. No public listener is required. See the Spotify tunnel guide. For other MCPs, use the private-chatgpt-mcp skill maintained in the personal codex-skills collection.
All Spotify and durable-state tools remain available. The tunnel must load the same local .env so the shared root and machine identity stay consistent. Chat requests from another computer execute on the tunnel host and append to that host's history stream. Keep the other Mac's distinct identity and local credentials; iCloud sync remains responsible for cross-machine availability.
spotify_get_workflow returns any of the five bundled workflows with reference notes. Use spotify_check_playlist_constraints to run the existing Python checker with a JSON manifest without shell access. spotify_list_artifacts lists one directory with pagination; spotify_read_artifact returns saved text; spotify_write_artifact saves UTF-8 md/txt/json/csv/html sidecars. Inputs and text artifacts are capped at 512 KiB. Paths are relative to the configured artifact root; symlinks and traversal are refused. Existing artifacts are not overwritten: save a timestamped revision to preserve historical evidence across Macs. Writes return portable artifacts/... references suitable for durable history. This connection returns artifact contents to ChatGPT, not a public download URL or automatic file attachment.