deep-roon-mcp 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/deep-roon-mcp.js +77 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ server of its own and sends nothing anywhere else. What you ask goes to your MCP
|
|
|
22
22
|
|
|
23
23
|
## Status
|
|
24
24
|
|
|
25
|
-
Version 0.4: fifty-
|
|
25
|
+
Version 0.4: fifty-nine tools. Qobuz, TIDAL or both — whatever is signed in on your Core.
|
|
26
26
|
|
|
27
27
|
**Reading** — changes nothing in Roon:
|
|
28
28
|
|
|
@@ -32,6 +32,7 @@ Version 0.4: fifty-seven tools. Qobuz, TIDAL or both — whatever is signed in o
|
|
|
32
32
|
| `roon_new` | Qobuz's shelves (new, qobuzissimes, press awards, top, trending, ideal discography) or TIDAL's (new, recommended, top, rising, masters; albums or tracks) — by genre |
|
|
33
33
|
| `roon_genres` | Qobuz's genres and their ids, or TIDAL's genres and moods |
|
|
34
34
|
| `roon_album` | An album's page: tracks, release dates, label, barcode, credits |
|
|
35
|
+
| `roon_track_files` | Where your own files are: per track of an album, an artist or a list of tracks, the file path as the Core sees it (and duplicates) — Roon's export data |
|
|
35
36
|
| `roon_review` | The review Roon shows, in full, with its author |
|
|
36
37
|
| `roon_bio` | The biography Roon shows, in full, with related artists |
|
|
37
38
|
| `roon_artist` | An artist's discography at Qobuz or TIDAL, oldest first |
|
|
@@ -108,7 +109,7 @@ The settings (Claude Desktop › Settings › Extensions › Deep Roon MCP):
|
|
|
108
109
|
| Setting | What it does |
|
|
109
110
|
|---|---|
|
|
110
111
|
| Roon Core address | Empty: found on your network. Fill in the Core's address for another subnet or a private VPN (Tailscale) |
|
|
111
|
-
| Essentials only | Seventeen tools instead of fifty-
|
|
112
|
+
| Essentials only | Seventeen tools instead of fifty-nine, for small models |
|
|
112
113
|
| Use Roon's internal protocol | Off: only the official API — zones, playback, volume, grouping; no search, reviews, Focus or history |
|
|
113
114
|
|
|
114
115
|
### Other clients
|
package/dist/deep-roon-mcp.js
CHANGED
|
@@ -15525,7 +15525,7 @@ var require_lib2 = __commonJS({
|
|
|
15525
15525
|
var version2;
|
|
15526
15526
|
var init_package = __esm({
|
|
15527
15527
|
"package.json"() {
|
|
15528
|
-
version2 = "0.4.
|
|
15528
|
+
version2 = "0.4.6";
|
|
15529
15529
|
}
|
|
15530
15530
|
});
|
|
15531
15531
|
|
|
@@ -16426,6 +16426,71 @@ async function discography(key, wanted) {
|
|
|
16426
16426
|
return { service: SERVICE_NAME[service2], artist, albums };
|
|
16427
16427
|
});
|
|
16428
16428
|
}
|
|
16429
|
+
async function trackFiles(a) {
|
|
16430
|
+
return withCore(async (c) => {
|
|
16431
|
+
const cb = { type: "ResultCallback<IList<TrackExportInfo>>", name: "cb_result" };
|
|
16432
|
+
let res;
|
|
16433
|
+
if (a.tracks?.length) {
|
|
16434
|
+
if (a.tracks.some((k) => !/^\d+$/.test(k))) throw new RoonError("A track key is a number", "invalid");
|
|
16435
|
+
res = await short(c.call(
|
|
16436
|
+
"Library",
|
|
16437
|
+
"GetTrackExportInfo",
|
|
16438
|
+
[{ type: "IEnumerable<long>", name: "ids" }, cb],
|
|
16439
|
+
buildArgs([Arg.collection(a.tracks.map((k) => buildArgs([Arg.long(BigInt(k))])))]),
|
|
16440
|
+
library(c)
|
|
16441
|
+
), 2e4);
|
|
16442
|
+
} else if (a.album) {
|
|
16443
|
+
const al = await findAlbum(c, a.album);
|
|
16444
|
+
res = await short(c.call(
|
|
16445
|
+
"Library",
|
|
16446
|
+
"GetTrackExportInfoForAlbums",
|
|
16447
|
+
[{ type: "IEnumerable<AlbumBase>", name: "albums" }, cb],
|
|
16448
|
+
buildArgs([Arg.collection([buildArgs([Arg.ref(al.oid)])])]),
|
|
16449
|
+
library(c)
|
|
16450
|
+
), 2e4);
|
|
16451
|
+
} else if (a.artist) {
|
|
16452
|
+
const p = await performer(c, a.artist);
|
|
16453
|
+
res = await short(c.call(
|
|
16454
|
+
"Library",
|
|
16455
|
+
"GetTrackExportInfoForPerformers",
|
|
16456
|
+
[{ type: "IEnumerable<PerformerBase>", name: "performers" }, cb],
|
|
16457
|
+
buildArgs([Arg.collection([buildArgs([Arg.ref(p.$ref)])])]),
|
|
16458
|
+
library(c)
|
|
16459
|
+
), 3e4);
|
|
16460
|
+
} else throw new RoonError("Give an album key, an artist key or track keys", "invalid");
|
|
16461
|
+
if (!res?.success) throw new RoonError(`Roon refused the file list (${res?.status ?? "no answer"})`);
|
|
16462
|
+
if (res.payload.length < 2) return { tracks: [] };
|
|
16463
|
+
const r = new BinaryReader(Uint8Array.from(res.payload));
|
|
16464
|
+
r.integer();
|
|
16465
|
+
const n = r.flexInt();
|
|
16466
|
+
const graph = c.graph;
|
|
16467
|
+
const infos = [];
|
|
16468
|
+
for (let i = 0; i < n && r.remaining > 0; i++) infos.push(graph.readValue(r, 23));
|
|
16469
|
+
await untilLoaded(c, infos.map((x) => field2(x, "Track")).filter(isRef2), 3e3);
|
|
16470
|
+
const paden = /* @__PURE__ */ __name((x) => items(c, field2(x, "DuplicateTracks")).map((d) => field2(d, "FilePath")).filter((p) => typeof p === "string" && !!p), "paden");
|
|
16471
|
+
const tracks2 = infos.map((x) => {
|
|
16472
|
+
const t = obj(c, field2(x, "Track"));
|
|
16473
|
+
const row = { key: String(field2(t, "TrackId") ?? "") };
|
|
16474
|
+
const title = field2(t, "Title");
|
|
16475
|
+
if (title) row.title = title;
|
|
16476
|
+
const al = field2(obj(c, field2(t, "Album")), "Title");
|
|
16477
|
+
if (al) row.album = al;
|
|
16478
|
+
const n2 = field2(t, "TrackNumber");
|
|
16479
|
+
if (n2 != null) row.n = n2;
|
|
16480
|
+
const path2 = field2(x, "FilePath");
|
|
16481
|
+
if (typeof path2 === "string" && path2) row.path = path2;
|
|
16482
|
+
const format = field2(t, "Format");
|
|
16483
|
+
if (format) row.format = format;
|
|
16484
|
+
const dup = paden(x);
|
|
16485
|
+
if (dup.length) row.duplicates = dup;
|
|
16486
|
+
return row;
|
|
16487
|
+
});
|
|
16488
|
+
const gevraagd = a.tracks?.length ?? 0, ontbreekt = gevraagd ? gevraagd - tracks2.length : 0;
|
|
16489
|
+
const zonder = tracks2.filter((t) => !t.path).length + Math.max(0, ontbreekt);
|
|
16490
|
+
const of = gevraagd || tracks2.length;
|
|
16491
|
+
return { tracks: tracks2, ...zonder ? { note: `${zonder} of ${of} ${of === 1 ? "track" : "tracks"} ${zonder === 1 ? "has" : "have"} no file (streaming tracks, or not on this Core's storage)` } : {} };
|
|
16492
|
+
});
|
|
16493
|
+
}
|
|
16429
16494
|
var SERVICE_NAME, SERVICE_TYPE, NONE2, SECTIONS, library, CREDIT_KIND, cut, choose;
|
|
16430
16495
|
var init_library = __esm({
|
|
16431
16496
|
"src/roon/library.ts"() {
|
|
@@ -16482,6 +16547,7 @@ var init_library = __esm({
|
|
|
16482
16547
|
__name(performer, "performer");
|
|
16483
16548
|
__name(bio, "bio");
|
|
16484
16549
|
__name(discography, "discography");
|
|
16550
|
+
__name(trackFiles, "trackFiles");
|
|
16485
16551
|
}
|
|
16486
16552
|
});
|
|
16487
16553
|
|
|
@@ -50376,6 +50442,16 @@ function registerReadTools(server, internal = true) {
|
|
|
50376
50442
|
inputSchema: { key: external_exports.string().describe("An album key."), credits: external_exports.boolean().optional() },
|
|
50377
50443
|
annotations: READ
|
|
50378
50444
|
}, ({ key, credits }) => run(() => album(key, credits === true)));
|
|
50445
|
+
reg("roon_track_files", {
|
|
50446
|
+
title: "File paths",
|
|
50447
|
+
description: "Where the user's own music files are: for an album (`album` key), an artist (`artist` key, all their tracks in the library) or a list of `tracks` keys, each track with its `key`, title, album, track number, format and `path` \u2014 the file as the Roon Core sees it (on the Core's storage), plus `duplicates` when Roon has the same track twice. The data behind Roon's own Export. Use it to link Roon's tracks to files, e.g. to join with data the user keeps per file. Streaming tracks (Qobuz, TIDAL) have no path.",
|
|
50448
|
+
inputSchema: {
|
|
50449
|
+
album: external_exports.string().optional().describe("An album key."),
|
|
50450
|
+
artist: external_exports.string().optional().describe("An artist key."),
|
|
50451
|
+
tracks: external_exports.array(external_exports.string()).max(500).optional().describe("Track keys.")
|
|
50452
|
+
},
|
|
50453
|
+
annotations: READ
|
|
50454
|
+
}, ({ album: album2, artist, tracks: tracks2 }) => run(() => trackFiles({ album: album2, artist, tracks: tracks2 })));
|
|
50379
50455
|
reg("roon_review", {
|
|
50380
50456
|
title: "Album review",
|
|
50381
50457
|
description: "The review Roon shows on an album's page, in full, with its author \u2014 by album `key`. When the user asks for the review (\"what does Roon say about this album\", \"show me the review\"), they want to read the review itself: show the text complete and exactly as returned, as a quote with the author's name \u2014 no paraphrase or shortening unless they ask for a summary, and don't go to the web for it. `wikipedia: true` adds the album's Wikipedia text; it also comes by itself when Roon has no review of its own. `max_chars` only when a shorter excerpt is asked for.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deep-roon-mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "An MCP server for Roon that reaches the parts the official API doesn't: reviews, biographies, Qobuz's and TIDAL's shelves, TIDAL's playlists, your history, search across your library and the streaming services",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "René Bouwmeester <21277119+renebouwmeester@users.noreply.github.com>",
|