mcp-aoty
# mcp-aoty
MCP server for exploring Album of the Year critic and user scores, reviews, releases,
charts, critic lists, and music news.
## Attribution
> Album data comes from the unofficial Album of the Year API at
> https://aoty.prigoana.com (https://github.com/edideaur/AOTY-api), which is free
> to use with attribution. Underlying reviews, scores, and editorial content are
> the work of Album of the Year (https://www.albumoftheyear.org) and its
> contributing publications.
- Upstream API: [aoty.prigoana.com](https://aoty.prigoana.com)
- Upstream source: [edideaur/AOTY-api](https://github.com/edideaur/AOTY-api)
- Data source: [Album of the Year](https://www.albumoftheyear.org)
That credit is a condition of use, so it is carried in five places and none of them are
optional: this section, [`NOTICE`](./NOTICE), the `User-Agent` sent with every request,
the `instructions` string every MCP client receives at handshake, and the
`get_attribution` tool.
## Prerequisites
- Python 3.14+
- [uv](https://docs.astral.sh/uv/)
## Setup
```bash
uv sync
```
## Configuration
No API key exists or is needed. The request interval is a deliberate courtesy
throttle because the upstream API is a free personal Cloudflare Worker.
| Variable | Required | Default | Purpose |
| ---------------------- | -------- | -------------------------------- | -------------------------------------------- |
| `AOTY_BASE_URL` | No | `https://aoty.prigoana.com` | Unofficial Album of the Year API base URL |
| `AOTY_MIN_INTERVAL_MS` | No | `250` | Minimum interval between upstream requests |
### Claude Desktop
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"aoty": {
"command": "uvx",
"args": ["mcp-aoty"],
"env": {
"AOTY_MIN_INTERVAL_MS": "250"
}
}
}
}
```
### Claude Code
```bash
claude mcp add aoty -e AOTY_MIN_INTERVAL_MS=250 -- uvx mcp-aoty
```
## Tools
### Diagnostics
| Tool | Description |
| ---------------------- | ------------------------------------------------ |
| `get_server_version` | Return the installed server version |
| `get_attribution` | Return upstream API and data-source attribution |
### Albums
| Tool | Description |
| ----------- | ------------------------------------------------------------------ |
| `get_album` | Get album details by authoritative slug, or fuzzy artist and name |
### Search
| Tool | Description |
| ---------------- | --------------------------------------------- |
| `search_all` | Search albums, artists, and labels together |
| `search_albums` | Search album results |
| `search_artists` | Search artist results |
| `search_labels` | Search record-label results |
### Charts and releases
| Tool | Description |
| ----------------------- | --------------------------------------------- |
| `get_new_releases` | Get paginated new album releases |
| `get_new_singles` | Get paginated new single releases |
| `get_upcoming_releases` | Get paginated upcoming releases |
| `get_popular_albums` | Get currently popular albums |
| `get_popular_singles` | Get currently popular singles |
| `get_anticipated_albums`| Get anticipated upcoming albums |
| `get_under_radar_albums`| Get under-the-radar albums |
| `get_must_hear_albums` | Get must-hear albums by optional year or decade|
### Lists and news
| Tool | Description |
| ------------------ | ------------------------------------------------ |
| `get_critic_lists` | Get critic best-of lists for an optional year |
| `get_critic_list` | Get the entries in a critic list by slug |
| `get_music_news` | Get paginated music-news items by feed type |
## Data source and etiquette
albumoftheyear.org has no official API; this server talks only to the unofficial
community API and never requests albumoftheyear.org directly. AOTY's
`robots.txt` disallows AI crawlers (`ClaudeBot`, `anthropic-ai`, `GPTBot`,
`CCBot`, `Google-Extended`) and `/search/*`, so those directives bind the
upstream Worker rather than this client. The API is a scraper and can break
when AOTY changes its HTML; keep request volume low.
## Known limitations
- `artist` plus `name` lookup is fuzzy: `Cassius` plus `1999` returns the
single instead of the LP. Use `search_albums`, then pass its `slug` to
`get_album`.
- Album blocks carry no `id`, so this server derives slugs from their URLs.
- A `critic_score_raw` value of `"NR"` means not rated. `critic_score` is `None` in that
case, which is distinct from a score being absent entirely.
- Search hits often omit scores even when the album page has them. Call `get_album`
with the slug when you need scores.
- A missing album is served as HTTP 200 carrying AOTY's generic page, not a 404. This
server detects that stub and raises rather than returning a phantom record titled
"Album of the Year".
- Scraped text arrives with HTML entities intact; this server decodes them before
returning.
- Scores are live and move.
## Development
```bash
uv sync
uv run pytest tests/ -x -q
uv run ruff check src/ tests/
uv run pyright src/
```
TDQS
Scored across 18 tools
Most tools map clearly to distinct AOTY resources such as albums, singles, news, or critic lists, but the several album-list tools (popular, anticipated, under-the-radar, must-hear) share a similar "list albums" shape and could be confused despite different curation semantics. Search tools are cleanly separated by target type.
All tools follow a consistent get_ or search_ verb-plus-noun pattern in snake_case, with no mixed conventions or vague action verbs. Even the meta tools get_server_version and get_attribution fit the same naming scheme.
18 tools is on the heavier side, but each tool corresponds to a distinct AOTY feed or searchable entity, so the count is defensible for a comprehensive music-site server. It feels slightly over-scoped rather than bloated.
The server covers album browsing, searching, release timelines, singles, news, and critic lists well, but artist and label searches return slugs with no corresponding get_artist or get_label detail tool, creating dead ends. Singles also lack a detail lookup, leaving notable gaps despite strong core album coverage.