An open API service indexing awesome lists of open source software.

https://github.com/lelabdev/spotify-graph

Music taste graph DB — Rust CLI for artist discovery via graph traversal
https://github.com/lelabdev/spotify-graph

mcp mcp-server music-discovery music-graph rust sqlite

Last synced: about 2 months ago
JSON representation

Music taste graph DB — Rust CLI for artist discovery via graph traversal

Awesome Lists containing this project

README

          

# spotify-graph

Rust-powered musical graph — SQLite-backed, MCP-compatible, with a built-in local dashboard.

## Why

Static playlists don't capture the fluid nature of musical tastes. spotify-graph models artists as a weighted graph where discovery happens through proximity — not platform algorithms.

## Architecture

```
spotify-graph (single binary)
├── mcp → MCP stdio server (current mode)
├── tracker → Poll Last.fm (primary) + Spotify (fallback) → SQLite
└── serve → axum HTTP API + Askama/HTMX dashboard
```

Everything in Rust. No Python, no Node, no separate services.

### Data flow

```
Spotify (listen) ──auto-scrobble──→ Last.fm (hub)

┌─────────────────┤
▼ ▼
plays history similar artists + tags
│ │
└────────┬────────┘

SQLite (local)

┌────────────┼────────────┐
▼ ▼ ▼
MCP tools REST API Dashboard
(stdio) (axum) (Askama+HTMX)
```

## Stack

- **Rust** — axum (HTTP), rmcp (MCP), rusqlite (SQLite), reqwest (API calls)
- **SQLite** — single file, no ORM
- **Askama** — compile-time HTML templates
- **HTMX** — server-driven interactivity (no JS framework)
- **Cytoscape.js** — graph visualization (bonus, post-v1)
- **Last.fm API** — primary source for plays, tags, similar artists
- **Spotify API** — fallback for plays + audio features

## Data Model

### Nodes — Artists / Tracks / Labels

Each node has 6 continuous axes (1–10) describing its sonic profile:

| Axis | 1 | 10 |
|------|---|----|
| **Energy** | Silence | Full power |
| **Tempo** | No beat | 140+ BPM |
| **Repetition** | Varied | Pure hypnotic |
| **Organic** | 100% synth | 100% acoustic |
| **Depth** | Surface | Immersive / meditative |
| **Brightness** | Dark | Bright |

Plus CSV tags, free-form notes, and an optional Spotify URI.

### Edges — Relations

Types: `similar_vibe`, `same_scene`, `same_label`, `influenced_by`, `collaborated_with`, `lastfm_similar`

Each edge carries a weight (0–1) representing link strength.

### Plays

Tracks played, sourced from Last.fm (primary) and Spotify (fallback). Artist, track name, timestamp, duration, URI.

### Feedback (post-MVP)

`liked` / `disliked` / `neutral` per context (coding, chill, morning…).

## CLI

```bash
# MCP server (stdio) — for Hermes/any MCP client
spotify-graph mcp

# Track plays — poll Last.fm + Spotify, enrich graph
spotify-graph tracker

# Serve dashboard + API
spotify-graph serve --port 3030
```

Default (no subcommand) starts MCP mode for backward compatibility.

## API Endpoints

### Reads
- `GET /api/stats` — graph statistics
- `GET /api/plays?limit=50&offset=0&artist=` — play history
- `GET /api/artists` — artist list with tags
- `GET /api/artists/:name` — artist detail + relations
- `GET /api/discover/:name?depth=2` — BFS traversal

### Writes
- `POST /api/nodes` — add a node
- `POST /api/edges` — add a relation
- `PATCH /api/artists/:name` — edit tags/axes/note
- `DELETE /api/nodes/:name` — remove a node (cascade)

## MCP Tools

| Tool | Description |
|------|-------------|
| `spotify_graph_add_node` | Add a node with audio attributes |
| `spotify_graph_link` | Link two nodes with a typed relation |
| `spotify_graph_discover` | BFS traversal filtered by minimum weight |
| `spotify_graph_log_play` | Log a single play event |
| `spotify_graph_log_plays_batch` | Log multiple plays at once |
| `spotify_graph_seed` | Bulk JSON import of nodes + edges |
| `spotify_graph_stats` | Graph statistics |

## Configuration

```toml
# ~/.config/spotify-graph/config.toml

[server]
port = 3030
host = "0.0.0.0"

[database]
path = "~/.local/share/spotify-graph/graph.sqlite"

[lastfm]
api_key = "..."
username = "..."
rate_limit_ms = 250

[spotify]
client_id = "..."
auth_path = "~/.hermes/auth.json"

[tracker]
poll_interval_min = 120
max_similar_match = 0.4
```

Defaults are used if the file is missing. Override via `SPOTIFY_GRAPH_*` env vars.

## Build

```bash
cargo build --release
# binary: target/release/spotify-graph
```

## Dashboard Pages (v1)

| Route | Description |
|-------|-------------|
| `/` | Quick stats |
| `/plays` | Play history, filters, pagination |
| `/artists` | Artist list + tags, inline edit |
| `/artists/:name` | Detail: tags, axes, relations |
| `/discover` | BFS discovery — interactive |
| `/graph` | Force-directed visualization (Cytoscape.js, post-v1) |

## Project Structure (target)

```
spotify-graph/
├── src/
│ ├── main.rs # CLI (clap subcommands)
│ ├── config.rs # TOML config loading
│ ├── db/
│ │ ├── mod.rs
│ │ ├── schema.sql
│ │ └── queries.rs # SQLite queries
│ ├── mcp/
│ │ ├── mod.rs
│ │ └── tools.rs # MCP tool handlers
│ ├── tracker/
│ │ ├── mod.rs
│ │ ├── lastfm.rs # Last.fm API client
│ │ └── spotify.rs # Spotify API client
│ └── api/
│ ├── mod.rs
│ ├── routes.rs # axum routes
│ ├── handlers.rs # route handlers
│ ├── error.rs # error types
│ └── templates/ # Askama HTML templates
│ ├── base.html
│ ├── index.html
│ ├── plays.html
│ ├── artists.html
│ ├── detail.html
│ └── discover.html
├── static/ # Cytoscape.js, HTMX, Tailwind
├── Cargo.toml
└── schema.sql
```

## Roadmap

### V0.1 — Foundation ✅
- [x] Schema + SQLite DB
- [x] 5 MCP tools (add_node, link, discover, seed, stats)
- [x] Initial dataset (Ludo's artists)
- [x] Python tracker (Spotify polling)

### V2 — Full Rust + Dashboard 🚧
- [ ] #21 — CLI subcommands (mcp / tracker / serve)
- [ ] #22 — Last.fm tracker (primary) + Spotify fallback (Rust)
- [ ] #23 — REST API (axum)
- [ ] #24 — Dashboard (Askama + HTMX)
- [ ] #25 — Graph visualization (Cytoscape.js)
- [ ] #26 — Remove Python scripts
- [ ] #27 — Config file (TOML)

### Backlog
- [ ] #17 — MCP tool: search
- [ ] #18 — MCP tool: Graphviz export
- [ ] Audio features from Spotify API (energy, tempo, etc.)
- [ ] Feedback system (liked/disliked per context)

## License

[MIT](LICENSE) — [lelabdev](https://github.com/lelabdev)