@frontmcp/plugin-dashboard 1.9.6 → 1.9.7

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/README.md CHANGED
@@ -1,46 +1,83 @@
1
1
  # @frontmcp/plugin-dashboard
2
2
 
3
- Dashboard plugin for FrontMCP - provides visual monitoring and introspection of MCP servers.
3
+ A FrontMCP add-on that shows a server's apps, tools, resources and prompts in a browser, as a graph. Use it while
4
+ you build a server, to see what it serves. It has two parts: `DashboardApp`, which you add to `apps` and which
5
+ serves the dashboard, and `DashboardPlugin`, which sets its options. It runs on FrontMCP's Node HTTP server.
4
6
 
5
- ## Installation
7
+ [![npm](https://img.shields.io/npm/v/@frontmcp/plugin-dashboard.svg)](https://www.npmjs.com/package/@frontmcp/plugin-dashboard) [![License](https://img.shields.io/npm/l/@frontmcp/plugin-dashboard.svg)](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
8
+
9
+ ## Install
6
10
 
7
11
  ```bash
8
- npm install @frontmcp/plugin-dashboard
12
+ npm install @frontmcp/plugin-dashboard @frontmcp/sdk
9
13
  ```
10
14
 
15
+ Requires Node.js 24 or later. Keep `@frontmcp/sdk` on the same version as this package.
16
+
11
17
  ## Usage
12
18
 
13
- ```typescript
14
- import { DashboardPlugin } from '@frontmcp/plugin-dashboard';
15
- import { FrontMcp } from '@frontmcp/sdk';
19
+ Add `DashboardApp` to `apps`, and register `DashboardPlugin.init()` for its options:
20
+
21
+ ```ts
22
+ import { DashboardApp, DashboardPlugin } from '@frontmcp/plugin-dashboard';
23
+ import { App, FrontMcp } from '@frontmcp/sdk';
16
24
 
17
- const app = new FrontMcp({
18
- plugins: [DashboardPlugin],
19
- });
25
+ @App({ id: 'help-desk', name: 'Help Desk' })
26
+ class HelpDeskApp {}
27
+
28
+ @FrontMcp({
29
+ info: { name: 'help-desk', version: '1.0.0' },
30
+ apps: [HelpDeskApp, DashboardApp],
31
+ plugins: [DashboardPlugin.init({ auth: { enabled: true, token: process.env.DASHBOARD_TOKEN } })],
32
+ })
33
+ export default class Server {}
20
34
  ```
21
35
 
22
- ## Features
36
+ Then open `/dashboard` on the server's port (`PORT`, else 3000), with the token as `Authorization: Bearer <token>` or
37
+ `?token=<token>`. The page then sets a cookie that its own requests send.
23
38
 
24
- - **Visual Dashboard**: Web-based UI for monitoring MCP servers
25
- - **Graph Visualization**: View tools, resources, and their relationships
26
- - **Introspection Tools**: Built-in tools to list and describe server capabilities
27
- - **Real-time Updates**: Monitor server state in real-time
39
+ - **`GET /dashboard`** answers with an HTML page that loads React, React Flow and dagre from `esm.sh`, so the browser
40
+ needs internet access.
41
+ - **`/dashboard`** is also an MCP endpoint of its own, with three tools that describe the whole server:
42
+ `dashboard:graph`, `dashboard:list-tools` and `dashboard:list-resources`. They aren't listed at the server's main
43
+ endpoint. The page reads from them.
44
+ - **`DashboardPlugin.init()` alone serves nothing.** `DashboardApp` serves the dashboard, with the default options
45
+ when there's no plugin.
46
+ - **It's off in production.** Unless you set `enabled`, the dashboard is on when `NODE_ENV` isn't `production`, and
47
+ answers `404` when it is. To use it in production, set `enabled: true` with `auth`.
28
48
 
29
- ## Configuration
49
+ ## Options
30
50
 
31
- ```typescript
32
- import { DashboardPlugin } from '@frontmcp/plugin-dashboard';
51
+ | Option | Default | Description |
52
+ | ---------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
53
+ | `enabled` | On, unless `NODE_ENV` is `production` | Serve the dashboard. |
54
+ | `basePath` | `'/dashboard'` | Where the page is served. |
55
+ | `auth.enabled`, `auth.token` | Off | Require the token for the page and the MCP endpoint, and answer `401` without it. `enabled` without `token` makes `init()` throw. |
56
+ | `cdn` | `esm.sh` URLs | Where the page loads its libraries from: `entrypoint`, `react`, `reactDom`, `reactDomClient`, `reactJsxRuntime`, `reactRouter`, `xyflow`, `dagre`, `xyflowCss`. |
33
57
 
34
- const app = new FrontMcp({
35
- plugins: [
36
- DashboardPlugin.configure({
37
- basePath: '/dashboard',
38
- enableTools: true,
39
- }),
40
- ],
41
- });
42
- ```
58
+ The dashboard's MCP endpoint applies the server's own authentication first, then the dashboard's `auth`. On a server
59
+ with its own authentication, send the dashboard token in the `x-frontmcp-dashboard-token` header.
60
+
61
+ Dashboard options apply to the whole process: a second `DashboardPlugin.init()` with a different `auth` throws, and
62
+ one with other differences replaces the first for every server, with a warning.
63
+
64
+ ## Docs
65
+
66
+ - [The dashboard](https://frontmcp.dev/reference/plugins#the-dashboard): options, tokens, and the MCP endpoint
67
+ - [Plugins and adapters](https://frontmcp.dev/reference/plugins): every official plugin, and where to register it
68
+
69
+ ## Related packages
70
+
71
+ - [`@frontmcp/sdk`](https://www.npmjs.com/package/@frontmcp/sdk): the framework this add-on extends
72
+ - [`@frontmcp/plugins`](https://www.npmjs.com/package/@frontmcp/plugins): a meta-package that re-exports
73
+ `DashboardApp` and `DashboardPlugin` with three other plugins
74
+
75
+ ## Contributing
76
+
77
+ Source: [`plugins/plugin-dashboard`](https://github.com/agentfront/frontmcp/tree/main/plugins/plugin-dashboard).
78
+ Issues and pull requests are welcome; see
79
+ [CONTRIBUTING.md](https://github.com/agentfront/frontmcp/blob/main/CONTRIBUTING.md).
43
80
 
44
81
  ## License
45
82
 
46
- Apache-2.0
83
+ Apache-2.0. See [LICENSE](https://github.com/agentfront/frontmcp/blob/main/LICENSE).
@@ -36,22 +36,21 @@ declare class DashboardHttpPlugin extends DynamicPlugin<DashboardPluginOptions,
36
36
  * - Real-time event streaming via SSE (built into FrontMCP)
37
37
  * - Access to server scope for monitoring
38
38
  *
39
- * The dashboard UI is loaded from CDN (esm.sh by default) and connects
40
- * to the dashboard via MCP protocol over SSE transport.
39
+ * The dashboard UI is loaded from CDN (esm.sh by default) and reads the server's
40
+ * structure from the dashboard's own MCP endpoint, at the dashboard's path.
41
41
  *
42
42
  * @example
43
43
  * ```typescript
44
- * import { DashboardApp } from '@frontmcp/plugins';
44
+ * import { DashboardApp } from '@frontmcp/plugin-dashboard';
45
45
  *
46
- * @FrontMCP({
47
- * name: 'my-server',
48
- * apps: [DashboardApp],
46
+ * @FrontMcp({
47
+ * info: { name: 'my-server', version: '1.0.0' },
48
+ * apps: [MyApp, DashboardApp],
49
49
  * })
50
50
  * class MyServer {}
51
51
  * ```
52
52
  *
53
- * Then access the dashboard at `http://localhost:3000/dashboard`
54
- * The dashboard connects via SSE at the standard `/sse` endpoint.
53
+ * Then open the dashboard at `http://localhost:3000/dashboard`. Register `DashboardPlugin.init()` to set its options.
55
54
  */
56
55
  export declare class DashboardApp {
57
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.app.d.ts","sourceRoot":"","sources":["../../src/app/dashboard.app.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAOb,KAAK,SAAS,EAGd,KAAK,YAAY,EAIlB,MAAM,eAAe,CAAC;AAYvB,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EACjC,MAAM,oBAAoB,CAAC;AAsF5B;;;;;GAKG;AACH,cAIM,mBAAoB,SAAQ,aAAa,CAAC,sBAAsB,EAAE,2BAA2B,CAAC;IAClG,OAAO,EAAE,sBAAsB,CAAC;gBAEpB,OAAO,GAAE,2BAAgC;IAQrD;;;;;;;;;;OAUG;IAEG,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBzE,gEAAgE;IAChE,OAAO,CAAC,OAAO;IAQf;;OAEG;WACa,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,GAAG,YAAY,EAAE;CAgCvF;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAmCa,YAAY;CAAG;AAG5B,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
1
+ {"version":3,"file":"dashboard.app.d.ts","sourceRoot":"","sources":["../../src/app/dashboard.app.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAOb,KAAK,SAAS,EAGd,KAAK,YAAY,EAIlB,MAAM,eAAe,CAAC;AAYvB,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EACjC,MAAM,oBAAoB,CAAC;AAsF5B;;;;;GAKG;AACH,cAIM,mBAAoB,SAAQ,aAAa,CAAC,sBAAsB,EAAE,2BAA2B,CAAC;IAClG,OAAO,EAAE,sBAAsB,CAAC;gBAEpB,OAAO,GAAE,2BAAgC;IAQrD;;;;;;;;;;OAUG;IAEG,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBzE,gEAAgE;IAChE,OAAO,CAAC,OAAO;IAQf;;OAEG;WACa,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,GAAG,YAAY,EAAE;CAgCvF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAmCa,YAAY;CAAG;AAG5B,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
@@ -8,41 +8,46 @@ import { type DashboardPluginOptions, type DashboardPluginOptionsInput } from '.
8
8
  * - Real-time monitoring via MCP protocol
9
9
  * - Graph-based visualization of your MCP architecture
10
10
  *
11
- * The dashboard UI is loaded from CDN (esm.sh by default) and connects
12
- * to the dashboard via MCP protocol over SSE transport.
11
+ * The dashboard UI is loaded from CDN (esm.sh by default) and reads the server's
12
+ * structure from the dashboard's own MCP endpoint, at the dashboard's path.
13
+ *
14
+ * `DashboardApp` serves the dashboard; this plugin, registered with `init()`, sets its options.
13
15
  *
14
16
  * @example Basic usage
15
17
  * ```typescript
16
- * import { DashboardPlugin } from '@frontmcp/plugins';
18
+ * import { DashboardApp, DashboardPlugin } from '@frontmcp/plugin-dashboard';
17
19
  *
18
- * @FrontMCP({
19
- * name: 'my-server',
20
- * plugins: [new DashboardPlugin()],
20
+ * @FrontMcp({
21
+ * info: { name: 'my-server', version: '1.0.0' },
22
+ * apps: [MyApp, DashboardApp],
23
+ * plugins: [DashboardPlugin.init()],
21
24
  * })
22
25
  * class MyServer {}
23
26
  * ```
24
27
  *
25
28
  * @example With authentication
26
29
  * ```typescript
27
- * @FrontMCP({
28
- * name: 'my-server',
30
+ * @FrontMcp({
31
+ * info: { name: 'my-server', version: '1.0.0' },
32
+ * apps: [MyApp, DashboardApp],
29
33
  * plugins: [
30
- * new DashboardPlugin({
31
- * auth: { enabled: true, token: 'my-secret-token' },
34
+ * DashboardPlugin.init({
35
+ * auth: { enabled: true, token: process.env.DASHBOARD_TOKEN },
32
36
  * }),
33
37
  * ],
34
38
  * })
35
39
  * class MyServer {}
36
40
  *
37
- * // Access: http://localhost:3000/dashboard?token=my-secret-token
41
+ * // Access: http://localhost:3000/dashboard?token=<DASHBOARD_TOKEN>
38
42
  * ```
39
43
  *
40
44
  * @example With custom CDN entrypoint
41
45
  * ```typescript
42
- * @FrontMCP({
43
- * name: 'my-server',
46
+ * @FrontMcp({
47
+ * info: { name: 'my-server', version: '1.0.0' },
48
+ * apps: [MyApp, DashboardApp],
44
49
  * plugins: [
45
- * new DashboardPlugin({
50
+ * DashboardPlugin.init({
46
51
  * cdn: {
47
52
  * entrypoint: 'https://cdn.example.com/dashboard-ui@1.0.0/index.js',
48
53
  * },
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.plugin.d.ts","sourceRoot":"","sources":["../src/dashboard.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAU,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAIzE,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EACjC,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAMH,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,aAAa,CAAC,sBAAsB,EAAE,2BAA2B,CAAC;IAC7G,OAAO,EAAE,sBAAsB,CAAC;gBAEpB,OAAO,GAAE,2BAAgC;IAUrD;;;;OAIG;WACa,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,GAAG,YAAY,EAAE;CAkBvF"}
1
+ {"version":3,"file":"dashboard.plugin.d.ts","sourceRoot":"","sources":["../src/dashboard.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAU,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAIzE,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EACjC,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAMH,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,aAAa,CAAC,sBAAsB,EAAE,2BAA2B,CAAC;IAC7G,OAAO,EAAE,sBAAsB,CAAC;gBAEpB,OAAO,GAAE,2BAAgC;IAUrD;;;;OAIG;WACa,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,GAAG,YAAY,EAAE;CAkBvF"}
package/esm/index.mjs CHANGED
@@ -1206,6 +1206,14 @@ var init_base = __esm({
1206
1206
  "Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-delete."
1207
1207
  );
1208
1208
  }
1209
+ /** Default: not supported, for the same reason as {@link deleteIfEquals}. */
1210
+ async expireIfEquals(_key, _expectedValue, _ttlSeconds) {
1211
+ throw new StorageNotSupportedError(
1212
+ "expireIfEquals",
1213
+ this.backendName,
1214
+ "Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-expire."
1215
+ );
1216
+ }
1209
1217
  // ============================================
1210
1218
  // Pub/Sub (default: not supported)
1211
1219
  // ============================================
@@ -1360,6 +1368,12 @@ var init_memory = __esm({
1360
1368
  if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
1361
1369
  return this.deleteEntry(key);
1362
1370
  }
1371
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
1372
+ this.ensureConnected();
1373
+ const entry = this.store.get(key);
1374
+ if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
1375
+ return this.expire(key, ttlSeconds);
1376
+ }
1363
1377
  async exists(key) {
1364
1378
  this.ensureConnected();
1365
1379
  const entry = this.store.get(key);
@@ -2663,10 +2677,12 @@ var init_utils = __esm({
2663
2677
  }
2664
2678
  });
2665
2679
  var COMPARE_AND_DELETE_SCRIPT;
2680
+ var COMPARE_AND_EXPIRE_SCRIPT;
2666
2681
  var init_compare_and_delete = __esm({
2667
2682
  "libs/utils/src/storage/utils/compare-and-delete.ts"() {
2668
2683
  "use strict";
2669
2684
  COMPARE_AND_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;
2685
+ COMPARE_AND_EXPIRE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) end return 0`;
2670
2686
  }
2671
2687
  });
2672
2688
  var redis_exports = {};
@@ -2909,6 +2925,17 @@ var init_redis = __esm({
2909
2925
  );
2910
2926
  return deleted === 1;
2911
2927
  }
2928
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
2929
+ validateTTL(ttlSeconds);
2930
+ const extended = await this.getConnectedClient().eval(
2931
+ COMPARE_AND_EXPIRE_SCRIPT,
2932
+ 1,
2933
+ this.prefixKey(key),
2934
+ expectedValue,
2935
+ String(ttlSeconds)
2936
+ );
2937
+ return extended === 1;
2938
+ }
2912
2939
  async exists(key) {
2913
2940
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
2914
2941
  return result > 0;
@@ -3240,6 +3267,15 @@ var init_vercel_kv = __esm({
3240
3267
  );
3241
3268
  return deleted === 1;
3242
3269
  }
3270
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
3271
+ validateTTL(ttlSeconds);
3272
+ const extended = await this.getConnectedClient().eval(
3273
+ COMPARE_AND_EXPIRE_SCRIPT,
3274
+ [this.prefixKey(key)],
3275
+ [expectedValue, String(ttlSeconds)]
3276
+ );
3277
+ return extended === 1;
3278
+ }
3243
3279
  async exists(key) {
3244
3280
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
3245
3281
  return result > 0;
@@ -3474,6 +3510,15 @@ var init_upstash = __esm({
3474
3510
  );
3475
3511
  return deleted === 1;
3476
3512
  }
3513
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
3514
+ validateTTL(ttlSeconds);
3515
+ const extended = await this.getConnectedClient().eval(
3516
+ COMPARE_AND_EXPIRE_SCRIPT,
3517
+ [this.prefixKey(key)],
3518
+ [expectedValue, String(ttlSeconds)]
3519
+ );
3520
+ return extended === 1;
3521
+ }
3477
3522
  async exists(key) {
3478
3523
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
3479
3524
  return result > 0;
package/esm/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.9.6",
4
- "description": "Dashboard plugin for FrontMCP - visual monitoring and introspection of MCP servers",
3
+ "version": "1.9.7",
4
+ "description": "Dashboard plugin for FrontMCP: a web page and MCP endpoint that show a server's apps, tools, resources and prompts",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
7
7
  "engines": {
8
8
  "node": ">=24.0.0"
9
9
  },
10
10
  "keywords": [
11
+ "frontmcp",
11
12
  "mcp",
13
+ "model-context-protocol",
12
14
  "dashboard",
13
- "monitoring",
15
+ "devtools",
16
+ "inspector",
14
17
  "plugin",
15
- "frontmcp",
16
18
  "agentfront"
17
19
  ],
18
20
  "repository": {
@@ -46,13 +48,12 @@
46
48
  }
47
49
  }
48
50
  },
49
- "dependencies": {
50
- "@frontmcp/sdk": "1.9.6"
51
- },
52
51
  "devDependencies": {
52
+ "@frontmcp/sdk": "1.9.7",
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.9.6"
56
+ "@frontmcp/lazy-zod": "1.9.7",
57
+ "@frontmcp/sdk": "1.9.7"
57
58
  }
58
59
  }
package/index.js CHANGED
@@ -1215,6 +1215,14 @@ var init_base = __esm({
1215
1215
  "Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-delete."
1216
1216
  );
1217
1217
  }
1218
+ /** Default: not supported, for the same reason as {@link deleteIfEquals}. */
1219
+ async expireIfEquals(_key, _expectedValue, _ttlSeconds) {
1220
+ throw new StorageNotSupportedError(
1221
+ "expireIfEquals",
1222
+ this.backendName,
1223
+ "Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-expire."
1224
+ );
1225
+ }
1218
1226
  // ============================================
1219
1227
  // Pub/Sub (default: not supported)
1220
1228
  // ============================================
@@ -1369,6 +1377,12 @@ var init_memory = __esm({
1369
1377
  if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
1370
1378
  return this.deleteEntry(key);
1371
1379
  }
1380
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
1381
+ this.ensureConnected();
1382
+ const entry = this.store.get(key);
1383
+ if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
1384
+ return this.expire(key, ttlSeconds);
1385
+ }
1372
1386
  async exists(key) {
1373
1387
  this.ensureConnected();
1374
1388
  const entry = this.store.get(key);
@@ -2672,10 +2686,12 @@ var init_utils = __esm({
2672
2686
  }
2673
2687
  });
2674
2688
  var COMPARE_AND_DELETE_SCRIPT;
2689
+ var COMPARE_AND_EXPIRE_SCRIPT;
2675
2690
  var init_compare_and_delete = __esm({
2676
2691
  "libs/utils/src/storage/utils/compare-and-delete.ts"() {
2677
2692
  "use strict";
2678
2693
  COMPARE_AND_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;
2694
+ COMPARE_AND_EXPIRE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) end return 0`;
2679
2695
  }
2680
2696
  });
2681
2697
  var redis_exports = {};
@@ -2918,6 +2934,17 @@ var init_redis = __esm({
2918
2934
  );
2919
2935
  return deleted === 1;
2920
2936
  }
2937
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
2938
+ validateTTL(ttlSeconds);
2939
+ const extended = await this.getConnectedClient().eval(
2940
+ COMPARE_AND_EXPIRE_SCRIPT,
2941
+ 1,
2942
+ this.prefixKey(key),
2943
+ expectedValue,
2944
+ String(ttlSeconds)
2945
+ );
2946
+ return extended === 1;
2947
+ }
2921
2948
  async exists(key) {
2922
2949
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
2923
2950
  return result > 0;
@@ -3249,6 +3276,15 @@ var init_vercel_kv = __esm({
3249
3276
  );
3250
3277
  return deleted === 1;
3251
3278
  }
3279
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
3280
+ validateTTL(ttlSeconds);
3281
+ const extended = await this.getConnectedClient().eval(
3282
+ COMPARE_AND_EXPIRE_SCRIPT,
3283
+ [this.prefixKey(key)],
3284
+ [expectedValue, String(ttlSeconds)]
3285
+ );
3286
+ return extended === 1;
3287
+ }
3252
3288
  async exists(key) {
3253
3289
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
3254
3290
  return result > 0;
@@ -3483,6 +3519,15 @@ var init_upstash = __esm({
3483
3519
  );
3484
3520
  return deleted === 1;
3485
3521
  }
3522
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
3523
+ validateTTL(ttlSeconds);
3524
+ const extended = await this.getConnectedClient().eval(
3525
+ COMPARE_AND_EXPIRE_SCRIPT,
3526
+ [this.prefixKey(key)],
3527
+ [expectedValue, String(ttlSeconds)]
3528
+ );
3529
+ return extended === 1;
3530
+ }
3486
3531
  async exists(key) {
3487
3532
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
3488
3533
  return result > 0;
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.9.6",
4
- "description": "Dashboard plugin for FrontMCP - visual monitoring and introspection of MCP servers",
3
+ "version": "1.9.7",
4
+ "description": "Dashboard plugin for FrontMCP: a web page and MCP endpoint that show a server's apps, tools, resources and prompts",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
7
7
  "engines": {
8
8
  "node": ">=24.0.0"
9
9
  },
10
10
  "keywords": [
11
+ "frontmcp",
11
12
  "mcp",
13
+ "model-context-protocol",
12
14
  "dashboard",
13
- "monitoring",
15
+ "devtools",
16
+ "inspector",
14
17
  "plugin",
15
- "frontmcp",
16
18
  "agentfront"
17
19
  ],
18
20
  "repository": {
@@ -46,13 +48,12 @@
46
48
  }
47
49
  }
48
50
  },
49
- "dependencies": {
50
- "@frontmcp/sdk": "1.9.6"
51
- },
52
51
  "devDependencies": {
52
+ "@frontmcp/sdk": "1.9.7",
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.9.6"
56
+ "@frontmcp/lazy-zod": "1.9.7",
57
+ "@frontmcp/sdk": "1.9.7"
57
58
  }
58
59
  }