Skip to main content
Glama
rachadchazbek

Musicians MCP

README.md
# Musicians MCP Server

Python MCP server for a classical musicians database stored in Supabase/PostgreSQL.

This project now supports both:

- **trusted database read/review tools**, and
- **deterministic external-source enrichment/staging tools**

for MusicBrainz and Wikidata.

The MCP client is the AI agent. This server does **not** call any LLM APIs internally.

## Features

### Core database tools

- `health_check()`
- `search_musicians(query: str, limit: int = 10)`
- `get_musician_profile(musician_id: str)`
- `create_import_job(names: list[str], job_name: str | None = None)`
- `get_review_queue(limit: int = 20)`
- `approve_staged_musician(staged_profile_id: str)`

### External-source enrichment and staging tools

- `resolve_musician_identity(name: str, limit: int = 5)`
- `fetch_wikidata_profile(qid: str)`
- `fetch_musicbrainz_artist(mbid: str)`
- `stage_musician_from_sources(...)`
- `detect_staged_profile_conflicts(staged_profile_id: str)`
- `process_import_job_next(import_job_id: str)`
- `process_import_job_batch(import_job_id: str, max_names: int = 5)`

## Project Structure

```text
.
├── .env.example
├── pyproject.toml
├── README.md
└── src
    └── musicians_mcp
        ├── __init__.py
        ├── db.py
        ├── external_sources.py
        └── server.py
```

## Requirements

- Python 3.11+
- Supabase project URL
- Supabase service role key
- a valid MusicBrainz User-Agent string
- a valid Wikimedia User-Agent string

