nova-control-mcp-server 0.0.5 → 0.0.7

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 CHANGED
@@ -113,21 +113,24 @@ Point your MCP client at `http://<host>:3000/mcp`. For Claude Desktop with a rem
113
113
 
114
114
  ## Tools
115
115
 
116
- The server exposes the following tools. The serial connection is opened lazily on the first tool call and kept alive for the lifetime of the server process.
116
+ The server exposes the following tools. The serial connection is opened lazily on the first tool call; call `disconnect` to free the port, or let the server exit cleanly.
117
+
118
+ All movement tools accept an optional `within_ms` parameter. When provided, the robot moves smoothly to the target using a trapezoidal velocity profile (ramp-up → constant speed → ramp-down) over the given number of milliseconds. Without `within_ms`, the servos move at constant maximum speed.
117
119
 
118
120
  | tool | parameters | description |
119
121
  |---|---|---|
120
- | `home` | | send all servos to their home positions |
121
- | `move` | `shift_to?`, `roll_to?`, `pitch_to?`, `rotate_to?`, `lift_to?` (all `number`, at least one required) | set one or more servo positions atomically |
122
- | `move_to` | `within_ms: number` (required), `s1?`, `s2?`, `s3?`, `s4?`, `s5?` (all optional `number`) | move one or more servos to target positions in exactly `within_ms` milliseconds, using a trapezoidal ramp-up/ramp-down profile |
123
- | `shift_to` | `deg: number` | shift head forward (>90°) or back (<90°) — s1 |
124
- | `roll_to` | `deg: number` | roll head clockwise (>90°) or counter-clockwise (<90°) — s2 |
125
- | `pitch_to` | `deg: number` | pitch head up (>110°) or down (<110°) — s3 |
126
- | `rotate_to` | `deg: number` | rotate body around Z-axis — s4 |
127
- | `lift_to` | `deg: number` | lift head on secondary axis, range 20°–150° — s5 |
122
+ | `home` | `within_ms?` | send all servos to their home positions |
123
+ | `move` | `shift_to?`, `roll_to?`, `pitch_to?`, `rotate_to?`, `lift_to?` (at least one required); `within_ms?` | set one or more servo positions atomically |
124
+ | `move_to` | `within_ms: number` (required, must be > 0); `s1?`, `s2?`, `s3?`, `s4?`, `s5?` | move one or more servos smoothly to target positions using a trapezoidal ramp-up/ramp-down profile |
125
+ | `shift_to` | `angle: number`; `within_ms?` | shift head forward (>90°) or back (<90°) — s1 |
126
+ | `roll_to` | `angle: number`; `within_ms?` | roll head clockwise (>90°) or counter-clockwise (<90°) — s2 |
127
+ | `pitch_to` | `angle: number`; `within_ms?` | pitch head up (>110°) or down (<110°) — s3 |
128
+ | `rotate_to` | `angle: number`; `within_ms?` | rotate body around Z-axis — s4 |
129
+ | `lift_to` | `angle: number`; `within_ms?` | lift head on secondary axis, range 20°–150° — s5 |
128
130
  | `wait` | `ms: number` | pause for `ms` milliseconds before the next action |
129
131
  | `get_state` | — | return current servo positions as a JSON object with keys `s1`–`s5` |
