nova-control-mcp-server 0.0.3 → 0.0.5
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 +57 -2
- package/dist/nova-control-mcp-server.js +163 -67
- package/package.json +1 -1
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
|
|
|
@@ -67,6 +67,49 @@ Add the same block under `mcpServers` in `~/.cursor/mcp.json`.
|
|
|
67
67
|
|---|---|---|---|
|
|
68
68
|
| `--port <path>` | `-p` | *(required)* | serial port path (e.g. `/dev/ttyACM0`, `COM3`) |
|
|
69
69
|
| `--baud <rate>` | `-b` | `9600` | baud rate |
|
|
70
|
+
| `--transport <mode>` | `-t` | `stdio` | transport: `stdio` or `http` |
|
|
71
|
+
| `--listen <port>` | `-l` | `3000` | HTTP listen port (only used when `--transport http`) |
|
|
72
|
+
|
|
73
|
+
## HTTP transport (Docker / remote)
|
|
74
|
+
|
|
75
|
+
Use `--transport http` to expose the MCP server over HTTP instead of stdio. This is the recommended mode when the server runs in a Docker container or on a remote machine.
|
|
76
|
+
|
|
77
|
+
The server listens on the given port and accepts MCP requests at `POST /mcp`.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
nova-control-mcp-server --port /dev/ttyACM0 --transport http --listen 3000
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Docker example
|
|
84
|
+
|
|
85
|
+
```dockerfile
|
|
86
|
+
FROM node:22-slim
|
|
87
|
+
RUN npm install -g nova-control-mcp-server
|
|
88
|
+
EXPOSE 3000
|
|
89
|
+
CMD ["nova-control-mcp-server", "--port", "/dev/ttyACM0", \
|
|
90
|
+
"--transport", "http", "--listen", "3000"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Run the container with the serial device passed through:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
docker run --device /dev/ttyACM0 -p 3000:3000 nova-mcp
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Connecting an MCP client to the HTTP transport
|
|
100
|
+
|
|
101
|
+
Point your MCP client at `http://<host>:3000/mcp`. For Claude Desktop with a remote server, use the `url` transport type:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"nova-control": {
|
|
107
|
+
"type": "http",
|
|
108
|
+
"url": "http://localhost:3000/mcp"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
70
113
|
|
|
71
114
|
## Tools
|
|
72
115
|
|
|
@@ -76,6 +119,7 @@ The server exposes the following tools. The serial connection is opened lazily o
|
|
|
76
119
|
|---|---|---|
|
|
77
120
|
| `home` | — | send all servos to their home positions |
|
|
78
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 |
|
|
79
123
|
| `shift_to` | `deg: number` | shift head forward (>90°) or back (<90°) — s1 |
|
|
80
124
|
| `roll_to` | `deg: number` | roll head clockwise (>90°) or counter-clockwise (<90°) — s2 |
|
|
81
125
|
| `pitch_to` | `deg: number` | pitch head up (>110°) or down (<110°) — s3 |
|
|
@@ -83,6 +127,7 @@ The server exposes the following tools. The serial connection is opened lazily o
|
|
|
83
127
|
| `lift_to` | `deg: number` | lift head on secondary axis, range 20°–150° — s5 |
|
|
84
128
|
| `wait` | `ms: number` | pause for `ms` milliseconds before the next action |
|
|
85
129
|
| `get_state` | — | return current servo positions as a JSON object with keys `s1`–`s5` |
|
|
130
|
+
| `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`) |
|
|
86
131
|
|
|
87
132
|
### Servo mapping
|
|
88
133
|
|
|
@@ -99,14 +144,24 @@ The server exposes the following tools. The serial connection is opened lazily o
|
|
|
99
144
|
Once the server is running inside Claude Desktop you can give natural-language instructions like:
|
|
100
145
|
|
|
101
146
|
- *"Move NOVA's head to look straight up."* → `pitch_to(deg: 130)`
|
|
102
|
-
- *"Rotate the body 45° to the
|
|
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)`
|
|
103
149
|
- *"Shift to 100°, wait half a second, then return to home."* → `shift_to(100)` + `wait(500)` + `home()`
|
|
104
150
|
- *"What is the current servo state?"* → `get_state()`
|
|
151
|
+
- *"Run the greeting sequence from this script."* → `run_script(script: "home\nwait 500\nshift-to 110\nwait 400\nhome")`
|
|
105
152
|
|
|
106
153
|
## Exit behaviour
|
|
107
154
|
|
|
108
155
|
The server exits cleanly on `SIGINT` (Ctrl+C) or `SIGTERM`, closing the serial connection before quitting.
|
|
109
156
|
|
|
157
|
+
## Related packages
|
|
158
|
+
|
|
159
|
+
| package | description |
|
|
160
|
+
|---|---|
|
|
161
|
+
| [`nova-control-browser`](../nova-control-browser/README.md) | browser ESM module — Web Serial API (Chrome / Edge 89+) |
|
|
162
|
+
| [`nova-control-node`](../nova-control-node/README.md) | Node.js ESM module — `serialport` package (used internally by this server) |
|
|
163
|
+
| [`nova-control-command`](../nova-control-command/README.md) | CLI — one-shot commands, interactive REPL, and script files |
|
|
164
|
+
|
|
110
165
|
## License
|
|
111
166
|
|
|
112
167
|
MIT
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath as e } from "node:url";
|
|
3
3
|
import { realpathSync as t } from "node:fs";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { createServer as n } from "node:http";
|
|
5
|
+
import { parseArgs as r } from "node:util";
|
|
6
|
+
import { Server as i } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport as a } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { StreamableHTTPServerTransport as o } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
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";
|
|
9
11
|
//#region src/nova-control-mcp-server.ts
|
|
10
|
-
function
|
|
12
|
+
function d() {
|
|
11
13
|
try {
|
|
12
|
-
let { values: e } =
|
|
14
|
+
let { values: e } = r({
|
|
13
15
|
args: process.argv.slice(2),
|
|
14
16
|
options: {
|
|
15
17
|
port: {
|
|
@@ -19,33 +21,45 @@ function c() {
|
|
|
19
21
|
baud: {
|
|
20
22
|
type: "string",
|
|
21
23
|
short: "b"
|
|
24
|
+
},
|
|
25
|
+
transport: {
|
|
26
|
+
type: "string",
|
|
27
|
+
short: "t"
|
|
28
|
+
},
|
|
29
|
+
listen: {
|
|
30
|
+
type: "string",
|
|
31
|
+
short: "l"
|
|
22
32
|
}
|
|
23
33
|
},
|
|
24
34
|
strict: !0,
|
|
25
35
|
allowPositionals: !1
|
|
26
36
|
});
|
|
27
|
-
|
|
37
|
+
e.port ?? (process.stderr.write("nova-control-mcp: --port is required\n"), process.exit(1));
|
|
38
|
+
let t = e.transport ?? "stdio";
|
|
39
|
+
return t !== "stdio" && t !== "http" && (process.stderr.write(`nova-control-mcp: --transport must be 'stdio' or 'http', got '${t}'\n`), process.exit(1)), {
|
|
28
40
|
Port: e.port,
|
|
29
|
-
BaudRate: Number(e.baud ?? "9600")
|
|
41
|
+
BaudRate: Number(e.baud ?? "9600"),
|
|
42
|
+
Transport: t,
|
|
43
|
+
ListenPort: Number(e.listen ?? "3000")
|
|
30
44
|
};
|
|
31
45
|
} catch (e) {
|
|
32
46
|
process.stderr.write(`nova-control-mcp: ${e.message ?? e}\n`), process.exit(1);
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
|
-
var
|
|
36
|
-
async function
|
|
37
|
-
return
|
|
49
|
+
var f = "", p = 9600, m;
|
|
50
|
+
async function h() {
|
|
51
|
+
return m ??= await l(f, p), m;
|
|
38
52
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
53
|
+
function g() {
|
|
54
|
+
m != null && (m.destroy(), m = void 0);
|
|
41
55
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
56
|
+
function _(e, t = 9600) {
|
|
57
|
+
f = e, p = t;
|
|
44
58
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
59
|
+
function v() {
|
|
60
|
+
g(), f = "", p = 9600;
|
|
47
61
|
}
|
|
48
|
-
var
|
|
62
|
+
var y = [
|
|
49
63
|
{
|
|
50
64
|
name: "home",
|
|
51
65
|
description: "send all servos to their home positions",
|
|
@@ -88,11 +102,11 @@ var g = [
|
|
|
88
102
|
description: "shift head forward (>90°) or back (<90°) — s1",
|
|
89
103
|
inputSchema: {
|
|
90
104
|
type: "object",
|
|
91
|
-
properties: {
|
|
105
|
+
properties: { angle: {
|
|
92
106
|
type: "number",
|
|
93
107
|
description: "target angle in degrees"
|
|
94
108
|
} },
|
|
95
|
-
required: ["
|
|
109
|
+
required: ["angle"]
|
|
96
110
|
}
|
|
97
111
|
},
|
|
98
112
|
{
|
|
@@ -100,11 +114,11 @@ var g = [
|
|
|
100
114
|
description: "roll head clockwise (>90°) or counter-clockwise (<90°) — s2",
|
|
101
115
|
inputSchema: {
|
|
102
116
|
type: "object",
|
|
103
|
-
properties: {
|
|
117
|
+
properties: { angle: {
|
|
104
118
|
type: "number",
|
|
105
119
|
description: "target angle in degrees"
|
|
106
120
|
} },
|
|
107
|
-
required: ["
|
|
121
|
+
required: ["angle"]
|
|
108
122
|
}
|
|
109
123
|
},
|
|
110
124
|
{
|
|
@@ -112,11 +126,11 @@ var g = [
|
|
|
112
126
|
description: "pitch head up (>110°) or down (<110°) — s3",
|
|
113
127
|
inputSchema: {
|
|
114
128
|
type: "object",
|
|
115
|
-
properties: {
|
|
129
|
+
properties: { angle: {
|
|
116
130
|
type: "number",
|
|
117
131
|
description: "target angle in degrees"
|
|
118
132
|
} },
|
|
119
|
-
required: ["
|
|
133
|
+
required: ["angle"]
|
|
120
134
|
}
|
|
121
135
|
},
|
|
122
136
|
{
|
|
@@ -124,11 +138,11 @@ var g = [
|
|
|
124
138
|
description: "rotate body around Z-axis — s4",
|
|
125
139
|
inputSchema: {
|
|
126
140
|
type: "object",
|
|
127
|
-
properties: {
|
|
141
|
+
properties: { angle: {
|
|
128
142
|
type: "number",
|
|
129
143
|
description: "target angle in degrees"
|
|
130
144
|
} },
|
|
131
|
-
required: ["
|
|
145
|
+
required: ["angle"]
|
|
132
146
|
}
|
|
133
147
|
},
|
|
134
148
|
{
|
|
@@ -136,11 +150,45 @@ var g = [
|
|
|
136
150
|
description: "lift head on secondary axis, range 20°–150° — s5",
|
|
137
151
|
inputSchema: {
|
|
138
152
|
type: "object",
|
|
139
|
-
properties: {
|
|
153
|
+
properties: { angle: {
|
|
140
154
|
type: "number",
|
|
141
155
|
description: "target angle in degrees"
|
|
142
156
|
} },
|
|
143
|
-
required: ["
|
|
157
|
+
required: ["angle"]
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "move_to",
|
|
162
|
+
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",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
within_ms: {
|
|
167
|
+
type: "number",
|
|
168
|
+
description: "total movement duration in milliseconds (must be > 0)"
|
|
169
|
+
},
|
|
170
|
+
s1: {
|
|
171
|
+
type: "number",
|
|
172
|
+
description: "head shift target angle (optional)"
|
|
173
|
+
},
|
|
174
|
+
s2: {
|
|
175
|
+
type: "number",
|
|
176
|
+
description: "head roll target angle (optional)"
|
|
177
|
+
},
|
|
178
|
+
s3: {
|
|
179
|
+
type: "number",
|
|
180
|
+
description: "head pitch target angle (optional)"
|
|
181
|
+
},
|
|
182
|
+
s4: {
|
|
183
|
+
type: "number",
|
|
184
|
+
description: "body rotate target angle (optional)"
|
|
185
|
+
},
|
|
186
|
+
s5: {
|
|
187
|
+
type: "number",
|
|
188
|
+
description: "head lift target angle (optional)"
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
required: ["within_ms"]
|
|
144
192
|
}
|
|
145
193
|
},
|
|
146
194
|
{
|
|
@@ -162,82 +210,111 @@ var g = [
|
|
|
162
210
|
type: "object",
|
|
163
211
|
properties: {}
|
|
164
212
|
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
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>",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: { script: {
|
|
220
|
+
type: "string",
|
|
221
|
+
description: "multi-line movement script"
|
|
222
|
+
} },
|
|
223
|
+
required: ["script"]
|
|
224
|
+
}
|
|
165
225
|
}
|
|
166
226
|
];
|
|
167
|
-
async function
|
|
168
|
-
return await (await
|
|
227
|
+
async function b() {
|
|
228
|
+
return await (await h()).home(), "all servos moved to home positions";
|
|
169
229
|
}
|
|
170
|
-
async function
|
|
230
|
+
async function x(e) {
|
|
171
231
|
let t = {};
|
|
172
232
|
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");
|
|
173
|
-
let n = await
|
|
233
|
+
let n = await h();
|
|
174
234
|
return n.State = t, await n.sendServoState(), `servos updated: ${JSON.stringify(t)}`;
|
|
175
235
|
}
|
|
176
|
-
async function
|
|
177
|
-
let t = Number(e.
|
|
236
|
+
async function S(e) {
|
|
237
|
+
let t = Number(e.angle), n = await h();
|
|
178
238
|
return n.State = { s1: t }, await n.sendServoState(), `s1 (shift) → ${t}°`;
|
|
179
239
|
}
|
|
180
|
-
async function
|
|
181
|
-
let t = Number(e.
|
|
240
|
+
async function C(e) {
|
|
241
|
+
let t = Number(e.angle), n = await h();
|
|
182
242
|
return n.State = { s2: t }, await n.sendServoState(), `s2 (roll) → ${t}°`;
|
|
183
243
|
}
|
|
184
|
-
async function
|
|
185
|
-
let t = Number(e.
|
|
244
|
+
async function w(e) {
|
|
245
|
+
let t = Number(e.angle), n = await h();
|
|
186
246
|
return n.State = { s3: t }, await n.sendServoState(), `s3 (pitch) → ${t}°`;
|
|
187
247
|
}
|
|
188
|
-
async function
|
|
189
|
-
let t = Number(e.
|
|
248
|
+
async function T(e) {
|
|
249
|
+
let t = Number(e.angle), n = await h();
|
|
190
250
|
return n.State = { s4: t }, await n.sendServoState(), `s4 (rotate) → ${t}°`;
|
|
191
251
|
}
|
|
192
|
-
async function
|
|
193
|
-
let t = Number(e.
|
|
252
|
+
async function E(e) {
|
|
253
|
+
let t = Number(e.angle), n = await h();
|
|
194
254
|
return n.State = { s5: t }, await n.sendServoState(), `s5 (lift) → ${t}°`;
|
|
195
255
|
}
|
|
196
|
-
async function
|
|
256
|
+
async function D(e) {
|
|
257
|
+
let t = Number(e.within_ms);
|
|
258
|
+
if (isNaN(t) || t <= 0) throw Error("move_to: within_ms must be a positive number");
|
|
259
|
+
let n = {};
|
|
260
|
+
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";
|
|
262
|
+
}
|
|
263
|
+
async function O(e) {
|
|
197
264
|
let t = Number(e.ms);
|
|
198
265
|
if (isNaN(t) || t < 0) throw Error(`wait: invalid duration '${e.ms}' — expected a non-negative number`);
|
|
199
266
|
return await new Promise((e) => setTimeout(e, t)), `waited ${t} ms`;
|
|
200
267
|
}
|
|
201
|
-
async function
|
|
202
|
-
let e = await
|
|
268
|
+
async function k() {
|
|
269
|
+
let e = await h();
|
|
203
270
|
return JSON.stringify(e.State);
|
|
204
271
|
}
|
|
205
|
-
function
|
|
206
|
-
let
|
|
272
|
+
async function A(e) {
|
|
273
|
+
let t = String(e.script ?? "");
|
|
274
|
+
return await u(await h(), t), "script executed successfully";
|
|
275
|
+
}
|
|
276
|
+
function j() {
|
|
277
|
+
let e = new i({
|
|
207
278
|
name: "nova-control-mcp-server",
|
|
208
|
-
version: "0.0.
|
|
279
|
+
version: "0.0.5"
|
|
209
280
|
}, { capabilities: { tools: {} } });
|
|
210
|
-
return e.setRequestHandler(
|
|
281
|
+
return e.setRequestHandler(c, async () => ({ tools: y })), e.setRequestHandler(s, async (e) => {
|
|
211
282
|
let t = e.params.name, n = e.params.arguments ?? {};
|
|
212
283
|
try {
|
|
213
284
|
let e;
|
|
214
285
|
switch (t) {
|
|
215
286
|
case "home":
|
|
216
|
-
e = await
|
|
287
|
+
e = await b();
|
|
217
288
|
break;
|
|
218
289
|
case "move":
|
|
219
|
-
e = await
|
|
290
|
+
e = await x(n);
|
|
220
291
|
break;
|
|
221
292
|
case "shift_to":
|
|
222
|
-
e = await
|
|
293
|
+
e = await S(n);
|
|
223
294
|
break;
|
|
224
295
|
case "roll_to":
|
|
225
|
-
e = await
|
|
296
|
+
e = await C(n);
|
|
226
297
|
break;
|
|
227
298
|
case "pitch_to":
|
|
228
|
-
e = await
|
|
299
|
+
e = await w(n);
|
|
229
300
|
break;
|
|
230
301
|
case "rotate_to":
|
|
231
|
-
e = await
|
|
302
|
+
e = await T(n);
|
|
232
303
|
break;
|
|
233
304
|
case "lift_to":
|
|
234
|
-
e = await
|
|
305
|
+
e = await E(n);
|
|
306
|
+
break;
|
|
307
|
+
case "move_to":
|
|
308
|
+
e = await D(n);
|
|
235
309
|
break;
|
|
236
310
|
case "wait":
|
|
237
|
-
e = await
|
|
311
|
+
e = await O(n);
|
|
238
312
|
break;
|
|
239
313
|
case "get_state":
|
|
240
|
-
e = await
|
|
314
|
+
e = await k();
|
|
315
|
+
break;
|
|
316
|
+
case "run_script":
|
|
317
|
+
e = await A(n);
|
|
241
318
|
break;
|
|
242
319
|
default: return {
|
|
243
320
|
content: [{
|
|
@@ -262,17 +339,36 @@ function E() {
|
|
|
262
339
|
}
|
|
263
340
|
}), e;
|
|
264
341
|
}
|
|
265
|
-
async function
|
|
266
|
-
let
|
|
267
|
-
|
|
268
|
-
let n = E(), r = new i();
|
|
269
|
-
await n.connect(r);
|
|
342
|
+
async function M(e) {
|
|
343
|
+
let t = new a();
|
|
344
|
+
await e.connect(t);
|
|
270
345
|
for (let e of ["SIGINT", "SIGTERM"]) process.on(e, () => {
|
|
271
|
-
|
|
346
|
+
g(), process.exit(0);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
async function N(e, t) {
|
|
350
|
+
let r = new o({ sessionIdGenerator: void 0 });
|
|
351
|
+
await e.connect(r);
|
|
352
|
+
let i = n(async (e, t) => {
|
|
353
|
+
e.url === "/mcp" ? await r.handleRequest(e, t) : (t.writeHead(404, { "Content-Type": "text/plain" }), t.end("not found"));
|
|
272
354
|
});
|
|
355
|
+
await new Promise((e, n) => {
|
|
356
|
+
i.listen(t, () => {
|
|
357
|
+
process.stderr.write(`nova-control-mcp: HTTP transport listening on port ${t} — POST /mcp\n`), e();
|
|
358
|
+
}), i.once("error", n);
|
|
359
|
+
});
|
|
360
|
+
for (let e of ["SIGINT", "SIGTERM"]) process.on(e, async () => {
|
|
361
|
+
await r.close(), i.close(), g(), process.exit(0);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
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);
|
|
273
369
|
}
|
|
274
|
-
t(process.argv[1]) === e(import.meta.url) &&
|
|
370
|
+
t(process.argv[1]) === e(import.meta.url) && P().catch((e) => {
|
|
275
371
|
process.stderr.write(`nova-control-mcp: fatal: ${e.message ?? e}\n`), process.exit(1);
|
|
276
372
|
});
|
|
277
373
|
//#endregion
|
|
278
|
-
export {
|
|
374
|
+
export { v as _destroyForTests, _ as _setupForTests, j as createServer };
|