@thalovant/mcp-server 0.1.20 → 0.1.22

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.22
4
+
5
+ - Require Node SDK 0.3.14 for fail-closed persistent Noise trust, complete filesystem state transactions across Node processes, active reply-ID guards, and intent-description timeouts that only recover when usable definitions were received. Preserve process-local runtime identity leases and require distinct identities across independently running MCP processes.
6
+ - Correct global-deny precedence, process restart guidance, and historical unmatched-intent/redaction release notes.
7
+ - Validate successful provisioning results and protected identity-file creation, including nonempty credentials, matching persisted values, redacted output and private permissions. Share the fake control-plane server and handle malformed test requests safely.
8
+ - Require Node 22.22.2 or newer within the 22.x line for the pinned npm 12 publisher.
9
+
10
+ ## 0.1.21
11
+
12
+ - Propagate MCP request cancellation through runtime identity acquisition and SDK connection, Ask, Query and event waits in stdio and Streamable HTTP mode. Require Node SDK 0.3.12 for Query cancellation and explicit HTTP admission cleanup failures, including contradictory disconnect acknowledgments.
13
+ - Skip cancelled queued calls without constructing a client or interrupting the active identity owner. Retain admitted work and actual cleanup after caller cancellation, including SDK operations without cancellation support; preserve failed-cleanup poisoning and never replay application writes.
14
+ - Check cancellation during connection setup before action/code sends, raw event publication and inventory discovery. Tool schemas and the 41-tool default catalogue remain compatible.
15
+ - Verify actual MCP cancellation against independent HTTPS Noise peers on both MCP transports, with late queued execution, pending physical write and healthy successor regressions. Add real remote-cleanup refusal cases that prove an identity with unconfirmed HTTP cleanup remains unavailable instead of admitting an overlapping client.
16
+
3
17
  ## 0.1.20
4
18
 
5
19
  - Require HTTPS for credential-bearing API calls, preserving explicit loopback HTTP for local development. Reject API redirects through the SDK. Instantiate runtime clients only after acquiring their identity lease.
@@ -22,15 +36,13 @@
22
36
  - 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.
23
37
  - 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.
24
38
 
25
- ## Unreleased
39
+ ## Historical updates before 0.1.18
26
40
 
27
41
  ### Fixed
28
42
 
