@thinremote/thinr-cli 1.3.2 → 1.3.3
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/CHANGELOG.md +13 -0
- package/lib/mcp/tools-alarms.js +4 -7
- package/lib/mcp/tools-buckets.js +5 -9
- package/lib/mcp/tools-devices.js +10 -10
- package/lib/mcp/tools-exec.js +19 -12
- package/lib/mcp/tools-filesystem.js +24 -24
- package/lib/mcp/tools-install.js +5 -6
- package/lib/mcp/tools-playbook.js +5 -6
- package/lib/mcp/tools-products.js +16 -24
- package/lib/mcp/tools-profile.js +2 -1
- package/lib/mcp/tools-properties.js +6 -6
- package/lib/mcp/tools-resources.js +6 -6
- package/lib/mcp/tools-scripts.js +9 -9
- package/lib/mcp/tools-tokens.js +17 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.3] - 2026-09-24
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- MCP tools take the target device as `device_id`. A tool argument named
|
|
8
|
+
`device` never reaches this server through Claude Desktop's remote-devices
|
|
9
|
+
bridge, which claims that name for its own call routing and strips it, so
|
|
10
|
+
every tool needing a device failed with `device is required` however the
|
|
11
|
+
caller spelled the call, while tools without one worked. Reproduced on one
|
|
12
|
+
machine with one binary: a direct JSON-RPC call succeeded, the same call
|
|
13
|
+
through Claude Desktop arrived without the field, and renaming the argument
|
|
14
|
+
fixed it with nothing else changed. `device` is no longer accepted.
|
|
15
|
+
|
|
3
16
|
## [1.3.2] - 2026-09-24
|
|
4
17
|
|
|
5
18
|
### Fixed
|
package/lib/mcp/tools-alarms.js
CHANGED
|
@@ -43,9 +43,7 @@ function normalizeSeverityInput(value) {
|
|
|
43
43
|
for (const v of arr) {
|
|
44
44
|
const parsed = parseAlarmSeverity(v);
|
|
45
45
|
if (parsed === undefined) {
|
|
46
|
-
throw inputError(
|
|
47
|
-
`Unknown alarm severity "${v}". Valid: low, medium, high, critical.`,
|
|
48
|
-
);
|
|
46
|
+
throw inputError(`Unknown alarm severity "${v}". Valid: low, medium, high, critical.`);
|
|
49
47
|
}
|
|
50
48
|
out.push(parsed);
|
|
51
49
|
}
|
|
@@ -195,7 +193,8 @@ async function toolAlarmInstanceUpdate(args) {
|
|
|
195
193
|
content: [
|
|
196
194
|
{
|
|
197
195
|
type: 'text',
|
|
198
|
-
text:
|
|
196
|
+
text:
|
|
197
|
+
`Updated ${args.instance} → state=${stateLabel}` +
|
|
199
198
|
(updated?.annotation ? `, annotation="${updated.annotation}"` : ''),
|
|
200
199
|
},
|
|
201
200
|
],
|
|
@@ -228,9 +227,7 @@ async function toolAlarmRules(args) {
|
|
|
228
227
|
return `${enabled} ${id}${name}${desc}`;
|
|
229
228
|
});
|
|
230
229
|
return {
|
|
231
|
-
content: [
|
|
232
|
-
{ type: 'text', text: `${rules.length} alarm rule(s):\n${lines.join('\n')}` },
|
|
233
|
-
],
|
|
230
|
+
content: [{ type: 'text', text: `${rules.length} alarm rule(s):\n${lines.join('\n')}` }],
|
|
234
231
|
isError: false,
|
|
235
232
|
};
|
|
236
233
|
}
|
package/lib/mcp/tools-buckets.js
CHANGED
|
@@ -9,7 +9,7 @@ async function toolBucketRead(args) {
|
|
|
9
9
|
);
|
|
10
10
|
const data = await getBucketData({
|
|
11
11
|
bucket: args.bucket,
|
|
12
|
-
device: args.
|
|
12
|
+
device: args.device_id,
|
|
13
13
|
user: args.user,
|
|
14
14
|
items: args.items || 10,
|
|
15
15
|
sort: args.sort || 'desc',
|
|
@@ -22,9 +22,7 @@ async function toolBucketRead(args) {
|
|
|
22
22
|
group_by: args.group_by,
|
|
23
23
|
});
|
|
24
24
|
const text =
|
|
25
|
-
Array.isArray(data) && data.length === 0
|
|
26
|
-
? 'No data'
|
|
27
|
-
: JSON.stringify(data, null, 2);
|
|
25
|
+
Array.isArray(data) && data.length === 0 ? 'No data' : JSON.stringify(data, null, 2);
|
|
28
26
|
return {
|
|
29
27
|
content: [{ type: 'text', text }],
|
|
30
28
|
isError: false,
|
|
@@ -49,10 +47,9 @@ Custom buckets may not share the \`monitoring\` schema — don't assume a \`devi
|
|
|
49
47
|
description:
|
|
50
48
|
'Bucket ID as declared in the product profile (e.g. "monitoring", or a custom one like "sensors", "events").',
|
|
51
49
|
},
|
|
52
|
-
|
|
50
|
+
device_id: {
|
|
53
51
|
type: 'string',
|
|
54
|
-
description:
|
|
55
|
-
'Device ID. Omit for a one-row-per-device fleet view.',
|
|
52
|
+
description: 'Device ID. Omit for a one-row-per-device fleet view.',
|
|
56
53
|
},
|
|
57
54
|
items: {
|
|
58
55
|
type: 'number',
|
|
@@ -61,8 +58,7 @@ Custom buckets may not share the \`monitoring\` schema — don't assume a \`devi
|
|
|
61
58
|
},
|
|
62
59
|
minutes: {
|
|
63
60
|
type: 'number',
|
|
64
|
-
description:
|
|
65
|
-
'Get data from the last N minutes. Alternative to min_ts/max_ts.',
|
|
61
|
+
description: 'Get data from the last N minutes. Alternative to min_ts/max_ts.',
|
|
66
62
|
},
|
|
67
63
|
min_ts: { type: 'number', description: 'Minimum timestamp in milliseconds' },
|
|
68
64
|
max_ts: {
|
package/lib/mcp/tools-devices.js
CHANGED
|
@@ -39,7 +39,7 @@ async function toolDevices(args) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
async function toolDeviceInfo(args) {
|
|
42
|
-
const { api: deviceApi } = getAPI(args.
|
|
42
|
+
const { api: deviceApi } = getAPI(args.device_id, args.user);
|
|
43
43
|
const parts = [];
|
|
44
44
|
|
|
45
45
|
// Server-side record first — surfaces the assigned product,
|
|
@@ -47,7 +47,7 @@ async function toolDeviceInfo(args) {
|
|
|
47
47
|
// can't see. Best effort: don't fail the whole tool if the
|
|
48
48
|
// record can't be read.
|
|
49
49
|
try {
|
|
50
|
-
const record = await getDevice(args.
|
|
50
|
+
const record = await getDevice(args.device_id, args.user);
|
|
51
51
|
const meta = [];
|
|
52
52
|
if (record.device) meta.push(`device: ${record.device}`);
|
|
53
53
|
if (record.name) meta.push(`name: ${record.name}`);
|
|
@@ -120,7 +120,7 @@ async function toolDeviceInfo(args) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
async function toolDeviceSetProduct(args) {
|
|
123
|
-
const device = args.
|
|
123
|
+
const device = args.device_id;
|
|
124
124
|
if (!device) throw inputError('device is required');
|
|
125
125
|
if (args.product === undefined || args.product === null)
|
|
126
126
|
throw inputError('product is required (pass "" to unassign)');
|
|
@@ -133,7 +133,7 @@ async function toolDeviceSetProduct(args) {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
async function toolDeviceDelete(args) {
|
|
136
|
-
const device = args.
|
|
136
|
+
const device = args.device_id;
|
|
137
137
|
if (!device) throw inputError('device is required');
|
|
138
138
|
const removed = await deleteDevice(device, args.user);
|
|
139
139
|
return {
|
|
@@ -182,10 +182,10 @@ export const tools = [
|
|
|
182
182
|
inputSchema: {
|
|
183
183
|
type: 'object',
|
|
184
184
|
properties: {
|
|
185
|
-
|
|
185
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
186
186
|
user: { type: 'string', description: 'API user' },
|
|
187
187
|
},
|
|
188
|
-
required: ['
|
|
188
|
+
required: ['device_id'],
|
|
189
189
|
},
|
|
190
190
|
handler: toolDeviceInfo,
|
|
191
191
|
},
|
|
@@ -196,14 +196,14 @@ export const tools = [
|
|
|
196
196
|
inputSchema: {
|
|
197
197
|
type: 'object',
|
|
198
198
|
properties: {
|
|
199
|
-
|
|
199
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
200
200
|
product: {
|
|
201
201
|
type: 'string',
|
|
202
202
|
description: 'Product ID to assign, or empty string "" to unassign.',
|
|
203
203
|
},
|
|
204
204
|
user: { type: 'string', description: 'API user' },
|
|
205
205
|
},
|
|
206
|
-
required: ['
|
|
206
|
+
required: ['device_id', 'product'],
|
|
207
207
|
},
|
|
208
208
|
handler: toolDeviceSetProduct,
|
|
209
209
|
},
|
|
@@ -214,10 +214,10 @@ export const tools = [
|
|
|
214
214
|
inputSchema: {
|
|
215
215
|
type: 'object',
|
|
216
216
|
properties: {
|
|
217
|
-
|
|
217
|
+
device_id: { type: 'string', description: 'Device ID to delete.' },
|
|
218
218
|
user: { type: 'string', description: 'API user' },
|
|
219
219
|
},
|
|
220
|
-
required: ['
|
|
220
|
+
required: ['device_id'],
|
|
221
221
|
},
|
|
222
222
|
handler: toolDeviceDelete,
|
|
223
223
|
},
|
package/lib/mcp/tools-exec.js
CHANGED
|
@@ -5,7 +5,7 @@ import { BYTES, TIMEOUTS } from '../constants.js';
|
|
|
5
5
|
import { getAPI } from './helpers.js';
|
|
6
6
|
|
|
7
7
|
async function toolExec(args) {
|
|
8
|
-
const { api } = getAPI(args.
|
|
8
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
9
9
|
// Default guards against hung commands locking the session; callers
|
|
10
10
|
// override for known-long operations. Knob lives in lib/constants.js.
|
|
11
11
|
const timeout =
|
|
@@ -33,7 +33,7 @@ async function toolExec(args) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
async function toolUpdate(args) {
|
|
36
|
-
const { api: deviceApi } = getAPI(args.
|
|
36
|
+
const { api: deviceApi } = getAPI(args.device_id, args.user);
|
|
37
37
|
const payload = {
|
|
38
38
|
action: args.action,
|
|
39
39
|
channel: args.channel || 'latest',
|
|
@@ -46,7 +46,7 @@ async function toolUpdate(args) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async function toolMonitoring(args) {
|
|
49
|
-
const device = args.
|
|
49
|
+
const device = args.device_id;
|
|
50
50
|
const user = args.user;
|
|
51
51
|
|
|
52
52
|
// Real-time: read the device's `monitoring` resource directly.
|
|
@@ -143,11 +143,11 @@ export const tools = [
|
|
|
143
143
|
type: 'object',
|
|
144
144
|
properties: {
|
|
145
145
|
command: { type: 'string', description: 'Shell command to execute' },
|
|
146
|
-
|
|
146
|
+
device_id: { type: 'string', description: 'Device ID (from thinr_devices)' },
|
|
147
147
|
timeout: { type: 'number', description: 'Timeout in seconds (default: 30)' },
|
|
148
148
|
user: { type: 'string', description: 'API user (for admin impersonation)' },
|
|
149
149
|
},
|
|
150
|
-
required: ['command', '
|
|
150
|
+
required: ['command', 'device_id'],
|
|
151
151
|
},
|
|
152
152
|
handler: toolExec,
|
|
153
153
|
},
|
|
@@ -157,7 +157,7 @@ export const tools = [
|
|
|
157
157
|
inputSchema: {
|
|
158
158
|
type: 'object',
|
|
159
159
|
properties: {
|
|
160
|
-
|
|
160
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
161
161
|
action: {
|
|
162
162
|
type: 'string',
|
|
163
163
|
description: '"check" to check for updates, "apply" to install the update',
|
|
@@ -165,7 +165,7 @@ export const tools = [
|
|
|
165
165
|
channel: { type: 'string', description: 'Update channel (default: "latest")' },
|
|
166
166
|
user: { type: 'string', description: 'API user' },
|
|
167
167
|
},
|
|
168
|
-
required: ['action', '
|
|
168
|
+
required: ['action', 'device_id'],
|
|
169
169
|
},
|
|
170
170
|
handler: toolUpdate,
|
|
171
171
|
},
|
|
@@ -185,14 +185,15 @@ Modes:
|
|
|
185
185
|
description:
|
|
186
186
|
'If true, get live data directly from the device. If false/omitted, query historical data from the monitoring bucket.',
|
|
187
187
|
},
|
|
188
|
-
|
|
188
|
+
device_id: {
|
|
189
189
|
type: 'string',
|
|
190
190
|
description:
|
|
191
191
|
'Device ID. Required for realtime. Omit for a one-row-per-device fleet view.',
|
|
192
192
|
},
|
|
193
193
|
items: {
|
|
194
194
|
type: 'number',
|
|
195
|
-
description:
|
|
195
|
+
description:
|
|
196
|
+
'Number of data points to return (default: 10). In multi-device mode set ≥ number of devices (e.g. 200) since each device contributes one row.',
|
|
196
197
|
},
|
|
197
198
|
minutes: {
|
|
198
199
|
type: 'number',
|
|
@@ -205,12 +206,18 @@ Modes:
|
|
|
205
206
|
},
|
|
206
207
|
sort: {
|
|
207
208
|
type: 'string',
|
|
208
|
-
description:
|
|
209
|
+
description:
|
|
210
|
+
'Sort order by timestamp: "asc" or "desc" (default: "desc"). Does NOT sort by metric — do that client-side on the result.',
|
|
211
|
+
},
|
|
212
|
+
agg: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
description:
|
|
215
|
+
'Time-window size for windowed aggregation: "5m", "10m", "1h", "6h". Only meaningful with a specific device. For a per-device single value across the whole range, omit `agg` and pass `agg_type` alone.',
|
|
209
216
|
},
|
|
210
|
-
agg: { type: 'string', description: 'Time-window size for windowed aggregation: "5m", "10m", "1h", "6h". Only meaningful with a specific device. For a per-device single value across the whole range, omit `agg` and pass `agg_type` alone.' },
|
|
211
217
|
agg_type: {
|
|
212
218
|
type: 'string',
|
|
213
|
-
description:
|
|
219
|
+
description:
|
|
220
|
+
'Reducer applied to each group/window. Supported: "mean", "max", "min", "sum", "count", "first", "last", "median", "spread" (max-min), "stddev". Most common for monitoring: max, mean, min.',
|
|
214
221
|
},
|
|
215
222
|
fields: {
|
|
216
223
|
type: 'string',
|
|
@@ -5,7 +5,7 @@ import { getAPI } from './helpers.js';
|
|
|
5
5
|
import { inputError } from '../errors.js';
|
|
6
6
|
|
|
7
7
|
async function toolRead(args) {
|
|
8
|
-
const { api } = getAPI(args.
|
|
8
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
9
9
|
const content = await api.readFile(args.path);
|
|
10
10
|
return {
|
|
11
11
|
content: [{ type: 'text', text: content.toString('utf8') }],
|
|
@@ -14,7 +14,7 @@ async function toolRead(args) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async function toolWrite(args) {
|
|
17
|
-
const { api } = getAPI(args.
|
|
17
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
18
18
|
await api.writeFile(args.path, Buffer.from(args.content, 'utf8'));
|
|
19
19
|
return {
|
|
20
20
|
content: [{ type: 'text', text: `Written ${args.content.length} bytes to ${args.path}` }],
|
|
@@ -23,7 +23,7 @@ async function toolWrite(args) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async function toolLs(args) {
|
|
26
|
-
const { api } = getAPI(args.
|
|
26
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
27
27
|
const entries = await api.listDir(args.path || '/', args.include_hidden || false);
|
|
28
28
|
const lines = entries.map((e) => {
|
|
29
29
|
const type = e.type === 'directory' ? 'd' : '-';
|
|
@@ -38,7 +38,7 @@ async function toolLs(args) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async function toolMkdir(args) {
|
|
41
|
-
const { api } = getAPI(args.
|
|
41
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
42
42
|
await api.mkdir(args.path);
|
|
43
43
|
return {
|
|
44
44
|
content: [{ type: 'text', text: `Created directory: ${args.path}` }],
|
|
@@ -47,7 +47,7 @@ async function toolMkdir(args) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async function toolRm(args) {
|
|
50
|
-
const { api } = getAPI(args.
|
|
50
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
51
51
|
await api.delete(args.path, args.recursive !== false);
|
|
52
52
|
return {
|
|
53
53
|
content: [{ type: 'text', text: `Deleted: ${args.path}` }],
|
|
@@ -56,7 +56,7 @@ async function toolRm(args) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
async function toolMv(args) {
|
|
59
|
-
const { api } = getAPI(args.
|
|
59
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
60
60
|
await api.move(args.source, args.destination, args.overwrite || false);
|
|
61
61
|
return {
|
|
62
62
|
content: [{ type: 'text', text: `Moved: ${args.source} → ${args.destination}` }],
|
|
@@ -75,7 +75,7 @@ async function toolPush(args) {
|
|
|
75
75
|
const remotePath = args.destination.endsWith('/')
|
|
76
76
|
? args.destination + basename(localPath)
|
|
77
77
|
: args.destination;
|
|
78
|
-
const { api } = getAPI(args.
|
|
78
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
79
79
|
const content = await readLocalFile(localPath);
|
|
80
80
|
await api.writeFile(remotePath, content);
|
|
81
81
|
return {
|
|
@@ -99,7 +99,7 @@ async function toolPull(args) {
|
|
|
99
99
|
const localPath = args.destination.endsWith('/')
|
|
100
100
|
? resolvePath(args.destination, basename(args.source))
|
|
101
101
|
: resolvePath(args.destination);
|
|
102
|
-
const { api } = getAPI(args.
|
|
102
|
+
const { api } = getAPI(args.device_id, args.user);
|
|
103
103
|
const buf = await api.readFile(args.source);
|
|
104
104
|
await writeLocalFile(localPath, buf);
|
|
105
105
|
return {
|
|
@@ -121,10 +121,10 @@ export const tools = [
|
|
|
121
121
|
type: 'object',
|
|
122
122
|
properties: {
|
|
123
123
|
path: { type: 'string', description: 'Absolute file path on the device' },
|
|
124
|
-
|
|
124
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
125
125
|
user: { type: 'string', description: 'API user' },
|
|
126
126
|
},
|
|
127
|
-
required: ['path', '
|
|
127
|
+
required: ['path', 'device_id'],
|
|
128
128
|
},
|
|
129
129
|
handler: toolRead,
|
|
130
130
|
},
|
|
@@ -136,10 +136,10 @@ export const tools = [
|
|
|
136
136
|
properties: {
|
|
137
137
|
path: { type: 'string', description: 'Absolute file path on the device' },
|
|
138
138
|
content: { type: 'string', description: 'File content to write' },
|
|
139
|
-
|
|
139
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
140
140
|
user: { type: 'string', description: 'API user' },
|
|
141
141
|
},
|
|
142
|
-
required: ['path', 'content', '
|
|
142
|
+
required: ['path', 'content', 'device_id'],
|
|
143
143
|
},
|
|
144
144
|
handler: toolWrite,
|
|
145
145
|
},
|
|
@@ -154,10 +154,10 @@ export const tools = [
|
|
|
154
154
|
type: 'boolean',
|
|
155
155
|
description: 'Include hidden files (default: false)',
|
|
156
156
|
},
|
|
157
|
-
|
|
157
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
158
158
|
user: { type: 'string', description: 'API user' },
|
|
159
159
|
},
|
|
160
|
-
required: ['
|
|
160
|
+
required: ['device_id'],
|
|
161
161
|
},
|
|
162
162
|
handler: toolLs,
|
|
163
163
|
},
|
|
@@ -168,10 +168,10 @@ export const tools = [
|
|
|
168
168
|
type: 'object',
|
|
169
169
|
properties: {
|
|
170
170
|
path: { type: 'string', description: 'Directory path to create' },
|
|
171
|
-
|
|
171
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
172
172
|
user: { type: 'string', description: 'API user' },
|
|
173
173
|
},
|
|
174
|
-
required: ['path', '
|
|
174
|
+
required: ['path', 'device_id'],
|
|
175
175
|
},
|
|
176
176
|
handler: toolMkdir,
|
|
177
177
|
},
|
|
@@ -186,10 +186,10 @@ export const tools = [
|
|
|
186
186
|
type: 'boolean',
|
|
187
187
|
description: 'Delete directory contents recursively (default: true)',
|
|
188
188
|
},
|
|
189
|
-
|
|
189
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
190
190
|
user: { type: 'string', description: 'API user' },
|
|
191
191
|
},
|
|
192
|
-
required: ['path', '
|
|
192
|
+
required: ['path', 'device_id'],
|
|
193
193
|
},
|
|
194
194
|
handler: toolRm,
|
|
195
195
|
},
|
|
@@ -205,10 +205,10 @@ export const tools = [
|
|
|
205
205
|
type: 'boolean',
|
|
206
206
|
description: 'Overwrite destination if exists (default: false)',
|
|
207
207
|
},
|
|
208
|
-
|
|
208
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
209
209
|
user: { type: 'string', description: 'API user' },
|
|
210
210
|
},
|
|
211
|
-
required: ['source', 'destination', '
|
|
211
|
+
required: ['source', 'destination', 'device_id'],
|
|
212
212
|
},
|
|
213
213
|
handler: toolMv,
|
|
214
214
|
},
|
|
@@ -227,10 +227,10 @@ export const tools = [
|
|
|
227
227
|
description:
|
|
228
228
|
'Absolute path on the device. Trailing "/" keeps the source basename.',
|
|
229
229
|
},
|
|
230
|
-
|
|
230
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
231
231
|
user: { type: 'string', description: 'API user' },
|
|
232
232
|
},
|
|
233
|
-
required: ['source', 'destination', '
|
|
233
|
+
required: ['source', 'destination', 'device_id'],
|
|
234
234
|
},
|
|
235
235
|
handler: toolPush,
|
|
236
236
|
},
|
|
@@ -249,10 +249,10 @@ export const tools = [
|
|
|
249
249
|
description:
|
|
250
250
|
'Local file path (absolute or relative to the MCP server CWD). Trailing "/" keeps the source basename.',
|
|
251
251
|
},
|
|
252
|
-
|
|
252
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
253
253
|
user: { type: 'string', description: 'API user' },
|
|
254
254
|
},
|
|
255
|
-
required: ['source', 'destination', '
|
|
255
|
+
required: ['source', 'destination', 'device_id'],
|
|
256
256
|
},
|
|
257
257
|
handler: toolPull,
|
|
258
258
|
},
|
package/lib/mcp/tools-install.js
CHANGED
|
@@ -41,7 +41,7 @@ async function toolAgentInstallCommand(args) {
|
|
|
41
41
|
const url = `${scheme}://get.thinremote.io/${script}`;
|
|
42
42
|
|
|
43
43
|
const agentArgs = ['install', '--token', shQuote(config.token)];
|
|
44
|
-
if (args.
|
|
44
|
+
if (args.device_id) agentArgs.push('--device', shQuote(args.device_id));
|
|
45
45
|
if (args.product) agentArgs.push('--product', shQuote(args.product));
|
|
46
46
|
if (args.overwrite) agentArgs.push('--overwrite');
|
|
47
47
|
if (args.no_start) agentArgs.push('--no-start');
|
|
@@ -83,7 +83,7 @@ async function toolAgentInstallCommand(args) {
|
|
|
83
83
|
command,
|
|
84
84
|
'',
|
|
85
85
|
'Notes:',
|
|
86
|
-
|
|
86
|
+
"- The command embeds the active profile's API token — treat it as a credential.",
|
|
87
87
|
'- Installing the system service needs root. Pass `sudo: true` if the target host is not already root.',
|
|
88
88
|
'- The agent reads the target server from the JWT, so `--host` is not needed.',
|
|
89
89
|
];
|
|
@@ -106,10 +106,10 @@ export const tools = [
|
|
|
106
106
|
inputSchema: {
|
|
107
107
|
type: 'object',
|
|
108
108
|
properties: {
|
|
109
|
-
|
|
109
|
+
device_id: {
|
|
110
110
|
type: 'string',
|
|
111
111
|
description:
|
|
112
|
-
|
|
112
|
+
"Custom device identifier. Omit to let the agent default to the target host's hostname.",
|
|
113
113
|
},
|
|
114
114
|
product: {
|
|
115
115
|
type: 'string',
|
|
@@ -127,8 +127,7 @@ export const tools = [
|
|
|
127
127
|
},
|
|
128
128
|
no_verify_ssl: {
|
|
129
129
|
type: 'boolean',
|
|
130
|
-
description:
|
|
131
|
-
'Disable TLS certificate verification (for self-signed servers).',
|
|
130
|
+
description: 'Disable TLS certificate verification (for self-signed servers).',
|
|
132
131
|
},
|
|
133
132
|
channel: {
|
|
134
133
|
type: 'string',
|
|
@@ -170,15 +170,15 @@ Response carries a consolidated report: devices visited, ok / failed counts, and
|
|
|
170
170
|
},
|
|
171
171
|
target_product: {
|
|
172
172
|
type: 'string',
|
|
173
|
-
description:
|
|
173
|
+
description: "Override the playbook's target.product.",
|
|
174
174
|
},
|
|
175
175
|
target_group: {
|
|
176
176
|
type: 'string',
|
|
177
|
-
description:
|
|
177
|
+
description: "Override the playbook's target.group.",
|
|
178
178
|
},
|
|
179
179
|
target_devices: {
|
|
180
180
|
type: 'array',
|
|
181
|
-
description:
|
|
181
|
+
description: "Override the playbook's target.devices with an explicit list.",
|
|
182
182
|
},
|
|
183
183
|
concurrency: {
|
|
184
184
|
type: 'number',
|
|
@@ -187,7 +187,7 @@ Response carries a consolidated report: devices visited, ok / failed counts, and
|
|
|
187
187
|
fail_fast: {
|
|
188
188
|
type: 'boolean',
|
|
189
189
|
description:
|
|
190
|
-
|
|
190
|
+
"Stop dequeueing new devices on the first failure (default: playbook's own value).",
|
|
191
191
|
},
|
|
192
192
|
continue_on_error: {
|
|
193
193
|
type: 'boolean',
|
|
@@ -196,8 +196,7 @@ Response carries a consolidated report: devices visited, ok / failed counts, and
|
|
|
196
196
|
},
|
|
197
197
|
vars: {
|
|
198
198
|
type: 'object',
|
|
199
|
-
description:
|
|
200
|
-
'Merge into the playbook\'s `vars` block before substitution.',
|
|
199
|
+
description: "Merge into the playbook's `vars` block before substitution.",
|
|
201
200
|
},
|
|
202
201
|
user: {
|
|
203
202
|
type: 'string',
|
|
@@ -141,20 +141,14 @@ async function toolProductUpdate(args) {
|
|
|
141
141
|
|
|
142
142
|
const fields = Object.keys(patch);
|
|
143
143
|
if (fields.length === 0) {
|
|
144
|
-
throw inputError(
|
|
145
|
-
'at least one of name, description, enabled, icon must be provided',
|
|
146
|
-
);
|
|
144
|
+
throw inputError('at least one of name, description, enabled, icon must be provided');
|
|
147
145
|
}
|
|
148
146
|
|
|
149
147
|
await updateProduct(args.product, args.user, patch);
|
|
150
148
|
|
|
151
|
-
const summary = fields
|
|
152
|
-
.map((f) => (f === 'config' ? 'icon' : f))
|
|
153
|
-
.join(', ');
|
|
149
|
+
const summary = fields.map((f) => (f === 'config' ? 'icon' : f)).join(', ');
|
|
154
150
|
return {
|
|
155
|
-
content: [
|
|
156
|
-
{ type: 'text', text: `Updated product "${args.product}" (${summary}).` },
|
|
157
|
-
],
|
|
151
|
+
content: [{ type: 'text', text: `Updated product "${args.product}" (${summary}).` }],
|
|
158
152
|
isError: false,
|
|
159
153
|
};
|
|
160
154
|
}
|
|
@@ -430,7 +424,7 @@ async function toolProductPlaybookWrite(args) {
|
|
|
430
424
|
async function toolProductPlaybookRun(args) {
|
|
431
425
|
if (!args.product) throw inputError('product is required');
|
|
432
426
|
if (!args.name) throw inputError('name is required');
|
|
433
|
-
if (!args.
|
|
427
|
+
if (!args.device_id) throw inputError('device is required (single-device only)');
|
|
434
428
|
|
|
435
429
|
const entry = await findProductPlaybook(args.product, args.name, args.user);
|
|
436
430
|
if (!entry) {
|
|
@@ -453,7 +447,7 @@ async function toolProductPlaybookRun(args) {
|
|
|
453
447
|
throw inputError(err.message);
|
|
454
448
|
}
|
|
455
449
|
|
|
456
|
-
pb.target.devices = [args.
|
|
450
|
+
pb.target.devices = [args.device_id];
|
|
457
451
|
pb.target.product = args.product;
|
|
458
452
|
pb.target.group = null;
|
|
459
453
|
|
|
@@ -463,7 +457,7 @@ async function toolProductPlaybookRun(args) {
|
|
|
463
457
|
dry_run: true,
|
|
464
458
|
product: args.product,
|
|
465
459
|
name: args.name,
|
|
466
|
-
device: args.
|
|
460
|
+
device: args.device_id,
|
|
467
461
|
variables: listVariables(pb),
|
|
468
462
|
vars: resolvedScope,
|
|
469
463
|
steps: plan,
|
|
@@ -475,11 +469,10 @@ async function toolProductPlaybookRun(args) {
|
|
|
475
469
|
}
|
|
476
470
|
|
|
477
471
|
const all = await getDevices({ product: args.product }, args.user);
|
|
478
|
-
const device =
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
};
|
|
472
|
+
const device = all.find((d) => d.device === args.device_id) || {
|
|
473
|
+
device: args.device_id,
|
|
474
|
+
connection: { active: false },
|
|
475
|
+
};
|
|
483
476
|
|
|
484
477
|
const results = await runPlaybook(pb, [device], {
|
|
485
478
|
user: args.user,
|
|
@@ -492,7 +485,7 @@ async function toolProductPlaybookRun(args) {
|
|
|
492
485
|
const payload = {
|
|
493
486
|
product: args.product,
|
|
494
487
|
name: args.name,
|
|
495
|
-
device: args.
|
|
488
|
+
device: args.device_id,
|
|
496
489
|
mode: args.check ? 'check' : 'apply',
|
|
497
490
|
ok: !!result?.ok,
|
|
498
491
|
result,
|
|
@@ -508,9 +501,7 @@ async function toolProductPlaybookRollout(args) {
|
|
|
508
501
|
if (!args.name) throw inputError('name is required');
|
|
509
502
|
|
|
510
503
|
const batchSize =
|
|
511
|
-
Number.isFinite(args.batch_size) && args.batch_size > 0
|
|
512
|
-
? Math.floor(args.batch_size)
|
|
513
|
-
: 5;
|
|
504
|
+
Number.isFinite(args.batch_size) && args.batch_size > 0 ? Math.floor(args.batch_size) : 5;
|
|
514
505
|
const failureThreshold =
|
|
515
506
|
args.failure_threshold === undefined || args.failure_threshold === null
|
|
516
507
|
? DEFAULT_FAILURE_THRESHOLD
|
|
@@ -1455,9 +1446,10 @@ Pass \`dry_run: true\` to get the resolved plan without contacting the device. P
|
|
|
1455
1446
|
type: 'string',
|
|
1456
1447
|
description: 'Playbook name as registered in the product index.',
|
|
1457
1448
|
},
|
|
1458
|
-
|
|
1449
|
+
device_id: {
|
|
1459
1450
|
type: 'string',
|
|
1460
|
-
description:
|
|
1451
|
+
description:
|
|
1452
|
+
'Target device ID (single-device only). Pick any device of the product.',
|
|
1461
1453
|
},
|
|
1462
1454
|
vars: {
|
|
1463
1455
|
type: 'object',
|
|
@@ -1477,7 +1469,7 @@ Pass \`dry_run: true\` to get the resolved plan without contacting the device. P
|
|
|
1477
1469
|
},
|
|
1478
1470
|
user: { type: 'string', description: 'API user (admin impersonation).' },
|
|
1479
1471
|
},
|
|
1480
|
-
required: ['product', 'name', '
|
|
1472
|
+
required: ['product', 'name', 'device_id'],
|
|
1481
1473
|
},
|
|
1482
1474
|
handler: toolProductPlaybookRun,
|
|
1483
1475
|
},
|
package/lib/mcp/tools-profile.js
CHANGED
|
@@ -401,7 +401,8 @@ Gotchas:
|
|
|
401
401
|
},
|
|
402
402
|
enabled: {
|
|
403
403
|
type: 'boolean',
|
|
404
|
-
description:
|
|
404
|
+
description:
|
|
405
|
+
'Default: true. Set to false to stage a definition without activating it.',
|
|
405
406
|
},
|
|
406
407
|
user: { type: 'string', description: 'API user.' },
|
|
407
408
|
},
|
|
@@ -3,7 +3,7 @@ import { getDeviceProperties, getDeviceProperty, setDeviceProperty } from '../pr
|
|
|
3
3
|
import { inputError } from '../errors.js';
|
|
4
4
|
|
|
5
5
|
async function toolPropertyGet(args) {
|
|
6
|
-
const device = args.
|
|
6
|
+
const device = args.device_id;
|
|
7
7
|
if (!device)
|
|
8
8
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
9
9
|
if (args.property) {
|
|
@@ -22,7 +22,7 @@ async function toolPropertyGet(args) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async function toolPropertySet(args) {
|
|
25
|
-
const device = args.
|
|
25
|
+
const device = args.device_id;
|
|
26
26
|
if (!device)
|
|
27
27
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
28
28
|
if (!args.property) throw inputError('property is required');
|
|
@@ -51,10 +51,10 @@ export const tools = [
|
|
|
51
51
|
description:
|
|
52
52
|
'Property name. Omit to list the names of all properties on the device.',
|
|
53
53
|
},
|
|
54
|
-
|
|
54
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
55
55
|
user: { type: 'string', description: 'API user' },
|
|
56
56
|
},
|
|
57
|
-
required: ['
|
|
57
|
+
required: ['device_id'],
|
|
58
58
|
},
|
|
59
59
|
handler: toolPropertyGet,
|
|
60
60
|
},
|
|
@@ -69,10 +69,10 @@ export const tools = [
|
|
|
69
69
|
description:
|
|
70
70
|
'Any JSON-serialisable value (string, number, boolean, object, array).',
|
|
71
71
|
},
|
|
72
|
-
|
|
72
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
73
73
|
user: { type: 'string', description: 'API user' },
|
|
74
74
|
},
|
|
75
|
-
required: ['property', 'value', '
|
|
75
|
+
required: ['property', 'value', 'device_id'],
|
|
76
76
|
},
|
|
77
77
|
handler: toolPropertySet,
|
|
78
78
|
},
|
|
@@ -3,7 +3,7 @@ import { callDeviceResource, listDeviceResourcesWithSchemas } from '../resource.
|
|
|
3
3
|
import { inputError } from '../errors.js';
|
|
4
4
|
|
|
5
5
|
async function toolResourceList(args) {
|
|
6
|
-
const device = args.
|
|
6
|
+
const device = args.device_id;
|
|
7
7
|
if (!device)
|
|
8
8
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
9
9
|
const entries = await listDeviceResourcesWithSchemas(device, { user: args.user });
|
|
@@ -22,7 +22,7 @@ async function toolResourceList(args) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async function toolResourceCall(args) {
|
|
25
|
-
const device = args.
|
|
25
|
+
const device = args.device_id;
|
|
26
26
|
if (!device)
|
|
27
27
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
28
28
|
if (!args.resource) throw inputError('resource is required');
|
|
@@ -47,10 +47,10 @@ export const tools = [
|
|
|
47
47
|
inputSchema: {
|
|
48
48
|
type: 'object',
|
|
49
49
|
properties: {
|
|
50
|
-
|
|
50
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
51
51
|
user: { type: 'string', description: 'API user' },
|
|
52
52
|
},
|
|
53
|
-
required: ['
|
|
53
|
+
required: ['device_id'],
|
|
54
54
|
},
|
|
55
55
|
handler: toolResourceList,
|
|
56
56
|
},
|
|
@@ -68,10 +68,10 @@ export const tools = [
|
|
|
68
68
|
description:
|
|
69
69
|
'JSON object with the input parameters declared by the resource. Omit or pass {} for input-less resources.',
|
|
70
70
|
},
|
|
71
|
-
|
|
71
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
72
72
|
user: { type: 'string', description: 'API user' },
|
|
73
73
|
},
|
|
74
|
-
required: ['resource', '
|
|
74
|
+
required: ['resource', 'device_id'],
|
|
75
75
|
},
|
|
76
76
|
handler: toolResourceCall,
|
|
77
77
|
},
|
package/lib/mcp/tools-scripts.js
CHANGED
|
@@ -4,7 +4,7 @@ import { inputError } from '../errors.js';
|
|
|
4
4
|
import { getAPI } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
async function toolScriptList(args) {
|
|
7
|
-
const device = args.
|
|
7
|
+
const device = args.device_id;
|
|
8
8
|
if (!device)
|
|
9
9
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
10
10
|
const info = await readDeviceResource(device, '$scripts/info', { user: args.user });
|
|
@@ -25,7 +25,7 @@ async function toolScriptList(args) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async function toolScriptWrite(args) {
|
|
28
|
-
const device = args.
|
|
28
|
+
const device = args.device_id;
|
|
29
29
|
if (!device)
|
|
30
30
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
31
31
|
if (!args.name) throw inputError('name is required');
|
|
@@ -76,7 +76,7 @@ async function toolScriptWrite(args) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async function toolScriptDelete(args) {
|
|
79
|
-
const device = args.
|
|
79
|
+
const device = args.device_id;
|
|
80
80
|
if (!device)
|
|
81
81
|
throw inputError('No device specified. Use thinr_devices to list available devices.');
|
|
82
82
|
if (!args.name) throw inputError('name is required');
|
|
@@ -113,10 +113,10 @@ export const tools = [
|
|
|
113
113
|
inputSchema: {
|
|
114
114
|
type: 'object',
|
|
115
115
|
properties: {
|
|
116
|
-
|
|
116
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
117
117
|
user: { type: 'string', description: 'API user' },
|
|
118
118
|
},
|
|
119
|
-
required: ['
|
|
119
|
+
required: ['device_id'],
|
|
120
120
|
},
|
|
121
121
|
handler: toolScriptList,
|
|
122
122
|
},
|
|
@@ -144,10 +144,10 @@ This tool writes the script, makes it executable, and calls the agent's \`$scrip
|
|
|
144
144
|
type: 'string',
|
|
145
145
|
description: 'Full script source code, including shebang.',
|
|
146
146
|
},
|
|
147
|
-
|
|
147
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
148
148
|
user: { type: 'string', description: 'API user' },
|
|
149
149
|
},
|
|
150
|
-
required: ['name', 'content', '
|
|
150
|
+
required: ['name', 'content', 'device_id'],
|
|
151
151
|
},
|
|
152
152
|
handler: toolScriptWrite,
|
|
153
153
|
},
|
|
@@ -162,10 +162,10 @@ This tool writes the script, makes it executable, and calls the agent's \`$scrip
|
|
|
162
162
|
description:
|
|
163
163
|
'Name of the script to remove (must match the file basename, without extension).',
|
|
164
164
|
},
|
|
165
|
-
|
|
165
|
+
device_id: { type: 'string', description: 'Device ID' },
|
|
166
166
|
user: { type: 'string', description: 'API user' },
|
|
167
167
|
},
|
|
168
|
-
required: ['name', '
|
|
168
|
+
required: ['name', 'device_id'],
|
|
169
169
|
},
|
|
170
170
|
handler: toolScriptDelete,
|
|
171
171
|
},
|
package/lib/mcp/tools-tokens.js
CHANGED
|
@@ -115,13 +115,11 @@ async function toolTokenDelete(args) {
|
|
|
115
115
|
// ─── Device-level tokens ────────────────────────────────────────────
|
|
116
116
|
|
|
117
117
|
async function toolDeviceTokenList(args) {
|
|
118
|
-
if (!args.
|
|
119
|
-
const tokens = await listDeviceTokens(args.
|
|
118
|
+
if (!args.device_id) throw inputError('device is required');
|
|
119
|
+
const tokens = await listDeviceTokens(args.device_id, args.user);
|
|
120
120
|
if (tokens.length === 0) {
|
|
121
121
|
return {
|
|
122
|
-
content: [
|
|
123
|
-
{ type: 'text', text: `No device tokens on device "${args.device}".` },
|
|
124
|
-
],
|
|
122
|
+
content: [{ type: 'text', text: `No device tokens on device "${args.device_id}".` }],
|
|
125
123
|
isError: false,
|
|
126
124
|
};
|
|
127
125
|
}
|
|
@@ -129,7 +127,7 @@ async function toolDeviceTokenList(args) {
|
|
|
129
127
|
content: [
|
|
130
128
|
{
|
|
131
129
|
type: 'text',
|
|
132
|
-
text: `${tokens.length} device token(s) on "${args.
|
|
130
|
+
text: `${tokens.length} device token(s) on "${args.device_id}":\n${JSON.stringify(tokens, null, 2)}`,
|
|
133
131
|
},
|
|
134
132
|
],
|
|
135
133
|
isError: false,
|
|
@@ -137,11 +135,11 @@ async function toolDeviceTokenList(args) {
|
|
|
137
135
|
}
|
|
138
136
|
|
|
139
137
|
async function toolDeviceTokenCreate(args) {
|
|
140
|
-
if (!args.
|
|
138
|
+
if (!args.device_id) throw inputError('device is required');
|
|
141
139
|
if (!args.token_name) throw inputError('token_name is required');
|
|
142
140
|
|
|
143
141
|
const doc = await createDeviceToken(
|
|
144
|
-
args.
|
|
142
|
+
args.device_id,
|
|
145
143
|
{
|
|
146
144
|
token_name: args.token_name,
|
|
147
145
|
token_resources: args.token_resources,
|
|
@@ -153,7 +151,7 @@ async function toolDeviceTokenCreate(args) {
|
|
|
153
151
|
content: [
|
|
154
152
|
{
|
|
155
153
|
type: 'text',
|
|
156
|
-
text: `Created device token "${args.token_name}" on "${args.
|
|
154
|
+
text: `Created device token "${args.token_name}" on "${args.device_id}".\n${JSON.stringify(doc, null, 2)}`,
|
|
157
155
|
},
|
|
158
156
|
],
|
|
159
157
|
isError: false,
|
|
@@ -161,16 +159,16 @@ async function toolDeviceTokenCreate(args) {
|
|
|
161
159
|
}
|
|
162
160
|
|
|
163
161
|
async function toolDeviceTokenDelete(args) {
|
|
164
|
-
if (!args.
|
|
162
|
+
if (!args.device_id) throw inputError('device is required');
|
|
165
163
|
if (!args.token_id) throw inputError('token_id is required');
|
|
166
|
-
const removed = await deleteDeviceToken(args.
|
|
164
|
+
const removed = await deleteDeviceToken(args.device_id, args.token_id, args.user);
|
|
167
165
|
return {
|
|
168
166
|
content: [
|
|
169
167
|
{
|
|
170
168
|
type: 'text',
|
|
171
169
|
text: removed
|
|
172
|
-
? `Deleted device token "${args.token_id}" from "${args.
|
|
173
|
-
: `Device token "${args.token_id}" was not configured on "${args.
|
|
170
|
+
? `Deleted device token "${args.token_id}" from "${args.device_id}".`
|
|
171
|
+
: `Device token "${args.token_id}" was not configured on "${args.device_id}".`,
|
|
174
172
|
},
|
|
175
173
|
],
|
|
176
174
|
isError: false,
|
|
@@ -318,10 +316,10 @@ ${EXPIRY_NOTE}`,
|
|
|
318
316
|
inputSchema: {
|
|
319
317
|
type: 'object',
|
|
320
318
|
properties: {
|
|
321
|
-
|
|
319
|
+
device_id: { type: 'string', description: 'Device ID.' },
|
|
322
320
|
user: { type: 'string', description: 'API user.' },
|
|
323
321
|
},
|
|
324
|
-
required: ['
|
|
322
|
+
required: ['device_id'],
|
|
325
323
|
},
|
|
326
324
|
handler: toolDeviceTokenList,
|
|
327
325
|
},
|
|
@@ -336,7 +334,7 @@ Example: a token that lets a remote script call only the \`monitoring\` and \`re
|
|
|
336
334
|
inputSchema: {
|
|
337
335
|
type: 'object',
|
|
338
336
|
properties: {
|
|
339
|
-
|
|
337
|
+
device_id: { type: 'string', description: 'Device ID.' },
|
|
340
338
|
token_name: { type: 'string', description: 'Display name for the new token.' },
|
|
341
339
|
token_resources: {
|
|
342
340
|
type: 'array',
|
|
@@ -350,7 +348,7 @@ Example: a token that lets a remote script call only the \`monitoring\` and \`re
|
|
|
350
348
|
},
|
|
351
349
|
user: { type: 'string', description: 'API user.' },
|
|
352
350
|
},
|
|
353
|
-
required: ['
|
|
351
|
+
required: ['device_id', 'token_name'],
|
|
354
352
|
},
|
|
355
353
|
handler: toolDeviceTokenCreate,
|
|
356
354
|
},
|
|
@@ -360,11 +358,11 @@ Example: a token that lets a remote script call only the \`monitoring\` and \`re
|
|
|
360
358
|
inputSchema: {
|
|
361
359
|
type: 'object',
|
|
362
360
|
properties: {
|
|
363
|
-
|
|
361
|
+
device_id: { type: 'string', description: 'Device ID.' },
|
|
364
362
|
token_id: { type: 'string', description: 'Token id to remove.' },
|
|
365
363
|
user: { type: 'string', description: 'API user.' },
|
|
366
364
|
},
|
|
367
|
-
required: ['
|
|
365
|
+
required: ['device_id', 'token_id'],
|
|
368
366
|
},
|
|
369
367
|
handler: toolDeviceTokenDelete,
|
|
370
368
|
},
|