Skip to main content
Glama
plohoy
by plohoy
README.md
# Music Genre MCP

An offline-first Model Context Protocol server for resolving music genres and
building deterministic prompts for music-generation models.

It prevents a common failure mode in agentic music systems: an unknown or
misspelled genre silently falling back to a generic prompt such as `120 BPM,
A minor`. Music Genre MCP resolves canonical names and aliases, tracks
provenance, and fails closed when a generator profile is unavailable.

## Features

- 2,201 canonical MusicBrainz genres and MBIDs in the included snapshot.
- Conservative Wikidata aliases and explicitly sourced genre relationships.
- Separate `known` and `generation_ready` states.
- Deterministic YuE2-oriented style prompts and explicit genre mixing.
- SQLite runtime with no network access.
- Provenance, confidence, evidence, relations, and empirical-tempo schema.
- No generic BPM, key, or genre fallback for unknown input.

## Installation

Python 3.11 or newer is required.

```bash
git clone https://github.com/plohoy/music-genre-mcp.git
cd music-genre-mcp
python3 -m venv .venv
./.venv/bin/pip install -e .
```

The repository includes a ready SQLite database and reproducible source
snapshots. To rebuild and reimport them without refreshing the network data:

```bash
python scripts/build_db.py
python scripts/import_musicbrainz.py
python scripts/import_wikidata.py
python scripts/validate_db.py
```

Add `--refresh` to an importer only when you intentionally want a fresh remote
snapshot.

## MCP configuration

Generic stdio configuration:

```json
{
  "mcpServers": {
    "music-genres": {
      "command": "/absolute/path/music-genre-mcp/.venv/bin/music-genres-mcp",
      "env": {
        "MUSIC_GENRES_DB": "/absolute/path/music-genre-mcp/data/music_genres.db"
      }
    }
  }
}
```

Hermes `config.yaml`:

```yaml
mcp_servers:
  music-genres:
    command: /absolute/path/music-genre-mcp/.venv/bin/music-genres-mcp
    env:
      MUSIC_GENRES_DB: /absolute/path/music-genre-mcp/data/music_genres.db
    enabled: true
    timeout: 30
```

Test the connection:

```bash
hermes mcp test music-genres
```

## MCP tools

| Tool | Purpose |
|---|---|
| `get_genre` | Resolve a name or alias and return its complete local profile. |
| `search_genres` | Search canonical names and aliases. |
| `list_genres` | Page through the local vocabulary. |
| `related_genres` | Return explicitly sourced genre relations. |
| `mix_genres` | Combine only the genres explicitly requested. |
| `build_generator_style_prompt` | Build a deterministic YuE2 style prompt. |

Unknown input returns `status: not_found`. Every canonical genre shipped in the
catalogue has a precompiled generator profile; runtime lookups never perform
network enrichment and never pause to build a profile.

## Command-line usage

```bash
music-genres get "trip-hop"
music-genres search "industrial techno"
music-genres prompt "industrial techno"
music-genres prompt idm ambient
```

Example prompt for `industrial techno`:

```text
industrial techno, dark industrial techno, relentless four-on-the-floor kick,
distorted warehouse percussion, metallic impacts, grinding bass, cold machine
drones, atonal analog stabs, hypnotic functional arrangement, raw underground
production, no trance melody, no EDM buildup, no trap, no dubstep, 138 BPM,
F minor
```

## Integration with music-generation models

### Recommended agent contract

1. Extract only genre names explicitly supplied by the user.
2. Call `build_generator_style_prompt`.
3. Stop on `not_found`; show suggestions.
4. Pass the returned `prompt` directly to the generator.
5. Apply explicit user BPM/key through native backend fields when available.
6. Never append an LLM-written reinterpretation after the resolved prompt; it
   can overpower the intended genre.

For approved local profiles, the prompt builder also supplies generator-facing
rhythm, bass, instrumentation, melodic language, arrangement, production,
tempo, key and neighbouring-genre exclusions. Weighted blends retain the
requested proportions, use their weighted-average BPM, and inherit the key of
the dominant component. This completion happens only after successful genre
resolution; unknown genres never receive a generic fallback profile.

### YuE2

