Awesome-openclaw-skills apple-music-2
Apple Music integration via AppleScript (macOS) or MusicKit API
git clone https://github.com/sundial-org/awesome-openclaw-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/sundial-org/awesome-openclaw-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/apple-music-2" ~/.claude/skills/sundial-org-awesome-openclaw-skills-apple-music-2 && rm -rf "$T"
T=$(mktemp -d) && git clone --depth=1 https://github.com/sundial-org/awesome-openclaw-skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/apple-music-2" ~/.openclaw/skills/sundial-org-awesome-openclaw-skills-apple-music-2 && rm -rf "$T"
skills/apple-music-2/SKILL.mdApple Music Integration
Guide for integrating with Apple Music. Covers AppleScript (macOS), MusicKit API (cross-platform), and the critical library-first requirement.
When to Use
Invoke when users ask to:
- Manage playlists (create, add/remove tracks, list)
- Control playback (play, pause, skip, volume)
- Search catalog or library
- Add songs to library
- Access listening history or recommendations
Critical Rule: Library-First Workflow
You CANNOT add catalog songs directly to playlists.
Songs must be in the user's library first:
- ❌ Catalog ID → Playlist (fails)
- ✅ Catalog ID → Library → Playlist (works)
Why: Playlists use library IDs (
i.abc123), not catalog IDs (1234567890).
This applies to both AppleScript and API approaches.
Platform Comparison
| Feature | AppleScript (macOS) | MusicKit API |
|---|---|---|
| Setup required | None | Dev account + tokens |
| Playlist management | Full | API-created only |
| Playback control | Full | None |
| Catalog search | No | Yes |
| Library access | Instant | With tokens |
| Cross-platform | No | Yes |
AppleScript (macOS)
Zero setup. Works immediately with the Music app.
Run via Bash:
osascript -e 'tell application "Music" to playpause' osascript -e 'tell application "Music" to return name of current track'
Multi-line scripts:
osascript <<'EOF' tell application "Music" set t to current track return {name of t, artist of t} end tell EOF
Available Operations
| Category | Operations |
|---|---|
| Playback | play, pause, stop, resume, next track, previous track, fast forward, rewind |
| Player State | player position, player state, sound volume, mute, shuffle enabled/mode, song repeat |
| Current Track | name, artist, album, duration, time, rating, loved, disliked, genre, year, track number |
| Library | search, list tracks, get track properties, set ratings |
| Playlists | list, create, delete, rename, add tracks, remove tracks, get tracks |
| AirPlay | list devices, select device, current device |
Track Properties (Read)
tell application "Music" set t to current track -- Basic info name of t -- "Hey Jude" artist of t -- "The Beatles" album of t -- "1 (Remastered)" album artist of t -- "The Beatles" composer of t -- "Lennon-McCartney" genre of t -- "Rock" year of t -- 1968 -- Timing duration of t -- 431.0 (seconds) time of t -- "7:11" (formatted) start of t -- start time in seconds finish of t -- end time in seconds -- Track info track number of t -- 21 track count of t -- 27 disc number of t -- 1 disc count of t -- 1 -- Ratings rating of t -- 0-100 (20 per star) loved of t -- true/false disliked of t -- true/false -- Playback played count of t -- 42 played date of t -- date last played skipped count of t -- 3 skipped date of t -- date last skipped -- IDs persistent ID of t -- "ABC123DEF456" database ID of t -- 12345 end tell
Track Properties (Writable)
tell application "Music" set t to current track set rating of t to 80 -- 4 stars set loved of t to true set disliked of t to false set name of t to "New Name" -- rename track set genre of t to "Alternative" set year of t to 1995 end tell
Player State Properties
tell application "Music" player state -- stopped, playing, paused, fast forwarding, rewinding player position -- current position in seconds (read/write) sound volume -- 0-100 (read/write) mute -- true/false (read/write) shuffle enabled -- true/false (read/write) shuffle mode -- songs, albums, groupings song repeat -- off, one, all (read/write) current track -- track object current playlist -- playlist object current stream URL -- URL if streaming end tell
Playback Commands
tell application "Music" -- Play controls play -- play current selection pause stop resume playpause -- toggle play/pause next track previous track fast forward rewind -- Play specific content play (first track of library playlist 1 whose name contains "Hey Jude") play user playlist "Road Trip" -- Settings set player position to 60 -- seek to 1:00 set sound volume to 50 -- 0-100 set mute to true set shuffle enabled to true set song repeat to all -- off, one, all end tell
Library Queries
tell application "Music" -- All library tracks every track of library playlist 1 -- Search by name tracks of library playlist 1 whose name contains "Beatles" -- Search by artist tracks of library playlist 1 whose artist contains "Beatles" -- Search by album tracks of library playlist 1 whose album contains "Abbey Road" -- Combined search tracks of library playlist 1 whose name contains "Hey" and artist contains "Beatles" -- By genre tracks of library playlist 1 whose genre is "Rock" -- By year tracks of library playlist 1 whose year is 1969 -- By rating tracks of library playlist 1 whose rating > 60 -- 3+ stars -- Loved tracks tracks of library playlist 1 whose loved is true -- Recently played (sort by played date) tracks of library playlist 1 whose played date > (current date) - 7 * days end tell
Playlist Operations
tell application "Music" -- List all playlists name of every user playlist -- Get playlist user playlist "Road Trip" first user playlist whose name contains "Road" -- Create playlist make new user playlist with properties {name:"New Playlist", description:"My playlist"} -- Delete playlist delete user playlist "Old Playlist" -- Rename playlist set name of user playlist "Old Name" to "New Name" -- Get playlist tracks every track of user playlist "Road Trip" name of every track of user playlist "Road Trip" -- Add track to playlist (must be library track) set targetPlaylist to user playlist "Road Trip" set targetTrack to first track of library playlist 1 whose name contains "Hey Jude" duplicate targetTrack to targetPlaylist -- Remove track from playlist delete (first track of user playlist "Road Trip" whose name contains "Hey Jude") -- Playlist properties duration of user playlist "Road Trip" -- total duration time of user playlist "Road Trip" -- formatted duration count of tracks of user playlist "Road Trip" end tell
AirPlay
tell application "Music" -- List AirPlay devices name of every AirPlay device -- Get current device current AirPlay devices -- Set output device set current AirPlay devices to {AirPlay device "Living Room"} -- Multiple devices set current AirPlay devices to {AirPlay device "Living Room", AirPlay device "Kitchen"} -- Device properties set d to AirPlay device "Living Room" name of d kind of d -- computer, AirPort Express, Apple TV, AirPlay device, Bluetooth device active of d -- true if playing available of d -- true if reachable selected of d -- true if in current devices sound volume of d -- 0-100 end tell
String Escaping
Always escape user input:
def escape_applescript(s): return s.replace('\\', '\\\\').replace('"', '\\"') safe_name = escape_applescript(user_input) script = f'tell application "Music" to play user playlist "{safe_name}"'
Limitations
- No catalog access - only library content
- macOS only - no Windows/Linux
MusicKit API
Cross-platform but requires Apple Developer account ($99/year) and token setup.
Authentication
Requirements:
- Apple Developer account
- MusicKit key (.p8 file) from developer portal
- Developer token (JWT, 180 day max)
- User music token (browser OAuth)
Generate developer token:
import jwt, datetime with open('AuthKey_XXXXXXXXXX.p8') as f: private_key = f.read() token = jwt.encode( { 'iss': 'TEAM_ID', 'iat': int(datetime.datetime.now().timestamp()), 'exp': int((datetime.datetime.now() + datetime.timedelta(days=180)).timestamp()) }, private_key, algorithm='ES256', headers={'alg': 'ES256', 'kid': 'KEY_ID'} )
Get user token: Browser OAuth to
https://authorize.music.apple.com/woa
Headers for all requests:
Authorization: Bearer {developer_token} Music-User-Token: {user_music_token}
Base URL:
https://api.music.apple.com/v1
Available Endpoints
Catalog (Public - dev token only)
| Endpoint | Method | Description |
|---|---|---|
| GET | Search songs, albums, artists, playlists |
| GET | Song details |
| GET | Album details |
| GET | Album tracks |
| GET | Artist details |
| GET | Artist's albums |
| GET | Artist's top songs |
| GET | Similar artists |
| GET | Playlist details |
| GET | Top charts |
| GET | All genres |
| GET | Search autocomplete |
| GET | Radio station |
Library (Requires user token)
| Endpoint | Method | Description |
|---|---|---|
| GET | All library songs |
| GET | All library albums |
| GET | All library artists |
| GET | All library playlists |
| GET | Playlist details |
| GET | Playlist tracks |
| GET | Search library |
| POST | Add to library |
| GET | Get library ID from catalog ID |
Playlist Management
| Endpoint | Method | Description |
|---|---|---|
| POST | Create playlist |
| POST | Add tracks to playlist |
Personalization
| Endpoint | Method | Description |
|---|---|---|
| GET | Personalized recommendations |
| GET | Frequently played |
| GET | Recently played |
| GET | Recently added |
Ratings
| Endpoint | Method | Description |
|---|---|---|
| GET | Get song rating |
| PUT | Set song rating |
| DELETE | Remove rating |
| GET/PUT/DELETE | Album ratings |
| GET/PUT/DELETE | Playlist ratings |
Storefronts
| Endpoint | Method | Description |
|---|---|---|
| GET | All storefronts |
| GET | Storefront details |
| GET | User's storefront |
Common Query Parameters
| Parameter | Description | Example |
|---|---|---|
| Search query | |
| Resource types | |
| Results per page (max 25) | |
| Pagination offset | |
| Related resources | |
| Additional attributes | |
| Language code | |
Search Example
GET /v1/catalog/us/search?term=wonderwall&types=songs&limit=10 Response: { "results": { "songs": { "data": [{ "id": "1234567890", "type": "songs", "attributes": { "name": "Wonderwall", "artistName": "Oasis", "albumName": "(What's the Story) Morning Glory?", "durationInMillis": 258773, "releaseDate": "1995-10-02", "genreNames": ["Alternative", "Music"] } }] } } }
Library-First Workflow (Complete)
Adding a catalog song to a playlist requires 4 API calls:
import requests headers = { "Authorization": f"Bearer {dev_token}", "Music-User-Token": user_token } # 1. Search catalog r = requests.get( "https://api.music.apple.com/v1/catalog/us/search", headers=headers, params={"term": "Wonderwall Oasis", "types": "songs", "limit": 1} ) catalog_id = r.json()['results']['songs']['data'][0]['id'] # 2. Add to library requests.post( "https://api.music.apple.com/v1/me/library", headers=headers, params={"ids[songs]": catalog_id} ) # 3. Get library ID (catalog ID → library ID) r = requests.get( f"https://api.music.apple.com/v1/catalog/us/songs/{catalog_id}/library", headers=headers ) library_id = r.json()['data'][0]['id'] # 4. Add to playlist (library IDs only!) requests.post( f"https://api.music.apple.com/v1/me/library/playlists/{playlist_id}/tracks", headers={**headers, "Content-Type": "application/json"}, json={"data": [{"id": library_id, "type": "library-songs"}]} )
Create Playlist
POST /v1/me/library/playlists Content-Type: application/json { "attributes": { "name": "Road Trip", "description": "Summer vibes" }, "relationships": { "tracks": { "data": [] } } }
Ratings
# Love a song (value: 1 = love, -1 = dislike) PUT /v1/me/ratings/songs/{id} Content-Type: application/json {"attributes": {"value": 1}}
Limitations
- No playback control - API cannot play/pause/skip
- Playlist editing - can only modify API-created playlists
- Token management - dev tokens expire every 180 days
- Rate limits - Apple enforces request limits
Common Mistakes
❌ Using catalog IDs in playlists:
# WRONG json={"data": [{"id": "1234567890", "type": "songs"}]}
Fix: Add to library first, get library ID, then add.
❌ Playing catalog songs via AppleScript:
# WRONG play track id "1234567890"
Fix: Song must be in library.
❌ Unescaped AppleScript strings:
# WRONG name = "Rock 'n Roll" script = f'tell application "Music" to play playlist "{name}"'
Fix: Escape quotes.
❌ Expired tokens: Dev tokens last 180 days max. Fix: Check expiration, handle 401 errors.
The Easy Way: mcp-applemusic
The mcp-applemusic MCP server handles all this complexity automatically: AppleScript escaping, token management, library-first workflow, ID conversions.
Install:
git clone https://github.com/epheterson/mcp-applemusic.git cd mcp-applemusic && python3 -m venv venv && source venv/bin/activate pip install -e .
Configure Claude Desktop:
{ "mcpServers": { "Apple Music": { "command": "/path/to/mcp-applemusic/venv/bin/python", "args": ["-m", "applemusic_mcp"] } } }
On macOS, most features work immediately. For catalog features or Windows/Linux, see the repo README.
| Manual | mcp-applemusic |
|---|---|
| 4 API calls to add song | |
| AppleScript escaping | Automatic |
| Token management | Automatic with warnings |