nova-control-mcp-server 0.0.7 → 0.0.9
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 +1 -1
- package/dist/nova-control-mcp-server.js +183 -135
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -129,7 +129,7 @@ All movement tools accept an optional `within_ms` parameter. When provided, the
|
|
|
129
129
|
| `lift_to` | `angle: number`; `within_ms?` | lift head on secondary axis, range 20°–150° — s5 |
|
|
130
130
|
| `wait` | `ms: number` | pause for `ms` milliseconds before the next action |
|
|
131
131
|
| `get_state` | — | return current servo positions as a JSON object with keys `s1`–`s5` |
|
|
132
|
-
| `run_script` | `script: string` | execute a multi-line movement script (one command per line; blank lines and `#`-comments ignored; commands: `home`, `shift-to`, `roll-to`, `pitch-to`, `rotate-to`, `lift-to`, `move`, `wait
|
|
132
|
+
| `run_script` | `script: string` | execute a multi-line movement script (one command per line; blank lines and `#`-comments ignored; commands: `home [<within_ms>]`, `shift-to <angle> [<within_ms>]`, `roll-to`, `pitch-to`, `rotate-to`, `lift-to`, `move [...] [within-ms <ms>]`, `wait <ms>`) |
|
|
133
133
|
| `disconnect` | — | close the serial connection to free the port; the connection reopens automatically on the next movement command |
|
|
134
134
|
|
|
135
135
|
### Servo mapping
|
|
@@ -7,42 +7,43 @@ import { Server as i } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
7
7
|
import { StdioServerTransport as a } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { StreamableHTTPServerTransport as o } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
9
|
import { CallToolRequestSchema as s, ListToolsRequestSchema as c } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
-
import {
|
|
10
|
+
import { z as l } from "zod";
|
|
11
|
+
import { SerialPort as u } from "serialport";
|
|
11
12
|
//#region ../nova-control-node/dist/nova-control-node.js
|
|
12
|
-
var
|
|
13
|
+
var d = 9600, f = Object.freeze({
|
|
13
14
|
s1: 90,
|
|
14
15
|
s2: 90,
|
|
15
16
|
s3: 110,
|
|
16
17
|
s4: 90,
|
|
17
18
|
s5: 95
|
|
18
|
-
}),
|
|
19
|
+
}), p = Object.freeze({
|
|
19
20
|
s1: [45, 135],
|
|
20
21
|
s2: [10, 170],
|
|
21
22
|
s3: [40, 150],
|
|
22
23
|
s4: [30, 180],
|
|
23
24
|
s5: [20, 150]
|
|
24
|
-
}),
|
|
25
|
-
s1: (
|
|
26
|
-
s2: (
|
|
27
|
-
s3: (
|
|
28
|
-
s4: (
|
|
29
|
-
s5: (
|
|
25
|
+
}), m = Object.freeze({
|
|
26
|
+
s1: (p.s1[1] - p.s1[0]) / 1e3,
|
|
27
|
+
s2: (p.s2[1] - p.s2[0]) / 1e3,
|
|
28
|
+
s3: (p.s3[1] - p.s3[0]) / 1e3,
|
|
29
|
+
s4: (p.s4[1] - p.s4[0]) / 1e3,
|
|
30
|
+
s5: (p.s5[1] - p.s5[0]) / 1e3
|
|
30
31
|
});
|
|
31
|
-
function
|
|
32
|
-
let [n, r] =
|
|
32
|
+
function h(e, t) {
|
|
33
|
+
let [n, r] = p[t];
|
|
33
34
|
return Math.max(n, Math.min(r, Math.round(e)));
|
|
34
35
|
}
|
|
35
|
-
function
|
|
36
|
+
function g(e) {
|
|
36
37
|
return new Uint8Array([
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
h(e.s4, "s4"),
|
|
39
|
+
h(e.s3, "s3"),
|
|
40
|
+
h(e.s2, "s2"),
|
|
41
|
+
h(e.s1, "s1"),
|
|
42
|
+
h(e.s5, "s5")
|
|
42
43
|
]);
|
|
43
44
|
}
|
|
44
|
-
async function
|
|
45
|
-
let n = new
|
|
45
|
+
async function _(e, t) {
|
|
46
|
+
let n = new u({
|
|
46
47
|
path: e,
|
|
47
48
|
baudRate: t,
|
|
48
49
|
autoOpen: !1
|
|
@@ -68,22 +69,22 @@ async function g(e, t) {
|
|
|
68
69
|
}
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
|
-
function
|
|
72
|
+
function v(e, t) {
|
|
72
73
|
let n = Math.min(.499, Math.max(0, t)), r = 1 / (1 - n);
|
|
73
74
|
if (e <= n) return r * e * e / (2 * n);
|
|
74
75
|
if (e <= 1 - n) return r * (e - n / 2);
|
|
75
76
|
let i = 1 - e;
|
|
76
77
|
return 1 - r * i * i / (2 * n);
|
|
77
78
|
}
|
|
78
|
-
async function
|
|
79
|
-
let r = n?.StepIntervalMs ?? 20, i = n?.RampRatio ?? .25, a = await
|
|
79
|
+
async function y(e, t = d, n) {
|
|
80
|
+
let r = n?.StepIntervalMs ?? 20, i = n?.RampRatio ?? .25, a = await _(e, t), o = { ...f }, s, c = Promise.resolve();
|
|
80
81
|
function l(e) {
|
|
81
82
|
s = {
|
|
82
83
|
...s ?? o,
|
|
83
84
|
...e
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
|
-
async function
|
|
87
|
+
async function u() {
|
|
87
88
|
let e = c;
|
|
88
89
|
c = (async () => {
|
|
89
90
|
try {
|
|
@@ -98,14 +99,14 @@ async function v(e, t = u, n) {
|
|
|
98
99
|
"s4",
|
|
99
100
|
"s5"
|
|
100
101
|
]) {
|
|
101
|
-
let a = e[i] - o[i], s = r > 0 ?
|
|
102
|
+
let a = e[i] - o[i], s = r > 0 ? m[i] * r : Infinity;
|
|
102
103
|
Math.abs(a) > s ? (n[i] = o[i] + Math.sign(a) * s, t = !1) : n[i] = e[i];
|
|
103
104
|
}
|
|
104
|
-
t && (s = void 0), o = { ...n }, await a.write(
|
|
105
|
+
t && (s = void 0), o = { ...n }, await a.write(g(n)), t || await new Promise((e) => setTimeout(e, r));
|
|
105
106
|
}
|
|
106
107
|
})(), await c;
|
|
107
108
|
}
|
|
108
|
-
async function
|
|
109
|
+
async function p(e, t) {
|
|
109
110
|
let n = c;
|
|
110
111
|
c = (async () => {
|
|
111
112
|
try {
|
|
@@ -114,33 +115,33 @@ async function v(e, t = u, n) {
|
|
|
114
115
|
let c = { ...o }, l = r > 0 ? Math.max(1, Math.round(t / r)) : 1;
|
|
115
116
|
s = void 0;
|
|
116
117
|
for (let t = 1; t <= l; t++) {
|
|
117
|
-
let n =
|
|
118
|
+
let n = v(t / l, i), s = { ...o };
|
|
118
119
|
for (let t of Object.keys(e)) s[t] = Math.round(c[t] + (e[t] - c[t]) * n);
|
|
119
|
-
o = s, await a.write(
|
|
120
|
+
o = s, await a.write(g(s)), t < l && await new Promise((e) => setTimeout(e, r));
|
|
120
121
|
}
|
|
121
122
|
})(), await c;
|
|
122
123
|
}
|
|
123
124
|
return {
|
|
124
125
|
async home(e) {
|
|
125
|
-
e != null && e > 0 ? await
|
|
126
|
+
e != null && e > 0 ? await p({ ...f }, e) : (l({ ...f }), await u());
|
|
126
127
|
},
|
|
127
128
|
async shiftHeadTo(e, t) {
|
|
128
|
-
t != null && t > 0 ? await
|
|
129
|
+
t != null && t > 0 ? await p({ s1: e }, t) : (l({ s1: e }), await u());
|
|
129
130
|
},
|
|
130
131
|
async rollHeadTo(e, t) {
|
|
131
|
-
t != null && t > 0 ? await
|
|
132
|
+
t != null && t > 0 ? await p({ s2: e }, t) : (l({ s2: e }), await u());
|
|
132
133
|
},
|
|
133
134
|
async pitchHeadTo(e, t) {
|
|
134
|
-
t != null && t > 0 ? await
|
|
135
|
+
t != null && t > 0 ? await p({ s3: e }, t) : (l({ s3: e }), await u());
|
|
135
136
|
},
|
|
136
137
|
async liftHeadTo(e, t) {
|
|
137
|
-
t != null && t > 0 ? await
|
|
138
|
+
t != null && t > 0 ? await p({ s5: e }, t) : (l({ s5: e }), await u());
|
|
138
139
|
},
|
|
139
140
|
async rotateBodyTo(e, t) {
|
|
140
|
-
t != null && t > 0 ? await
|
|
141
|
+
t != null && t > 0 ? await p({ s4: e }, t) : (l({ s4: e }), await u());
|
|
141
142
|
},
|
|
142
143
|
async moveTo(e, t) {
|
|
143
|
-
t != null && t > 0 ? await
|
|
144
|
+
t != null && t > 0 ? await p(e, t) : (l(e), await u());
|
|
144
145
|
},
|
|
145
146
|
get State() {
|
|
146
147
|
return structuredClone(s ?? o);
|
|
@@ -152,79 +153,97 @@ async function v(e, t = u, n) {
|
|
|
152
153
|
};
|
|
153
154
|
},
|
|
154
155
|
async sendServoState() {
|
|
155
|
-
await
|
|
156
|
+
await u();
|
|
156
157
|
},
|
|
157
158
|
destroy() {
|
|
158
159
|
a.destroy();
|
|
159
160
|
}
|
|
160
161
|
};
|
|
161
162
|
}
|
|
162
|
-
async function
|
|
163
|
+
async function b(e, t) {
|
|
163
164
|
let n = t.split("\n");
|
|
164
165
|
for (let t = 0; t < n.length; t++) {
|
|
165
166
|
let r = n[t].trim(), i = t + 1;
|
|
166
167
|
if (r === "" || r.startsWith("#")) continue;
|
|
167
168
|
let a = r.split(/\s+/), o = a[0].toLowerCase();
|
|
168
169
|
switch (!0) {
|
|
169
|
-
case o === "home":
|
|
170
|
-
|
|
170
|
+
case o === "home": {
|
|
171
|
+
let t = a[1] == null ? void 0 : Number(a[1]);
|
|
172
|
+
if (t != null && isNaN(t)) throw Error(`line ${i}: home: within_ms must be a number, got '${a[1]}'`);
|
|
173
|
+
await e.home(t);
|
|
171
174
|
break;
|
|
175
|
+
}
|
|
172
176
|
case o === "shift-to": {
|
|
173
177
|
let t = Number(a[1]);
|
|
174
178
|
if (isNaN(t)) throw Error(`line ${i}: shift-to requires a numeric angle, got '${a[1]}'`);
|
|
175
|
-
|
|
179
|
+
let n = a[2] == null ? void 0 : Number(a[2]);
|
|
180
|
+
if (n != null && isNaN(n)) throw Error(`line ${i}: shift-to: within_ms must be a number, got '${a[2]}'`);
|
|
181
|
+
await e.shiftHeadTo(t, n);
|
|
176
182
|
break;
|
|
177
183
|
}
|
|
178
184
|
case o === "roll-to": {
|
|
179
185
|
let t = Number(a[1]);
|
|
180
186
|
if (isNaN(t)) throw Error(`line ${i}: roll-to requires a numeric angle, got '${a[1]}'`);
|
|
181
|
-
|
|
187
|
+
let n = a[2] == null ? void 0 : Number(a[2]);
|
|
188
|
+
if (n != null && isNaN(n)) throw Error(`line ${i}: roll-to: within_ms must be a number, got '${a[2]}'`);
|
|
189
|
+
await e.rollHeadTo(t, n);
|
|
182
190
|
break;
|
|
183
191
|
}
|
|
184
192
|
case o === "pitch-to": {
|
|
185
193
|
let t = Number(a[1]);
|
|
186
194
|
if (isNaN(t)) throw Error(`line ${i}: pitch-to requires a numeric angle, got '${a[1]}'`);
|
|
187
|
-
|
|
195
|
+
let n = a[2] == null ? void 0 : Number(a[2]);
|
|
196
|
+
if (n != null && isNaN(n)) throw Error(`line ${i}: pitch-to: within_ms must be a number, got '${a[2]}'`);
|
|
197
|
+
await e.pitchHeadTo(t, n);
|
|
188
198
|
break;
|
|
189
199
|
}
|
|
190
200
|
case o === "rotate-to": {
|
|
191
201
|
let t = Number(a[1]);
|
|
192
202
|
if (isNaN(t)) throw Error(`line ${i}: rotate-to requires a numeric angle, got '${a[1]}'`);
|
|
193
|
-
|
|
203
|
+
let n = a[2] == null ? void 0 : Number(a[2]);
|
|
204
|
+
if (n != null && isNaN(n)) throw Error(`line ${i}: rotate-to: within_ms must be a number, got '${a[2]}'`);
|
|
205
|
+
await e.rotateBodyTo(t, n);
|
|
194
206
|
break;
|
|
195
207
|
}
|
|
196
208
|
case o === "lift-to": {
|
|
197
209
|
let t = Number(a[1]);
|
|
198
210
|
if (isNaN(t)) throw Error(`line ${i}: lift-to requires a numeric angle, got '${a[1]}'`);
|
|
199
|
-
|
|
211
|
+
let n = a[2] == null ? void 0 : Number(a[2]);
|
|
212
|
+
if (n != null && isNaN(n)) throw Error(`line ${i}: lift-to: within_ms must be a number, got '${a[2]}'`);
|
|
213
|
+
await e.liftHeadTo(t, n);
|
|
200
214
|
break;
|
|
201
215
|
}
|
|
202
216
|
case o === "move": {
|
|
203
|
-
let t = {};
|
|
217
|
+
let t = {}, n;
|
|
204
218
|
for (let e = 1; e < a.length; e += 2) {
|
|
205
|
-
let
|
|
206
|
-
if (
|
|
207
|
-
|
|
219
|
+
let r = a[e].toLowerCase(), o = Number(a[e + 1]);
|
|
220
|
+
if (r === "within-ms") {
|
|
221
|
+
if (isNaN(o)) throw Error(`line ${i}: within-ms requires a numeric value, got '${a[e + 1]}'`);
|
|
222
|
+
n = o;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
if (isNaN(o)) throw Error(`line ${i}: '${r}' requires a numeric angle, got '${a[e + 1]}'`);
|
|
226
|
+
switch (r) {
|
|
208
227
|
case "shift-to":
|
|
209
|
-
t.s1 =
|
|
228
|
+
t.s1 = o;
|
|
210
229
|
break;
|
|
211
230
|
case "roll-to":
|
|
212
|
-
t.s2 =
|
|
231
|
+
t.s2 = o;
|
|
213
232
|
break;
|
|
214
233
|
case "pitch-to":
|
|
215
|
-
t.s3 =
|
|
234
|
+
t.s3 = o;
|
|
216
235
|
break;
|
|
217
236
|
case "rotate-to":
|
|
218
|
-
t.s4 =
|
|
237
|
+
t.s4 = o;
|
|
219
238
|
break;
|
|
220
239
|
case "lift-to":
|
|
221
|
-
t.s5 =
|
|
240
|
+
t.s5 = o;
|
|
222
241
|
break;
|
|
223
|
-
default: throw Error(`line ${i}: unknown move argument '${
|
|
242
|
+
default: throw Error(`line ${i}: unknown move argument '${r}'`);
|
|
224
243
|
}
|
|
225
244
|
}
|
|
226
245
|
if (Object.keys(t).length === 0) throw Error(`line ${i}: move requires at least one servo argument`);
|
|
227
|
-
e.
|
|
246
|
+
await e.moveTo(t, n);
|
|
228
247
|
break;
|
|
229
248
|
}
|
|
230
249
|
case o === "wait": {
|
|
@@ -239,7 +258,7 @@ async function y(e, t) {
|
|
|
239
258
|
}
|
|
240
259
|
//#endregion
|
|
241
260
|
//#region src/nova-control-mcp-server.ts
|
|
242
|
-
function
|
|
261
|
+
function x() {
|
|
243
262
|
try {
|
|
244
263
|
let { values: e } = r({
|
|
245
264
|
args: process.argv.slice(2),
|
|
@@ -276,20 +295,20 @@ function b() {
|
|
|
276
295
|
process.stderr.write(`nova-control-mcp: ${e.message ?? e}\n`), process.exit(1);
|
|
277
296
|
}
|
|
278
297
|
}
|
|
279
|
-
var
|
|
280
|
-
async function
|
|
281
|
-
return
|
|
298
|
+
var S = "", C = 9600, w;
|
|
299
|
+
async function T() {
|
|
300
|
+
return w ??= await y(S, C), w;
|
|
282
301
|
}
|
|
283
|
-
function
|
|
284
|
-
|
|
302
|
+
function E() {
|
|
303
|
+
w != null && (w.destroy(), w = void 0);
|
|
285
304
|
}
|
|
286
|
-
function
|
|
287
|
-
|
|
305
|
+
function D(e, t = 9600) {
|
|
306
|
+
S = e, C = t;
|
|
288
307
|
}
|
|
289
|
-
function
|
|
290
|
-
|
|
308
|
+
function O() {
|
|
309
|
+
E(), S = "", C = 9600;
|
|
291
310
|
}
|
|
292
|
-
var
|
|
311
|
+
var k = [
|
|
293
312
|
{
|
|
294
313
|
name: "home",
|
|
295
314
|
description: "send all servos to their home positions — pass within_ms for smooth, fluid motion with automatic velocity ramp-up and ramp-down; without within_ms the robot moves at constant maximum speed",
|
|
@@ -480,7 +499,7 @@ var O = [
|
|
|
480
499
|
},
|
|
481
500
|
{
|
|
482
501
|
name: "run_script",
|
|
483
|
-
description: "execute a multi-line movement script — one command per line; blank lines and lines starting with # are ignored; commands: home | shift-to <angle> | roll-to <angle> | pitch-to <angle> | rotate-to <angle> | lift-to <angle> | move [shift-to <angle>] [roll-to <angle>] [pitch-to <angle>] [rotate-to <angle>] [lift-to <angle>] | wait <ms>",
|
|
502
|
+
description: "execute a multi-line movement script — one command per line; blank lines and lines starting with # are ignored; each command is fully awaited before the next begins; commands: home [<within_ms>] | shift-to <angle> [<within_ms>] | roll-to <angle> [<within_ms>] | pitch-to <angle> [<within_ms>] | rotate-to <angle> [<within_ms>] | lift-to <angle> [<within_ms>] | move [shift-to <angle>] [roll-to <angle>] [pitch-to <angle>] [rotate-to <angle>] [lift-to <angle>] [within-ms <ms>] | wait <ms>",
|
|
484
503
|
inputSchema: {
|
|
485
504
|
type: "object",
|
|
486
505
|
properties: { script: {
|
|
@@ -498,105 +517,134 @@ var O = [
|
|
|
498
517
|
properties: {}
|
|
499
518
|
}
|
|
500
519
|
}
|
|
501
|
-
]
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
520
|
+
], A = l.number().finite(), j = l.number().positive(), M = l.object({ within_ms: j.optional() }), N = l.object({
|
|
521
|
+
angle: A,
|
|
522
|
+
within_ms: j.optional()
|
|
523
|
+
}), P = l.object({
|
|
524
|
+
shift_to: A.optional(),
|
|
525
|
+
roll_to: A.optional(),
|
|
526
|
+
pitch_to: A.optional(),
|
|
527
|
+
rotate_to: A.optional(),
|
|
528
|
+
lift_to: A.optional(),
|
|
529
|
+
within_ms: j.optional()
|
|
530
|
+
}).superRefine((e, t) => {
|
|
531
|
+
e.shift_to == null && e.roll_to == null && e.pitch_to == null && e.rotate_to == null && e.lift_to == null && t.addIssue({
|
|
532
|
+
code: l.ZodIssueCode.custom,
|
|
533
|
+
message: "move: at least one of shift_to, roll_to, pitch_to, rotate_to, lift_to is required"
|
|
534
|
+
});
|
|
535
|
+
}), F = l.object({
|
|
536
|
+
within_ms: j,
|
|
537
|
+
s1: A.optional(),
|
|
538
|
+
s2: A.optional(),
|
|
539
|
+
s3: A.optional(),
|
|
540
|
+
s4: A.optional(),
|
|
541
|
+
s5: A.optional()
|
|
542
|
+
}).superRefine((e, t) => {
|
|
543
|
+
e.s1 == null && e.s2 == null && e.s3 == null && e.s4 == null && e.s5 == null && t.addIssue({
|
|
544
|
+
code: l.ZodIssueCode.custom,
|
|
545
|
+
message: "move_to: at least one servo target (s1–s5) must be specified"
|
|
546
|
+
});
|
|
547
|
+
}), I = l.object({ ms: l.number().finite().nonnegative() }), L = l.object({ script: l.string() });
|
|
548
|
+
function R(e) {
|
|
549
|
+
return e.issues.map((e) => `${e.path.length > 0 ? `${e.path.join(".")}: ` : ""}${e.message}`).join("; ");
|
|
505
550
|
}
|
|
506
|
-
async function
|
|
507
|
-
let t =
|
|
508
|
-
|
|
509
|
-
let n = e.within_ms == null ? void 0 : Number(e.within_ms);
|
|
510
|
-
return await (await w()).moveTo(t, n), `servos updated: ${JSON.stringify(t)}`;
|
|
551
|
+
async function z(e) {
|
|
552
|
+
let { within_ms: t } = M.parse(e);
|
|
553
|
+
return await (await T()).home(t), "all servos moved to home positions";
|
|
511
554
|
}
|
|
512
|
-
async function
|
|
513
|
-
let t =
|
|
514
|
-
return
|
|
555
|
+
async function B(e) {
|
|
556
|
+
let t = P.parse(e), n = {};
|
|
557
|
+
return t.shift_to != null && (n.s1 = t.shift_to), t.roll_to != null && (n.s2 = t.roll_to), t.pitch_to != null && (n.s3 = t.pitch_to), t.rotate_to != null && (n.s4 = t.rotate_to), t.lift_to != null && (n.s5 = t.lift_to), await (await T()).moveTo(n, t.within_ms), `servos updated: ${JSON.stringify(n)}`;
|
|
515
558
|
}
|
|
516
|
-
async function
|
|
517
|
-
let
|
|
518
|
-
return await (await
|
|
559
|
+
async function V(e) {
|
|
560
|
+
let { angle: t, within_ms: n } = N.parse(e);
|
|
561
|
+
return await (await T()).shiftHeadTo(t, n), `s1 (shift) → ${t}°`;
|
|
519
562
|
}
|
|
520
|
-
async function
|
|
521
|
-
let
|
|
522
|
-
return await (await
|
|
563
|
+
async function H(e) {
|
|
564
|
+
let { angle: t, within_ms: n } = N.parse(e);
|
|
565
|
+
return await (await T()).rollHeadTo(t, n), `s2 (roll) → ${t}°`;
|
|
523
566
|
}
|
|
524
|
-
async function
|
|
525
|
-
let
|
|
526
|
-
return await (await
|
|
567
|
+
async function U(e) {
|
|
568
|
+
let { angle: t, within_ms: n } = N.parse(e);
|
|
569
|
+
return await (await T()).pitchHeadTo(t, n), `s3 (pitch) → ${t}°`;
|
|
527
570
|
}
|
|
528
|
-
async function
|
|
529
|
-
let
|
|
530
|
-
return await (await
|
|
571
|
+
async function W(e) {
|
|
572
|
+
let { angle: t, within_ms: n } = N.parse(e);
|
|
573
|
+
return await (await T()).rotateBodyTo(t, n), `s4 (rotate) → ${t}°`;
|
|
531
574
|
}
|
|
532
|
-
async function
|
|
533
|
-
|
|
575
|
+
async function G(e) {
|
|
576
|
+
let { angle: t, within_ms: n } = N.parse(e);
|
|
577
|
+
return await (await T()).liftHeadTo(t, n), `s5 (lift) → ${t}°`;
|
|
534
578
|
}
|
|
535
|
-
async function
|
|
536
|
-
|
|
537
|
-
if (isNaN(t) || t <= 0) throw Error("move_to: within_ms must be a positive number");
|
|
538
|
-
let n = {};
|
|
539
|
-
if (e.s1 != null && (n.s1 = Number(e.s1)), e.s2 != null && (n.s2 = Number(e.s2)), e.s3 != null && (n.s3 = Number(e.s3)), e.s4 != null && (n.s4 = Number(e.s4)), e.s5 != null && (n.s5 = Number(e.s5)), Object.keys(n).length === 0) throw Error("move_to: at least one servo target (s1–s5) must be specified");
|
|
540
|
-
return await (await w()).moveTo(n, t), "move completed";
|
|
579
|
+
async function K() {
|
|
580
|
+
return w == null ? "not connected" : (E(), "disconnected");
|
|
541
581
|
}
|
|
542
|
-
async function
|
|
543
|
-
let t =
|
|
544
|
-
|
|
545
|
-
return await new Promise((e) => setTimeout(e, t)), `waited ${t} ms`;
|
|
582
|
+
async function q(e) {
|
|
583
|
+
let t = F.parse(e), n = {};
|
|
584
|
+
return t.s1 != null && (n.s1 = t.s1), t.s2 != null && (n.s2 = t.s2), t.s3 != null && (n.s3 = t.s3), t.s4 != null && (n.s4 = t.s4), t.s5 != null && (n.s5 = t.s5), await (await T()).moveTo(n, t.within_ms), "move completed";
|
|
546
585
|
}
|
|
547
|
-
async function
|
|
548
|
-
let
|
|
586
|
+
async function J(e) {
|
|
587
|
+
let t;
|
|
588
|
+
try {
|
|
589
|
+
t = I.parse(e);
|
|
590
|
+
} catch {
|
|
591
|
+
throw Error(`wait: invalid duration '${e.ms}' — expected a non-negative number`);
|
|
592
|
+
}
|
|
593
|
+
return await new Promise((e) => setTimeout(e, t.ms)), `waited ${t.ms} ms`;
|
|
594
|
+
}
|
|
595
|
+
async function Y() {
|
|
596
|
+
let e = await T();
|
|
549
597
|
return JSON.stringify(e.State);
|
|
550
598
|
}
|
|
551
|
-
async function
|
|
552
|
-
let t =
|
|
553
|
-
return await
|
|
599
|
+
async function X(e) {
|
|
600
|
+
let { script: t } = L.parse(e);
|
|
601
|
+
return await b(await T(), t), "script executed successfully";
|
|
554
602
|
}
|
|
555
|
-
function
|
|
603
|
+
function Z() {
|
|
556
604
|
let e = new i({
|
|
557
605
|
name: "nova-control-mcp-server",
|
|
558
|
-
version: "0.0.
|
|
606
|
+
version: "0.0.8"
|
|
559
607
|
}, { capabilities: { tools: {} } });
|
|
560
|
-
return e.setRequestHandler(c, async () => ({ tools:
|
|
608
|
+
return e.setRequestHandler(c, async () => ({ tools: k })), e.setRequestHandler(s, async (e) => {
|
|
561
609
|
let t = e.params.name, n = e.params.arguments ?? {};
|
|
562
610
|
try {
|
|
563
611
|
let e;
|
|
564
612
|
switch (t) {
|
|
565
613
|
case "home":
|
|
566
|
-
e = await
|
|
614
|
+
e = await z(n);
|
|
567
615
|
break;
|
|
568
616
|
case "move":
|
|
569
|
-
e = await
|
|
617
|
+
e = await B(n);
|
|
570
618
|
break;
|
|
571
619
|
case "shift_to":
|
|
572
|
-
e = await
|
|
620
|
+
e = await V(n);
|
|
573
621
|
break;
|
|
574
622
|
case "roll_to":
|
|
575
|
-
e = await
|
|
623
|
+
e = await H(n);
|
|
576
624
|
break;
|
|
577
625
|
case "pitch_to":
|
|
578
|
-
e = await
|
|
626
|
+
e = await U(n);
|
|
579
627
|
break;
|
|
580
628
|
case "rotate_to":
|
|
581
|
-
e = await
|
|
629
|
+
e = await W(n);
|
|
582
630
|
break;
|
|
583
631
|
case "lift_to":
|
|
584
|
-
e = await
|
|
632
|
+
e = await G(n);
|
|
585
633
|
break;
|
|
586
634
|
case "move_to":
|
|
587
|
-
e = await
|
|
635
|
+
e = await q(n);
|
|
588
636
|
break;
|
|
589
637
|
case "wait":
|
|
590
|
-
e = await
|
|
638
|
+
e = await J(n);
|
|
591
639
|
break;
|
|
592
640
|
case "get_state":
|
|
593
|
-
e = await
|
|
641
|
+
e = await Y();
|
|
594
642
|
break;
|
|
595
643
|
case "run_script":
|
|
596
|
-
e = await
|
|
644
|
+
e = await X(n);
|
|
597
645
|
break;
|
|
598
646
|
case "disconnect":
|
|
599
|
-
e = await
|
|
647
|
+
e = await K();
|
|
600
648
|
break;
|
|
601
649
|
default: return {
|
|
602
650
|
content: [{
|
|
@@ -614,21 +662,21 @@ function V() {
|
|
|
614
662
|
return {
|
|
615
663
|
content: [{
|
|
616
664
|
type: "text",
|
|
617
|
-
text: e instanceof Error ? e.message : String(e)
|
|
665
|
+
text: e instanceof l.ZodError ? R(e) : e instanceof Error ? e.message : String(e)
|
|
618
666
|
}],
|
|
619
667
|
isError: !0
|
|
620
668
|
};
|
|
621
669
|
}
|
|
622
670
|
}), e;
|
|
623
671
|
}
|
|
624
|
-
async function
|
|
672
|
+
async function Q(e) {
|
|
625
673
|
let t = new a();
|
|
626
674
|
await e.connect(t);
|
|
627
675
|
for (let e of ["SIGINT", "SIGTERM"]) process.on(e, () => {
|
|
628
|
-
|
|
676
|
+
E(), process.exit(0);
|
|
629
677
|
});
|
|
630
678
|
}
|
|
631
|
-
async function
|
|
679
|
+
async function $(e, t) {
|
|
632
680
|
let r = new o({ sessionIdGenerator: void 0 });
|
|
633
681
|
await e.connect(r);
|
|
634
682
|
let i = n(async (e, t) => {
|
|
@@ -640,17 +688,17 @@ async function U(e, t) {
|
|
|
640
688
|
}), i.once("error", n);
|
|
641
689
|
});
|
|
642
690
|
for (let e of ["SIGINT", "SIGTERM"]) process.on(e, async () => {
|
|
643
|
-
await r.close(), i.close(),
|
|
691
|
+
await r.close(), i.close(), E(), process.exit(0);
|
|
644
692
|
});
|
|
645
693
|
}
|
|
646
|
-
async function
|
|
647
|
-
let { Port: e, BaudRate: t, Transport: n, ListenPort: r } =
|
|
648
|
-
|
|
649
|
-
let i =
|
|
650
|
-
n === "http" ? await
|
|
694
|
+
async function ee() {
|
|
695
|
+
let { Port: e, BaudRate: t, Transport: n, ListenPort: r } = x();
|
|
696
|
+
S = e, C = t;
|
|
697
|
+
let i = Z();
|
|
698
|
+
n === "http" ? await $(i, r) : await Q(i);
|
|
651
699
|
}
|
|
652
|
-
t(process.argv[1]) === e(import.meta.url) &&
|
|
700
|
+
t(process.argv[1]) === e(import.meta.url) && ee().catch((e) => {
|
|
653
701
|
process.stderr.write(`nova-control-mcp: fatal: ${e.message ?? e}\n`), process.exit(1);
|
|
654
702
|
});
|
|
655
703
|
//#endregion
|
|
656
|
-
export {
|
|
704
|
+
export { O as _destroyForTests, D as _setupForTests, Z as createServer };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nova-control-mcp-server",
|
|
3
3
|
"description": "MCP server for controlling a NOVA DIY Artificial Intelligence Robot by Creoqode",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.9",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"nova",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
33
|
-
"nova-control-node": "*"
|
|
33
|
+
"nova-control-node": "*",
|
|
34
|
+
"zod": "^4.3.6"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/node": "^22.0.0",
|