@thalovant/mcp-server 0.1.7 → 0.1.9

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/.env.example CHANGED
@@ -3,6 +3,9 @@ THALOVANT_PROFILE=prod
3
3
  THALOVANT_API_URL=https://api.thalovant.com
4
4
 
5
5
  # Use one control-plane auth option for private/admin tools.
6
+ # Recommended: a scoped, revocable API token (dashboard or device flow).
7
+ # Minimum scopes for the full tool surface: hubs:read, clients:write, memory:read, memory:write.
8
+ # THALOVANT_API_TOKEN=
6
9
  # THALOVANT_ACCESS_TOKEN=
7
10
  # THALOVANT_EMAIL=
8
11
  # THALOVANT_PASSWORD=
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.9
4
+
5
+ - Added first-class scoped API token auth for the control plane: set `THALOVANT_API_TOKEN` and the server authenticates with the token directly and never calls the login endpoint. Precedence is `THALOVANT_API_TOKEN`, then `THALOVANT_ACCESS_TOKEN`, then the `THALOVANT_EMAIL`/`THALOVANT_PASSWORD` login fallback.
6
+ - `thalovant_config_status` now reports the active control-plane auth mode (`controlPlaneAuthMode`: `api-token`, `access-token`, `email-password`, or `none`) and `hasApiToken`, without revealing token values.
7
+ - Documented API tokens as the recommended auth mode for AI and CI use, including the minimum token scopes for the full control-plane tool surface: `hubs:read`, `clients:write`, `memory:read`, `memory:write`.
8
+ - Clarified the unauthenticated-tool error to name `THALOVANT_API_TOKEN` as the recommended option.
9
+
10
+ ## 0.1.8
11
+
12
+ - Tightened memory tool input validation to match the control-plane API exactly: `title` max 160, `content` max 4096, `source` max 64, `consentScope` max 128, `consentVersion` max 64, `retentionPolicy` max 64 on create/update, and list `query` max 240, so invalid values fail with a clear client-side error instead of an opaque API 422.
13
+ - Capped `thalovant_list_public_hubs` `limit` at 48 to match the public hubs endpoint, which silently clamps larger page sizes; authenticated `thalovant_list_hubs` keeps its limit of 100.
14
+ - Corrected `thalovant_get_hub` and `thalovant_create_client_identity` descriptions: the authenticated hub routes require a hub UUID; slugs are only accepted by the public hub tools.
15
+
3
16
  ## 0.1.7
4
17
 
5
18
  - Updated the MCP transport SDK to 1.30.0, removing the affected Hono adapter dependency range.
package/README.md CHANGED
@@ -29,13 +29,43 @@ npm run build
29
29
 
30
30
  Node.js 20 or newer is required.
31
31
 
32
- ## Local Stdio
32
+ ## Control-Plane Auth
33
33
 
34
34
  Public hub discovery does not need Thalovant credentials. Private control-plane tools and runtime hub tools read credentials only from the MCP server environment or server-side principal credential files. Do not pass API tokens or passwords through chat or tool arguments.
35
35
 
36
+ The server selects control-plane auth in this order:
37
+
38
+ 1. `THALOVANT_API_TOKEN` — a scoped Thalovant API token. Recommended.
39
+ 2. `THALOVANT_ACCESS_TOKEN` — a pre-issued session access token.
40
+ 3. `THALOVANT_EMAIL` + `THALOVANT_PASSWORD` — interactive-account login fallback.
41
+
42
+ When a token is set, the server never calls the login endpoint. `thalovant_config_status` reports the active mode as `controlPlaneAuthMode` without revealing token values.
43
+
44
+ ### API Tokens (Recommended For AI Agents And CI)
45
+
46
+ Scoped API tokens are the right credential for AI and automation use: they are minted from the Thalovant dashboard (or through the device flow), carry only the scopes you grant, can be revoked individually, and never involve your account password or MFA. Tokens start with `tvpat_`.
47
+
48
+ ```bash
49
+ export THALOVANT_API_TOKEN="tvpat_..."
50
+ export THALOVANT_API_URL="https://api.thalovant.com"
51
+
52
+ npm start
53
+ ```
54
+
55
+ Minimum scopes for the full control-plane tool surface:
56
+
57
+ | Scope | Used by |
58
+ |-------|---------|
59
+ | `hubs:read` | `thalovant_list_hubs`, `thalovant_get_hub`, `thalovant_get_analytics_overview`, and the hub lookup inside `thalovant_create_client_identity` |
60
+ | `clients:write` | `thalovant_create_client_identity` (`POST /v1/clients`) |
61
+ | `memory:read` | `thalovant_list_memory_items`, `thalovant_get_memory_summary`, `thalovant_get_memory_item` |
62
+ | `memory:write` | `thalovant_create_memory_item`, `thalovant_update_memory_item`, `thalovant_delete_memory_item` |
63
+
64
+ Grant fewer scopes for narrower deployments: a read-only assistant needs only `hubs:read` and `memory:read`. `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.
65
+
66
+ ### Login Fallback
67
+
36
68
  ```bash