29
43
  - The server talks to HiveMind 5.x hubs. `@thalovant/sdk` 0.3.x speaks the protocol v3 Noise transport (the published 0.1.16 bundled 0.2.37 and could not connect to a 5.x hub at all); 0.3.3 additionally derives the v3 pre-shared key with WebAssembly argon2id and caches the derived key (only the key, never a password fingerprint) beside the client identity, so a reconnect skips the ~3 s derivation. Bumped the dependency floor to `^0.3.3` and refreshed the lockfile so the npm package and OCI image both ship it (thalovant-mcp#50).
30
44
  - `thalovant_ask` picks up the reply-correlation fixes from `@thalovant/sdk` 0.2.37: a hub-rewritten session id no longer rejects replies, and replies are correlated by request id rather than the session id a hub replaces. Bumped the dependency floor to `^0.2.37` and refreshed the lockfile so the npm package and OCI image both ship them.
31
- - The `thalovant_ask` tool surfaces an unrecovered intent miss promptly instead of waiting out the full timeout, and still lets a fallback reply win, via `@thalovant/sdk` 0.2.35's soft-failure ask path (thalovant-python-sdk#22). Bumped the dependency floor to `^0.2.35` and refreshed the lockfile.
32
- - The `thalovant_ask` tool recognises `ovos.intent.unmatched` on the bus ask path. `@thalovant/sdk` 0.2.33 fixed only the query path; the bus path (which `ask()` uses) registered handlers per event name and dropped the current OVOS intent-miss name. Bumped the dependency floor to `@thalovant/sdk ^0.2.34` and refreshed the lockfile so the npm package and OCI image ship it (thalovant-python-sdk#22).
33
- - The `thalovant_ask` tool now fails fast when an utterance matches no intent. It depends on `@thalovant/sdk`, whose ask loop previously only recognised the legacy `complete_intent_failure` event and missed the current OVOS `ovos.intent.unmatched` name, so an unmatched utterance waited out the full timeout. Bumped the dependency floor to `@thalovant/sdk ^0.2.33` (which carries the fix) and refreshed the lockfile, so the published npm package and OCI image both ship it (thalovant-python-sdk#22).
45
+ - Earlier unmatched-intent fixes progressed from query-path recognition in `@thalovant/sdk` 0.2.33 to bus-path event collection in 0.2.34, then recoverable soft-miss settlement in 0.2.35. The earlier event-only changes did not by themselves finish a silent bus Ask promptly. Later releases above supersede these dependency floors and preserve fallback replies within the documented fixed wait.
34
46
 
35
47
  ### Security
36
48
 
@@ -38,7 +50,7 @@
38
50
  - `thalovant_create_client_identity` now confines the optional `savePath` to a configurable identity directory (`THALOVANT_MCP_IDENTITY_DIR`, default `<config-dir>/thalovant/identities`). Absolute paths outside the directory and `..` traversal are rejected, and the destination is validated before the identity is created in the control plane, so a model can no longer drop a `0600` credential file into a git working tree or synced folder. `thalovant_config_status` reports `identityDir`.
39
51
  - `thalovant_healthcheck` output is now passed through `redactSecrets`, matching every other tool output.
40
52
  - `thalovant_get_analytics_overview` no longer advertises the `admin` and (admin-only) `ownerId` arguments to the model, so it no longer teaches an admin mode that only ever 403s for non-admin callers; only the plain overview call remains. Injected `admin`/`ownerId` values are stripped and never reach the control plane.
41
- - Extended output redaction to additional secret-ish keys: `device_code`, `user_code`, `psk`, `cert`, and `jwt`.
53
+ - Extended output redaction to additional secret-ish keys: `device_code`, `deviceCode`, `user_code`, `userCode`, `authToken`, `authorization`, `bearer`, `psk`, `cert`, and `jwt`.
42
54
  - Added an opt-in read-only mode (`THALOVANT_MCP_READONLY`): only tools annotated `readOnlyHint: true` are registered, so an observe-only agent needs no hand-written denylist. `thalovant_config_status` reports `readOnly`.
43
55
 
44
56
  ## 0.1.10
package/README.md CHANGED
@@ -385,17 +385,17 @@ A long-lived control-plane token combined with an always-available delete tool i
385
385
  export THALOVANT_ENABLE_DESTRUCTIVE_TOOLS="true"
386
386
  ```
387
387
 
388
- Accepted true values are `1`, `true`, `yes`, and `on`; anything else, including unset, leaves the tools off. The flag is read when a server instance is created, so restart the server (or, in Streamable HTTP mode, start a new session) after changing it. `thalovant_config_status` reports the current state as `destructiveToolsEnabled` and lists the tools the flag controls.
388
+ Accepted true values are `1`, `true`, `yes`, and `on`; anything else, including unset, leaves the tools off. The flag is read when a server instance is created. Restart the server process with the updated environment after changing it. `thalovant_config_status` reports the current state as `destructiveToolsEnabled` and lists the tools the flag controls.
389
389
 
390
390
  This is a separate mechanism from the existing tool policy, deliberately. `MCP_TOOL_ALLOWLIST` / `MCP_TOOL_DENYLIST` and the per-principal `allowedTools` / `deniedTools` are call-time filters where an empty allowlist means "allow everything"; they cannot express a tool that is off until an operator turns it on, and they cannot hide a tool from `tools/list`. Once `THALOVANT_ENABLE_DESTRUCTIVE_TOOLS` is set the delete tools are ordinary tools again and remain subject to that policy, so the two layers compose:
391
391
 
392
392
  ```bash
393
- # Enable deletes server-wide, but deny them to everyone except trusted principals.
393
+ # Register delete tools, then deny their use by every principal.
394
394
  export THALOVANT_ENABLE_DESTRUCTIVE_TOOLS="true"
395
395
  export MCP_TOOL_DENYLIST="thalovant_delete_hub,thalovant_delete_runtime_group"
396
396
  ```
397
397
 
398
- with the trusted principal's credential file granting them back via `allowedTools`.
398
+ The global deny applies to every principal and cannot be overridden by a principal's `allowedTools`. To restrict only selected principals, leave these tools out of the global denylist and use those principals' `deniedTools` instead.
399
399
 
400
400
  Deleting a hub still requires a current etag (`412` otherwise), and deleting a runtime group fails with `409` while it is the workspace default or still has hubs attached.
401
401
 
@@ -419,7 +419,7 @@ Runtime calls sharing the same hub client identity run sequentially within one
419
419
  MCP process. Use a distinct client identity for each independently running MCP
420
420
  server so the hub can keep their sessions separate.
421
421
 
422
- Version 0.1.20 uses `@thalovant/sdk` `^0.3.9` (0.3.9 through versions below 0.4.0). This release enforces
422
+ Version 0.1.22 uses `@thalovant/sdk` `^0.3.14` (0.3.14 through versions below 0.4.0). This release enforces
423
423
  secure effective MQTT URLs and carries a single connection deadline through
424
424
  MQTT setup and HTTP failure cleanup. Runtime tools support
425
425
  HiveMind v3 Noise over WSS, HTTPS and MQTT over TLS. `thalovant_healthcheck`
@@ -438,7 +438,31 @@ caller times out, a later tool waits up to six seconds for that cleanup before o
438
438
  A queued tool that reaches this deadline fails without executing later or releasing
439
439
  the previous session's identity.
440
440
  An actual cleanup failure marks the identity unavailable to later calls instead
441
- of risking concurrent sessions.
441
+ of risking concurrent sessions. These leases are process-local; separate server
442
+ processes or replicas need distinct runtime identities or external coordination.
443
+
444
+ MCP request cancellation applies to queued runtime tools in both stdio and
445
+ Streamable HTTP mode. A cancelled queued call never constructs a runtime client
446
+ or executes later. Cancellation also reaches runtime connection setup, Ask,
447
+ Query and event waits. Action/code sends, raw event publication and inventory
448
+ queries check cancellation during connection setup, then retain ownership until
449
+ the admitted SDK operation finishes. Cancelling the MCP request cannot undo an
450
+ already admitted operation or its effects. The identity remains reserved through
451
+ that work and actual cleanup; application requests are never automatically replayed.
452
+ Control-plane tools do not gain runtime cancellation semantics in this release.
453
+
454
+ For example, an MCP TypeScript client can cancel a runtime query after ten seconds,
455
+ including time spent waiting for its identity lease:
456
+
457
+ ```typescript
458
+ await mcpClient.callTool({
459
+ name: "thalovant_query",
460
+ arguments: { identityFile: "/secure/identity.json", text: "What is the weather?", timeoutMs: 30000 },
461
+ }, undefined, { signal: AbortSignal.timeout(10000) });
462
+ ```
463
+
464
+ Intent inventory retains its connection setup and per-query/batch budgets plus
465
+ a separate optional fallback probe; it has no single inventory-wide deadline.
442
466
 
443
467
  ## Development
444
468
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { withRuntimeLease } from "./runtime-lease.js";
2
+ import { throwIfRuntimeCancelled, withRuntimeLease } from "./runtime-lease.js";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -520,7 +520,7 @@ function registerThalovantTool(server, name, config, handler) {
520
520
  const start = Date.now();
521
521
  try {
522
522
  await authorizeTool(name, principal);
523
- const result = await requestContext.run({ toolName: name, principal }, () => handler(args, extra));
523
+ const result = await requestContext.run({ toolName: name, principal, signal: extra.signal }, () => handler(args, extra));
524
524
  await auditLog({
525
525
  ts: new Date().toISOString(),
526
526
  event: "mcp.tool",
@@ -670,11 +670,13 @@ async function createRuntimeIdentity(options) {
670
670
  }
671
671
  /** Use one identity lease through the action and actual retained cleanup. */
672
672
  async function withRuntimeClient(options, run) {
673
+ const signal = requestContext.getStore()?.signal;
674
+ throwIfRuntimeCancelled(signal);
673
675
  const identity = await createRuntimeIdentity(options);
674
676
  const key = createHash("sha256").update(JSON.stringify([
675
677
  identity.endpointBase(), identity.accessKey,
676
678
  ])).digest("hex");
677
- return withRuntimeLease(key, () => new ThalovantClient(identity, { protocol: options.protocol ?? "wss" }), run);
679
+ return withRuntimeLease(key, () => new ThalovantClient(identity, { protocol: options.protocol ?? "wss" }), client => run(client, signal), 6000, signal);
678
680
  }
679
681
  function summarizeReply(reply) {
680
682
  const displayItems = reply.displayItems({ maxTextChars: 1_000 });
@@ -1307,8 +1309,8 @@ export function createServer() {
1307
1309
  openWorldHint: true,
1308
1310
  },
1309
1311
  }, async ({ timeoutMs, ...runtime }) => {
1310
- return withRuntimeClient(runtime, async (client) => {
1311
- await client.connect(clampTimeout(timeoutMs));
1312
+ return withRuntimeClient(runtime, async (client, signal) => {
1313
+ await client.connect(clampTimeout(timeoutMs), signal);
1312
1314
  return jsonContent(redactSecrets(client.healthcheck()));
1313
1315
  });
1314
1316
  });
@@ -1323,7 +1325,9 @@ export function createServer() {
1323
1325
  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."),
1324
1326
  },
1325
1327
  annotations: { readOnlyHint: true, openWorldHint: true },
1326
- }, async ({ languages, describe, fallback, timeoutMs, ...runtime }) => withRuntimeClient(runtime, async (client) => {
1328
+ }, async ({ languages, describe, fallback, timeoutMs, ...runtime }) => withRuntimeClient(runtime, async (client, signal) => {
1329
+ await client.connect(undefined, signal);
1330
+ throwIfRuntimeCancelled(signal);
1327
1331
  const inventory = await client.intents(languages, { describe, fallback, timeoutMs: clampTimeout(timeoutMs) });
1328
1332
  return jsonContent(redactSecrets({
1329
1333
  ...inventory.asObject(),
@@ -1351,9 +1355,10 @@ export function createServer() {
1351
1355
  openWorldHint: true,
1352
1356
  },
1353
1357
  }, async ({ text, timeoutMs, lang, sessionId, requestId, context, replySettleMs, emptyReplyWaitMs, ...runtime }) => {
1354
- return withRuntimeClient(runtime, async (client) => {
1358
+ return withRuntimeClient(runtime, async (client, signal) => {
1355
1359
  const builtContext = context ? buildClientContext({}, context) : undefined;
1356
1360
  const reply = await client.ask(text, {
1361
+ signal,
1357
1362
  timeoutMs: clampTimeout(timeoutMs),
1358
1363
  lang,
1359
1364
  sessionId,
@@ -1381,8 +1386,9 @@ export function createServer() {
1381
1386
  },
1382
1387
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
1383
1388
  }, async ({ text, timeoutMs, lang, sessionId, requestId, queryId, context, replySettleMs, ...runtime }) => {
1384
- return withRuntimeClient(runtime, async (client) => {
1389
+ return withRuntimeClient(runtime, async (client, signal) => {
1385
1390
  const reply = await client.query(text, {
1391
+ signal,
1386
1392
  timeoutMs: clampTimeout(timeoutMs), lang, sessionId, requestId, queryId,
1387
1393
  context: context ? buildClientContext({}, context) : undefined, replySettleMs,
1388
1394
  });
@@ -1408,7 +1414,9 @@ export function createServer() {
1408
1414
  openWorldHint: true,
1409
1415
  },
1410
1416
  }, async ({ payload, title, lang, sessionId, requestId, context, ...runtime }) => {
1411
- return withRuntimeClient(runtime, async (client) => {
1417
+ return withRuntimeClient(runtime, async (client, signal) => {
1418
+ await client.connect(undefined, signal);
1419
+ throwIfRuntimeCancelled(signal);
1412
1420
  await client.sendAction(payload, {
1413
1421
  title,
1414
1422
  lang,
@@ -1439,7 +1447,9 @@ export function createServer() {
1439
1447
  openWorldHint: true,
1440
1448
  },
1441
1449
  }, async ({ value, kind, label, lang, sessionId, requestId, context, ...runtime }) => {
1442
- return withRuntimeClient(runtime, async (client) => {
1450
+ return withRuntimeClient(runtime, async (client, signal) => {
1451
+ await client.connect(undefined, signal);
1452
+ throwIfRuntimeCancelled(signal);
1443
1453
  await client.sendCode(value, {
1444
1454
  kind,
1445
1455
  label,
@@ -1467,7 +1477,9 @@ export function createServer() {
1467
1477
  openWorldHint: true,
1468
1478
  },
1469
1479
  }, async ({ eventType, data, context, ...runtime }) => {
1470
- return withRuntimeClient(runtime, async (client) => {
1480
+ return withRuntimeClient(runtime, async (client, signal) => {
1481
+ await client.connect(undefined, signal);
1482
+ throwIfRuntimeCancelled(signal);
1471
1483
  await client.emit(eventType, data, context ? buildClientContext({}, context) : undefined);
1472
1484
  return textContent("Event emitted.");
1473
1485
  });
@@ -1488,8 +1500,9 @@ export function createServer() {
1488
1500
  openWorldHint: true,
1489
1501
  },
1490
1502
  }, async ({ eventName, timeoutMs, sessionId, requestId, context, ...runtime }) => {
1491
- return withRuntimeClient(runtime, async (client) => {
1503
+ return withRuntimeClient(runtime, async (client, signal) => {
1492
1504
  const event = await client.waitForEvent(eventName, {
1505
+ signal,
1493
1506
  timeoutMs: clampTimeout(timeoutMs),
1494
1507
  sessionId,
1495
1508
  requestId,
@@ -3,6 +3,7 @@ interface RuntimeClosable {
3
3
  close(): Promise<void>;
4
4
  waitForClosed(): Promise<void>;
5
5
  }
6
- /** A caller timeout never releases an identity before actual cleanup settles. */
7
- export declare function withRuntimeLease<T, C extends RuntimeClosable>(key: string, createClient: () => C, run: (client: C) => Promise<T>, acquireTimeoutMs?: number): Promise<T>;
6
+ export declare function throwIfRuntimeCancelled(signal?: AbortSignal): void;
7
+ /** Caller cancellation/deadlines never release an identity before owned work and cleanup settle. */
8
+ export declare function withRuntimeLease<T, C extends RuntimeClosable>(key: string, createClient: () => C, run: (client: C) => Promise<T>, acquireTimeoutMs?: number, signal?: AbortSignal): Promise<T>;
8
9
  export {};
@@ -1,6 +1,17 @@
1
1
  const leases = new Map();
2
- /** A caller timeout never releases an identity before actual cleanup settles. */
3
- export async function withRuntimeLease(key, createClient, run, acquireTimeoutMs = 6000) {
2
+ function cancellationError() {
3
+ // MCP cancellation reasons are caller supplied and may contain credentials.
4
+ const error = new Error("Runtime tool request cancelled.");
5
+ error.name = "AbortError";
6
+ return error;
7
+ }
8
+ export function throwIfRuntimeCancelled(signal) {
9
+ if (signal?.aborted)
10
+ throw cancellationError();
11
+ }
12
+ /** Caller cancellation/deadlines never release an identity before owned work and cleanup settle. */
13
+ export async function withRuntimeLease(key, createClient, run, acquireTimeoutMs = 6000, signal) {
14
+ throwIfRuntimeCancelled(signal);
4
15
  if (!Number.isFinite(acquireTimeoutMs) || acquireTimeoutMs <= 0) {
5
16
  throw new Error("Runtime identity acquisition deadline expired.");
6
17
  }
@@ -8,8 +19,7 @@ export async function withRuntimeLease(key, createClient, run, acquireTimeoutMs
8
19
  let release;
9
20
  let fail;
10
21
  const current = new Promise((resolve, reject) => { release = resolve; fail = reject; });
11
- // A failed cleanup poisons this identity's barrier. Observe it even when no
12
- // later caller arrives; later tools fail instead of replacing that session.
22
+ // Observe poisoned barriers even when no later caller arrives.
13
23
  void current.catch(() => undefined);
14
24
  leases.set(key, current);
15
25
  const releaseIdentity = () => {
@@ -20,45 +30,68 @@ export async function withRuntimeLease(key, createClient, run, acquireTimeoutMs
20
30
  const failIdentity = () => {
21
31
  fail(new Error("Previous runtime session cleanup failed; this identity remains unavailable."));
22
32
  };
23
- let timer;
24
- try {
25
- await Promise.race([
26
- previous,
27
- new Promise((_, reject) => {
28
- timer = setTimeout(() => reject(new Error("Runtime identity acquisition deadline expired; previous session cleanup is still pending.")), acquireTimeoutMs);
29
- }),
30
- ]);
31
- }
32
- catch (error) {
33
- // An expired waiter never runs or closes its unused client. Its place in
34
- // the chain remains occupied until the prior owner actually retires.
35
- void previous.then(releaseIdentity, failIdentity);
36
- throw error;
37
- }
38
- finally {
39
- clearTimeout(timer);
40
- }
41
- let client;
42
- try {
43
- client = createClient();
44
- }
45
- catch (error) {
46
- releaseIdentity();
47
- throw error;
48
- }
33
+ let onAbort;
34
+ const cancelled = new Promise((_, reject) => {
35
+ onAbort = () => reject(cancellationError());
36
+ signal?.addEventListener("abort", onAbort, { once: true });
37
+ });
38
+ // The signal may fire during a synchronous client factory or cleanup callback.
39
+ // Observe it even when execution fails before the final race is installed.
40
+ void cancelled.catch(() => undefined);
49
41
  try {
50
- return await run(client);
51
- }
52
- finally {
53
- let closing;
42
+ let timer;
43
+ try {
44
+ await Promise.race([
45
+ previous, cancelled,
46
+ new Promise((_, reject) => {
47
+ timer = setTimeout(() => reject(new Error("Runtime identity acquisition deadline expired; previous session cleanup is still pending.")), acquireTimeoutMs);
48
+ }),
49
+ ]);
50
+ throwIfRuntimeCancelled(signal);
51
+ }
52
+ catch (error) {
53
+ // An abandoned waiter never constructs a client or runs later. Its place
54
+ // in the chain remains occupied until the prior owner actually retires.
55
+ void previous.then(releaseIdentity, failIdentity);
56
+ throw error;
57
+ }
58
+ finally {
59
+ clearTimeout(timer);
60
+ }
61
+ let client;
54
62
  try {
55
- closing = client.close();
63
+ client = createClient();
56
64
  }
57
65
  catch (error) {
58
- failIdentity();
66
+ releaseIdentity();
59
67
  throw error;
60
68
  }
61
- void client.waitForClosed().then(releaseIdentity, failIdentity);
62
- await closing;
69
+ const owned = (async () => {
70
+ try {
71
+ throwIfRuntimeCancelled(signal);
72
+ return await run(client);
73
+ }
74
+ finally {
75
+ let closing;
76
+ try {
77
+ closing = client.close();
78
+ // Observe close independently if waitForClosed throws synchronously.
79
+ void closing.catch(() => undefined);
80
+ void client.waitForClosed().then(releaseIdentity, failIdentity);
81
+ }
82
+ catch (error) {
83
+ failIdentity();
84
+ throw error;
85
+ }
86
+ await closing;
87
+ }
88
+ })();
89
+ // SDK methods accepting a signal can retire promptly. Other admitted work
90
+ // must finish naturally before close begins; cancellation never replays it.
91
+ // The race observes both branches, including late work/cleanup rejections.
92
+ return await Promise.race([owned, cancelled]);
93
+ }
94
+ finally {
95
+ signal?.removeEventListener("abort", onAbort);
63
96
  }
64
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thalovant/mcp-server",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
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.9",
67
+ "@thalovant/sdk": "^0.3.14",
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.20",
10
+ "version": "0.1.22",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@thalovant/mcp-server",
15
- "version": "0.1.20",
15
+ "version": "0.1.22",
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.20",
22
+ "identifier": "ghcr.io/thalovant/thalovant-mcp:0.1.22",
23
23
  "transport": {
24
24
  "type": "streamable-http",
25
25
  "url": "http://127.0.0.1:3000/mcp"