mcpField
A
apple-music

apple-music

github.com/ephetersonその他12351 回インストール5 日前に更新
リポジトリ

apple-music について

AppleScript、UI オートメーション、MusicKit API を使った Apple Music 連携ガイドです。再生、ライブラリやプレイリストの操作、カタログ検索に加え、カタログの曲をプレイリストに追加する前にライブラリへ登録する必要がある点も説明します。

主な機能

  1. 01AppleScript による再生とライブラリ操作
  2. 02プレイリストと曲の管理
  3. 03カタログ検索と再生
  4. 04MusicKit API 連携
  5. 05UI オートメーションの手順

活用シーン

  1. 01再生を操作して現在の曲を確認
  2. 02プレイリストの作成と曲の管理
  3. 03カタログ検索や曲のライブラリ追加
  4. 04カタログ曲の再生と AirPlay 出力先の変更

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/epheterson/applemusic-mcp --skill apple-music

インストール後、Claude Code で/apple-musicを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE
.gitignore
.secrets-allowlist
Makefile
README.md
CHANGELOG.md
SECURITY.md
RELEASING.md
config.example.json
pyproject.toml
server.json
.github/
dependabot.yml
.github/workflows/
codeql.yml
scorecard.yml
release.yml
test.yml
publish.yml
scripts/
check_live_env.py
check_versions.py
preflight-ui.sh
preflight.sh
integration_test.py
integration_test_applescript.py
validate_endpoints.py
src/applemusic_mcp/
__init__.py
__main__.py
amp_api.py
paths.py
audit_log.py
musickit_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 seconds30 秒のサブプロセスのタイムアウトが発生しました。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 を操作します。

  1. open "music://…/album/<id>"(アルバム/プレイリスト)または …?i=<songId>(1 曲)
  2. アルバム/プレイリスト: Play ボタンを見つけ、位置とサイズを読み取り、CoreGraphics で中央をクリックします。
  3. 1 曲: トラックリスト内で名前を使ってトラック行を照合し、CoreGraphics でダブルクリックします(アルバム全体ではなく、その曲だけが再生されます)。

ホバーのコツ(行ごとのコントロール)

Music.app では、マウスを行に重ねるまで、トラックごとの再生/「ライブラリに追加」コントロールが表示されません。

  1. System Events でトラックの UI 要素の位置を取得します
  2. CoreGraphics でその位置にマウスを移動します(実際のホバーイベントが生成されます)
  3. 非表示だった checkbox(再生)と button(ライブラリに追加)がアクセシビリティツリーに表示されます
  4. System Events の AXPress ではなく、その座標で CoreGraphics を使ってクリックします

UI から検索する

  1. 検索フィールドの値を設定します:set value of searchField to "query"
  2. Return キーを押します:key code 36
  3. 結果が読み込まれるまで待ちます(約 4 秒)
  4. scroll area 2 から「Top Results」リストを解析します
  5. 各結果は 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 に設定してください。
チャートやおすすめを閲覧するdiscoverAPI のみです。この機能には 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): 検索オートコンプリートのポップオーバーはアクセシビリティツリーに表示され、カタログへのディープリンクでは移動できなくなっています。ポップオーバーを使います。
    1. 正規タイトルで UI 検索します(§ UI から検索)
    2. 名前とアーティストが一致する最初の Song の結果を選びます。Song 以外の結果に切り替えないでください。一致する Song がなければ、問題なく処理を終了します。検索状態が古いままだと、誤った結果をクリックする可能性があります。
    3. 行をクリックすると、曲の詳細ページに移動します。
    4. CoreGraphics でトラックの行にカーソルを合わせると、隠れていた「Add to Library」ボタンにアクセスできるようになります。「Download」ボタンが表示された場合は、そのトラックはすでにライブラリにあります(成功であり、失敗ではありません)。
    5. 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 ドル)とトークンの設定が必要です。

認証

要件:

  1. Apple Developer アカウント
  2. 開発者ポータルから取得した MusicKit キー(.p8 ファイル)
  3. デベロッパートークン(JWT、最長 180 日)
  4. ユーザーミュージックトークン(ブラウザー 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}/searchGET曲、アルバム、アーティスト、プレイリストを検索
/catalog/{storefront}/songs/{id}GET曲の詳細
/catalog/{storefront}/albums/{id}GETアルバムの詳細
/catalog/{storefront}/albums/{id}/tracksGETアルバムのトラック
/catalog/{storefront}/artists/{id}GETアーティストの詳細
/catalog/{storefront}/artists/{id}/albumsGETアーティストのアルバム
/catalog/{storefront}/artists/{id}/songsGETアーティストの人気曲
/catalog/{storefront}/artists/{id}/related-artistsGET類似アーティスト
/catalog/{storefront}/playlists/{id}GETプレイリストの詳細
/catalog/{storefront}/chartsGET人気チャート
/catalog/{storefront}/genresGETすべてのジャンル
/catalog/{storefront}/search/suggestionsGET検索候補の自動補完
/catalog/{storefront}/stations/{id}GETラジオステーション
ライブラリ(ユーザートークンが必要)
エンドポイントメソッド説明
/me/library/songsGETライブラリ内のすべての曲
/me/library/albumsGETライブラリ内のすべてのアルバム
/me/library/artistsGETライブラリ内のすべてのアーティスト
/me/library/playlistsGETライブラリ内のすべてのプレイリスト
/me/library/playlists/{id}GETプレイリストの詳細
/me/library/playlists/{id}/tracksGETプレイリストのトラック
/me/library/searchGETライブラリを検索
/me/libraryPOSTライブラリに追加
/catalog/{sf}/songs/{id}/libraryGETカタログIDからライブラリIDを取得
プレイリストの管理
エンドポイントメソッド説明
/me/library/playlistsPOSTプレイリストを作成
/me/library/playlists/{id}/tracksPOSTプレイリストにトラックを追加
パーソナライズ
エンドポイントメソッド説明
/me/recommendationsGETパーソナライズされたおすすめ
/me/history/heavy-rotationGET再生頻度の高い項目
/me/recent/playedGET最近再生した項目
/me/recent/addedGET最近追加した項目
評価
エンドポイントメソッド説明
/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プレイリストの評価
ストアフロント
エンドポイントメソッド説明
/storefrontsGETすべてのストアフロント
/storefronts/{id}GETストアフロントの詳細
/me/storefrontGETユーザーのストアフロント

共通のクエリパラメーター

パラメーター説明例
term検索クエリterm=beatles
typesリソースの種類types=songs,albums
limit1 ページあたりの結果数(最大 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 のエスケープ自動
トークン管理警告を含めて自動

コメント