```python
import subprocess
from music_genres.repository import build_style_prompt

profile = build_style_prompt(["industrial techno"])
if profile["status"] != "ok":
    raise ValueError(profile)

subprocess.run([
    "python", "generate.py",
    "--model", "8bit",
    "--style", profile["prompt"],
    "--lyrics", "[Instrumental]",
    "--cot", "full",
    "--max-semantic-tokens", "1500",
    "--out", "industrial-techno.wav",
], check=True)
```

YuE2 treats textual BPM, key, and genre descriptors as conditioning rather
than hard constraints. With `--cot full`, its generated ABC plan may override
tempo or harmony. For strict musical structure, provide a reviewed external
ABC file or use a reference-audio workflow.

### MiniMax Music

```python
from music_genres.repository import build_style_prompt

profile = build_style_prompt(["trip-hop"])
style_prompt = profile["prompt"]

# Send style_prompt as the model's style/caption field.
# Send lyrics separately. For an instrumental request, use the backend's
# native instrumental/no-vocals control rather than relying only on prose.
```

Duration, instrumental mode, BPM, and key should remain separate API fields
whenever the backend exposes them.

### Generic HTTP generator

```python
import requests
from music_genres.repository import build_style_prompt

profile = build_style_prompt(["neurofunk drum and bass"])
if profile["status"] == "ok":
    response = requests.post(
        "http://127.0.0.1:9000/generate",
        json={
            "style": profile["prompt"],
            "instrumental": True,
            "duration_seconds": 60,
        },
        timeout=1800,
    )
    response.raise_for_status()
```

### Explicit genre blends

```python
profile = build_style_prompt(["idm", "ambient"], weights=[0.6, 0.4])
```

The MCP adds no third genre. Weighted blends use their weighted-average BPM and
inherit the key of the dominant requested component.

## Data and provenance

The SQLite schema contains genres, aliases, relations, descriptors, generator
profiles, tempo distributions, sources, and evidence. Generator BPM/key values
are labelled heuristic and are not presented as empirical musicological truth.

Primary sources:

- [MusicBrainz](https://musicbrainz.org/doc/About/Data_License): canonical
  vocabulary and identifiers; core data is CC0.
- [Wikidata](https://www.wikidata.org/wiki/Wikidata:Licensing): aliases,
  identifiers, and explicit graph claims; structured data is CC0.
- [AcousticBrainz](https://acousticbrainz.org/download): planned empirical
  distributions after recording-to-genre coverage validation.
- [FMA](https://github.com/mdeff/fma): planned hierarchy and feature
  cross-validation.

See [`artifacts/`](artifacts/) for source, licence, feasibility, and method
reports.

## Development

```bash
./.venv/bin/pip install -e '.[dev]'
pytest
ruff check src tests scripts
python scripts/validate_db.py
```

The runtime server never contacts external services. Network access is needed
only when refreshing build-time snapshots.

## Precompiled generator profiles

The checked-in catalogue is fully precompiled: all canonical genres receive a
generator profile before release. Curated/evidence-backed profiles are
preserved; remaining entries are compiled deterministically from ordered
musical-family archetypes, compound-genre matching and lexical modifiers.
Profiles that cannot be classified beyond their canonical label are explicitly
marked `label-only` rather than presented as researched musicology.

Inspect demand and status without network access:

```bash
music-genres profile-requests --limit 20
music-genres profile-status jazz
```

Refresh the complete precompiled catalogue after importing a new vocabulary:

```bash
python scripts/compile_all_profiles.py        # preview coverage
python scripts/compile_all_profiles.py --apply
python scripts/validate_db.py
```

An administrator can still replace a low-confidence profile with sourced
enrichment:

```bash
python scripts/enrich_profile.py jazz --review-only
python scripts/enrich_profile.py jazz
python scripts/enrich_pending.py --limit 10
```

Optional enrichment resolves an exact Wikidata entity, retrieves its English Wikipedia
article as attributed evidence, and extracts only descriptors from the checked-in
controlled vocabulary. A candidate is promoted only when validation finds at
least three supported descriptors spanning at least two categories. Explicit BPM
ranges are preserved; BPM, key, and scale remain unset when the evidence does not
state them. Ambiguous or thin evidence is stored as `needs_review`.

The MCP exposes `get_generator_profile_status` for provenance inspection.
Network enrichment is deliberately excluded from the runtime MCP surface so an
ordinary model call cannot mutate the genre knowledge base from arbitrary web
content.

## License

Project code is MIT licensed. Imported data keeps its original licence and
provenance; see `docs/SOURCES.md` and `artifacts/stage-0/licenses.md`.