nova-control-mcp-server 0.0.6 → 0.0.8

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.
@@ -7,9 +7,257 @@ 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
+ let t = a[1] == null ? void 0 : Number(a[1]);
171
+ if (t != null && isNaN(t)) throw Error(`line ${i}: home: within_ms must be a number, got '${a[1]}'`);
172
+ await e.home(t);
173
+ break;
174
+ }
175
+ case o === "shift-to": {
176
+ let t = Number(a[1]);
177
+ if (isNaN(t)) throw Error(`line ${i}: shift-to requires a numeric angle, got '${a[1]}'`);
178
+ let n = a[2] == null ? void 0 : Number(a[2]);
179
+ if (n != null && isNaN(n)) throw Error(`line ${i}: shift-to: within_ms must be a number, got '${a[2]}'`);
180
+ await e.shiftHeadTo(t, n);
181
+ break;
182
+ }
183
+ case o === "roll-to": {
184
+ let t = Number(a[1]);
185
+ if (isNaN(t)) throw Error(`line ${i}: roll-to requires a numeric angle, got '${a[1]}'`);
186
+ let n = a[2] == null ? void 0 : Number(a[2]);
187
+ if (n != null && isNaN(n)) throw Error(`line ${i}: roll-to: within_ms must be a number, got '${a[2]}'`);
188
+ await e.rollHeadTo(t, n);
189
+ break;
190
+ }
191
+ case o === "pitch-to": {
192
+ let t = Number(a[1]);
193
+ if (isNaN(t)) throw Error(`line ${i}: pitch-to requires a numeric angle, got '${a[1]}'`);
194
+ let n = a[2] == null ? void 0 : Number(a[2]);
195
+ if (n != null && isNaN(n)) throw Error(`line ${i}: pitch-to: within_ms must be a number, got '${a[2]}'`);
196
+ await e.pitchHeadTo(t, n);
197
+ break;
198
+ }
199
+ case o === "rotate-to": {
200
+ let t = Number(a[1]);
201
+ if (isNaN(t)) throw Error(`line ${i}: rotate-to requires a numeric angle, got '${a[1]}'`);
202
+ let n = a[2] == null ? void 0 : Number(a[2]);
203
+ if (n != null && isNaN(n)) throw Error(`line ${i}: rotate-to: within_ms must be a number, got '${a[2]}'`);
204
+ await e.rotateBodyTo(t, n);
205
+ break;
206
+ }
207
+ case o === "lift-to": {
208
+ let t = Number(a[1]);
209
+ if (isNaN(t)) throw Error(`line ${i}: lift-to requires a numeric angle, got '${a[1]}'`);
210
+ let n = a[2] == null ? void 0 : Number(a[2]);
211
+ if (n != null && isNaN(n)) throw Error(`line ${i}: lift-to: within_ms must be a number, got '${a[2]}'`);
212
+ await e.liftHeadTo(t, n);
213
+ break;
214
+ }
215
+ case o === "move": {
216
+ let t = {}, n;
217
+ for (let e = 1; e < a.length; e += 2) {
218
+ let r = a[e].toLowerCase(), o = Number(a[e + 1]);
219
+ if (r === "within-ms") {
220
+ if (isNaN(o)) throw Error(`line ${i}: within-ms requires a numeric value, got '${a[e + 1]}'`);
221
+ n = o;
222
+ break;
223
+ }
224
+ if (isNaN(o)) throw Error(`line ${i}: '${r}' requires a numeric angle, got '${a[e + 1]}'`);
225
+ switch (r) {
226
+ case "shift-to":
227
+ t.s1 = o;
228
+ break;
229
+ case "roll-to":
230
+ t.s2 = o;
231
+ break;
232
+ case "pitch-to":
233
+ t.s3 = o;
234
+ break;
235
+ case "rotate-to":
236
+ t.s4 = o;
237
+ break;
238
+ case "lift-to":
239
+ t.s5 = o;
240
+ break;
241
+ default: throw Error(`line ${i}: unknown move argument '${r}'`);
242
+ }
243
+ }
244
+ if (Object.keys(t).length === 0) throw Error(`line ${i}: move requires at least one servo argument`);
245
+ await e.moveTo(t, n);
246
+ break;
247
+ }
248
+ case o === "wait": {
249
+ let e = Number(a[1]);
250
+ if (isNaN(e) || e < 0) throw Error(`line ${i}: wait requires a non-negative number in ms, got '${a[1]}'`);
251
+ await new Promise((t) => setTimeout(t, e));
252
+ break;
253
+ }
254
+ default: throw Error(`line ${i}: unknown command '${o}'`);
255
+ }
256
+ }
257
+ }
258
+ //#endregion
11
259
  //#region src/nova-control-mcp-server.ts
12
- function d() {
260
+ function b() {
13
261
  try {
14
262
  let { values: e } = r({
15
263
  args: process.argv.slice(2),
@@ -46,20 +294,20 @@ function d() {
46
294
  process.stderr.write(`nova-control-mcp: ${e.message ?? e}\n`), process.exit(1);
47
295
  }
48
296
  }
49
- var f = "", p = 9600, m;
50
- async function h() {
51
- return m ??= await l(f, p), m;
297
+ var x = "", S = 9600, C;
298
+ async function w() {
299
+ return C ??= await v(x, S), C;
52
300
  }
53
- function g() {
54
- m != null && (m.destroy(), m = void 0);
301
+ function T() {
302
+ C != null && (C.destroy(), C = void 0);
55
303
  }
56
- function _(e, t = 9600) {
57
- f = e, p = t;
304
+ function E(e, t = 9600) {
305
+ x = e, S = t;
58
306
  }
59
- function v() {
60
- g(), f = "", p = 9600;
307
+ function D() {
308
+ T(), x = "", S = 9600;
61
309
  }
62
- var y = [
310
+ var O = [
63
311
  {
64
312
  name: "home",
65
313
  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",
@@ -269,104 +517,104 @@ var y = [
269
517
  }
270
518
  }
271
519
  ];
272
- async function b(e) {
520
+ async function k(e) {
273
521
  let t = e.within_ms == null ? void 0 : Number(e.within_ms);
274
- return await (await h()).home(t), "all servos moved to home positions";
522
+ return await (await w()).home(t), "all servos moved to home positions";
275
523
  }
276
- async function x(e) {
524
+ async function A(e) {
277
525
  let t = {};
278
526
  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");
279
527
  let n = e.within_ms == null ? void 0 : Number(e.within_ms);
280
- return await (await h()).moveTo(t, n), `servos updated: ${JSON.stringify(t)}`;
528
+ return await (await w()).moveTo(t, n), `servos updated: ${JSON.stringify(t)}`;
281
529
  }
282
- async function S(e) {
530
+ async function j(e) {
283
531
  let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
284
- return await (await h()).shiftHeadTo(t, n), `s1 (shift) → ${t}°`;
532
+ return await (await w()).shiftHeadTo(t, n), `s1 (shift) → ${t}°`;
285
533
  }
286
- async function C(e) {
534
+ async function M(e) {
287
535
  let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
288
- return await (await h()).rollHeadTo(t, n), `s2 (roll) → ${t}°`;
536
+ return await (await w()).rollHeadTo(t, n), `s2 (roll) → ${t}°`;
289
537
  }
290
- async function w(e) {
538
+ async function N(e) {
291
539
  let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
292
- return await (await h()).pitchHeadTo(t, n), `s3 (pitch) → ${t}°`;
540
+ return await (await w()).pitchHeadTo(t, n), `s3 (pitch) → ${t}°`;
293
541
  }
294
- async function T(e) {
542
+ async function P(e) {
295
543
  let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
296
- return await (await h()).rotateBodyTo(t, n), `s4 (rotate) → ${t}°`;
544
+ return await (await w()).rotateBodyTo(t, n), `s4 (rotate) → ${t}°`;
297
545
  }
298
- async function E(e) {
546
+ async function F(e) {
299
547
  let t = Number(e.angle), n = e.within_ms == null ? void 0 : Number(e.within_ms);
300
- return await (await h()).liftHeadTo(t, n), `s5 (lift) → ${t}°`;
548
+ return await (await w()).liftHeadTo(t, n), `s5 (lift) → ${t}°`;
301
549
  }
302
- async function D() {
303
- return m == null ? "not connected" : (g(), "disconnected");
550
+ async function I() {
551
+ return C == null ? "not connected" : (T(), "disconnected");
304
552
  }
305
- async function O(e) {
553
+ async function L(e) {
306
554
  let t = Number(e.within_ms);
307
555
  if (isNaN(t) || t <= 0) throw Error("move_to: within_ms must be a positive number");
308
556
  let n = {};
309
557
  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");
310
- return await (await h()).moveTo(n, t), "move completed";
558
+ return await (await w()).moveTo(n, t), "move completed";
311
559
  }
312
- async function k(e) {
560
+ async function R(e) {
313
561
  let t = Number(e.ms);
314
562
  if (isNaN(t) || t < 0) throw Error(`wait: invalid duration '${e.ms}' — expected a non-negative number`);
315
563
  return await new Promise((e) => setTimeout(e, t)), `waited ${t} ms`;
316
564
  }
317
- async function A() {
318
- let e = await h();
565
+ async function z() {
566
+ let e = await w();
319
567
  return JSON.stringify(e.State);
320
568
  }
321
- async function j(e) {
569
+ async function B(e) {
322
570
  let t = String(e.script ?? "");
323
- return await u(await h(), t), "script executed successfully";
571
+ return await y(await w(), t), "script executed successfully";
324
572
  }
325
- function M() {
573
+ function V() {
326
574
  let e = new i({
327
575
  name: "nova-control-mcp-server",
328
- version: "0.0.6"
576
+ version: "0.0.8"
329
577
  }, { capabilities: { tools: {} } });
330
- return e.setRequestHandler(c, async () => ({ tools: y })), e.setRequestHandler(s, async (e) => {
578
+ return e.setRequestHandler(c, async () => ({ tools: O })), e.setRequestHandler(s, async (e) => {
331
579
  let t = e.params.name, n = e.params.arguments ?? {};
332
580
  try {
333
581
  let e;
334
582
  switch (t) {
335
583
  case "home":
336
- e = await b(n);
584
+ e = await k(n);
337
585
  break;
338
586
  case "move":
339
- e = await x(n);
587
+ e = await A(n);
340
588
  break;
341
589
  case "shift_to":
342
- e = await S(n);
590
+ e = await j(n);
343
591
  break;
344
592
  case "roll_to":
345
- e = await C(n);
593
+ e = await M(n);
346
594
  break;
347
595
  case "pitch_to":
348
- e = await w(n);
596
+ e = await N(n);
349
597
  break;
350
598
  case "rotate_to":
351
- e = await T(n);
599
+ e = await P(n);
352
600
  break;
353
601
  case "lift_to":
354
- e = await E(n);
602
+ e = await F(n);
355
603
  break;
356
604
  case "move_to":
357
- e = await O(n);
605
+ e = await L(n);
358
606
  break;
359
607
  case "wait":
360
- e = await k(n);
608
+ e = await R(n);
361
609
  break;
362
610
  case "get_state":
363
- e = await A();
611
+ e = await z();
364
612
  break;
365
613
  case "run_script":
366
- e = await j(n);
614
+ e = await B(n);
367
615
  break;
368
616
  case "disconnect":
369
- e = await D();
617
+ e = await I();
370
618
  break;
371
619
  default: return {
372
620
  content: [{
@@ -391,14 +639,14 @@ function M() {
391
639
  }
392
640
  }), e;
393
641
  }
394
- async function N(e) {
642
+ async function H(e) {
395
643
  let t = new a();
396
644
  await e.connect(t);
397
645
  for (let e of ["SIGINT", "SIGTERM"]) process.on(e, () => {
398
- g(), process.exit(0);
646
+ T(), process.exit(0);
399
647
  });
400
648
  }
401
- async function P(e, t) {
649
+ async function U(e, t) {
402
650
  let r = new o({ sessionIdGenerator: void 0 });
403
651
  await e.connect(r);
404
652
  let i = n(async (e, t) => {
@@ -410,17 +658,17 @@ async function P(e, t) {
410
658
  }), i.once("error", n);
411
659
  });
412
660
  for (let e of ["SIGINT", "SIGTERM"]) process.on(e, async () => {
413
- await r.close(), i.close(), g(), process.exit(0);
661
+ await r.close(), i.close(), T(), process.exit(0);
414
662
  });
415
663
  }
416
- async function F() {
417
- let { Port: e, BaudRate: t, Transport: n, ListenPort: r } = d();
418
- f = e, p = t;
419
- let i = M();
420
- n === "http" ? await P(i, r) : await N(i);
664
+ async function W() {
665
+ let { Port: e, BaudRate: t, Transport: n, ListenPort: r } = b();
666
+ x = e, S = t;
667
+ let i = V();
668
+ n === "http" ? await U(i, r) : await H(i);
421
669
  }
422
- t(process.argv[1]) === e(import.meta.url) && F().catch((e) => {
670
+ t(process.argv[1]) === e(import.meta.url) && W().catch((e) => {
423
671
  process.stderr.write(`nova-control-mcp: fatal: ${e.message ?? e}\n`), process.exit(1);
424
672
  });
425
673
  //#endregion
426
- export { v as _destroyForTests, _ as _setupForTests, M as createServer };
674
+ 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.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "nova",