# Gradus Notation (npm · @gradusmusic/notation-mcp)

Render music notation (SVG/MusicXML/MIDI) + theory search. Free tool from the Gradus curriculum.

- Trust score: 65/100 (medium)
- Change this week: +25
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- npm · `@gradusmusic/notation-mcp`: 65/100 (this document), [markdown](https://verifymcp.io/servers/com-gradusmusic-notation/gradusmusic-notation-mcp.md), [page](https://verifymcp.io/servers/com-gradusmusic-notation/gradusmusic-notation-mcp)

## Channel facts

- Registry: `npm`
- Package: `@gradusmusic/notation-mcp`
- Version: `0.2.6`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Supply Chain Security**: 86/100
  - No malware found by supply-chain analysis.
  - Only part of the dependency tree could be resolved (94 of 98), so this covers what we could see, not the whole tree.
  - No install/post-install scripts declared.
  - Only part of the dependency tree could be resolved (94 of 98), so this covers what we could see, not the whole tree.
- **Provenance & Transparency**: 45/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 10 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 59/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 4820 tokens (~482/item across 10 items; 10 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 27/100
  - Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 92/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 76% of tool parameters carry a description.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add com-gradusmusic-notation -- npx -y @gradusmusic/notation-mcp
```

### Codex

```bash
codex mcp add com-gradusmusic-notation -- npx -y @gradusmusic/notation-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "com-gradusmusic-notation": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "@gradusmusic/notation-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add com-gradusmusic-notation --command npx --arg -y --arg @gradusmusic/notation-mcp
```

### Hermes

```yaml
mcp_servers:
  com-gradusmusic-notation:
    command: "npx"
    args: ["-y", "@gradusmusic/notation-mcp"]
```

### Other

```json
{
  "mcpServers": {
    "com-gradusmusic-notation": {
      "command": "npx",
      "args": [
        "-y",
        "@gradusmusic/notation-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-03 (score 65, +1)

- [functional improvement] Stability: unverified → 0.27

### 2026-08-02 (score 64, +48)

- [security regression] Provenance: unverified → fail
- [security improvement] Malware scan: unverified → pass
- [security improvement] Install scripts: unverified → pass
- [security improvement] Known CVEs: unverified → partial
- [security] Stability: Stability not yet verified: not enough scan history yet (needs a 30-day window).
- [functional improvement] Schema quality: unverified → excellent
- [functional improvement] Dependency health: unverified → partial
- [functional improvement] Maintenance: unverified → pass
- [functional improvement] MCP protocol: unverified → pass
- [functional improvement] License: unverified → pass
- [functional] Licence: MIT

### 2026-07-31 (score 16, −24)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-27 (score 40)

First indexed and scored.

## MCP tools (10)

### `notation_render` (~778 tokens)

Render music notation from a JSON score into three output formats in a single call: inline SVG (engraved through Verovio with the Bravura SMuFL font — same engine as IMSLP and the Music Encoding Initiative), round-trippable MusicXML (opens cleanly in Sibelius, Finale, MuseScore, Dorico), and base64-encoded SMF Type-1 MIDI.

WHEN TO USE: when the agent needs to surface engraved notation to its user (composer demoing an idea, teacher making a worksheet, content creator embedding a notation example), or when converting a JSON score to file formats other agents/tools can consume (MusicXML for desktop notation software, MIDI for sequencers).

WHEN NOT TO USE: if you are not sure the input is well-formed → call notation_validate first (much cheaper, no rendering); if you do not yet know the input format → call notation_examples or notation_schema; if you need theory facts before composing → call knowledge_search first.

INPUT: requires `instruments` (non-empty array). Each instrument has `name` (required; clef is inferred from the name — "Cello" → bass, "Viola" → alto, "Timpani" → percussion — overridable via `clef`) and either `notes` (single-voice shortcut) or `voices` (multi-voice). Pitches use scientific notation ("C4", "F#5", "Bb3"); durations use letter codes ("w" "h" "q" "8" "16" "32" "64" with optional "." for dotted, ".." for double-dotted). Bar lines are inferred from `timeSignature` (default [4,4]); notes that cross a bar line are split and tied automatically — agents do not count beats.

OUTPUT (JSON, success): { ok: true, requestId, outputs: { svg: string, musicxml: string, midiBase64: string }, meta: { measureCount, instrumentCount, voiceCount, durationBeats, renderTimeMs }, warnings?: ValidationIssue[], attribution }. ValidationIssue = { path, code, message, fix?, severity: "error"|"warning" }. SVG is typically 60-100 KB with Bravura font embedded; MusicXML is a few KB; MIDI is sub-1 KB.

OUTPUT (JSON, validation failure): { ok: false, requestId, errors: V…

Input parameters:

- `composer` (string): Optional composer rendered top-right.
- `instruments` (array, required): One or more instrument staves. At least one required.
- `keySignature` (string): Human-readable key signature. Accepts "C major", "G major", "D minor", "F# major", "Bb minor", etc.
- `tempo` (number): Tempo in BPM. Affects MIDI timing only; not visually rendered.
- `timeSignature` (array): [beats, beat-unit]. Common values: [4,4], [3,4], [6,8], [2,2], [12,8].
- `title` (string): Optional title rendered above the score.

### `notation_validate` (~510 tokens)

Pre-flight validate a notation_render input without rendering. Returns errors with concrete `fix` field that tells the agent exactly how to repair malformed input. Substantially cheaper than notation_render because it skips the Verovio engraving step entirely.

WHEN TO USE: when iterating on input shape and uncertain whether it is well-formed; when input came from user-supplied or LLM-generated data that may be malformed; when surfacing precise validation errors to your end user before committing to a full render; when learning the input format (combine with notation_examples to see canonical inputs).

WHEN NOT TO USE: if input is known to be valid (just call notation_render directly — it validates internally too); if you have not learned the schema yet (call notation_schema or notation_examples first to see the format).

INPUT: identical shape to notation_render. `instruments` array required (each with `name` and `notes` or `voices`).

OUTPUT (JSON, valid): { ok: true, requestId, valid: true, warnings: ValidationIssue[], meta: { measureCount, instrumentCount, voiceCount, durationBeats }, attribution }. Warnings are non-blocking notices (e.g. unusual time signature handling).

OUTPUT (JSON, invalid): { ok: false, requestId, valid: false, errors: ValidationIssue[], warnings, attribution }. Each ValidationIssue: { path: "instruments[0].voices[0].notes[3]", code: "BAD_PITCH"|"BAD_DURATION"|"MISSING_FIELD"|"BAD_KEY_SIG"|..., message, fix: "Use scientific notation: letter A-G + optional # or b + octave number, e.g. C4, F#5, Bb3.", severity: "error"|"warning" }. Surface the `fix` to your user or use it to auto-repair.

EXAMPLE INPUT: { "instruments": [{ "name": "Violin", "notes": ["C5/q","D5/q","E5/q","F5/q"] }] }
TYPICAL LATENCY: 30-100 ms (no Verovio render; pure JSON-to-Score conversion + bar-line arithmetic).

Input parameters:

- `composer` (string)
- `instruments` (array, required): Same shape as notation_render. See notation_schema for the full JSON Schema.
- `keySignature` (string)
- `tempo` (number)
- `timeSignature` (array)
- `title` (string)

### `knowledge_search` (~767 tokens)

Search the Gradus music-theory knowledge base for authoritative source material. The corpus includes hand-authored curriculum prose, Bach chorale analysis (408 chorales), score commentaries on 50+ orchestral works, and primary historical sources from Fux (1725) through Boulanger.

WHEN TO USE: before generating notation if you need to look up a specific theory fact — typical voice leading for a Neapolitan-to-V resolution, idiomatic figured-bass realizations of a particular cadence, what makes a chromatic mediant feel like one composer's style versus another. Hitting this first prevents the agent from inventing chord progressions that are stylistically wrong.

WHEN NOT TO USE: for generic music vocabulary ("what is a chord?") that any LLM already knows; for non-theory queries like composer biographies, performance recommendations, or history dates — those are out of scope; for fetching actual score notation (use notation_render or notation_examples instead).

INPUT: provide EITHER `topics` (kebab-case tags) OR `step` (curriculum step 1-49). Topics are stronger; step is the fallback when you do not know the canonical topic tag. Both empty returns a MISSING_QUERY error.

OUTPUT (JSON): { ok: true, requestId, chunks: [{ id, sourceType, sourceId, title, content, composer?, era?, topics: string[], curriculumSteps: number[], tokenEstimate }], meta: { query, returnedCount, totalTokens, responseTimeMs }, attribution }. `sourceType` is one of: kg_concept, score_analysis, score_commentary, bach_chorale_analysis, composer, dictionary, curriculum, lesson_content, practicum, voice_leading, fugue, chorale_exercise, etc. Empty `chunks: []` when nothing matched the topics — agent should fall back to its own knowledge or try a different topic tag.

EXAMPLE INPUT: { "topics": ["voice-leading", "deceptive-cadence"], "limit": 3 }
TYPICAL LATENCY: 200-700 ms (one Voyage 3 embedding call + Supabase pgvector RPC).

Input parameters:

- `limit` (integer): Maximum chunks to return. Default 8 is right for most queries; raise for broad surveys, lower for tight context budgets.
- `maxTokens` (integer): Token budget for the combined chunk content. Default 1500 fits comfortably in most agent context windows. The endpoint greedy-selects highest-similarity chunks within this budget.
- `step` (integer): Curriculum step number (1-49). Fallback when you do not know the topic tag. Maps to the Gradus 10-stage curriculum: Stage I 1-7 (single voice, intervals, scales), II 8-13 (counterpoint, all 5 species…
- `topics` (array): Topic tags in kebab-case. Matched semantically via Voyage 3 Large embeddings plus a topic-overlap boost; exact-match is not required, so close synonyms work. Examples: ["voice-leading","deceptive-cad…

### `notation_examples` (~316 tokens)

Fetch six canonical example inputs covering the most common notation_render use cases: single-line melody, two-voice counterpoint (cantus firmus + counterpoint), block-chord progression (cadence), mixed rhythms with dynamics + articulations, four-instrument string quartet, and notes tied across a bar line.

WHEN TO USE: first encounter with this MCP — fetch examples to learn the input format with concrete worked patterns; before a notation_render call when uncertain how to express a particular musical structure (a chord, a multi-voice staff, a tied note); to show your end user what kinds of notation are possible.

WHEN NOT TO USE: after caching the response (the examples are stable across the v1 API; fetch once and reuse forever); when you only need formal type definitions (use notation_schema for JSON Schema instead).

INPUT: none. Pass an empty object `{}`.

OUTPUT (JSON): { ok: true, examples: [{ id, title, description, use_when, input: NotationInput }], docs: { schema, render, validate }, attribution }. Six examples with stable ids: single-melody, two-voice-counterpoint, chord-progression, mixed-rhythms, string-quartet-snippet, tied-across-bar. Each `input` is a complete payload that can be passed directly to notation_render.

EXAMPLE INPUT: {} (no parameters)
TYPICAL LATENCY: 30-200 ms. Response is cached at CDN with long TTL — subsequent calls are essentially free.

### `notation_schema` (~283 tokens)

Fetch the JSON Schema (Draft 2020-12) describing the notation_render input shape. Includes every field, its type, defaults, validation rules (including the shorthand-string regex pattern), and `$defs` for Instrument, VoiceLine, Note, and NoteObject.

WHEN TO USE: first encounter with this MCP and you want machine-readable type definitions; building a client that validates input client-side before calling notation_render; generating code (TypeScript types, Zod schemas, etc.) that consumes the format.

WHEN NOT TO USE: after caching the response (stable across the v1 API); when you want learning-by-example (use notation_examples instead — worked payloads are easier to read than schema definitions).

INPUT: none. Pass an empty object `{}`.

OUTPUT (JSON): { ok: true, schema: { $schema: "https://json-schema.org/draft/2020-12/schema", $id, title, type: "object", required: ["instruments"], properties, $defs: { Instrument, VoiceLine, Note, NoteObject } }, docs: { examples, render, validate }, attribution }. The `schema` field is a complete JSON Schema document.

EXAMPLE INPUT: {} (no parameters)
TYPICAL LATENCY: 30-200 ms. Response is cached at CDN with long TTL — subsequent calls are essentially free.

### `theory_validate_ranges` (~437 tokens)

Check every note in a Score JSON against its instrument's standard practical range. Returns warnings for out-of-range pitches with measure, beat, MIDI number, and severity.

WHEN TO USE: after parsing a MusicXML file with theory_parse_xml and before analysis — catch unplayable or extreme notes early; when generating or editing a score programmatically and want to verify instrument idiomatic range; when a student submits a composition for critique and range errors should be flagged.

SEVERITY LEVELS: "error" = note is > 1 semitone outside the practical range; "warn" = note is at the boundary (within 1 semitone).

SUPPORTED INSTRUMENTS (partial name match, case-insensitive): Violin, Viola, Cello, Double Bass, Harp, Flute, Piccolo, Oboe, English Horn, Clarinet, Bass Clarinet, Bassoon, Contrabassoon, Soprano/Alto/Tenor/Baritone Sax, Horn, Trumpet, Trombone, Tuba, Piano, Organ, Marimba, Xylophone, Vibraphone, Glockenspiel, Timpani, Soprano/Mezzo/Alto/Tenor/Baritone/Bass (voice).

INPUT: a maestroAnalyst Score object — obtain one by calling theory_parse_xml with MusicXML text.

OUTPUT: { ok, requestId, warnings: [{ measure, beat, pitch, midi, partId, instrumentName, min, max, severity }], attribution }. Empty `warnings` array means all notes are in range.

EXAMPLE: pass a Score with a Violin part containing a note at A7 (MIDI 105) — it will return severity "error" since violin tops out around B7/MIDI 107 but A7 is beyond practical range.

Input parameters:

- `keySignatures` (array)
- `measureCount` (integer)
- `notes` (array, required): Flat array of Note objects from a Score.
- `parts` (array, required): Array of PartInfo objects ({ id, name }).
- `timeSignatures` (array)

### `theory_respell` (~378 tokens)

Suggest the preferred enharmonic spelling for one or more pitches in a given key context. Picks the spelling that is diatonic to the key (e.g. F# in G major, Gb in F major). Uses the key's accidental preference (sharps/flats) as a tiebreaker for chromatic passing tones.

WHEN TO USE: after OMR (optical music recognition) to correct mis-spelled accidentals; when generating notation and unsure whether to write F# or Gb; when transposing — respell after the semitone shift to maintain diatonic spelling; before calling notation_render to clean up accidentals.

INPUT: { keyContext: string, pitches: string[] } OR { keyContext: string, pitch: string }. Pitch strings use scientific notation: "F#4", "Bb3", "C5", "Eb4".

OUTPUT: { ok, requestId, keyContext, results: [{ input, output, changed }], attribution }. `changed` is true when the spelling was adjusted.

EXAMPLES:
  { keyContext: "F major", pitches: ["F#4", "Bb4", "E4"] }
  → F#4→Gb4 (Gb is diatonic in F major), Bb4 unchanged, E4 unchanged.
  { keyContext: "G major", pitch: "Gb4" } → Gb4→F#4 (F# is diatonic in G major).

Input parameters:

- `keyContext` (string, required): Key signature string, e.g. "C major", "G major", "Bb minor", "F# major".
- `pitch` (string): Single pitch string (scientific notation). Use this OR `pitches`.
- `pitches` (array): Array of pitch strings. Use this OR `pitch`.

### `theory_parse_xml` (~294 tokens)

Parse a MusicXML string into a maestroAnalyst Score object. The Score is the input type for theory_validate_ranges and can be passed to any maestroAnalyst analysis function.

WHEN TO USE: when you have a MusicXML file (e.g., exported from Sibelius, Finale, MuseScore, Dorico, or produced by notation_render) and want to analyse it — detect key, check ranges, respell accidentals. This is the entry point for the native analysis pipeline that replaces music21.

LIMITATIONS: accepts plain MusicXML text (score-partwise format). Does NOT accept .mxl ZIP archives — decompress first if needed. Score-timewise and other non-partwise formats are not supported.

INPUT: { xml: string } — the full MusicXML document text.

OUTPUT: { ok, requestId, score: Score, meta: { partCount, noteCount, measureCount }, attribution }. The Score JSON can then be passed to theory_validate_ranges or any other theory tool.

TYPICAL SIZE: a Bach chorale (4 parts, 32 measures) produces a Score with ~512 notes. A Beethoven symphony movement (12 parts, 400 measures) may produce 8,000+ notes. Fit within your context budget or process in chunks.

Input parameters:

- `xml` (string, required): Raw MusicXML document string (score-partwise format). Must begin with <?xml or <score-partwise.

### `theory_analyze_score` (~543 tokens)

One-shot endpoint: parse MusicXML → run the full MaestroAnalyzer harmonic analysis pipeline → query the Gradus Knowledge Base (GKB) for curated theory chunks matched to the score's detected features. Returns both the algorithmic analysis and relevant hand-authored knowledge in a single call.

WHEN TO USE: when an agent has a MusicXML score and wants to know what's harmonically interesting about it — key, local-key trajectory, chord analyses with Roman numerals, cadences, phrase structure, style period, AND relevant theory context from the GKB (voice-leading rules, harmonic vocabulary, orchestration notes, historical context). This is the richest single-call analysis available.

WHEN NOT TO USE: if you only need range checking (theory_validate_ranges); if you only need re-spelling (theory_respell); if you want raw GKB search without score analysis (knowledge_search).

INPUT: { xml: string, maxKnowledgeTokens?: number (default 1500), includeKnowledge?: boolean (default true) }

OUTPUT: {
  meta: { partCount, noteCount, measureCount },
  analysis: {
    overallKey: { key, mode, confidence },
    localKeys: [{ measure, key, confidence }],
    chordAnalyses: [{ measure, beat, primary, readings: [{ rn, rnAscii, inversion, localKey, confidence }], tendencyTones }],
    cadences: [{ type: "PAC"|"IAC"|"HC"|"DC"|"Plagal"|"Phrygian"|"unclear", ... }],
    phrases: [{ index, measureStart, measureEnd, fermataMeasures }],
  },
  submissionHints: { stylePeriod, focusAreas, rationale },  // inferred style heuristic
  knowledge: {
    topics: string[],   // GKB tags derived from the analysis
    chunks: [{ title, content, sourceType, era, composer, curriculumSteps }],
    totalTokens: number,
  },
}

TYPICAL LATENCY: 200-600 ms (analysis is pure JS; GKB adds one Voyage embedding call ~100-200 ms).

Input parameters:

- `includeKnowledge` (boolean): Set false to skip GKB lookup and get analysis-only. Useful when knowledge is not needed or when latency matters.
- `maxKnowledgeTokens` (integer): Token budget for GKB knowledge chunks. Raise for richer context, lower for tight budgets.
- `options` (object): Optional AnalyzeScoreOptions: { useLocalKeys: "phrase"|"window"|"overall", localKeyHalfWindow?: number }.
- `xml` (string, required): Raw MusicXML document string (score-partwise format).

### `theory_pitch_utils` (~514 tokens)

A collection of fast, pure pitch-utility operations that replace the most-used music21 pitch functions with zero network round-trip.

OPERATIONS:
  midi_to_pitch   — MIDI number → pitch string. midi=60 → "C4". preferFlats=true → "Db" spellings.
  pitch_to_midi   — pitch string → MIDI number. "C4"→60, "F#5"→78, "Bb3"→46. Returns null for rests.
  interval_name   — semitone count → interval quality string. 0→"P1" 3→"m3" 4→"M3" 7→"P5" 12→"P8". Compound intervals: 14→"M2+8".
  transpose_pitch — shift a pitch by semitones. "C4"+7→"G4", "E5"+-2→"D5". preferFlats controls black-key spelling.

WHEN TO USE: fast arithmetic during score generation or analysis without invoking a full analysis pipeline; populating MIDI output tables; labeling intervals in educational contexts; transposing individual notes while composing.

INPUT: { op: string, ...params } where op is one of the operations above.

EXAMPLES:
  { op: "midi_to_pitch", midi: 60 }                      → { pitch: "C4" }
  { op: "pitch_to_midi", pitch: "F#5" }                  → { midi: 78 }
  { op: "interval_name", semitones: 7 }                  → { interval: "P5" }
  { op: "transpose_pitch", pitch: "C4", semitones: 7 }   → { pitch: "G4" }
  { op: "transpose_pitch", pitch: "E4", semitones: 1, preferFlats: true } → { pitch: "F4" }

Input parameters:

- `midi` (integer): MIDI number (0–127). Required for midi_to_pitch.
- `op` (string, required): Operation to perform.
- `pitch` (string): Pitch string e.g. "C4", "F#5". Required for pitch_to_midi and transpose_pitch.
- `preferFlats` (boolean): Use flat spellings for black keys (Db instead of C#). Optional.
- `semitones` (integer): Semitone offset. Required for interval_name and transpose_pitch.

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/com-gradusmusic-notation/gradusmusic-notation-mcp#diagnostics

## Score history

- 2026-08-03: 65
- 2026-08-02: 64
- 2026-08-01: 16
- 2026-07-31: 16
- 2026-07-30: 40
- 2026-07-28: 40
- 2026-07-27: 40

## Links

- npm package: https://www.npmjs.com/package/@gradusmusic/notation-mcp
- Socket report: https://socket.dev/npm/package/@gradusmusic/notation-mcp
- Repository: https://github.com/delmas41/gradusnotation
- Website: https://gradusmusic.com/notation-api
- Changelog RSS feed: https://verifymcp.io/servers/com-gradusmusic-notation/gradusmusic-notation-mcp/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/com-gradusmusic-notation/gradusmusic-notation-mcp/changelog.json
- HTML version of this page: https://verifymcp.io/servers/com-gradusmusic-notation/gradusmusic-notation-mcp
