deep-roon-mcp 0.3.0 → 0.3.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/dist/deep-roon-mcp.js +44 -8
- package/package.json +1 -1
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.3.
|
|
15528
|
+
version2 = "0.3.6";
|
|
15529
15529
|
}
|
|
15530
15530
|
});
|
|
15531
15531
|
|
|
@@ -15852,6 +15852,11 @@ function field2(o, name) {
|
|
|
15852
15852
|
for (const [k, v] of Object.entries(f)) if (k.endsWith("::" + name)) return v;
|
|
15853
15853
|
return void 0;
|
|
15854
15854
|
}
|
|
15855
|
+
function imageKey(c, o, name = "Image") {
|
|
15856
|
+
const url2 = field2(obj(c, field2(o, name)), "Url");
|
|
15857
|
+
const m = /^broker:\/\/\/image\/([^.]+)\./.exec(String(url2 ?? ""));
|
|
15858
|
+
return m ? m[1] : void 0;
|
|
15859
|
+
}
|
|
15855
15860
|
function dateParts(v) {
|
|
15856
15861
|
if (!Buffer.isBuffer(v) || v.length < 2) return void 0;
|
|
15857
15862
|
const n = new BinaryReader(Uint8Array.from(v)).flexInt();
|
|
@@ -15953,6 +15958,8 @@ function albumRow(c, o) {
|
|
|
15953
15958
|
if (flag(field2(o, "IsListenLater"))) r.listen_later = true;
|
|
15954
15959
|
const p = field2(o, "Popularity");
|
|
15955
15960
|
if (typeof p === "number") r.popularity = Math.round(p * 100) / 100;
|
|
15961
|
+
const img = imageKey(c, o);
|
|
15962
|
+
if (img) r.image = img;
|
|
15956
15963
|
return r;
|
|
15957
15964
|
}
|
|
15958
15965
|
function resultRow(c, o) {
|
|
@@ -15962,6 +15969,8 @@ function resultRow(c, o) {
|
|
|
15962
15969
|
const r = { kind: "artist", key: String(field2(o, "PerformerId") ?? ""), name: field2(o, "Name") ?? "" };
|
|
15963
15970
|
const n = field2(o, "MainPerformerAlbumCount");
|
|
15964
15971
|
if (typeof n === "number" && n > 0) r.albums_in_library = n;
|
|
15972
|
+
const img = imageKey(c, o, "Photo") ?? imageKey(c, o, "Avatar");
|
|
15973
|
+
if (img) r.image = img;
|
|
15965
15974
|
return r;
|
|
15966
15975
|
}
|
|
15967
15976
|
if (t === "TrackLite" || t === "Track") {
|
|
@@ -15976,6 +15985,8 @@ function resultRow(c, o) {
|
|
|
15976
15985
|
const l = field2(o, "LengthSeconds");
|
|
15977
15986
|
if (typeof l === "number") r.length = duration3(l);
|
|
15978
15987
|
r.source = sourceOf(o, al) ?? "library";
|
|
15988
|
+
const img = imageKey(c, al) ?? imageKey(c, o);
|
|
15989
|
+
if (img) r.image = img;
|
|
15979
15990
|
return r;
|
|
15980
15991
|
}
|
|
15981
15992
|
return { kind: t, title: field2(o, "Title") ?? field2(o, "Name") ?? "" };
|
|
@@ -15996,6 +16007,7 @@ var init_graph = __esm({
|
|
|
15996
16007
|
return (o?.fields?.$items ?? o?.$items ?? []).filter((x) => typeof x === "string");
|
|
15997
16008
|
}, "strings");
|
|
15998
16009
|
flag = /* @__PURE__ */ __name((v) => v === true || Buffer.isBuffer(v) && v.length > 1 && v[0] >= 1, "flag");
|
|
16010
|
+
__name(imageKey, "imageKey");
|
|
15999
16011
|
__name(dateParts, "dateParts");
|
|
16000
16012
|
yearOf = /* @__PURE__ */ __name((v) => dateParts(v)?.[0], "yearOf");
|
|
16001
16013
|
__name(dateText, "dateText");
|
|
@@ -16357,6 +16369,8 @@ async function bio(key, max, wiki) {
|
|
|
16357
16369
|
}
|
|
16358
16370
|
const g = list("Genres").filter((x) => typeof x === "string");
|
|
16359
16371
|
if (g.length) artist.genres = g;
|
|
16372
|
+
const img = imageKey(c, p, "Photo") ?? imageKey(c, p, "Avatar");
|
|
16373
|
+
if (img) artist.image = img;
|
|
16360
16374
|
return {
|
|
16361
16375
|
artist,
|
|
16362
16376
|
texts,
|
|
@@ -16388,6 +16402,8 @@ async function discography(key, wanted) {
|
|
|
16388
16402
|
if (typeof n === "number") artist.albums_in_library = n;
|
|
16389
16403
|
const from = field2(p, "BirthPlace");
|
|
16390
16404
|
if (from) artist.from = from;
|
|
16405
|
+
const img = imageKey(c, p, "Photo") ?? imageKey(c, p, "Avatar");
|
|
16406
|
+
if (img) artist.image = img;
|
|
16391
16407
|
return { service: SERVICE_NAME[service2], artist, albums };
|
|
16392
16408
|
});
|
|
16393
16409
|
}
|
|
@@ -49384,6 +49400,8 @@ async function nowPlaying() {
|
|
|
49384
49400
|
r.volume_max = v.max;
|
|
49385
49401
|
if (v.is_muted) r.muted = true;
|
|
49386
49402
|
}
|
|
49403
|
+
const img = off?.now_playing?.image_key;
|
|
49404
|
+
if (img) r.image = img;
|
|
49387
49405
|
const outs = c.graph.findByType("Endpoint").filter((e) => {
|
|
49388
49406
|
const zz = field2(e, "Zone");
|
|
49389
49407
|
return isRef2(zz) && String(zz.$ref) === String(z2.oid);
|
|
@@ -49397,17 +49415,23 @@ async function nowPlaying() {
|
|
|
49397
49415
|
if (r.state === "stopped") return r;
|
|
49398
49416
|
const np = obj(c, field2(z2, "NowPlaying"));
|
|
49399
49417
|
const tr = obj(c, field2(items(c, field2(np, "Tracks"))[0], "Track"));
|
|
49400
|
-
|
|
49418
|
+
const lines = off?.now_playing?.three_line;
|
|
49419
|
+
const t = String(field2(tr, "Title") ?? "").toLowerCase(), l1 = String(lines?.line1 ?? "").toLowerCase();
|
|
49420
|
+
const stream = !!l1 && !!t && !l1.includes(t) && !t.includes(l1);
|
|
49421
|
+
if (tr && field2(tr, "Title") && !stream) {
|
|
49401
49422
|
const al = obj(c, field2(tr, "Album"));
|
|
49402
49423
|
r.title = field2(tr, "Title");
|
|
49403
49424
|
r.artist = clean(field2(al, "PerformedBy"));
|
|
49404
49425
|
r.album = field2(al, "Title");
|
|
49426
|
+
const tk = field2(tr, "TrackId");
|
|
49427
|
+
if (tk !== void 0 && tk !== null) r.track_key = String(tk);
|
|
49428
|
+
const ak = field2(al, "AlbumId");
|
|
49429
|
+
if (ak !== void 0 && ak !== null) r.album_key = String(ak);
|
|
49405
49430
|
const f = field2(tr, "Format");
|
|
49406
49431
|
if (f) r.format = f;
|
|
49407
49432
|
const l = field2(tr, "LengthSeconds");
|
|
49408
49433
|
if (typeof l === "number") r.length = duration3(l);
|
|
49409
49434
|
} else {
|
|
49410
|
-
const lines = off?.now_playing?.three_line;
|
|
49411
49435
|
if (lines?.line1) {
|
|
49412
49436
|
r.title = lines.line1;
|
|
49413
49437
|
if (lines.line2) r.artist = lines.line2;
|
|
@@ -49418,7 +49442,7 @@ async function nowPlaying() {
|
|
|
49418
49442
|
const pos = field2(z2, "SeekPosition");
|
|
49419
49443
|
if (typeof pos === "number") r.position = duration3(pos);
|
|
49420
49444
|
const left = field2(obj(c, field2(z2, "Queue")), "TrackCountRemaining");
|
|
49421
|
-
if (typeof left === "number") r.tracks_left_in_queue = left;
|
|
49445
|
+
if (typeof left === "number" && !r.source) r.tracks_left_in_queue = left;
|
|
49422
49446
|
return r;
|
|
49423
49447
|
});
|
|
49424
49448
|
return { zones: out };
|
|
@@ -49659,7 +49683,7 @@ function registerReadTools(server, internal = true) {
|
|
|
49659
49683
|
const reg = /* @__PURE__ */ __name(((name, cfg, cb) => R(name) ? server.registerTool.call(server, name, cfg, cb) : void 0), "reg");
|
|
49660
49684
|
reg("roon_search", {
|
|
49661
49685
|
title: "Search Roon",
|
|
49662
|
-
description: 'Search Roon \u2014 the user\'s Roon library and the catalogues of the streaming services signed in (Qobuz, TIDAL) at once; each row says its `source` (library, Qobuz, TIDAL). Returns albums (with format, e.g. FLAC 96kHz 24bit), artists and tracks. `kind` narrows to album, track or artist \u2014 use `kind: "track"` with the song title (and artist) to find one specific track; a broad query without `kind` can fill up with artists. Albums and tracks carry a `key` for `roon_album`; artists carry a `key` for `roon_artist` and `roon_bio`. Albums say their `year`, and `in_library` when the user has them in Roon. Keys stay valid between calls.',
|
|
49686
|
+
description: 'Search Roon \u2014 the user\'s Roon library and the catalogues of the streaming services signed in (Qobuz, TIDAL) at once; each row says its `source` (library, Qobuz, TIDAL). Rows carry `image`, the key of the cover: the Core serves it at http://<core address>:9330/image/<key>.jpg, or scaled at /image/<key>.300.jpg (for clients that draw; a chat shows covers with roon_cover). The `image` of roon_now_playing is an official key, served at /api/image/<key>?scale=fit&width=600&height=600. Returns albums (with format, e.g. FLAC 96kHz 24bit), artists and tracks. `kind` narrows to album, track or artist \u2014 use `kind: "track"` with the song title (and artist) to find one specific track; a broad query without `kind` can fill up with artists. Albums and tracks carry a `key` for `roon_album`; artists carry a `key` for `roon_artist` and `roon_bio`. Albums say their `year`, and `in_library` when the user has them in Roon. Keys stay valid between calls.',
|
|
49663
49687
|
inputSchema: { query: external_exports.string().min(1), kind: external_exports.enum(["album", "track", "artist"]).optional(), limit: limit(100, 30) },
|
|
49664
49688
|
annotations: READ
|
|
49665
49689
|
}, ({ query, kind, limit: n }) => run(() => search(query, n ?? 30, kind)));
|
|
@@ -49866,6 +49890,14 @@ async function playTracks(keys, zoneName, how) {
|
|
|
49866
49890
|
__name(playTracks, "playTracks");
|
|
49867
49891
|
var BUTTONS = { pause: "Pause", play: "Play", playpause: "PlayPause", stop: "Stop", next: "Next", previous: "Previous" };
|
|
49868
49892
|
async function control2(zoneName, action) {
|
|
49893
|
+
if (action === "release") {
|
|
49894
|
+
return withCore(async (c) => {
|
|
49895
|
+
const z2 = findZone(c, zoneName);
|
|
49896
|
+
c.remoting.callMethodNoReply(z2.oid, formatMethodSignature("Zone", "Suspend", []), Buffer.alloc(0));
|
|
49897
|
+
log(`control: Suspend on ${z2.name}`);
|
|
49898
|
+
return { ok: true, zone: z2.name, action };
|
|
49899
|
+
});
|
|
49900
|
+
}
|
|
49869
49901
|
const m = BUTTONS[action];
|
|
49870
49902
|
if (!m) throw new RoonError(`Unknown action; choose from ${Object.keys(BUTTONS).join(", ")}`, "invalid");
|
|
49871
49903
|
const r = await withCore(async (c) => {
|
|
@@ -49948,6 +49980,10 @@ function queueRow(c, oid) {
|
|
|
49948
49980
|
if (album2) r.album = album2;
|
|
49949
49981
|
const l = field2(tr, "LengthSeconds");
|
|
49950
49982
|
if (typeof l === "number") r.length = duration3(l);
|
|
49983
|
+
const k = field2(tr, "TrackId");
|
|
49984
|
+
if (k !== void 0 && k !== null) r.key = String(k);
|
|
49985
|
+
const img = imageKey(c, al) ?? imageKey(c, tr);
|
|
49986
|
+
if (img) r.image = img;
|
|
49951
49987
|
return r;
|
|
49952
49988
|
}
|
|
49953
49989
|
__name(queueRow, "queueRow");
|
|
@@ -50128,10 +50164,10 @@ function registerControlTools(server, internal = true) {
|
|
|
50128
50164
|
}, ({ keys, zone: zn, mode: m }) => run(() => playTracks(keys, zn, m ?? "now")));
|
|
50129
50165
|
reg("roon_control", {
|
|
50130
50166
|
title: "Transport",
|
|
50131
|
-
description: "The buttons of a Roon zone: pause, play (resume), playpause, stop, next, previous. Only on an explicit request, never as a probe.",
|
|
50132
|
-
inputSchema: { zone, action: external_exports.enum(["pause", "play", "playpause", "stop", "next", "previous"]) },
|
|
50167
|
+
description: "The buttons of a Roon zone: pause, play (resume), playpause, stop, next, previous \u2014 and release: let go of the audio device right away (what Roon does by itself a few seconds after a pause), so another source can use the speaker. Only on an explicit request, never as a probe.",
|
|
50168
|
+
inputSchema: { zone, action: external_exports.enum(["pause", "play", "playpause", "stop", "next", "previous", "release"]) },
|
|
50133
50169
|
annotations: PLAYS
|
|
50134
|
-
}, ({ zone: zn, action }) => run(() => internal ? control2(zn, action) : control(zn, action)));
|
|
50170
|
+
}, ({ zone: zn, action }) => run(() => internal ? control2(zn, action) : action === "release" ? Promise.reject(new Error("release needs Roon's internal protocol (switched off)")) : control(zn, action)));
|
|
50135
50171
|
reg("roon_seek", {
|
|
50136
50172
|
title: "Seek",
|
|
50137
50173
|
description: "Jump to a position in the track that plays on a Roon zone, in seconds from the start. 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.
|
|
3
|
+
"version": "0.3.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>",
|