## Setup

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -e .
cp .env.example .env
```

Update `.env` with:

```env
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-supabase-service-role-key
MUSICBRAINZ_USER_AGENT=ClassicalMusiciansMCP/0.1.0 (your-email-or-project-url)
WIKIMEDIA_USER_AGENT=ClassicalMusiciansMCP/0.1.0 (your-email-or-project-url)
```

User-Agent notes:

- MusicBrainz expects a descriptive User-Agent.
- Wikimedia also expects a descriptive User-Agent for automated access.
- A good format is:
  `ClassicalMusiciansMCP/0.1.0 (your-email-or-project-url)`

## Run the Server

```bash
source .venv/bin/activate
python -m musicians_mcp.server
```

Or with the installed script:

```bash
source .venv/bin/activate
musicians-mcp
```

## Example MCP Client Config

```json
{
  "mcpServers": {
    "musicians": {
      "command": "/absolute/path/to/Musicians MCP/.venv/bin/python",
      "args": ["-m", "musicians_mcp.server"],
      "cwd": "/absolute/path/to/Musicians MCP",
      "env": {
        "SUPABASE_URL": "https://your-project-ref.supabase.co",
        "SUPABASE_SERVICE_ROLE_KEY": "your-supabase-service-role-key",
        "MUSICBRAINZ_USER_AGENT": "ClassicalMusiciansMCP/0.1.0 (your-email-or-project-url)",
        "WIKIMEDIA_USER_AGENT": "ClassicalMusiciansMCP/0.1.0 (your-email-or-project-url)"
      },
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

If your MCP client loads environment variables from the working directory, you can keep the values in `.env` instead.

## Safety Rules

This server is intentionally conservative.

- It does **not** write directly to `musicians`.
- It does **not** auto-approve staged musicians.
- It does **not** call `approve_staged_musician` during staging or batch processing.
- It only stages facts that came from source-backed deterministic fetches.
- It writes review signals and ambiguity details into staged rows/audit logs.
- If identity resolution is ambiguous, it keeps the profile in review-oriented status instead of pretending the identity is verified.

Trusted promotion must still happen manually through:

- `approve_staged_musician(p_staged_profile_id uuid)`

## External Source Behavior

### MusicBrainz

The server can:

- search artists by name
- fetch artist details by MBID
- normalize aliases, artist type, country/area, life-span, disambiguation, and source URL

### Wikidata

The server can:

- search entities by name
- fetch item details by QID
- normalize labels, description, aliases, dates, citizenship, occupations, instruments, notable works, websites, and selected external identifiers

### Request discipline

Both source clients:

- always send configured User-Agent headers
- apply conservative pacing
- use retry logic for transient HTTP failures
- log every external request

## Tool Summary

### `health_check()`
Verifies that the server can connect to Supabase.

### `search_musicians(query, limit=10)`
Searches `musicians` by:

- `full_name`
- `display_name`
- `sort_name`
- `nationality`
- `primary_role`

Returns:

- `id`
- `full_name`
- `display_name`
- `nationality`
- `primary_role`
- `birth_date`
- `death_date`
- `confidence`
- `is_verified`

### `get_musician_profile(musician_id)`
Returns a single trusted musician plus related:

- `fact_claims`
- `external_identifiers`

### `create_import_job(names, job_name=None)`
Creates an import job and returns:

- `job_id`
- `total_names`
- `status`

### `get_review_queue(limit=20)`
Reads staged profiles from `staged_musician_review_queue`.

### `approve_staged_musician(staged_profile_id)`
Manually approves a staged profile through the Postgres RPC.

### `resolve_musician_identity(name, limit=5)`
Searches both Wikidata and MusicBrainz and returns ranked normalized candidates.

Each candidate includes:

- `source`
- `external_id`
- `name`
- `description` / `disambiguation`
- `birth_date`
- `death_date`
- `country`
- `roles`
- `match_score`
- `source_url`

This tool does **not** write to the database.

### `fetch_wikidata_profile(qid)`
Fetches and normalizes one Wikidata item.

Returns:

- structured profile data
- raw key fields useful for debugging

This tool does **not** write to the database.

### `fetch_musicbrainz_artist(mbid)`
Fetches and normalizes one MusicBrainz artist.

Returns:

- structured profile data
- raw key fields useful for debugging

This tool does **not** write to the database.

### `stage_musician_from_sources(...)`
Resolves and stages a musician from deterministic external sources.

Behavior:

- if explicit external IDs are provided, it fetches those exact profiles
- otherwise it resolves likely candidates first
- only strong matches may be selected automatically
- ambiguous cases remain review-oriented
- inserts one row into `staged_musician_profiles`
- inserts source-backed rows into `staged_fact_claims`
- writes audit entries into `import_audit_logs`
- does **not** write to `musicians`
- does **not** call approval automatically

Returns:

- `staged_profile_id`
- `status`
- `confidence`
- `selected_sources`
- `warnings`

### `detect_staged_profile_conflicts(staged_profile_id)`
Checks staged facts for conflicts such as:

- different birth dates
- different death dates
- different nationality/citizenship values
- inconsistent roles/occupations

Returns property-level conflict details.

### `process_import_job_next(import_job_id)`
Processes exactly one pending name from an import job.

It:

- stages a single profile
- updates processed names safely
- writes audit logs
- never approves automatically

### `process_import_job_batch(import_job_id, max_names=5)`
Processes up to `max_names` pending names.

Returns per-name:

- staging results
- errors
- job status progression

## Recommended Manual Test Flow

1. Run `health_check`
2. Run `resolve_musician_identity("Claude Debussy")`
3. Run `fetch_wikidata_profile` on the selected QID
4. Run `fetch_musicbrainz_artist` on the selected MusicBrainz artist ID
5. Run `create_import_job(["Claude Debussy", "Maurice Ravel"])`
6. Run `process_import_job_next(job_id)`
7. Run `get_review_queue()`
8. Run `detect_staged_profile_conflicts(staged_profile_id)`
9. Only then run `approve_staged_musician` manually if the staged record is acceptable

## Local Verification Commands

After setup, these are the main commands to run locally:

```bash
source .venv/bin/activate
python -m pip install -e .
python -m compileall src
python -c "import sys; sys.path.insert(0, 'src'); import musicians_mcp.server; print('server import ok')"
python -m musicians_mcp.server
```

## Notes on Schema Flexibility

This implementation is intentionally defensive around the import/staging tables because exact column layouts were not fully specified here.

In particular, `db.py` uses fallback payload shapes for:

- `musician_import_jobs`
- `staged_musician_profiles`
- `staged_fact_claims`
- `import_audit_logs`

That makes the first version more resilient, but if you share the exact table schemas next, I can tighten the insert/update logic to match them precisely and reduce fallback branching.