130
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`) |
133
+ | `disconnect` | — | close the serial connection to free the port; the connection reopens automatically on the next movement command |
131
134
 
132
135
  ### Servo mapping
133
136
 
@@ -143,12 +146,13 @@ The server exposes the following tools. The serial connection is opened lazily o
143
146
 
144
147
  Once the server is running inside Claude Desktop you can give natural-language instructions like:
145
148
 
146
- - *"Move NOVA's head to look straight up."* → `pitch_to(deg: 130)`
147
- - *"Rotate the body 45° to the right."* → `rotate_to(deg: 45)`
148
- - *"Move the head to 120° over 800 ms."* → `move_to(within_ms: 800, s1: 120)`
149
- - *"Shift to 100°, wait half a second, then return to home."* → `shift_to(100)` + `wait(500)` + `home()`
149
+ - *"Move NOVA's head to look straight up."* → `pitch_to(angle: 130)`
150
+ - *"Rotate the body 45° to the right."* → `rotate_to(angle: 45)`
151
+ - *"Move the head to 120° smoothly over 800 ms."* → `move_to(within_ms: 800, s1: 120)`
152
+ - *"Shift to 100° with a fluid ramp, wait half a second, then return to home."* → `shift_to(angle: 100, within_ms: 600)` + `wait(500)` + `home()`
150
153
  - *"What is the current servo state?"* → `get_state()`
151
154
  - *"Run the greeting sequence from this script."* → `run_script(script: "home\nwait 500\nshift-to 110\nwait 400\nhome")`
155
+ - *"Free the serial port."* → `disconnect()`
152
156
 
153
157
  ## Exit behaviour
154
158
 
@@ -7,9 +7,239 @@ 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 { openNova as l, runScript as u } from "nova-control-node";
10
+ import { SerialPort as l } from "serialport";
11
+ //#region ../nova-control-node/dist/nova-control-node.js
12
+ var u = 9600, d = Object.freeze({
13
+ s1: 90,
14
+ s2: 90,
15
+ s3: 110,
16
+ s4: 90,
17
+ s5: 95
18
+ }), f = Object.freeze({
19
+ s1: [45, 135],
20
+ s2: [10, 170],
21
+ s3: [40, 150],
22
+ s4: [30, 180],
23
+ s5: [20, 150]
24
+ }), p = Object.freeze({
25
+ s1: (f.s1[1] - f.s1[0]) / 1e3,
26
+ s2: (f.s2[1] - f.s2[0]) / 1e3,
27
+ s3: (f.s3[1] - f.s3[0]) / 1e3,
28
+ s4: (f.s4[1] - f.s4[0]) / 1e3,
29
+ s5: (f.s5[1] - f.s5[0]) / 1e3
30
+ });
31
+ function m(e, t) {
32
+ let [n, r] = f[t];
33
+ return Math.max(n, Math.min(r, Math.round(e)));
34
+ }
35
+ function h(e) {
36
+ return new Uint8Array([
37
+ m(e.s4, "s4"),
38
+ m(e.s3, "s3"),
39
+ m(e.s2, "s2"),
40
+ m(e.s1, "s1"),
41
+ m(e.s5, "s5")
42
+ ]);
43
+ }
44
+ async function g(e, t) {
45
+ let n = new l({
46
+ path: e,
47
+ baudRate: t,
48
+ autoOpen: !1
49
+ });
50
+ return await new Promise((e, t) => {
51
+ n.open((n) => {
52
+ n == null ? e() : t(n);
53
+ });
54
+ }), await new Promise((e) => setTimeout(e, 2e3)), {
55
+ async write(e) {
56
+ await new Promise((t, r) => {
57
+ n.write(Buffer.from(e), (e) => {
58
+ e == null ? t() : r(e);
59
+ });
60
+ }), await new Promise((e, t) => {
61
+ n.drain((n) => {
62
+ n == null ? e() : t(n);
63
+ });
64
+ });
65
+ },
66
+ destroy() {
67
+ n.close();
68
+ }
69
+ };
70
+ }
71
+ function _(e, t) {
72
+ let n = Math.min(.499, Math.max(0, t)), r = 1 / (1 - n);
73
+ if (e <= n) return r * e * e / (2 * n);
74
+ if (e <= 1 - n) return r * (e - n / 2);
75
+ let i = 1 - e;
76
+ return 1 - r * i * i / (2 * n);
77
+ }
78
+ async function v(e, t = u, n) {
79
+ let r = n?.StepIntervalMs ?? 20, i = n?.RampRatio ?? .25, a = await g(e, t), o = { ...d }, s, c = Promise.resolve();
80
+ function l(e) {
81
+ s = {
82
+ ...s ?? o,
83
+ ...e
84
+ };
85
+ }
86
+ async function f() {
87
+ let e = c;
88
+ c = (async () => {
89
+ try {
90
+ await e;
91
+ } catch {}
92
+ for (; s != null;) {
93
+ let e = s, t = !0, n = { ...o };
94
+ for (let i of [
95
+ "s1",
96
+ "s2",
97
+ "s3",
98
+ "s4",
99
+ "s5"
100
+ ]) {
101
+ let a = e[i] - o[i], s = r > 0 ? p[i] * r : Infinity;
102
+ Math.abs(a) > s ? (n[i] = o[i] + Math.sign(a) * s, t = !1) : n[i] = e[i];
103
+ }
104
+ t && (s = void 0), o = { ...n }, await a.write(h(n)), t || await new Promise((e) => setTimeout(e, r));
105
+ }
106
+ })(), await c;
107
+ }
108
+ async function m(e, t) {
109
+ let n = c;
110
+ c = (async () => {
111
+ try {
112
+ await n;
113
+ } catch {}
114
+ let c = { ...o }, l = r > 0 ? Math.max(1, Math.round(t / r)) : 1;
115
+ s = void 0;
116
+ for (let t = 1; t <= l; t++) {
117
+ let n = _(t / l, i), s = { ...o };
118
+ for (let t of Object.keys(e)) s[t] = Math.round(c[t] + (e[t] - c[t]) * n);
119
+ o = s, await a.write(h(s)), t < l && await new Promise((e) => setTimeout(e, r));
120
+ }
121
+ })(), await c;
122
+ }
123
+ return {
124
+ async home(e) {
125
+ e != null && e > 0 ? await m({ ...d }, e) : (l({ ...d }), await f());
126
+ },
127
+ async shiftHeadTo(e, t) {
128
+ t != null && t > 0 ? await m({ s1: e }, t) : (l({ s1: e }), await f());
129
+ },
130
+ async rollHeadTo(e, t) {
131
+ t != null && t > 0 ? await m({ s2: e }, t) : (l({ s2: e }), await f());
132
+ },
133
+ async pitchHeadTo(e, t) {
134
+ t != null && t > 0 ? await m({ s3: e }, t) : (l({ s3: e }), await f());
135
+ },
136
+ async liftHeadTo(e, t) {
137
+ t != null && t > 0 ? await m({ s5: e }, t) : (l({ s5: e }), await f());
138
+ },
139
+ async rotateBodyTo(e, t) {
140
+ t != null && t > 0 ? await m({ s4: e }, t) : (l({ s4: e }), await f());
141
+ },
142
+ async moveTo(e, t) {
143
+ t != null && t > 0 ? await m(e, t) : (l(e), await f());
144
+ },
145
+ get State() {
146
+ return structuredClone(s ?? o);
147
+ },
148
+ set State(e) {
149
+ s = {
150
+ ...o,
151
+ ...e
152
+ };
153
+ },
154
+ async sendServoState() {
155
+ await f();
156
+ },
157
+ destroy() {
158
+ a.destroy();
159
+ }
160
+ };
161
+ }
162
+ async function y(e, t) {
163
+ let n = t.split("\n");
164
+ for (let t = 0; t < n.length; t++) {
165
+ let r = n[t].trim(), i = t + 1;
166
+ if (r === "" || r.startsWith("#")) continue;
167
+ let a = r.split(/\s+/), o = a[0].toLowerCase();
168
+ switch (!0) {
169
+ case o === "home":
170
+ await e.home();
171
+ break;
172
+ case o === "shift-to": {
173
+ let t = Number(a[1]);
174
+ if (isNaN(t)) throw Error(`line ${i}: shift-to requires a numeric angle, got '${a[1]}'`);
175
+ await e.shiftHeadTo(t);
176
+ break;
177
+ }
178
+ case o === "roll-to": {
179
+ let t = Number(a[1]);
180
+ if (isNaN(t)) throw Error(`line ${i}: roll-to requires a numeric angle, got '${a[1]}'`);
181
+ await e.rollHeadTo(t);
182
+ break;
183
+ }
184
+ case o === "pitch-to": {
185
+ let t = Number(a[1]);
186
+ if (isNaN(t)) throw Error(`line ${i}: pitch-to requires a numeric angle, got '${a[1]}'`);
187
+ await e.pitchHeadTo(t);
188
+ break;
189
+ }
190
+ case o === "rotate-to": {
191
+ let t = Number(a[1]);
192
+ if (isNaN(t)) throw Error(`line ${i}: rotate-to requires a numeric angle, got '${a[1]}'`);
193
+ await e.rotateBodyTo(t);
194
+ break;
195
+ }
196
+ case o === "lift-to": {
197
+ let t = Number(a[1]);
198
+ if (isNaN(t)) throw Error(`line ${i}: lift-to requires a numeric angle, got '${a[1]}'`);
199
+ await e.liftHeadTo(t);
200
+ break;
201
+ }
202
+ case o === "move": {
203
+ let t = {};
204
+ for (let e = 1; e < a.length; e += 2) {
205
+ let n = a[e].toLowerCase(), r = Number(a[e + 1]);
206
+ if (isNaN(r)) throw Error(`line ${i}: '${n}' requires a numeric angle, got '${a[e + 1]}'`);
207
+ switch (n) {
208
+ case "shift-to":
209
+ t.s1 = r;
210
+ break;
211
+ case "roll-to":
212
+ t.s2 = r;
213
+ break;
214
+ case "pitch-to":
215
+ t.s3 = r;
216
+ break;
217
+ case "rotate-to":
218
+ t.s4 = r;
219
+ break;
220
+ case "lift-to":
221
+ t.s5 = r;
222
+ break;
223
+ default: throw Error(`line ${i}: unknown move argument '${n}'`);
224
+ }
225
+ }
226
+ if (Object.keys(t).length === 0) throw Error(`line ${i}: move requires at least one servo argument`);
227
+ e.State = t, await e.sendServoState();
228
+ break;
229
+ }
230
+ case o === "wait": {
231
+ let e = Number(a[1]);
232
+ if (isNaN(e) || e < 0) throw Error(`line ${i}: wait requires a non-negative number in ms, got '${a[1]}'`);
233
+ await new Promise((t) => setTimeout(t, e));
234
+ break;
235
+ }
236
+ default: throw Error(`line ${i}: unknown command '${o}'`);
237
+ }
238
+ }
239
+ }
240
+ //#endregion
11
241
  //#region src/nova-control-mcp-server.ts
12
- function d() {
242
+ function b() {
13
243
  try {
14
244
  let { values: e } = r({
15
245
  args: process.argv.slice(2),
@@ -46,34 +276,41 @@ function d() {
46
276
  process.stderr.write(`nova-control-mcp: ${e.message ?? e}\n`), process.exit(1);
47
277
  }
48
278
  }
49
- var f = "", p = 9600, m;
50
- async function h() {
51
- return m ??= await l(f, p), m;
279
+ var x = "", S = 9600, C;
280
+ async function w() {
281
+ return C ??= await v(x, S), C;
52
282
  }
53
- function g() {
54
- m != null && (m.destroy(), m = void 0);
283
+ function T() {
284
+ C != null && (C.destroy(), C = void 0);
55
285
  }
56
- function _(e, t = 9600) {
57
- f = e, p = t;
286
+ function E(e, t = 9600) {
287
+ x = e, S = t;
58
288
  }
59
- function v() {
60
- g(), f = "", p = 9600;
289
+ function D() {
290
+ T(), x = "", S = 9600;
61
291
  }
62
- var y = [
292
+ var O = [
63
293
  {
64
294
  name: "home",
65
- description: "send all servos to their home positions",
295
+ 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",
66
296
  inputSchema: {
67
297
  type: "object",
68
- properties: {}
298
+ properties: { within_ms: {
299
+ type: "number",
300
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down — servos glide gradually to the target instead of jumping; omit for constant-speed movement"
301
+ } }
69
302
  }
70
303
  },
71
304
  {
72
305
  name: "move",
73
- description: "set one or more servo positions atomically — at least one of shift_to, roll_to, pitch_to, rotate_to, lift_to is required",
306
+ description: "set one or more servo positions atomically — at least one of shift_to, roll_to, pitch_to, rotate_to, lift_to is required; pass within_ms for smooth, fluid motion with automatic velocity ramp-up and ramp-down",
74
307
  inputSchema: {
75
308
  type: "object",
76
309
  properties: {
310
+ within_ms: {
311
+ type: "number",
312
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down — servos glide gradually to their targets instead of jumping; omit for constant-speed movement"
313
+ },
77
314
  shift_to: {
78
315
  type: "number",
79
316
  description: "shift head forward (>90°) or back (<90°) — s1"
@@ -99,61 +336,91 @@ var y = [
99
336
  },
100
337
  {
101
338
  name: "shift_to",
102
- description: "shift head forward (>90°) or back (<90°) — s1",
339
+ description: "shift head forward (>90°) or back (<90°) — s1; pass within_ms for smooth motion with trapezoidal velocity profile (ramp-up → constant speed → ramp-down)",
103
340
  inputSchema: {
104
341
  type: "object",
105
- properties: { angle: {
106
- type: "number",
107
- description: "target angle in degrees"
108
- } },
342
+ properties: {
343
+ angle: {
344
+ type: "number",
345
+ description: "target angle in degrees"
346
+ },
347
+ within_ms: {
348
+ type: "number",
349
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
350
+ }
351
+ },
109
352
  required: ["angle"]
110
353
  }
111
354
  },
112
355
  {
113
356
  name: "roll_to",
114
- description: "roll head clockwise (>90°) or counter-clockwise (<90°) — s2",
357
+ description: "roll head clockwise (>90°) or counter-clockwise (<90°) — s2; pass within_ms for smooth motion with trapezoidal velocity profile",
115
358
  inputSchema: {
116
359
  type: "object",
117
- properties: { angle: {
118
- type: "number",
119
- description: "target angle in degrees"
120
- } },
360
+ properties: {
361
+ angle: {
362
+ type: "number",
363
+ description: "target angle in degrees"
364
+ },
365
+ within_ms: {
366
+ type: "number",
367
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
368
+ }
369
+ },
121
370
  required: ["angle"]
122
371
  }
123
372
  },
124
373
  {
125
374
  name: "pitch_to",
126
- description: "pitch head up (>110°) or down (<110°) — s3",
375
+ description: "pitch head up (>110°) or down (<110°) — s3; pass within_ms for smooth motion with trapezoidal velocity profile",
127
376
  inputSchema: {
128
377
  type: "object",
129
- properties: { angle: {
130
- type: "number",
131
- description: "target angle in degrees"
132
- } },
378
+ properties: {
379
+ angle: {
380
+ type: "number",
381
+ description: "target angle in degrees"
382
+ },
383
+ within_ms: {
384
+ type: "number",
385
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
386
+ }
387
+ },
133
388
  required: ["angle"]
134
389
  }
135
390
  },
136
391
  {
137
392
  name: "rotate_to",
138
- description: "rotate body around Z-axis — s4",
393
+ description: "rotate body around Z-axis — s4; pass within_ms for smooth motion with trapezoidal velocity profile",
139
394
  inputSchema: {
140
395
  type: "object",
141
- properties: { angle: {
142
- type: "number",
143
- description: "target angle in degrees"
144
- } },
396
+ properties: {
397
+ angle: {
398
+ type: "number",
399
+ description: "target angle in degrees"
400
+ },
401
+ within_ms: {
402
+ type: "number",
403
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
404
+ }
405
+ },
145
406
  required: ["angle"]
146
407
  }
147
408
  },
148
409
  {
149
410
  name: "lift_to",
150
- description: "lift head on secondary axis, range 20°–150° — s5",
411
+ description: "lift head on secondary axis, range 20°–150° — s5; pass within_ms for smooth motion with trapezoidal velocity profile",
151
412
  inputSchema: {
152
413
  type: "object",
153
- properties: { angle: {
154
- type: "number",
155
- description: "target angle in degrees"
156
- } },
414
+ properties: {
415
+ angle: {
416
+ type: "number",
417
+ description: "target angle in degrees"
418
+ },
419
+ within_ms: {
420
+ type: "number",
421
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
422
+ }
423
+ },
157
424
  required: ["angle"]
158
425
  }
159
426
  },
@@ -213,7 +480,7 @@ var y = [
213
480
  },
214
481
  {
215
482
  name: "run_script",
216
- description: "execute a multi-line movement script — one command per line; blank lines and lines starting with # are ignored; commands: home | shift-to <deg> | roll-to <deg> | pitch-to <deg> | rotate-to <deg> | lift-to <deg> | move [shift-to <deg>] [roll-to <deg>] [pitch-to <deg>] [rotate-to <deg>] [lift-to <deg>] | wait <ms>",
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>",
217
484
  inputSchema: {
218
485
  type: "object",
219
486
  properties: { script: {
@@ -222,99 +489,114 @@ var y = [
222
489
  } },
223
490
  required: ["script"]
224
491
  }
492
+ },
493
+ {
494
+ name: "disconnect",
495
+ description: "close the serial connection to the robot — call this when you are done to free the serial port; the connection reopens automatically on the first subsequent movement command",
496
+ inputSchema: {
497
+ type: "object",
498
+ properties: {}
499
+ }
225
500
  }
226
501
  ];
227
- async function b() {
228
- return await (await h()).home(), "all servos moved to home positions";
502
+ async function k(e) {
503
+ let t = e.within_ms == null ? void 0 : Number(e.within_ms);
504
+ return await (await w()).home(t), "all servos moved to home positions";
229
505
  }
230
- async function x(e) {
506
+ async function A(e) {
231
507
  let t = {};
232
508
  if (e.shift_to != null && (t.s1 = Number(e.shift_to)), e.roll_to != null && (t.s2 = Number(e.roll_to)), e.pitch_to != null && (t.s3 = Number(e.pitch_to)), e.rotate_to != null && (t.s4 = Number(e.rotate_to)), e.lift_to != null && (t.s5 = Number(e.lift_to)), Object.keys(t).length === 0) throw Error("move: at least one of shift_to, roll_to, pitch_to, rotate_to, lift_to is required");
233
- let n = await h();
234
- return n.State = t, await n.sendServoState(), `servos updated: ${JSON.stringify(t)}`;
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)}`;
511
+ }
512
+ async function j(e) {
513
+ let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
514
+ return await (await w()).shiftHeadTo(t, n), `s1 (shift) → ${t}°`;
235
515
  }
