Install
openclaw skills install @chrischall/musescore-fpxQuery musescore.com (sheet music search, score/license metadata, official download links) from a shell with the fpx CLI (@fetchproxy/cli) instead of running the musescore-mcp server — one-shot HTTP calls through a signed-in browser tab. Use when you want MuseScore data without the MCP, in a script, or on a machine where the MCP isn't installed.
openclaw skills install @chrischall/musescore-fpxmusescore.com has no public API (the old one was shut down) and sits
behind a Cloudflare managed challenge ("Just a moment…") that 403s any plain
curl/Node request. fpx routes the request through the user's own
signed-in browser tab (the Transporter extension), which has already cleared
the challenge, so the same page loads that a browser would get. No MuseScore
login is required for search/metadata — just a normal open tab.
This is the same data the musescore_search / musescore_get_score /
musescore_download MCP tools return, reached with one CLI call instead of a
running server.
npm install -g @fetchproxy/cli # provides `fpx`
fpx profile add musescore --domain musescore.com
fpx pair -p musescore # prints a pair code → approve in Transporter
Requirements: the Transporter browser extension installed, with an open
musescore.com tab (apex host — no www) that has cleared the Cloudflare
challenge, and its Chrome Site access allowing musescore.com. Pairing
persists — after the first approval every later fpx call reuses it.
Every page is a plain GET, HTML in, HTML out:
fpx get 'https://musescore.com/sheetmusic?text=zelda&recording_type=free-download' -p musescore \
> /tmp/search.html
The fetched HTML has no result cards — MuseScore hydrates the DOM
client-side. The real data is an HTML-entity-encoded JSON store embedded
in the markup (MuseScore's analogue of a Next.js __NEXT_DATA__ blob). jq
alone can't read it — decode the entities and bracket-match the array/object
you want first. references/extract-store.js is a small, dependency-free
Node script that does exactly that (mirrors src/store.ts's
recoverJson/findArrayByKey/findEnclosingObject); pipe the saved HTML
through it, then jq:
node references/extract-store.js scores < /tmp/search.html | jq '.[] | {id, title, composer_name}'
Ready-to-run URL patterns (search, score detail, static render assets) and
their jq projections are in references/endpoints.md.
A score's canonical path is /user/<userId>/scores/<scoreId> — search cards
carry id (score id) and user.id (uploader id), so resolve those from a
search hit before fetching the detail/download page:
node references/extract-store.js scores < /tmp/search.html \
| jq -r '.[0] | "\(.user.id)\t\(.id)\t\(.title)"'
# 12345 67890 Zelda Theme (arr.)
The score-detail store's type_download_list array carries the official
download link (musescore.com/score/download/index?score_id=…&type=…&h=…).
That endpoint is Cloudflare-walled AND 302-redirects cross-origin to a
presigned S3 URL. A browser-tab fetch() (what fpx get/request does) hits
the bot wall on that endpoint and, even if it didn't, can't read an opaque
cross-origin redirect. The MCP works around this with a download bridge
capability (chrome.downloads.download, which issues the request from the
browser's own network stack) — fpx has no equivalent verb, so from the
shell you can only resolve the URL, not save the file. Print it and open it
yourself in the signed-in tab:
node references/extract-store.js type_download_list < /tmp/score.html \
| jq -r '.[] | select(.type=="pdf") | .url'
Entitlement to check before trusting that link will actually download:
is_free === true OR hasAccess === true (see references/endpoints.md).
0 — success.2 — bridge unavailable: extension not connected or pairing pending → run
fpx pair -p musescore, confirm a musescore.com tab is open.3 — bot wall: the tab hasn't cleared Cloudflare → open/refresh a
musescore.com tab and retry.4 — upstream non-2xx from MuseScore.fpx health -p musescore shows bridge connection state when a call fails.musescore.com/static/musescore/scoredata/g/<hash>/score_0.svg|png@0)
are session + Referer-gated — they only work fetched via fpx (through the
tab), never a plain curl. See references/endpoints.md.