@iobroker/mcp-server 1.5.1 → 1.6.0
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 +11 -4
- package/build/devices.d.ts +1 -1
- package/build/devices.js +1 -1
- package/build/index.d.ts +25 -4
- package/build/index.js +142 -96
- package/build/index.js.map +1 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createInProcessMcp = exports.McpServer = void 0;
|
|
7
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
7
8
|
const node_crypto_1 = require("node:crypto");
|
|
8
9
|
const node_os_1 = __importDefault(require("node:os"));
|
|
9
10
|
const node_net_1 = __importDefault(require("node:net"));
|
|
@@ -173,8 +174,10 @@ class McpServer {
|
|
|
173
174
|
config;
|
|
174
175
|
extension;
|
|
175
176
|
routerPrefix;
|
|
176
|
-
/** The ioBroker user whose permissions
|
|
177
|
+
/** The ioBroker user whose permissions MCP requests run with when no user is logged in. */
|
|
177
178
|
defaultUser;
|
|
179
|
+
/** The user of the MCP request being processed, set by {@link runAs} for each HTTP request. */
|
|
180
|
+
requestUser = new node_async_hooks_1.AsyncLocalStorage();
|
|
178
181
|
/** Default language used to localize device/room/function names. */
|
|
179
182
|
language;
|
|
180
183
|
/**
|
|
@@ -227,11 +230,13 @@ class McpServer {
|
|
|
227
230
|
// (e.g. `/mcp/`) within the shared web server, and strip the trailing slash so the route
|
|
228
231
|
// matches both `/mcp` and `/mcp/` (Express, non-strict).
|
|
229
232
|
this.mcpPath = this.extension ? this.routerPrefix.replace(/\/$/, '') : '/mcp';
|
|
230
|
-
// Determine the ioBroker user whose permissions
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
const rawUser = this.
|
|
234
|
-
|
|
233
|
+
// Determine the ioBroker user whose permissions unauthenticated MCP requests run with.
|
|
234
|
+
// As a web extension the host `web` instance decides about authentication, so its default
|
|
235
|
+
// user wins there; otherwise prefer this adapter's own setting. Finally fall back to "admin".
|
|
236
|
+
const rawUser = (this.extension
|
|
237
|
+
? webSettings.defaultUser || this.config.defaultUser
|
|
238
|
+
: this.config.defaultUser || webSettings.defaultUser) || 'admin';
|
|
239
|
+
this.defaultUser = McpServer.toUserId(rawUser);
|
|
235
240
|
this.config.defaultUser = this.defaultUser;
|
|
236
241
|
this.language = webSettings.language || 'en';
|
|
237
242
|
// Embedded callers may set the permission toggles explicitly (they don't read the mcp config).
|
|
@@ -607,15 +612,57 @@ class McpServer {
|
|
|
607
612
|
pro: false,
|
|
608
613
|
};
|
|
609
614
|
}
|
|
615
|
+
/** Normalize a plain user name or a `system.user.*` id to the full id. */
|
|
616
|
+
static toUserId(user) {
|
|
617
|
+
return (user.startsWith('system.user.') ? user : `system.user.${user}`);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* The ioBroker user a request runs as: the logged-in user when the request was authenticated (by
|
|
621
|
+
* us or, as a web extension, by the host `web` instance), otherwise the default user.
|
|
622
|
+
*/
|
|
623
|
+
getRequestUser(req) {
|
|
624
|
+
const user = req.user;
|
|
625
|
+
return user ? McpServer.toUserId(user) : this.defaultUser;
|
|
626
|
+
}
|
|
627
|
+
/** The user the current tool call runs as. Outside a request (embedded mode) the default user. */
|
|
628
|
+
get user() {
|
|
629
|
+
return this.requestUser.getStore() || this.defaultUser;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Process a request of a session with the permissions of the session's user. The SDK dispatches the
|
|
633
|
+
* contained messages within this call, so every tool reached from here sees that user in {@link user}.
|
|
634
|
+
*/
|
|
635
|
+
runAs(user, handle) {
|
|
636
|
+
return this.requestUser.run(user, handle);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Get the session of a request. A session is bound to the user that opened it: requests of another
|
|
640
|
+
* user are answered like an unknown session, so that a leaked session id gives no access.
|
|
641
|
+
*/
|
|
642
|
+
getSession(req, res, sessionId) {
|
|
643
|
+
const session = this.sessions[sessionId];
|
|
644
|
+
if (session && session.user !== this.getRequestUser(req)) {
|
|
645
|
+
this.adapter.log.warn(`Rejected MCP request from ${req.ip}: session ${sessionId} belongs to another user than ${this.getRequestUser(req)}`);
|
|
646
|
+
this.sendSessionNotFound(res, sessionId);
|
|
647
|
+
return undefined;
|
|
648
|
+
}
|
|
649
|
+
if (!session) {
|
|
650
|
+
this.sendSessionNotFound(res, sessionId);
|
|
651
|
+
}
|
|
652
|
+
return session;
|
|
653
|
+
}
|
|
610
654
|
/** Handle client -> server messages (initialize, tools/call, ...). */
|
|
611
655
|
async handleMcpPost(req, res) {
|
|
612
656
|
const sessionId = req.headers['mcp-session-id'];
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
657
|
+
const user = this.getRequestUser(req);
|
|
658
|
+
let transport;
|
|
659
|
+
if (sessionId) {
|
|
660
|
+
transport = this.getSession(req, res, sessionId)?.transport;
|
|
661
|
+
if (!transport) {
|
|
617
662
|
return;
|
|
618
663
|
}
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
619
666
|
// No session yet: only an "initialize" request may create one.
|
|
620
667
|
if (!(0, types_js_1.isInitializeRequest)(req.body)) {
|
|
621
668
|
res.status(400).json({
|
|
@@ -630,8 +677,8 @@ class McpServer {
|
|
|
630
677
|
transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
631
678
|
sessionIdGenerator: () => (0, node_crypto_1.randomUUID)(),
|
|
632
679
|
onsessioninitialized: (sid) => {
|
|
633
|
-
this.sessions[sid] = { transport: transport, server, subscriptions };
|
|
634
|
-
this.adapter.log.debug(`MCP session initialized: ${sid}`);
|
|
680
|
+
this.sessions[sid] = { transport: transport, server, subscriptions, user };
|
|
681
|
+
this.adapter.log.debug(`MCP session initialized: ${sid} (user ${user})`);
|
|
635
682
|
},
|
|
636
683
|
});
|
|
637
684
|
transport.onclose = () => {
|
|
@@ -644,21 +691,20 @@ class McpServer {
|
|
|
644
691
|
};
|
|
645
692
|
await server.connect(transport);
|
|
646
693
|
}
|
|
647
|
-
|
|
694
|
+
const sessionTransport = transport;
|
|
695
|
+
await this.runAs(user, () => sessionTransport.handleRequest(req, res, req.body));
|
|
648
696
|
}
|
|
649
697
|
/** Handle server -> client SSE stream (GET) and session termination (DELETE). */
|
|
650
698
|
async handleMcpSessionRequest(req, res) {
|
|
651
699
|
const sessionId = req.headers['mcp-session-id'];
|
|
652
|
-
|
|
653
|
-
if (!transport) {
|
|
654
|
-
if (sessionId) {
|
|
655
|
-
this.sendSessionNotFound(res, sessionId);
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
700
|
+
if (!sessionId) {
|
|
658
701
|
res.status(400).send('Invalid or missing session ID');
|
|
659
702
|
return;
|
|
660
703
|
}
|
|
661
|
-
|
|
704
|
+
const session = this.getSession(req, res, sessionId);
|
|
705
|
+
if (session) {
|
|
706
|
+
await this.runAs(session.user, () => session.transport.handleRequest(req, res));
|
|
707
|
+
}
|
|
662
708
|
}
|
|
663
709
|
/**
|
|
664
710
|
* Answer a request of an unknown session, e.g. after a restart of the adapter or when the session was closed.
|
|
@@ -1259,7 +1305,7 @@ class McpServer {
|
|
|
1259
1305
|
inputSchema: { id: zod_1.z.string().describe('Object ID, e.g. system.adapter.admin.0 or hm-rpc.0.ABC') },
|
|
1260
1306
|
}, async ({ id }) => {
|
|
1261
1307
|
try {
|
|
1262
|
-
const object = await this.adapter.getForeignObjectAsync(id, { user: this.
|
|
1308
|
+
const object = await this.adapter.getForeignObjectAsync(id, { user: this.user });
|
|
1263
1309
|
return ok({ ok: true, data: { object: object ?? null } });
|
|
1264
1310
|
}
|
|
1265
1311
|
catch (e) {
|
|
@@ -1303,7 +1349,7 @@ class McpServer {
|
|
|
1303
1349
|
}
|
|
1304
1350
|
const { adapterName, fileName } = McpServer.parseFilePath(path);
|
|
1305
1351
|
const file = await this.adapter.readFileAsync(adapterName, fileName, {
|
|
1306
|
-
user: this.
|
|
1352
|
+
user: this.user,
|
|
1307
1353
|
});
|
|
1308
1354
|
const data = Buffer.isBuffer(file.file) ? file.file : Buffer.from(file.file || '');
|
|
1309
1355
|
const mimeType = file.mimeType || 'application/octet-stream';
|
|
@@ -1348,7 +1394,7 @@ class McpServer {
|
|
|
1348
1394
|
try {
|
|
1349
1395
|
const { adapterName, fileName } = McpServer.parseFilePath(path);
|
|
1350
1396
|
const exists = await this.adapter.fileExistsAsync(adapterName, fileName, {
|
|
1351
|
-
user: this.
|
|
1397
|
+
user: this.user,
|
|
1352
1398
|
});
|
|
1353
1399
|
return ok({ ok: true, data: { path, exists: !!exists } });
|
|
1354
1400
|
}
|
|
@@ -1560,7 +1606,7 @@ class McpServer {
|
|
|
1560
1606
|
const upload = this.getUpload(upload_id);
|
|
1561
1607
|
const { adapterName, fileName } = McpServer.parseFilePath(path);
|
|
1562
1608
|
await this.adapter.writeFileAsync(adapterName, fileName, node_fs_1.default.readFileSync(upload.file), {
|
|
1563
|
-
user: this.
|
|
1609
|
+
user: this.user,
|
|
1564
1610
|
});
|
|
1565
1611
|
this.removeTransfer(upload_id);
|
|
1566
1612
|
return ok({ ok: true, data: { path, size: upload.size } });
|
|
@@ -1585,7 +1631,7 @@ class McpServer {
|
|
|
1585
1631
|
}, async ({ path }) => {
|
|
1586
1632
|
try {
|
|
1587
1633
|
const { adapterName, fileName } = McpServer.parseFilePath(path);
|
|
1588
|
-
await this.adapter.delFileAsync(adapterName, fileName, { user: this.
|
|
1634
|
+
await this.adapter.delFileAsync(adapterName, fileName, { user: this.user });
|
|
1589
1635
|
return ok({ ok: true, data: { path, deleted: true } });
|
|
1590
1636
|
}
|
|
1591
1637
|
catch (e) {
|
|
@@ -1631,7 +1677,7 @@ class McpServer {
|
|
|
1631
1677
|
if (!dirName) {
|
|
1632
1678
|
throw new Error('Path must contain a directory after the adapter, e.g. "0_userdata.0/my-folder"');
|
|
1633
1679
|
}
|
|
1634
|
-
await this.adapter.mkdirAsync(adapterName, dirName, { user: this.
|
|
1680
|
+
await this.adapter.mkdirAsync(adapterName, dirName, { user: this.user });
|
|
1635
1681
|
return ok({ ok: true, data: { path } });
|
|
1636
1682
|
}
|
|
1637
1683
|
catch (e) {
|
|
@@ -2206,7 +2252,7 @@ class McpServer {
|
|
|
2206
2252
|
mimeType: 'application/json',
|
|
2207
2253
|
}, async (uri, variables) => {
|
|
2208
2254
|
const id = decodeURIComponent(String(variables.id));
|
|
2209
|
-
const state = await this.adapter.getForeignStateAsync(id, { user: this.
|
|
2255
|
+
const state = await this.adapter.getForeignStateAsync(id, { user: this.user });
|
|
2210
2256
|
const body = state
|
|
2211
2257
|
? { id, val: state.val, ack: state.ack, ts: state.ts, lc: state.lc, q: state.q }
|
|
2212
2258
|
: { id, val: null };
|
|
@@ -2218,7 +2264,7 @@ class McpServer {
|
|
|
2218
2264
|
mimeType: 'application/json',
|
|
2219
2265
|
}, async (uri, variables) => {
|
|
2220
2266
|
const id = decodeURIComponent(String(variables.id));
|
|
2221
|
-
const obj = await this.adapter.getForeignObjectAsync(id, { user: this.
|
|
2267
|
+
const obj = await this.adapter.getForeignObjectAsync(id, { user: this.user });
|
|
2222
2268
|
return {
|
|
2223
2269
|
contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(obj ?? null) }],
|
|
2224
2270
|
};
|
|
@@ -2278,13 +2324,13 @@ class McpServer {
|
|
|
2278
2324
|
try {
|
|
2279
2325
|
if (id.includes('*')) {
|
|
2280
2326
|
// Wildcard pattern: expand to all matching states.
|
|
2281
|
-
const matches = await this.adapter.getForeignStatesAsync(id, { user: this.
|
|
2327
|
+
const matches = await this.adapter.getForeignStatesAsync(id, { user: this.user });
|
|
2282
2328
|
for (const matchId of Object.keys(matches || {})) {
|
|
2283
2329
|
states.push(McpServer.stateEntry(matchId, matches[matchId]));
|
|
2284
2330
|
}
|
|
2285
2331
|
}
|
|
2286
2332
|
else {
|
|
2287
|
-
const state = await this.adapter.getForeignStateAsync(id, { user: this.
|
|
2333
|
+
const state = await this.adapter.getForeignStateAsync(id, { user: this.user });
|
|
2288
2334
|
states.push(McpServer.stateEntry(id, state));
|
|
2289
2335
|
}
|
|
2290
2336
|
}
|
|
@@ -2302,7 +2348,7 @@ class McpServer {
|
|
|
2302
2348
|
}
|
|
2303
2349
|
/**
|
|
2304
2350
|
* Write multiple states as one operation: the write permission of every state is verified first, so a
|
|
2305
|
-
* batch containing a single state the
|
|
2351
|
+
* batch containing a single state the user may not write fails as a whole and nothing is
|
|
2306
2352
|
* written. Other failures of single states are reported per item and do not abort the rest.
|
|
2307
2353
|
*/
|
|
2308
2354
|
async setStates(items) {
|
|
@@ -2320,7 +2366,7 @@ class McpServer {
|
|
|
2320
2366
|
return results;
|
|
2321
2367
|
}
|
|
2322
2368
|
/**
|
|
2323
|
-
* Verify that the
|
|
2369
|
+
* Verify that the user of the request may write every given state, mirroring the checks js-controller runs in
|
|
2324
2370
|
* `setForeignState`: the user must be able to read the object (needed for the type coercion in
|
|
2325
2371
|
* {@link setState}) and the state ACL must allow writing — for an alias also on its write target.
|
|
2326
2372
|
*
|
|
@@ -2328,10 +2374,10 @@ class McpServer {
|
|
|
2328
2374
|
* @throws {Error} when at least one state may not be written; the message lists all of them
|
|
2329
2375
|
*/
|
|
2330
2376
|
async assertStatesWritable(ids) {
|
|
2331
|
-
if (this.
|
|
2377
|
+
if (this.user === 'system.user.admin') {
|
|
2332
2378
|
return;
|
|
2333
2379
|
}
|
|
2334
|
-
const permissions = await this.adapter.calculatePermissionsAsync(this.
|
|
2380
|
+
const permissions = await this.adapter.calculatePermissionsAsync(this.user, []);
|
|
2335
2381
|
if (permissions.groups?.includes('system.group.administrator')) {
|
|
2336
2382
|
return;
|
|
2337
2383
|
}
|
|
@@ -2339,7 +2385,7 @@ class McpServer {
|
|
|
2339
2385
|
for (const id of new Set(ids)) {
|
|
2340
2386
|
let obj;
|
|
2341
2387
|
try {
|
|
2342
|
-
obj = await this.adapter.getForeignObjectAsync(id, { user: this.
|
|
2388
|
+
obj = await this.adapter.getForeignObjectAsync(id, { user: this.user });
|
|
2343
2389
|
}
|
|
2344
2390
|
catch {
|
|
2345
2391
|
denied.push(id);
|
|
@@ -2358,14 +2404,14 @@ class McpServer {
|
|
|
2358
2404
|
}
|
|
2359
2405
|
}
|
|
2360
2406
|
if (denied.length) {
|
|
2361
|
-
throw new Error(`permissionError: user "${this.
|
|
2407
|
+
throw new Error(`permissionError: user "${this.user}" may not write ${denied.join(', ')} - nothing was written`);
|
|
2362
2408
|
}
|
|
2363
2409
|
}
|
|
2364
2410
|
/**
|
|
2365
2411
|
* The state ACL check of js-controller for `setState` on a single object.
|
|
2366
2412
|
*
|
|
2367
2413
|
* @param obj the state object (objects without ACL are not restricted by js-controller)
|
|
2368
|
-
* @param permissions the merged group permissions of the
|
|
2414
|
+
* @param permissions the merged group permissions of the user
|
|
2369
2415
|
*/
|
|
2370
2416
|
isStateWritable(obj, permissions) {
|
|
2371
2417
|
const acl = obj?.acl;
|
|
@@ -2377,7 +2423,7 @@ class McpServer {
|
|
|
2377
2423
|
return false;
|
|
2378
2424
|
}
|
|
2379
2425
|
let mask = 0x002; // everyone write
|
|
2380
|
-
if (acl.owner === this.
|
|
2426
|
+
if (acl.owner === this.user) {
|
|
2381
2427
|
mask = 0x200; // owner write
|
|
2382
2428
|
}
|
|
2383
2429
|
else if (acl.ownerGroup && permissions.groups?.includes(acl.ownerGroup)) {
|
|
@@ -2663,13 +2709,13 @@ class McpServer {
|
|
|
2663
2709
|
}
|
|
2664
2710
|
async getSystemInfo() {
|
|
2665
2711
|
const hostObj = await this.adapter.getForeignObjectAsync(`system.host.${this.adapter.host}`, {
|
|
2666
|
-
user: this.
|
|
2712
|
+
user: this.user,
|
|
2667
2713
|
});
|
|
2668
2714
|
const jsControllerVersion = hostObj?.common?.installedVersion || 'unknown';
|
|
2669
2715
|
const totalMem = Math.round(node_os_1.default.totalmem() / (1024 * 1024));
|
|
2670
2716
|
const freeMem = Math.round(node_os_1.default.freemem() / (1024 * 1024));
|
|
2671
2717
|
const instanceObjs = await this.adapter.getForeignObjectsAsync('system.adapter.*', 'instance', {
|
|
2672
|
-
user: this.
|
|
2718
|
+
user: this.user,
|
|
2673
2719
|
});
|
|
2674
2720
|
return {
|
|
2675
2721
|
js_controller: jsControllerVersion,
|
|
@@ -2687,7 +2733,7 @@ class McpServer {
|
|
|
2687
2733
|
const result = await this.adapter.getObjectListAsync({
|
|
2688
2734
|
startkey: '',
|
|
2689
2735
|
endkey: '\u9999',
|
|
2690
|
-
}, { sorted: true, user: this.
|
|
2736
|
+
}, { sorted: true, user: this.user });
|
|
2691
2737
|
const allObjects = result.rows;
|
|
2692
2738
|
const roomMembers = room ? await this.getEnumMembers('enum.rooms.', room) : null;
|
|
2693
2739
|
const needle = query.toLowerCase();
|
|
@@ -2723,7 +2769,7 @@ class McpServer {
|
|
|
2723
2769
|
}
|
|
2724
2770
|
async listDevices(params) {
|
|
2725
2771
|
const lang = params.language || this.language;
|
|
2726
|
-
const rooms = await (0, devices_1.getAiFriendlyStructure)(this.adapter, lang, { user: this.
|
|
2772
|
+
const rooms = await (0, devices_1.getAiFriendlyStructure)(this.adapter, lang, { user: this.user });
|
|
2727
2773
|
if (params.room) {
|
|
2728
2774
|
const needle = params.room.toLowerCase();
|
|
2729
2775
|
return rooms.filter(r => r.roomName.toLowerCase() === needle);
|
|
@@ -2753,20 +2799,20 @@ class McpServer {
|
|
|
2753
2799
|
options.step = step;
|
|
2754
2800
|
}
|
|
2755
2801
|
// GetHistoryOptions has no typed `user` field, but the controller honors it for ACL checks.
|
|
2756
|
-
options.user = this.
|
|
2802
|
+
options.user = this.user;
|
|
2757
2803
|
const res = await this.adapter.getHistoryAsync(params.id, options);
|
|
2758
2804
|
return { id: params.id, values: res?.result || [] };
|
|
2759
2805
|
}
|
|
2760
2806
|
async listInstances() {
|
|
2761
2807
|
const objs = await this.adapter.getForeignObjectsAsync('system.adapter.*', 'instance', {
|
|
2762
|
-
user: this.
|
|
2808
|
+
user: this.user,
|
|
2763
2809
|
});
|
|
2764
2810
|
const result = [];
|
|
2765
2811
|
for (const [fullId, obj] of Object.entries(objs || {})) {
|
|
2766
2812
|
const id = fullId.replace('system.adapter.', '');
|
|
2767
|
-
const aliveState = await this.adapter.getForeignStateAsync(`${fullId}.alive`, { user: this.
|
|
2813
|
+
const aliveState = await this.adapter.getForeignStateAsync(`${fullId}.alive`, { user: this.user });
|
|
2768
2814
|
const connState = await this.adapter.getForeignStateAsync(`${fullId}.connected`, {
|
|
2769
|
-
user: this.
|
|
2815
|
+
user: this.user,
|
|
2770
2816
|
});
|
|
2771
2817
|
result.push({
|
|
2772
2818
|
id,
|
|
@@ -2782,7 +2828,7 @@ class McpServer {
|
|
|
2782
2828
|
/** List installed adapters (system.adapter.<name> objects, no instances). */
|
|
2783
2829
|
async listAdapters(language) {
|
|
2784
2830
|
const lang = language || this.language;
|
|
2785
|
-
const view = await this.adapter.getObjectViewAsync('system', 'adapter', { startkey: 'system.adapter.', endkey: 'system.adapter.香' }, { user: this.
|
|
2831
|
+
const view = await this.adapter.getObjectViewAsync('system', 'adapter', { startkey: 'system.adapter.', endkey: 'system.adapter.香' }, { user: this.user });
|
|
2786
2832
|
return view.rows.map(row => {
|
|
2787
2833
|
const common = (row.value?.common || {});
|
|
2788
2834
|
return {
|
|
@@ -2809,7 +2855,7 @@ class McpServer {
|
|
|
2809
2855
|
return [];
|
|
2810
2856
|
}
|
|
2811
2857
|
// Determine which adapters are already installed.
|
|
2812
|
-
const installedView = await this.adapter.getObjectViewAsync('system', 'adapter', { startkey: 'system.adapter.', endkey: 'system.adapter.香' }, { user: this.
|
|
2858
|
+
const installedView = await this.adapter.getObjectViewAsync('system', 'adapter', { startkey: 'system.adapter.', endkey: 'system.adapter.香' }, { user: this.user });
|
|
2813
2859
|
const installed = new Set(installedView.rows.map(row => row.value?.common?.name || row.id.replace('system.adapter.', '')));
|
|
2814
2860
|
const results = [];
|
|
2815
2861
|
for (const [name, entry] of Object.entries(json)) {
|
|
@@ -2847,10 +2893,10 @@ class McpServer {
|
|
|
2847
2893
|
return results;
|
|
2848
2894
|
}
|
|
2849
2895
|
async listHosts() {
|
|
2850
|
-
const objs = await this.adapter.getForeignObjectsAsync('system.host.*', 'host', { user: this.
|
|
2896
|
+
const objs = await this.adapter.getForeignObjectsAsync('system.host.*', 'host', { user: this.user });
|
|
2851
2897
|
const result = [];
|
|
2852
2898
|
for (const [fullId, obj] of Object.entries(objs || {})) {
|
|
2853
|
-
const aliveState = await this.adapter.getForeignStateAsync(`${fullId}.alive`, { user: this.
|
|
2899
|
+
const aliveState = await this.adapter.getForeignStateAsync(`${fullId}.alive`, { user: this.user });
|
|
2854
2900
|
result.push({
|
|
2855
2901
|
id: fullId.replace('system.host.', ''),
|
|
2856
2902
|
alive: !!aliveState?.val,
|
|
@@ -2865,7 +2911,7 @@ class McpServer {
|
|
|
2865
2911
|
* (ported from the ioBroker n8n node's `readIobEnums`).
|
|
2866
2912
|
*/
|
|
2867
2913
|
async readEnums(type, language, withIcons) {
|
|
2868
|
-
const enums = await this.adapter.getObjectViewAsync('system', 'enum', { startkey: `enum.${type}.`, endkey: `enum.${type}.香` }, { user: this.
|
|
2914
|
+
const enums = await this.adapter.getObjectViewAsync('system', 'enum', { startkey: `enum.${type}.`, endkey: `enum.${type}.香` }, { user: this.user });
|
|
2869
2915
|
const result = [];
|
|
2870
2916
|
// Cache member objects so a member shared by several enums is read only once.
|
|
2871
2917
|
const cache = {};
|
|
@@ -2885,7 +2931,7 @@ class McpServer {
|
|
|
2885
2931
|
if (obj === undefined) {
|
|
2886
2932
|
try {
|
|
2887
2933
|
cache[member] =
|
|
2888
|
-
(await this.adapter.getForeignObjectAsync(member, { user: this.
|
|
2934
|
+
(await this.adapter.getForeignObjectAsync(member, { user: this.user })) || false;
|
|
2889
2935
|
}
|
|
2890
2936
|
catch {
|
|
2891
2937
|
cache[member] = false;
|
|
@@ -2914,10 +2960,10 @@ class McpServer {
|
|
|
2914
2960
|
}
|
|
2915
2961
|
/** Write a state, coercing the value to the state's declared type (boolean/number/string). */
|
|
2916
2962
|
async setState(id, value, ack) {
|
|
2917
|
-
const obj = await this.adapter.getForeignObjectAsync(id, { user: this.
|
|
2963
|
+
const obj = await this.adapter.getForeignObjectAsync(id, { user: this.user });
|
|
2918
2964
|
const stateType = obj?.common?.type;
|
|
2919
2965
|
const coerced = McpServer.coerceValue(value, stateType);
|
|
2920
|
-
await this.adapter.setForeignStateAsync(id, coerced, ack, { user: this.
|
|
2966
|
+
await this.adapter.setForeignStateAsync(id, coerced, ack, { user: this.user });
|
|
2921
2967
|
return coerced;
|
|
2922
2968
|
}
|
|
2923
2969
|
/** Coerce an arbitrary value to the given ioBroker state type. */
|
|
@@ -2971,7 +3017,7 @@ class McpServer {
|
|
|
2971
3017
|
*/
|
|
2972
3018
|
async readFile(path, base64, offset = 0, length = DEFAULT_MAX_READ_BYTES) {
|
|
2973
3019
|
const { adapterName, fileName } = McpServer.parseFilePath(path);
|
|
2974
|
-
const data = await this.adapter.readFileAsync(adapterName, fileName, { user: this.
|
|
3020
|
+
const data = await this.adapter.readFileAsync(adapterName, fileName, { user: this.user });
|
|
2975
3021
|
const file = data?.file;
|
|
2976
3022
|
const mimeType = data?.mimeType;
|
|
2977
3023
|
if (file === undefined || file === null) {
|
|
@@ -3017,12 +3063,12 @@ class McpServer {
|
|
|
3017
3063
|
async writeFile(path, content, base64) {
|
|
3018
3064
|
const { adapterName, fileName } = McpServer.parseFilePath(path);
|
|
3019
3065
|
const data = base64 ? Buffer.from(content, 'base64') : content;
|
|
3020
|
-
await this.adapter.writeFileAsync(adapterName, fileName, data, { user: this.
|
|
3066
|
+
await this.adapter.writeFileAsync(adapterName, fileName, data, { user: this.user });
|
|
3021
3067
|
}
|
|
3022
3068
|
/** List a directory in an adapter file storage. */
|
|
3023
3069
|
async listFiles(path) {
|
|
3024
3070
|
const { adapterName, dirName } = McpServer.parseDirPath(path);
|
|
3025
|
-
const entries = await this.adapter.readDirAsync(adapterName, dirName, { user: this.
|
|
3071
|
+
const entries = await this.adapter.readDirAsync(adapterName, dirName, { user: this.user });
|
|
3026
3072
|
return (entries || []).map(entry => ({
|
|
3027
3073
|
file: entry.file,
|
|
3028
3074
|
isDir: !!entry.isDir,
|
|
@@ -3037,11 +3083,11 @@ class McpServer {
|
|
|
3037
3083
|
if (from.adapterName !== to.adapterName) {
|
|
3038
3084
|
throw new Error('Renaming across adapter storages is not supported; both paths must share the adapter');
|
|
3039
3085
|
}
|
|
3040
|
-
await this.adapter.renameAsync(from.adapterName, from.fileName, to.fileName, { user: this.
|
|
3086
|
+
await this.adapter.renameAsync(from.adapterName, from.fileName, to.fileName, { user: this.user });
|
|
3041
3087
|
}
|
|
3042
3088
|
/** Create or update an object, merging common/native into an existing object (n8n `setIobObject`). */
|
|
3043
3089
|
async setObject(id, obj) {
|
|
3044
|
-
let existing = await this.adapter.getForeignObjectAsync(id, { user: this.
|
|
3090
|
+
let existing = await this.adapter.getForeignObjectAsync(id, { user: this.user });
|
|
3045
3091
|
if (existing) {
|
|
3046
3092
|
if (obj.common) {
|
|
3047
3093
|
existing.common = { ...existing.common, ...obj.common };
|
|
@@ -3053,7 +3099,7 @@ class McpServer {
|
|
|
3053
3099
|
else {
|
|
3054
3100
|
existing = obj;
|
|
3055
3101
|
}
|
|
3056
|
-
return this.adapter.setForeignObjectAsync(id, existing, { user: this.
|
|
3102
|
+
return this.adapter.setForeignObjectAsync(id, existing, { user: this.user });
|
|
3057
3103
|
}
|
|
3058
3104
|
/** Split a file path "<adapter>/<dir>/<file>" into adapter name and file name. */
|
|
3059
3105
|
static parseFilePath(path) {
|
|
@@ -3077,15 +3123,15 @@ class McpServer {
|
|
|
3077
3123
|
}
|
|
3078
3124
|
/** Delete an object, optionally with all its children. */
|
|
3079
3125
|
async deleteObject(id, recursive) {
|
|
3080
|
-
const existing = await this.adapter.getForeignObjectAsync(id, { user: this.
|
|
3126
|
+
const existing = await this.adapter.getForeignObjectAsync(id, { user: this.user });
|
|
3081
3127
|
if (!existing) {
|
|
3082
3128
|
throw new Error(`Object "${id}" does not exist`);
|
|
3083
3129
|
}
|
|
3084
|
-
await this.adapter.delForeignObjectAsync(id, { user: this.
|
|
3130
|
+
await this.adapter.delForeignObjectAsync(id, { user: this.user, recursive: !!recursive });
|
|
3085
3131
|
}
|
|
3086
3132
|
/** Create a new state object; refuses to overwrite an existing object. */
|
|
3087
3133
|
async createState(params) {
|
|
3088
|
-
const existing = await this.adapter.getForeignObjectAsync(params.id, { user: this.
|
|
3134
|
+
const existing = await this.adapter.getForeignObjectAsync(params.id, { user: this.user });
|
|
3089
3135
|
if (existing) {
|
|
3090
3136
|
throw new Error(`Object "${params.id}" already exists - use set_object to modify it`);
|
|
3091
3137
|
}
|
|
@@ -3111,10 +3157,10 @@ class McpServer {
|
|
|
3111
3157
|
if (params.desc !== undefined) {
|
|
3112
3158
|
common.desc = params.desc;
|
|
3113
3159
|
}
|
|
3114
|
-
await this.adapter.setForeignObjectAsync(params.id, { type: 'state', common, native: {} }, { user: this.
|
|
3160
|
+
await this.adapter.setForeignObjectAsync(params.id, { type: 'state', common, native: {} }, { user: this.user });
|
|
3115
3161
|
if (params.def !== undefined) {
|
|
3116
3162
|
const coerced = McpServer.coerceValue(params.def, common.type);
|
|
3117
|
-
await this.adapter.setForeignStateAsync(params.id, coerced, true, { user: this.
|
|
3163
|
+
await this.adapter.setForeignStateAsync(params.id, coerced, true, { user: this.user });
|
|
3118
3164
|
}
|
|
3119
3165
|
return { id: params.id };
|
|
3120
3166
|
}
|
|
@@ -3122,7 +3168,7 @@ class McpServer {
|
|
|
3122
3168
|
async createScene(params) {
|
|
3123
3169
|
const instance = params.instance || 'scene.0';
|
|
3124
3170
|
const instanceObj = await this.adapter.getForeignObjectAsync(`system.adapter.${instance}`, {
|
|
3125
|
-
user: this.
|
|
3171
|
+
user: this.user,
|
|
3126
3172
|
});
|
|
3127
3173
|
if (!instanceObj) {
|
|
3128
3174
|
throw new Error(`Scene adapter instance "${instance}" is not installed. Install the ioBroker "scenes" adapter first.`);
|
|
@@ -3157,7 +3203,7 @@ class McpServer {
|
|
|
3157
3203
|
burstInterval: 0,
|
|
3158
3204
|
},
|
|
3159
3205
|
};
|
|
3160
|
-
await this.adapter.setForeignObjectAsync(id, sceneObj, { user: this.
|
|
3206
|
+
await this.adapter.setForeignObjectAsync(id, sceneObj, { user: this.user });
|
|
3161
3207
|
return { id, members: params.members.length };
|
|
3162
3208
|
}
|
|
3163
3209
|
/**
|
|
@@ -3195,7 +3241,7 @@ class McpServer {
|
|
|
3195
3241
|
let data;
|
|
3196
3242
|
if (params.file_path !== undefined) {
|
|
3197
3243
|
const { adapterName, fileName: storageName } = McpServer.parseFilePath(params.file_path);
|
|
3198
|
-
const result = await this.adapter.readFileAsync(adapterName, storageName, { user: this.
|
|
3244
|
+
const result = await this.adapter.readFileAsync(adapterName, storageName, { user: this.user });
|
|
3199
3245
|
data = Buffer.isBuffer(result.file) ? result.file : Buffer.from(result.file || '');
|
|
3200
3246
|
fileName = storageName.split('/').pop() || '';
|
|
3201
3247
|
}
|
|
@@ -3239,7 +3285,7 @@ class McpServer {
|
|
|
3239
3285
|
if (!name || /\s/.test(name)) {
|
|
3240
3286
|
throw new Error(`Invalid host "${name}"`);
|
|
3241
3287
|
}
|
|
3242
|
-
const hostObj = await this.adapter.getForeignObjectAsync(`system.host.${name}`, { user: this.
|
|
3288
|
+
const hostObj = await this.adapter.getForeignObjectAsync(`system.host.${name}`, { user: this.user });
|
|
3243
3289
|
if (hostObj?.type !== 'host') {
|
|
3244
3290
|
throw new Error(`Host "${name}" not found`);
|
|
3245
3291
|
}
|
|
@@ -3303,12 +3349,12 @@ class McpServer {
|
|
|
3303
3349
|
/** Like admin after an installation: touching the instance objects makes the controller restart them. */
|
|
3304
3350
|
async restartAdapterInstances(adapterName) {
|
|
3305
3351
|
const instances = await this.adapter.getForeignObjectsAsync(`system.adapter.${adapterName}.*`, 'instance', {
|
|
3306
|
-
user: this.
|
|
3352
|
+
user: this.user,
|
|
3307
3353
|
});
|
|
3308
3354
|
const restarted = [];
|
|
3309
3355
|
for (const [instanceId, instance] of Object.entries(instances || {})) {
|
|
3310
3356
|
if (instance?.common?.enabled) {
|
|
3311
|
-
await this.adapter.extendForeignObjectAsync(instanceId, {}, { user: this.
|
|
3357
|
+
await this.adapter.extendForeignObjectAsync(instanceId, {}, { user: this.user });
|
|
3312
3358
|
restarted.push(instanceId.replace('system.adapter.', ''));
|
|
3313
3359
|
}
|
|
3314
3360
|
}
|
|
@@ -3321,7 +3367,7 @@ class McpServer {
|
|
|
3321
3367
|
throw new Error(`Invalid instance "${id}", expected e.g. "hue.0"`);
|
|
3322
3368
|
}
|
|
3323
3369
|
const fullId = `system.adapter.${shortId}`;
|
|
3324
|
-
const obj = await this.adapter.getForeignObjectAsync(fullId, { user: this.
|
|
3370
|
+
const obj = await this.adapter.getForeignObjectAsync(fullId, { user: this.user });
|
|
3325
3371
|
if (obj?.type !== 'instance') {
|
|
3326
3372
|
throw new Error(`Instance "${shortId}" not found`);
|
|
3327
3373
|
}
|
|
@@ -3332,7 +3378,7 @@ class McpServer {
|
|
|
3332
3378
|
const instance = await this.getInstance(id);
|
|
3333
3379
|
const changed = !!instance.obj.common.enabled !== enabled;
|
|
3334
3380
|
if (changed) {
|
|
3335
|
-
await this.adapter.extendForeignObjectAsync(instance.fullId, { common: { enabled } }, { user: this.
|
|
3381
|
+
await this.adapter.extendForeignObjectAsync(instance.fullId, { common: { enabled } }, { user: this.user });
|
|
3336
3382
|
}
|
|
3337
3383
|
return { id: instance.id, enabled, changed };
|
|
3338
3384
|
}
|
|
@@ -3342,7 +3388,7 @@ class McpServer {
|
|
|
3342
3388
|
if (!instance.obj.common.enabled) {
|
|
3343
3389
|
throw new Error(`Instance "${instance.id}" is not enabled - start it instead`);
|
|
3344
3390
|
}
|
|
3345
|
-
await this.adapter.extendForeignObjectAsync(instance.fullId, {}, { user: this.
|
|
3391
|
+
await this.adapter.extendForeignObjectAsync(instance.fullId, {}, { user: this.user });
|
|
3346
3392
|
return { id: instance.id, restarted: true };
|
|
3347
3393
|
}
|
|
3348
3394
|
/**
|
|
@@ -3352,11 +3398,11 @@ class McpServer {
|
|
|
3352
3398
|
async setLogLevel(id, level, persistent) {
|
|
3353
3399
|
const instance = await this.getInstance(id);
|
|
3354
3400
|
if (persistent) {
|
|
3355
|
-
await this.adapter.extendForeignObjectAsync(instance.fullId, { common: { loglevel: level } }, { user: this.
|
|
3401
|
+
await this.adapter.extendForeignObjectAsync(instance.fullId, { common: { loglevel: level } }, { user: this.user });
|
|
3356
3402
|
}
|
|
3357
3403
|
else {
|
|
3358
3404
|
await this.adapter.setForeignStateAsync(`${instance.fullId}.logLevel`, level, false, {
|
|
3359
|
-
user: this.
|
|
3405
|
+
user: this.user,
|
|
3360
3406
|
});
|
|
3361
3407
|
}
|
|
3362
3408
|
return { id: instance.id, level, persistent: !!persistent };
|
|
@@ -3377,7 +3423,7 @@ class McpServer {
|
|
|
3377
3423
|
const adapterName = McpServer.checkAdapterName(params.adapter);
|
|
3378
3424
|
const host = await this.resolveHost(params.host);
|
|
3379
3425
|
if (params.instance !== undefined) {
|
|
3380
|
-
const existing = await this.adapter.getForeignObjectAsync(`system.adapter.${adapterName}.${params.instance}`, { user: this.
|
|
3426
|
+
const existing = await this.adapter.getForeignObjectAsync(`system.adapter.${adapterName}.${params.instance}`, { user: this.user });
|
|
3381
3427
|
if (existing) {
|
|
3382
3428
|
throw new Error(`Instance "${adapterName}.${params.instance}" already exists`);
|
|
3383
3429
|
}
|
|
@@ -3397,7 +3443,7 @@ class McpServer {
|
|
|
3397
3443
|
throw new Error(`Invalid version "${params.version}"`);
|
|
3398
3444
|
}
|
|
3399
3445
|
const adapterObj = await this.adapter.getForeignObjectAsync(`system.adapter.${adapterName}`, {
|
|
3400
|
-
user: this.
|
|
3446
|
+
user: this.user,
|
|
3401
3447
|
});
|
|
3402
3448
|
if (adapterObj?.type !== 'adapter') {
|
|
3403
3449
|
throw new Error(`Adapter "${adapterName}" is not installed - use add_instance to install it`);
|
|
@@ -3428,9 +3474,9 @@ class McpServer {
|
|
|
3428
3474
|
* matters when the user has custom repos active next to the default one.
|
|
3429
3475
|
*/
|
|
3430
3476
|
async getRepositoryJson() {
|
|
3431
|
-
const reposObj = await this.adapter.getForeignObjectAsync('system.repositories', { user: this.
|
|
3477
|
+
const reposObj = await this.adapter.getForeignObjectAsync('system.repositories', { user: this.user });
|
|
3432
3478
|
const repositories = (reposObj?.native?.repositories || {});
|
|
3433
|
-
const sysCfg = await this.adapter.getForeignObjectAsync('system.config', { user: this.
|
|
3479
|
+
const sysCfg = await this.adapter.getForeignObjectAsync('system.config', { user: this.user });
|
|
3434
3480
|
const active = sysCfg?.common?.activeRepo;
|
|
3435
3481
|
const activeNames = Array.isArray(active) ? active : active ? [active] : [];
|
|
3436
3482
|
const activeWithJson = activeNames.filter(name => repositories[name]?.json);
|
|
@@ -3523,7 +3569,7 @@ class McpServer {
|
|
|
3523
3569
|
/** Read a script object, failing if it is missing or not a script. */
|
|
3524
3570
|
async getScriptObject(id) {
|
|
3525
3571
|
const scriptId = McpServer.scriptId(id);
|
|
3526
|
-
const obj = await this.adapter.getForeignObjectAsync(scriptId, { user: this.
|
|
3572
|
+
const obj = await this.adapter.getForeignObjectAsync(scriptId, { user: this.user });
|
|
3527
3573
|
if (obj?.type !== 'script') {
|
|
3528
3574
|
throw new Error(`Script "${scriptId}" not found`);
|
|
3529
3575
|
}
|
|
@@ -3532,7 +3578,7 @@ class McpServer {
|
|
|
3532
3578
|
/** List the scripts of the javascript adapter (without their source). */
|
|
3533
3579
|
async listScripts(folder) {
|
|
3534
3580
|
const prefix = folder ? `script.js.${folder.replace(/^script\.js\./, '').replace(/\.$/, '')}.` : 'script.js.';
|
|
3535
|
-
const objs = await this.adapter.getForeignObjectsAsync(`${prefix}*`, 'script', { user: this.
|
|
3581
|
+
const objs = await this.adapter.getForeignObjectsAsync(`${prefix}*`, 'script', { user: this.user });
|
|
3536
3582
|
return Object.entries(objs || {}).map(([id, obj]) => this.scriptInfo(id, obj));
|
|
3537
3583
|
}
|
|
3538
3584
|
/**
|
|
@@ -3541,7 +3587,7 @@ class McpServer {
|
|
|
3541
3587
|
*/
|
|
3542
3588
|
async setScript(params) {
|
|
3543
3589
|
const scriptId = McpServer.scriptId(params.id);
|
|
3544
|
-
const existing = await this.adapter.getForeignObjectAsync(scriptId, { user: this.
|
|
3590
|
+
const existing = await this.adapter.getForeignObjectAsync(scriptId, { user: this.user });
|
|
3545
3591
|
if (existing && existing.type !== 'script') {
|
|
3546
3592
|
throw new Error(`"${scriptId}" exists and is not a script`);
|
|
3547
3593
|
}
|
|
@@ -3557,7 +3603,7 @@ class McpServer {
|
|
|
3557
3603
|
if (params.enabled !== undefined) {
|
|
3558
3604
|
common.enabled = params.enabled;
|
|
3559
3605
|
}
|
|
3560
|
-
await this.adapter.extendForeignObjectAsync(scriptId, { common }, { user: this.
|
|
3606
|
+
await this.adapter.extendForeignObjectAsync(scriptId, { common }, { user: this.user });
|
|
3561
3607
|
return { id: scriptId, created: false };
|
|
3562
3608
|
}
|
|
3563
3609
|
const engine = (await this.getInstance(params.instance || 'javascript.0')).id;
|
|
@@ -3565,8 +3611,8 @@ class McpServer {
|
|
|
3565
3611
|
const segments = scriptId.split('.');
|
|
3566
3612
|
for (let i = 3; i < segments.length; i++) {
|
|
3567
3613
|
const folderId = segments.slice(0, i).join('.');
|
|
3568
|
-
if (!(await this.adapter.getForeignObjectAsync(folderId, { user: this.
|
|
3569
|
-
await this.adapter.setForeignObjectAsync(folderId, { type: 'channel', common: { name: segments[i - 1] }, native: {} }, { user: this.
|
|
3614
|
+
if (!(await this.adapter.getForeignObjectAsync(folderId, { user: this.user }))) {
|
|
3615
|
+
await this.adapter.setForeignObjectAsync(folderId, { type: 'channel', common: { name: segments[i - 1] }, native: {} }, { user: this.user });
|
|
3570
3616
|
}
|
|
3571
3617
|
}
|
|
3572
3618
|
await this.adapter.setForeignObjectAsync(scriptId, {
|
|
@@ -3582,19 +3628,19 @@ class McpServer {
|
|
|
3582
3628
|
verbose: false,
|
|
3583
3629
|
},
|
|
3584
3630
|
native: {},
|
|
3585
|
-
}, { user: this.
|
|
3631
|
+
}, { user: this.user });
|
|
3586
3632
|
return { id: scriptId, created: true };
|
|
3587
3633
|
}
|
|
3588
3634
|
/** Enable (start) or disable (stop) a script. */
|
|
3589
3635
|
async enableScript(id, enabled) {
|
|
3590
3636
|
const script = await this.getScriptObject(id);
|
|
3591
|
-
await this.adapter.extendForeignObjectAsync(script.id, { common: { enabled } }, { user: this.
|
|
3637
|
+
await this.adapter.extendForeignObjectAsync(script.id, { common: { enabled } }, { user: this.user });
|
|
3592
3638
|
return { id: script.id, enabled };
|
|
3593
3639
|
}
|
|
3594
3640
|
/** Delete a script. */
|
|
3595
3641
|
async deleteScript(id) {
|
|
3596
3642
|
const script = await this.getScriptObject(id);
|
|
3597
|
-
await this.adapter.delForeignObjectAsync(script.id, { user: this.
|
|
3643
|
+
await this.adapter.delForeignObjectAsync(script.id, { user: this.user });
|
|
3598
3644
|
return { id: script.id, deleted: true };
|
|
3599
3645
|
}
|
|
3600
3646
|
/**
|
|
@@ -3604,7 +3650,7 @@ class McpServer {
|
|
|
3604
3650
|
async runScript(params) {
|
|
3605
3651
|
const instance = await this.getInstance(params.instance || 'javascript.0');
|
|
3606
3652
|
const timeout = params.timeout ?? 5000;
|
|
3607
|
-
const result = await this.adapter.sendToAsync(instance.id, 'execute', { source: params.source, engineType: params.engineType, timeout, verbose: false, logLevel: 'info' }, { timeout: timeout + 15_000, user: this.
|
|
3653
|
+
const result = await this.adapter.sendToAsync(instance.id, 'execute', { source: params.source, engineType: params.engineType, timeout, verbose: false, logLevel: 'info' }, { timeout: timeout + 15_000, user: this.user });
|
|
3608
3654
|
if (!result || typeof result !== 'object') {
|
|
3609
3655
|
throw new Error(`"${instance.id}" did not answer - javascript adapter 10.1.0 or newer is required`);
|
|
3610
3656
|
}
|
|
@@ -3624,7 +3670,7 @@ class McpServer {
|
|
|
3624
3670
|
if (!this.isSendToAllowed(instance.id)) {
|
|
3625
3671
|
throw new Error(`"${instance.id}" is not in the allowlist of send_to`);
|
|
3626
3672
|
}
|
|
3627
|
-
const response = await this.adapter.sendToAsync(instance.id, params.command, (params.message ?? {}), { timeout: params.timeout ?? 10_000, user: this.
|
|
3673
|
+
const response = await this.adapter.sendToAsync(instance.id, params.command, (params.message ?? {}), { timeout: params.timeout ?? 10_000, user: this.user });
|
|
3628
3674
|
return { instance: instance.id, command: params.command, response: response ?? null };
|
|
3629
3675
|
}
|
|
3630
3676
|
/**
|
|
@@ -3636,16 +3682,16 @@ class McpServer {
|
|
|
3636
3682
|
if (!/^alias\.\d+\.[^*\s]+$/.test(id)) {
|
|
3637
3683
|
throw new Error(`Invalid alias id "${params.id}"`);
|
|
3638
3684
|
}
|
|
3639
|
-
if (await this.adapter.getForeignObjectAsync(id, { user: this.
|
|
3685
|
+
if (await this.adapter.getForeignObjectAsync(id, { user: this.user })) {
|
|
3640
3686
|
throw new Error(`Object "${id}" already exists`);
|
|
3641
3687
|
}
|
|
3642
|
-
const target = await this.adapter.getForeignObjectAsync(params.target, { user: this.
|
|
3688
|
+
const target = await this.adapter.getForeignObjectAsync(params.target, { user: this.user });
|
|
3643
3689
|
if (target?.type !== 'state') {
|
|
3644
3690
|
throw new Error(`Target "${params.target}" is not a state`);
|
|
3645
3691
|
}
|
|
3646
3692
|
if (params.write_target) {
|
|
3647
3693
|
const writeTarget = await this.adapter.getForeignObjectAsync(params.write_target, {
|
|
3648
|
-
user: this.
|
|
3694
|
+
user: this.user,
|
|
3649
3695
|
});
|
|
3650
3696
|
if (writeTarget?.type !== 'state') {
|
|
3651
3697
|
throw new Error(`Write target "${params.write_target}" is not a state`);
|
|
@@ -3670,7 +3716,7 @@ class McpServer {
|
|
|
3670
3716
|
if (params.unit !== undefined || source.unit !== undefined) {
|
|
3671
3717
|
common.unit = params.unit ?? source.unit;
|
|
3672
3718
|
}
|
|
3673
|
-
await this.adapter.setForeignObjectAsync(id, { type: 'state', common, native: {} }, { user: this.
|
|
3719
|
+
await this.adapter.setForeignObjectAsync(id, { type: 'state', common, native: {} }, { user: this.user });
|
|
3674
3720
|
return { id, target: params.target, ...(params.write_target ? { write_target: params.write_target } : {}) };
|
|
3675
3721
|
}
|
|
3676
3722
|
/**
|
|
@@ -3838,7 +3884,7 @@ class McpServer {
|
|
|
3838
3884
|
// --- helpers ---
|
|
3839
3885
|
/** Resolve the member ids of a room/function enum matched by id or (localized) name. */
|
|
3840
3886
|
async getEnumMembers(prefix, nameOrId) {
|
|
3841
|
-
const enums = await this.adapter.getForeignObjectsAsync(`${prefix}*`, 'enum', { user: this.
|
|
3887
|
+
const enums = await this.adapter.getForeignObjectsAsync(`${prefix}*`, 'enum', { user: this.user });
|
|
3842
3888
|
const needle = nameOrId.toLowerCase();
|
|
3843
3889
|
for (const [id, obj] of Object.entries(enums || {})) {
|
|
3844
3890
|
if (id.toLowerCase() === needle || this.getName(obj?.common?.name).toLowerCase() === needle) {
|