deep-roon-mcp 0.2.1 → 0.2.2
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 +2 -0
- package/dist/deep-roon-mcp.js +51 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -179,6 +179,8 @@ status; it holds no secrets).
|
|
|
179
179
|
|
|
180
180
|
- Short connections, as Roon Labs asked: opened for a question, closed after two minutes of quiet, one at a time, questions
|
|
181
181
|
one after another; a PING every 10 s while open, so the Core doesn't drop the session.
|
|
182
|
+
- Several clients at once (Claude Desktop and Claude Code, or ChatGPT next to them) work: Roon keeps one connection
|
|
183
|
+
per extension, and the client that asks a question takes it over for that moment.
|
|
182
184
|
- Album, track and artist keys stay valid between calls.
|
|
183
185
|
- The log goes to stderr (your MCP client keeps it).
|
|
184
186
|
- At the start it asks GitHub once whether a newer release is out (`DEEP_ROON_NO_UPDATE_CHECK=1` turns that off). Nothing
|
package/dist/deep-roon-mcp.js
CHANGED
|
@@ -5340,7 +5340,7 @@ var require_core2 = __commonJS({
|
|
|
5340
5340
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
5341
5341
|
var id_1 = require_id();
|
|
5342
5342
|
var ref_1 = require_ref();
|
|
5343
|
-
var
|
|
5343
|
+
var core2 = [
|
|
5344
5344
|
"$schema",
|
|
5345
5345
|
"$id",
|
|
5346
5346
|
"$defs",
|
|
@@ -5350,7 +5350,7 @@ var require_core2 = __commonJS({
|
|
|
5350
5350
|
id_1.default,
|
|
5351
5351
|
ref_1.default
|
|
5352
5352
|
];
|
|
5353
|
-
exports2.default =
|
|
5353
|
+
exports2.default = core2;
|
|
5354
5354
|
}
|
|
5355
5355
|
});
|
|
5356
5356
|
|
|
@@ -13239,7 +13239,7 @@ var require_stream = __commonJS({
|
|
|
13239
13239
|
};
|
|
13240
13240
|
duplex._final = function(callback) {
|
|
13241
13241
|
if (ws.readyState === ws.CONNECTING) {
|
|
13242
|
-
ws.once("open", /* @__PURE__ */ __name(function
|
|
13242
|
+
ws.once("open", /* @__PURE__ */ __name(function open4() {
|
|
13243
13243
|
duplex._final(callback);
|
|
13244
13244
|
}, "open"));
|
|
13245
13245
|
return;
|
|
@@ -13260,7 +13260,7 @@ var require_stream = __commonJS({
|
|
|
13260
13260
|
};
|
|
13261
13261
|
duplex._write = function(chunk, encoding, callback) {
|
|
13262
13262
|
if (ws.readyState === ws.CONNECTING) {
|
|
13263
|
-
ws.once("open", /* @__PURE__ */ __name(function
|
|
13263
|
+
ws.once("open", /* @__PURE__ */ __name(function open4() {
|
|
13264
13264
|
duplex._write(chunk, encoding, callback);
|
|
13265
13265
|
}, "open"));
|
|
13266
13266
|
return;
|
|
@@ -14277,7 +14277,7 @@ var require_moomsg = __commonJS({
|
|
|
14277
14277
|
var require_core3 = __commonJS({
|
|
14278
14278
|
"node_modules/node-roon-api/core.js"(exports2, module2) {
|
|
14279
14279
|
"use strict";
|
|
14280
|
-
function Core(moo,
|
|
14280
|
+
function Core(moo, roon2, registration) {
|
|
14281
14281
|
this.moo = moo;
|
|
14282
14282
|
this.core_id = registration.core_id;
|
|
14283
14283
|
this.display_name = registration.display_name;
|
|
@@ -14285,8 +14285,8 @@ var require_core3 = __commonJS({
|
|
|
14285
14285
|
this.registration = registration;
|
|
14286
14286
|
this.services = {};
|
|
14287
14287
|
var svcs = {};
|
|
14288
|
-
|
|
14289
|
-
|
|
14288
|
+
roon2.services_opts.required_services.forEach((svcobj) => svcobj.services.forEach((svc) => svcs[svc.name] = svcobj));
|
|
14289
|
+
roon2.services_opts.optional_services.forEach((svcobj) => svcobj.services.forEach((svc) => svcs[svc.name] = svcobj));
|
|
14290
14290
|
registration.provided_services.forEach((e) => {
|
|
14291
14291
|
this.services[svcs[e].name] = new svcs[e](this);
|
|
14292
14292
|
});
|
|
@@ -14903,24 +14903,24 @@ var require_lib = __commonJS({
|
|
|
14903
14903
|
});
|
|
14904
14904
|
this.pairing_service_1 = {
|
|
14905
14905
|
services: [svc],
|
|
14906
|
-
found_core: /* @__PURE__ */ __name((
|
|
14906
|
+
found_core: /* @__PURE__ */ __name((core2) => {
|
|
14907
14907
|
if (!this.paired_core_id) {
|
|
14908
14908
|
let settings3 = this.get_persisted_state();
|
|
14909
|
-
settings3.paired_core_id =
|
|
14909
|
+
settings3.paired_core_id = core2.core_id;
|
|
14910
14910
|
this.set_persisted_state(settings3);
|
|
14911
|
-
this.paired_core_id =
|
|
14912
|
-
this.paired_core =
|
|
14911
|
+
this.paired_core_id = core2.core_id;
|
|
14912
|
+
this.paired_core = core2;
|
|
14913
14913
|
this.is_paired = true;
|
|
14914
14914
|
svc.send_continue_all("subscribe_pairing", "Changed", { paired_core_id: this.paired_core_id });
|
|
14915
14915
|
}
|
|
14916
|
-
if (
|
|
14917
|
-
if (this.extension_opts.core_paired) this.extension_opts.core_paired(
|
|
14916
|
+
if (core2.core_id == this.paired_core_id) {
|
|
14917
|
+
if (this.extension_opts.core_paired) this.extension_opts.core_paired(core2);
|
|
14918
14918
|
}
|
|
14919
14919
|
}, "found_core"),
|
|
14920
|
-
lost_core: /* @__PURE__ */ __name((
|
|
14921
|
-
if (
|
|
14920
|
+
lost_core: /* @__PURE__ */ __name((core2) => {
|
|
14921
|
+
if (core2.core_id == this.paired_core_id)
|
|
14922
14922
|
this.is_paired = false;
|
|
14923
|
-
if (this.extension_opts.core_unpaired) this.extension_opts.core_unpaired(
|
|
14923
|
+
if (this.extension_opts.core_unpaired) this.extension_opts.core_unpaired(core2);
|
|
14924
14924
|
}, "lost_core")
|
|
14925
14925
|
};
|
|
14926
14926
|
o.provided_services.push(this.pairing_service_1);
|
|
@@ -15113,8 +15113,8 @@ var require_lib2 = __commonJS({
|
|
|
15113
15113
|
return zo.zone_id;
|
|
15114
15114
|
}
|
|
15115
15115
|
__name(zoid, "zoid");
|
|
15116
|
-
function RoonApiTransport2(
|
|
15117
|
-
this.core =
|
|
15116
|
+
function RoonApiTransport2(core2) {
|
|
15117
|
+
this.core = core2;
|
|
15118
15118
|
this._queues = {};
|
|
15119
15119
|
}
|
|
15120
15120
|
__name(RoonApiTransport2, "RoonApiTransport");
|
|
@@ -15416,7 +15416,7 @@ var require_lib2 = __commonJS({
|
|
|
15416
15416
|
var version2;
|
|
15417
15417
|
var init_package = __esm({
|
|
15418
15418
|
"package.json"() {
|
|
15419
|
-
version2 = "0.2.
|
|
15419
|
+
version2 = "0.2.2";
|
|
15420
15420
|
}
|
|
15421
15421
|
});
|
|
15422
15422
|
|
|
@@ -15443,7 +15443,8 @@ function configDir() {
|
|
|
15443
15443
|
function startOfficial(target2) {
|
|
15444
15444
|
if (started) return;
|
|
15445
15445
|
started = true;
|
|
15446
|
-
|
|
15446
|
+
core = target2;
|
|
15447
|
+
roon = new import_node_roon_api.default({
|
|
15447
15448
|
extension_id: "nl.renebouwmeester.deep-roon-mcp",
|
|
15448
15449
|
display_name: "Deep Roon MCP",
|
|
15449
15450
|
display_version: version2,
|
|
@@ -15453,13 +15454,13 @@ function startOfficial(target2) {
|
|
|
15453
15454
|
log_level: "none",
|
|
15454
15455
|
get_persisted_state: readState,
|
|
15455
15456
|
set_persisted_state: writeState,
|
|
15456
|
-
core_paired(
|
|
15457
|
-
transport =
|
|
15457
|
+
core_paired(core2) {
|
|
15458
|
+
transport = core2.services.RoonApiTransport ?? Object.entries(core2.services ?? {}).find(([k]) => k.startsWith("RoonApiTransport"))?.[1];
|
|
15458
15459
|
if (!transport) {
|
|
15459
15460
|
log("paired, but the Core offers no transport service");
|
|
15460
15461
|
return;
|
|
15461
15462
|
}
|
|
15462
|
-
log(`paired with ${
|
|
15463
|
+
log(`paired with ${core2.display_name} (Roon \u203A Settings \u203A Extensions)`);
|
|
15463
15464
|
transport.subscribe_zones((cmd, data) => {
|
|
15464
15465
|
if (cmd === "Subscribed") {
|
|
15465
15466
|
zones.clear();
|
|
@@ -15490,25 +15491,26 @@ function startOfficial(target2) {
|
|
|
15490
15491
|
}
|
|
15491
15492
|
});
|
|
15492
15493
|
roon.init_services({ required_services: [import_node_roon_api_transport.default] });
|
|
15493
|
-
let tries = 0;
|
|
15494
|
-
const connect = /* @__PURE__ */ __name(() => {
|
|
15495
|
-
roon.ws_connect({
|
|
15496
|
-
host: target2.host,
|
|
15497
|
-
port: target2.port,
|
|
15498
|
-
onclose: /* @__PURE__ */ __name(() => {
|
|
15499
|
-
transport = null;
|
|
15500
|
-
zones.clear();
|
|
15501
|
-
zonesIn = false;
|
|
15502
|
-
const delay = [5, 15, 60][tries] ?? 300;
|
|
15503
|
-
tries += 1;
|
|
15504
|
-
setTimeout(connect, delay * 1e3).unref();
|
|
15505
|
-
}, "onclose")
|
|
15506
|
-
});
|
|
15507
|
-
}, "connect");
|
|
15508
15494
|
connect();
|
|
15509
15495
|
}
|
|
15496
|
+
function connect() {
|
|
15497
|
+
if (open2 || !roon || !core) return;
|
|
15498
|
+
open2 = true;
|
|
15499
|
+
roon.ws_connect({
|
|
15500
|
+
host: core.host,
|
|
15501
|
+
port: core.port,
|
|
15502
|
+
onclose: /* @__PURE__ */ __name(() => {
|
|
15503
|
+
open2 = false;
|
|
15504
|
+
transport = null;
|
|
15505
|
+
zones.clear();
|
|
15506
|
+
zonesIn = false;
|
|
15507
|
+
log("the extension connection closed (another copy of Deep Roon MCP may have taken it over) \u2014 it opens again when a tool needs it");
|
|
15508
|
+
}, "onclose")
|
|
15509
|
+
});
|
|
15510
|
+
}
|
|
15510
15511
|
async function paired(ms = 8e3) {
|
|
15511
15512
|
if (transport) return transport;
|
|
15513
|
+
connect();
|
|
15512
15514
|
await new Promise((resolve) => {
|
|
15513
15515
|
waiting.push(resolve);
|
|
15514
15516
|
setTimeout(resolve, ms).unref();
|
|
@@ -15547,7 +15549,7 @@ async function setVolume(name, change) {
|
|
|
15547
15549
|
}
|
|
15548
15550
|
return { zone: z2.display_name, outputs: outs.map((o) => ({ output: o.display_name, scale: `${o.volume.min}\u2013${o.volume.max}` })), volume: change.value, muted: change.mute };
|
|
15549
15551
|
}
|
|
15550
|
-
var import_fs, import_os, import_path, import_node_roon_api, import_node_roon_api_transport, STATE_FILE, readState, writeState, transport, zones, started, waiting, zonesIn, zoneWaiting, officialZones, isPaired;
|
|
15552
|
+
var import_fs, import_os, import_path, import_node_roon_api, import_node_roon_api_transport, STATE_FILE, readState, writeState, transport, zones, started, waiting, zonesIn, zoneWaiting, roon, core, open2, officialZones, isPaired;
|
|
15551
15553
|
var init_official = __esm({
|
|
15552
15554
|
"src/core/official.ts"() {
|
|
15553
15555
|
"use strict";
|
|
@@ -15578,7 +15580,11 @@ var init_official = __esm({
|
|
|
15578
15580
|
waiting = [];
|
|
15579
15581
|
zonesIn = false;
|
|
15580
15582
|
zoneWaiting = [];
|
|
15583
|
+
roon = null;
|
|
15584
|
+
core = null;
|
|
15585
|
+
open2 = false;
|
|
15581
15586
|
__name(startOfficial, "startOfficial");
|
|
15587
|
+
__name(connect, "connect");
|
|
15582
15588
|
__name(paired, "paired");
|
|
15583
15589
|
officialZones = /* @__PURE__ */ __name(() => [...zones.values()], "officialZones");
|
|
15584
15590
|
isPaired = /* @__PURE__ */ __name(() => !!transport, "isPaired");
|
|
@@ -15637,7 +15643,7 @@ function close(reason) {
|
|
|
15637
15643
|
log(`connection closed (${reason})`);
|
|
15638
15644
|
}
|
|
15639
15645
|
}
|
|
15640
|
-
async function
|
|
15646
|
+
async function open3() {
|
|
15641
15647
|
if (client) return client;
|
|
15642
15648
|
const t = await findTarget();
|
|
15643
15649
|
await paired();
|
|
@@ -15678,7 +15684,7 @@ function withCore(work2) {
|
|
|
15678
15684
|
idleTimer = null;
|
|
15679
15685
|
}
|
|
15680
15686
|
try {
|
|
15681
|
-
return await work2(await
|
|
15687
|
+
return await work2(await open3());
|
|
15682
15688
|
} catch (e) {
|
|
15683
15689
|
if (!(e instanceof RoonError && (e.kind === "not_found" || e.kind === "invalid"))) close("after an error");
|
|
15684
15690
|
throw e;
|
|
@@ -15692,7 +15698,7 @@ function withCore(work2) {
|
|
|
15692
15698
|
}
|
|
15693
15699
|
async function fresh() {
|
|
15694
15700
|
close("a fresh graph");
|
|
15695
|
-
return
|
|
15701
|
+
return open3();
|
|
15696
15702
|
}
|
|
15697
15703
|
function closeSoon(reason) {
|
|
15698
15704
|
close(reason);
|
|
@@ -15722,7 +15728,7 @@ var init_session = __esm({
|
|
|
15722
15728
|
__name(findTarget, "findTarget");
|
|
15723
15729
|
__name(locate, "locate");
|
|
15724
15730
|
__name(close, "close");
|
|
15725
|
-
__name(
|
|
15731
|
+
__name(open3, "open");
|
|
15726
15732
|
__name(withCore, "withCore");
|
|
15727
15733
|
__name(fresh, "fresh");
|
|
15728
15734
|
__name(closeSoon, "closeSoon");
|
|
@@ -48972,7 +48978,7 @@ function registerStatusTool(server, internal) {
|
|
|
48972
48978
|
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
48973
48979
|
}, () => run(async () => {
|
|
48974
48980
|
const t = await findTarget().catch((e) => ({ error: e.message }));
|
|
48975
|
-
const
|
|
48981
|
+
const core2 = "error" in t ? { error: t.error } : { name: t.info.name, version: t.info.version, address: t.host };
|
|
48976
48982
|
if (!("error" in t)) await paired(5e3).catch(() => null);
|
|
48977
48983
|
const paired2 = isPaired();
|
|
48978
48984
|
if (internal && paired2 && !selfTestResults()) await selfTest(runSelfTest);
|
|
@@ -48980,7 +48986,7 @@ function registerStatusTool(server, internal) {
|
|
|
48980
48986
|
return {
|
|
48981
48987
|
deep_roon_mcp: version2,
|
|
48982
48988
|
newer_version: updateInfo() ?? void 0,
|
|
48983
|
-
core,
|
|
48989
|
+
core: core2,
|
|
48984
48990
|
enabled_in_roon: paired2 || 'not yet: Roon \u203A Settings \u203A Extensions \u203A Enable next to "Deep Roon MCP"',
|
|
48985
48991
|
internal_protocol: internal ? checks2 ? checks2.every((c) => c.ok) ? "works" : "partly broken \u2014 see self_test" : "not tested yet" : "switched off (only the official API)",
|
|
48986
48992
|
self_test: checks2 ?? void 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deep-roon-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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>",
|