MCP Hub
Back to servers

CHUK Music MCP Server

A comprehensive music design system that allows AI to compose music using deterministic YAML arrangements. It provides 37 tools for managing patterns, styles, and structures that compile into Score IR and MIDI files.

Tools
37
Updated
Dec 24, 2025

CHUK Music MCP Server

A music design system for MCP — shadcn/ui for music composition.

You copy patterns into your project, you own them, you modify them. The library provides correct primitives and well-designed starting points, not a black box.

Vision

Music as a design system, not a DAW. This is a control-plane for composition.

LLMs operate at the intent level — structure, energy, arrangement. The system handles music theory. Composers own their patterns.

Features

  • Pattern System: Copyable, ownable pattern templates (drums, bass, harmony, melody, fx)
  • Style System: Constraint bundles that constrain, suggest, and validate
  • Arrangement Model: Layers × Sections structure with energy curves and harmony
  • Score IR: Versioned intermediate representation — golden-file testable, diffable, round-trippable
  • MIDI Export: Deterministic compilation from YAML to playable MIDI files
  • MCP Integration: Full MCP server with 37+ tools for AI-assisted composition

Quick Start

# Install
git clone https://github.com/chuk-ai/chuk-mcp-music
cd chuk-mcp-music
pip install -e ".[dev]"

# Compile an arrangement
python examples/compile_arrangement.py

# Verify determinism
shasum output/demo.mid
# e3b0c442...  (always the same for the same input)

The Stack

Intent (LLM)
    ↓
Tokens (semantic constraints)
    ↓
Structure (sections, energy curves)
    ↓
Layers (drums, bass, harmony, melody, fx)
    ↓
Patterns (owned, modifiable recipes)
    ↓
Score IR (symbolic, inspectable, versioned)
    ↓
MIDI (deterministic compilation)
    ↓
Audio (optional, downstream)

What You Own vs What the Library Owns

You OwnLibrary Owns
Patterns you copy to your projectSchema definitions (pattern/v1, score_ir/v1)
Arrangement YAML filesCompiler pipeline
Style overrides and customizationsValidation rules
Project-specific pattern librariesDefault pattern/style libraries
Output MIDI filesIR specification and canonicalization

The boundary is clear: you own the content, the library owns the machinery.

Key Concepts

Patterns (The shadcn Layer)

Patterns are copyable, ownable, modifiable templates:

# patterns/bass/root-pulse.yaml
schema: pattern/v1
name: root-pulse
role: bass
pitched: true

parameters:
  density:
    type: enum
    values: [half, quarter, eighth]
    default: quarter

variants:
  driving:
    density: eighth

template:
  events:
    - degree: chord.root
      beat: 0
      velocity: $velocity_base

Arrangements (Your Composition)

# arrangements/demo.arrangement.yaml
schema: arrangement/v1
key: D_minor
tempo: 124

harmony:
  default_progression: [i, VI, III, VII]

sections:
  - name: intro
    bars: 8
  - name: verse
    bars: 16

layers:
  bass:
    role: bass
    patterns:
      pulse:
        ref: bass/root-pulse
        variant: driving
    arrangement:
      intro: null
      verse: pulse

Styles (Constraint Bundles)

Styles do three things:

  1. Constrain — tempo ranges, forbidden patterns, key preferences
  2. Suggest — pattern shortlists per layer, register hints
  3. Validate — lint errors with actionable fixes
# styles/library/melodic-techno.yaml
schema: style/v1
name: melodic-techno
description: Driving, melodic electronic music

tokens:
  tempo:
    range: [120, 128]
    default: 124
  key_preference: minor

structure_hints:
  breakdown_required: true
  section_multiples: 8

layer_hints:
  bass:
    suggested: [bass/rolling-sixteenths, bass/root-pulse]
    register: low

forbidden:
  patterns: [drums/trap-*]

Validation output:

{
  "valid": false,
  "errors": [
    {"message": "Tempo 140 outside style range [120, 128]", "severity": "error"},
    {"message": "Pattern drums/trap-hat forbidden by style", "severity": "error"}
  ],
  "suggestions": [
    {"message": "Consider drums/four-on-floor for drums layer", "severity": "info"}
  ]
}

Score IR (Intermediate Representation)

The Score IR is the stable, inspectable contract between arrangement and MIDI:

Arrangement YAML → Score IR (diffable, versioned) → MIDI

Same arrangement → same Score IR → same MIDI. Always.

Schema Excerpt (score_ir/v1)

{
  "schema": "score_ir/v1",
  "name": "my-track",
  "key": "D_minor",
  "tempo": 124,
  "time_signature": {"numerator": 4, "denominator": 4},
  "ticks_per_beat": 480,
  "total_bars": 24,
  "notes": [
    {
      "start_ticks": 0,
      "pitch": 50,
      "duration_ticks": 480,
      "velocity": 90,
      "channel": 1,
      "source_layer": "bass",
      "source_pattern": "bass/root-pulse",
      "source_section": "verse",
      "bar": 0,
      "beat": 0.0
    }
  ],
  "sections": [
    {"name": "intro", "start_ticks": 0, "end_ticks": 15360, "bars": 8}
  ]
}

Canonicalization Rules

  • Notes sorted by (start_ticks, channel, pitch)
  • Sections sorted by start_ticks
  • All times in ticks (480 ticks per beat)
  • Source traceability on every note

Usage

# Compile and inspect
result = compiler.compile(arrangement)
print(result.score_ir.summary())
# {'name': 'my-track', 'total_bars': 32, 'total_notes': 256,
#  'layers': {'drums': 128, 'bass': 64, 'harmony': 64},
#  'pitch_range': (36, 72), 'velocity_range': (60, 110)}

