deep-roon-mcp 0.3.6 → 0.3.8

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.2: fifty-one tools. Qobuz, TIDAL or both — whatever is signed in on your Core.
25
+ Version 0.3: fifty-three tools. Qobuz, TIDAL or both — whatever is signed in on your Core.
26
26
 
27
27
  **Reading** — changes nothing in Roon:
28
28
 
@@ -38,7 +38,7 @@ Version 0.2: fifty-one tools. Qobuz, TIDAL or both — whatever is signed in on
38
38
  | `roon_status` | How Deep Roon MCP stands: Core, pairing, internal protocol, self-test, newer version |
39
39
  | `roon_zones` | Roon's zones and groups |
40
40
  | `roon_now_playing` | What plays in each zone, with the volume |
41
- | `roon_queue` | A zone's queue |
41
+ | `roon_queue` | A zone's queue, numbered (1 is what plays) |
42
42
  | `roon_cover` | The cover of what plays, as an image |
43
43
  | `roon_history` | Roon's play history, newest first |
44
44
  | `roon_top` | Most played albums, artists or tracks over your whole history |
@@ -68,6 +68,7 @@ Version 0.2: fifty-one tools. Qobuz, TIDAL or both — whatever is signed in on
68
68
  | `roon_seek` | A position in the playing track |
69
69
  | `roon_volume` | A zone's volume, mute |
70
70
  | `roon_settings` | Shuffle, repeat, Roon Radio |
71
+ | `roon_queue_jump`, `roon_queue_remove` | Jump to an item of the queue, take items out of it |
71
72
  | `roon_queue_clear` | Clear what comes after the playing track (or everything) |
72
73
  | `roon_transfer` | Move what plays, with its queue, to another zone |
73
74
  | `roon_group`, `roon_ungroup` | Group zones, and undo it |
@@ -104,7 +105,7 @@ The settings (Claude Desktop › Settings › Extensions › Deep Roon MCP):
104
105
  | Setting | What it does |
105
106
  |---|---|
106
107
  | Roon Core address | Empty: found on your network. Fill in the Core's address for another subnet or a private VPN (Tailscale) |
107
- | Essentials only | Seventeen tools instead of fifty-one, for small models |
108
+ | Essentials only | Seventeen tools instead of fifty-three, for small models |
108
109
  | Use Roon's internal protocol | Off: only the official API — zones, playback, volume, grouping; no search, reviews, Focus or history |
109
110
 
110
111
  ### 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.3.6";
15528
+ version2 = "0.3.8";
15529
15529
  }
15530
15530
  });
15531
15531
 
@@ -16353,7 +16353,12 @@ async function bio(key, max, wiki) {
16353
16353
  for (const n of names2) for (const x of list(n)) {
16354
16354
  const o = obj(c, x);
16355
16355
  const name = field2(o, "Name");
16356
- if (name && !out.some((y) => y.name === name)) out.push({ key: String(field2(o, "PerformerId") ?? "") || void 0, name });
16356
+ if (name && !out.some((y) => y.name === name)) {
16357
+ const r = { key: String(field2(o, "PerformerId") ?? "") || void 0, name };
16358
+ const img2 = imageKey(c, o, "Photo") ?? imageKey(c, o, "Avatar");
16359
+ if (img2) r.image = img2;
16360
+ out.push(r);
16361
+ }
16357
16362
  }
16358
16363
  return out.length ? out.slice(0, 12) : void 0;
16359
16364
  }, "people");
@@ -49297,7 +49302,15 @@ async function history(limit3) {
49297
49302
  closeSoon("after the history");
49298
49303
  return {
49299
49304
  total: out.total ?? null,
49300
- plays: out.events.map((e) => ({ at: localTime(e.playedAt), title: e.title, artist: clean(e.artist), album: e.album ?? void 0, played_pct: e.completionPct ?? void 0 }))
49305
+ plays: out.events.map((e) => ({
49306
+ at: localTime(e.playedAt),
49307
+ title: e.title,
49308
+ artist: clean(e.artist),
49309
+ album: e.album ?? void 0,
49310
+ played_pct: e.completionPct ?? void 0,
49311
+ track_key: e.roonTrackId ?? void 0
49312
+ }))
49313
+ // for roon_play_tracks (13 Oct 2026, Rosso's history)
49301
49314
  };
49302
49315
  });
49303
49316
  }
@@ -49987,6 +50000,40 @@ function queueRow(c, oid) {
49987
50000
  return r;
49988
50001
  }
49989
50002
  __name(queueRow, "queueRow");
