@cyanheads/musicbrainz-mcp-server
Search artists, releases, recordings, works, and labels; traverse relationships; resolve ISRC/ISWC/barcode; fetch cover art via MCP. STDIO or Streamable HTTP.
10 Tools • 1 Entity Resource • 1 Skill
[](./CHANGELOG.md) [](./LICENSE) [](https://modelcontextprotocol.io/) [](https://www.typescriptlang.org/) [](https://bun.sh/)
[](https://github.com/cyanheads/musicbrainz-mcp-server/releases/latest/download/musicbrainz-mcp-server.mcpb) [](https://cursor.com/en/install-mcp?name=musicbrainz-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvbXVzaWNicmFpbnotbWNwLXNlcnZlciJdfQ==) [](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22musicbrainz-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fmusicbrainz-mcp-server%22%5D%7D)
[](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
**Public Hosted Server:** [https://musicbrainz.caseyjhand.com/mcp](https://musicbrainz.caseyjhand.com/mcp)
---
## Overview
Open music metadata from MusicBrainz and the Cover Art Archive: artists, releases, recordings, works, and labels. Search by name, look up by MBID, ISRC, ISWC, or barcode, page through complete discographies, and fetch cover art.
### Tools
| Tool | Description |
|:---|:---|
| `musicbrainz_search_entities` | Lucene search over one entity type, returning ranked MBIDs with relevance scores |
| `musicbrainz_get_artist` | Artist profile: areas, IPI/ISNI, life span, discography, relationships, external links |
| `musicbrainz_get_release_group` | Release-group (the album across its editions): types, first-release date, editions, cover-art availability |
| `musicbrainz_get_release` | One edition: tracklist with artist credits, label and catalog number, barcode, packaging, external links |
| `musicbrainz_get_recording` | Recording (one performance): length, ISRCs, releases it appears on, performer and production credits |
| `musicbrainz_get_work` | Work (a composition): type, ISWCs, writers, movements, arrangements, and recordings |
| `musicbrainz_get_label` | Label: area, IPI/ISNI, life span, label code, label relationships, external links |
| `musicbrainz_lookup_identifier` | Resolve an ISRC, ISWC, or barcode to recordings, works, or releases |
| `musicbrainz_browse_entities` | Page through every entity linked to a parent MBID |
| `musicbrainz_get_cover_art` | Cover Art Archive images for a release or release-group |
### Resources
| Resource | Description |
|:---|:---|
| `musicbrainz://{entity_type}/{mbid}` | One entity as raw MusicBrainz JSON, with the matching `get_*` tool's linked data folded in |
The `get_*` tools provide normalized lookup views for tool-only clients. The entity resource has no list; find MBIDs with `musicbrainz_search_entities`.
### Agent skill
[`musicbrainz-guide`](skills/musicbrainz-guide/SKILL.md) covers identity checks, edition selection, discographies, credits, artwork, and Lucene queries. It ships with npm, Docker, MCPB, and plugin installs, and is available through the MCP Skills extension and `skill://` resources.
## Capability reference
### `musicbrainz_search_entities` tool
- One `entityType` per call (`artist`, `release-group`, `release`, `recording`, `work`, `label`) and a Lucene `query` with field scoping (`artist:radiohead AND country:GB`); positive integer `limit` (default 25, values above 100 clamp to 100 with a notice), `offset` for paging
- Returns the effective `offset` and upstream `totalCount`. Continuation uses `offset + rows returned`, including short pages. An empty page at or beyond a positive total reports exhaustion; an unexpectedly empty page before the total reports the discrepancy without suggesting a nonadvancing offset
- Search exposes only the first 500 matches. A page crossing that boundary is shortened with a notice; offsets of 500 or more fail as `search_window_exceeded`. Narrow the query or browse a linked set to reach further matches; `totalCount` remains the full upstream count
- Each hit carries `mbid`, `name`, and the raw 0–100 `score` in MusicBrainz order, plus type-specific fields such as `artistCredit`, `isrcs`, `iswcs`, and `length`. Release rows use `status` (Official, Bootleg, …); artist, release-group, work, and label subtypes use `type`. A whitespace-only query fails as `blank_query`
- Release-group hits also carry available `secondaryTypes`; recording hits carry available `firstReleaseDate`, including partial dates
---
### `musicbrainz_get_artist` tool
- `mbid`, plus `inc_release_groups` and `inc_relationships` (both default `true`) to drop the heavier sub-resources
- Type, gender, country, area, `lifeSpan`, aliases, and tags, plus `releaseGroups` (one page), `relationships` (band membership, collaborations), and `externalLinks` (Wikidata, Discogs, official site)
- `areaId` can be passed to browse `link.area`; `beginArea` and `endArea` retain available MBID/name members. `ipis` and `isnis` preserve the catalog's identifier strings, including leading zeros
---
### `musicbrainz_get_release_group` tool
- `mbid` only; returns `primaryType`, `secondaryTypes`, `firstReleaseDate`, `artistCredit` with `artistCreditString`, tags, and one page of `releases` (editions)
- `externalLinks` carries album-level identities such as Wikidata and Discogs masters
- `coverArt` (`exists`, `count`, `front`, `back`) comes from a live Cover Art Archive check; when that check fails the field is omitted with a notice, never reported as "no art"
---
### `musicbrainz_get_release` tool
- `mbid`, `media_offset` (default 0), and `media_limit` (default 20, no maximum); returns ordered `media` with complete tracks per medium (`position`, `title`, `length` as m:ss, `recordingId`), `labelInfo` (label and `catalogNumber`), `barcode`, `status`, `date`, `country`, `packaging`, `language`/`script`, and `releaseGroupId`
- `mediaTotal` and `trackTotal` count all normalized media and track occurrences; upstream per-medium `trackCount` is preserved. `mediaOffset` identifies the window. When more media remain, `truncated`, `shown`, `cap`, and a notice give the next `media_offset`; empty end pages report exhaustion. The release resource retains the complete raw tracklist
- `coverArt` is the availability stub from the release record; call `musicbrainz_get_cover_art` for image URLs
- Tracks retain their own available `artistCredit` and `artistCreditString`, independently of release or recording credits. `externalLinks` belongs to the selected edition
---
### `musicbrainz_get_recording` tool
- `mbid`, plus `inc_relationships` (default `true`) for performer, producer, and engineer credits, work links, recording-to-recording relationships such as samples and remixes, and external links
- Returns `length`, `isrcs`, `artistCredit`, `firstReleaseDate`, one upstream page of `releases` (up to 25), `relationships`, and `externalLinks`
- `releaseAppearanceSummary` reports `shown`, `cap: 25`, and `completeness`: a short present page is `complete`, a full or absent page without an exact count is `unknown`, and an exact upstream count exceeding the returned page proves `truncated`. `totalCount` is omitted when unknown; no count request is added. Enumerate appearances with `musicbrainz_browse_entities` (`target_type=release`, `link.recording=`, `offset=0`), because embedded and browse order may differ
---
### `musicbrainz_get_work` tool
- `mbid`, plus `inc_relationships` (default `true`) for writer, composer, and lyricist credits, movements and arrangements, the recordings that perform it, and external links
- Returns `type`, `languages`, `iswcs`, aliases, and tags. Only the first 25 recording relationships are kept (other relationships and links are all kept); `totalCount` gives the full count, and `truncated`, `shown`, and `cap` appear when some were cut
- `attributes` preserves ordered type/value rows, including musical key, catalogue metadata and society work IDs. Repeated types and exact string values are retained, even with relationships disabled
- To list every recording of a work, browse `recording` by `work`, then call `musicbrainz_get_recording` for relationship attributes and dates
- Follow a `relationships` entry with `targetType: "work"` using its `targetId` in another `musicbrainz_get_work` call. `direction` distinguishes parent/child links; optional `orderingKey` preserves the upstream sequence key (including zero), and `targetDisambiguation` distinguishes arrangements. Relationships retain upstream array order, which need not match movement order
---
### `musicbrainz_get_label` tool
- `mbid` only; returns type, country, area and `areaId`, `ipis`, `isnis`, `lifeSpan`, `labelCode` (the LC number without its prefix), aliases, tags, label `relationships`, and `externalLinks`
- Releases are not embedded; list them with `musicbrainz_browse_entities` (`target_type=release`, `link.label`)
---
### `musicbrainz_lookup_identifier` tool
- `id_type` `isrc` → recordings, `iswc` → works, `barcode` → releases; a barcode is UPC/EAN digits and returns up to 25 ranked matches, each with a `score`
- ISRC accepts compact `GBAYE9701274` or printed `GB-AYE-97-01274`, ignoring case and surrounding whitespace. ISWC accepts `T-010.140.236-1` or `T0101402361`, with optional group punctuation, ignoring whitespace and case. Results echo the canonical compact ISRC or `T-DDD.DDD.DDD-C` ISWC; malformed codes fail before a request
- `result.kind` (`recordings` | `works` | `releases`) names the arm that came back; failures are `invalid_identifier` or `identifier_not_found`
---
### `musicbrainz_browse_entities` tool
- `target_type` plus exactly one parent MBID in `link`; `releaseGroup`/`release_group` alias `release-group`. Positive integer `limit` defaults to 25; values above 100 clamp to 100 with a notice. Set `MUSICBRAINZ_CLAMP_OVERSIZED_LIMITS=off` to enforce maximum 100 for both search and browse
- Reports upstream `totalCount`; while more remain, `truncated` is set and a nonempty page gives the next `offset` using actual rows returned. Empty pages distinguish zero linked entities, exhaustion, and an unexpected empty page before the total. Clamp and paging guidance share one notice. Release rows use `status`; other entities use `type`
- A missing, extra, or malformed link fails as `invalid_link`, an unknown parent as `entity_not_found`. For ordered tracks of an edition use `musicbrainz_get_release`
- Rows retain available release-group `secondaryTypes` and recording `firstReleaseDate`. `Album` alone does not distinguish studio albums from live albums or compilations
- Optional `type` and `status` filters accept pipe-separated values with case and surrounding token whitespace normalized, echoed as `type`/`status`. Primary types union; primary and secondary categories intersect. Release-group-by-artist requires every supplied secondary type. Empty optional filters are ignored; unsupported target/parent combinations and invalid tokens fail as `invalid_filter` before fetching
- `type` is supported for release-groups by artist, and releases by artist, label, recording, or `track_artist`. `status` is supported for those release parents plus release-group. Area browse supports neither filter. Preserve applied filters when paging
- `link.track_artist` finds releases crediting an artist on tracks but not in the release credit. Obtain area MBIDs from `get_artist` or `get_label` `areaId`; those tools do not resolve arbitrary area MBIDs
| Target | Accepted parent keys |
|:---|:---|
| `artist` | `area`, `recording`, `release`, `release-group`, `work` |
| `label` | `area`, `release` |
| `release-group` | `artist`, `release` |
| `release` | `area`, `artist`, `label`, `recording`, `release-group`, `track_artist` |
| `recording` | `artist`, `release`, `work` |
| `work` | `artist` |
---
### `musicbrainz_get_cover_art` tool
- `mbid` and `entity_type`: `release` (default) or `release-group`, which resolves to a `representativeRelease`; `limit` defaults to 25 and clamps above 100, and `offset` defaults to 0. These windows apply after the archive index is cached and are independent of the search/browse clamp setting
- `images` retain archive order with `front`/`back`, `types`, `imageUrl`, and 250/500/1200px thumbnails. `totalCount` is exact, `offset` identifies the window, and `hasArt` reflects the full archive even on an empty end page. A single notice combines any clamp, continuation, exhaustion, or no-art guidance; `truncated`, `shown`, and `cap` appear when more images remain
- An unknown MBID fails as `entity_not_found`; if MusicBrainz can't confirm the MBID exists, the empty set comes back with a notice saying so
---
### `musicbrainz://{entity_type}/{mbid}` resource
- `entity_type` is `artist`, `release-group`, `release`, `recording`, `work`, or `label`; returns the raw MusicBrainz record as `application/json`
- Fetches the same default linked data as the matching `get_*` tool; failures are `invalid_mbid` and `entity_not_found`
- Work JSON includes raw work-to-work relationships and keeps the first 25 recording relationships; `recordingRelationshipSummary` gives the exact recording count and whether any were cut
- Recording JSON carries the same `releaseAppearanceSummary` as the recording tool without changing cached raw data. Release JSON retains every medium and track; the tool's media window does not apply to resources
## Features
Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
MusicBrainz-specific:
- Keyless client for the MusicBrainz Web Service v2 and the Cover Art Archive. Every request carries the descriptive `User-Agent` MusicBrainz requires, `musicbrainz-mcp-server/ ( )`, with `MUSICBRAINZ_CONTACT` as the contact
- Entities are addressed by MBID (a hyphenated UUID); uppercase and surrounding whitespace are normalized. Malformed or all-zero IDs fail as `invalid_mbid` (`invalid_link` for browse); a well-formed unknown ID fails as `entity_not_found`
- MusicBrainz calls are held to ~1 request/second across all clients of an instance, so long browse runs take a while
- Lookups fold discography, relationships, tracklists, and external links into one request; responses are cached (24 h by default) and transient upstream errors are retried with backoff
Agent-friendly output:
- Artist release-groups, release-group editions, and recording release appearances embed one page (25); browse enumerates the full set. Recording appearances disclose completeness without a count request. Work recording relationships are capped at 25 with the exact count reported; labels embed no releases
- Search echoes the effective query; search and browse report the exact upstream total. Missing or invalid totals fail as malformed upstream responses instead of inventing a count
- Discriminated outputs: `musicbrainz_lookup_identifier` returns a `kind`-tagged union, so callers branch on data, not string parsing
- Raw upstream relevance `score`, not a derived confidence; missing upstream fields stay absent rather than invented
## Getting started
Connect to the hosted instance:
```sh
claude mcp add --transport http musicbrainz-mcp-server https://musicbrainz.caseyjhand.com/mcp
```
Or run it locally:
```sh
claude mcp add musicbrainz-mcp-server -- npx -y @cyanheads/musicbrainz-mcp-server
```
For other clients, add this to the MCP config file:
```json
{
"mcpServers": {
"musicbrainz-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/musicbrainz-mcp-server"]
}
}
}
```
No API key or account is required. Set `MUSICBRAINZ_CONTACT` to your own email or URL for shared or hosted deployments. For Docker or a source build, see [Running the server](#running-the-server).
## Configuration
| Variable | Description | Default |
|:---|:---|:---|
| `MUSICBRAINZ_CONTACT` | Email or URL sent in the `User-Agent`; set your own on shared or hosted instances. | repo URL |
| `MUSICBRAINZ_BASE_URL` | MusicBrainz Web Service v2 base URL (e.g. a private mirror). | `https://musicbrainz.org/ws/2` |
| `MUSICBRAINZ_RATE_LIMIT_RPS` | Client-side requests-per-second ceiling (0.1–50). | `1` |
| `MUSICBRAINZ_CACHE_TTL` | Response cache TTL in seconds; `0` disables caching. | `86400` |
| `MUSICBRAINZ_TIMEOUT_MS` | Per-request HTTP timeout, in ms. | `30000` |
| `MUSICBRAINZ_MAX_RETRIES` | Retry attempts for transient upstream failures. | `3` |
| `MUSICBRAINZ_CLAMP_OVERSIZED_LIMITS` | Clamp search/browse limits above 100 with a notice; `off` restores maximum-100 schema validation. Strict boolean (`true`/`false`, `on`/`off`, `1`/`0`, `yes`/`no`). | `true` |
| `COVER_ART_BASE_URL` | Cover Art Archive base URL. | `https://coverartarchive.org` |
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
| `MCP_HTTP_PORT` | HTTP server port. | `3010` |
| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.). | `info` |
| `OTEL_ENABLED` | Enable [OpenTelemetry](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
See [`.env.example`](./.env.example) for the full list of optional overrides.
## Running the server
### Local development
Requires [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
```sh
git clone https://github.com/cyanheads/musicbrainz-mcp-server.git
cd musicbrainz-mcp-server
bun install
cp .env.example .env
```
- **Build and run:**
```sh
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
```
- **Run checks and tests:**
```sh
bun run devcheck # Lint, format, typecheck, security, packaging
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against the linter rules
```
### Docker
```sh
docker build -t musicbrainz-mcp-server .
docker run --rm -e MUSICBRAINZ_CONTACT=you@example.com -p 3010:3010 musicbrainz-mcp-server
```
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/musicbrainz-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
## Project structure
| Directory | Purpose |
|:---|:---|
| `src/index.ts` | `createApp()` entry point — registers tools and the resource, inits both services. |
| `src/config` | Server-specific environment variable parsing and validation with Zod. |
| `src/services/musicbrainz` | MusicBrainz WS/2 client — User-Agent, rate limiter, response cache, retry, domain types. |
| `src/services/cover-art` | Cover Art Archive client — maps a 404 to an empty image set, follows the release-group redirect. |
| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Ten read-only tools across search, lookup, browse, and cover art. |
| `src/mcp-server/resources` | Resource definitions. The `musicbrainz://{entity_type}/{mbid}` entity mirror. |
| `skills/` | Published agent guide and query reference, also served through MCP skill resources. |
| `tests/` | Unit and integration tests mirroring `src/`. |
## Development guide
See [`CLAUDE.md`/`AGENTS.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no `try/catch` in tool logic
- Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage
- Register new tools and resources via the barrels in `src/mcp-server/*/definitions/index.ts`
- Route every upstream call through the services, never a direct `fetch()`, so the User-Agent, rate limiter, and cache apply
- Wrap external API data: validate raw → normalize to domain type → return output schema; never fabricate missing fields
## Contributing
Issues are welcome. Run checks and tests before submitting:
```sh
bun run devcheck
bun run test
```
## License
Apache-2.0 — see [LICENSE](./LICENSE) for details.
MusicBrainz core entity data is [CC0](https://musicbrainz.org/doc/About/Data_License). The server reads only that core metadata and never fetches annotation text, which carries a different license. Cover art comes from the [Cover Art Archive](https://coverartarchive.org/), a joint project of MusicBrainz and the Internet Archive; image URLs are linked, never rehosted, and each image's copyright stays with its rights holders. Cite MusicBrainz and the Cover Art Archive in downstream use.