Metadata-Version: 2.4
Name: mcp-aoty
Version: 0.1.0
Summary: MCP server for Album of the Year (albumoftheyear.org) critic and user scores
Keywords: mcp,aoty,model-context-protocol
Author: Matthew O'Brien
Author-email: Matthew O'Brien <obrien.mlotwis@gmail.com>
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Multimedia :: Sound/Audio
Requires-Dist: mcp>=1.27.0,<2
Requires-Dist: httpx>=0.28.0
Requires-Python: >=3.14
Project-URL: Repository, https://github.com/obrien-matthew/mcp-aoty
Project-URL: Issues, https://github.com/obrien-matthew/mcp-aoty/issues
Description-Content-Type: text/markdown

# 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/
```
