apple-music について
AppleScript、UI オートメーション、MusicKit API を使った Apple Music 連携ガイドです。再生、ライブラリやプレイリストの操作、カタログ検索に加え、カタログの曲をプレイリストに追加する前にライブラリへ登録する必要がある点も説明します。
主な機能
- 01AppleScript による再生とライブラリ操作
- 02プレイリストと曲の管理
- 03カタログ検索と再生
- 04MusicKit API 連携
- 05UI オートメーションの手順
活用シーン
- 01再生を操作して現在の曲を確認
- 02プレイリストの作成と曲の管理
- 03カタログ検索や曲のライブラリ追加
- 04カタログ曲の再生と AirPlay 出力先の変更
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ epheterson/ applemusic-mcp --skill apple-musicインストール後、Claude Code で/apple-musicを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSE.gitignore.secrets-allowlistMakefileREADME.mdCHANGELOG.mdSECURITY.mdRELEASING.mdconfig.example.jsonpyproject.tomlserver.json.github/dependabot.yml.github/workflows/codeql.ymlscorecard.ymlrelease.ymltest.ymlpublish.ymlscripts/check_live_env.pycheck_versions.pypreflight-ui.shpreflight.shintegration_test.pyintegration_test_applescript.pyvalidate_endpoints.pysrc/applemusic_mcp/__init__.py__main__.pyamp_api.pypaths.pyaudit_log.pymusickit_js.py詳細
Apple Music 連携
Apple Music と連携するためのガイドです。方法は 3 つあります。AppleScript(直接操作)、UI 自動化(API を使わずにカタログを操作)、MusicKit API(クロスプラットフォーム)です。
使用する場面
ユーザーから次の依頼があったときに呼び出します。
- プレイリストの管理(作成、トラックの追加・削除、一覧表示)
- 再生の操作(再生、一時停止、スキップ、音量)
- Apple Music の URL(アルバム、プレイリスト、曲)の再生
- カタログまたはライブラリの検索
- 曲のライブラリへの追加
- 再生履歴またはおすすめへのアクセス
重要なルール:ライブラリを優先するワークフロー
カタログの曲をプレイリストに直接追加することはできません。
曲はまずユーザーのライブラリに追加する必要があります。
- ❌ カタログ ID → プレイリスト(失敗)
- ✅ カタログ ID → ライブラリ → プレイリスト(成功)
理由: プレイリストではカタログ ID(1234567890)ではなく、ライブラリ ID(i.abc123)を使用します。
これは AppleScript と API の両方の方法に当てはまります。
AppleScript(macOS)
セットアップは不要です。Music アプリですぐに使えます。
Bash から実行:
osascript -e 'tell application "Music" to playpause'
osascript -e 'tell application "Music" to return name of current track'
複数行のスクリプト:
osascript <<'EOF'
tell application "Music"
set t to current track
return {name of t, artist of t}
end tell
EOF
使用可能な操作
| カテゴリ | 操作 |
|---|---|
| 再生 | 再生、一時停止、停止、再開、次のトラック、前のトラック、早送り、巻き戻し、URL の再生 |
| プレーヤーの状態 | 再生位置、プレーヤーの状態、音量、ミュート、シャッフルの有効状態・モード、曲のリピート |
| 再生中のトラック | 名前、アーティスト、アルバム、再生時間、時刻、評価、お気に入り、低評価、ジャンル、年、トラック番号 |
| ライブラリ | 検索、トラック一覧の取得、トラックのプロパティ取得、評価の設定 |
| プレイリスト | 一覧表示、作成、削除、名前変更、トラックの追加、トラックの削除、トラックの取得 |
| AirPlay | デバイス一覧の取得、デバイスの選択、現在のデバイス |
トラックのプロパティ(読み取り)
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
-- Content rating. DO NOT trust this for filtering.
-- On Apple Music (cloud) tracks this property is unset, and reading it
-- RAISES "A descriptor type mismatch occurred" rather than returning
-- false. Catching that and defaulting to false is what turns "I could
-- not tell" into "this is clean": on a real 463-track playlist Music.app
-- yielded zero explicit tracks where the catalog rated eight of them
-- explicit. If a caller asked for clean content, resolve the rating
-- against the catalog (`contentRating == "explicit"`, matching on name
-- AND artist) and report anything you could not verify as unknown.
explicit of t -- true/false, RAISES on most cloud tracks
-- 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
トラックのプロパティ(書き込み可能)
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
プレーヤーの状態プロパティ
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
再生コマンド
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
句読点やアクセント記号が異なるタイトルの照合
whose name contains "..." は字形を厳密に照合します。Music 標準の search
コマンドとは異なり、Music でよく使われる曲がりアポストロフィ(U+2019)と
ストレートアポストロフィ(U+0027)の違いや、アクセント記号を無視しません。
そのため、whose name contains "That's a No No" では、タイトルが That's a No No として保存されている曲は見つかりません。確実な方法は次の 2 つです。
-- 1. Match the quote-free fragments instead of the apostrophe itself.
-- Works regardless of which apostrophe variant is stored or typed.
play (first track of library playlist 1 whose ¬
(name contains "That" and name contains "s a No No"))
-- 2. For accents/ellipsis/anything: bulk-fetch names in ONE Apple Event and
-- fold in-memory. `ignoring punctuation and diacriticals` uses Apple's
-- Unicode tables (curly quotes, ellipses, café≈cafe). Do NOT access
-- `name of t` per-iteration in a loop — that is one Apple Event per track
-- (~17s on a 12k library); fetch the whole list at once (~instant).
tell application "Music"
set allNames to (get name of every track of library playlist 1)
set idx to 0
ignoring punctuation and diacriticals
repeat with i from 1 to (count of allNames)
if ((item i of allNames) as text) contains "Fur Elise" then
set idx to i
exit repeat
end if
end repeat
end ignoring
if idx > 0 then play (track idx of library playlist 1)
end tell
ライブラリのクエリ
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
-- Favorite (loved) tracks. Music.app renamed Loved -> Favorite and the
-- property with it: newer versions use `favorited`, older use `loved`,
-- and the unsupported name can fail INSIDE a whose-filter with "The
-- variable loved is not defined". Try `whose favorited is true` first,
-- fall back to `whose loved is true`, each wrapped in `try`.
tracks of library playlist 1 whose favorited is true
tracks of library playlist 1 whose loved is true -- legacy Music versions
-- Recently played (sort by played date)
tracks of library playlist 1 whose played date > (current date) - 7 * days
end tell
プレイリストの操作
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. Match the name EXACTLY; a `contains` fallback here
-- turns "Work" into "Workout".
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"
-- Get the FIRST N, one Apple Event per property. Read the range off the
-- playlist itself. Do NOT stash the tracks in a variable first --
-- `set t to tracks of pl` materializes a plain list, and a property of a
-- plain list does not distribute: Music raises -1728 and names every
-- track of the playlist in the error message.
set pl to user playlist "Road Trip"
set n to 25
if n > (count of tracks of pl) then set n to (count of tracks of pl)
if n < 1 then return {} -- `tracks 1 thru 0` errors
get name of tracks 1 thru n of pl as list -- `as list`: n=1 returns a
-- bare value, not a list
-- 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. `contains` + `first` is a TRAP for anything
-- destructive: it deletes whichever match sorts first, irreversibly.
-- "Love" matches 376 tracks in a real library; "Alice" matches three
-- playlists including a 467-track one. Enumerate first and act only when
-- the target is unambiguous.
set hits to (every track of user playlist "Road Trip" whose name contains "Hey Jude")
if (count of hits) is 1 then
delete (item 1 of hits)
else
-- report the candidates and let the user choose; do not guess
end if
-- 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
文字列のエスケープ
ユーザー入力は必ずエスケープしてください。
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}"'
よくある失敗
osascript の標準エラー出力メッセージは、いくつかの環境状態に対応します。AppleScript が失敗した場合は、ユーザーに伝える内容を決める前に分類してください。標準エラー出力をそのまま表示すると誤解を招きます。また、API の経路へ黙って切り替えると、無関係なエラー(実際の問題は Music.app が閉じていたのに、「Developer token not found」と表示されるなど)が漏れてしまいます。
| 標準エラー出力のシグナル | 意味 | ユーザーへの伝え方 |
|---|---|---|
(-609), Connection is invalid, (-10810), isn't running, Can't get application "Music" | Music.app が起動していないか、セッション中にクラッシュしました | 「Music.app が起動していません。開いてから再試行してください。」 |
(-1743), Not authorized, not allowed assistive access, assistive access | ホストプロセス(Claude Desktop、Python CLI、Terminal)に、Music のオートメーション権限が付与されていません | 「システム設定 → プライバシーとセキュリティ → オートメーションを開き、このコードを実行しているアプリを探して、『Music』の切り替えを有効にしてください。」 |
AppleScript timed out after 30 seconds | 30 秒のサブプロセスのタイムアウトが発生しました。Music.app が応答しないか、起動中の可能性があります | 「Music.app が応答していない可能性があります。終了してから、再度開いてください。」 |
syntax error, expected … but found … | スクリプト自体に不備があります(開発者側のバグ) | 生のエラーを報告してください。これはユーザーではなく、こちら側の問題です。 |
| その他 | ロジックレベルのエラー(トラックが見つからない、プレイリストが空など) | 生の標準エラー出力を表示してください。妥当なフォールバックがある場合は、API への切り替えも安全です。 |
not allowed だけで判定しないでください。 Music.app は「operation not allowed on smart playlists」のようなロジックレベルのエラーも出力します。これらをオートメーション権限の拒否として分類してはいけません。not allowed assistive または assistive access というフレーズ全体を照合してください。
is_available() だけで判定しないでください。 この関数が確認するのは darwin + osascript exists だけです。Music.app が起動しているか、オートメーション権限が付与されているかは判定できません。これらの状態を確認する手掛かりは、上記のエラー分類しかありません。
制限事項
- macOS のみ — Windows/Linux には対応していません
- UI 機能にはディスプレイが必要です — UI オートメーションは、ヘッドレス環境や Music.app を最小化した状態では動作しません
- Music.app が起動している必要があります —
is_available()はosascriptが PATH 上にあるだけで True を返しますが、すべてのtell application "Music"ブロックで Music.app が実際に起動している必要があります。新たに起動した直後の最初の呼び出しでは、ユーザーが一度承認する必要のあるオートメーション権限の確認が表示されることもあります。
UI 自動化(macOS)
ライブラリ/プレイリストへの追加を行う UI 自動化は、0.15.0 で削除されました。 カタログからライブラリへの追加と auto_add→playlist は、統合された Apple Music API(生成した、または Apple の公開 Web プレーヤーから取得した開発者トークンと、
media-user-token)を通じて実行されます。以前の UI による追加方法はバージョンに左右されやすく(macOS/Music.app のリリース間で動作しなくなりました。#37)、API の方法を有効にするには、applemusic-mcp login(または開発者トークンを使ってlogin --dev)を実行します。macOS では、loginはデフォルトでサインイン済みの Safari から media-user-token を読み取ります(Chrome/Playwright は不要)。ただし、Safari → 設定 → 詳細 → 開発 → 「Apple Events からの JavaScript を許可」が有効になっている必要があります。login --chromeを使うと、代わりに Chrome の Web プレーヤーを使用します(pip install 'applemusic-mcp[browser]'が必要です)。macOS 以外では、loginにより Chrome が開きます(Playwright はデフォルトで同梱されています)。以下の UI プリミティブは、引き続き再生/URL からの再生とカタログの検索にのみ使用され、追加には使用されません。エンジン(
mode設定/呼び出しごとのengine=):auto(デフォルト。再生にはネイティブの Music.app、Up Next キューには Safari、macOS ではデータに API、macOS 以外では Chrome)、native、safari、chrome、api。Safari エンジンは、同じdo JavaScriptチャネル(DRM ネイティブ、Chrome 不要)を通じて、サインイン済み Safari の MusicKit を操作し、再生/操作/now_playing/設定とキュー全体に対応します。そのため macOS では、Playwright を使わずに Web プレーヤーのすべての機能を利用できます。queueを使用すると、そのエンジンがアクティブになり、playback controlから操作できるようになります。
残りの UI 自動化では、System Events(アクセシビリティ API)と CoreGraphics(マウスイベント)を通じて Music.app を操作します。
要件: ディスプレイが接続されていること、Music.app が表示されていること、System Events にアクセシビリティ権限があること(システム設定 → プライバシーとセキュリティ → アクセシビリティ)。
主な概念
System Events は、アクセシビリティ階層を通じて UI 要素を読み取り、クリックします。
tell application "System Events" to tell process "Music"
-- Main content area
scroll area 2 of splitter group 1 of window "Music"
-- Search field
text field 1 of UI element 1 of row 1 of outline 1 of scroll area 1 of splitter group 1 of window "Music"
end tell
CoreGraphics のマウスイベント(JXA 経由)は、実際のマウス入力(ホバー、クリック、ダブルクリック)を生成します。
// osascript -l JavaScript
ObjC.import("CoreGraphics");
var point = $.CGPointMake(x, y);
// hover (reveals hidden controls):
$.CGEventPost($.kCGHIDEventTap, $.CGEventCreateMouseEvent($(), $.kCGEventMouseMoved, point, 0));
// click: post LeftMouseDown then LeftMouseUp at the point.
// double-click: post two down/up pairs with the click-state field (1) set to 1 then 2.
重要: System Events の
click(AXPress)では、Music.app のカスタム再生/シャッフルボタンやトラック行は作動しません。成功を返しますが、再生は始まりません。System Events で要素の矩形を読み取り、要素の画面座標で CoreGraphics のマウスイベントを使ってクリックしてください。シングルクリックではトラック行が選択されるだけです。再生するにはダブルクリックしてください。
カタログ(ライブラリにない)トラックをネイティブに再生する
AppleScript には、ライブラリにないカタログトラックを再生する動詞がありません。ディープリンクでページを開き、その後 CoreGraphics で UI を操作します。
open "music://…/album/<id>"(アルバム/プレイリスト)または…?i=<songId>(1 曲)- アルバム/プレイリスト:
Playボタンを見つけ、位置とサイズを読み取り、CoreGraphics で中央をクリックします。 - 1 曲: トラックリスト内で名前を使ってトラック行を照合し、CoreGraphics でダブルクリックします(アルバム全体ではなく、その曲だけが再生されます)。
ホバーのコツ(行ごとのコントロール)
Music.app では、マウスを行に重ねるまで、トラックごとの再生/「ライブラリに追加」コントロールが表示されません。
- System Events でトラックの UI 要素の位置を取得します
- CoreGraphics でその位置にマウスを移動します(実際のホバーイベントが生成されます)
- 非表示だった
checkbox(再生)とbutton(ライブラリに追加)がアクセシビリティツリーに表示されます - System Events の AXPress ではなく、その座標で CoreGraphics を使ってクリックします
UI から検索する
- 検索フィールドの値を設定します:
set value of searchField to "query" - Return キーを押します:
key code 36 - 結果が読み込まれるまで待ちます(約 4 秒)
scroll area 2から「Top Results」リストを解析します- 各結果は
descriptionが名前で、種別/アーティストを示す静的テキストを持つUI elementです
注: 検索結果の種別を区切る文字には、Unicode の 3 分の 1 em スペース(U+2004)と中黒(U+00B7)が使われます:Song꘎·꘎Radiohead
ウインドウの復旧
Music.app はウインドウなしで実行されることがあります。ウインドウが存在することを確認するには、次のようにします。
tell application "Music" to activate
tell application "System Events" to tell process "Music"
if (count of windows) is 0 then
click menu item "Music" of menu "Window" of menu bar 1
end if
end tell
脆弱性
Apple が Music.app のレイアウトを更新すると、UI のパスが使えなくなります。パスは定数として一元管理し、macOS のアップデート後にテストしてください。Accessibility Inspector.app を使って、現在の階層を確認してください。
検索フィールドの場所は macOS 15(サイドバーのアウトライン行)と macOS 26/Tahoe(ツールバーグループ)の間で変わりました。サーバーは実行時に AppleScript の exists チェックでツールバー要素を調べ、見つからない場合はサイドバーのパスにフォールバックします。そのため、パスをハードコードせずにどちらの OS バージョンもサポートできます。新しい macOS バージョンで UI 検索が使えなくなった場合は、まず検索フィールドのパスを確認してください。
ツールの振り分け(最上位アクションの使い分け)
ユーザーの依頼に応じて、適切な MCP ツールはライブラリを検索するのか、カタログを検索するのかによって異なります。混同しやすい点です。ここで誤った分岐を選ぶと、本来はトークンなしで実行できる操作なのに、ユーザーが「Developer token not found」の解決に手間取ることになります。
| 目的 | 使用するもの | 備考 |
|---|---|---|
| ユーザーがすでに所有している曲を探す | library(action='search', query='...') | ローカルライブラリのみです。macOS では AppleScript、それ以外では API を使います。 |
| 名前でプレイリストを探す | playlist(action='list', filter='alice') | 名前を部分一致で照合し、一致するプレイリストと ID を返します。この用途に action='search' は使用しないでください。これは指定したプレイリスト内のトラックを検索するもので、playlist パラメーターが必要です。 |
| 特定のジャンルに属するユーザーのトラックを一覧表示する | library(action='search', query='Rock', types='genre') | トラックのジャンルフィールドで絞り込みます。macOS 専用です。ジャンル名を通常の全文検索に渡さないでください。ジャンルフィールドは検索対象にならないため、「Rock」は曲名が 「Rock Your Body」 の曲にも誤って一致します。一致するものがない場合は「No tracks found」を返します。macOS 以外では、API 経由のジャンル絞り込みは利用できないと通知します。 |
| Apple Music の全カタログから曲を探す | catalog(action='search', query='...') | まず API を試し、トークンなしの macOS では Music.app の UI 検索にフォールバックします。 |
| カタログの曲をユーザーのライブラリに追加する | library(action='add', track='...') | API のみです。開発者トークン(生成または取得済み)とメディアユーザートークン(signin)が必要です。UI 自動化のフォールバックは 0.15.0 で削除されました(上記の注記を参照)。 |
| 曲(ライブラリ内またはカタログ内)をプレイリストに追加する | playlist(action='add', track='...', auto_add=True) | 曲がまだライブラリにない場合、カタログから追加するには auto_add=True が必要です。不要なライブラリへの書き込みを避けるため、デフォルトは False です。「このプレイリストを埋める」ワークフローでは True に設定してください。 |
| チャートやおすすめを閲覧する | discover | API のみです。この機能には UI のフォールバックはありません。 |
library(action='search') は 1 ページ分を返します。limit(デフォルトは 25)が結果数の上限で、offset を使って結果の続きを取得できます。テキストヘッダーには start-end of total と表示されるため、end < total の場合は続きがあります。offset を limit 分増やして次のページを取得してください(action='browse' と同様です)。
library(action='search') が「No songs found in library」を返した場合、API トークンを設定する必要があるという意味ではありません。代わりに catalog(action='search') または playlist(action='add', auto_add=True) を試すという意味です。これらはすべて、トークンなしの macOS 経由で利用できます。
複合フロー(API なし)
API トークンなしで applemusic-mcp のカタログ機能を再現する手順です。各ユーザーリクエストが「カタログへのアクセスを必要とするか」を判定してください。ライブラリ操作と再生操作だけなら AppleScript のままにし、カタログ検索のみ UI 自動化を使います。
カタログの曲をユーザーのライブラリに追加する
ユーザーが "Silvera - GOJIRA" のような単一の文字列を指定した場合は、検索する前に - で分割し、カタログ検索に正しい曲名を渡してください。
ステップ 0 — まず正規タイトルを解決します(省略しないでください)。 無料でトークン不要の iTunes Search API(https://itunes.apple.com/search?term=<name>+<artist>&entity=song&country=<storefront>)を呼び出し、アーティストを優先して最適な結果を選びます。ユーザーがアーティストを指定している場合、曲名が完全一致していても別のアーティストの結果は誤った曲です。誤った曲を気付かずに追加するリスクを避けるため、その結果は却下してください(例:「Lemons」/「Brye」は、Hairitage の「Lemons」ではなく、Brye の「LEMONS (feat. Cavetown)」に解決する必要があります)。以下の UI 検索には、解決した正規タイトルを使ってください。Apple のオートコンプリートはランキングの影響を受けるため、これは重要です。曖昧な検索語(「Lemons」)では人気のある同名曲が表示され、知名度の低い曲が見つからない場合がありますが、正規タイトル(「LEMONS (feat. Cavetown)」)を使えば該当する行が表示されます。
次に、バージョンに応じた操作画面を使います。
- macOS 26 / 新しい Music(Music ≥ 1.6.x): 検索オートコンプリートのポップオーバーはアクセシビリティツリーに表示され、カタログへのディープリンクでは移動できなくなっています。ポップオーバーを使います。
- 正規タイトルで UI 検索します(§ UI から検索)
- 名前とアーティストが一致する最初の
Songの結果を選びます。Song以外の結果に切り替えないでください。一致するSongがなければ、問題なく処理を終了します。検索状態が古いままだと、誤った結果をクリックする可能性があります。 - 行をクリックすると、曲の詳細ページに移動します。
- CoreGraphics でトラックの行にカーソルを合わせると、隠れていた「Add to Library」ボタンにアクセスできるようになります。「Download」ボタンが表示された場合は、そのトラックはすでにライブラリにあります(成功であり、失敗ではありません)。
- CoreGraphics の実際のクリック操作で「Add to Library」をクリックします(SwiftUI のボタンは AXPress を無視します)。
- macOS 15 / 古い Music(Music ≤ 1.5.x): オートコンプリートのポップオーバーはアクセシビリティツリーに表示されませんが、解決した
music://…?i=…ディープリンクをopenで開くと、アルバムページに移動します。解決した URL へのディープリンクを開き、ハイライトされたトラックの行を見つけてカーソルを合わせ、その行の「Add to Library」ボタンをクリックします。
最後に、次の操作を行います。
6. 次の呼び出しが新しい状態から始まるよう、検索フィールドをクリアします。
7. 追加を確認します。iCloud ライブラリについてはポップオーバーの UI が正となるため、次の手順が遅れていても、ポップオーバーで追加が成功したことを信頼してください。トラックがローカルに表示されるまで、search_library または library playlist 1 の直接検索で確認を繰り返します(最初に表示されるまで約 3 秒。iCloud の同期にはさらに時間がかかることがあるため、最大約 18 秒とします)。UI で追加を確認した後に検索結果に見つからない場合は、「失敗」ではなく「同期中」を意味します。
カタログの曲を特定のプレイリストに追加する
上記の**「ライブラリに追加」**を実行し、その後、AppleScript の duplicate で新しくライブラリに追加したトラックを対象のプレイリストに複製します。
tell application "Music"
set targetTrack to first track of library playlist 1 ¬
whose name contains "Silvera" and artist contains "GOJIRA"
duplicate targetTrack to user playlist "Road Trip"
end tell
UI から「Add to Playlist」のメニュー項目をクリックしないでください。AppleScript の duplicate を使う方法の方が確実です。開発者トークンがあっても、POST /v1/me/library/playlists/{id}/tracks は呼び出さないでください。API 経由で作成されていないプレイリスト(Music.app で作成されたプレイリストが通常該当します)では、HTTP 500 が返されます。duplicate はすべてのプレイリストで動作します。
書き込みのルーティング(公認ルートを優先)
サーバーは、書き込み先のパスを再生モードではなく、認証情報と機能に基づいて選択します。
- 公認 — 生成したデベロッパートークンを使う、公式 Apple Music API(
api.music.apple.com)です。デベロッパートークンがあり、その操作がサポートされている場合は、常にこの方法を優先します。 - Web — サインイン済みのセッショントークンを使う、ウェブプレーヤーのバックエンドです。公開 API では対応できない処理(プレイリストの削除、Music.app で作成したプレイリストへの追加、フォルダーからの移動)に限って使用するか、デベロッパートークンがない場合に使用します。
- ネイティブ — macOS 上で AppleScript を使ってローカルの Music.app を操作します(トークン不要)。Mac ではこれがデフォルトの書き込み方法です。
そのため、mode=web(再生の選択肢)を指定しても、書き込みが Web の経路に強制されるわけではありません。デベロッパートークンがあれば、引き続き公式 API 経由で書き込みます。星評価には、モードにかかわらず AppleScript(macOS)が必要です。各書き込みでは使用した経路(via Apple Music API/via web player/via Music.app)が報告されます。config(action="status") の Writes: 行には、解決された経路が表示されます。
追加後の検証
UI 経由の操作では、検索状態が古いと誤った検索結果をクリックすることがあり、AppleScript の状態も新しく追加した直後はしばらく遅れて反映されます。追加した曲が実際に登録されたことを、必ず確認してください。
tell application "Music"
set matches to (every track of user playlist "Road Trip" ¬
whose name contains "Silvera" and artist contains "GOJIRA")
return (count of matches) > 0
end tell
失敗する前に、約 1 秒待ってから一度だけ再試行してください。2 回目の検証でも失敗する場合は、その結果を信頼してください。追加は完了していません。成功したと誤って伝えるのではなく、エラーを表示してください。
MusicKit API
クロスプラットフォームで利用できますが、Apple Developer アカウント(年間 99 ドル)とトークンの設定が必要です。
認証
要件:
- Apple Developer アカウント
- 開発者ポータルから取得した MusicKit キー(.p8 ファイル)
- デベロッパートークン(JWT、最長 180 日)
- ユーザーミュージックトークン(ブラウザー OAuth)
デベロッパートークンを生成する:
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'}
)
ユーザートークンを取得する: https://authorize.music.apple.com/woa でブラウザー OAuth を行います。
すべてのリクエストに使用するヘッダー:
Authorization: Bearer {developer_token}
Music-User-Token: {user_music_token}
ベース URL: https://api.music.apple.com/v1
レート制限(一括処理を行う前にお読みください)
Apple は直近約 60 分間のローリングウィンドウでリクエストを制限し、HTTP 429/コード 42900 を返します。また、Retry-After ヘッダーも X-Rate-Limit ヘッダーも送信しません。残りのクォータは確認できないため、ブロックされて初めて制限に達したことがわかります。
| トークン | 実用上の上限 | 備考 |
|---|---|---|
自分のデベロッパートークン(login --dev、自分の .p8) | 1 時間あたり約 3600 リクエスト。Apple が公表している値です | 自分専用のクォータです |
Apple の公開ウェブプレーヤートークン(通常の login、トークンレス/Safari 経路) | 実測で 1 時間あたり数百件 | ユーザーごとではなく共有です。上限ははるかに低く、自分で使えるクォータではありません |
注意すべき影響:
- 少し待っても効果はありません。 15 分間リクエストがなくても、引き続き 429 が返されます。制限はバーストが終わってからおよそ 1 時間後に解除されますが、その後さらに数件リクエストすると再び制限されます。ウィンドウ内の再試行はすべてカウントに加算され、ブロックが続く原因になります。
- 429 がエラーではなく、空の結果に見えることがあります。 曲ごとに 1 回検索する(「タイトルとアーティストを解決 → カタログ ID を取得する」)のが標準的なインポート方法であり、この制限に最も早く達する方法です。呼び出し側が検索結果の空を「曲が見つからない」と扱うと、レート制限によって誤った不一致が黙って発生します。429 の後の空の結果は不明として扱い、存在しないとは判断せず、キャッシュもしないでください。
- 一括処理には
login --devを使用してください。 自分のデベロッパートークンには、独自の、はるかに大きなクォータが割り当てられます。#42 で報告・実測されています。
トラックごとに検索する代わりに、ISRCで一括解決する
呼び出し元がISRCを持っている場合(Spotify/Rekordbox/Plexのエクスポートにはすべて含まれています)、
GET /v1/catalog/{storefront}/songs?filter[isrc]=ISRC1,ISRC2,… を使うと、1回のリクエストで25件を
正確に照合できます。トラックごとに検索する場合と比べてリクエスト数は約25分の1になり、あいまい一致による
エラーもありません。ツール経由の場合:catalog(action="resolve", isrcs="…", format="json")。
filter[isrc]は検索ではなくフィルターなので、正しく扱うべき点が2つあります。
- 見つからなくても通知されません。 Appleは照合できたものだけを返し、残りは省略します。そのため、
解決できなかったISRCは、リクエストしたISRCの集合とレスポンスの
attributes.isrcとの差分からしか 分かりません。その差分を計算してください。すべて返ってくるとは限りません。 - 「リクエストしたが見つからなかった」と「まだリクエストしていない」は区別してください。 バッチが失敗した場合(429)、残りのISRCの状態は「カタログにない」ではなく 不明です。この2つをまとめてしまうと、このセクションで注意を促している誤検出の問題が再発します。
1つのISRCが複数のカタログ内の曲に対応する場合があります(地域別リリースやリマスターなど)。そのため、最初の曲を黙って採用するのではなく、一致件数を保持してください。
ISRCがない場合は、書き込む前にドライランしてください。 タイトルの照合はあいまいで、失敗しても通知されません。アーティスト名なしでDont Let Me Downを指定すると、The BeatlesではなくThe Chainsmokersの曲が解決されます。まずリスト全体を解決し、信頼度マーカー(完全一致か、あいまい一致か)とともに候補を表示して、確認が済んでから書き込んでください。トラックごとに「検索してから追加」を繰り返してはいけません。ツール経由の場合:catalog(action="resolve", tracks=..., format="json")は何も追加せず、playlist(action="add")ですぐ使えるidsを返します。または、特定のプレイリストの内容と照合してプレビューも行うplaylist(action="add", ..., dry_run=True)を使用できます。この方法はトラックごとに1回のリクエストを消費するため(Appleにはタイトルとアーティストを組み合わせて一括検索するエンドポイントがありません)、レート制限の解決策にはなりません。その場合はISRCを使用してください。
手動の場合:
resolved = {}
for i in range(0, len(isrcs), 25):
batch = isrcs[i : i + 25]
r = requests.get(
f"https://api.music.apple.com/v1/catalog/{storefront}/songs",
headers=headers,
params={"filter[isrc]": ",".join(batch)},
)
if r.status_code == 429:
break # later batches only extend the window
for song in r.json().get("data", []):
resolved.setdefault(song["attributes"]["isrc"], song["id"])
利用可能なエンドポイント
カタログ(公開 — 開発用トークンのみ)
| エンドポイント | メソッド | 説明 |
|---|---|---|
/catalog/{storefront}/search | GET | 曲、アルバム、アーティスト、プレイリストを検索 |
/catalog/{storefront}/songs/{id} | GET | 曲の詳細 |
/catalog/{storefront}/albums/{id} | GET | アルバムの詳細 |
/catalog/{storefront}/albums/{id}/tracks | GET | アルバムのトラック |
/catalog/{storefront}/artists/{id} | GET | アーティストの詳細 |
/catalog/{storefront}/artists/{id}/albums | GET | アーティストのアルバム |
/catalog/{storefront}/artists/{id}/songs | GET | アーティストの人気曲 |
/catalog/{storefront}/artists/{id}/related-artists | GET | 類似アーティスト |
/catalog/{storefront}/playlists/{id} | GET | プレイリストの詳細 |
/catalog/{storefront}/charts | GET | 人気チャート |
/catalog/{storefront}/genres | GET | すべてのジャンル |
/catalog/{storefront}/search/suggestions | GET | 検索候補の自動補完 |
/catalog/{storefront}/stations/{id} | GET | ラジオステーション |
ライブラリ(ユーザートークンが必要)
| エンドポイント | メソッド | 説明 |
|---|---|---|
/me/library/songs | GET | ライブラリ内のすべての曲 |
/me/library/albums | GET | ライブラリ内のすべてのアルバム |
/me/library/artists | GET | ライブラリ内のすべてのアーティスト |
/me/library/playlists | GET | ライブラリ内のすべてのプレイリスト |
/me/library/playlists/{id} | GET | プレイリストの詳細 |
/me/library/playlists/{id}/tracks | GET | プレイリストのトラック |
/me/library/search | GET | ライブラリを検索 |
/me/library | POST | ライブラリに追加 |
/catalog/{sf}/songs/{id}/library | GET | カタログIDからライブラリIDを取得 |
プレイリストの管理
| エンドポイント | メソッド | 説明 |
|---|---|---|
/me/library/playlists | POST | プレイリストを作成 |
/me/library/playlists/{id}/tracks | POST | プレイリストにトラックを追加 |
パーソナライズ
| エンドポイント | メソッド | 説明 |
|---|---|---|
/me/recommendations | GET | パーソナライズされたおすすめ |
/me/history/heavy-rotation | GET | 再生頻度の高い項目 |
/me/recent/played | GET | 最近再生した項目 |
/me/recent/added | GET | 最近追加した項目 |
評価
| エンドポイント | メソッド | 説明 |
|---|---|---|
/me/ratings/songs/{id} | GET | 曲の評価を取得 |
/me/ratings/songs/{id} | PUT | 曲の評価を設定 |
/me/ratings/songs/{id} | DELETE | 評価を削除 |
/me/ratings/albums/{id} | GET/PUT/DELETE | アルバムの評価 |
/me/ratings/playlists/{id} | GET/PUT/DELETE | プレイリストの評価 |
ストアフロント
| エンドポイント | メソッド | 説明 |
|---|---|---|
/storefronts | GET | すべてのストアフロント |
/storefronts/{id} | GET | ストアフロントの詳細 |
/me/storefront | GET | ユーザーのストアフロント |
共通のクエリパラメーター
| パラメーター | 説明 | 例 |
|---|---|---|
term | 検索クエリ | term=beatles |
types | リソースの種類 | types=songs,albums |
limit | 1 ページあたりの結果数(最大 25 件) | limit=10 |
offset | ページネーションのオフセット | offset=25 |
include | 関連リソース | include=artists,albums |
extend | 追加属性 | extend=editorialNotes |
l | 言語コード | l=en-US |
検索例
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"]
}
}]
}
}
}
ライブラリ優先のワークフロー(完全版)
カタログの曲をプレイリストに追加するには、API 呼び出しが 4 回必要です。
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"}]}
)
プレイリストを作成
POST /v1/me/library/playlists
Content-Type: application/json
{
"attributes": {
"name": "Road Trip",
"description": "Summer vibes"
},
"relationships": {
"tracks": {
"data": []
}
}
}
評価
# Love a song (value: 1 = love, -1 = dislike)
PUT /v1/me/ratings/songs/{id}
Content-Type: application/json
{"attributes": {"value": 1}}
制限事項
- 再生操作は不可 - API では再生/一時停止/スキップを操作できません
- プレイリストの編集 - API で作成したプレイリストのみ変更できます
- トークン管理 - 開発者トークンは 180 日ごとに有効期限が切れます
- レート制限 - Apple はリクエスト数を制限しています
よくある間違い
❌ プレイリストでカタログ ID を使用する:
# WRONG
json={"data": [{"id": "1234567890", "type": "songs"}]}
修正方法: 先にライブラリに追加し、ライブラリ ID を取得してから追加します。
❌ AppleScript 経由でカタログの曲を再生する:
# WRONG
play track id "1234567890"
修正方法: 曲がライブラリに登録されている必要があります。
❌ AppleScript の文字列をエスケープしない:
# WRONG
name = "Rock 'n Roll"
script = f'tell application "Music" to play playlist "{name}"'
修正方法: 引用符をエスケープします。
❌ トークンの有効期限切れ: 開発者トークンの有効期間は最大 180 日です。 修正方法: 有効期限を確認し、401 エラーに対処します。
手軽な方法:applemusic-mcp
applemusic-mcp MCP サーバーは、AppleScript のエスケープ、トークン管理、ライブラリ優先のワークフロー、ID の変換といった複雑な処理をすべて自動で行います。
インストール:
git clone https://github.com/epheterson/applemusic-mcp.git
cd applemusic-mcp && python3 -m venv venv && source venv/bin/activate
pip install -e .
Claude Desktop を設定:
{
"mcpServers": {
"Apple Music": {
"command": "/path/to/applemusic-mcp/venv/bin/python",
"args": ["-m", "applemusic_mcp"]
}
}
}
macOS では、ほとんどの機能がすぐに使えます。カタログ機能または Windows/Linux については、リポジトリの README をご覧ください。
| 手動 | applemusic-mcp |
|---|---|
| 曲の追加に API 呼び出しが 4 回必要 | playlist(action="add", auto_add=True) |
| URL をコピーして Music で開く | playback(action="play", url="...") |
| カタログの曲をライブラリに追加 | library(action="add")(統合 API — 開発者トークンまたは Web トークン) |
| ライブラリの変更を手動で記録 | library(action="snapshot") |
| AppleScript のエスケープ | 自動 |
| トークン管理 | 警告を含めて自動 |