@thalovant/mcp-server 0.3.0 → 0.4.0

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 — 2026-09-12
4
+
5
+ - Add locale-aware sentence inventory examples with SDK 0.6.0, canonical slot defaults and unique rendered limits.
6
+ - Handle SIGTERM/SIGINT explicitly and close MCP sessions and HTTP connections when running as PID 1.
7
+ - Align the Dockerfile version label with the package and registry metadata.
8
+
3
9
  ## 0.3.0 — 2026-09-12
4
10
 
5
11
  - 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.3.0 requires `@thalovant/sdk` `^0.5.1` (0.5.1 through versions below 0.6.0). This release enforces
467
+ Version 0.4.0 requires `@thalovant/sdk` `^0.6.0` (0.6.0 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,14 @@ 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.1.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.
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 into example sentences; preserve whole-sentence priority."),
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: async () => {
2505
+ close: () => {
2506
+ if (closing)
2507
+ return closing;
2487
2508
  clearInterval(cleanupTimer);
2488
- for (const sessionId of Array.from(sessions.keys())) {
2489
- await closeSession(sessionId);
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])
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "thalovant_intent_inventory",
3
+ "arguments": {
4
+ "identityFile": "/path/to/identity.json",
5
+ "languages": [
6
+ "fr-CA",
7
+ "en-US"
8
+ ],
9
+ "sentence": true,
10
+ "slots": {
11
+ "location": "Montréal"
12
+ },
13
+ "exampleLimit": 3
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thalovant/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
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.5.1",
67
+ "@thalovant/sdk": "^0.6.0",
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.3.0",
10
+ "version": "0.4.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@thalovant/mcp-server",
15
- "version": "0.3.0",
15
+ "version": "0.4.0",
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.3.0",
22
+ "identifier": "ghcr.io/thalovant/thalovant-mcp:0.4.0",
23
23
  "transport": {
24
24
  "type": "streamable-http",
25
25
  "url": "http://127.0.0.1:3000/mcp"