# Compare two versions
diff = old_ir.diff_summary(new_ir)
# {'notes_added': 12, 'notes_removed': 8, 'notes_unchanged': 244,
#  'tempo_changed': False, 'key_changed': False}

# Debug: "Why is this note here?"
for note in result.score_ir.notes:
    if note.pitch == 50 and note.bar == 3:
        print(f"From {note.source_layer}/{note.source_pattern} in {note.source_section}")

Common Workflows

1. Create Track from Style

# Apply style → suggest patterns → add sections → arrange → compile
music_apply_style(arrangement="my-track", style="melodic-techno")
music_suggest_patterns(arrangement="my-track", role="bass")
# Returns: ["bass/rolling-sixteenths", "bass/root-pulse"]

music_add_section(arrangement="my-track", name="intro", bars=8)
music_add_section(arrangement="my-track", name="verse", bars=16)
music_arrange_layer(arrangement="my-track", layer="bass",
                    section_patterns={"intro": None, "verse": "main"})
music_compile_midi(arrangement="my-track")

2. Iterate on Bassline

# Preview → tweak → diff → compile
music_preview_section(arrangement="my-track", section="verse")
music_update_pattern_params(arrangement="my-track", layer="bass",
                            params={"density": "eighth"})
music_diff_ir(arrangement="my-track-v1", other_arrangement="my-track-v2")
# {'notes_added': 32, 'notes_removed': 16, ...}

music_compile_midi(arrangement="my-track")

3. Debug a Bad Result

# Compile to IR → inspect provenance → validate → fix
result = music_compile_to_ir(arrangement="my-track")
# Inspect which pattern produced the wrong notes
# Each note has: source_layer, source_pattern, source_section, bar, beat

music_validate(arrangement="my-track")
# {"valid": false, "errors": [{"message": "Channel conflict on channel 1"}]}

# Fix the issue
music_set_layer_level(arrangement="my-track", name="bass", level=0.8)

4. Extract Stems (IR Round-Trip)

# Compile → modify IR → emit separate MIDI files
ir = music_compile_to_ir(arrangement="my-track")

# Extract just the bass layer
bass_ir = music_modify_ir(ir_json=ir["score_ir"], filter_layers=["bass"])
music_emit_midi_from_ir(ir_json=bass_ir["score_ir"], output_name="bass-stem")

# Extract drums, reduce velocity
drums_ir = music_modify_ir(ir_json=ir["score_ir"],
                           filter_layers=["drums"],
                           velocity_scale=0.8)
music_emit_midi_from_ir(ir_json=drums_ir["score_ir"], output_name="drums-stem")

# Transpose harmony up an octave
harmony_ir = music_modify_ir(ir_json=ir["score_ir"],
                             filter_layers=["harmony"],
                             transpose=12)
music_emit_midi_from_ir(ir_json=harmony_ir["score_ir"], output_name="harmony-high")

MCP Tools

The server provides 37+ tools organized by domain:

Arrangement Tools (6):

  • music_create_arrangement - Create a new arrangement
  • music_get_arrangement - Get arrangement details
  • music_list_arrangements - List all arrangements
  • music_save_arrangement - Save to YAML
  • music_delete_arrangement - Delete an arrangement
  • music_duplicate_arrangement - Clone an arrangement

Structure Tools (11):

  • music_add_section, music_remove_section, music_reorder_sections
  • music_set_section_energy, music_add_layer, music_remove_layer
  • music_arrange_layer, music_mute_layer, music_solo_layer
  • music_set_layer_level, music_set_harmony

Pattern Tools (6):

  • music_list_patterns, music_describe_pattern
  • music_add_pattern, music_remove_pattern
  • music_update_pattern_params, music_copy_pattern_to_project

Style Tools (6):

  • music_list_styles, music_describe_style
  • music_suggest_patterns, music_validate_style
  • music_apply_style, music_copy_style_to_project

Compilation Tools (8):

  • music_compile_midi, music_preview_section
  • music_compile_to_ir, music_diff_ir
  • music_modify_ir, music_emit_midi_from_ir
  • music_export_yaml, music_validate

Humanization (Planned)

Determinism doesn't mean robotic. Humanization is seeded and explicit:

# In arrangement
humanize:
  timing_ms: 8      # ±8ms timing drift
  velocity: 6       # ±6 velocity variation
  seed: 42          # Reproducible randomness

Same seed → same humanization → still deterministic. Change the seed to explore variations.

Development

# Clone and install
git clone https://github.com/chuk-ai/chuk-mcp-music
cd chuk-mcp-music
pip install -e ".[dev]"

# Run full check suite
make check    # Linting, types, security, tests (532 tests)

# Run tests with coverage
make test-cov  # Currently at 89% coverage

# Format code
ruff format .
ruff check --fix .

Project Structure

src/chuk_mcp_music/
├── core/           # Music primitives (pitch, rhythm, chord, scale)
├── models/         # Pydantic models (arrangement, pattern, style)
├── arrangement/    # Arrangement management
├── patterns/       # Pattern system and library
│   └── library/    # Built-in patterns (copy these!)
├── styles/         # Style system and library
│   └── library/    # Built-in styles
├── compiler/       # Compilation pipeline
│   ├── arranger.py # Arrangement → Score IR → MIDI
│   ├── score_ir.py # Intermediate representation (versioned, diffable)
│   └── midi.py     # MIDI file generation
├── tools/          # MCP tool implementations
└── async_server.py # MCP server entry point

Roadmap

See roadmap.md for the full design document.

Next up:

  • Export profiles (GM, Ableton, Logic drum maps)
  • CC automation lanes (filter sweeps, sidechain ducking)
  • Real-time preview via Web MIDI
  • Pattern learning from MIDI import

License

MIT

Reviews

No reviews yet

Sign in to write a review