236
- async function S(e) {
237
- let t = Number(e.angle), n = await h();
238
- return n.State = { s1: t }, await n.sendServoState(), `s1 (shift) → ${t}°`;
516
+ async function M(e) {
517
+ let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
518
+ return await (await w()).rollHeadTo(t, n), `s2 (roll) → ${t}°`;
239
519
  }
240
- async function C(e) {
241
- let t = Number(e.angle), n = await h();
242
- return n.State = { s2: t }, await n.sendServoState(), `s2 (roll) → ${t}°`;
520
+ async function N(e) {
521
+ let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
522
+ return await (await w()).pitchHeadTo(t, n), `s3 (pitch) → ${t}°`;
243
523
  }
244
- async function w(e) {
245
- let t = Number(e.angle), n = await h();
246
- return n.State = { s3: t }, await n.sendServoState(), `s3 (pitch) → ${t}°`;
524
+ async function P(e) {
525
+ let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
526
+ return await (await w()).rotateBodyTo(t, n), `s4 (rotate) → ${t}°`;
247
527
  }
248
- async function T(e) {
249
- let t = Number(e.angle), n = await h();
250
- return n.State = { s4: t }, await n.sendServoState(), `s4 (rotate) → ${t}°`;
528
+ async function F(e) {
529
+ let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
530
+ return await (await w()).liftHeadTo(t, n), `s5 (lift) → ${t}°`;
251
531
  }
252
- async function E(e) {
253
- let t = Number(e.angle), n = await h();
254
- return n.State = { s5: t }, await n.sendServoState(), `s5 (lift) → ${t}°`;
532
+ async function I() {
533
+ return C == null ? "not connected" : (T(), "disconnected");
255
534
  }
256
- async function D(e) {
535
+ async function L(e) {
257
536
  let t = Number(e.within_ms);
258
537
  if (isNaN(t) || t <= 0) throw Error("move_to: within_ms must be a positive number");
259
538
  let n = {};
260
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");
261
- return await (await h()).moveTo(n, t), "move completed";
540
+ return await (await w()).moveTo(n, t), "move completed";
262
541
  }
263
- async function O(e) {
542
+ async function R(e) {
264
543
  let t = Number(e.ms);
265
544
  if (isNaN(t) || t < 0) throw Error(`wait: invalid duration '${e.ms}' — expected a non-negative number`);
266
545
  return await new Promise((e) => setTimeout(e, t)), `waited ${t} ms`;
267
546
  }
268
- async function k() {
269
- let e = await h();
547
+ async function z() {
548
+ let e = await w();
270
549
  return JSON.stringify(e.State);
271
550
  }
272
- async function A(e) {
551
+ async function B(e) {
273
552
  let t = String(e.script ?? "");
274
- return await u(await h(), t), "script executed successfully";
553
+ return await y(await w(), t), "script executed successfully";
275
554
  }
276
- function j() {
555
+ function V() {
277
556
  let e = new i({
278
557
  name: "nova-control-mcp-server",
279
- version: "0.0.5"
558
+ version: "0.0.7"
280
559
  }, { capabilities: { tools: {} } });
281
- return e.setRequestHandler(c, async () => ({ tools: y })), e.setRequestHandler(s, async (e) => {
560
+ return e.setRequestHandler(c, async () => ({ tools: O })), e.setRequestHandler(s, async (e) => {
282
561
  let t = e.params.name, n = e.params.arguments ?? {};
283
562
  try {
284
563
  let e;
285
564
  switch (t) {
286
565
  case "home":
287
- e = await b();
566
+ e = await k(n);
288
567
  break;
289
568
  case "move":
290
- e = await x(n);
569
+ e = await A(n);
291
570
  break;
292
571
  case "shift_to":
293
- e = await S(n);
572
+ e = await j(n);
294
573
  break;
295
574
  case "roll_to":
296
- e = await C(n);
575
+ e = await M(n);
297
576
  break;
298
577
  case "pitch_to":
299
- e = await w(n);
578
+ e = await N(n);
300
579
  break;
301
580
  case "rotate_to":
302
- e = await T(n);
581
+ e = await P(n);
303
582
  break;
304
583
  case "lift_to":
305
- e = await E(n);
584
+ e = await F(n);
306
585
  break;
307
586
  case "move_to":
308
- e = await D(n);
587
+ e = await L(n);
309
588
  break;
310
589
  case "wait":
311
- e = await O(n);
590
+ e = await R(n);
312
591
  break;
313
592
  case "get_state":
314
- e = await k();
593
+ e = await z();
315
594
  break;
316
595
  case "run_script":
317
- e = await A(n);
596
+ e = await B(n);
597
+ break;
598
+ case "disconnect":
599
+ e = await I();
318
600
  break;
319
601
  default: return {
320
602
  content: [{
@@ -339,14 +621,14 @@ function j() {
339
621
  }
340
622
  }), e;
341
623
  }
342
- async function M(e) {
624
+ async function H(e) {
343
625
  let t = new a();
344
626
  await e.connect(t);
345
627
  for (let e of ["SIGINT", "SIGTERM"]) process.on(e, () => {
346
- g(), process.exit(0);
628
+ T(), process.exit(0);
347
629
  });
348
630
  }
349
- async function N(e, t) {
631
+ async function U(e, t) {
350
632
  let r = new o({ sessionIdGenerator: void 0 });
351
633
  await e.connect(r);
352
634
  let i = n(async (e, t) => {
@@ -358,17 +640,17 @@ async function N(e, t) {
358
640
  }), i.once("error", n);
359
641
  });
360
642
  for (let e of ["SIGINT", "SIGTERM"]) process.on(e, async () => {
361
- await r.close(), i.close(), g(), process.exit(0);
643
+ await r.close(), i.close(), T(), process.exit(0);
362
644
  });
363
645
  }
364
- async function P() {
365
- let { Port: e, BaudRate: t, Transport: n, ListenPort: r } = d();
366
- f = e, p = t;
367
- let i = j();
368
- n === "http" ? await N(i, r) : await M(i);
646
+ async function W() {
647
+ let { Port: e, BaudRate: t, Transport: n, ListenPort: r } = b();
648
+ x = e, S = t;
649
+ let i = V();
650
+ n === "http" ? await U(i, r) : await H(i);
369
651
  }
370
- t(process.argv[1]) === e(import.meta.url) && P().catch((e) => {
652
+ t(process.argv[1]) === e(import.meta.url) && W().catch((e) => {
371
653
  process.stderr.write(`nova-control-mcp: fatal: ${e.message ?? e}\n`), process.exit(1);
372
654
  });
373
655
  //#endregion
374
- export { v as _destroyForTests, _ as _setupForTests, j as createServer };
656
+ export { D as _destroyForTests, E as _setupForTests, V 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.5",
4
+ "version": "0.0.7",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "nova",