setlist-mcp
MCP server for setlist.fm — search concert setlists, artists, venues, and tours via natural language. Read-only (setlist.fm has no write API).
Setup
Option A — npx (recommended)
Add to .mcp.json in your project or ~/.claude/mcp.json:
{
"mcpServers": {
"setlist": {
"command": "npx",
"args": ["-y", "setlist-mcp"],
"env": {
"SETLIST_API_KEY": "your-api-key-here"
}
}
}
}
Option B — from source
git clone https://github.com/chrischall/setlist-mcp
cd setlist-mcp
npm install && npm run build
Then add to .mcp.json:
{
"mcpServers": {
"setlist": {
"command": "node",
"args": ["/path/to/setlist-mcp/dist/index.js"],
"env": {
"SETLIST_API_KEY": "your-api-key-here"
}
}
}
}
Or use a .env file in the project directory with SETLIST_API_KEY=<value>.
Getting your API key
Apply for a free API key (non-commercial use) at setlist.fm/settings/api — you'll need a setlist.fm account. The key is sent as the x-api-key header on every request.
Optional: set SETLIST_ACCEPT_LANGUAGE (one of en, es, fr, de, pt, tr, it, pl) to localize city/country names.
Tools
All tools are prefixed setlist_. Everything below is read-only; the two
attendance tools (setlist_mark_attended / setlist_unmark_attended) write to
your setlist.fm account. Each undoes the other, so neither asks for a
confirmation step.
Artists
setlist_search_artists— find artists byartistNameorartistMbid; returns each artist's MusicBrainz ID (mbid).setlist_get_artist— get an artist bymbid.setlist_get_artist_setlists— an artist's setlists (most recent first), bymbid, paginated viap.
Setlists
setlist_search_setlists— search by any mix of artist, venue, city, country, tour,date(ISO yyyy-MM-dd), oryear.setlist_get_setlist— a setlist (with full song list) bysetlistId.setlist_get_setlist_version— a specific historical version byversionId.
Batch
setlist_resolve_concerts— resolve up to 24{artist, date, city?, venue?}to their best-match setlists in one call (withsongCount/hasSongs+ a{matched, stubs, tourReferenced, unmatched, pending, errored}summary). When a show is an empty stub but the act toured a repeating set, the result also includes atourReference— a populated, representative setlist from the same tour on a nearby date (withsongs+ its ownurl), clearly labeled as a reference, not the exact show (passtourFallback: falseto skip). Calls are paced to setlist.fm's rate limit; if a batch can't finish in time the rest come backpending: true— re-call with just those. A concert whose lookup fails upstream comes back withmatch: nulland anerror(counted inerrored) without sinking the batch. For more than 24 shows, chunk into batches of ≤24.
Venues
setlist_search_venues— find venues bynameand/or location.setlist_get_venue— get a venue byvenueId.setlist_get_venue_setlists— setlists performed at a venue, paginated viap.
Cities & countries
setlist_search_cities— find cities byname/location; returns each city'sgeoId.setlist_get_city— get a city bygeoId.setlist_search_countries— list all supported countries and their codes.
Users
setlist_get_user— a user's public profile byuserId.setlist_get_user_attended— concerts a user marked as attended.setlist_get_user_edited— setlists a user has created or edited.
Utility
setlist_healthcheck— verify the API key works and the API is reachable.
Response shape (view)
Fifteen tools take view: "compact" | "full" — every search and every getter
above, from setlist_search_artists through setlist_get_user_edited — and
compact is the default. The slim rung arrives without being asked for,
because an efficiency a caller has to know about and request is one that
usually is not requested.
Compact strips image and avatar URLs, and claims no field projection. These tools hand setlist.fm's payload back close to verbatim, and this repo holds no captured fixture or documented field list for those endpoints — so nothing here can honestly say which of setlist.fm's fields matter and which are noise. Stripping media needs no such knowledge and is subtractive, so it cannot lose a field nobody knew about; an invented field list would risk returning a record with holes in it that still reads like a verified answer. Expect the same record minus the picture URLs, not a named field set.
Note what this does not touch: a setlist's url is not a media URL, and it
is the link setlist.fm's API terms require you to show — it survives BOTH rungs,
and tests/view.test.ts pins that. Compact is not a reason to drop attribution.
In practice that leaves compact with little to remove: none of the setlist.fm payloads captured in this repo carry a media key at all, so today the two rungs are usually the same bytes. The rung is there so that a field arriving later with a picture in it is stripped by default, rather than after somebody notices.
view: "full" returns setlist.fm's payload untouched. There is no raw
rung: full already IS the untouched payload, so a third value could only
alias it.
The other five tools take no view, each for its own reason:
setlist_resolve_concertsalready answers in its own shape. It builds the{matched, stubs, tourReferenced, unmatched, pending, errored}summary and the per-show rows itself, unconditionally — that projection IS the tool's output, not a slimmed copy of an upstream payload, so there is no fatter version to offer. Aviewhere would be a parameter that decides nothing.setlist_id_from_urlparses an id out of a URL string. No network call, no payload, nothing to project.setlist_mark_attendedandsetlist_unmark_attendedare writes. A write's response is a receipt — a status, an id — with nothing to strip and everything to keep.setlist_healthcheckanswers whether the API key works and the API is reachable.
Typical flows
- "What did Radiohead play at their last show?" →
setlist_search_artists(Radiohead → mbid) →setlist_get_artist_setlists(latest) →setlist_get_setlistfor the song list. - "Setlists at Red Rocks in 2023" →
setlist_search_venues(Red Rocks → venueId) →setlist_search_setlistswithvenueId+year: 2023. - "Phish on 2023-08-07" →
setlist_search_setlistswithartistName: "Phish",date: "2023-08-07".
Attribution & API terms
setlist.fm's API terms bind anyone using this data. When you present setlist.fm results to a user:
- Always cite the source. Each setlist/artist/venue result includes a
url— show it as a real, clickable link to setlist.fm (e.g. "Source: The Beatles setlist on setlist.fm"). The terms require a followable link — never anofollow. If a particular result has nourl, link to https://www.setlist.fm instead. - Non-commercial use only. A free API key covers non-commercial use; commercial use needs setlist.fm's permission.
- Live data, not a datastore. The terms forbid persistent caching — this server fetches fresh on every call and you should treat results as point-in-time, not build a local copy.
- Don't expose or share the API key. It's read from
SETLIST_API_KEYand never appears in results.
Interpreting setlist data
- Songs live in
sets.set[]; each set may have anencorenumber (1 = first encore) and aname(e.g. an acoustic set or a full album). - Each
songmay carry:tape: true(a pre-recorded intro/outro — not actually performed live),cover(the original artist when it's a cover),with(a guest performer), andinfo(a note like "acoustic" or "first time live"). Surface these when relevant rather than dropping them.
Notes
- Stub setlists: every setlist result carries
songCount/setCount/hasSongs. A page can exist with no songs logged (hasSongs: false) — skip those without a secondget_setlistcall. - Disambiguate by location:
artistName+datecan return shows in multiple cities. AddcityName/cityIdorvenueName/venueIdto pin the right one (e.g. TSO on a date plays both Charlotte and Orlando). - All performers at a venue/festival on a day: call
setlist_search_setlistswithvenueName(orvenueId) +dateand no artist. - IDs chain:
search_*tools return thembid/setlistId/venueId/geoIdyou feed into theget_*tools. - All dates are ISO
yyyy-MM-dd— both thedate/lastUpdatedinputs and everyeventDatein the output. (The server translates to/from setlist.fm's nativedd-MM-yyyyinternally.) - Results are paginated; pass
p(1-based) to page through large result sets. - setlist.fm rate-limits the standard tier (~2 req/sec); a 429 is retried once.
