Strong tool naming (verb_noun pattern throughout) and comprehensive descriptions (avg 180 chars, well within 10-1024 range). All 23 tools have clear, actionable descriptions explaining WHAT, WHEN, and consequences. Input schemas present for all tools with proper types. Tool annotations (readOnlyHint, destructiveHint, idempotentHint) correctly applied. Key gaps: parameter descriptions are minimal (many lack detail on constraints/formats); output schemas not formally documented in code; error handling guidance sparse; no pagination metadata (total_count, next_cursor) on list operations despite large result sets.
Add a track (by persistent_id from search_library, get_current_track, or a playlist listing) to an existing playlist.
Create a new, empty playlist.
Favorite (or unfavorite) a track by its persistent_id (from search_library, get_current_track, or a playlist listing).
Get info about the currently playing (or paused) track, including player state.
Get full metadata for a single track by its persistent_id (from search_library, get_current_track, or a playlist listing), including genre, year, bpm, date added, play/skip counts, rating, and favorited status — fields the list/search tools omit to stay fast.
Get the current playback volume (0-100).
Parameter descriptions lack constraint details. 'level' in set_volume has no min/max stated in description; 'mode' in set_shuffle/set_repeat lacks enum values in description text (only in docstring). LLMs cannot read JSON Schema, they rely on description text for constraints.
Output schemas not formally documented. Tools return Pydantic models (PlayerStatus, Track, Playlist) but no JSON Schema definitions visible in code. LLMs cannot plan downstream calls without knowing return field names and types.
List operations (search_library, list_playlist_tracks) lack pagination metadata. No total_count or next_cursor returned. With default limit=20 and potentially thousands of tracks, LLMs cannot know if results are complete or how to fetch more.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 63 | 2026-07-28+ | v2 |
List the tracks currently in a playlist, one page at a time (default 50). Each track has both `id` (playlist-scoped — what remove_track_from_playlist expects) and `persistent_id` (stable across contexts — what favorite_track/rate_track/get_track_details/add_track_to_playlist/play_track expect). For large playlists, prefer search_playlist_tracks to find a specific track instead of paging through the whole thing.
List all playlists in the Apple Music library.
Skip to the next track.
Pause playback in Apple Music.
Resume/start playback in Apple Music.
Toggle play/pause in Apple Music.
Start playing a playlist by exact name.
Play a specific track by its persistent_id (from search_library, a playlist listing, or get_current_track).
Go back to the previous track.
Set a track's star rating, 0-100 (20 per star, e.g. 100 = 5 stars, 0 = no rating). persistent_id can come from search_library, get_current_track, or a playlist listing.
Remove a track from a playlist. DESTRUCTIVE and irreversible — always asks the user to confirm before removing. track_id must be a playlist-scoped id from list_playlist_tracks, not a search_library id.
Search the local Apple Music library by track name, artist, or album.
Search within a single playlist by track name, artist, or album. Fast even on very large playlists (thousands of tracks) since the filtering happens inside Music.app rather than by listing every track. Prefer this over list_playlist_tracks when looking for a specific song.
Seek to a position (in seconds) within the currently playing track.
Set repeat mode: "off", "one" (repeat current track), or "all".
Turn shuffle on/off, optionally also setting the shuffle mode ("songs", "albums", or "groupings").
Set playback volume (0-100).
Error handling not visible in source. No try/catch blocks, validation, or error messages shown. If set_volume receives 150 or set_repeat receives 'invalid', what error does the LLM see? Bare exceptions will not guide recovery.
remove_track_from_playlist uses ctx.elicit() for confirmation, but elicitation is not a standard MCP feature in the current spec (2026-07-28). This pattern may not work with all MCP clients. Verify compatibility with your target client.