@thalovant/mcp-server 0.1.17 → 0.1.19
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/CHANGELOG.md +11 -0
- package/README.md +20 -0
- package/dist/index.js +48 -44
- package/package.json +2 -2
- package/server.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.19
|
|
4
|
+
|
|
5
|
+
- Require Node SDK 0.3.5 or newer so runtime tools receive secure MQTT URL enforcement and bounded MQTT setup and HTTP failure cleanup.
|
|
6
|
+
- Keep package, registry, and container source metadata on version 0.1.19. Runtime tool names, inputs, and identity serialization remain compatible.
|
|
7
|
+
|
|
8
|
+
## 0.1.18
|
|
9
|
+
|
|
10
|
+
- Serialize runtime tool calls sharing a hub client identity so concurrent calls cannot replace each other's Noise sessions or HTTP message queues. Different identities remain independent.
|
|
11
|
+
- Use Node SDK 0.3.4 or newer for authenticated HiveMind v3 Noise across WSS, HTTPS and MQTT. HTTPS retains replica affinity and uses binary encrypted polling; MQTT reauthenticates when reconnecting.
|
|
12
|
+
- Derive the advertised MCP server version from the package manifest so stdio, Streamable HTTP, npm, OCI and registry metadata agree. Add transport smoke assertions for version consistency.
|
|
13
|
+
|
|
3
14
|
## Unreleased
|
|
4
15
|
|
|
5
16
|
### Fixed
|
package/README.md
CHANGED
|
@@ -393,6 +393,26 @@ With the flag unset, a call with any `sourceType` other than `catalog` fails bef
|
|
|
393
393
|
|
|
394
394
|
Set `THALOVANT_MCP_READONLY=1` to register only tools annotated `readOnlyHint: true`. Write and destructive tools are then never registered and never appear in `tools/list`, so an operator can run an observe-only agent without hand-writing a denylist. Like the other registration-time gates it is read when a server instance is created; `thalovant_config_status` reports the state as `readOnly`.
|
|
395
395
|
|
|
396
|
+
## HiveMind Runtime Compatibility
|
|
397
|
+
|
|
398
|
+
Runtime calls sharing the same hub client identity run sequentially within one
|
|
399
|
+
MCP process. Use a distinct client identity for each independently running MCP
|
|
400
|
+
server so the hub can keep their sessions separate.
|
|
401
|
+
|
|
402
|
+
Version 0.1.19 uses `@thalovant/sdk` 0.3.5 or newer. This release enforces
|
|
403
|
+
secure effective MQTT URLs and carries a single connection deadline through
|
|
404
|
+
MQTT setup and HTTP failure cleanup. Runtime tools support
|
|
405
|
+
HiveMind v3 Noise over WSS, HTTPS and MQTT over TLS. `thalovant_healthcheck`
|
|
406
|
+
reports readiness only after authentication; a reachable hub or broker alone
|
|
407
|
+
does not establish a runtime session.
|
|
408
|
+
|
|
409
|
+
Keep the SDK configuration directory persistent and private between server
|
|
410
|
+
restarts (`~/.config/thalovant`, or the configured XDG/Windows equivalent). It
|
|
411
|
+
contains the client Noise key and trusted server pins. An authentication
|
|
412
|
+
failure preserves those pins; replacing a server key requires an explicit,
|
|
413
|
+
verified trust change. HTTPS identities must advertise the hub's HTTPS plugin,
|
|
414
|
+
and MQTT requires the identity's broker credentials and topic prefix.
|
|
415
|
+
|
|
396
416
|
## Development
|
|
397
417
|
|
|
398
418
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { DEFAULT_CONTROL_API_URL, ThalovantClient, ThalovantControlPlane, Thalov
|
|
|
7
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
8
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
9
9
|
import { createServer as createHttpServer } from "node:http";
|
|
10
|
-
import { realpathSync } from "node:fs";
|
|
10
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
11
11
|
import { appendFile, readFile, writeFile } from "node:fs/promises";
|
|
12
12
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
13
|
import { homedir } from "node:os";
|
|
@@ -15,7 +15,7 @@ import { mkdir } from "node:fs/promises";
|
|
|
15
15
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
16
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
17
17
|
import { z } from "zod";
|
|
18
|
-
const VERSION = "
|
|
18
|
+
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
19
19
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
20
20
|
const MAX_TIMEOUT_MS = 300_000;
|
|
21
21
|
const MAX_LIMIT = 100;
|
|
@@ -651,6 +651,38 @@ async function createRuntimeClient(options) {
|
|
|
651
651
|
protocol,
|
|
652
652
|
});
|
|
653
653
|
}
|
|
654
|
+
// The hub HTTP/MQTT queues and Noise sessions are keyed by client identity.
|
|
655
|
+
// A second tool call must finish after the first closes that identity's session.
|
|
656
|
+
const runtimeLeases = new Map();
|
|
657
|
+
/**
|
|
658
|
+
* Hold an identity lease through both the tool action and client cleanup.
|
|
659
|
+
* This keeps HTTP/MQTT admissions from replacing another call's Noise session;
|
|
660
|
+
* a failed action still closes its client before the next lease starts.
|
|
661
|
+
*/
|
|
662
|
+
async function withRuntimeClient(options, run) {
|
|
663
|
+
const client = await createRuntimeClient(options);
|
|
664
|
+
const key = createHash("sha256").update(JSON.stringify([
|
|
665
|
+
client.identity.endpointBase(), client.identity.accessKey,
|
|
666
|
+
])).digest("hex");
|
|
667
|
+
const previous = runtimeLeases.get(key) ?? Promise.resolve();
|
|
668
|
+
let release;
|
|
669
|
+
const current = new Promise(resolve => { release = resolve; });
|
|
670
|
+
runtimeLeases.set(key, current);
|
|
671
|
+
await previous;
|
|
672
|
+
try {
|
|
673
|
+
return await run(client);
|
|
674
|
+
}
|
|
675
|
+
finally {
|
|
676
|
+
try {
|
|
677
|
+
await client.close();
|
|
678
|
+
}
|
|
679
|
+
finally {
|
|
680
|
+
release();
|
|
681
|
+
if (runtimeLeases.get(key) === current)
|
|
682
|
+
runtimeLeases.delete(key);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
654
686
|
function summarizeReply(reply) {
|
|
655
687
|
const displayItems = reply.displayItems({ maxTextChars: 1_000 });
|
|
656
688
|
return {
|
|
@@ -1249,8 +1281,7 @@ export function createServer() {
|
|
|
1249
1281
|
openWorldHint: false,
|
|
1250
1282
|
},
|
|
1251
1283
|
}, async (args) => {
|
|
1252
|
-
|
|
1253
|
-
try {
|
|
1284
|
+
return withRuntimeClient(args, async (client) => {
|
|
1254
1285
|
const identity = client.identity;
|
|
1255
1286
|
return jsonContent({
|
|
1256
1287
|
identity: redactSecrets(identity.asObject(false)),
|
|
@@ -1259,10 +1290,7 @@ export function createServer() {
|
|
|
1259
1290
|
supportsRequestedProtocol: identity.supportsProtocol(args.protocol),
|
|
1260
1291
|
health: client.healthcheck(),
|
|
1261
1292
|
});
|
|
1262
|
-
}
|
|
1263
|
-
finally {
|
|
1264
|
-
await client.close();
|
|
1265
|
-
}
|
|
1293
|
+
});
|
|
1266
1294
|
});
|
|
1267
1295
|
registerThalovantTool(server, "thalovant_healthcheck", {
|
|
1268
1296
|
title: "Runtime Healthcheck",
|
|
@@ -1276,14 +1304,10 @@ export function createServer() {
|
|
|
1276
1304
|
openWorldHint: true,
|
|
1277
1305
|
},
|
|
1278
1306
|
}, async ({ timeoutMs, ...runtime }) => {
|
|
1279
|
-
|
|
1280
|
-
try {
|
|
1307
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1281
1308
|
await client.connect(clampTimeout(timeoutMs));
|
|
1282
1309
|
return jsonContent(redactSecrets(client.healthcheck()));
|
|
1283
|
-
}
|
|
1284
|
-
finally {
|
|
1285
|
-
await client.close();
|
|
1286
|
-
}
|
|
1310
|
+
});
|
|
1287
1311
|
});
|
|
1288
1312
|
registerThalovantTool(server, "thalovant_ask", {
|
|
1289
1313
|
title: "Ask Hub",
|
|
@@ -1306,8 +1330,7 @@ export function createServer() {
|
|
|
1306
1330
|
openWorldHint: true,
|
|
1307
1331
|
},
|
|
1308
1332
|
}, async ({ text, timeoutMs, lang, sessionId, requestId, context, replySettleMs, emptyReplyWaitMs, ...runtime }) => {
|
|
1309
|
-
|
|
1310
|
-
try {
|
|
1333
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1311
1334
|
const builtContext = context ? buildClientContext({}, context) : undefined;
|
|
1312
1335
|
const reply = await client.ask(text, {
|
|
1313
1336
|
timeoutMs: clampTimeout(timeoutMs),
|
|
@@ -1319,10 +1342,7 @@ export function createServer() {
|
|
|
1319
1342
|
emptyReplyWaitMs,
|
|
1320
1343
|
});
|
|
1321
1344
|
return jsonContent(redactSecrets(summarizeReply(reply)));
|
|
1322
|
-
}
|
|
1323
|
-
finally {
|
|
1324
|
-
await client.close();
|
|
1325
|
-
}
|
|
1345
|
+
});
|
|
1326
1346
|
});
|
|
1327
1347
|
registerThalovantTool(server, "thalovant_send_action", {
|
|
1328
1348
|
title: "Send Action",
|
|
@@ -1343,8 +1363,7 @@ export function createServer() {
|
|
|
1343
1363
|
openWorldHint: true,
|
|
1344
1364
|
},
|
|
1345
1365
|
}, async ({ payload, title, lang, sessionId, requestId, context, ...runtime }) => {
|
|
1346
|
-
|
|
1347
|
-
try {
|
|
1366
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1348
1367
|
await client.sendAction(payload, {
|
|
1349
1368
|
title,
|
|
1350
1369
|
lang,
|
|
@@ -1353,10 +1372,7 @@ export function createServer() {
|
|
|
1353
1372
|
context: context ? buildClientContext({}, context) : undefined,
|
|
1354
1373
|
});
|
|
1355
1374
|
return textContent("Action sent.");
|
|
1356
|
-
}
|
|
1357
|
-
finally {
|
|
1358
|
-
await client.close();
|
|
1359
|
-
}
|
|
1375
|
+
});
|
|
1360
1376
|
});
|
|
1361
1377
|
registerThalovantTool(server, "thalovant_send_code", {
|
|
1362
1378
|
title: "Send Code",
|
|
@@ -1378,8 +1394,7 @@ export function createServer() {
|
|
|
1378
1394
|
openWorldHint: true,
|
|
1379
1395
|
},
|
|
1380
1396
|
}, async ({ value, kind, label, lang, sessionId, requestId, context, ...runtime }) => {
|
|
1381
|
-
|
|
1382
|
-
try {
|
|
1397
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1383
1398
|
await client.sendCode(value, {
|
|
1384
1399
|
kind,
|
|
1385
1400
|
label,
|
|
@@ -1389,10 +1404,7 @@ export function createServer() {
|
|
|
1389
1404
|
context: context ? buildClientContext({}, context) : undefined,
|
|
1390
1405
|
});
|
|
1391
1406
|
return textContent("Code sent.");
|
|
1392
|
-
}
|
|
1393
|
-
finally {
|
|
1394
|
-
await client.close();
|
|
1395
|
-
}
|
|
1407
|
+
});
|
|
1396
1408
|
});
|
|
1397
1409
|
registerThalovantTool(server, "thalovant_emit_event", {
|
|
1398
1410
|
title: "Emit Event",
|
|
@@ -1410,14 +1422,10 @@ export function createServer() {
|
|
|
1410
1422
|
openWorldHint: true,
|
|
1411
1423
|
},
|
|
1412
1424
|
}, async ({ eventType, data, context, ...runtime }) => {
|
|
1413
|
-
|
|
1414
|
-
try {
|
|
1425
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1415
1426
|
await client.emit(eventType, data, context ? buildClientContext({}, context) : undefined);
|
|
1416
1427
|
return textContent("Event emitted.");
|
|
1417
|
-
}
|
|
1418
|
-
finally {
|
|
1419
|
-
await client.close();
|
|
1420
|
-
}
|
|
1428
|
+
});
|
|
1421
1429
|
});
|
|
1422
1430
|
registerThalovantTool(server, "thalovant_wait_for_event", {
|
|
1423
1431
|
title: "Wait For Event",
|
|
@@ -1435,8 +1443,7 @@ export function createServer() {
|
|
|
1435
1443
|
openWorldHint: true,
|
|
1436
1444
|
},
|
|
1437
1445
|
}, async ({ eventName, timeoutMs, sessionId, requestId, context, ...runtime }) => {
|
|
1438
|
-
|
|
1439
|
-
try {
|
|
1446
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1440
1447
|
const event = await client.waitForEvent(eventName, {
|
|
1441
1448
|
timeoutMs: clampTimeout(timeoutMs),
|
|
1442
1449
|
sessionId,
|
|
@@ -1444,10 +1451,7 @@ export function createServer() {
|
|
|
1444
1451
|
context: context ? buildClientContext({}, context) : undefined,
|
|
1445
1452
|
});
|
|
1446
1453
|
return jsonContent(redactSecrets(event.asObject()));
|
|
1447
|
-
}
|
|
1448
|
-
finally {
|
|
1449
|
-
await client.close();
|
|
1450
|
-
}
|
|
1454
|
+
});
|
|
1451
1455
|
});
|
|
1452
1456
|
registerThalovantTool(server, "thalovant_get_analytics_overview", {
|
|
1453
1457
|
title: "Analytics Overview",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thalovant/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Stdio and Streamable HTTP MCP server for Thalovant control-plane and hub runtime APIs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
67
|
-
"@thalovant/sdk": "^0.3.
|
|
67
|
+
"@thalovant/sdk": "^0.3.5",
|
|
68
68
|
"jose": "^6.2.8",
|
|
69
69
|
"zod": "^3.25.76"
|
|
70
70
|
},
|
package/server.json
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
"url": "https://github.com/thalovant/thalovant-mcp",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.1.
|
|
10
|
+
"version": "0.1.19",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@thalovant/mcp-server",
|
|
15
|
-
"version": "0.1.
|
|
15
|
+
"version": "0.1.19",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"registryType": "oci",
|
|
22
|
-
"identifier": "ghcr.io/thalovant/thalovant-mcp:0.1.
|
|
22
|
+
"identifier": "ghcr.io/thalovant/thalovant-mcp:0.1.19",
|
|
23
23
|
"transport": {
|
|
24
24
|
"type": "streamable-http",
|
|
25
25
|
"url": "http://127.0.0.1:3000/mcp"
|