37
- export THALOVANT_ACCESS_TOKEN="..."
38
- # or
39
69
  export THALOVANT_EMAIL="you@example.com"
40
70
  export THALOVANT_PASSWORD="..."
41
71
 
@@ -45,6 +75,10 @@ export THALOVANT_API_URL="https://api.thalovant.com"
45
75
  npm start
46
76
  ```
47
77
 
78
+ If neither a token nor email/password is configured, authenticated control-plane tools fail with a clear error naming the supported options.
79
+
80
+ ## Local Stdio
81
+
48
82
  The server speaks MCP over stdio and does not write logs to stdout.
49
83
 
50
84
  Runtime hub tools load local identities in this order:
@@ -208,6 +242,8 @@ MCP_HTTP_TRUST_PROXY=false
208
242
 
209
243
  ## Claude Desktop
210
244
 
245
+ Recommended: authenticate with a scoped API token so the MCP config never contains your account password.
246
+
211
247
  ```json
212
248
  {
213
249
  "mcpServers": {
@@ -215,6 +251,7 @@ MCP_HTTP_TRUST_PROXY=false
215
251
  "command": "node",
216
252
  "args": ["/home/goldyfruit/Development/Thalovant/mcp/dist/index.js"],
217
253
  "env": {
254
+ "THALOVANT_API_TOKEN": "tvpat_...",
218
255
  "THALOVANT_PROFILE": "prod"
219
256
  }
220
257
  }
@@ -233,6 +270,7 @@ Use the same stdio command in your MCP client config:
233
270
  "command": "node",
234
271
  "args": ["/home/goldyfruit/Development/Thalovant/mcp/dist/index.js"],
235
272
  "env": {
273
+ "THALOVANT_API_TOKEN": "tvpat_...",
236
274
  "THALOVANT_PROFILE": "prod"
237
275
  }
238
276
  }
package/dist/index.js CHANGED
@@ -14,10 +14,11 @@ import { mkdir } from "node:fs/promises";
14
14
  import { fileURLToPath, pathToFileURL } from "node:url";
15
15
  import { createRemoteJWKSet, jwtVerify } from "jose";
16
16
  import { z } from "zod";
17
- const VERSION = "0.1.7";
17
+ const VERSION = "0.1.9";
18
18
  const DEFAULT_TIMEOUT_MS = 30_000;
19
19
  const MAX_TIMEOUT_MS = 300_000;
20
20
  const MAX_LIMIT = 100;
21
+ const MAX_PUBLIC_HUBS_LIMIT = 48;
21
22
  const DEFAULT_HTTP_BODY_LIMIT_BYTES = 1_048_576;
22
23
  const DEFAULT_HTTP_RATE_LIMIT_MAX = 120;
23
24
  const DEFAULT_HTTP_RATE_LIMIT_WINDOW_MS = 60_000;
@@ -48,7 +49,14 @@ const SECRET_KEYS = [
48
49
  "secret",
49
50
  ];
50
51
  const protocolSchema = z.enum(["wss", "https", "mqtt"]);
51
- const limitSchema = z.number().int().min(1).max(MAX_LIMIT).default(25);
52
+ const limitSchema = z.number().int().min(1).max(MAX_LIMIT).default(25).describe("Page size, 1-100.");
53
+ const publicHubsLimitSchema = z
54
+ .number()
55
+ .int()
56
+ .min(1)
57
+ .max(MAX_PUBLIC_HUBS_LIMIT)
58
+ .default(25)
59
+ .describe("Page size, 1-48. The public hubs endpoint caps page size at 48.");
52
60
  const optionalCursorSchema = z.string().min(1).optional();
53
61
  const jsonRecordSchema = z.record(z.unknown());
