A Model Context Protocol (MCP) server for ListenBrainz — provides 18 tools to query listening history, statistics, MusicBrainz-enriched metadata, feedback, and popularity data.
- Python 3.12+
- uv (recommended) or pip
- A ListenBrainz account and API token
git clone https://github.com/serjflint/listenbrainz-mcp
cd listenbrainz-mcp
uv syncPlace your ListenBrainz API token in one of:
~/.listenbrainz_token(file, preferred)LISTENBRAINZ_TOKENenvironment variable
- Log in to listenbrainz.org
- Go to Settings → API (or visit https://listenbrainz.org/settings/)
- Copy your User Token
- Save it:
echo "your-token-here" > ~/.listenbrainz_token
Full API documentation: https://listenbrainz.readthedocs.io/en/latest/users/api/index.html
Security: Never commit your token to version control.
# stdio transport (for Roo Code / Claude Desktop)
listenbrainz-mcp --transport stdio
# HTTP transport
listenbrainz-mcp --transport http --port 8000To enable the AI agent to automatically use this MCP server, copy the bundled skill:
mkdir -p ~/.agents/skills/listenbrainz-mcp
cp SKILL.md ~/.agents/skills/listenbrainz-mcp/SKILL.mdThe skill teaches the agent when and how to use each of the 18 tools.
Add to your MCP settings:
{
"listenbrainz": {
"type": "stdio",
"command": "/path/to/.venv/bin/listenbrainz-mcp",
"args": ["--transport", "stdio"]
}
}get_listens— Get recent listens with optional count and time range filtersget_listens_for_period— Get all listens within a time period with automatic pagination (default: last 7 days)get_playing_now— Get the currently playing trackget_listen_count— Get total listen count
get_top_artists— Get top artists by listen countget_top_recordings— Get top recordings/tracksget_top_releases— Get top releases/albumsget_top_release_groups— Get top release groupsget_listening_activity— Get listening activity over time
get_recording_metadata— Get MusicBrainz metadata for recordings (by MBID)get_artist_metadata— Get MusicBrainz metadata for artists (by MBID)get_release_group_metadata— Get MusicBrainz metadata for release groups (by MBID)lookup_recording_by_name— Look up a recording by artist and track name
get_feedback— Get loved/hated tracks
get_recording_popularity— Get popularity data for recordingsget_artist_popularity— Get popularity data for artistsget_top_recordings_for_artist— Get top recordings for an artistget_top_release_groups_for_artist— Get top release groups for an artist
make install # Install dependencies (including dev)
make test # Run pytest tests
make lint # Run ruff, ty, mypy, pyrefly
make format # Format code with ruff
make check # Run format + lint + testsrc/listenbrainz_mcp/
├── __main__.py # CLI entry point (--transport stdio|http)
├── client.py # Async HTTP client, token resolution
├── models/ # msgspec.Struct models grouped by tool
│ ├── __init__.py
│ ├── shared.py # Common enums and nested structs
│ ├── listens.py # Listen, ListenCount, TrackMetadata
│ ├── stats.py # StatsArtist, StatsRecording, ListeningActivity
│ ├── metadata.py # MetadataIncParams, MetadataRecording
│ ├── feedback.py # Feedback, FeedbackPayload
│ ├── popularity.py # PopularityRecording, PopularityArtist
│ ├── similar.py # SimilarArtist, SimilarRecording
│ └── utility.py # User, Tag, TagList
├── retry.py # Exponential backoff decorator (tenacity)
├── server.py # FastMCP server with 18 registered tools
└── tools/
├── listens.py # get_listens, get_listens_for_period, get_playing_now, get_listen_count
├── stats.py # get_top_artists, get_top_recordings, get_top_releases, get_top_release_groups, get_listening_activity
├── metadata.py # get_recording_metadata, get_artist_metadata, get_release_group_metadata, lookup_recording_by_name
├── feedback.py # get_feedback
└── popularity.py # get_recording_popularity, get_artist_popularity, get_top_recordings_for_artist, get_top_release_groups_for_artist
Models are auto-generated from the ListenBrainz OpenAPI spec using datamodel-code-generator.
# Ensure dependencies are installed
uv sync --extra dev
# Generate all models from OpenAPI spec
.venv/bin/datamodel-codegen \
--input ~/workspace/listenbrainz-openapi/lb.yaml \
--input-file-type openapi \
--output-model-type msgspec.Struct \
--target-python-version 3.12 \
--use-union-operator \
--use-generic-container-types \
--use-field-description \
--openapi-scopes schemas paths \
--use-operation-id-as-name \
--snake-case-field \
--reuse-model \
--output src/listenbrainz_mcp/models/generated.py \
--formatters ruff-format| Option | Description |
|---|---|
--openapi-scopes schemas paths |
Generate from both schemas and path operations |
--use-operation-id-as-name |
Use operationId for class names (cleaner names) |
--snake-case-field |
Convert field names to snake_case |
--reuse-model |
Reuse identical model definitions |
If a component YAML only has properties at the top level, add type: object:
# Before (invalid)
properties:
field1:
type: string
# After (valid)
type: object
properties:
field1:
type: stringThe generated generated.py can be split into separate module files using scripts/split_models.py based on the research_class_mapping.json mapping.
# Regenerate and split models
uv run datamodel-codegen \
--input ~/workspace/listenbrainz-openapi/lb.yaml \
--input-file-type openapi \
--output-model-type msgspec.Struct \
--target-python-version 3.12 \
--use-union-operator \
--use-generic-container-types \
--use-field-description \
--openapi-scopes schemas paths \
--use-operation-id-as-name \
--snake-case-field \
--reuse-model \
--output src/listenbrainz_mcp/models/generated.py
# Split into modules
python3 scripts/split_models.py
# Format and fix imports
ruff format src/listenbrainz_mcp/models/gen/
ruff check --fix src/listenbrainz_mcp/models/gen/The mapping file (research_class_mapping.json) contains:
tags: OpenAPI tag groups with paths and class listspaths: API endpoint to tag mappingclasses: Class name to module mappingmodules: Tag to module name mapping
Classes are distributed across modules: shared, stats, listens, recommendations, utility, playlists, feedback, popularity, metadata, social.
When migrating tools from hand-made models to generated models, keep in mind:
-
Query parameters are not generated: datamodel-code-generator only generates response schemas, not request/parameter schemas. Use
TypedDictfor query parameters:class FeedbackQueryParams(TypedDict): count: int offset: int score: int | None metadata: str | None
-
Check for
msgspec.UNSETinstead ofNone: Generated models usemsgspec.UnsetTypefor optional fields. Check withis not msgspec.UNSETinstead ofis not None:feedback_list = feedback_payload.feedback if feedback_list is not msgspec.UNSET: return list(feedback_list) return []
-
Return types use
Sequence: Generated models often useSequence[T]instead oflist[T]. Convert as needed when returning to MCP.
This MCP server is designed to work well with:
-
Web Scrobbler — browser extension that automatically scrobbles music from YouTube, Яндекс.Музыка, Spotify, and 400+ other services to ListenBrainz (and Last.fm). The listening data retrieved by this MCP server is typically submitted via Web Scrobbler.
-
yandex-music-mcp — MCP server for Яндекс.Музыка that inspired this project. Combine both servers to query your Яндекс.Музыка library and cross-reference it with your ListenBrainz listening history.
The goal is to coordinate these tools with the ListenBrainz ecosystem to provide a seamless AI-assisted music listening experience.
This project interacts with the ListenBrainz service. The ListenBrainz server is open source and available at github.com/metabrainz/listenbrainz-server under the GNU General Public License v2.0.
This MCP client communicates with the ListenBrainz public REST API over HTTP. No source code from listenbrainz-server was copied or derived. As a standalone client that only uses the network API, this project is independently licensed under the MIT License.
MIT