MusicXML Comparer

io.github.raulkiviPublished by GitHub account raulkiviv0.2.1Updated Nov 3, 2026

Music-aware, multi-level comparison of two MusicXML files

VerifiedSTDIODesktop onlyData & AnalyticsMedia & Design

Overview

AI-generated overview

Compares two MusicXML scores with music-aware, multi-level diffs, from an overall similarity score down to individual notes.

What you can do
  • Compare two scores inline or by file path and get a structured diff result
  • Get a quick similarity score and summary statistics without note-level detail
  • List note-level changes filtered by part name and measure range
  • Generate a human-readable comparison report covering parts, measures, key and time signature changes
  • Export MusicXML with per-note color annotations marking the diff, inline or as files
  • Describe a score's title, key, time, tempo, parts, measures and duration
When to use it
Useful when checking different editions or arrangements of the same piece, evaluating OMR or conversion output against a known-good reference, round-trip fidelity checks, regression testing after model or conversion changes, or confirming that edits only touched intended elements.
Requirements
Runs locally as a Python package (comparer-mcp) via uvx; needs Python 3.11+ and the music21 and mcp libraries, with no system libraries required. File paths must lie inside the output folder (SHEET_MUSIC_OUTPUT_ROOT, default the working directory), a client root, or a folder listed in SHEET_MUSIC_INPUT_ROOTS. Desktop client only.
Before you install
The annotated export writes MusicXML files to disk when results are large. File-path inputs are restricted to configured folders, so the server reads local score files within those roots. No authentication or credentials are declared.

Installation

In SourceWeft

  1. Open MusicXML Comparer in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

comparer-mcp

MCP server for music-aware comparison of MusicXML files. Provides structured, multi-level diffs that report what changed between two scores — from global similarity down to individual notes.

What it does

  • Version / arrangement comparison — Compare different editions, arrangements, or simplifications of the same piece
  • OMR quality evaluation — Compare omr-mcp output against a known-good reference
  • Round-trip fidelity — Verify MusicXML → ABC → MusicXML through musicxml-abc-mcp
  • Regression testing — Detect regressions when OMR models or conversion logic change
  • Score editing validation — Confirm LLM-driven edits only changed intended elements

Installation

bash
bash install.sh

See SETUP.md for client config snippets (Claude Desktop, Cursor, Windsurf, Continue, Zed) and TROUBLESHOOTING.md if something doesn't work.

MCP Tools

ToolDescription
compare_musicxmlFull diff of two scores, inline or by path → structured ComparisonResult JSON
compare_musicxml_filesFull diff of two MusicXML file paths (.musicxml, .xml, or .mxl) inside the allowed folders → structured ComparisonResult JSON
quick_similaritySimilarity score (0.0–1.0) + summary statistics only, no per-note detail
list_changesNote-level diffs (operation != MATCH), optionally filtered by part name and/or measure range
generate_comparison_reportHuman-readable version comparison report — similarity headline, missing/extra parts and measures, key/time signature changes, note-level differences grouped by measure range (e.g. "transposed by 3 semitones in measures 17-24")
export_annotated_musicxmlMusicXML with per-note color annotations marking the diff. Small results come back inline (reference_annotated_musicxml + target_annotated_musicxml); large ones are written to files and returned as *_path fields plus a score_summary (return: auto/path/inline). Pass the paths to render-mcp's render_to_pdf/render_to_image as musicxml_path
describe_scoreTitle, key, time, tempo, parts (IDs, names, clefs, ranges), measures and duration of a score, without returning the score
health_checkServer status — verifies music21 is importable and runs a self-comparison smoke test
list_capabilitiesServer metadata per project conventions

Each score can be passed inline (reference_xml, target_xml) or as a file path (reference_path, target_path; .musicxml, .xml or .mxl), exactly one of each pair. Paths save copying whole scores through the conversation. They must be inside the output folder (SHEET_MUSIC_OUTPUT_ROOT, default the working directory), one of the client's roots, or a folder listed in SHEET_MUSIC_INPUT_ROOTS.

