@thalovant/mcp-server 0.3.0 → 0.4.1
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 +18 -2
- package/dist/index.js +65 -9
- package/examples/intent-listing.json +15 -0
- 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.4.1 — 2026-09-12
|
|
4
|
+
|
|
5
|
+
- Use Node SDK 0.6.1 with thalovant-languages 0.2.1 listing data, matching Python 0.6.8 across 270 languages.
|
|
6
|
+
- Verify Spanish questions and complete French phrases through the encrypted runtime inventory tool.
|
|
7
|
+
|
|
8
|
+
## 0.4.0 — 2026-09-12
|
|
9
|
+
|
|
10
|
+
- Add locale-aware sentence inventory examples with SDK 0.6.0, canonical slot defaults and unique rendered limits.
|
|
11
|
+
- Handle SIGTERM/SIGINT explicitly and close MCP sessions and HTTP connections when running as PID 1.
|
|
12
|
+
- Align the Dockerfile version label with the package and registry metadata.
|
|
13
|
+
|
|
3
14
|
## 0.3.0 — 2026-09-12
|
|
4
15
|
|
|
5
16
|
- Use Node SDK 0.5.0 request hints, bounded embedded audio replies, and speakable intent examples. Keep embedded audio out of model text; offer explicit audio/resource content blocks through includeAudio.
|
package/README.md
CHANGED
|
@@ -384,7 +384,7 @@ Destructive, **not registered unless explicitly enabled** (see [Destructive Tool
|
|
|
384
384
|
Tool outputs redact credential-shaped fields. `thalovant_create_client_identity` does not return secret identity material; pass `savePath` when you want the full identity written to a local file with mode `0600`. `savePath` is confined to the server's identity directory (`THALOVANT_MCP_IDENTITY_DIR`, default `<config-dir>/thalovant/identities`): pass a plain filename, since absolute paths outside that directory and `..` traversal are rejected, so a model cannot drop a credential file into a git working tree or synced folder. `thalovant_config_status` reports the active `identityDir`.
|
|
385
385
|
|
|
386
386
|
`thalovant_intent_inventory` uses the runtime identity and accepts `languages`,
|
|
387
|
-
`describe`, `fallback`, `speakable`, `slots`, `exampleLimit`, and a per-query/batch `timeoutMs`. It returns registered
|
|
387
|
+
`describe`, `fallback`, `speakable`, `sentence`, `slots`, `exampleLimit`, and a per-query/batch `timeoutMs`. It returns registered
|
|
388
388
|
intents and examples, fallback skills, `fallbacks_known`, and `may_answer` by
|
|
389
389
|
requested language. A missing or denied optional fallback-skill query remains
|
|
390
390
|
unknown, rather than being reported as a known empty list. The optional probe
|
|
@@ -464,7 +464,7 @@ Runtime calls sharing the same hub client identity run sequentially within one
|
|
|
464
464
|
MCP process. Use a distinct client identity for each independently running MCP
|
|
465
465
|
server so the hub can keep their sessions separate.
|
|
466
466
|
|
|
467
|
-
Version 0.
|
|
467
|
+
Version 0.4.1 requires `@thalovant/sdk` `^0.6.1` (0.6.1 through versions below 0.7.0). This release enforces
|
|
468
468
|
secure effective MQTT URLs and carries a single connection deadline through
|
|
469
469
|
MQTT setup and HTTP failure cleanup. Runtime tools support
|
|
470
470
|
HiveMind v3 Noise over WSS, HTTPS and MQTT over TLS. `thalovant_healthcheck`
|
|
@@ -557,3 +557,19 @@ never repeats an accepted mutation and starts no new read after its deadline;
|
|
|
557
557
|
an already-running HTTP request retains its normal request timeout.
|
|
558
558
|
|
|
559
559
|
Read history with `thalovant_list_hub_skill_history` (`hubId`, optional `limit`). This tool remains available in read-only mode.
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
Inventory `sentence: true` implies speakable rendering and capitalizes/punctuates
|
|
563
|
+
examples using bundled thalovant-languages 0.2.1 rules. Regional locale matching
|
|
564
|
+
follows OVOS distances; explicit `slots` override locale sample values. Complete
|
|
565
|
+
phrases rank first, and `exampleLimit` counts unique nonempty rendered results.
|
|
566
|
+
Raw intent definitions remain in the response; unknown locales retain bare text.
|
|
567
|
+
|
|
568
|
+
The CLI handles SIGTERM and SIGINT in stdio and HTTP modes, including when it
|
|
569
|
+
runs as the container's PID 1. It closes sessions and HTTP connections before a
|
|
570
|
+
successful exit, with a ten-second failure deadline for stalled cleanup.
|
|
571
|
+
|
|
572
|
+
Version 0.4.1 uses Node SDK 0.6.1 and the same 270-language listing data as
|
|
573
|
+
Python 0.6.8. With `sentence: true`, Spanish questions receive their question
|
|
574
|
+
mark and complete French phrases keep their final period. Undescribed
|
|
575
|
+
languages remain bare.
|
package/dist/index.js
CHANGED
|
@@ -1346,7 +1346,8 @@ export function createServer() {
|
|
|
1346
1346
|
inputSchema: {
|
|
1347
1347
|
...runtimeAuthSchema,
|
|
1348
1348
|
languages: z.array(z.string().trim().min(1).max(100)).min(1).max(20).default(["en-us"]),
|
|
1349
|
-
speakable: z.boolean().default(false).describe("Render intent patterns
|
|
1349
|
+
speakable: z.boolean().default(false).describe("Render intent patterns with locale slot examples; preserve complete-phrase priority."),
|
|
1350
|
+
sentence: z.boolean().default(false).describe("Capitalize and punctuate examples using the selected locale. Implies speakable rendering."),
|
|
1350
1351
|
slots: z.record(z.string().max(500)).optional().describe("Example values for named intent slots."),
|
|
1351
1352
|
exampleLimit: z.number().int().min(1).max(20).default(2),
|
|
1352
1353
|
describe: z.boolean().default(true),
|
|
@@ -1354,14 +1355,14 @@ export function createServer() {
|
|
|
1354
1355
|
timeoutMs: z.number().int().min(1_000).max(MAX_TIMEOUT_MS).default(DEFAULT_TIMEOUT_MS).describe("Per-query/batch timeout; the optional fallback-skill probe is capped at 1500ms."),
|
|
1355
1356
|
},
|
|
1356
1357
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
1357
|
-
}, async ({ languages, describe, fallback, timeoutMs, speakable, slots, exampleLimit, ...runtime }) => withRuntimeClient(runtime, async (client, signal) => {
|
|
1358
|
+
}, async ({ languages, describe, fallback, timeoutMs, speakable, sentence, slots, exampleLimit, ...runtime }) => withRuntimeClient(runtime, async (client, signal) => {
|
|
1358
1359
|
await client.connect(undefined, signal);
|
|
1359
1360
|
throwIfRuntimeCancelled(signal);
|
|
1360
1361
|
const inventory = await client.intents(languages, { describe, fallback, timeoutMs: clampTimeout(timeoutMs) });
|
|
1361
1362
|
return jsonContent(redactSecrets({
|
|
1362
1363
|
...inventory.asObject(),
|
|
1363
1364
|
examples: inventory.intents.map(intent => ({ id: intent.id,
|
|
1364
|
-
languages: Object.fromEntries(inventory.languages.map(lang => [lang, intent.examples(lang, exampleLimit, { speakable, slots })])),
|
|
1365
|
+
languages: Object.fromEntries(inventory.languages.map(lang => [lang, intent.examples(lang, exampleLimit, { speakable, sentence, slots })])),
|
|
1365
1366
|
})),
|
|
1366
1367
|
may_answer: Object.fromEntries(inventory.languages.map(lang => [lang, inventory.mayAnswer(lang)])),
|
|
1367
1368
|
}));
|
|
@@ -2322,8 +2323,13 @@ export async function startHttpServer(config = getHttpConfig()) {
|
|
|
2322
2323
|
}
|
|
2323
2324
|
}, Math.min(config.sessionTtlMs, 60_000));
|
|
2324
2325
|
cleanupTimer.unref();
|
|
2326
|
+
let closing;
|
|
2325
2327
|
const httpServer = createHttpServer(async (req, res) => {
|
|
2326
2328
|
setSecurityHeaders(res);
|
|
2329
|
+
if (closing) {
|
|
2330
|
+
sendJson(res, 503, { error: "Server is shutting down." });
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2327
2333
|
try {
|
|
2328
2334
|
if (!validateHost(req, config)) {
|
|
2329
2335
|
sendJson(res, 403, { error: "Forbidden host." });
|
|
@@ -2366,11 +2372,19 @@ export async function startHttpServer(config = getHttpConfig()) {
|
|
|
2366
2372
|
return;
|
|
2367
2373
|
}
|
|
2368
2374
|
const auth = await authenticate(req, config);
|
|
2375
|
+
if (closing) {
|
|
2376
|
+
sendJson(res, 503, { error: "Server is shutting down." });
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2369
2379
|
const authenticatedReq = req;
|
|
2370
2380
|
authenticatedReq.auth = authInfoFromAuthResult(auth);
|
|
2371
2381
|
authenticatedReq.auth.resource = config.resourceUrl;
|
|
2372
2382
|
if (req.method === "POST") {
|
|
2373
2383
|
const body = await readJsonBody(req, config.maxBodyBytes);
|
|
2384
|
+
if (closing) {
|
|
2385
|
+
sendJson(res, 503, { error: "Server is shutting down." });
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2374
2388
|
const sessionId = typeof req.headers["mcp-session-id"] === "string" ? req.headers["mcp-session-id"] : undefined;
|
|
2375
2389
|
if (sessionId) {
|
|
2376
2390
|
const session = sessions.get(sessionId);
|
|
@@ -2425,6 +2439,11 @@ export async function startHttpServer(config = getHttpConfig()) {
|
|
|
2425
2439
|
});
|
|
2426
2440
|
};
|
|
2427
2441
|
await mcpServer.connect(transport);
|
|
2442
|
+
if (closing) {
|
|
2443
|
+
await mcpServer.close();
|
|
2444
|
+
sendJson(res, 503, { error: "Server is shutting down." });
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2428
2447
|
await transport.handleRequest(authenticatedReq, res, body);
|
|
2429
2448
|
return;
|
|
2430
2449
|
}
|
|
@@ -2483,12 +2502,12 @@ export async function startHttpServer(config = getHttpConfig()) {
|
|
|
2483
2502
|
console.error(`Thalovant MCP Streamable HTTP listening at ${url}`);
|
|
2484
2503
|
return {
|
|
2485
2504
|
url,
|
|
2486
|
-
close:
|
|
2505
|
+
close: () => {
|
|
2506
|
+
if (closing)
|
|
2507
|
+
return closing;
|
|
2487
2508
|
clearInterval(cleanupTimer);
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
}
|
|
2491
|
-
await new Promise((resolveClose, rejectClose) => {
|
|
2509
|
+
// Stop admission before closing sessions; then release remaining HTTP sockets.
|
|
2510
|
+
const stopped = new Promise((resolveClose, rejectClose) => {
|
|
2492
2511
|
httpServer.close((error) => {
|
|
2493
2512
|
if (error)
|
|
2494
2513
|
rejectClose(error);
|
|
@@ -2496,9 +2515,44 @@ export async function startHttpServer(config = getHttpConfig()) {
|
|
|
2496
2515
|
resolveClose();
|
|
2497
2516
|
});
|
|
2498
2517
|
});
|
|
2518
|
+
closing = Promise.all([
|
|
2519
|
+
stopped,
|
|
2520
|
+
(async () => {
|
|
2521
|
+
try {
|
|
2522
|
+
await Promise.all(Array.from(sessions.keys(), closeSession));
|
|
2523
|
+
}
|
|
2524
|
+
finally {
|
|
2525
|
+
httpServer.closeAllConnections();
|
|
2526
|
+
}
|
|
2527
|
+
})(),
|
|
2528
|
+
]).then(() => undefined);
|
|
2529
|
+
return closing;
|
|
2499
2530
|
},
|
|
2500
2531
|
};
|
|
2501
2532
|
}
|
|
2533
|
+
// CLI signal handling is explicit because Node may run as PID 1 in the OCI image.
|
|
2534
|
+
function handleShutdown(close) {
|
|
2535
|
+
let stopping = false;
|
|
2536
|
+
const shutdown = () => {
|
|
2537
|
+
if (stopping)
|
|
2538
|
+
return;
|
|
2539
|
+
stopping = true;
|
|
2540
|
+
const deadline = setTimeout(() => {
|
|
2541
|
+
console.error("MCP shutdown exceeded 10 seconds.");
|
|
2542
|
+
process.exit(1);
|
|
2543
|
+
}, 10_000);
|
|
2544
|
+
void Promise.resolve().then(close).then(() => {
|
|
2545
|
+
clearTimeout(deadline);
|
|
2546
|
+
process.exit(0);
|
|
2547
|
+
}, (error) => {
|
|
2548
|
+
clearTimeout(deadline);
|
|
2549
|
+
console.error("Error during MCP shutdown:", error);
|
|
2550
|
+
process.exit(1);
|
|
2551
|
+
});
|
|
2552
|
+
};
|
|
2553
|
+
process.on("SIGTERM", shutdown);
|
|
2554
|
+
process.on("SIGINT", shutdown);
|
|
2555
|
+
}
|
|
2502
2556
|
export async function main() {
|
|
2503
2557
|
const requestedTransport = process.argv.includes("--http")
|
|
2504
2558
|
? "http"
|
|
@@ -2506,12 +2560,14 @@ export async function main() {
|
|
|
2506
2560
|
? "stdio"
|
|
2507
2561
|
: (process.env.MCP_TRANSPORT ?? "stdio");
|
|
2508
2562
|
if (requestedTransport === "http" || requestedTransport === "streamable-http") {
|
|
2509
|
-
await startHttpServer();
|
|
2563
|
+
const http = await startHttpServer();
|
|
2564
|
+
handleShutdown(http.close);
|
|
2510
2565
|
return;
|
|
2511
2566
|
}
|
|
2512
2567
|
const transport = new StdioServerTransport();
|
|
2513
2568
|
const server = createServer();
|
|
2514
2569
|
await server.connect(transport);
|
|
2570
|
+
handleShutdown(() => server.close());
|
|
2515
2571
|
}
|
|
2516
2572
|
function isMainModule() {
|
|
2517
2573
|
if (!process.argv[1])
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thalovant/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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.
|
|
67
|
+
"@thalovant/sdk": "^0.6.1",
|
|
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.
|
|
10
|
+
"version": "0.4.1",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@thalovant/mcp-server",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.4.1",
|
|
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.
|
|
22
|
+
"identifier": "ghcr.io/thalovant/thalovant-mcp:0.4.1",
|
|
23
23
|
"transport": {
|
|
24
24
|
"type": "streamable-http",
|
|
25
25
|
"url": "http://127.0.0.1:3000/mcp"
|