54
62
  const controlPlaneSchema = {
@@ -415,7 +423,8 @@ async function createControlPlane(options) {
415
423
  const credential = await credentialForPrincipal(principal);
416
424
  const canUseShared = allowSharedThalovantCredentials(principal);
417
425
  const apiUrl = options.apiUrl ?? credential?.control?.apiUrl ?? (canUseShared ? process.env.THALOVANT_API_URL : undefined) ?? DEFAULT_CONTROL_API_URL;
418
- const accessToken = credential?.control?.accessToken ?? (canUseShared ? process.env.THALOVANT_ACCESS_TOKEN : undefined);
426
+ const accessToken = credential?.control?.accessToken ??
427
+ (canUseShared ? process.env.THALOVANT_API_TOKEN ?? process.env.THALOVANT_ACCESS_TOKEN : undefined);
419
428
  const api = new ThalovantControlPlane(apiUrl, {
420
429
  accessToken,
421
430
  userAgent: `thalovant-mcp/${VERSION}`,
@@ -431,9 +440,18 @@ async function createControlPlane(options) {
431
440
  }
432
441
  function ensureAuthenticated(api) {
433
442
  if (!api.accessToken) {
434
- throw new Error("This tool requires Thalovant API auth. Configure THALOVANT_ACCESS_TOKEN, THALOVANT_EMAIL/THALOVANT_PASSWORD, or per-principal Thalovant credentials for this MCP principal.");
443
+ throw new Error("This tool requires Thalovant API auth. Configure THALOVANT_API_TOKEN (recommended: a scoped, revocable API token), THALOVANT_ACCESS_TOKEN, THALOVANT_EMAIL/THALOVANT_PASSWORD, or per-principal Thalovant credentials for this MCP principal.");
435
444
  }
436
445
  }
446
+ function controlPlaneAuthModeFromEnv() {
447
+ if (process.env.THALOVANT_API_TOKEN)
448
+ return "api-token";
449
+ if (process.env.THALOVANT_ACCESS_TOKEN)
450
+ return "access-token";
451
+ if (process.env.THALOVANT_EMAIL && process.env.THALOVANT_PASSWORD)
452
+ return "email-password";
453
+ return "none";
454
+ }
437
455
  async function createRuntimeClient(options) {
438
456
  const protocol = options.protocol ?? "wss";
439
457
  const principal = currentPrincipal();
@@ -888,6 +906,8 @@ export function createServer() {
888
906
  },
889
907
  }, async () => jsonContent({
890
908
  apiUrl: process.env.THALOVANT_API_URL ?? DEFAULT_CONTROL_API_URL,
909
+ controlPlaneAuthMode: controlPlaneAuthModeFromEnv(),
910
+ hasApiToken: Boolean(process.env.THALOVANT_API_TOKEN),
891
911
  hasAccessToken: Boolean(process.env.THALOVANT_ACCESS_TOKEN),
892
912
  hasEmailPassword: Boolean(process.env.THALOVANT_EMAIL && process.env.THALOVANT_PASSWORD),
893
913
  profile: process.env.THALOVANT_PROFILE,
@@ -897,9 +917,9 @@ export function createServer() {
897
917
  }));
898
918
  registerThalovantTool(server, "thalovant_list_public_hubs", {
899
919
  title: "List Public Hubs",
900
- description: "List Thalovant public hubs. This read-only discovery call does not require authentication.",
920
+ description: "List Thalovant public hubs. This read-only discovery call does not require authentication. Page size is capped at 48.",
901
921
  inputSchema: {
902
- limit: limitSchema,
922
+ limit: publicHubsLimitSchema,
903
923
  cursor: optionalCursorSchema,
904
924
  apiUrl: controlPlaneSchema.apiUrl,
905
925
  },
@@ -946,10 +966,13 @@ export function createServer() {
946
966
  });
947
967
  registerThalovantTool(server, "thalovant_get_hub", {
948
968
  title: "Get Hub",
949
- description: "Fetch one authenticated Thalovant hub by id.",
969
+ description: "Fetch one authenticated Thalovant hub by UUID.",
950
970
  inputSchema: {
951
971
  ...controlPlaneSchema,
952
- hubId: z.string().min(1),
972
+ hubId: z
973
+ .string()
974
+ .min(1)
975
+ .describe("Hub UUID. This authenticated route rejects slugs; slugs are only accepted by the public hub tools (thalovant_get_public_hub)."),
953
976
  },
954
977
  annotations: {
955
978
  readOnlyHint: true,
@@ -965,7 +988,10 @@ export function createServer() {
965
988
  description: "Create a Thalovant client identity for a hub. The identity is secret; output is redacted unless savePath is used.",
966
989
  inputSchema: {
967
990
  ...controlPlaneSchema,
968
- hubId: z.string().min(1).describe("Hub id or slug accepted by the Thalovant SDK."),
991
+ hubId: z
992
+ .string()
993
+ .min(1)
994
+ .describe("Hub UUID. This authenticated route rejects slugs; slugs are only accepted by the public hub tools (thalovant_get_public_hub)."),
969
995
  name: z.string().min(1).max(128),
970
996
  siteId: z.string().min(1).optional(),
971
997
  ownerId: z.string().min(1).optional(),
@@ -1259,7 +1285,7 @@ export function createServer() {
1259
1285
  kind: z.enum(["note", "preference", "fact"]).optional(),
1260
1286
  ownerId: z.string().min(1).optional(),
1261
1287
  hubId: z.string().min(1).optional(),
1262
- query: z.string().min(1).optional(),
1288
+ query: z.string().min(1).max(240).optional(),
1263
1289
  includeDeleted: z.boolean().optional(),
1264
1290
  includeExpired: z.boolean().optional(),
1265
1291
  limit: limitSchema,
@@ -1314,16 +1340,16 @@ export function createServer() {
1314
1340
  ...controlPlaneSchema,
1315
1341
  scope: z.enum(["personal", "workspace", "hub"]).optional(),
1316
1342
  kind: z.enum(["note", "preference", "fact"]).optional(),
1317
- title: z.string().max(512).nullable().optional(),
1318
- content: z.string().min(1).max(20_000),
1343
+ title: z.string().max(160).nullable().optional(),
1344
+ content: z.string().min(1).max(4_096),
1319
1345
  tags: z.array(z.string().min(1)).optional(),
1320
1346
  ownerId: z.string().min(1).optional(),
1321
1347
  hubId: z.string().min(1).optional(),
1322
- source: z.string().min(1).optional(),
1348
+ source: z.string().min(1).max(64).optional(),
1323
1349
  metadata: jsonRecordSchema.optional(),
1324
- consentScope: z.string().min(1).optional(),
1325
- consentVersion: z.string().nullable().optional(),
1326
- retentionPolicy: z.string().min(1).optional(),
1350
+ consentScope: z.string().min(1).max(128).optional(),
1351
+ consentVersion: z.string().max(64).nullable().optional(),
1352
+ retentionPolicy: z.string().min(1).max(64).optional(),
1327
1353
  expiresAt: z.string().nullable().optional(),
1328
1354
  },
1329
1355
  annotations: {
@@ -1345,13 +1371,13 @@ export function createServer() {
1345
1371
  ...controlPlaneSchema,
1346
1372
  memoryId: z.string().min(1),
1347
1373
  kind: z.enum(["note", "preference", "fact"]).optional(),
1348
- title: z.string().max(512).nullable().optional(),
1349
- content: z.string().min(1).max(20_000).optional(),
1374
+ title: z.string().max(160).nullable().optional(),
1375
+ content: z.string().min(1).max(4_096).optional(),
1350
1376
  tags: z.array(z.string().min(1)).optional(),
1351
1377
  metadata: jsonRecordSchema.optional(),
1352
- consentScope: z.string().min(1).optional(),
1353
- consentVersion: z.string().nullable().optional(),
1354
- retentionPolicy: z.string().min(1).optional(),
1378
+ consentScope: z.string().min(1).max(128).optional(),
1379
+ consentVersion: z.string().max(64).nullable().optional(),
1380
+ retentionPolicy: z.string().min(1).max(64).optional(),
1355
1381
  expiresAt: z.string().nullable().optional(),
1356
1382
  clearExpiresAt: z.boolean().optional(),
1357
1383
  },
@@ -4,6 +4,7 @@
4
4
  "command": "node",
5
5
  "args": ["/absolute/path/to/thalovant-mcp/dist/index.js"],
6
6
  "env": {
7
+ "THALOVANT_API_TOKEN": "tvpat_...",
7
8
  "THALOVANT_PROFILE": "prod"
8
9
  }
9
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thalovant/mcp-server",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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",
@@ -69,7 +69,7 @@
69
69
  "zod": "^3.25.76"
70
70
  },
71
71
  "overrides": {
72
- "fast-uri": "3.1.4"
72
+ "fast-uri": "3.1.5"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@types/node": "^22.18.12",
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.7",
10
+ "version": "0.1.9",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@thalovant/mcp-server",
15
- "version": "0.1.7",
15
+ "version": "0.1.9",
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.7",
22
+ "identifier": "ghcr.io/thalovant/thalovant-mcp:0.1.9",
23
23
  "transport": {
24
24
  "type": "streamable-http",
25
25
  "url": "http://127.0.0.1:3000/mcp"