SpotifyMCP
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| SPOTIFY_CLIENT_ID | Yes | Spotify Client ID from your Spotify app. Required. | |
| SPOTIFY_MCP_HISTORY | No | Set to '1' to log mutations to JSONL for undo. | |
| SPOTIFY_MCP_READONLY | No | Set to '1' to hide every write tool (read-only mode). | |
| SPOTIFY_MCP_TOOLSETS | No | Comma-separated list of tool groups to trim by group for hosts that cap tool counts. Example: playback,catalog |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tasks | {
"list": {},
"cancel": {},
"requests": {
"tools": {
"call": {}
}
}
} |
| tools | {
"listChanged": true
} |
| prompts | {
"listChanged": true
} |
| resources | {
"listChanged": true
} |
| completions | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| searchA | Search Spotify's catalog for tracks, artists, albums, playlists, shows, episodes, or audiobooks. Pass |
| get_saved_tracksA | Get tracks saved in the user's Liked Songs. Set fetch_all=true to retrieve the entire collection. Output is capped by max_results (default: SPOTIFY_MCP_MAX_ITEMS). |
| get_saved_albumsA | Get albums saved in the user's library. Set fetch_all=true to retrieve the entire collection. Output is capped by max_results (default: SPOTIFY_MCP_MAX_ITEMS). |
| get_saved_showsA | Get podcast shows saved in the user's library. Set fetch_all=true to retrieve the entire collection. Output is capped by max_results (default: SPOTIFY_MCP_MAX_ITEMS). |
| get_saved_episodesA | Get podcast episodes saved in the user's library. Set fetch_all=true to retrieve the entire collection. Output is capped by max_results (default: SPOTIFY_MCP_MAX_ITEMS). |
| save_to_libraryA | Preferred. Accepts the widest URI mix (track, album, episode, show, audiobook, user, playlist) in one request. Save one or more items to the user's library via Spotify's unified library endpoint. Max 40. PREVIEWS BY DEFAULT — pass dry_run=false to commit. |
| remove_from_libraryA | Preferred. Accepts the widest URI mix (track, album, episode, show, audiobook, user, playlist) in one request. Remove one or more items from the user's library via Spotify's unified library endpoint. Max 40. PREVIEWS BY DEFAULT — pass dry_run=false to commit. Removals of 10+ items additionally require elicitation confirmation (or SPOTIFY_MCP_CONFIRM=never for automation). |
| get_saved_countsA | Library size snapshot: counts for tracks/albums/shows/episodes/audiobooks/playlists via limit=1 reads — no item paging. The total covers library saves only: playlists are owned and followed collections, so their count is reported alongside the library rows and excluded from the total. A collection that could not be read (rate limited, gated, or erroring) is reported as unreadable with its reason and left out of the total — it is never reported as 0. One attempt per collection: a rate limit is surfaced, not retried. Quota: 6 GETs. |
| search_saved_albumsA | Search saved albums (client-side filter over bounded walk). Quota: GET /me/albums paged. |
| search_saved_showsA | Search saved podcast shows (bounded walk + client-side filter). Quota: GET /me/shows paged. |
| search_saved_episodesB | Search saved episodes (bounded walk + client-side filter). Quota: GET /me/episodes paged. |
| search_saved_audiobooksA | Search saved audiobooks (bounded walk + client-side filter). Quota: GET /me/audiobooks paged. |
| check_in_libraryA | Preferred. Accepts the widest URI mix (track, album, episode, show, audiobook, artist, user, playlist) in one request. Check whether items are saved in or followed by the user — this tests LIBRARY-SAVED/FOLLOWED state, distinct from check_following_artists which only tests artist FOLLOW state. Returns a boolean per URI via Spotify's unified endpoint. Max 40. Following an artist is no longer expressible: Spotify's February 2026 changes removed PUT/DELETE /me/following, and this endpoint's save side does not accept spotify:artist: URIs, so there is no endpoint that can follow or unfollow an artist. This read still answers the question for artist URIs. |
| search_saved_tracksA | Search your Liked Songs (saved tracks) by text query and optional facets — client-side filter over a bounded walk of /me/tracks. For catalog-wide search use search. Reports walk truncation. |
| get_now_playingA | Full device/session state for what is playing right now — item, progress, plus shuffle/repeat mode, active device, and volume. For a lightweight item+progress poll use get_currently_playing instead. |
| get_currently_playingA | Lightweight poll of what is playing right now: the item and progress only. For full session state (shuffle/repeat mode, active device, volume) use get_now_playing instead. |
| play_from_searchA | Search Spotify by name and immediately play the best match. Works for songs and podcast episodes — no URI needed. |
| playC | Start or resume playback. Optionally target specific content. |
| pauseB | Pause playback on the active device |
| skip_nextB | Skip to the next track in the queue or context. |
| skip_previousA | Skip to the previous track. If more than 3 seconds in, restarts the current track first. |
| seekB | Seek to a position in the current track |
| set_volumeA | Set playback volume on one device, on a selection of devices, or across every live device. |
| set_shuffleC | Enable or disable shuffle mode |
| set_repeatB | Set repeat mode: off, context (repeat playlist/album), or track (repeat single track) |
| get_queueA | Read the playback queue. Use this for queue CONTENTS (what is playing, what is up next, how long it runs, what repeats). Use peek_next for a short lookahead. Quota: 1 read; view=enriched and include=runtime add one GET /me/player, and the context label adds one catalog read. |
| add_to_queueA | Add a track or episode to the end of the playback queue. |
| get_devicesA | List available Spotify Connect devices. The same rows are readable as a resource with no tool call at spotify://player/devices ('?format=json' returns the raw API object). |
| transfer_playbackA | Move playback to a different Spotify Connect device, named by exact id, by the label you gave it, or by a case-insensitive name substring. A plain transfer restarts the track at 0:00 on the target; |
| get_followed_artistsA | Get the artists the user follows. fetch_all=true walks every page; limit/after page manually. |
| check_following_artistsA | Check if the user follows specific artists — this tests FOLLOW state, not library-saved state (for that use check_in_library). Accepts IDs or spotify:artist: URIs. Rows carry {id, uri, follows}; returns a boolean per ID. Max 50. |
| following_analyticsB | Followed-artist rollups from the tag sidecar; popularity/followers unavailable (Spotify no longer returns those fields). Quota: GET /me/following. |
| get_user_profileA | Get any Spotify user's public profile (display name, profile image). Removed by Spotify's February 2026 Web API changes — unavailable for newer app registrations. The |
| get_user_playlists_by_idA | List another Spotify user's public playlists (paginated). Removed by Spotify's February 2026 Web API changes — unavailable for newer app registrations. Output is capped by max_results (default: SPOTIFY_MCP_MAX_ITEMS). |
| get_user_playlistsB | List the current user's playlists |
| get_playlistA | Get a playlist's metadata (including cover image) and items. Use market to relink tracks and flag unavailable ones, and fields/additional_types to trim the payload — both are forwarded to the metadata read and the item pages. |
| get_playlist_itemsA | List a playlist's items on a single page. Use market to relink tracks and flag unavailable ones, and fields/additional_types to trim the payload. |
| get_playlist_coverB | Get a playlist's cover image URLs |
| upload_playlist_coverA | Replace a playlist's cover image with a base64-encoded JPEG. Requires the ugc-image-upload scope on the Spotify developer dashboard app (plus playlist-modify-public/private); without it Spotify rejects the upload with 403. |
| create_playlistA | Create a new playlist for the current user. Set dry_run=true to preview without creating. |
| add_to_playlistB | Add tracks or episodes to a playlist. Max 100 URIs per call. |
| remove_from_playlistB | Remove tracks or episodes from a playlist. Max 100 entries per call. |
| update_playlistC | Update a playlist's name, description, or visibility |
| reorder_playlist_itemsA | Move a range of items within a playlist. Spotify semantics: when insert_before > range_start, the effective destination shifts down by range_length because the moved range is lifted out first (e.g. moving [2] to insert_before=4 lands it AT index 3). |
| replace_playlist_itemsA | Replace ALL items in a playlist with the supplied URIs, overwriting the current contents. Lists longer than 100 URIs are sent in chunks internally (replace + appends). |
| find_duplicates_in_playlistA | Find duplicate tracks in a playlist under one published matching rule. |
| remove_duplicate_playlist_itemsA | Remove duplicate items from a playlist: keeps the first occurrence of each track and removes later repeats, under the |
| clean_all_playlistsA | Scan every playlist in your library for duplicate items (repeated URIs, and on opt-in same-song copies under different URIs when match_by=name_artist). Reports per-playlist findings by default; pass apply=true to remove them (keeps the first occurrence of each group). Bulk removals ask for one confirmation before anything is deleted. See the duplicate-matching vocabulary in SPEC section 4. |
| check_playlist_followingA | Check if you follow 1–50 playlists. Follow state: GET /me/library/contains?uris=spotify:playlist:,… (40/req, 1–2 GETs). Unreadable state reports unknown, never not-followed. |
| clone_playlist_coverB | Copy cover image from source playlist to target. Quota: GET images + PUT images (plus image fetch). |
| compare_playlist_coversA | Compare two playlists covers: URL equality, dimensions. Quota: 2 GETs. |
| get_playlist_snapshotA | Expose snapshot_id + item count for optimistic concurrency. Quota: 2 GETs. |
| playlist_collab_toggleB | Toggle collaborative/public flags (guards public=true && collaborative=true 400). Quota: GET + PUT. |
| playlist_sortA | Sort a playlist in place by added_at/name/artist/duration. Quota: GET all + PUT/POST. popularity is not a sort key: the API no longer returns it on playlist items. |
| playlist_shuffleB | Fisher-Yates shuffle a playlist (seeded optional). Quota: GET all + PUT/POST. |
| playlist_reverseA | Reverse a playlist in one atomic replace. Quota: GET all + PUT/POST. |
| playlist_unionB | Union of 2–10 playlists into target (deduped, first-seen order). An empty union empties the target the same way subtract does. Quota: N GETs + PUT/POST; replacing an existing target also reads its current items and its playlist metadata to measure the destructive impact. |
| playlist_subtractB | Remove tracks of B..N from A by REWRITING A: one PUT replaces every row with the ones that survive, so any row of A absent from the union is DELETED, and the rows that remain are re-written in order. Subtracting every track empties A via one PUT with an empty uris array (Spotify's documented clear); a reply with no snapshot_id reports unconfirmed, not ok. Quota: N GETs + PUT. |
| playlist_symmetric_differenceB | Tracks in exactly one of two playlists (XOR). Quota: 2 GETs. |
| playlist_trimB | Trim playlist to N items (keep first/last/random) by OVERWRITING the playlist: every row outside the kept set is DELETED, and the kept rows are re-written in the new order. An overwrite that deletes rows asks for confirmation first. Quota: 2 walks of the playlist items + 2 metadata GETs + PUT/POST. |
| merge_playlistsA | Merge multiple playlists into one. Deduplicates tracks across sources (first-seen order wins) and adds them in batches of 100. Pass target_playlist_id to append to an existing playlist (it is NOT cleared) or new_name to create a fresh playlist. |
| diff_playlistsA | Compare two playlists up to the configured source cap: tracks only in A, only in B (by track ID), and tracks present in both but at different positions. Rendered rows are capped by max_results; truncation metadata reports when source walks hit scan_cap. |
| overlap_playlistsB | Find tracks shared across playlists: reports how many playlists each track appears in and lists tracks present in at least min_overlap playlists (default: all of them), most-shared first. |
| batch_add_to_playlistA | Add tracks from multiple source URIs (tracks, albums, artists, playlists) to a target playlist in one call. Dedupes within the batch and optionally against the existing playlist. Batches writes in groups of 100. Dry-run previews without writing. Elicitation for 100+ tracks. Target the playlist by ID/URI/URL or by lane name. |
| copy_playlistA | Duplicate an existing playlist into a new playlist, preserving track order. Creates the new playlist then adds tracks in batches of 100. Dry-run reports what would be created. |
| move_items_between_playlistsA | Bulk rehome items between playlists. Mode copy keeps the source intact; mode move removes from source after copying — by playlist position, removing exactly the transferred occurrences and leaving any other copy in the source. Supports dedupe against target and optional name/artist filter. Either side may be named by lane. |
| follow_playlistA | Follow a playlist — save it to your Spotify library — via PUT /me/library. Spotify removed the playlist-followers endpoints in Feb 2026; this is the only way to follow a playlist. Supports dry_run (default true); writes require confirmation unless SPOTIFY_MCP_CONFIRM=never. |
| unfollow_playlistA | Unfollow a playlist — remove it from your Spotify library — via DELETE /me/library. Always asks before writing unless SPOTIFY_MCP_CONFIRM=never. |
| pin_playlistA | DEPRECATED, use follow_playlist. This tool never pinned anything; it has always saved the playlist to your Spotify library via PUT /me/library. |
| unpin_playlistA | DEPRECATED, use unfollow_playlist. This tool never unpinned anything; it has always removed the playlist from your Spotify library via DELETE /me/library. |
| playlist_template_applyB | Create an instant mood/vibe playlist from a template (focus, wind-down, gym, commute) composed from your existing listening data. Creates a new playlist and fills it. |
| spotify_doctorA | Run read-only diagnostics: token presence/expiry, auth-time scopes vs write tools enabled by active toolsets, Premium gating, rate-limit cooldown, config state, visible account details when reachable, and the live registered-tool surface with toolset/scope/READONLY trim causes. The only live request is GET /me; no mutation requests are issued. |
| list_accountsA | List every local account this server can act as, and which one it is acting as now. Returns each account's account_id, profile name, display name and token file PATH — never any token material. Reads the local registry and one GET /me for the acting account; no mutation requests are issued. |
| switch_accountA | Change which registered account this session acts as. Takes effect for every subsequent call in this session: the client re-points its token file, drops the previous account's cached reads and validators, and refuses to switch while a request is in flight. Asks for confirmation first, because every later write then lands in the new account's library. Changes local session state and the account registry only — it issues no Spotify writes. |
| find_toolA | Search the live tool registry by name or description substring — the fastest way to discover which of the 500+ tools handles a job. Discovery set: find_tool/inspect_tool/toolset_report are always available (also via catalog). Use this first when unsure which verb to use (e.g., playlist vs snapshot vs search). |
| inspect_toolA | Show one tool's full description and input schema before calling it |
| toolset_reportA | Report the active toolsets and registration modules, plus the live registered tool count — answers "how much surface is exposed right now". Discovery set; always available. Also see find_tool / inspect_tool. |
| expand_mood_to_queriesA | Turn a free-text listening mood into search terms (genres, keywords, terms to avoid) for search. Uses the host model via MCP sampling when the host advertises the sampling capability; otherwise returns a built-in static map. Calls no Spotify endpoint and changes nothing. A model reply that cannot be parsed returns isError after one retry — it is never replaced by a guess. |
| save_discover_weeklyA | Archive your Discover Weekly into a regular playlist (creates or overwrites the archive). Resolves Discover Weekly via /me/playlists exact match first, falling back to search (unverified); dry_run previews; idempotent if archive already matches. Result echoes source identity (owner, url, verified). |
| save_release_radarA | Archive your Release Radar into a regular playlist (creates or overwrites the archive). Resolves Release Radar via /me/playlists exact match first, falling back to search (unverified); dry_run previews; idempotent if archive already matches. Result echoes source identity (owner, url, verified). |
| export_library_jsonA | Export your full library (saved tracks, albums, shows, episodes, audiobooks) to a local directory as JSON or CSV sidecar files. Respects SPOTIFY_MCP_FETCH_ALL_CAP per type; when capped, reports cap_reached + truncated and a prose footer ("first N of … — raise SPOTIFY_MCP_FETCH_ALL_CAP"). |
| export_followed_artistsA | Export your followed artists to a local directory as JSON or CSV. Fields: uri, name, genres. The file's exported_at is the export time, not a per-artist follow date (Spotify does not expose followed_at). |
| export_profile_stateA | Export local sidecar stores (scenes, genre-tags, playback-ext, search-history, mutations, artist-watchlist) to a single schema-versioned JSON archive. artist-watchlist is ~/.spotify-mcp/artist-watchlist.json; SPOTIFY_MCP_DATA_DIR overrides that directory. |
| import_profile_stateA | Restore local sidecar stores from a profile-state archive. merge unions each store on its own keys and de-duplicates search_history by entry id; overwrite replaces each store, keeping a 0600 .bak of what it replaced. The mutation ledger (stores.mutations_history) is export-only: it is never appended to or replaced, and an archive carrying it is reported as skipped. dry_run reports the per-store plan and writes nothing. Refuses newer schema versions. |
| export_listening_historyA | Export your listening history (recently played) to a JSON or CSV sidecar by walking /me/player/recently-played with before-cursor pagination. Respects SPOTIFY_MCP_FETCH_ALL_CAP; writes file 0600 and reports path + counts. |
| export_all_playlistsA | Export every owned (or all) playlist with metadata + items to a sidecar file. Quota: GET /me/playlists + N×GET /playlists/{id}/items; capped by fetchAllCap. |
| library_snapshot_diffA | Diff two sidecar JSON files (library.json or playlists.json): added/removed counts + samples. Quota: local only (no API). |
| history_searchA | Search portability, backup and mutation-history stores; hits name their source. Quota: local only (no API). |
| import_from_sidecarA | Additive restore from a library.json sidecar written by export_library_json: re-adds every missing saved item across all five collections (tracks, albums, shows, episodes, audiobooks) through the unified PUT /me/library endpoint, skipping items the library already holds. Rows whose uri is not a canonical spotify::<22-char id> URI are counted as invalid and never sent. Collections the file does not carry are named in absent_keys. The exporter's own truncated / cap_reached flags are surfaced as sidecar_truncated + truncated_collections: a capped sidecar restores only the rows it holds and says so, and a file with no flag reports completeness as UNKNOWN rather than complete. The prompt and the result both state the sidecar path, the date the file itself declares (exported_at, or the named reason it declares none — never the mtime), the row count, the one use being made of it, and whether a human confirmed that use (see consent_note). dry_run=true by default. Quota: local read + contains-check + chunked writes when dry_run=false. |
| library_genre_reportA | Aggregate your saved library by user-declared genre tags. Scans all saved tracks and albums, joins each item's artists against your tag sidecar (see tag_management), and reports per-genre track/album counts plus the contributing artists. Genres are unavailable from Spotify itself, so only artists you have tagged appear here. |
| filter_by_genreA | List URIs of your saved tracks or albums whose artists carry a given genre tag (case-insensitive tag-name match against your sidecar). Output is directly usable as create_playlist / add_to_playlist input. Read-only. |
| tag_managementA | Declare or retract genre tags for an artist in your local sidecar (~/.spotify-mcp/genre-tags.json), which library_genre_report and filter_by_genre consume. add requires at least one tag; remove drops the listed tags, or the artist entirely when none are listed. Supports dry_run preview. |
| library_hygieneA | Read-only album hygiene over your liked tracks: flags near-complete albums worth saving and lone singles with nothing else liked from their artist (low confidence). Album totals come from the /me/tracks walk; a width-bounded per-id GET /albums/{id} fills in the rest. Never mutates. dry_run previews cost. |
| show_new_episodesA | Find new episodes across your saved podcast shows: reports episodes released within the lookback window (default 7 days), marking which are already saved in your episode library. Fetches /me/shows then each show's latest episodes, overlapping episode lookups (width: SPOTIFY_MCP_MAX_CONCURRENCY, else SPOTIFY_MCP_FANOUT_CONCURRENCY, else 4) so a 25-show scan is not 25 serial round trips. WARNING: M saved shows → M+1 requests (1 show page + M episode lookups). Use max_shows to budget and cost_preview to see the cost without making any calls. This tool is read-only: nothing is ever changed. |
| find_duplicate_saved_tracksA | Read-only duplicate detection over your saved (liked) tracks. Exact groups require the same non-null ISRC and album id with duration within ±2s, and keep the oldest dated save. On opt-in, near-duplicate groups show same-title/artist tracks whose ISRC, release, or duration differs; these are distinct saved tracks and are review-only with no removals. Undated saves sort after dated saves. Optionally pass a playlist_id to cross-reference which group members also appear in that playlist. Never mutates your library. |
| plan_podcast_sessionA | Greedy-pack your saved podcast episodes into a listening session of a given length. Episodes play their remaining time (duration minus resume position); fully played ones are skipped. Scanning stops at the first unplayed episode that doesn't fit. |
| start_podcast_sessionA | Plan a podcast session (see plan_podcast_session) and start it on a device. Limitation: Spotify cannot apply resume offsets when queueing — only the first episode can start at its resume point (via PUT /me/player/play on its show context); later episodes are appended to the queue and play from the beginning. With dry_run, nothing is played or queued. |
| backup_firstA | Create a pre-flight library snapshot before a destructive operation. Returns snapshot file path and counts for later restore. Read-only against Spotify. |
| backup_libraryA | Snapshot your ENTIRE library to a local JSON file (read-only against Spotify): liked tracks, saved albums/shows/episodes/audiobooks, followed artists, and every playlist with its items. Walks capped at SPOTIFY_MCP_FETCH_ALL_CAP (500/category) and each playlist at SPOTIFY_MCP_PLAYLIST_ITEMS_CAP (500); the smaller wins, so a full backup of a large playlist needs both raised. Files land in SPOTIFY_MCP_BACKUP_DIR (default ~/.spotify-mcp/backups), mode 0600. |
| list_backupsB | List complete and partial library backups (newest first) using bounded metadata sidecar reads, with a bounded prefix fallback for legacy snapshots. Expires snapshots past SPOTIFY_MCP_BACKUP_RETENTION_DAYS (default 30, 0 disables) and reports what it removed, plus the store envelope (dir_bytes, oldest_created, oldest_retention_until). |
| delete_backupA | Delete one library backup file (and its metadata sidecar) from SPOTIFY_MCP_BACKUP_DIR. Irreversible — the library rows in the file cannot be recovered from anywhere else. Destructive and confirmation-gated: dry_run defaults to true, and executing is refused when the client cannot prompt (SPOTIFY_MCP_CONFIRM=never bypasses). Paths outside the backup directory are refused. |
| clean_backup_artifactsA | Delete the NON-library files sharing SPOTIFY_MCP_BACKUP_DIR with your library backups: closed listening sessions ( |
| restore_library_snapshotA | STRICTLY ADDITIVE restore of a library snapshot written by backup_library (see list_backups). Adds only what is missing: saves absent tracks/albums/shows/episodes/audiobooks, follows unfollowed artists, and creates NEW playlists named 'Restored · ()' — existing playlists are never touched and nothing is deleted, renamed, or overwritten. Truncated snapshots are previewable but refused before confirmation or writes; quota-hit, contentless or wrong-schema_version snapshots are refused outright. Prompt and result state where the stored data came from, the date the FILE declares (naming the reason when absent — never the mtime), the item count, and the single use made of it; dry_run defaults to TRUE (read-only preview); setting dry_run=false requires explicit confirmation before any write, fails closed when elicitation is unavailable or errors, and allows writes when SPOTIFY_MCP_CONFIRM=never. |
| undo_mutationA | Undo a specific mutation by receipt ID. Inverts the recorded direction: an add/save is undone by removing, a removal by re-adding (playlist items or library). A playlist add undo removes only the rows it created, refusing when no row positions are recorded. The result reflects the refetched post-state. Non-reversible kinds return not reversible. Executing needs confirmation and is refused when the client cannot prompt (SPOTIFY_MCP_CONFIRM=never bypasses). |
| undo_last_mutationA | Undo the most recent reversible mutation (receipt FIFO). Same inversion semantics as undo_mutation: add/save → remove, removal → re-add. Executing needs confirmation and is refused when the client cannot prompt (SPOTIFY_MCP_CONFIRM=never bypasses). |
| verify_receiptA | Verify that a previous mutation actually landed on Spotify by looking up its receipt. Receipts are session-scoped: the 100 most recent mutations, in this process only, and lost on restart unless SPOTIFY_MCP_RECEIPTS is set. An unknown or expired id returns isError with found:false — a fact about the lookup, not about the mutation. |
| whats_newA | Personal new-releases radar: derive what's new from followed artists (new albums/singles) and saved shows (new podcast episodes), replacing the removed browse/new-releases surface. WARNING: the follow list is paged 50 artists per request, so on a COLD read cache the album leg costs ceil(max_artists/50) follow pages PLUS up to max_artists album lookups (#679); a repeat scan inside the cache window re-probes the same canonical request and spends no request (#900). A large library can still exhaust small dev-account quotas in one call. Use max_artists to budget and dry_run to preview the cost before running; dry_run labels its figure a budget bound (it is arithmetic over the budget, not a measurement) and a real call reports the request count it actually issued in |
| search_deepA | Paged catalog search that walks past the API limit of 10 results per type. Fetches up to 5 pages of 10 results per requested type server-side from a caller-supplied |
| save_sceneA | Save a named playback scene (device + volume + shuffle/repeat + optional context) to the local sidecar (~/.spotify-mcp/scenes.json) |
| list_scenesB | List saved playback scenes from the local sidecar |
| delete_sceneA | Delete a saved playback scene from the local sidecar |
| apply_sceneA | Apply a saved scene: resolve its device hint, transfer playback, then set volume/shuffle/repeat and start the saved context (in that order; missing targets are skipped) |
| schedule_wind_downA | Preview or start a volume wind-down: absolute-minute volume steps, then pause. Set dry_run to preview without replacing or cancelling an active timer. |
| wind_down_statusA | Read the current or most recent wind-down progress, including per-step failures |
| cancel_wind_downA | Cancel the in-process wind-down ramp, if one is running |
| grow_playlistA | Propose tracks to grow one of your playlists using ONLY your own listening data (no recommendations): finds tracks appearing in >=2 of your OTHER playlists, boosts ones sharing an artist with the target playlist, excludes tracks already in it (and optionally your saved library), and returns top candidates with evidence. Read-only: review the proposals, then call add_to_playlist with the URIs you want. |
| export_playlistA | Export a playlist's full item list as an M3U playlist file or a CSV spreadsheet. Pages every item; pass output_path to write a file (created with mode 0600) inside the configured output root, or omit it to get the document inline. CSV cells are formula-safe. |
| import_playlistA | Parse an M3U or CSV document (the inverse of export_playlist) and append its Spotify URIs to a target playlist. Pass the document inline via content, or read it from input_path (a regular file inside the configured read roots). URIs already in the playlist are skipped, so a re-run adds nothing. Adds in batches of 100. Use dry_run=true to preview without writing. Result records the document source and the use made of it (consent_note); under 100 new URIs nothing is gated. |
| create_smart_playlistA | Create a playlist from rules over your own listening data: top tracks (by time range), recently played, or saved tracks — with optional artist-name filtering and a one-track-per-artist toggle. When source=saved_tracks the pool is the newest N saved tracks (N=scan_cap, default fetchAllCap=500) and truncation is reported. No deprecated recommendations endpoints involved. Every source has a pool ceiling (top_tracks 100, recently_played 50, saved_tracks scan_cap), reported as pool_capped with pool_cap — a capped pool is a floor, not a complete scan. dry_run previews the exact track list without creating anything. |
| search_within_playlistA | Text search inside a single playlist: a client-side filter over the rows the walk read, cheaper than paging get_playlist_items yourself for a narrow query — but it can only see that window, so scanned_items/scan_cap/scan_truncated say how much of the playlist was actually read. Matches item name, artist, album and show name; kind narrows a mixed playlist to tracks only or episodes only. Quota: GET /playlists/{id}/items paged. |
| search_history_statsB | Analytics over the local search-history sidecar: top queries, type breakdown, recency. Quota: local only (no API). |
| audiobook_progressA | Audiobook progress rollup: chapters total, played count, current chapter, percent complete. Walks every chapter page up to the shared fetch-all cap and reports scan coverage. Quota: 1 audiobook GET + chapter page GETs. |
| unsave_orphan_tracksA | Find saved tracks that appear in no playlist (orphans) and optionally unsave them. Quota: walks library + all playlists (capped). PREVIEWS BY DEFAULT — pass dry_run=false to commit. Removing 10+ orphans additionally requires elicitation confirmation (or SPOTIFY_MCP_CONFIRM=never for automation). |
| playlist_to_libraryA | Save all tracks of a playlist to your Liked Songs (library). Quota: GET playlist items + PUT /me/tracks (chunked 50). |
| followed_playlists_auditB | Inventory of followed vs owned playlists: counts, collab, public, follower totals. Quota: GET /me/playlists paged. |
| get_playlist_added_datesB | List when each track was added to a playlist (added_at + added_by). Quota: GET /playlists/{id}/items paged. |
| split_playlistB | Split a playlist into N chunks (new playlists). Quota: GET all + N POST /me/playlists + N POST items. |
| find_duplicate_tracks_across_playlistsA | Find tracks that appear in more than one of the given playlists (cross-playlist dupes). Quota: N GETs (one per playlist). |
| remove_from_library_by_playlistA | Remove from Liked Songs any tracks that also appear in a given playlist. Quota: 2 GETs + DELETE (chunked). PREVIEWS BY DEFAULT — pass dry_run=false to commit. Removing 10+ saved tracks additionally requires elicitation confirmation (or SPOTIFY_MCP_CONFIRM=never for automation). |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| dj | Act as a DJ. Based on my top artists and current mood, queue up a set of songs. |
| playlist_from_mood | Create a playlist for a given mood. Searches for tracks and adds them to a new playlist. |
| music_taste_summary | Summarize the user's music taste based on their top tracks and artists. |
| discover_weekly_alternative | Based on my top tracks and recently played songs, find lesser-known songs I probably haven't heard. |
| playlist_audit | Audit a playlist for duplicate tracks and unplayable ('dead') entries, with cleanup suggestions. |
| listening_recap | Write a recap of recent listening: top tracks/artists plus recently-played context. |
| migrate_library | Collect tracks from your saved albums into a single playlist. |
| podcast_catchup | List new podcast episodes published recently across your saved shows, and queue them if asked. |
| artist_deep_dive | Tour an artist's discography: profile, albums, standout tracks. |
| music_briefing | Daily or weekly music briefing: new podcast episodes, new releases from followed artists, catalog freshness, and recently played — composed from radar tools. |
| morning_briefing | Morning briefing: new releases + listening streak + top track. |
| weekly_digest | Weekly digest: taste shift + streaks + recommendations. |
| crate_digging | Crate dig deep cuts for your top artists. |
| triage_liked_songs | Triage your Liked Songs backlog into era- or genre-bucket playlists. |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| me | Current user profile ('?format=json' returns the raw API object) |
| player-state | Current Spotify playback state (live; '?format=json' returns the raw API object) Subscribable: a notifications/resources/updated fires only when the current item, play/pause, shuffle, repeat or active device changes. A re-check that finds no change sends nothing, and a failed read sends nothing either. |
| player-queue | Current playback queue ('?format=json' returns the raw API object) Subscribable: a notifications/resources/updated fires only when the currently-playing item or the ordered queue changes. A re-check that finds no change sends nothing, and a failed read sends nothing either. |
| player-devices | Available Spotify Connect devices, with the active one flagged (live; '?format=json' returns the raw API object) |
| top-tracks | User's top tracks ('?format=json' returns the raw API object) Parameters: ?time_range (long_term | medium_term | short_term, default medium_term; any other value reads medium_term), ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0). |
| top-artists | User's top artists ('?format=json' returns the raw API object) Parameters: ?time_range (long_term | medium_term | short_term, default medium_term; any other value reads medium_term), ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0). |
| recently-played | Recently played tracks ('?format=json' returns the raw API object) Parameters: ?limit (1-50, default 20; values outside the range are clamped), ?after (Unix epoch milliseconds; page further back), ?before (Unix epoch milliseconds; page forward). Subscribable: a notifications/resources/updated fires when the recently-played page changes — a new play, a new window. A re-check that finds no change sends nothing, and a failed read sends nothing either. |
| playlists | All user playlists, names and IDs ('?format=json' returns the raw items) |
| saved-albums | Albums saved in your library Parameters: ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0). |
| saved-shows | Podcast shows saved in your library Parameters: ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0). |
| saved-episodes | Podcast episodes saved in your library Parameters: ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0). |
| saved-tracks | Tracks saved in your library, paginated via ?offset&limit ('?format=json' returns raw paged object) Parameters: ?offset (zero-based, default 0), ?limit (1-50, default 20; values outside the range are clamped). |
| followed-artists | Artists you follow ('?format=json' returns the raw items) |
| saved-audiobooks | Audiobooks saved in your library ('?format=json' returns the raw items) Parameters: ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0). |
| listening-history | Recent listening history (last 20, live; '?format=json' returns raw API object) |
| genre-heatmap | Genre counts over a live sample of your top artists (medium term, up to 50 — not your followed artists); the rendered output names the source and how many artists were read ('?format=json' returns the counts with their coverage) |
| rate-limit | Last rate-limit event: Retry-After/wait or 'never throttled' Subscribable: a notifications/resources/updated fires only on a NEW throttle event. The request counters and the cooldown countdown advance continuously and are not change events — re-read the resource for those. |
TDQS
Scored across 129 tools
129 tools with massive overlap: at least four search variants (search, search_deep, search_fresh, search_by_isrc) plus wow search helpers; five duplicate detectors (find_duplicates_in_playlist, find_duplicate_saved_tracks, find_duplicate_tracks_across_playlists, clean_all_playlists, remove_duplicate_playlist_items); multiple playlist export/import/backup tools (export_playlist, export_all_playlists, import_playlist, backup_library, backup_first, import_from_sidecar / backup_library / restore_library_snapshot, backup_library / export_library_json, backup_library / export_profile_state); get_now_playing vs get_currently_playing; follow_playlist vs pin_playlist (deprecated); follow/unfollow vs check_following_artists vs check_in_library. The descriptions do contain decision guides and cross-references, which helps, but the volume of overlapping purpose makes misselection highly probable.
Names are overwhelmingly snake_case verb_noun (get_devices, create_playlist, remove_from_library, add_to_queue), with occasional noun_verb constructions (playlist_shuffle, library_genre_report) but consistently readable. No camelCase mixing except the deprecated processV2-style naming is absent. One or two oddballs (whats_new, wind_down_status, spotify_doctor) but not enough to break the pattern.
129 tools for a music/podcast control server vastly exceeds a manageable surface. Even accounting for discovery helpers (find_tool, inspect_tool, toolset_report), the set is huge and many tools have narrow, heavily qualified purposes, making it effectively unusable without a meta-discovery layer. This is an extreme count mismatch.
The surface covers core playback, library CRUD, playlist manipulation, podcast/audiobook, search, export/import/backup, and account management extremely thoroughly. Notable gaps are around artist follow/unfollow (explicitly unavailable in API) and playlist reordering by drag-and-drop, but those are external limitations or edge cases. For the apparent domain, the coverage is nearly exhaustive.