@thalovant/mcp-server 0.7.0 → 0.7.2

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,8 +1,20 @@
1
- Last Edit: Claude Opus 5.5 - 2026-11-03 - Motive: 0.7.0 chat apps tools; date the published 0.6.6.
1
+ Last Edit: Claude Opus 5.5 - 2026-11-07 - Motive: Version 0.7.2, dated for its tag: the skill_unavailable_unpublished hint (thalovant-api#495).
2
2
 
3
3
  # Changelog
4
4
 
5
- ## 0.7.0 - Unreleased
5
+ ## 0.7.2 - 2026-11-07
6
+
7
+ - **A skill with no published release is explained by its code.** thalovant-api#495 adds a second reason the store refuses a skill: `unpublished`, when no installable release of it exists. Installing one answers 409 `skill_unavailable_unpublished` with `reason: unpublished`, before any paid-plan 402, and it got the generic 409 hint, which talks about a version already installed. It now says the skill cannot be installed because no release has been published, that no plan, scope, version or retry changes that and there is nothing to buy, that a non-admin token does not see it in `thalovant_list_marketplace_skills`, and that `thalovant_list_runtime_group_marketplace` lists it only for a group that already has it, with `unavailable_reason` and `installable` and `purchase_required` false. Any other `skill_unavailable_*` code gets the store's refusal in general terms instead of the version hint. `thalovant_list_runtime_group_marketplace`'s description names the field. Three tests drive the hub and runtime-group installs and an unknown code through the fake control plane.
8
+ - The parity contract refreshes the evidence hashes of `src/index.ts` and `test/provisioning.test.ts`.
9
+ - `package.json`, `package-lock.json`, `server.json`, the OCI label, the README and this file move to 0.7.2 together.
10
+
11
+ ## 0.7.1 - 2026-11-06
12
+
13
+ - **Two refusals explained by their code.** A skill the store does not install for licensing reasons (YouTube, YouTube Music, Spotify, SoundCloud and Bandcamp; the API's 409 `skill_unavailable_licensing` with `reason: licensing`, declared with the launch data-protection change) got the generic 409 hint, which talks about a version already installed. It now says the service's terms do not allow a hosted assistant to play its content, that no plan, scope, version or retry changes that, that radio and aggregator skills install as usual, and that `thalovant_list_marketplace_skills` reports it per skill as `unavailable_reason` and `installable`. A memory create or update refused with 409 `memory_limit` (the plan's remembered items are used up; a Free plan keeps one) carried no hint at all, because the memory tools never went through the hint path; `thalovant_create_memory_item` and `thalovant_update_memory_item` now do, under a profile of their own that adds a hint for that code alone (the hub-centric scope and plan hints would misdescribe the memory routes), naming the `limit` and `upgrade_plan` fields, the delete tool that frees a slot, the update tool that changes the item that exists, and that granting a scope will not help. Two tests drive both through the fake control plane.
14
+ - **The real-transport test gets a 90 s budget.** `test/runtime-noise.test.ts` opens eight HTTPS sessions against a local hub, each with its own Noise handshake over a polled connection, and takes about 22 s on a workstation; its 30 s budget ran out on a shared runner twice (`main`, 2026-11-03 and 2026-11-06), passing on the rerun both times. The budget is for the runner, not the calls: every call keeps its own `timeoutMs`, and no assertion changes.
15
+ - `package.json`, `package-lock.json`, `server.json`, the OCI label, the README and this file move to 0.7.1 together.
16
+
17
+ ## 0.7.0 - 2026-11-03
6
18
 
7
19
  A hub can answer in a chat network through a chat app: Telegram and Discord, and Matrix, Mattermost and Twitch as beta once the API lists them. These tools let an agent read and look after the bridges a person connected in the console. Existing tool schemas are unchanged.
8
20
 
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- Last Edit: Claude Opus 5.5 - 2026-11-03 - Motive: Chat apps tools (connectors, bridges, pause/resume, chats, test message, allowance) for 0.7.0.
1
+ Last Edit: Claude Opus 5.5 - 2026-11-07 - Motive: Version 0.7.2, which carries the skill_unavailable_unpublished hint.
2
2
 
3
3
  # Thalovant MCP Server
4
4
 
@@ -89,7 +89,7 @@ Scope is not the whole story for provisioning. Every hub and runtime-group write
89
89
  - A correctly scoped token on a free plan fails `402 API access requires a paid plan.`
90
90
  - `thalovant_install_runtime_group_skill` can fail with a **second, distinct** 402, `This skill requires paid marketplace access for the tenant plan.`, when the plan is paid but does not include `access_tier: paid` catalog entries.
91
91
 
92
- Discovery is deliberately not paid-gated: **a free-tier token can browse the marketplace catalog and set hub ratings, but cannot install skills or provision hubs.** Use `thalovant_list_runtime_group_marketplace` before installing — it reports `installable`, `purchase_required`, and `access_message` per skill, which turns an opaque 402 into a decision you can make up front.
92
+ Discovery is deliberately not paid-gated: **a free-tier token can browse the marketplace catalog and set hub ratings, but cannot install skills or provision hubs.** Use `thalovant_list_runtime_group_marketplace` before installing — it reports `installable`, `purchase_required`, and `access_message` per skill, which turns an opaque 402 into a decision you can make up front. A skill the store does not install carries `unavailable_reason` (`licensing` or `unpublished`) with `installable` and `purchase_required` false, and is listed there only for a group that already has it.
93
93
 
94
94
  Grant fewer scopes for narrower deployments: a read-only assistant needs only `hubs:read` and `memory:read`, and a discovery-only agent that browses skills but never provisions needs `hubs:read` alone. `thalovant_get_analytics_overview` with `admin: true` additionally requires an admin account with `admin:analytics`, which API tokens for regular use should not carry. Runtime hub tools (`thalovant_ask`, `thalovant_send_action`, and friends) use Thalovant client identities, not control-plane tokens.
95
95
 
@@ -103,7 +103,7 @@ Grant fewer scopes for narrower deployments: a read-only assistant needs only `h
103
103
 
104
104
  `thalovant_list_hub_skills` returns the whole `GET /v1/hubs/{hub_id}/skills` envelope: `hub_id`, `runtime_group_id`, `observed_at`, `source`, the runtime's phase and message, and `data`, one row per skill (possibly empty) with `skill`, `title`, `marketplace_skill_id`, `package_name`, `source_type`, `install_source`, `version`, `version_pin`, `installed_version`, `observed_version`, `previous_version`, `latest_version`, `available_version`, `update_available`, `changelog`, `active`, `state`, the runtime's phase, message and last error, and `last_transition_at`. `state` is one of `pending`, `installed`, `failed`, `removing`, `drifted`, `quarantined`, or `unmanaged`; a change in progress shows as `pending`.
105
105
 
106
- Each write answers `202` with `operation_id`, `hub_id`, `runtime_group_id`, `skill`, `version` (`null` for a removal), `previous_version`, and `state` (`installing`, `updating`, or `removing`); pass `wait: true` to poll that operation every 2 s until it converges (`installed`, or `removed` for a removal; a `failed` or `timed_out` operation raises an error carrying its `error_message`), with a 120 s default `timeoutMs`, or follow it yourself with `thalovant_get_operation`. Installing a skill that is already installed at another version performs an update; the same version fails `409` with code `skill_version_already_installed`. A hub with no runtime group fails `404` with code `hub_without_runtime_group` (a plain `404` means an unknown hub or a skill that is not installed), and an unresolvable `latest` or an invalid version fails `422`. Errors are RFC 7807 problem bodies; the tool error keeps the short `message`, which appends the root `code` in parentheses, for example `Thalovant API request failed with HTTP 409: Skill version already installed. (skill_version_already_installed)`, and then the code and the body's other fields (see [Tool Errors](#tool-errors)).
106
+ Each write answers `202` with `operation_id`, `hub_id`, `runtime_group_id`, `skill`, `version` (`null` for a removal), `previous_version`, and `state` (`installing`, `updating`, or `removing`); pass `wait: true` to poll that operation every 2 s until it converges (`installed`, or `removed` for a removal; a `failed` or `timed_out` operation raises an error carrying its `error_message`), with a 120 s default `timeoutMs`, or follow it yourself with `thalovant_get_operation`. Installing a skill that is already installed at another version performs an update; the same version fails `409` with code `skill_version_already_installed`. A hub with no runtime group fails `404` with code `hub_without_runtime_group` (a plain `404` means an unknown hub or a skill that is not installed), and an unresolvable `latest` or an invalid version fails `422`. Errors are RFC 7807 problem bodies; the tool error keeps the short `message`, which appends the root `code` in parentheses, for example `Thalovant API request failed with HTTP 409: Skill version already installed. (skill_version_already_installed)`, and then the code and the body's other fields (see [Tool Errors](#tool-errors)). A skill the store does not install for licensing reasons (YouTube, YouTube Music, Spotify, SoundCloud and Bandcamp: the service's terms do not allow a hosted assistant to play its content) fails `409` with code `skill_unavailable_licensing` and `reason: licensing`, which `thalovant_list_marketplace_skills` announces per skill as `unavailable_reason` and `installable`; no plan, scope or version changes that. A skill with no published release fails `409` with code `skill_unavailable_unpublished` and `reason: unpublished`, before any paid-plan `402`: it cannot be installed or bought until a release is published. A non-admin token does not see such a skill in `thalovant_list_marketplace_skills` at all.
107
107
 
108
108
  Listing needs `hubs:inspect` (implied by `hubs:read`); the writes need `hubs:write` and a paid plan, and because scope is checked before plan a free-plan token sees `403`, never `402`. Hub-restricted tokens (a `hub_ids` allowlist) are honoured on all four routes. The server uses the published Node SDK hub-skill methods and preserves MCP cancellation checks during polling. A failed status read retains the accepted operation ID; use `thalovant_get_operation` with that ID instead of submitting the write again. No new poll starts at or after the polling deadline. That deadline does not cancel an HTTP request already in flight.
109
109
 
@@ -517,6 +517,17 @@ fields: {"allowed_images":{"bus":[...],"core":[...]},"allowed_repositories":{"co
517
517
 
518
518
  A hint may follow for statuses that need one. A `platform_image_required` or
519
519
  `plan_limit` 403 gets its own hint rather than the general scope hint.
520
+ A 409 `skill_unavailable_licensing` says the store refuses the skill for its
521
+ licensing and where the catalog announces it, instead of the version hint. A
522
+ 409 `skill_unavailable_unpublished` says the skill cannot be installed because
523
+ no release of it has been published, that no plan, scope, version or retry
524
+ changes that and there is nothing to buy, and where the listings show it; any
525
+ other `skill_unavailable_*` code gets the store's refusal in general terms. A
526
+ memory create or update refused with 409 `memory_limit` (the plan's remembered
527
+ items are used up; a Free plan keeps one, `limit` and `upgrade_plan` are in the
528
+ fields) names the delete tool that frees a slot, the update tool that changes
529
+ the item that exists, and the plan to move to; other memory refusals carry the
530
+ SDK's message alone.
520
531
 
521
532
  ## Request hints, embedded audio, and configuration merging
522
533
 
@@ -589,7 +600,7 @@ Runtime calls sharing the same hub client identity run sequentially within one
589
600
  MCP process. Use a distinct client identity for each independently running MCP
590
601
  server so the hub can keep their sessions separate.
591
602
 
592
- Version 0.7.0 requires `@thalovant/sdk` `^0.10.0` (0.10.0 through versions below 0.11.0), which carries the chat app methods and `ThalovantApiError.fields`. Since 0.5.1 the server enforces
603
+ Version 0.7.2 requires `@thalovant/sdk` `^0.10.0` (0.10.0 through versions below 0.11.0), which carries the chat app methods and `ThalovantApiError.fields`. Since 0.5.1 the server enforces
593
604
  secure effective MQTT URLs and carries a single connection deadline through
594
605
  MQTT setup and HTTP failure cleanup. Runtime tools support
595
606
  HiveMind v3 Noise over WSS, HTTPS and MQTT over TLS. `thalovant_healthcheck`
package/dist/index.js CHANGED
@@ -259,11 +259,21 @@ function releaseOptionsFrom(args) {
259
259
  const SCOPE_HINT_403 = 'HTTP 403 "Insufficient scopes" — the token lacks the scope this route needs: hubs:write for provisioning, hubs:read for the marketplace catalog, hubs:inspect for runtime-group and hub runtime views (hubs:write implies hubs:read, which implies hubs:inspect and hubs:preview). The API checks scope BEFORE the paid-plan gate, so a 403 can mask a plan problem and granting the scope may surface a 402 next. Free-plan API tokens are capped at hubs:read, clients:read, and clients:write, so a free-tier API token fails provisioning with this 403 and never reaches the 402. A 403 here can also mean the caller does not own the resource ("Ownership required"), or a plan restriction such as HUB_AUTOSCALING_NOT_INCLUDED or a custom hub domain the plan does not allow.';
260
260
  const PLATFORM_IMAGE_HINT_403 = 'HTTP 403 platform_image_required — only a platform administrator can run an image the platform does not release. This is not a scope problem: pick, for each key in refused_images, one of that key\'s allowed_images, or any tag or digest of its allowed_repositories entry (both are in the fields above), and retry.';
261
261
  const PLAN_LIMIT_HINT_403 = 'HTTP 403 plan_limit — the plan\'s allowance for the named resource is used up (see resource, used and limit in the fields above). Granting a scope will not help: free one (delete or reuse an existing one) or move the workspace to a larger plan.';
262
+ const LICENSING_HINT_409 = "HTTP 409 skill_unavailable_licensing — the store does not install this skill on Thalovant: the service's terms do not allow a hosted assistant to play its content (YouTube, YouTube Music, Spotify, SoundCloud and Bandcamp; the problem's reason says licensing). No plan, scope or version changes that, and retrying will not either; radio and aggregator skills such as TuneIn, SomaFM and News install as usual. thalovant_list_marketplace_skills reports this per skill as unavailable_reason and installable (false), so check them before installing.";
263
+ const UNPUBLISHED_HINT_409 = 'HTTP 409 skill_unavailable_unpublished — this skill cannot be installed: no release of it has been published yet, so there is nothing a runtime could install (the problem\'s reason says unpublished). No plan, scope, version or retry changes that, and it cannot be bought either; it becomes installable only once a release is published. A non-admin token does not see it in thalovant_list_marketplace_skills at all (an admin\'s lists it with unavailable_reason "unpublished" and installable false), and thalovant_list_runtime_group_marketplace lists it only for a group that already has it, with unavailable_reason, installable false and purchase_required false.';
264
+ /** Any other `skill_unavailable_*` refusal: a reason the API may add later. */
265
+ const SKILL_UNAVAILABLE_HINT_409 = "HTTP 409 skill_unavailable — the store does not install this skill on Thalovant; the problem's reason and sentence say why. No plan, scope, version or retry changes that, and there is nothing to buy. thalovant_list_runtime_group_marketplace reports such a skill with unavailable_reason, and installable false, for a group that already has it.";
266
+ const MEMORY_LIMIT_HINT_409 = "HTTP 409 memory_limit — the plan's remembered items are used up (see limit in the fields above; a Free plan keeps one). Free a slot with thalovant_delete_memory_item, change the item that exists with thalovant_update_memory_item, or move the workspace to the plan upgrade_plan names (Home); granting a scope will not help.";
262
267
  const PLAN_HINT_402 = 'HTTP 402 "API access requires a paid plan." — the token is valid and correctly scoped but the tenant is on the free plan. Free-tier callers can browse the catalog (thalovant_list_marketplace_skills) and set hub ratings, but cannot create, update, release, or delete hubs and runtime groups, and cannot install skills.';
263
268
  const MARKETPLACE_HINT_402 = 'HTTP 402 "This skill requires paid marketplace access for the tenant plan." — a DIFFERENT 402 from the plan gate: the plan is paid enough to provision, but this catalog entry has access_tier "paid" and the plan does not include paid marketplace skills. thalovant_list_runtime_group_marketplace reports this per skill as purchase_required, installable, and access_message; check it before installing.';
264
269
  const HUB_SKILL_HINT_422 = 'HTTP 422 — the requested hub skill version is unusable: "latest" could not be resolved to a published version of that skill, or the version string is not a valid version. Check the skill name and its published versions with thalovant_list_marketplace_skills, or pass an explicit x.y.z.';
265
270
  const ETAG_HINT_412 = 'HTTP 412 "ETag mismatch" — the If-Match etag was missing or stale and nothing changed. Re-fetch the hub with thalovant_get_hub, read the `etag` field from the response BODY (there is no ETag response header), and retry with that exact value. The comparison is exact string equality, so do not rewrite or weaken the value.';
266
271
  function controlPlaneErrorHint(profile, status, body, code) {
272
+ if (profile === "memoryWrite") {
273
+ // The memory routes have their own scopes and refusals, which the hub-centric
274
+ // hints below would misdescribe; the one worth a hint is the plan's limit.
275
+ return status === 409 && code === "memory_limit" ? MEMORY_LIMIT_HINT_409 : undefined;
276
+ }
267
277
  switch (status) {
268
278
  case 402:
269
279
  return (profile === "skillInstall" || profile === "hubSkillInstall") && /marketplace/i.test(body) ? MARKETPLACE_HINT_402 : PLAN_HINT_402;
@@ -280,6 +290,14 @@ function controlPlaneErrorHint(profile, status, body, code) {
280
290
  ? "HTTP 404 (hub_without_runtime_group) — the hub exists but has no runtime group attached yet, so it cannot hold skills; attach it to a runtime group (thalovant_update_hub) before managing its skills. A 404 without that code means the hub is unknown, or the named skill is not installed on it."
281
291
  : undefined;
282
292
  case 409:
293
+ // A skill the store refuses answers 409 before any paid-plan 402, and is
294
+ // never the version clash the generic hint below describes.
295
+ if (code === "skill_unavailable_licensing")
296
+ return LICENSING_HINT_409;
297
+ if (code === "skill_unavailable_unpublished")
298
+ return UNPUBLISHED_HINT_409;
299
+ if (code?.startsWith("skill_unavailable_"))
300
+ return SKILL_UNAVAILABLE_HINT_409;
283
301
  if (profile === "hubWrite") {
284
302
  return 'HTTP 409 — for a hub create this is either a duplicate ("Hub with name ... already exists for this owner" / "Hub slug ... already exists"), an autoscaling slot limit (AUTOSCALING_HUB_LIMIT_REACHED), or "Idempotency key re-used with different payload". Only hub create honors Idempotency-Key: retrying an identical create is safe and returns the original hub, but reusing a key with a changed body conflicts — use a new idempotencyKey for a genuinely different hub.';
285
303
  }
@@ -1990,7 +2008,7 @@ export function createServer() {
1990
2008
  const { loginScope, apiUrl, ...payload } = args;
1991
2009
  const api = await createControlPlane({ loginScope, apiUrl });
1992
2010
  ensureAuthenticated(api);
1993
- return jsonContent(redactSecrets(await api.createMemoryItem(payload)));
2011
+ return jsonContent(redactSecrets(await callControlPlane("memoryWrite", () => api.createMemoryItem(payload))));
1994
2012
  });
1995
2013
  registerThalovantTool(server, "thalovant_update_memory_item", {
1996
2014
  title: "Update Memory Item",
@@ -2019,7 +2037,7 @@ export function createServer() {
2019
2037
  const { loginScope, apiUrl, memoryId, ...payload } = args;
2020
2038
  const api = await createControlPlane({ loginScope, apiUrl });
2021
2039
  ensureAuthenticated(api);
2022
- return jsonContent(redactSecrets(await api.updateMemoryItem(memoryId, payload)));
2040
+ return jsonContent(redactSecrets(await callControlPlane("memoryWrite", () => api.updateMemoryItem(memoryId, payload))));
2023
2041
  });
2024
2042
  registerThalovantTool(server, "thalovant_delete_memory_item", {
2025
2043
  title: "Delete Memory Item",
@@ -2070,7 +2088,7 @@ export function createServer() {
2070
2088
  });
2071
2089
  registerThalovantTool(server, "thalovant_list_runtime_group_marketplace", {
2072
2090
  title: "List Runtime Group Marketplace",
2073
- description: "List the marketplace catalog resolved against one runtime group. This is the view to read immediately before installing: every catalog entry comes back with the group's own state folded in — whether the skill is desired (active, version_pin, source_type), whether it was observed running (observed_source, observed_at, intent counts), operator status, and the plan verdict (purchase_required, installable, access_message). Check installable and purchase_required here to avoid a 402 at install time. Requires the hubs:inspect scope; browsing needs no paid plan. Answers 404 for an unknown group and 403 when the caller does not own it.",
2091
+ description: "List the marketplace catalog resolved against one runtime group. This is the view to read immediately before installing: every catalog entry comes back with the group's own state folded in — whether the skill is desired (active, version_pin, source_type), whether it was observed running (observed_source, observed_at, intent counts), operator status, and the plan verdict (purchase_required, installable, access_message). Check installable and purchase_required here to avoid a 402 at install time. A skill the store does not install carries unavailable_reason (licensing or unpublished) with installable and purchase_required false, and is listed only when the group already has it. Requires the hubs:inspect scope; browsing needs no paid plan. Answers 404 for an unknown group and 403 when the caller does not own it.",
2074
2092
  inputSchema: {
2075
2093
  ...controlPlaneSchema,
2076
2094
  runtimeGroupId: z.string().min(1).describe("Runtime group UUID."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thalovant/mcp-server",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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",
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.7.0",
10
+ "version": "0.7.2",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@thalovant/mcp-server",
15
- "version": "0.7.0",
15
+ "version": "0.7.2",
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.7.0",
22
+ "identifier": "ghcr.io/thalovant/thalovant-mcp:0.7.2",
23
23
  "transport": {
24
24
  "type": "streamable-http",
25
25
  "url": "http://127.0.0.1:3000/mcp"