50003
+ async function queueItemAt(c, zoneName, n) {
50004
+ const { z: z2, q } = queueOf(c, zoneName);
50005
+ const refs = await queueItems(c, q);
50006
+ const at = Math.max(0, field2(c.graph.getObject(q.oid), "CurrentItemIndex") ?? 0);
50007
+ const oid = refs[at + n - 1];
50008
+ if (oid === void 0) throw new RoonError(`The queue of ${z2.name} has no item ${n} (${Math.max(0, refs.length - at)} from here)`, "invalid");
50009
+ return { z: z2, q, oid, refs, at };
50010
+ }
50011
+ __name(queueItemAt, "queueItemAt");
50012
+ async function queueJump(zoneName, n) {
50013
+ const r = await withCore(async (c) => {
50014
+ const { z: z2, q, oid } = await queueItemAt(c, zoneName, n);
50015
+ c.remoting.callMethodNoReply(q.oid, formatMethodSignature("Queue", "PlayFromHere", [{ type: "TransportItem", name: "item" }]), buildArgs([Arg.ref(oid)]));
50016
+ log(`queue_jump: ${z2.name} \u2192 item ${n}`);
50017
+ return { zone: z2.name, n };
50018
+ });
50019
+ return { ok: true, ...r, ...await started2(r.zone, PRIORITY["now"]) };
50020
+ }
50021
+ __name(queueJump, "queueJump");
50022
+ async function queueRemove(zoneName, ns) {
50023
+ return withCore(async (c) => {
50024
+ const { z: z2, q } = queueOf(c, zoneName);
50025
+ const refs = await queueItems(c, q);
50026
+ const at = Math.max(0, field2(c.graph.getObject(q.oid), "CurrentItemIndex") ?? 0);
50027
+ const oids = [...new Set(ns)].map((n) => refs[at + n - 1]).filter((o) => o !== void 0);
50028
+ if (!oids.length) throw new RoonError("No such items in the queue", "invalid");
50029
+ c.remoting.callMethodNoReply(q.oid, formatMethodSignature("Queue", "RemoveItems", [{ type: "IEnumerable<TransportItem>", name: "items" }]), buildArgs([Arg.collection(oids.map((o) => buildArgs([Arg.ref(o)])))]));
50030
+ log(`queue_remove: ${z2.name} \u2212 ${oids.length} item(s)`);
50031
+ await wait(300);
50032
+ const after = await queueItems(c, q);
50033
+ return { ok: true, zone: z2.name, removed: oids.length, left: Math.max(0, after.length - at) };
50034
+ });
50035
+ }
50036
+ __name(queueRemove, "queueRemove");
49990
50037
  async function queue2(zoneName, limit3) {
49991
50038
  return withCore(async (c) => {
49992
50039
  const { z: z2, q } = queueOf(c, zoneName);
@@ -50143,6 +50190,7 @@ __name(cover2, "cover");
50143
50190
  // src/tools/control.ts
50144
50191
  init_official();
50145
50192
  init_session();
50193
+ var CHANGES_QUEUE = { readOnlyHint: false, destructiveHint: true, openWorldHint: false };
50146
50194
  var PLAYS = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
50147
50195
  var REMOVES = { readOnlyHint: false, destructiveHint: true, openWorldHint: false };
50148
50196
  var zone = external_exports.string().describe("A Roon zone name (see roon_zones).");
@@ -50195,6 +50243,18 @@ function registerControlTools(server, internal = true) {
50195
50243
  inputSchema: { zone, limit: external_exports.number().int().min(1).max(200).optional() },
50196
50244
  annotations: { readOnlyHint: true, openWorldHint: false }
50197
50245
  }, ({ zone: zn, limit: limit3 }) => run(() => queue2(zn, limit3 ?? 50)));
50246
+ reg("roon_queue_jump", {
50247
+ title: "Jump in the queue",
50248
+ description: "Play an item of the zone's queue now (`n` as roon_queue numbers them: 1 is what plays, 2 the next \u2026); what came before it is done. Only on an explicit request.",
50249
+ inputSchema: { zone, n: external_exports.number().int().min(1) },
50250
+ annotations: PLAYS
50251
+ }, ({ zone: zn, n }) => run(() => queueJump(zn, n)));
50252
+ reg("roon_queue_remove", {
50253
+ title: "Remove from the queue",
50254
+ description: "Take items out of the zone's queue, by their numbers from roon_queue (`ns`, e.g. [3, 4]). Only on an explicit request.",
50255
+ inputSchema: { zone, ns: external_exports.array(external_exports.number().int().min(2)).min(1).max(100).describe("Positions from roon_queue (2 and up: 1 is what plays).") },
50256
+ annotations: CHANGES_QUEUE
50257
+ }, ({ zone: zn, ns }) => run(() => queueRemove(zn, ns)));
50198
50258
  reg("roon_queue_clear", {
50199
50259
  title: "Clear the queue",
50200
50260
  description: "Clear the queue of a Roon zone. By default the track that plays keeps playing and everything after it is removed; `everything: true` also removes the playing track (the zone falls silent). Only on an explicit request.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deep-roon-mcp",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
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>",