@thalovant/mcp-server 0.1.17 → 0.1.18
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 +6 -0
- package/README.md +18 -0
- package/dist/index.js +43 -44
- package/package.json +2 -2
- package/server.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.18
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
- 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.
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
3
9
|
## Unreleased
|
|
4
10
|
|
|
5
11
|
### Fixed
|
package/README.md
CHANGED
|
@@ -393,6 +393,24 @@ 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.18 uses `@thalovant/sdk` 0.3.4 or newer. Runtime tools support
|
|
403
|
+
HiveMind v3 Noise over WSS, HTTPS and MQTT over TLS. `thalovant_healthcheck`
|
|
404
|
+
reports readiness only after authentication; a reachable hub or broker alone
|
|
405
|
+
does not establish a runtime session.
|
|
406
|
+
|
|
407
|
+
Keep the SDK configuration directory persistent and private between server
|
|
408
|
+
restarts (`~/.config/thalovant`, or the configured XDG/Windows equivalent). It
|
|
409
|
+
contains the client Noise key and trusted server pins. An authentication
|
|
410
|
+
failure preserves those pins; replacing a server key requires an explicit,
|
|
411
|
+
verified trust change. HTTPS identities must advertise the hub's HTTPS plugin,
|
|
412
|
+
and MQTT requires the identity's broker credentials and topic prefix.
|
|
413
|
+
|
|
396
414
|
## Development
|
|
397
415
|
|
|
398
416
|
```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,33 @@ 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
|
+
async function withRuntimeClient(options, run) {
|
|
658
|
+
const client = await createRuntimeClient(options);
|
|
659
|
+
const key = createHash("sha256").update(JSON.stringify([
|
|
660
|
+
client.identity.endpointBase(), client.identity.accessKey,
|
|
661
|
+
])).digest("hex");
|
|
662
|
+
const previous = runtimeLeases.get(key) ?? Promise.resolve();
|
|
663
|
+
let release;
|
|
664
|
+
const current = new Promise(resolve => { release = resolve; });
|
|
665
|
+
runtimeLeases.set(key, current);
|
|
666
|
+
await previous;
|
|
667
|
+
try {
|
|
668
|
+
return await run(client);
|
|
669
|
+
}
|
|
670
|
+
finally {
|
|
671
|
+
try {
|
|
672
|
+
await client.close();
|
|
673
|
+
}
|
|
674
|
+
finally {
|
|
675
|
+
release();
|
|
676
|
+
if (runtimeLeases.get(key) === current)
|
|
677
|
+
runtimeLeases.delete(key);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
654
681
|
function summarizeReply(reply) {
|
|
655
682
|
const displayItems = reply.displayItems({ maxTextChars: 1_000 });
|
|
656
683
|
return {
|
|
@@ -1249,8 +1276,7 @@ export function createServer() {
|
|
|
1249
1276
|
openWorldHint: false,
|
|
1250
1277
|
},
|
|
1251
1278
|
}, async (args) => {
|
|
1252
|
-
|
|
1253
|
-
try {
|
|
1279
|
+
return withRuntimeClient(args, async (client) => {
|
|
1254
1280
|
const identity = client.identity;
|
|
1255
1281
|
return jsonContent({
|
|
1256
1282
|
identity: redactSecrets(identity.asObject(false)),
|
|
@@ -1259,10 +1285,7 @@ export function createServer() {
|
|
|
1259
1285
|
supportsRequestedProtocol: identity.supportsProtocol(args.protocol),
|
|
1260
1286
|
health: client.healthcheck(),
|
|
1261
1287
|
});
|
|
1262
|
-
}
|
|
1263
|
-
finally {
|
|
1264
|
-
await client.close();
|
|
1265
|
-
}
|
|
1288
|
+
});
|
|
1266
1289
|
});
|
|
1267
1290
|
registerThalovantTool(server, "thalovant_healthcheck", {
|
|
1268
1291
|
title: "Runtime Healthcheck",
|
|
@@ -1276,14 +1299,10 @@ export function createServer() {
|
|
|
1276
1299
|
openWorldHint: true,
|
|
1277
1300
|
},
|
|
1278
1301
|
}, async ({ timeoutMs, ...runtime }) => {
|
|
1279
|
-
|
|
1280
|
-
try {
|
|
1302
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1281
1303
|
await client.connect(clampTimeout(timeoutMs));
|
|
1282
1304
|
return jsonContent(redactSecrets(client.healthcheck()));
|
|
1283
|
-
}
|
|
1284
|
-
finally {
|
|
1285
|
-
await client.close();
|
|
1286
|
-
}
|
|
1305
|
+
});
|
|
1287
1306
|
});
|
|
1288
1307
|
registerThalovantTool(server, "thalovant_ask", {
|
|
1289
1308
|
title: "Ask Hub",
|
|
@@ -1306,8 +1325,7 @@ export function createServer() {
|
|
|
1306
1325
|
openWorldHint: true,
|
|
1307
1326
|
},
|
|
1308
1327
|
}, async ({ text, timeoutMs, lang, sessionId, requestId, context, replySettleMs, emptyReplyWaitMs, ...runtime }) => {
|
|
1309
|
-
|
|
1310
|
-
try {
|
|
1328
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1311
1329
|
const builtContext = context ? buildClientContext({}, context) : undefined;
|
|
1312
1330
|
const reply = await client.ask(text, {
|
|
1313
1331
|
timeoutMs: clampTimeout(timeoutMs),
|
|
@@ -1319,10 +1337,7 @@ export function createServer() {
|
|
|
1319
1337
|
emptyReplyWaitMs,
|
|
1320
1338
|
});
|
|
1321
1339
|
return jsonContent(redactSecrets(summarizeReply(reply)));
|
|
1322
|
-
}
|
|
1323
|
-
finally {
|
|
1324
|
-
await client.close();
|
|
1325
|
-
}
|
|
1340
|
+
});
|
|
1326
1341
|
});
|
|
1327
1342
|
registerThalovantTool(server, "thalovant_send_action", {
|
|
1328
1343
|
title: "Send Action",
|
|
@@ -1343,8 +1358,7 @@ export function createServer() {
|
|
|
1343
1358
|
openWorldHint: true,
|
|
1344
1359
|
},
|
|
1345
1360
|
}, async ({ payload, title, lang, sessionId, requestId, context, ...runtime }) => {
|
|
1346
|
-
|
|
1347
|
-
try {
|
|
1361
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1348
1362
|
await client.sendAction(payload, {
|
|
1349
1363
|
title,
|
|
1350
1364
|
lang,
|
|
@@ -1353,10 +1367,7 @@ export function createServer() {
|
|
|
1353
1367
|
context: context ? buildClientContext({}, context) : undefined,
|
|
1354
1368
|
});
|
|
1355
1369
|
return textContent("Action sent.");
|
|
1356
|
-
}
|
|
1357
|
-
finally {
|
|
1358
|
-
await client.close();
|
|
1359
|
-
}
|
|
1370
|
+
});
|
|
1360
1371
|
});
|
|
1361
1372
|
registerThalovantTool(server, "thalovant_send_code", {
|
|
1362
1373
|
title: "Send Code",
|
|
@@ -1378,8 +1389,7 @@ export function createServer() {
|
|
|
1378
1389
|
openWorldHint: true,
|
|
1379
1390
|
},
|
|
1380
1391
|
}, async ({ value, kind, label, lang, sessionId, requestId, context, ...runtime }) => {
|
|
1381
|
-
|
|
1382
|
-
try {
|
|
1392
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1383
1393
|
await client.sendCode(value, {
|
|
1384
1394
|
kind,
|
|
1385
1395
|
label,
|
|
@@ -1389,10 +1399,7 @@ export function createServer() {
|
|
|
1389
1399
|
context: context ? buildClientContext({}, context) : undefined,
|
|
1390
1400
|
});
|
|
1391
1401
|
return textContent("Code sent.");
|
|
1392
|
-
}
|
|
1393
|
-
finally {
|
|
1394
|
-
await client.close();
|
|
1395
|
-
}
|
|
1402
|
+
});
|
|
1396
1403
|
});
|
|
1397
1404
|
registerThalovantTool(server, "thalovant_emit_event", {
|
|
1398
1405
|
title: "Emit Event",
|
|
@@ -1410,14 +1417,10 @@ export function createServer() {
|
|
|
1410
1417
|
openWorldHint: true,
|
|
1411
1418
|
},
|
|
1412
1419
|
}, async ({ eventType, data, context, ...runtime }) => {
|
|
1413
|
-
|
|
1414
|
-
try {
|
|
1420
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1415
1421
|
await client.emit(eventType, data, context ? buildClientContext({}, context) : undefined);
|
|
1416
1422
|
return textContent("Event emitted.");
|
|
1417
|
-
}
|
|
1418
|
-
finally {
|
|
1419
|
-
await client.close();
|
|
1420
|
-
}
|
|
1423
|
+
});
|
|
1421
1424
|
});
|
|
1422
1425
|
registerThalovantTool(server, "thalovant_wait_for_event", {
|
|
1423
1426
|
title: "Wait For Event",
|
|
@@ -1435,8 +1438,7 @@ export function createServer() {
|
|
|
1435
1438
|
openWorldHint: true,
|
|
1436
1439
|
},
|
|
1437
1440
|
}, async ({ eventName, timeoutMs, sessionId, requestId, context, ...runtime }) => {
|
|
1438
|
-
|
|
1439
|
-
try {
|
|
1441
|
+
return withRuntimeClient(runtime, async (client) => {
|
|
1440
1442
|
const event = await client.waitForEvent(eventName, {
|
|
1441
1443
|
timeoutMs: clampTimeout(timeoutMs),
|
|
1442
1444
|
sessionId,
|
|
@@ -1444,10 +1446,7 @@ export function createServer() {
|
|
|
1444
1446
|
context: context ? buildClientContext({}, context) : undefined,
|
|
1445
1447
|
});
|
|
1446
1448
|
return jsonContent(redactSecrets(event.asObject()));
|
|
1447
|
-
}
|
|
1448
|
-
finally {
|
|
1449
|
-
await client.close();
|
|
1450
|
-
}
|
|
1449
|
+
});
|
|
1451
1450
|
});
|
|
1452
1451
|
registerThalovantTool(server, "thalovant_get_analytics_overview", {
|
|
1453
1452
|
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.18",
|
|
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.4",
|
|
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.18",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@thalovant/mcp-server",
|
|
15
|
-
"version": "0.1.
|
|
15
|
+
"version": "0.1.18",
|
|
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.18",
|
|
23
23
|
"transport": {
|
|
24
24
|
"type": "streamable-http",
|
|
25
25
|
"url": "http://127.0.0.1:3000/mcp"
|