lastfm-taste-mcp
A read-only MCP server that lets an AI agent look at your Last.fm listening history: what you played this week, your most-played songs, what you loved, and what other music sounds like it.
The idea: you ask your agent something like "play something like what I've been into lately, but nothing I've heard before". The agent uses these tools to check your taste on Last.fm, picks the music, and then plays it using whatever music tool it has (Spotify, for example). This server only reads data. It doesn't play anything and can't change your Last.fm account.
Spotify / any player --scrobbles--> Last.fm
Agent --MCP (stdio)--> lastfm-taste-mcp --HTTPS + API key--> Last.fm API
Agent --its own music tool--> plays the pick
What you can ask
- "What were my top songs this week?"
- "Have I listened to more Radiohead this month than last month?"
- "Play something similar to my favourite track, but not something I've already played."
- "I want something chill. What do I already like that fits, and what's new?"
- "What genre is this artist?"
The agent decides which tools to call. Each tool returns a short plain-text answer (not raw Last.fm JSON), so a few calls don't fill up the agent's context.
Requirements
- Python 3.11 or newer and uv
- A Last.fm account that is collecting your plays (scrobbling)
- A Last.fm API key (free)
Setup
1. Make sure Last.fm is recording your plays
For Spotify: on last.fm go to Settings → Applications and connect Spotify Scrobbling. Last.fm only sees plays from the moment you connect, so a new account starts empty.
The Spotify connection expires every 6 months. When it does, scrobbling stops without any warning, and the data goes stale. The Applications page shows how many days are left. To renew it, click Disconnect and then Connect.
2. Get an API key
Create an API account at https://www.last.fm/api/account/create. The app name can be anything, and you can leave the callback URL empty. You only need the API key. This server never uses the shared secret.
3. Store the key in an env file
The server reads two environment variables:
| Variable | Required | Meaning |
|---|---|---|
LASTFM_API_KEY |
yes | Your Last.fm API key |
LASTFM_USERNAME |
no | Default user for the user_* tools, used when the agent doesn't name one |
The username is the one in your profile URL (last.fm/user/<name>). It can differ from the
Spotify name shown on Last.fm's settings page.
git clone https://github.com/nurasik14/lastfm-taste-mcp
cd lastfm-taste-mcp
(umask 077; printf 'LASTFM_API_KEY=%s\nLASTFM_USERNAME=%s\n' 'your-api-key' 'your-lastfm-name' > .env)
.env is in .gitignore. The umask 077 makes the file readable only by you.
4. Register it with your MCP client
hermes mcp add lastfm --command uv --args run --directory "$PWD" --env-file "$PWD/.env" lastfm-taste-mcp
hermes mcp test lastfm
--args must come last. Start a new Hermes session afterwards so the tools load. Hermes gives
stdio servers only a filtered environment, so pass the key through --env-file (as above) or
--env. Exporting it in your shell isn't enough.
Other MCP clients (Claude Desktop, Cursor, and others) use a JSON entry like this:
{
"mcpServers": {
"lastfm": {
"command": "uv",
"args": ["run", "--directory", "/path/to/lastfm-taste-mcp", "--env-file", "/path/to/lastfm-taste-mcp/.env", "lastfm-taste-mcp"]
}
}
}
Without cloning, uv can run it straight from GitHub. Your client then has to pass the
variables in an env block:
{
"mcpServers": {
"lastfm": {
"command": "uvx",
"args": ["--from", "git+https://github.com/nurasik14/lastfm-taste-mcp", "lastfm-taste-mcp"],
"env": { "LASTFM_API_KEY": "your-api-key", "LASTFM_USERNAME": "your-lastfm-name" }
}
}
}
The server starts and lists its tools even without a key. Tool calls then return "LASTFM_API_KEY is not set". If you see that after adding the key, restart the client: a server that was already running won't re-read the file.
Tools
All 24 tools are read-only and marked with MCP's readOnlyHint. Most take limit (default
10–20, max 200), and the list-style ones also take page.
Your listening. Every one takes an optional user and falls back to LASTFM_USERNAME.
| Tool | What it returns |
|---|---|
user_get_info |
Total scrobbles, artist/track/album counts, country, signup date |
user_get_recent_tracks |
Individual plays with timestamps, newest first; since / until take ISO dates |
user_get_top_tracks |
Most-played tracks for period = 7day, 1month, 3month, 6month, 12month, overall |
user_get_top_artists |
Most-played artists for a period |
user_get_top_albums |
Most-played albums for a period |
user_get_loved_tracks |
Tracks you hearted, with the date |
user_get_weekly_chart_list |
Past weeks that have charts, with their from / to values |
user_get_weekly_track_chart |
Track chart for one week (latest by default) |
user_get_weekly_artist_chart |
Artist chart for one week |
user_get_weekly_album_chart |
Album chart for one week |
user_get_top_tags |
Tags you applied on Last.fm (not the genre of your music) |
user_get_personal_tags |
Items you tagged with a given tag |
user_get_friends |
Your Last.fm friends |
What music sounds like.
| Tool | What it returns |
|---|---|
track_get_top_tags |
Genre/mood tags listeners gave a track |
artist_get_top_tags |
Tags for an artist (use it when a single track has none) |
album_get_top_tags |
Tags for an album |
track_get_info |
Album, length, global popularity, tags, and your play count and loved status for that track |
Finding music.
| Tool | What it returns |
|---|---|
track_get_similar |
Similar tracks with a 0–1 match score |
artist_get_similar |
Similar artists with a 0–1 match score |
artist_get_top_tracks |
An artist's most popular tracks across all listeners |
tag_get_top_tracks |
Most popular tracks with a tag (e.g. chill, shoegaze) |
tag_get_top_artists |
Most popular artists with a tag |
tag_get_info |
What a tag means and how widely it's used |
tag_get_similar |
Related tags |
Things to know
- Similar-track data is patchy.
track_get_similaroften returns nothing for remixes, covers, and lesser-known tracks.artist_get_similarplusartist_get_top_tracksusually works as a fallback.tag_get_similaris often empty as well. - Only finished listens count. Last.fm scrobbles a track once it has played for half its length or 4 minutes, so skipped songs never show up.
- Results are artist + title, not Spotify links. To play a pick, the agent has to look the song up in its music tool first.
- Errors come back as readable text for the agent: a misspelled name or unknown user (Last.fm error 6), a private profile (17), a rate limit (29), or a bad key (10).
- Rate limits. Last.fm limits how many requests each IP can make and asks clients not to make excessive calls. Every tool call is one API request.
Privacy and security
- Read-only. The server has no Last.fm login, so it can't scrobble, love tracks, or edit your profile.
- The API key is only read from the environment, never from the code. It travels in request URLs (that's how the Last.fm API works), so the server silences httpx's request logging. That log would otherwise write the key into your MCP client's log files.
- Last.fm listening data is public by default: anyone with an API key can read it. If you turn
on "Hide recent listening information" in your Last.fm privacy settings, some
user_*calls may be refused (error 17).
Development
uv sync # create .venv with the runtime and dev dependencies
uv run pytest -q # 25 tests; they use a fake Last.fm, so no network or key is needed
uv run lastfm-taste-mcp # run the server by hand (it speaks JSON-RPC on stdin/stdout)
src/lastfm_taste_mcp/
client.py Last.fm HTTP client, error translation, helpers for Last.fm's JSON quirks
server.py the 24 MCP tools (plain async functions registered with @server.tool)
__init__.py main(): start the server on stdio
tests/
conftest.py FakeLastfm, built on httpx.MockTransport
test_client.py request shape, errors, JSON quirks
test_server.py tool list, reply trimming and errors, via an in-process MCP client
test_stdio_smoke.py starts the real server over stdio, the way a client does
Built with the official MCP Python SDK (v2) and httpx. Data comes from the Last.fm API. This project isn't affiliated with Last.fm.