Skip to main content
Glama
Xinyue-Zhang01

Apple Music Remote MCP

README.md
# Apple Music Remote MCP v0.2 — playlist writes

This version upgrades the earlier read-only proxy so GPT can modify Apple Music
playlists while keeping read operations and destructive operations separated.

## Tools exposed

### `playlist_read` — read-only

Actions:

```text
list
folders
tracks
search
path
```

### `playlist_edit` — write, non-destructive

Actions:

```text
create
add
copy
move
rename
```

### `playlist_remove` — destructive write

Action:

```text
remove
```

This removes tracks from a playlist.

### `playlist_delete` — destructive write, disabled by default

Action:

```text
delete
```

To expose it, set:

```powershell
$env:ALLOW_PLAYLIST_DELETE = "1"
```

before starting `remote_mcp.py`.

### `library_read`

Personal library reads only.

### `catalog_read`

Apple Music catalog search/resolution. This is useful for identifying the exact
recording/edition before adding it to a playlist.

---

## Why the tools are split

MCP has behavioural annotations such as:

- `readOnlyHint`
- `destructiveHint`
- `idempotentHint`

Clients such as ChatGPT can use these hints to decide whether a call needs user
approval.

A single upstream `playlist` tool mixes read and write actions, so this proxy
splits it into multiple tools with accurate annotations.

The annotations are not the security boundary. Server-side action allowlists
are enforced again when each call arrives.

---

# Important security rule

Do **not** expose this write-enabled version on a permanent public hostname with
"No authentication" for long-term use.

If the endpoint is unauthenticated, anyone who obtains the MCP URL could invoke
the exposed write tools while the server is online.

Recommended workflow:

1. Test the new write tools locally.
2. Verify ChatGPT can scan and classify them.
3. Add OAuth/authentication before keeping writes enabled on a fixed public URL.

Playlist deletion is additionally disabled by default.

---

## Run locally

Activate your existing virtual environment:

```powershell
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
```

Start the server:

```powershell
python remote_mcp.py --port 8787
```

Then in another PowerShell:

```powershell
.\.venv\Scripts\Activate.ps1
python test_mcp.py
```

Expected tools:

```text
playlist_read
playlist_edit
playlist_remove
library_read
catalog_read
```

`playlist_delete` should NOT appear unless you explicitly enable it.

The test only performs a read (`playlist_read(action="list")`), so running the
test does not modify your Apple Music account.

---

## Safe first write test

After the server is connected to a trusted MCP client, use a disposable
playlist rather than an existing music collection.

Recommended sequence:

```text
1. Create a playlist named "MCP Write Test".
2. Add one clearly identified track to "MCP Write Test".
3. Read the playlist back and verify the track.
4. Rename it to "MCP Write Test 2".
```

Do not enable `playlist_delete` until ordinary writes have been verified.

---

## Re-scan in ChatGPT

After changing the MCP tool list, ChatGPT needs to refresh/re-scan the app's
tools. The existing server URL does not need to change.

Expected classification:

```text
playlist_read    read-only
playlist_edit    write
playlist_remove  destructive write
library_read     read-only
catalog_read     read-only
```

If you later enable playlist deletion, re-scan again so the new tool is
discovered.

---

## Apple Music constraints on Windows/API mode

The upstream `applemusic-mcp` can create, add, remove, rename, move and delete
through the Apple Music API/web-player rails, but Apple imposes an ownership
constraint: a playlist created by Music.app may not be writable from the API or
web-player client that did not create it.

If an existing playlist rejects an edit, test with a playlist created through
the MCP itself. That distinguishes an Apple playlist-ownership limitation from
a proxy problem.

For ambiguous track names, prefer:

```text
catalog_read(action="search" or "resolve")
```

or an upstream-supported dry run:

```text
playlist_edit(action="add", ..., dry_run=True)
```

before the real add.