Skip to main content
Glama
sarunabh

Mingus Chord Suggestion Tool

by sarunabh
README.md
# Mingus Chord Suggestion Tool

An MCP server that takes a chord progression, identifies what's not working, applies a style filter, and returns chord suggestions backed by music theory and Claude's reasoning.

## Architecture

```
┌─────────────────────────────────────────────────┐
│                   MCP Client                    │
│              (Claude Desktop, etc.)             │
└────────────────────┬────────────────────────────┘
                     │ stdio (JSON-RPC)
┌────────────────────▼────────────────────────────┐
│                  server.py                      │
│           MCP tool: suggest_chords              │
└──────┬───────────────────────┬──────────────────┘
       │                       │
┌──────▼──────┐       ┌────────▼────────┐
│music_theory │       │    style.py     │
│    .py      │       │                 │
│             │       │ vibe vocabulary │
│ - parse     │       │ - annotate      │
│   chords    │       │   candidates    │
│ - infer key │       │ - extended      │
│ - generate  │       │   chord gens    │
│   diatonic  │       │   (jazz/rnb)    │
│   + borrowed│       └────────┬────────┘
│   candidates│                │
└──────┬──────┘                │
       │    top candidates     │
       └───────────┬───────────┘
                   │
         ┌─────────▼──────────┐
         │  claude_advisor.py │
         │                    │
         │  Anthropic API     │
         │  - final selection │
         │  - theory rationale│
         │  - vibe rationale  │
         │  - placement hint  │
         └────────────────────┘
```

### Three-layer design

**Layer 1 — `music_theory.py` (mingus)**
- Parses chord shorthand to note sets
- Infers the most likely key by scoring all major/minor keys against the note content of the input chords
- Generates diatonic triads, diatonic seventh chords, and common borrowed chords (bVII, bVI, minor iv) as raw candidates

**Layer 2 — `style.py` (rule-based filter)**
- Maps each vibe to a chord vocabulary: preferred chord types, preferred quality suffixes
- Scores and labels each candidate as `strong fit`, `possible`, or `unlikely`
- Generates vibe-specific extended chords (jazz: maj9/m9/dom9/m7b5; R&B: sus4/7sus4/m9)

**Layer 3 — `claude_advisor.py` (Anthropic API)**
- Receives the top-scored candidates, the inferred key, the issue, and the vibe description
- Selects 2-3 final suggestions, writing theory rationale and vibe rationale for each
- Can go outside the candidate list if a better option exists
- Returns structured JSON

## Tool interface

**Tool name:** `suggest_chords`

**Input:**
```json
{
  "chords": ["Em", "Gmaj7"],
  "issue": "want a third chord that creates tension before resolving back to Em",
  "vibe": "jazzy"
}
```

**Output:**
```json
{
  "suggestions": [
    {
      "chord": "Am7",
      "theory": "Am7 is the vi7 in G major, a minor subdominant with a 7th. It creates a ii-V-I feel when followed by D7 → Gmaj7, or delivers a deceptive resolution if you return to Em instead.",
      "vibe_rationale": "The m7 quality is a jazz staple. The A-C-E-G voicing offers smooth voice leading from Gmaj7 (G-B-D-F#) by dropping B to A and F# to G.",
      "placement": "Between Gmaj7 and Em as a passing chord"
    }
  ],
  "overall_note": "The progression has a vi-I feel; adding a ii chord turns it into a classic jazz ii-I-vi loop.",
  "context": {
    "inferred_key": "G major",
    "input_progression": ["Em", "Gmaj7"],
    "vibe": "jazzy",
    "candidate_count": 21
  }
}
```

## Setup

### 1. Install dependencies

```bash
cd mingus-chord-tool
pip install -r requirements.txt
```

### 2. Set the API key

```bash
export ANTHROPIC_API_KEY="sk-ant-..."
```

### 3. Register with Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "mingus-chord-tool": {
      "command": "python",
      "args": ["/absolute/path/to/mingus-chord-tool/server.py"],
      "env": {
        "ANTHROPIC_API_KEY": "sk-ant-..."
      }
    }
  }
}
```

Restart Claude Desktop. The `suggest_chords` tool will appear in the tools panel.

### 4. Test without Claude Desktop

```bash
python -c "
from music_theory import summarize_theory
from style import annotate_candidates, get_vibe_context

theory = summarize_theory(['Em', 'Gmaj7'])
print('Key:', theory['inferred_key'], theory['inferred_mode'])
scored = annotate_candidates(theory['novel_candidates'], 'jazzy')
for c in scored[:5]:
    print(c['chord'], '-', c['vibe_label'])
"
```

## Design decisions

**Why not Spotify?**
Spotify's API exposes audio features (tempo, energy, valence) but not chord data. Chord inference from audio exists but is error-prone and adds a heavy dependency. Mingus computes harmony directly, which is more reliable for this use case.

**Why mingus for theory and Claude for suggestions?**
Mingus is deterministic -- it generates every diatonic and borrowed candidate without hallucinating. Claude's strength is contextual judgment: picking between candidates, balancing tension vs. resolution, and writing coherent rationale. Splitting the work this way means Claude never invents notes that don't belong to a chord, while still applying musical taste.

**Why a scored candidate list rather than asking Claude to reason from scratch?**
Passing scored candidates constrains the search space and gives Claude grounding. It also means the `vibe_label` in the output is independently computed -- you can verify it against the theory layer without trusting Claude's self-report.

**Key inference method**
The server scores every major and minor key by counting how many notes in the input chords belong to that key. This is a weighted note-overlap approach, not a full Roman numeral analysis. It works well for common progressions but will misfire on heavily chromatic or modal music. A more robust approach would add Roman numeral parsing and handle secondary dominants explicitly.

## Extending

- Add `modal` as a vibe (Dorian: ii7, bVII; Lydian: #IV, I; Phrygian: bII)
- Add a `mode` override parameter to skip key inference
- Add a `secondary_dominants` layer that computes V/ii, V/IV, V/V for each candidate
- Persist suggestion history and detect patterns across sessions