json
// Compare two MusicXML strings{  "tool": "compare_musicxml",  "arguments": {    "reference_xml": "<score-partwise>...</score-partwise>",    "target_xml": "<score-partwise>...</score-partwise>"  }}
// Compare two files{  "tool": "compare_musicxml_files",  "arguments": {    "reference_path": "/path/to/reference.musicxml",    "target_path": "/path/to/omr_output.musicxml"  }}
// Quick similarity check{  "tool": "quick_similarity",  "arguments": {    "reference_xml": "<score-partwise>...</score-partwise>",    "target_xml": "<score-partwise>...</score-partwise>"  }}// → {"similarity_score": 0.87, "summary": {...}}
// Filtered changes, e.g. "what changed in the Alto, measures 17-24?"{  "tool": "list_changes",  "arguments": {    "reference_xml": "<score-partwise>...</score-partwise>",    "target_xml": "<score-partwise>...</score-partwise>",    "part": "Alto",    "measures": [17, 24]  }}// → {"changes": [{"measure_number": 18, "operation": "PITCH_CHANGE", "part_name": "Alto", ...}], "count": 1}
// Human-readable report{  "tool": "generate_comparison_report",  "arguments": {    "reference_xml": "<score-partwise>...</score-partwise>",    "target_xml": "<score-partwise>...</score-partwise>"  }}// → {"report": "Overall similarity: 87% (minor differences)\n...", "similarity_score": 0.87}
// Colored MusicXML diff export — chain straight into render-mcp to visualize{  "tool": "export_annotated_musicxml",  "arguments": {    "reference_xml": "<score-partwise>...</score-partwise>",    "target_xml": "<score-partwise>...</score-partwise>"  }}// → {"reference_annotated_musicxml": "...", "target_annotated_musicxml": "...",//    "similarity_score": 0.87, "legend": {"PITCH_CHANGE": "#FF8800", ...}}// For scores over 8 KB (return: "auto", the default):// → {"reference_annotated_musicxml_path": ".../scores/annotated-reference_annotated_musicxml-....musicxml",//    "target_annotated_musicxml_path": "...", "similarity_score": 0.87, "legend": {...},//    "score_summary": {"reference_annotated_musicxml": {...}, "target_annotated_musicxml": {...}}}
// Paths instead of inline text{  "tool": "quick_similarity",  "arguments": {    "reference_path": "scores/reference.musicxml",    "target_path": "scores/omr_output.mxl"  }}

compare_musicxml, compare_musicxml_files, generate_comparison_report, and export_annotated_musicxml all accept an optional options object:

json
{  "expand_repeats": false,        // unfold repeats before comparing (default: false — opt-in)  "normalize_pitch": false,       // transpose transposing instruments to concert/sounding pitch  "ignore_articulations": false,  // exclude articulations from NoteInfo/diffs  "parts": ["Soprano"],           // compare only these parts: IDs, names or voices ("P1", "S");                                  //   part_filter is a deprecated alias  "measures": [1, 32]             // only differences in measures 1-32, inclusive (scores are not                                  //   cut); measure_range is a deprecated alias}

Using the engine directly

The comparison engine (src/comparer_mcp/engine.py) has no MCP dependency and is fully usable as a plain Python library, independent of the MCP server:

bash
cd comparer-mcpuv sync
python
from comparer_mcp.engine import compare, compare_files
# Compare two MusicXML stringsresult = compare(reference_xml, target_xml)
# Compare two files (.musicxml, .xml, or compressed .mxl)result = compare_files("reference.musicxml", "omr_output.mxl")
print(result.similarity_score)   # float, 0.0-1.0print(result.summary)            # ComparisonSummary: parts/measures/notes matched, missing, changedprint(result.part_diffs)         # list[PartDiff] -> list[MeasureDiff] -> list[NoteDiff]

Errors are raised as comparer_mcp.engine.ProcessingError (e.g. for a missing file or invalid MusicXML), carrying a .error_code attribute (FILE_NOT_FOUND, INVALID_INPUT, etc.) matching the format the MCP tools return.

Testing

bash
# Unit testsVIRTUAL_ENV= .venv/bin/pytest tests/ -v -m "not integration"
# Integration tests (real .mxl SATB fixtures shared with omr-mcp)VIRTUAL_ENV= .venv/bin/pytest tests/ -v -m integration
# Install dependenciesuv sync

Dependencies

  • music21 — MusicXML parsing, score object model, stream alignment
  • numpy — distance matrix for note alignment (transitive via music21)
  • mcp — MCP protocol

System requirements

  • Python 3.11+
  • No system libraries required

Phase status

PhaseStatus
Phase 1 — Core comparison (MVP)Complete — 45/45 tests passing (43 unit + 2 integration)
Phase 2 — Rich detail (key/time signature diffs, voice-aware alignment, articulations)Complete — 67/67 tests passing (65 unit + 2 integration)
Phase 3 — MCP server & integration (server.py, all 6 tools, install.sh, SETUP.md, client config examples)Complete — 94/94 tests passing (90 unit + 4 integration)
Phase 4 — Advanced features (options wiring, generate_comparison_report, export_annotated_musicxml)Complete — 134/134 tests passing (127 unit + 7 integration)
UX Wave 2 — Score references (reference_path/target_path, return for the annotated export, describe_score)Complete — 239 unit tests passing (+7 integration)

Source: comparer-mcp/README.md at commit 5fcea5d

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

1
  1. v0.2.1LatestSep 26, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts