nova-control-mcp-server 0.0.4 → 0.0.6

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
@@ -1,6 +1,6 @@
1
1
  # nova-control-mcp-server
2
2
 
3
- An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server for controlling the [NOVA DIY Artificial Intelligence Robot](https://www.creoqode.com/nova) by Creoqode. It exposes the same servo commands as [nova-control-command](../nova-control-command) as MCP tools, allowing any MCP-capable AI assistant (Claude Desktop, Cursor, …) to control the robot arm directly.
3
+ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server for controlling the [NOVA DIY Artificial Intelligence Robot](https://www.creoqode.com/nova) by Creoqode. It exposes the same servo commands as [nova-control-command](../nova-control-command/README.md) as MCP tools, allowing any MCP-capable AI assistant (Claude Desktop, Cursor, …) to control the robot arm directly.
4
4
 
5
5
  ## Prerequisites
6
6
 
@@ -113,19 +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
- | `shift_to` | `deg: number` | shift head forward (>90°) or back (<90°) s1 |
123
- | `roll_to` | `deg: number` | roll head clockwise (>90°) or counter-clockwise (<90°) — s2 |
124
- | `pitch_to` | `deg: number` | pitch head up (>110°) or down (<110°) — s3 |
125
- | `rotate_to` | `deg: number` | rotate body around Z-axiss4 |
126
- | `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 |
127
130
  | `wait` | `ms: number` | pause for `ms` milliseconds before the next action |
128
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`) |
133
+ | `disconnect` | — | close the serial connection to free the port; the connection reopens automatically on the next movement command |
129
134
 
130
135
  ### Servo mapping
131
136
 
@@ -141,15 +146,26 @@ The server exposes the following tools. The serial connection is opened lazily o
141
146
 
142
147
  Once the server is running inside Claude Desktop you can give natural-language instructions like:
143
148
 
144
- - *"Move NOVA's head to look straight up."* → `pitch_to(deg: 130)`
145
- - *"Rotate the body 45° to the left."* → `rotate_to(deg: 45)`
146
- - *"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()`
147
153
  - *"What is the current servo state?"* → `get_state()`
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()`
148
156
 
149
157
  ## Exit behaviour
150
158
 
151
159
  The server exits cleanly on `SIGINT` (Ctrl+C) or `SIGTERM`, closing the serial connection before quitting.
152
160
 
161
+ ## Related packages
162
+
163
+ | package | description |
164
+ |---|---|
165
+ | [`nova-control-browser`](../nova-control-browser/README.md) | browser ESM module — Web Serial API (Chrome / Edge 89+) |
166
+ | [`nova-control-node`](../nova-control-node/README.md) | Node.js ESM module — `serialport` package (used internally by this server) |
167
+ | [`nova-control-command`](../nova-control-command/README.md) | CLI — one-shot commands, interactive REPL, and script files |
168
+
153
169
  ## License
154
170
 
155
171
  MIT
@@ -7,9 +7,9 @@ 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 } from "nova-control-node";
10
+ import { openNova as l, runScript as u } from "nova-control-node";
11
11
  //#region src/nova-control-mcp-server.ts
12
- function u() {
12
+ function d() {
13
13
  try {
14
14
  let { values: e } = r({
15
15
  args: process.argv.slice(2),
@@ -46,34 +46,41 @@ function u() {
46
46
  process.stderr.write(`nova-control-mcp: ${e.message ?? e}\n`), process.exit(1);
47
47
  }
48
48
  }
49
- var d = "", f = 9600, p;
50
- async function m() {
51
- return p ??= await l(d, f), p;
49
+ var f = "", p = 9600, m;
50
+ async function h() {
51
+ return m ??= await l(f, p), m;
52
52
  }
53
- function h() {
54
- p != null && (p.destroy(), p = void 0);
53
+ function g() {
54
+ m != null && (m.destroy(), m = void 0);
55
55
  }
56
- function g(e, t = 9600) {
57
- d = e, f = t;
56
+ function _(e, t = 9600) {
57
+ f = e, p = t;
58
58
  }
59
- function _() {
60
- h(), d = "", f = 9600;
59
+ function v() {
60
+ g(), f = "", p = 9600;
61
61
  }
62
- var v = [
62
+ var y = [
63
63
  {
64
64
  name: "home",
65
- description: "send all servos to their home positions",
65
+ 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
66
  inputSchema: {
67
67
  type: "object",
68
- properties: {}
68
+ properties: { within_ms: {
69
+ type: "number",
70
+ 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"
71
+ } }
69
72
  }
70
73
  },
71
74
  {
72
75
  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",
76
+ 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
77
  inputSchema: {
75
78
  type: "object",
76
79
  properties: {
80
+ within_ms: {
81
+ type: "number",
82
+ 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"
83
+ },
77
84
  shift_to: {
78
85
  type: "number",
79
86
  description: "shift head forward (>90°) or back (<90°) — s1"
@@ -99,62 +106,126 @@ var v = [
99
106
  },
100
107
  {
101
108
  name: "shift_to",
102
- description: "shift head forward (>90°) or back (<90°) — s1",
109
+ 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
110
  inputSchema: {
104
111
  type: "object",
105
- properties: { deg: {
106
- type: "number",
107
- description: "target angle in degrees"
108
- } },
109
- required: ["deg"]
112
+ properties: {
113
+ angle: {
114
+ type: "number",
115
+ description: "target angle in degrees"
116
+ },
117
+ within_ms: {
118
+ type: "number",
119
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
120
+ }
121
+ },
122
+ required: ["angle"]
110
123
  }
111
124
  },
112
125
  {
113
126
  name: "roll_to",
114
- description: "roll head clockwise (>90°) or counter-clockwise (<90°) — s2",
127
+ description: "roll head clockwise (>90°) or counter-clockwise (<90°) — s2; pass within_ms for smooth motion with trapezoidal velocity profile",
115
128
  inputSchema: {
116
129
  type: "object",
117
- properties: { deg: {
118
- type: "number",
119
- description: "target angle in degrees"
120
- } },
121
- required: ["deg"]
130
+ properties: {
131
+ angle: {
132
+ type: "number",
133
+ description: "target angle in degrees"
134
+ },
135
+ within_ms: {
136
+ type: "number",
137
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
138
+ }
139
+ },
140
+ required: ["angle"]
122
141
  }
123
142
  },
124
143
  {
125
144
  name: "pitch_to",
126
- description: "pitch head up (>110°) or down (<110°) — s3",
145
+ description: "pitch head up (>110°) or down (<110°) — s3; pass within_ms for smooth motion with trapezoidal velocity profile",
127
146
  inputSchema: {
128
147
  type: "object",
129
- properties: { deg: {
130
- type: "number",
131
- description: "target angle in degrees"
132
- } },
133
- required: ["deg"]
148
+ properties: {
149
+ angle: {
150
+ type: "number",
151
+ description: "target angle in degrees"
152
+ },
153
+ within_ms: {
154
+ type: "number",
155
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
156
+ }
157
+ },
158
+ required: ["angle"]
134
159
  }
135
160
  },
136
161
  {
137
162
  name: "rotate_to",
138
- description: "rotate body around Z-axis — s4",
163
+ description: "rotate body around Z-axis — s4; pass within_ms for smooth motion with trapezoidal velocity profile",
139
164
  inputSchema: {
140
165
  type: "object",
141
- properties: { deg: {
142
- type: "number",
143
- description: "target angle in degrees"
144
- } },
145
- required: ["deg"]
166
+ properties: {
167
+ angle: {
168
+ type: "number",
169
+ description: "target angle in degrees"
170
+ },
171
+ within_ms: {
172
+ type: "number",
173
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
174
+ }
175
+ },
176
+ required: ["angle"]
146
177
  }
147
178
  },
148
179
  {
149
180
  name: "lift_to",
150
- description: "lift head on secondary axis, range 20°–150° — s5",
181
+ description: "lift head on secondary axis, range 20°–150° — s5; pass within_ms for smooth motion with trapezoidal velocity profile",
151
182
  inputSchema: {
152
183
  type: "object",
153
- properties: { deg: {
154
- type: "number",
155
- description: "target angle in degrees"
156
- } },
157
- required: ["deg"]
184
+ properties: {
185
+ angle: {
186
+ type: "number",
187
+ description: "target angle in degrees"
188
+ },
189
+ within_ms: {
190
+ type: "number",
191
+ description: "duration in milliseconds for smooth motion with trapezoidal ramp-up and ramp-down; omit for constant-speed movement"
192
+ }
193
+ },
194
+ required: ["angle"]
195
+ }
196
+ },
197
+ {
198
+ name: "move_to",
199
+ description: "move one or more servos smoothly to their target positions, completing the movement in the specified number of milliseconds using a trapezoidal ramp-up/ramp-down profile",
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: {
203
+ within_ms: {
204
+ type: "number",
205
+ description: "total movement duration in milliseconds (must be > 0)"
206
+ },
207
+ s1: {
208
+ type: "number",
209
+ description: "head shift target angle (optional)"
210
+ },
211
+ s2: {
212
+ type: "number",
213
+ description: "head roll target angle (optional)"
214
+ },
215
+ s3: {
216
+ type: "number",
217
+ description: "head pitch target angle (optional)"
218
+ },
219
+ s4: {
220
+ type: "number",
221
+ description: "body rotate target angle (optional)"
222
+ },
223
+ s5: {
224
+ type: "number",
225
+ description: "head lift target angle (optional)"
226
+ }
227
+ },
228
+ required: ["within_ms"]
158
229
  }
159
230
  },
160
231
  {
@@ -176,81 +247,125 @@ var v = [
176
247
  type: "object",
177
248
  properties: {}
178
249
  }
250
+ },
251
+ {
252
+ name: "run_script",
253
+ 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>",
254
+ inputSchema: {
255
+ type: "object",
256
+ properties: { script: {
257
+ type: "string",
258
+ description: "multi-line movement script"
259
+ } },
260
+ required: ["script"]
261
+ }
262
+ },
263
+ {
264
+ name: "disconnect",
265
+ 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",
266
+ inputSchema: {
267
+ type: "object",
268
+ properties: {}
269
+ }
179
270
  }
180
271
  ];
181
- async function y() {
182
- return await (await m()).home(), "all servos moved to home positions";
183
- }
184
272
  async function b(e) {
185
- let t = {};
186
- 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");
187
- let n = await m();
188
- return n.State = t, await n.sendServoState(), `servos updated: ${JSON.stringify(t)}`;
273
+ 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";
189
275
  }
190
276
  async function x(e) {
191
- let t = Number(e.deg), n = await m();
192
- return n.State = { s1: t }, await n.sendServoState(), `s1 (shift) ${t}°`;
277
+ let t = {};
278
+ 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
+ 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)}`;
193
281
  }
194
282
  async function S(e) {
195
- let t = Number(e.deg), n = await m();
196
- return n.State = { s2: t }, await n.sendServoState(), `s2 (roll) → ${t}°`;
283
+ 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}°`;
197
285
  }
198
286
  async function C(e) {
199
- let t = Number(e.deg), n = await m();
200
- return n.State = { s3: t }, await n.sendServoState(), `s3 (pitch) → ${t}°`;
287
+ 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}°`;
201
289
  }
202
290
  async function w(e) {
203
- let t = Number(e.deg), n = await m();
204
- return n.State = { s4: t }, await n.sendServoState(), `s4 (rotate) → ${t}°`;
291
+ 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}°`;
205
293
  }
206
294
  async function T(e) {
207
- let t = Number(e.deg), n = await m();
208
- return n.State = { s5: t }, await n.sendServoState(), `s5 (lift) → ${t}°`;
295
+ 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}°`;
209
297
  }
210
298
  async function E(e) {
299
+ 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}°`;
301
+ }
302
+ async function D() {
303
+ return m == null ? "not connected" : (g(), "disconnected");
304
+ }
305
+ async function O(e) {
306
+ let t = Number(e.within_ms);
307
+ if (isNaN(t) || t <= 0) throw Error("move_to: within_ms must be a positive number");
308
+ let n = {};
309
+ 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";
311
+ }
312
+ async function k(e) {
211
313
  let t = Number(e.ms);
212
314
  if (isNaN(t) || t < 0) throw Error(`wait: invalid duration '${e.ms}' — expected a non-negative number`);
213
315
  return await new Promise((e) => setTimeout(e, t)), `waited ${t} ms`;
214
316
  }
215
- async function D() {
216
- let e = await m();
317
+ async function A() {
318
+ let e = await h();
217
319
  return JSON.stringify(e.State);
218
320
  }
219
- function O() {
321
+ async function j(e) {
322
+ let t = String(e.script ?? "");
323
+ return await u(await h(), t), "script executed successfully";
324
+ }
325
+ function M() {
220
326
  let e = new i({
221
327
  name: "nova-control-mcp-server",
222
- version: "0.0.4"
328
+ version: "0.0.6"
223
329
  }, { capabilities: { tools: {} } });
224
- return e.setRequestHandler(c, async () => ({ tools: v })), e.setRequestHandler(s, async (e) => {
330
+ return e.setRequestHandler(c, async () => ({ tools: y })), e.setRequestHandler(s, async (e) => {
225
331
  let t = e.params.name, n = e.params.arguments ?? {};
226
332
  try {
227
333
  let e;
228
334
  switch (t) {
229
335
  case "home":
230
- e = await y();
336
+ e = await b(n);
231
337
  break;
232
338
  case "move":
233
- e = await b(n);
339
+ e = await x(n);
234
340
  break;
235
341
  case "shift_to":
236
- e = await x(n);
342
+ e = await S(n);
237
343
  break;
238
344
  case "roll_to":
239
- e = await S(n);
345
+ e = await C(n);
240
346
  break;
241
347
  case "pitch_to":
242
- e = await C(n);
348
+ e = await w(n);
243
349
  break;
244
350
  case "rotate_to":
245
- e = await w(n);
351
+ e = await T(n);
246
352
  break;
247
353
  case "lift_to":
248
- e = await T(n);
354
+ e = await E(n);
355
+ break;
356
+ case "move_to":
357
+ e = await O(n);
249
358
  break;
250
359
  case "wait":
251
- e = await E(n);
360
+ e = await k(n);
252
361
  break;
253
362
  case "get_state":
363
+ e = await A();
364
+ break;
365
+ case "run_script":
366
+ e = await j(n);
367
+ break;
368
+ case "disconnect":
254
369
  e = await D();
255
370
  break;
256
371
  default: return {
@@ -276,14 +391,14 @@ function O() {
276
391
  }
277
392
  }), e;
278
393
  }
279
- async function k(e) {
394
+ async function N(e) {
280
395
  let t = new a();
281
396
  await e.connect(t);
282
397
  for (let e of ["SIGINT", "SIGTERM"]) process.on(e, () => {
283
- h(), process.exit(0);
398
+ g(), process.exit(0);
284
399
  });
285
400
  }
286
- async function A(e, t) {
401
+ async function P(e, t) {
287
402
  let r = new o({ sessionIdGenerator: void 0 });
288
403
  await e.connect(r);
289
404
  let i = n(async (e, t) => {
@@ -295,17 +410,17 @@ async function A(e, t) {
295
410
  }), i.once("error", n);
296
411
  });
297
412
  for (let e of ["SIGINT", "SIGTERM"]) process.on(e, async () => {
298
- await r.close(), i.close(), h(), process.exit(0);
413
+ await r.close(), i.close(), g(), process.exit(0);
299
414
  });
300
415
  }
301
- async function j() {
302
- let { Port: e, BaudRate: t, Transport: n, ListenPort: r } = u();
303
- d = e, f = t;
304
- let i = O();
305
- n === "http" ? await A(i, r) : await k(i);
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);
306
421
  }
307
- t(process.argv[1]) === e(import.meta.url) && j().catch((e) => {
422
+ t(process.argv[1]) === e(import.meta.url) && F().catch((e) => {
308
423
  process.stderr.write(`nova-control-mcp: fatal: ${e.message ?? e}\n`), process.exit(1);
309
424
  });
310
425
  //#endregion
311
- export { _ as _destroyForTests, g as _setupForTests, O as createServer };
426
+ export { v as _destroyForTests, _ as _setupForTests, M 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",
4
+ "version": "0.0.6",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "nova",