@thalovant/mcp-server 0.2.0 → 0.3.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.3.0 — 2026-09-12
4
+
5
+ - 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.
6
+ - Default runtime configuration tools to revision-guarded merging with at most three attempts and only HTTP 412 retries. Preserve explicit replacement with merge: false. Guarded merging requires hubs:read and paid hubs:write.
7
+ - Cover actual encrypted runtime hints/audio and default conditional-write conflict handling through MCP tool calls. Preserve the existing tool catalog and align npm/OCI/registry versions.
8
+
3
9
  ## 0.2.0 — 2026-09-12
4
10
 
5
11
  - 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.
package/README.md CHANGED
@@ -56,7 +56,7 @@ Minimum scopes for the full control-plane tool surface:
56
56
 
57
57
  | Scope | Used by |
58
58
  |-------|---------|
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` |
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`, the guarded merge read in `thalovant_update_runtime_group_config`, and the hub lookup inside `thalovant_create_client_identity` |
60
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
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`) |
@@ -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`, and a per-query/batch `timeoutMs`. It returns registered
387
+ `describe`, `fallback`, `speakable`, `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
@@ -393,6 +393,33 @@ adds at most 1500ms. A silent listing can use engine manifests; disable this wit
393
393
  per-principal policy. `thalovant_get_operation` reads an operation ID returned by
394
394
  provisioning without replaying the write.
395
395
 
396
+ ## Request hints, embedded audio, and configuration merging
397
+
398
+ `thalovant_ask` accepts `sttLang`, an ordered `pipeline`, and `location`
399
+ with required `city` and optional `region`, `country`, `timezone`, `latitude`,
400
+ and `longitude`. Hints apply to the current request. Omit unused hints;
401
+ an empty pipeline is ignored, and blank language or city strings are rejected.
402
+
403
+ Ask and Query return language, ordered event metadata, `hasAudio` and
404
+ `droppedMedia`. Embedded hex stays out of text output. Opt into separate MCP
405
+ audio blocks with `includeAudio: true`; unknown formats become embedded binary
406
+ resource blocks. Clip and reply limits are 4 MiB and 16 MiB respectively,
407
+ checked by the SDK before retention. The client owns playback. No skill-supplied
408
+ path or URL is fetched. Inventory `speakable: true` renders examples without
409
+ changing the raw patterns; `slots` supplies illustrative slot values, and
410
+ `exampleLimit` limits examples per language to 1–20 (default 2).
411
+
412
+ Configuration updates default to `merge: true`: GET the revision, deep-merge
413
+ the caller's delta and PUT with `expected_revision`. Only HTTP 412 retries,
414
+ with a fresh read and at most three total attempts. This requires `hubs:read`,
415
+ `hubs:write`, and a paid plan. An older API without revision support fails before
416
+ writing. There is no unconditional fallback. Supplied personas replace;
417
+ omitted personas stay unchanged. For an intentional full replacement, pass
418
+ `merge: false`, requiring `hubs:write`, a paid plan, and coordination with other writers.
419
+ Guarded merges reject integers outside JavaScript's safe integer range in the
420
+ delta or stored configuration before writing. Store large identifiers as strings
421
+ or use an SDK that preserves large JSON integers.
422
+
396
423
  ## Destructive Tools
397
424
 
398
425
  `thalovant_delete_hub` and `thalovant_delete_runtime_group` are **disabled by default**. They are not merely blocked when called — they are never registered, so they do not appear in `tools/list` and a model cannot see or attempt them.
@@ -437,7 +464,7 @@ Runtime calls sharing the same hub client identity run sequentially within one
437
464
  MCP process. Use a distinct client identity for each independently running MCP
438
465
  server so the hub can keep their sessions separate.
439
466
 
440
- Version 0.2.0 uses `@thalovant/sdk` `^0.4.0` (0.4.0 through versions below 0.5.0). This release enforces
467
+ Version 0.3.0 requires `@thalovant/sdk` `^0.5.1` (0.5.1 through versions below 0.6.0). This release enforces
441
468
  secure effective MQTT URLs and carries a single connection deadline through
442
469
  MQTT setup and HTTP failure cleanup. Runtime tools support
443
470
  HiveMind v3 Noise over WSS, HTTPS and MQTT over TLS. `thalovant_healthcheck`
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { runtimeReplyContent } from "./reply-output.js";
2
3
  import { throwIfRuntimeCancelled, withRuntimeLease } from "./runtime-lease.js";
3
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
6
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
7
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
7
- import { DEFAULT_CONTROL_API_URL, ThalovantClient, ThalovantControlPlane, ThalovantIdentity, buildClientContext, } from "@thalovant/sdk";
8
+ import { DEFAULT_CONTROL_API_URL, ThalovantClient, ThalovantControlPlane, ThalovantIdentity, buildClientContext, buildLocation, } from "@thalovant/sdk";
8
9
  import { AsyncLocalStorage } from "node:async_hooks";
9
10
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
10
11
  import { createServer as createHttpServer } from "node:http";
@@ -718,21 +719,6 @@ async function withRuntimeClient(options, run) {
718
719
  ])).digest("hex");
719
720
  return withRuntimeLease(key, () => new ThalovantClient(identity, { protocol: options.protocol ?? "wss" }), client => run(client, signal), 6000, signal);
720
721
  }
721
- function summarizeReply(reply) {
722
- const displayItems = reply.displayItems({ maxTextChars: 1_000 });
723
- return {
724
- text: reply.text,
725
- displayText: reply.displayText,
726
- utterances: reply.utterances,
727
- handled: reply.handled,
728
- ok: reply.ok,
729
- sessionId: reply.sessionId,
730
- requestId: reply.requestId,
731
- displayItems,
732
- events: reply.events.map((event) => redactSecrets(event.asObject())),
733
- failureEvent: reply.failureEvent ? redactSecrets(reply.failureEvent.asObject()) : undefined,
734
- };
735
- }
736
722
  /**
737
723
  * Base directory that saved client-identity files are confined to.
738
724
  *
@@ -1360,17 +1346,23 @@ export function createServer() {
1360
1346
  inputSchema: {
1361
1347
  ...runtimeAuthSchema,
1362
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."),
1350
+ slots: z.record(z.string().max(500)).optional().describe("Example values for named intent slots."),
1351
+ exampleLimit: z.number().int().min(1).max(20).default(2),
1363
1352
  describe: z.boolean().default(true),
1364
1353
  fallback: z.boolean().default(true).describe("Try engine manifests if the intent listing is refused or unanswered."),
1365
1354
  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."),
1366
1355
  },
1367
1356
  annotations: { readOnlyHint: true, openWorldHint: true },
1368
- }, async ({ languages, describe, fallback, timeoutMs, ...runtime }) => withRuntimeClient(runtime, async (client, signal) => {
1357
+ }, async ({ languages, describe, fallback, timeoutMs, speakable, slots, exampleLimit, ...runtime }) => withRuntimeClient(runtime, async (client, signal) => {
1369
1358
  await client.connect(undefined, signal);
1370
1359
  throwIfRuntimeCancelled(signal);
1371
1360
  const inventory = await client.intents(languages, { describe, fallback, timeoutMs: clampTimeout(timeoutMs) });
1372
1361
  return jsonContent(redactSecrets({
1373
1362
  ...inventory.asObject(),
1363
+ examples: inventory.intents.map(intent => ({ id: intent.id,
1364
+ languages: Object.fromEntries(inventory.languages.map(lang => [lang, intent.examples(lang, exampleLimit, { speakable, slots })])),
1365
+ })),
1374
1366
  may_answer: Object.fromEntries(inventory.languages.map(lang => [lang, inventory.mayAnswer(lang)])),
1375
1367
  }));
1376
1368
  }));
@@ -1381,6 +1373,12 @@ export function createServer() {
1381
1373
  ...runtimeAuthSchema,
1382
1374
  text: z.string().min(1).max(20_000),
1383
1375
  timeoutMs: z.number().int().min(1_000).max(MAX_TIMEOUT_MS).default(DEFAULT_TIMEOUT_MS),
1376
+ sttLang: z.string().trim().min(1).max(100).optional().describe("Language recognized by speech-to-text; the hub validates this hint."),
1377
+ pipeline: z.array(z.string().trim().min(1).max(200)).max(32).optional(),
1378
+ location: z.object({ city: z.string().trim().min(1).max(500), region: z.string().max(500).optional(), country: z.string().max(100).optional(),
1379
+ latitude: z.number().finite().min(-90).max(90).optional(), longitude: z.number().finite().min(-180).max(180).optional(), timezone: z.string().max(200).optional(),
1380
+ }).optional().describe("Request location, preferred over the hub's configured city."),
1381
+ includeAudio: z.boolean().default(false).describe("Include bounded embedded skill clips as MCP audio/resources; default returns metadata without placing audio hex in text."),
1384
1382
  lang: z.string().min(1).optional(),
1385
1383
  sessionId: z.string().min(1).optional(),
1386
1384
  requestId: z.string().min(1).optional(),
@@ -1394,7 +1392,7 @@ export function createServer() {
1394
1392
  idempotentHint: false,
1395
1393
  openWorldHint: true,
1396
1394
  },
1397
- }, async ({ text, timeoutMs, lang, sessionId, requestId, context, replySettleMs, emptyReplyWaitMs, ...runtime }) => {
1395
+ }, async ({ text, timeoutMs, lang, sessionId, requestId, context, replySettleMs, emptyReplyWaitMs, sttLang, pipeline, location, includeAudio, ...runtime }) => {
1398
1396
  return withRuntimeClient(runtime, async (client, signal) => {
1399
1397
  const builtContext = context ? buildClientContext({}, context) : undefined;
1400
1398
  const reply = await client.ask(text, {
@@ -1404,10 +1402,12 @@ export function createServer() {
1404
1402
  sessionId,
1405
1403
  requestId,
1406
1404
  context: builtContext,
1405
+ sttLang, pipeline, location: location ? buildLocation(location) : undefined,
1407
1406
  replySettleMs,
1408
1407
  emptyReplyWaitMs,
1409
1408
  });
1410
- return jsonContent(redactSecrets(summarizeReply(reply)));
1409
+ const output = runtimeReplyContent(reply, includeAudio);
1410
+ return { content: [...jsonContent(redactSecrets(output.summary)).content, ...output.clips] };
1411
1411
  });
1412
1412
  });
1413
1413
  registerThalovantTool(server, "thalovant_query", {
@@ -1417,6 +1417,7 @@ export function createServer() {
1417
1417
  ...runtimeAuthSchema,
1418
1418
  text: z.string().trim().min(1).max(20_000),
1419
1419
  timeoutMs: z.number().int().min(1_000).max(MAX_TIMEOUT_MS).default(DEFAULT_TIMEOUT_MS),
1420
+ includeAudio: z.boolean().default(false).describe("Include bounded embedded skill clips as MCP audio/resources."),
1420
1421
  lang: z.string().min(1).optional(),
1421
1422
  sessionId: z.string().min(1).optional(),
1422
1423
  requestId: z.string().min(1).optional(),
@@ -1425,14 +1426,15 @@ export function createServer() {
1425
1426
  replySettleMs: z.number().int().min(0).max(10_000).optional(),
1426
1427
  },
1427
1428
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
1428
- }, async ({ text, timeoutMs, lang, sessionId, requestId, queryId, context, replySettleMs, ...runtime }) => {
1429
+ }, async ({ text, timeoutMs, lang, sessionId, requestId, queryId, context, replySettleMs, includeAudio, ...runtime }) => {
1429
1430
  return withRuntimeClient(runtime, async (client, signal) => {
1430
1431
  const reply = await client.query(text, {
1431
1432
  signal,
1432
1433
  timeoutMs: clampTimeout(timeoutMs), lang, sessionId, requestId, queryId,
1433
1434
  context: context ? buildClientContext({}, context) : undefined, replySettleMs,
1434
1435
  });
1435
- return jsonContent(redactSecrets(summarizeReply(reply)));
1436
+ const output = runtimeReplyContent(reply, includeAudio);
1437
+ return { content: [...jsonContent(redactSecrets(output.summary)).content, ...output.clips] };
1436
1438
  });
1437
1439
  });
1438
1440
  registerThalovantTool(server, "thalovant_send_action", {
@@ -2030,7 +2032,7 @@ export function createServer() {
2030
2032
  });
2031
2033
  registerThalovantTool(server, "thalovant_get_runtime_group_config", {
2032
2034
  title: "Get Runtime Group Config",
2033
- description: "Read a Thalovant runtime group's runtime configuration and personas. Requires the hubs:read scope. Read this before thalovant_update_runtime_group_config so you know what the merge will land on.",
2035
+ description: "Read a Thalovant runtime group's runtime configuration and personas. Requires the hubs:read scope. The revision returned by this call is used by safe configuration merges.",
2034
2036
  inputSchema: {
2035
2037
  ...controlPlaneSchema,
2036
2038
  runtimeGroupId: z.string().min(1).describe("Runtime group UUID."),
@@ -2046,23 +2048,24 @@ export function createServer() {
2046
2048
  });
2047
2049
  registerThalovantTool(server, "thalovant_update_runtime_group_config", {
2048
2050
  title: "Update Runtime Group Config",
2049
- description: "Merge runtime configuration into a Thalovant runtime group. The API MERGES config into the stored configuration rather than replacing it, and marks the group pending so the runtime operator reconciles the change. personas, when provided, is REPLACED wholesale rather than merged; omit it to leave stored personas untouched. Requires the hubs:write scope and a paid plan.",
2051
+ description: "Merge runtime configuration into a Thalovant runtime group. The SDK reads and deep-merges config, then writes with a revision precondition. Only explicit HTTP 412 conflicts retry, at most three attempts; older APIs fail before a write. Set merge=false for explicit unconditional replacement. personas, when provided, is REPLACED wholesale rather than merged; omit it to leave stored personas untouched. Requires hubs:read and paid hubs:write for merging; replacement needs paid hubs:write.",
2050
2052
  inputSchema: {
2051
2053
  ...controlPlaneSchema,
2052
2054
  runtimeGroupId: z.string().min(1).describe("Runtime group UUID."),
2055
+ merge: z.boolean().default(true).describe("False explicitly replaces the full configuration without a revision precondition."),
2053
2056
  config: jsonRecordSchema.describe("Configuration object merged into the stored configuration. Required."),
2054
2057
  personas: jsonRecordSchema.optional().describe("Replaces the stored personas outright. Left untouched when omitted."),
2055
2058
  },
2056
2059
  annotations: {
2057
2060
  readOnlyHint: false,
2058
- destructiveHint: false,
2061
+ destructiveHint: true,
2059
2062
  idempotentHint: false,
2060
2063
  openWorldHint: true,
2061
2064
  },
2062
- }, async ({ runtimeGroupId, config, personas, ...auth }) => {
2065
+ }, async ({ runtimeGroupId, config, personas, merge, ...auth }) => {
2063
2066
  const api = await createControlPlane(auth);
2064
2067
  ensureAuthenticated(api);
2065
- return jsonContent(redactSecrets(await callControlPlane("write", () => api.updateRuntimeGroupConfig(runtimeGroupId, config, { personas }))));
2068
+ return jsonContent(redactSecrets(await callControlPlane("write", () => api.updateRuntimeGroupConfig(runtimeGroupId, config, { personas, merge }))));
2066
2069
  });
2067
2070
  registerThalovantTool(server, "thalovant_release_runtime_group", {
2068
2071
  title: "Release Runtime Group",
@@ -0,0 +1,45 @@
1
+ import type { ThalovantReply } from "@thalovant/sdk";
2
+ /** Keep embedded skill bytes out of the language model's text context. */
3
+ export declare function runtimeReplyContent(reply: ThalovantReply, includeAudio?: boolean): {
4
+ summary: {
5
+ text: string;
6
+ displayText: string;
7
+ utterances: string[];
8
+ handled: boolean;
9
+ ok: boolean;
10
+ sessionId: string | undefined;
11
+ requestId: string | undefined;
12
+ lang: string | undefined;
13
+ hasAudio: boolean;
14
+ droppedMedia: number;
15
+ displayItems: import("@thalovant/sdk").ThalovantDisplayItem[];
16
+ events: {
17
+ data: {
18
+ [x: string]: unknown;
19
+ };
20
+ }[];
21
+ media: {
22
+ eventIndex: number;
23
+ byteLength?: number;
24
+ mimeType?: string;
25
+ error?: string;
26
+ }[];
27
+ failureEvent: {
28
+ data: {
29
+ [x: string]: unknown;
30
+ };
31
+ } | undefined;
32
+ };
33
+ clips: ({
34
+ type: "audio";
35
+ data: string;
36
+ mimeType: string;
37
+ } | {
38
+ type: "resource";
39
+ resource: {
40
+ uri: string;
41
+ mimeType: string;
42
+ blob: string;
43
+ };
44
+ })[];
45
+ };
@@ -0,0 +1,42 @@
1
+ import { createHash } from "node:crypto";
2
+ function eventSummary(event) {
3
+ const { binary_data: _encoded, ...data } = event.data;
4
+ return { ...event.asObject(), data };
5
+ }
6
+ /** Keep embedded skill bytes out of the language model's text context. */
7
+ export function runtimeReplyContent(reply, includeAudio = false) {
8
+ const clips = [];
9
+ const media = [];
10
+ const events = reply.events.map((event, eventIndex) => {
11
+ const output = eventSummary(event);
12
+ if (!event.isAudio)
13
+ return output;
14
+ try {
15
+ const bytes = Buffer.from(event.audioBytes());
16
+ const mimeType = bytes.subarray(0, 4).toString() === "RIFF" && bytes.subarray(8, 12).toString() === "WAVE" ? "audio/wav"
17
+ : bytes.subarray(0, 4).toString() === "OggS" ? "audio/ogg"
18
+ : bytes.subarray(0, 4).toString() === "fLaC" ? "audio/flac"
19
+ : bytes.subarray(0, 3).toString() === "ID3" ? "audio/mpeg" : "application/octet-stream";
20
+ media.push({ eventIndex, byteLength: bytes.length, mimeType });
21
+ if (includeAudio) {
22
+ const encoded = bytes.toString("base64");
23
+ clips.push(mimeType.startsWith("audio/") ? { type: "audio", data: encoded, mimeType } : {
24
+ type: "resource", resource: { uri: `thalovant-audio://sha256/${createHash("sha256").update(bytes).digest("hex")}`, mimeType, blob: encoded },
25
+ });
26
+ }
27
+ }
28
+ catch {
29
+ media.push({ eventIndex, error: "Missing, invalid or oversized embedded audio." });
30
+ }
31
+ return output;
32
+ });
33
+ return { summary: {
34
+ text: reply.text, displayText: reply.displayText, utterances: reply.utterances,
35
+ handled: reply.handled, ok: reply.ok, sessionId: reply.sessionId, requestId: reply.requestId,
36
+ lang: reply.lang, hasAudio: reply.hasAudio ?? false, droppedMedia: reply.droppedMedia ?? 0,
37
+ displayItems: reply.displayItems({ maxTextChars: 1_000 }), events, media,
38
+ // A failure reference must not bypass text sanitization or add a clip
39
+ // outside the SDK's admitted event sequence and aggregate media budget.
40
+ failureEvent: reply.failureEvent ? eventSummary(reply.failureEvent) : undefined,
41
+ }, clips };
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thalovant/mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.3.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.4.0",
67
+ "@thalovant/sdk": "^0.5.1",
68
68
  "jose": "^6.2.8",
69
69
  "zod": "^3.25.76"
70
70
  },
package/server.json CHANGED
@@ -7,19 +7,19 @@
7
7
  "url": "https://github.com/thalovant/thalovant-mcp",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.2.0",
10
+ "version": "0.3.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@thalovant/mcp-server",
15
- "version": "0.2.0",
15
+ "version": "0.3.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.2.0",
22
+ "identifier": "ghcr.io/thalovant/thalovant-mcp:0.3.0",
23
23
  "transport": {
24
24
  "type": "streamable-http",
25
25
  "url": "http://127.0.0.1:3000/mcp"