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 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-seven tools. Qobuz, TIDAL or both — whatever is signed in on your Core.
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-seven, for small models |
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
@@ -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.5";
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.5",
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>",