@thalovant/mcp-server 0.1.22 → 0.2.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 +18 -0
- package/README.md +40 -3
- package/dist/index.js +140 -2
- package/package.json +2 -2
- package/server.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-09-12
|
|
4
|
+
|
|
5
|
+
- Add a read-only hub skill history tool using Node SDK 0.4.0. The catalog now has 46 default tools, 23 in read-only mode, and 48 with destructive tools enabled.
|
|
6
|
+
- Correct shared-runtime skill scope and token requirements.
|
|
7
|
+
|
|
8
|
+
## 0.1.24
|
|
9
|
+
|
|
10
|
+
- Require Node SDK 0.3.16 and use its public hub-skill methods instead of the temporary duplicate HTTP and polling implementation. Preserve tool schemas, accepted-operation output, API guidance and MCP cancellation checks.
|
|
11
|
+
- Stop starting status reads once the polling budget expires. Keep the accepted operation ID visible when polling fails or the operation reports failure, so callers can inspect the existing operation without repeating the write. The polling deadline does not cancel an HTTP request already in flight.
|
|
12
|
+
- Add real stdio regressions for failed status reads, deadline boundaries and cancellation; retain the 45-tool default catalog across stdio and Streamable HTTP.
|
|
13
|
+
|
|
14
|
+
## 0.1.23
|
|
15
|
+
|
|
16
|
+
- Add hub-scoped skill management: `thalovant_list_hub_skills` (read-only, `hubs:inspect`), `thalovant_install_hub_skill`, `thalovant_update_hub_skill` and `thalovant_remove_hub_skill` (`hubs:write`, paid plan) act on one hub rather than a runtime group's skill set; changes apply live on the hub in about 15 seconds. The list tool 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` rows with a `pending|installed|failed|removing|drifted|quarantined|unmanaged` state). Writes answer with the accepted operation (`operation_id`, `hub_id`, `runtime_group_id`, `skill`, `version`, `previous_version`, `state`) and take `wait` to poll it every 2 s until it converges (`installed`/`removed`, or an error carrying the operation's `error_message`), honoring MCP request cancellation and a 120 s default `timeoutMs`. Installing an already-installed skill at another version performs an update.
|
|
17
|
+
- Surface the API's RFC 7807 problem codes: the tool error keeps the short message and appends the root `code`, e.g. `HTTP 409: Skill version already installed. (skill_version_already_installed)`, and the guidance explains `409 skill_version_already_installed`, `404 hub_without_runtime_group`, and a `422` for an unresolvable `latest` or invalid version on install.
|
|
18
|
+
- Call `GET/POST /v1/hubs/{hub_id}/skills` and `PATCH/DELETE /v1/hubs/{hub_id}/skills/{skill}` directly, with the SDK's header, HTTPS-or-loopback and redirect conventions, and poll with the SDK's `getOperation`, until the Node SDK ships these methods (`@thalovant/sdk` >= 0.3.15). Every route and response type (the final contract of thalovant-api #253) lives in one marked block in `src/index.ts`.
|
|
19
|
+
- The default catalogue now contains 45 tools (22 in read-only mode, 47 with destructive tools enabled). Existing tool schemas are unchanged.
|
|
20
|
+
|
|
3
21
|
## 0.1.22
|
|
4
22
|
|
|
5
23
|
- 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.
|
package/README.md
CHANGED
|
@@ -57,8 +57,8 @@ Minimum scopes for the full control-plane tool surface:
|
|
|
57
57
|
| Scope | Used by |
|
|
58
58
|
|-------|---------|
|
|
59
59
|
| `hubs:read` | `thalovant_list_hubs`, `thalovant_get_hub`, `thalovant_get_analytics_overview`, `thalovant_list_marketplace_skills`, `thalovant_list_runtime_groups`, `thalovant_get_runtime_group`, `thalovant_get_runtime_group_config`, and the hub lookup inside `thalovant_create_client_identity` |
|
|
60
|
-
| `hubs:inspect` | `thalovant_get_hub_runtime_capabilities`, `thalovant_list_runtime_group_marketplace`, `thalovant_list_runtime_group_inventory` |
|
|
61
|
-
| `hubs:write` | All hub and runtime-group provisioning: `thalovant_create_hub`, `thalovant_update_hub`, `thalovant_release_hub`, `thalovant_create_runtime_group`, `thalovant_update_runtime_group`, `thalovant_update_runtime_group_config`, `thalovant_release_runtime_group`, `thalovant_install_runtime_group_skill`, `thalovant_uninstall_runtime_group_skill`, the hub rating tools, and the opt-in delete tools |
|
|
60
|
+
| `hubs:inspect` | `thalovant_get_hub_runtime_capabilities`, `thalovant_list_runtime_group_marketplace`, `thalovant_list_runtime_group_inventory`, `thalovant_list_hub_skills`, `thalovant_list_hub_skill_history` |
|
|
61
|
+
| `hubs:write` | All hub and runtime-group provisioning: `thalovant_create_hub`, `thalovant_update_hub`, `thalovant_release_hub`, `thalovant_create_runtime_group`, `thalovant_update_runtime_group`, `thalovant_update_runtime_group_config`, `thalovant_release_runtime_group`, `thalovant_install_runtime_group_skill`, `thalovant_uninstall_runtime_group_skill`, the per-hub `thalovant_install_hub_skill`, `thalovant_update_hub_skill`, `thalovant_remove_hub_skill`, the hub rating tools, and the opt-in delete tools |
|
|
62
62
|
| `clients:write` | `thalovant_create_client_identity` (`POST /v1/clients`) |
|
|
63
63
|
| `memory:read` | `thalovant_list_memory_items`, `thalovant_get_memory_summary`, `thalovant_get_memory_item` |
|
|
64
64
|
| `memory:write` | `thalovant_create_memory_item`, `thalovant_update_memory_item`, `thalovant_delete_memory_item` |
|
|
@@ -79,6 +79,16 @@ Grant fewer scopes for narrower deployments: a read-only assistant needs only `h
|
|
|
79
79
|
|
|
80
80
|
`thalovant_update_hub` and `thalovant_delete_hub` use optimistic locking and require the hub's current etag, sent as `If-Match`. The etag is only available in the **body** of the hub resource — the API sends no `ETag` response header — so an agent must call `thalovant_get_hub` first and pass the `etag` field from that response. A missing or stale value fails `412 ETag mismatch` and changes nothing; re-fetch and retry. `name`, `namespace`, and `domain` are immutable after creation, so `thalovant_update_hub` does not accept them at all; send only the fields you are changing rather than round-tripping a whole hub resource. Runtime-group writes do not use etags.
|
|
81
81
|
|
|
82
|
+
### Hub Skills
|
|
83
|
+
|
|
84
|
+
`thalovant_list_hub_skills`, `thalovant_install_hub_skill`, `thalovant_update_hub_skill`, and `thalovant_remove_hub_skill` select a hub's attached runtime group; all hubs sharing it are affected. A hub can start with no skills at all and gain them one at a time; a change applies live on the hub in about 15 seconds with no restart. `hubId` must be the hub UUID — the authenticated hub routes reject slugs.
|
|
85
|
+
|
|
86
|
+
`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`.
|
|
87
|
+
|
|
88
|
+
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` and appends the root `code` in parentheses, for example `Thalovant API request failed with HTTP 409: Skill version already installed. (skill_version_already_installed)`.
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
82
92
|
### Login Fallback
|
|
83
93
|
|
|
84
94
|
```bash
|
|
@@ -358,6 +368,14 @@ Hub and runtime-group provisioning:
|
|
|
358
368
|
- `thalovant_install_runtime_group_skill`
|
|
359
369
|
- `thalovant_uninstall_runtime_group_skill`
|
|
360
370
|
|
|
371
|
+
Hub skills, acting on the hub’s shared runtime (see [Hub Skills](#hub-skills); the list tool is read-only):
|
|
372
|
+
|
|
373
|
+
- `thalovant_list_hub_skills`
|
|
374
|
+
- `thalovant_list_hub_skill_history`
|
|
375
|
+
- `thalovant_install_hub_skill`
|
|
376
|
+
- `thalovant_update_hub_skill`
|
|
377
|
+
- `thalovant_remove_hub_skill`
|
|
378
|
+
|
|
361
379
|
Destructive, **not registered unless explicitly enabled** (see [Destructive Tools](#destructive-tools)):
|
|
362
380
|
|
|
363
381
|
- `thalovant_delete_hub`
|
|
@@ -419,7 +437,7 @@ Runtime calls sharing the same hub client identity run sequentially within one
|
|
|
419
437
|
MCP process. Use a distinct client identity for each independently running MCP
|
|
420
438
|
server so the hub can keep their sessions separate.
|
|
421
439
|
|
|
422
|
-
Version 0.
|
|
440
|
+
Version 0.2.0 uses `@thalovant/sdk` `^0.4.0` (0.4.0 through versions below 0.5.0). This release enforces
|
|
423
441
|
secure effective MQTT URLs and carries a single connection deadline through
|
|
424
442
|
MQTT setup and HTTP failure cleanup. Runtime tools support
|
|
425
443
|
HiveMind v3 Noise over WSS, HTTPS and MQTT over TLS. `thalovant_healthcheck`
|
|
@@ -493,3 +511,22 @@ options, and returns the same normalized reply shape as `thalovant_ask`. A query
|
|
|
493
511
|
may trigger actions, so read-only mode hides it. Conversation workflows use
|
|
494
512
|
`sessionId` with ask/query and the existing event-wait tool; MCP does not expose
|
|
495
513
|
one tool for every SDK conversation or event-listener method.
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
### Shared-runtime skill management
|
|
517
|
+
|
|
518
|
+
The catalog has 46 default tools, 23 in read-only mode, and 48 with destructive tools enabled.
|
|
519
|
+
|
|
520
|
+
Hub-addressed skill methods select the runtime group attached to the hub UUID.
|
|
521
|
+
Every hub sharing that group sees the same skill changes and history. The API
|
|
522
|
+
requires a restricted token to cover all served hubs. Reads need `hubs:inspect`
|
|
523
|
+
(`hubs:read` implies it); writes need `hubs:write`, an eligible paid plan and ownership.
|
|
524
|
+
|
|
525
|
+
The history response contains newest-first `event` and `operation` entries,
|
|
526
|
+
including nullable actor/version fields. Its limit is 1–200 (50 where omitted).
|
|
527
|
+
An accepted mutation is not proof the skill is ready. Optional waiting polls the
|
|
528
|
+
operation, with a 120-second default timeout and two-second interval. Polling
|
|
529
|
+
never repeats an accepted mutation and starts no new read after its deadline;
|
|
530
|
+
an already-running HTTP request retains its normal request timeout.
|
|
531
|
+
|
|
532
|
+
Read history with `thalovant_list_hub_skill_history` (`hubId`, optional `limit`). This tool remains available in read-only mode.
|
package/dist/index.js
CHANGED
|
@@ -211,20 +211,27 @@ function releaseOptionsFrom(args) {
|
|
|
211
211
|
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.';
|
|
212
212
|
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.';
|
|
213
213
|
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.';
|
|
214
|
+
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.';
|
|
214
215
|
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.';
|
|
215
216
|
function controlPlaneErrorHint(profile, status, body) {
|
|
216
217
|
switch (status) {
|
|
217
218
|
case 402:
|
|
218
|
-
return profile === "skillInstall" && /marketplace/i.test(body) ? MARKETPLACE_HINT_402 : PLAN_HINT_402;
|
|
219
|
+
return (profile === "skillInstall" || profile === "hubSkillInstall") && /marketplace/i.test(body) ? MARKETPLACE_HINT_402 : PLAN_HINT_402;
|
|
219
220
|
case 403:
|
|
220
221
|
return SCOPE_HINT_403;
|
|
222
|
+
case 404:
|
|
223
|
+
return /hub_without_runtime_group/.test(body)
|
|
224
|
+
? "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."
|
|
225
|
+
: undefined;
|
|
221
226
|
case 409:
|
|
222
227
|
if (profile === "hubWrite") {
|
|
223
228
|
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.';
|
|
224
229
|
}
|
|
225
|
-
return "HTTP 409 — the resource is not in a state that allows this call: a hub with no connected client cannot report runtime capabilities, a runtime group cannot be deleted while it is the workspace default or still has hubs attached,
|
|
230
|
+
return "HTTP 409 — the resource is not in a state that allows this call: a hub with no connected client cannot report runtime capabilities, a runtime group cannot be deleted while it is the workspace default or still has hubs attached, a deactivated marketplace skill cannot be installed, and a hub skill install answers 409 with code skill_version_already_installed when the skill is already installed at exactly that version (POST with a DIFFERENT version performs an update, as does thalovant_update_hub_skill).";
|
|
226
231
|
case 412:
|
|
227
232
|
return ETAG_HINT_412;
|
|
233
|
+
case 422:
|
|
234
|
+
return profile === "hubSkillInstall" ? HUB_SKILL_HINT_422 : undefined;
|
|
228
235
|
default:
|
|
229
236
|
return undefined;
|
|
230
237
|
}
|
|
@@ -249,6 +256,39 @@ async function callControlPlane(profile, run) {
|
|
|
249
256
|
throw new Error(`${message}\n\n${hint}`);
|
|
250
257
|
}
|
|
251
258
|
}
|
|
259
|
+
const HUB_SKILL_DEFAULT_TIMEOUT_MS = 120_000;
|
|
260
|
+
const HUB_SKILL_MAX_TIMEOUT_MS = 600_000;
|
|
261
|
+
function sleepWithSignal(ms, signal) {
|
|
262
|
+
return new Promise((resolve, reject) => {
|
|
263
|
+
if (signal?.aborted) {
|
|
264
|
+
reject(new Error("The request was cancelled."));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const timer = setTimeout(() => {
|
|
268
|
+
signal?.removeEventListener("abort", onAbort);
|
|
269
|
+
resolve();
|
|
270
|
+
}, ms);
|
|
271
|
+
function onAbort() {
|
|
272
|
+
clearTimeout(timer);
|
|
273
|
+
reject(new Error("The request was cancelled."));
|
|
274
|
+
}
|
|
275
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/** Keep MCP cancellation attached to the SDK-owned operation polling. */
|
|
279
|
+
function hubSkillWaitOptions(wait, timeoutMs) {
|
|
280
|
+
const signal = requestContext.getStore()?.signal;
|
|
281
|
+
throwIfRuntimeCancelled(signal);
|
|
282
|
+
return {
|
|
283
|
+
wait: wait ?? false,
|
|
284
|
+
timeoutMs: timeoutMs ?? HUB_SKILL_DEFAULT_TIMEOUT_MS,
|
|
285
|
+
sleep: (ms) => sleepWithSignal(ms, signal),
|
|
286
|
+
now: () => {
|
|
287
|
+
throwIfRuntimeCancelled(signal);
|
|
288
|
+
return Date.now();
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
252
292
|
class HttpError extends Error {
|
|
253
293
|
status;
|
|
254
294
|
constructor(status, message) {
|
|
@@ -2112,6 +2152,104 @@ export function createServer() {
|
|
|
2112
2152
|
await callControlPlane("write", () => api.uninstallRuntimeGroupSkill(runtimeGroupId, skillId));
|
|
2113
2153
|
return textContent("Runtime group skill uninstalled.");
|
|
2114
2154
|
});
|
|
2155
|
+
registerThalovantTool(server, "thalovant_list_hub_skill_history", {
|
|
2156
|
+
title: "List Hub Skill History",
|
|
2157
|
+
description: "Read newest-first skill events and operations for the runtime group attached to a hub. This history is shared by all hubs using that runtime. Requires hubs:inspect (hubs:read implies it); restricted tokens must cover all served hubs. Returns event/operation kind, timestamps, versions, actors and outcome fields. No changes are made.",
|
|
2158
|
+
inputSchema: { ...controlPlaneSchema, hubId: z.string().uuid().describe("Hub UUID (not the slug)."), limit: z.number().int().min(1).max(200).default(50) },
|
|
2159
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
2160
|
+
}, async ({ hubId, limit, ...auth }) => {
|
|
2161
|
+
const api = await createControlPlane(auth);
|
|
2162
|
+
ensureAuthenticated(api);
|
|
2163
|
+
throwIfRuntimeCancelled(requestContext.getStore()?.signal);
|
|
2164
|
+
return jsonContent(redactSecrets(await callControlPlane("read", () => api.listHubSkillHistory(hubId, { limit }))));
|
|
2165
|
+
});
|
|
2166
|
+
registerThalovantTool(server, "thalovant_list_hub_skills", {
|
|
2167
|
+
title: "List Hub Skills",
|
|
2168
|
+
description: "List the skills installed on the runtime group attached to a Thalovant hub. Returns the whole envelope: hub_id, runtime_group_id, observed_at, source, the runtime's phase and message, and data — one row per skill with version, version_pin, installed_version, observed_version, previous_version, latest_version, available_version, update_available, changelog, active, state (pending, installed, failed, removing, drifted, quarantined, unmanaged; a change in progress shows as pending), the runtime's last error and last_transition_at. The hub UUID selects its attached runtime group. Skills and changes are shared by every hub using that group; restricted tokens must cover all served hubs. hubId must be the hub UUID — the authenticated hub routes reject slugs; a hub with no runtime group attached fails 404 with code hub_without_runtime_group. Requires the hubs:inspect scope (hubs:read implies it); hub-restricted tokens only see the hubs in their allowlist.",
|
|
2169
|
+
inputSchema: {
|
|
2170
|
+
...controlPlaneSchema,
|
|
2171
|
+
hubId: z.string().min(1).describe("Hub UUID (not the slug)."),
|
|
2172
|
+
},
|
|
2173
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
2174
|
+
}, async ({ hubId, ...auth }) => {
|
|
2175
|
+
const api = await createControlPlane(auth);
|
|
2176
|
+
ensureAuthenticated(api);
|
|
2177
|
+
throwIfRuntimeCancelled(requestContext.getStore()?.signal);
|
|
2178
|
+
return jsonContent(redactSecrets(await callControlPlane("read", () => api.listHubSkills(hubId))));
|
|
2179
|
+
});
|
|
2180
|
+
registerThalovantTool(server, "thalovant_install_hub_skill", {
|
|
2181
|
+
title: "Install Hub Skill",
|
|
2182
|
+
description: "Install a skill on the runtime group attached to a Thalovant hub. All hubs sharing that runtime group are affected. The change applies live on the hub in about 15 seconds, with no hub restart; a hub may start with no skills and gain them one at a time. Discover the skill name with thalovant_list_marketplace_skills. The API answers 202 with an operation_id and state \"installing\": pass wait: true to poll the operation until it converges (state \"installed\", or an error carrying the operation's failure message), with a 120 s default timeout; otherwise follow it with thalovant_get_operation. The 202 body also carries hub_id, runtime_group_id and previous_version. Installing a skill that is already installed at ANOTHER version performs an update; the SAME version fails 409 with code skill_version_already_installed. Fails 404 with code hub_without_runtime_group when the hub has no runtime group attached yet, and 422 when \"latest\" cannot be resolved or the version string is invalid. hubId must be the hub UUID — the authenticated hub routes reject slugs. Requires the hubs:write scope and a paid plan; scope is checked before plan, so a free-plan token sees 403, never 402. A hub-restricted token must cover every hub sharing the affected runtime group.",
|
|
2183
|
+
inputSchema: {
|
|
2184
|
+
...controlPlaneSchema,
|
|
2185
|
+
hubId: z.string().min(1).describe("Hub UUID (not the slug)."),
|
|
2186
|
+
skill: z.string().min(1).max(191).describe("Skill name, 1-191 characters."),
|
|
2187
|
+
version: z.string().min(1).max(64).optional().describe('Version to install, 1-64 characters. Defaults to "latest".'),
|
|
2188
|
+
wait: z.boolean().optional().describe("Poll the operation until the install converges (installed or failed). Defaults to false."),
|
|
2189
|
+
timeoutMs: z.number().int().min(1_000).max(HUB_SKILL_MAX_TIMEOUT_MS).optional().describe("How long to wait when wait is true, 1000-600000 ms. Defaults to 120000."),
|
|
2190
|
+
},
|
|
2191
|
+
annotations: {
|
|
2192
|
+
readOnlyHint: false,
|
|
2193
|
+
destructiveHint: false,
|
|
2194
|
+
idempotentHint: false,
|
|
2195
|
+
openWorldHint: true,
|
|
2196
|
+
},
|
|
2197
|
+
}, async ({ hubId, skill, version, wait, timeoutMs, ...auth }) => {
|
|
2198
|
+
const api = await createControlPlane(auth);
|
|
2199
|
+
ensureAuthenticated(api);
|
|
2200
|
+
const options = hubSkillWaitOptions(wait, timeoutMs);
|
|
2201
|
+
const result = await callControlPlane("hubSkillInstall", () => api.installHubSkill(hubId, skill, {
|
|
2202
|
+
...options, version: version ?? "latest",
|
|
2203
|
+
}));
|
|
2204
|
+
return jsonContent(redactSecrets({ ...result, operation: result.operation ?? null }));
|
|
2205
|
+
});
|
|
2206
|
+
registerThalovantTool(server, "thalovant_update_hub_skill", {
|
|
2207
|
+
title: "Update Hub Skill",
|
|
2208
|
+
description: "Move a skill already installed on the runtime group attached to a Thalovant hub to a specific version (not a runtime group or skill set). The change applies live on the hub in about 15 seconds, with no hub restart. version is required — read the current version and latest_version from thalovant_list_hub_skills. The API answers 202 with an operation_id, hub_id, runtime_group_id, previous_version and state \"updating\": pass wait: true to poll the operation until it converges (state \"installed\", or an error carrying the operation's failure message), with a 120 s default timeout; otherwise follow it with thalovant_get_operation. Fails 404 when the skill is not installed on the hub (code hub_without_runtime_group when the hub has no runtime group), 409 with code skill_version_already_installed when it is already at that version, and 422 for an invalid version. hubId must be the hub UUID — the authenticated hub routes reject slugs. Requires the hubs:write scope and a paid plan; scope is checked before plan, so a free-plan token sees 403, never 402. A hub-restricted token must cover every hub sharing the affected runtime group.",
|
|
2209
|
+
inputSchema: {
|
|
2210
|
+
...controlPlaneSchema,
|
|
2211
|
+
hubId: z.string().min(1).describe("Hub UUID (not the slug)."),
|
|
2212
|
+
skill: z.string().min(1).max(191).describe("Skill name, 1-191 characters."),
|
|
2213
|
+
version: z.string().min(1).max(64).describe("Required target version, 1-64 characters."),
|
|
2214
|
+
wait: z.boolean().optional().describe("Poll the operation until the update converges (installed or failed). Defaults to false."),
|
|
2215
|
+
timeoutMs: z.number().int().min(1_000).max(HUB_SKILL_MAX_TIMEOUT_MS).optional().describe("How long to wait when wait is true, 1000-600000 ms. Defaults to 120000."),
|
|
2216
|
+
},
|
|
2217
|
+
annotations: {
|
|
2218
|
+
readOnlyHint: false,
|
|
2219
|
+
destructiveHint: false,
|
|
2220
|
+
idempotentHint: true,
|
|
2221
|
+
openWorldHint: true,
|
|
2222
|
+
},
|
|
2223
|
+
}, async ({ hubId, skill, version, wait, timeoutMs, ...auth }) => {
|
|
2224
|
+
const api = await createControlPlane(auth);
|
|
2225
|
+
ensureAuthenticated(api);
|
|
2226
|
+
const options = hubSkillWaitOptions(wait, timeoutMs);
|
|
2227
|
+
const result = await callControlPlane("write", () => api.updateHubSkill(hubId, skill, { ...options, version }));
|
|
2228
|
+
return jsonContent(redactSecrets({ ...result, operation: result.operation ?? null }));
|
|
2229
|
+
});
|
|
2230
|
+
registerThalovantTool(server, "thalovant_remove_hub_skill", {
|
|
2231
|
+
title: "Remove Hub Skill",
|
|
2232
|
+
description: "Remove one skill from the runtime group attached to a Thalovant hub. Every hub sharing the runtime loses this skill; other skill attachments are unchanged, and the change applies live on the hub in about 15 seconds with no hub restart. The API answers 202 with an operation_id, hub_id, runtime_group_id, previous_version (version is null) and state \"removing\": pass wait: true to poll the operation until it converges (state \"removed\", or an error carrying the operation's failure message), with a 120 s default timeout; otherwise follow it with thalovant_get_operation. Fails 404 when the skill is not installed on the hub (code hub_without_runtime_group when the hub has no runtime group). hubId must be the hub UUID — the authenticated hub routes reject slugs. Requires the hubs:write scope and a paid plan; scope is checked before plan, so a free-plan token sees 403, never 402. A hub-restricted token must cover every hub sharing the affected runtime group.",
|
|
2233
|
+
inputSchema: {
|
|
2234
|
+
...controlPlaneSchema,
|
|
2235
|
+
hubId: z.string().min(1).describe("Hub UUID (not the slug)."),
|
|
2236
|
+
skill: z.string().min(1).max(191).describe("Skill name to remove, 1-191 characters."),
|
|
2237
|
+
wait: z.boolean().optional().describe("Poll the operation until the removal converges (removed or failed). Defaults to false."),
|
|
2238
|
+
timeoutMs: z.number().int().min(1_000).max(HUB_SKILL_MAX_TIMEOUT_MS).optional().describe("How long to wait when wait is true, 1000-600000 ms. Defaults to 120000."),
|
|
2239
|
+
},
|
|
2240
|
+
annotations: {
|
|
2241
|
+
readOnlyHint: false,
|
|
2242
|
+
destructiveHint: true,
|
|
2243
|
+
idempotentHint: true,
|
|
2244
|
+
openWorldHint: true,
|
|
2245
|
+
},
|
|
2246
|
+
}, async ({ hubId, skill, wait, timeoutMs, ...auth }) => {
|
|
2247
|
+
const api = await createControlPlane(auth);
|
|
2248
|
+
ensureAuthenticated(api);
|
|
2249
|
+
const options = hubSkillWaitOptions(wait, timeoutMs);
|
|
2250
|
+
const result = await callControlPlane("write", () => api.removeHubSkill(hubId, skill, options));
|
|
2251
|
+
return jsonContent(redactSecrets({ ...result, operation: result.operation ?? null }));
|
|
2252
|
+
});
|
|
2115
2253
|
if (destructiveToolsEnabled()) {
|
|
2116
2254
|
registerThalovantTool(server, "thalovant_delete_hub", {
|
|
2117
2255
|
title: "Delete Hub",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thalovant/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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.
|
|
67
|
+
"@thalovant/sdk": "^0.4.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.
|
|
10
|
+
"version": "0.2.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@thalovant/mcp-server",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.2.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.
|
|
22
|
+
"identifier": "ghcr.io/thalovant/thalovant-mcp:0.2.0",
|
|
23
23
|
"transport": {
|
|
24
24
|
"type": "streamable-http",
|
|
25
25
|
"url": "http://127.0.0.1:3000/mcp"
|