Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
SPOTIFY_CLIENT_IDYesSpotify Client ID from your Spotify app. Required.
SPOTIFY_MCP_HISTORYNoSet to '1' to log mutations to JSONL for undo.
SPOTIFY_MCP_READONLYNoSet to '1' to hide every write tool (read-only mode).
SPOTIFY_MCP_TOOLSETSNoComma-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

CapabilityDetails
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

NameDescription
searchA

Search Spotify's catalog for tracks, artists, albums, playlists, shows, episodes, or audiobooks. Pass types as an array (e.g. ["artist"]) to search a single kind — no track/album fallback noise. Decision guide: search (general, ≤10/type), search_deep (paginated fetch_all up to 50/type), search_fresh (tag:new last 2 weeks), search_by_isrc (exact ISRC), whats_new (personal radar from follows).

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. volume_percent sets an absolute level; delta_step nudges the current level by a signed step. op picks the variant: "mute" drops to 0 and remembers the level, "unmute" restores what mute remembered, "preset" applies the per-device presets stored by set_device_volume_preset, and "level" with no volume_percent copies the active device's level to the others. Quota: 1 write for a single device; 1 read + N writes when fanning out.

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; preserve_position: true carries the current track and play position across instead, and restore_shuffle_repeat: true re-applies the current shuffle and repeat modes on the target. Quota: 1 read to resolve the device, then 1–4 writes depending on the flags.

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 followers field was separately removed from user profiles, so no follower count is reported

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. match_by selects the rule (uri — the same track object twice; name_artist — same name and credited artists, catching relinks and remasters; name — same title only) and the rule is echoed back as match_by, so a group count is never unattributable. See the duplicate-matching vocabulary in SPEC section 4.

remove_duplicate_playlist_itemsA

Remove duplicate items from a playlist: keeps the first occurrence of each track and removes later repeats, under the match_by rule the other duplicate tools use — uri (default, exact repeats), name_artist (same name and credited artists, catching remasters and relinks under a new URI) or name (same title only). The rule applied is echoed as match_by. Supports dry_run; removals of 10+ items ask for confirmation via elicitation. See the duplicate-matching vocabulary in SPEC section 4.

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 (listening-session-*.json), playlist write pre-images (playlistops-pre-*.json) and legacy or migrated playback bookmarks (playback-bookmark-*.json). Library backups and their .meta.json sidecars are never touched — use delete_backup, and naming one here is refused. These files are local and gone for good once deleted. Preview by default; executing is confirmation-gated and refused when the client cannot prompt (SPOTIFY_MCP_CONFIRM=never bypasses). Ages are mtimes, and the default window is SPOTIFY_MCP_BACKUP_ARTIFACT_RETENTION_DAYS — separate from the library window. Unrecognised files are listed, not deleted.

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 cost. Decision guide: whats_new for personal follows radar; search_fresh for query-scoped tag:new, search/search_deep for general catalog, search_by_isrc for ISRC-exact.

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 offset, dedupes by id, and returns compact rows. Type walks overlap (width: SPOTIFY_MCP_MAX_CONCURRENCY, else SPOTIFY_MCP_FANOUT_CONCURRENCY, else 4); same request count. Decision guide: use search_deep when you need >10 results/type or a later window; otherwise use search (single page), search_fresh (new releases), search_by_isrc (ISRC-exact), whats_new (personal follows).

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

NameDescription
djAct as a DJ. Based on my top artists and current mood, queue up a set of songs.
playlist_from_moodCreate a playlist for a given mood. Searches for tracks and adds them to a new playlist.
music_taste_summarySummarize the user's music taste based on their top tracks and artists.
discover_weekly_alternativeBased on my top tracks and recently played songs, find lesser-known songs I probably haven't heard.
playlist_auditAudit a playlist for duplicate tracks and unplayable ('dead') entries, with cleanup suggestions.
listening_recapWrite a recap of recent listening: top tracks/artists plus recently-played context.
migrate_libraryCollect tracks from your saved albums into a single playlist.
podcast_catchupList new podcast episodes published recently across your saved shows, and queue them if asked.
artist_deep_diveTour an artist's discography: profile, albums, standout tracks.
music_briefingDaily or weekly music briefing: new podcast episodes, new releases from followed artists, catalog freshness, and recently played — composed from radar tools.
morning_briefingMorning briefing: new releases + listening streak + top track.
weekly_digestWeekly digest: taste shift + streaks + recommendations.
crate_diggingCrate dig deep cuts for your top artists.
triage_liked_songsTriage your Liked Songs backlog into era- or genre-bucket playlists.

Resources

Contextual data attached and managed by the client

NameDescription
meCurrent user profile ('?format=json' returns the raw API object)
player-stateCurrent 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-queueCurrent 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-devicesAvailable Spotify Connect devices, with the active one flagged (live; '?format=json' returns the raw API object)
top-tracksUser'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-artistsUser'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-playedRecently 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.
playlistsAll user playlists, names and IDs ('?format=json' returns the raw items)
saved-albumsAlbums saved in your library Parameters: ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0).
saved-showsPodcast shows saved in your library Parameters: ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0).
saved-episodesPodcast episodes saved in your library Parameters: ?limit (1-50, default 20; values outside the range are clamped), ?offset (zero-based, default 0).
saved-tracksTracks 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-artistsArtists you follow ('?format=json' returns the raw items)
saved-audiobooksAudiobooks 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-historyRecent listening history (last 20, live; '?format=json' returns raw API object)
genre-heatmapGenre 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-limitLast 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

B3.3/5.0

Scored across 129 tools

Disambiguation2/5

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.

Naming Consistency4/5

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.

Tool Count1/5

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.

Completeness4/5

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.

Maintenance

ActivityActive
ResponsivenessResponsive