Skip to content

About

MCP server for ListenBrainz — 18 tools for listening history, stats, metadata, feedback and popularity

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

ListenBrainz MCP Server

A Model Context Protocol (MCP) server for ListenBrainz — provides 18 tools to query listening history, statistics, MusicBrainz-enriched metadata, feedback, and popularity data.

Requirements

  • Python 3.12+
  • uv (recommended) or pip
  • A ListenBrainz account and API token

Installation

git clone https://github.com/serjflint/listenbrainz-mcp
cd listenbrainz-mcp
uv sync

Configuration

Place your ListenBrainz API token in one of:

  • ~/.listenbrainz_token (file, preferred)
  • LISTENBRAINZ_TOKEN environment variable

Getting your API token

  1. Log in to listenbrainz.org
  2. Go to Settings → API (or visit https://listenbrainz.org/settings/)
  3. Copy your User Token
  4. 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.

Usage

# stdio transport (for Roo Code / Claude Desktop)
listenbrainz-mcp --transport stdio

# HTTP transport
listenbrainz-mcp --transport http --port 8000

Installing the agent skill (Roo Code)

To 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.md

The skill teaches the agent when and how to use each of the 18 tools.

Roo Code / Claude Desktop configuration

Add to your MCP settings:

{
  "listenbrainz": {
    "type": "stdio",
    "command": "/path/to/.venv/bin/listenbrainz-mcp",
    "args": ["--transport", "stdio"]
  }
}

Available Tools

Listens (4 tools)

  • get_listens — Get recent listens with optional count and time range filters
  • get_listens_for_period — Get all listens within a time period with automatic pagination (default: last 7 days)
  • get_playing_now — Get the currently playing track
  • get_listen_count — Get total listen count

Statistics (5 tools)

  • get_top_artists — Get top artists by listen count
  • get_top_recordings — Get top recordings/tracks
  • get_top_releases — Get top releases/albums
  • get_top_release_groups — Get top release groups
  • get_listening_activity — Get listening activity over time

Metadata (4 tools)

  • 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

Feedback (1 tool)

  • get_feedback — Get loved/hated tracks

Popularity (4 tools)

  • get_recording_popularity — Get popularity data for recordings
  • get_artist_popularity — Get popularity data for artists
  • get_top_recordings_for_artist — Get top recordings for an artist
  • get_top_release_groups_for_artist — Get top release groups for an artist

Development

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 + test

Project Structure

src/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

Model Generation

Models are auto-generated from the ListenBrainz OpenAPI spec using datamodel-code-generator.

Generating Models

# 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

Key Options

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

Fixing OpenAPI Components

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: string

Splitting Models into Modules

The 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 lists
  • paths: API endpoint to tag mapping
  • classes: Class name to module mapping
  • modules: Tag to module name mapping

Classes are distributed across modules: shared, stats, listens, recommendations, utility, playlists, feedback, popularity, metadata, social.

Migration Caveats

When migrating tools from hand-made models to generated models, keep in mind:

  1. Query parameters are not generated: datamodel-code-generator only generates response schemas, not request/parameter schemas. Use TypedDict for query parameters:

    class FeedbackQueryParams(TypedDict):
        count: int
        offset: int
        score: int | None
        metadata: str | None
  2. Check for msgspec.UNSET instead of None: Generated models use msgspec.UnsetType for optional fields. Check with is not msgspec.UNSET instead of is not None:

    feedback_list = feedback_payload.feedback
    if feedback_list is not msgspec.UNSET:
        return list(feedback_list)
    return []
  3. Return types use Sequence: Generated models often use Sequence[T] instead of list[T]. Convert as needed when returning to MCP.

Related Projects

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.

Acknowledgements

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.

License

MIT

About

MCP server for ListenBrainz — 18 tools for listening history, stats, metadata, feedback and popularity

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages