@frontmcp/plugin-dashboard 1.8.6 → 1.9.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/esm/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
1
4
  var __create = Object.create;
2
5
  var __defProp = Object.defineProperty;
3
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -432,7 +435,6 @@ import {
432
435
  import { MCP_ERROR_CODES, PublicMcpError } from "@frontmcp/sdk";
433
436
 
434
437
  // libs/utils/dist/esm/index.mjs
435
- import { createRequire as __frontmcpCreateRequire } from "module";
436
438
  import { z as z2 } from "@frontmcp/lazy-zod";
437
439
  var import_event_emitter = __toESM(require_node_event_emitter(), 1);
438
440
  import { z as z22 } from "@frontmcp/lazy-zod";
@@ -444,8 +446,8 @@ import { hkdf } from "@noble/hashes/hkdf.js";
444
446
  import { randomBytes as nobleRandomBytes } from "@noble/hashes/utils.js";
445
447
  import { gcm } from "@noble/ciphers/aes.js";
446
448
  import { analyzeForReDoS, REDOS_THRESHOLDS } from "@enclave-vm/ast";
447
- import { z as z3 } from "@frontmcp/lazy-zod";
448
- var require2 = __frontmcpCreateRequire(import.meta.url || "file:///");
449
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : void 0;
450
+ var require2 = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || "file:///") : void 0;
449
451
  var __defProp2 = Object.defineProperty;
450
452
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
451
453
  var __require2 = /* @__PURE__ */ ((x) => typeof require2 !== "undefined" ? require2 : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -781,8 +783,8 @@ var init_key_persistence = __esm({
781
783
  */
782
784
  async get(kid) {
783
785
  if (this.enableCache) {
784
- const cached4 = this.cache.get(kid);
785
- if (cached4) return cached4;
786
+ const cached = this.cache.get(kid);
787
+ if (cached) return cached;
786
788
  }
787
789
  const raw = await this.storage.get(kid);
788
790
  if (!raw) return null;
@@ -1038,6 +1040,61 @@ var init_errors = __esm({
1038
1040
  };
1039
1041
  }
1040
1042
  });
1043
+ function globToRegex(pattern) {
1044
+ if (pattern.length > MAX_PATTERN_LENGTH) {
1045
+ throw new StoragePatternError(
1046
+ pattern.substring(0, 50) + "...",
1047
+ `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
1048
+ );
1049
+ }
1050
+ const wildcardCount = (pattern.match(/[*?]/g) || []).length;
1051
+ if (wildcardCount > MAX_WILDCARDS) {
1052
+ throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
1053
+ }
1054
+ if (pattern === "" || pattern === "*") {
1055
+ return /^.*$/;
1056
+ }
1057
+ let regexStr = "^";
1058
+ let prevChar = "";
1059
+ for (let i = 0; i < pattern.length; i++) {
1060
+ const char = pattern[i];
1061
+ switch (char) {
1062
+ case "*":
1063
+ if (prevChar !== "*") {
1064
+ regexStr += ".*";
1065
+ }
1066
+ break;
1067
+ case "?":
1068
+ regexStr += ".";
1069
+ break;
1070
+ default:
1071
+ regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
1072
+ }
1073
+ prevChar = char;
1074
+ }
1075
+ regexStr += "$";
1076
+ try {
1077
+ return new RegExp(regexStr);
1078
+ } catch {
1079
+ throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
1080
+ }
1081
+ }
1082
+ function matchesPattern(key, pattern) {
1083
+ const regex = globToRegex(pattern);
1084
+ return regex.test(key);
1085
+ }
1086
+ var MAX_PATTERN_LENGTH;
1087
+ var MAX_WILDCARDS;
1088
+ var REGEX_SPECIAL_CHARS;
1089
+ var init_pattern = __esm({
1090
+ "libs/utils/src/storage/utils/pattern.ts"() {
1091
+ "use strict";
1092
+ init_errors();
1093
+ MAX_PATTERN_LENGTH = 500;
1094
+ MAX_WILDCARDS = 20;
1095
+ REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
1096
+ }
1097
+ });
1041
1098
  function validateTTL(ttlSeconds) {
1042
1099
  if (typeof ttlSeconds !== "number") {
1043
1100
  throw new StorageTTLError(ttlSeconds, `TTL must be a number, got ${typeof ttlSeconds}`);
@@ -1146,6 +1203,17 @@ var init_base = __esm({
1146
1203
  const matchedKeys = await this.keys(pattern);
1147
1204
  return matchedKeys.length;
1148
1205
  }
1206
+ /**
1207
+ * Default: not supported, since a read followed by a delete is not atomic.
1208
+ * Override in adapters whose backend can compare and delete in one step.
1209
+ */
1210
+ async deleteIfEquals(_key, _expectedValue) {
1211
+ throw new StorageNotSupportedError(
1212
+ "deleteIfEquals",
1213
+ this.backendName,
1214
+ "Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-delete."
1215
+ );
1216
+ }
1149
1217
  // ============================================
1150
1218
  // Pub/Sub (default: not supported)
1151
1219
  // ============================================
@@ -1193,71 +1261,16 @@ var init_base = __esm({
1193
1261
  };
1194
1262
  }
1195
1263
  });
1196
- function globToRegex(pattern) {
1197
- if (pattern.length > MAX_PATTERN_LENGTH) {
1198
- throw new StoragePatternError(
1199
- pattern.substring(0, 50) + "...",
1200
- `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
1201
- );
1202
- }
1203
- const wildcardCount = (pattern.match(/[*?]/g) || []).length;
1204
- if (wildcardCount > MAX_WILDCARDS) {
1205
- throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
1206
- }
1207
- if (pattern === "" || pattern === "*") {
1208
- return /^.*$/;
1209
- }
1210
- let regexStr = "^";
1211
- let prevChar = "";
1212
- for (let i = 0; i < pattern.length; i++) {
1213
- const char = pattern[i];
1214
- switch (char) {
1215
- case "*":
1216
- if (prevChar !== "*") {
1217
- regexStr += ".*";
1218
- }
1219
- break;
1220
- case "?":
1221
- regexStr += ".";
1222
- break;
1223
- default:
1224
- regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
1225
- }
1226
- prevChar = char;
1227
- }
1228
- regexStr += "$";
1229
- try {
1230
- return new RegExp(regexStr);
1231
- } catch {
1232
- throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
1233
- }
1234
- }
1235
- function matchesPattern(key, pattern) {
1236
- const regex = globToRegex(pattern);
1237
- return regex.test(key);
1238
- }
1239
- var MAX_PATTERN_LENGTH;
1240
- var MAX_WILDCARDS;
1241
- var REGEX_SPECIAL_CHARS;
1242
- var init_pattern = __esm({
1243
- "libs/utils/src/storage/utils/pattern.ts"() {
1244
- "use strict";
1245
- init_errors();
1246
- MAX_PATTERN_LENGTH = 500;
1247
- MAX_WILDCARDS = 20;
1248
- REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
1249
- }
1250
- });
1251
1264
  var DEFAULT_SWEEP_INTERVAL_SECONDS;
1252
1265
  var MAX_TIMEOUT_MS;
1253
1266
  var MemoryStorageAdapter;
1254
1267
  var init_memory = __esm({
1255
1268
  "libs/utils/src/storage/adapters/memory.ts"() {
1256
1269
  "use strict";
1257
- init_base();
1258
1270
  init_errors();
1259
1271
  init_pattern();
1260
1272
  init_ttl();
1273
+ init_base();
1261
1274
  DEFAULT_SWEEP_INTERVAL_SECONDS = 60;
1262
1275
  MAX_TIMEOUT_MS = 2147483647;
1263
1276
  MemoryStorageAdapter = class extends BaseStorageAdapter {
@@ -1349,6 +1362,12 @@ var init_memory = __esm({
1349
1362
  this.ensureConnected();
1350
1363
  return this.deleteEntry(key);
1351
1364
  }
1365
+ async deleteIfEquals(key, expectedValue) {
1366
+ this.ensureConnected();
1367
+ const entry = this.store.get(key);
1368
+ if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
1369
+ return this.deleteEntry(key);
1370
+ }
1352
1371
  async exists(key) {
1353
1372
  this.ensureConnected();
1354
1373
  const entry = this.store.get(key);
@@ -2687,15 +2706,56 @@ var init_crypto = __esm({
2687
2706
  HKDF_SHA256_MAX_LENGTH = 255 * 32;
2688
2707
  }
2689
2708
  });
2709
+ function attachRedisErrorListener(client, options = {}) {
2710
+ const label = options.label ?? "redis";
2711
+ const intervalMs = options.intervalMs ?? DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
2712
+ let lastLoggedAt = 0;
2713
+ let suppressed = 0;
2714
+ const listener = (error) => {
2715
+ const now = Date.now();
2716
+ if (lastLoggedAt !== 0 && now - lastLoggedAt < intervalMs) {
2717
+ suppressed++;
2718
+ return;
2719
+ }
2720
+ const extra = suppressed > 0 ? ` (${suppressed} similar error(s) suppressed)` : "";
2721
+ suppressed = 0;
2722
+ lastLoggedAt = now;
2723
+ const message = `[${label}] Redis connection error: ${error?.message ?? String(error)}${extra}`;
2724
+ if (options.logger) {
2725
+ options.logger.warn(message);
2726
+ } else {
2727
+ console.warn(message);
2728
+ }
2729
+ };
2730
+ client.on("error", listener);
2731
+ return () => {
2732
+ client.removeListener("error", listener);
2733
+ };
2734
+ }
2735
+ var DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
2736
+ var init_redis_error_listener = __esm({
2737
+ "libs/utils/src/storage/redis-error-listener.ts"() {
2738
+ "use strict";
2739
+ DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 3e4;
2740
+ }
2741
+ });
2690
2742
  var init_utils = __esm({
2691
2743
  "libs/utils/src/storage/utils/index.ts"() {
2692
2744
  "use strict";
2693
2745
  init_ttl();
2694
2746
  }
2695
2747
  });
2748
+ var COMPARE_AND_DELETE_SCRIPT;
2749
+ var init_compare_and_delete = __esm({
2750
+ "libs/utils/src/storage/utils/compare-and-delete.ts"() {
2751
+ "use strict";
2752
+ COMPARE_AND_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;
2753
+ }
2754
+ });
2696
2755
  var redis_exports = {};
2697
2756
  __export(redis_exports, {
2698
- RedisStorageAdapter: () => RedisStorageAdapter
2757
+ RedisStorageAdapter: () => RedisStorageAdapter,
2758
+ createRedisClient: () => createRedisClient
2699
2759
  });
2700
2760
  function getRedisClass() {
2701
2761
  try {
@@ -2713,12 +2773,28 @@ function getRedisClass() {
2713
2773
  });
2714
2774
  }
2715
2775
  }
2776
+ function createRedisClient(options) {
2777
+ const RedisClass = getRedisClass();
2778
+ const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
2779
+ const client = options.url ? new RedisClass(options.url, baseOptions) : new RedisClass({
2780
+ ...baseOptions,
2781
+ host: options.host ?? "localhost",
2782
+ port: options.port ?? 6379,
2783
+ password: options.password,
2784
+ db: options.db ?? 0,
2785
+ tls: options.tls ? {} : void 0
2786
+ });
2787
+ attachRedisErrorListener(client, { label: options.label ?? "redis", logger: options.logger });
2788
+ return client;
2789
+ }
2716
2790
  var RedisStorageAdapter;
2717
2791
  var init_redis = __esm({
2718
2792
  "libs/utils/src/storage/adapters/redis.ts"() {
2719
2793
  "use strict";
2720
2794
  init_errors();
2795
+ init_redis_error_listener();
2721
2796
  init_utils();
2797
+ init_compare_and_delete();
2722
2798
  init_base();
2723
2799
  RedisStorageAdapter = class extends BaseStorageAdapter {
2724
2800
  backendName = "redis";
@@ -2726,6 +2802,7 @@ var init_redis = __esm({
2726
2802
  subscriber;
2727
2803
  options;
2728
2804
  ownsClient;
2805
+ detachErrorListener;
2729
2806
  connecting;
2730
2807
  keyPrefix;
2731
2808
  subscriptionHandlers = /* @__PURE__ */ new Map();
@@ -2782,7 +2859,10 @@ var init_redis = __esm({
2782
2859
  await client.ping();
2783
2860
  this.client = client;
2784
2861
  this.connected = true;
2785
- if (this.ownsClient) client.removeListener("error", recordSocketError);
2862
+ if (this.ownsClient) {
2863
+ client.removeListener("error", recordSocketError);
2864
+ this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
2865
+ }
2786
2866
  } catch (e) {
2787
2867
  if (this.ownsClient && client) {
2788
2868
  try {
@@ -2803,6 +2883,8 @@ var init_redis = __esm({
2803
2883
  if (this.ownsClient && this.client) {
2804
2884
  await this.client.quit();
2805
2885
  }
2886
+ this.detachErrorListener?.();
2887
+ this.detachErrorListener = void 0;
2806
2888
  this.client = void 0;
2807
2889
  this.connected = false;
2808
2890
  this.subscriptionHandlers.clear();
@@ -2867,6 +2949,15 @@ var init_redis = __esm({
2867
2949
  const result = await this.getConnectedClient().del(this.prefixKey(key));
2868
2950
  return result > 0;
2869
2951
  }
2952
+ async deleteIfEquals(key, expectedValue) {
2953
+ const deleted = await this.getConnectedClient().eval(
2954
+ COMPARE_AND_DELETE_SCRIPT,
2955
+ 1,
2956
+ this.prefixKey(key),
2957
+ expectedValue
2958
+ );
2959
+ return deleted === 1;
2960
+ }
2870
2961
  async exists(key) {
2871
2962
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
2872
2963
  return result > 0;
@@ -3030,6 +3121,7 @@ var init_redis = __esm({
3030
3121
  } else {
3031
3122
  throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
3032
3123
  }
3124
+ attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
3033
3125
  subscriber.on("message", (channel, message) => {
3034
3126
  const handlers = this.subscriptionHandlers.get(channel);
3035
3127
  if (handlers) {
@@ -3072,9 +3164,12 @@ var vercel_kv_exports = {};
3072
3164
  __export(vercel_kv_exports, {
3073
3165
  VercelKvStorageAdapter: () => VercelKvStorageAdapter
3074
3166
  });
3075
- function getVercelKv() {
3167
+ async function loadVercelKv() {
3076
3168
  try {
3077
- return __require2("@vercel/kv");
3169
+ const mod = await import("@vercel/kv");
3170
+ const resolved = typeof mod.createClient === "function" ? mod : mod.default;
3171
+ if (!resolved || typeof resolved.createClient !== "function") throw new Error("createClient missing");
3172
+ return resolved;
3078
3173
  } catch {
3079
3174
  throw new Error("@vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv");
3080
3175
  }
@@ -3083,9 +3178,10 @@ var VercelKvStorageAdapter;
3083
3178
  var init_vercel_kv = __esm({
3084
3179
  "libs/utils/src/storage/adapters/vercel-kv.ts"() {
3085
3180
  "use strict";
3086
- init_base();
3087
3181
  init_errors();
3182
+ init_compare_and_delete();
3088
3183
  init_ttl();
3184
+ init_base();
3089
3185
  VercelKvStorageAdapter = class extends BaseStorageAdapter {
3090
3186
  backendName = "vercel-kv";
3091
3187
  client;
@@ -3110,17 +3206,13 @@ var init_vercel_kv = __esm({
3110
3206
  async connect() {
3111
3207
  if (this.connected) return;
3112
3208
  try {
3113
- const { createClient, kv } = getVercelKv();
3114
- if (this.options.url === process.env["KV_REST_API_URL"]) {
3115
- this.client = kv;
3116
- } else {
3117
- const url = this.options.url;
3118
- const token = this.options.token;
3119
- if (!url || !token) {
3120
- throw new StorageConfigError("vercel-kv", "URL and token are required");
3121
- }
3122
- this.client = createClient({ url, token });
3209
+ const { createClient } = await loadVercelKv();
3210
+ const url = this.options.url;
3211
+ const token = this.options.token;
3212
+ if (!url || !token) {
3213
+ throw new StorageConfigError("vercel-kv", "URL and token are required");
3123
3214
  }
3215
+ this.client = createClient({ url, token, cache: void 0 });
3124
3216
  await this.client.exists("__healthcheck__");
3125
3217
  this.connected = true;
3126
3218
  } catch (e) {
@@ -3182,6 +3274,14 @@ var init_vercel_kv = __esm({
3182
3274
  const result = await this.getConnectedClient().del(this.prefixKey(key));
3183
3275
  return result > 0;
3184
3276
  }
3277
+ async deleteIfEquals(key, expectedValue) {
3278
+ const deleted = await this.getConnectedClient().eval(
3279
+ COMPARE_AND_DELETE_SCRIPT,
3280
+ [this.prefixKey(key)],
3281
+ [expectedValue]
3282
+ );
3283
+ return deleted === 1;
3284
+ }
3185
3285
  async exists(key) {
3186
3286
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
3187
3287
  return result > 0;
@@ -3298,9 +3398,10 @@ var UpstashStorageAdapter;
3298
3398
  var init_upstash = __esm({
3299
3399
  "libs/utils/src/storage/adapters/upstash.ts"() {
3300
3400
  "use strict";
3301
- init_base();
3302
3401
  init_errors();
3402
+ init_compare_and_delete();
3303
3403
  init_ttl();
3404
+ init_base();
3304
3405
  PUBSUB_POLL_INTERVAL_MS = 100;
3305
3406
  UpstashStorageAdapter = class extends BaseStorageAdapter {
3306
3407
  backendName = "upstash";
@@ -3405,6 +3506,14 @@ var init_upstash = __esm({
3405
3506
  const result = await this.getConnectedClient().del(this.prefixKey(key));
3406
3507
  return result > 0;
3407
3508
  }
3509
+ async deleteIfEquals(key, expectedValue) {
3510
+ const deleted = await this.getConnectedClient().eval(
3511
+ COMPARE_AND_DELETE_SCRIPT,
3512
+ [this.prefixKey(key)],
3513
+ [expectedValue]
3514
+ );
3515
+ return deleted === 1;
3516
+ }
3408
3517
  async exists(key) {
3409
3518
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
3410
3519
  return result > 0;
@@ -3593,7 +3702,9 @@ var init_cloudflare_kv = __esm({
3593
3702
  constructor(options) {
3594
3703
  super();
3595
3704
  if (!options?.namespace) {
3596
- throw new Error("CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`).");
3705
+ throw new Error(
3706
+ "CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`)."
3707
+ );
3597
3708
  }
3598
3709
  this.kv = options.namespace;
3599
3710
  this.keyPrefix = options.keyPrefix ?? "";
@@ -3687,6 +3798,13 @@ var init_cloudflare_kv = __esm({
3687
3798
  "Cloudflare KV has no atomic increment (a get+put is racy under eventual consistency). Use a Durable Object for atomic counters."
3688
3799
  );
3689
3800
  }
3801
+ async deleteIfEquals(_key, _expectedValue) {
3802
+ throw new StorageNotSupportedError(
3803
+ "deleteIfEquals",
3804
+ this.backendName,
3805
+ "Cloudflare KV has no compare-and-set; use a Durable Object for an atomic compare-and-delete."
3806
+ );
3807
+ }
3690
3808
  // ── Pub/Sub (unsupported) ────────────────────────────────────────────────
3691
3809
  getPubSubSuggestion() {
3692
3810
  return "Cloudflare KV does not support pub/sub. Use a Durable Object (or Queues) for fan-out.";
@@ -3800,18 +3918,10 @@ function compileBlocks(entries, parseNetwork) {
3800
3918
  }
3801
3919
  init_fs2();
3802
3920
  init_crypto();
3803
- var entryAvailabilitySchema = z3.object({
3804
- os: z3.array(z3.string().min(1)).optional(),
3805
- platform: z3.array(z3.string().min(1)).optional(),
3806
- runtime: z3.array(z3.string().min(1)).optional(),
3807
- deployment: z3.array(z3.string().min(1)).optional(),
3808
- provider: z3.array(z3.string().min(1)).optional(),
3809
- target: z3.array(z3.string().min(1)).optional(),
3810
- surface: z3.array(z3.enum(["mcp", "cli", "http-trigger", "job", "agent"])).optional(),
3811
- env: z3.array(z3.string().min(1)).optional()
3812
- }).strict();
3921
+ init_errors();
3813
3922
  init_errors();
3814
3923
  init_memory();
3924
+ init_redis_error_listener();
3815
3925
  init_errors();
3816
3926
  init_crypto();
3817
3927
  init_errors();
@@ -4781,11 +4891,11 @@ GraphDataProvider = __decorateClass([
4781
4891
  ], GraphDataProvider);
4782
4892
 
4783
4893
  // plugins/plugin-dashboard/src/tools/graph.tool.ts
4784
- import { z as z4 } from "@frontmcp/lazy-zod";
4894
+ import { z as z3 } from "@frontmcp/lazy-zod";
4785
4895
  import { Tool, ToolContext } from "@frontmcp/sdk";
4786
4896
  var graphToolInputSchema = {
4787
- includeSchemas: z4.boolean().optional().default(false).describe("Include full input/output schemas in the response"),
4788
- refresh: z4.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")
4897
+ includeSchemas: z3.boolean().optional().default(false).describe("Include full input/output schemas in the response"),
4898
+ refresh: z3.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")
4789
4899
  };
4790
4900
  var GraphTool = class extends ToolContext {
4791
4901
  async execute(input) {
@@ -4823,7 +4933,7 @@ GraphTool = __decorateClass([
4823
4933
  ], GraphTool);
4824
4934
 
4825
4935
  // plugins/plugin-dashboard/src/tools/list-resources.tool.ts
4826
- import { z as z5 } from "@frontmcp/lazy-zod";
4936
+ import { z as z4 } from "@frontmcp/lazy-zod";
4827
4937
  import { Tool as Tool2, ToolContext as ToolContext2 } from "@frontmcp/sdk";
4828
4938
 
4829
4939
  // plugins/plugin-dashboard/src/shared/safe-regex.ts
@@ -4842,20 +4952,20 @@ function safeRegex(pattern) {
4842
4952
 
4843
4953
  // plugins/plugin-dashboard/src/tools/list-resources.tool.ts
4844
4954
  var listResourcesInputSchema = {
4845
- filter: z5.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),
4846
- includeTemplates: z5.boolean().default(true).describe("Include resource templates in the response")
4955
+ filter: z4.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),
4956
+ includeTemplates: z4.boolean().default(true).describe("Include resource templates in the response")
4847
4957
  };
4848
- var listResourcesOutputSchema = z5.object({
4849
- resources: z5.array(
4850
- z5.object({
4851
- name: z5.string(),
4852
- uri: z5.string(),
4853
- description: z5.string().optional(),
4854
- mimeType: z5.string().optional(),
4855
- isTemplate: z5.boolean()
4958
+ var listResourcesOutputSchema = z4.object({
4959
+ resources: z4.array(
4960
+ z4.object({
4961
+ name: z4.string(),
4962
+ uri: z4.string(),
4963
+ description: z4.string().optional(),
4964
+ mimeType: z4.string().optional(),
4965
+ isTemplate: z4.boolean()
4856
4966
  })
4857
4967
  ),
4858
- count: z5.number()
4968
+ count: z4.number()
4859
4969
  });
4860
4970
  var ListResourcesTool = class extends ToolContext2 {
4861
4971
  async execute(input) {
@@ -4917,25 +5027,25 @@ ListResourcesTool = __decorateClass([
4917
5027
  ], ListResourcesTool);
4918
5028
 
4919
5029
  // plugins/plugin-dashboard/src/tools/list-tools.tool.ts
4920
- import { z as z6 } from "@frontmcp/lazy-zod";
5030
+ import { z as z5 } from "@frontmcp/lazy-zod";
4921
5031
  import { Tool as Tool3, ToolContext as ToolContext3 } from "@frontmcp/sdk";
4922
5032
  var listToolsInputSchema = {
4923
- filter: z6.string().optional().describe("Filter tools by name pattern (regex supported)"),
4924
- includePlugins: z6.boolean().default(true).describe("Include tools from plugins"),
4925
- includeSchemas: z6.boolean().default(false).describe("Include input/output schemas in the response")
5033
+ filter: z5.string().optional().describe("Filter tools by name pattern (regex supported)"),
5034
+ includePlugins: z5.boolean().default(true).describe("Include tools from plugins"),
5035
+ includeSchemas: z5.boolean().default(false).describe("Include input/output schemas in the response")
4926
5036
  };
4927
- var listToolsOutputSchema = z6.object({
4928
- tools: z6.array(
4929
- z6.object({
4930
- name: z6.string(),
4931
- fullName: z6.string(),
4932
- description: z6.string().optional(),
4933
- tags: z6.array(z6.string()).optional(),
4934
- inputSchema: z6.unknown().optional(),
4935
- outputSchema: z6.unknown().optional()
5037
+ var listToolsOutputSchema = z5.object({
5038
+ tools: z5.array(
5039
+ z5.object({
5040
+ name: z5.string(),
5041
+ fullName: z5.string(),
5042
+ description: z5.string().optional(),
5043
+ tags: z5.array(z5.string()).optional(),
5044
+ inputSchema: z5.unknown().optional(),
5045
+ outputSchema: z5.unknown().optional()
4936
5046
  })
4937
5047
  ),
4938
- count: z6.number()
5048
+ count: z5.number()
4939
5049
  });
4940
5050
  var ListToolsTool = class extends ToolContext3 {
4941
5051
  async execute(input) {
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.8.6",
3
+ "version": "1.9.0",
4
4
  "description": "Dashboard plugin for FrontMCP - visual monitoring and introspection of MCP servers",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -47,12 +47,12 @@
47
47
  }
48
48
  },
49
49
  "dependencies": {
50
- "@frontmcp/sdk": "1.8.6"
50
+ "@frontmcp/sdk": "1.9.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.8.6"
56
+ "@frontmcp/lazy-zod": "1.9.0"
57
57
  }
58
58
  }
package/index.js CHANGED
@@ -443,7 +443,6 @@ var import_sdk7 = require("@frontmcp/sdk");
443
443
  var import_sdk2 = require("@frontmcp/sdk");
444
444
 
445
445
  // libs/utils/dist/esm/index.mjs
446
- var import_module = require("module");
447
446
  var import_lazy_zod2 = require("@frontmcp/lazy-zod");
448
447
  var import_lazy_zod3 = require("@frontmcp/lazy-zod");
449
448
  var import_event_emitter = __toESM(require_node_event_emitter(), 1);
@@ -455,9 +454,9 @@ var import_utils = require("@noble/hashes/utils.js");
455
454
  var import_aes = require("@noble/ciphers/aes.js");
456
455
  var import_crypto_provider = __toESM(require_node(), 1);
457
456
  var import_ast = require("@enclave-vm/ast");
458
- var import_lazy_zod4 = require("@frontmcp/lazy-zod");
459
457
  var import_meta = {};
460
- var require2 = (0, import_module.createRequire)(import_meta.url || "file:///");
458
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : void 0;
459
+ var require2 = __frontmcpModule ? __frontmcpModule.createRequire(import_meta.url || "file:///") : void 0;
461
460
  var __defProp2 = Object.defineProperty;
462
461
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
463
462
  var __require = /* @__PURE__ */ ((x) => typeof require2 !== "undefined" ? require2 : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -793,8 +792,8 @@ var init_key_persistence = __esm({
793
792
  */
794
793
  async get(kid) {
795
794
  if (this.enableCache) {
796
- const cached4 = this.cache.get(kid);
797
- if (cached4) return cached4;
795
+ const cached = this.cache.get(kid);
796
+ if (cached) return cached;
798
797
  }
799
798
  const raw = await this.storage.get(kid);
800
799
  if (!raw) return null;
@@ -1050,6 +1049,61 @@ var init_errors = __esm({
1050
1049
  };
1051
1050
  }
1052
1051
  });
1052
+ function globToRegex(pattern) {
1053
+ if (pattern.length > MAX_PATTERN_LENGTH) {
1054
+ throw new StoragePatternError(
1055
+ pattern.substring(0, 50) + "...",
1056
+ `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
1057
+ );
1058
+ }
1059
+ const wildcardCount = (pattern.match(/[*?]/g) || []).length;
1060
+ if (wildcardCount > MAX_WILDCARDS) {
1061
+ throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
1062
+ }
1063
+ if (pattern === "" || pattern === "*") {
1064
+ return /^.*$/;
1065
+ }
1066
+ let regexStr = "^";
1067
+ let prevChar = "";
1068
+ for (let i = 0; i < pattern.length; i++) {
1069
+ const char = pattern[i];
1070
+ switch (char) {
1071
+ case "*":
1072
+ if (prevChar !== "*") {
1073
+ regexStr += ".*";
1074
+ }
1075
+ break;
1076
+ case "?":
1077
+ regexStr += ".";
1078
+ break;
1079
+ default:
1080
+ regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
1081
+ }
1082
+ prevChar = char;
1083
+ }
1084
+ regexStr += "$";
1085
+ try {
1086
+ return new RegExp(regexStr);
1087
+ } catch {
1088
+ throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
1089
+ }
1090
+ }
1091
+ function matchesPattern(key, pattern) {
1092
+ const regex = globToRegex(pattern);
1093
+ return regex.test(key);
1094
+ }
1095
+ var MAX_PATTERN_LENGTH;
1096
+ var MAX_WILDCARDS;
1097
+ var REGEX_SPECIAL_CHARS;
1098
+ var init_pattern = __esm({
1099
+ "libs/utils/src/storage/utils/pattern.ts"() {
1100
+ "use strict";
1101
+ init_errors();
1102
+ MAX_PATTERN_LENGTH = 500;
1103
+ MAX_WILDCARDS = 20;
1104
+ REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
1105
+ }
1106
+ });
1053
1107
  function validateTTL(ttlSeconds) {
1054
1108
  if (typeof ttlSeconds !== "number") {
1055
1109
  throw new StorageTTLError(ttlSeconds, `TTL must be a number, got ${typeof ttlSeconds}`);
@@ -1158,6 +1212,17 @@ var init_base = __esm({
1158
1212
  const matchedKeys = await this.keys(pattern);
1159
1213
  return matchedKeys.length;
1160
1214
  }
1215
+ /**
1216
+ * Default: not supported, since a read followed by a delete is not atomic.
1217
+ * Override in adapters whose backend can compare and delete in one step.
1218
+ */
1219
+ async deleteIfEquals(_key, _expectedValue) {
1220
+ throw new StorageNotSupportedError(
1221
+ "deleteIfEquals",
1222
+ this.backendName,
1223
+ "Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-delete."
1224
+ );
1225
+ }
1161
1226
  // ============================================
1162
1227
  // Pub/Sub (default: not supported)
1163
1228
  // ============================================
@@ -1205,71 +1270,16 @@ var init_base = __esm({
1205
1270
  };
1206
1271
  }
1207
1272
  });
1208
- function globToRegex(pattern) {
1209
- if (pattern.length > MAX_PATTERN_LENGTH) {
1210
- throw new StoragePatternError(
1211
- pattern.substring(0, 50) + "...",
1212
- `Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`
1213
- );
1214
- }
1215
- const wildcardCount = (pattern.match(/[*?]/g) || []).length;
1216
- if (wildcardCount > MAX_WILDCARDS) {
1217
- throw new StoragePatternError(pattern, `Pattern has too many wildcards (max: ${MAX_WILDCARDS})`);
1218
- }
1219
- if (pattern === "" || pattern === "*") {
1220
- return /^.*$/;
1221
- }
1222
- let regexStr = "^";
1223
- let prevChar = "";
1224
- for (let i = 0; i < pattern.length; i++) {
1225
- const char = pattern[i];
1226
- switch (char) {
1227
- case "*":
1228
- if (prevChar !== "*") {
1229
- regexStr += ".*";
1230
- }
1231
- break;
1232
- case "?":
1233
- regexStr += ".";
1234
- break;
1235
- default:
1236
- regexStr += char.replace(REGEX_SPECIAL_CHARS, "\\$&");
1237
- }
1238
- prevChar = char;
1239
- }
1240
- regexStr += "$";
1241
- try {
1242
- return new RegExp(regexStr);
1243
- } catch {
1244
- throw new StoragePatternError(pattern, "Failed to compile pattern to regex");
1245
- }
1246
- }
1247
- function matchesPattern(key, pattern) {
1248
- const regex = globToRegex(pattern);
1249
- return regex.test(key);
1250
- }
1251
- var MAX_PATTERN_LENGTH;
1252
- var MAX_WILDCARDS;
1253
- var REGEX_SPECIAL_CHARS;
1254
- var init_pattern = __esm({
1255
- "libs/utils/src/storage/utils/pattern.ts"() {
1256
- "use strict";
1257
- init_errors();
1258
- MAX_PATTERN_LENGTH = 500;
1259
- MAX_WILDCARDS = 20;
1260
- REGEX_SPECIAL_CHARS = /[.+^${}()|[\]\\]/g;
1261
- }
1262
- });
1263
1273
  var DEFAULT_SWEEP_INTERVAL_SECONDS;
1264
1274
  var MAX_TIMEOUT_MS;
1265
1275
  var MemoryStorageAdapter;
1266
1276
  var init_memory = __esm({
1267
1277
  "libs/utils/src/storage/adapters/memory.ts"() {
1268
1278
  "use strict";
1269
- init_base();
1270
1279
  init_errors();
1271
1280
  init_pattern();
1272
1281
  init_ttl();
1282
+ init_base();
1273
1283
  DEFAULT_SWEEP_INTERVAL_SECONDS = 60;
1274
1284
  MAX_TIMEOUT_MS = 2147483647;
1275
1285
  MemoryStorageAdapter = class extends BaseStorageAdapter {
@@ -1361,6 +1371,12 @@ var init_memory = __esm({
1361
1371
  this.ensureConnected();
1362
1372
  return this.deleteEntry(key);
1363
1373
  }
1374
+ async deleteIfEquals(key, expectedValue) {
1375
+ this.ensureConnected();
1376
+ const entry = this.store.get(key);
1377
+ if (!entry || isExpired(entry.expiresAt) || entry.value !== expectedValue) return false;
1378
+ return this.deleteEntry(key);
1379
+ }
1364
1380
  async exists(key) {
1365
1381
  this.ensureConnected();
1366
1382
  const entry = this.store.get(key);
@@ -2699,15 +2715,56 @@ var init_crypto = __esm({
2699
2715
  HKDF_SHA256_MAX_LENGTH = 255 * 32;
2700
2716
  }
2701
2717
  });
2718
+ function attachRedisErrorListener(client, options = {}) {
2719
+ const label = options.label ?? "redis";
2720
+ const intervalMs = options.intervalMs ?? DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
2721
+ let lastLoggedAt = 0;
2722
+ let suppressed = 0;
2723
+ const listener = (error) => {
2724
+ const now = Date.now();
2725
+ if (lastLoggedAt !== 0 && now - lastLoggedAt < intervalMs) {
2726
+ suppressed++;
2727
+ return;
2728
+ }
2729
+ const extra = suppressed > 0 ? ` (${suppressed} similar error(s) suppressed)` : "";
2730
+ suppressed = 0;
2731
+ lastLoggedAt = now;
2732
+ const message = `[${label}] Redis connection error: ${error?.message ?? String(error)}${extra}`;
2733
+ if (options.logger) {
2734
+ options.logger.warn(message);
2735
+ } else {
2736
+ console.warn(message);
2737
+ }
2738
+ };
2739
+ client.on("error", listener);
2740
+ return () => {
2741
+ client.removeListener("error", listener);
2742
+ };
2743
+ }
2744
+ var DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
2745
+ var init_redis_error_listener = __esm({
2746
+ "libs/utils/src/storage/redis-error-listener.ts"() {
2747
+ "use strict";
2748
+ DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 3e4;
2749
+ }
2750
+ });
2702
2751
  var init_utils = __esm({
2703
2752
  "libs/utils/src/storage/utils/index.ts"() {
2704
2753
  "use strict";
2705
2754
  init_ttl();
2706
2755
  }
2707
2756
  });
2757
+ var COMPARE_AND_DELETE_SCRIPT;
2758
+ var init_compare_and_delete = __esm({
2759
+ "libs/utils/src/storage/utils/compare-and-delete.ts"() {
2760
+ "use strict";
2761
+ COMPARE_AND_DELETE_SCRIPT = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;
2762
+ }
2763
+ });
2708
2764
  var redis_exports = {};
2709
2765
  __export2(redis_exports, {
2710
- RedisStorageAdapter: () => RedisStorageAdapter
2766
+ RedisStorageAdapter: () => RedisStorageAdapter,
2767
+ createRedisClient: () => createRedisClient
2711
2768
  });
2712
2769
  function getRedisClass() {
2713
2770
  try {
@@ -2725,12 +2782,28 @@ function getRedisClass() {
2725
2782
  });
2726
2783
  }
2727
2784
  }
2785
+ function createRedisClient(options) {
2786
+ const RedisClass = getRedisClass();
2787
+ const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
2788
+ const client = options.url ? new RedisClass(options.url, baseOptions) : new RedisClass({
2789
+ ...baseOptions,
2790
+ host: options.host ?? "localhost",
2791
+ port: options.port ?? 6379,
2792
+ password: options.password,
2793
+ db: options.db ?? 0,
2794
+ tls: options.tls ? {} : void 0
2795
+ });
2796
+ attachRedisErrorListener(client, { label: options.label ?? "redis", logger: options.logger });
2797
+ return client;
2798
+ }
2728
2799
  var RedisStorageAdapter;
2729
2800
  var init_redis = __esm({
2730
2801
  "libs/utils/src/storage/adapters/redis.ts"() {
2731
2802
  "use strict";
2732
2803
  init_errors();
2804
+ init_redis_error_listener();
2733
2805
  init_utils();
2806
+ init_compare_and_delete();
2734
2807
  init_base();
2735
2808
  RedisStorageAdapter = class extends BaseStorageAdapter {
2736
2809
  backendName = "redis";
@@ -2738,6 +2811,7 @@ var init_redis = __esm({
2738
2811
  subscriber;
2739
2812
  options;
2740
2813
  ownsClient;
2814
+ detachErrorListener;
2741
2815
  connecting;
2742
2816
  keyPrefix;
2743
2817
  subscriptionHandlers = /* @__PURE__ */ new Map();
@@ -2794,7 +2868,10 @@ var init_redis = __esm({
2794
2868
  await client.ping();
2795
2869
  this.client = client;
2796
2870
  this.connected = true;
2797
- if (this.ownsClient) client.removeListener("error", recordSocketError);
2871
+ if (this.ownsClient) {
2872
+ client.removeListener("error", recordSocketError);
2873
+ this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
2874
+ }
2798
2875
  } catch (e) {
2799
2876
  if (this.ownsClient && client) {
2800
2877
  try {
@@ -2815,6 +2892,8 @@ var init_redis = __esm({
2815
2892
  if (this.ownsClient && this.client) {
2816
2893
  await this.client.quit();
2817
2894
  }
2895
+ this.detachErrorListener?.();
2896
+ this.detachErrorListener = void 0;
2818
2897
  this.client = void 0;
2819
2898
  this.connected = false;
2820
2899
  this.subscriptionHandlers.clear();
@@ -2879,6 +2958,15 @@ var init_redis = __esm({
2879
2958
  const result = await this.getConnectedClient().del(this.prefixKey(key));
2880
2959
  return result > 0;
2881
2960
  }
2961
+ async deleteIfEquals(key, expectedValue) {
2962
+ const deleted = await this.getConnectedClient().eval(
2963
+ COMPARE_AND_DELETE_SCRIPT,
2964
+ 1,
2965
+ this.prefixKey(key),
2966
+ expectedValue
2967
+ );
2968
+ return deleted === 1;
2969
+ }
2882
2970
  async exists(key) {
2883
2971
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
2884
2972
  return result > 0;
@@ -3042,6 +3130,7 @@ var init_redis = __esm({
3042
3130
  } else {
3043
3131
  throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
3044
3132
  }
3133
+ attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
3045
3134
  subscriber.on("message", (channel, message) => {
3046
3135
  const handlers = this.subscriptionHandlers.get(channel);
3047
3136
  if (handlers) {
@@ -3084,9 +3173,12 @@ var vercel_kv_exports = {};
3084
3173
  __export2(vercel_kv_exports, {
3085
3174
  VercelKvStorageAdapter: () => VercelKvStorageAdapter
3086
3175
  });
3087
- function getVercelKv() {
3176
+ async function loadVercelKv() {
3088
3177
  try {
3089
- return __require("@vercel/kv");
3178
+ const mod = await import("@vercel/kv");
3179
+ const resolved = typeof mod.createClient === "function" ? mod : mod.default;
3180
+ if (!resolved || typeof resolved.createClient !== "function") throw new Error("createClient missing");
3181
+ return resolved;
3090
3182
  } catch {
3091
3183
  throw new Error("@vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv");
3092
3184
  }
@@ -3095,9 +3187,10 @@ var VercelKvStorageAdapter;
3095
3187
  var init_vercel_kv = __esm({
3096
3188
  "libs/utils/src/storage/adapters/vercel-kv.ts"() {
3097
3189
  "use strict";
3098
- init_base();
3099
3190
  init_errors();
3191
+ init_compare_and_delete();
3100
3192
  init_ttl();
3193
+ init_base();
3101
3194
  VercelKvStorageAdapter = class extends BaseStorageAdapter {
3102
3195
  backendName = "vercel-kv";
3103
3196
  client;
@@ -3122,17 +3215,13 @@ var init_vercel_kv = __esm({
3122
3215
  async connect() {
3123
3216
  if (this.connected) return;
3124
3217
  try {
3125
- const { createClient, kv } = getVercelKv();
3126
- if (this.options.url === process.env["KV_REST_API_URL"]) {
3127
- this.client = kv;
3128
- } else {
3129
- const url = this.options.url;
3130
- const token = this.options.token;
3131
- if (!url || !token) {
3132
- throw new StorageConfigError("vercel-kv", "URL and token are required");
3133
- }
3134
- this.client = createClient({ url, token });
3218
+ const { createClient } = await loadVercelKv();
3219
+ const url = this.options.url;
3220
+ const token = this.options.token;
3221
+ if (!url || !token) {
3222
+ throw new StorageConfigError("vercel-kv", "URL and token are required");
3135
3223
  }
3224
+ this.client = createClient({ url, token, cache: void 0 });
3136
3225
  await this.client.exists("__healthcheck__");
3137
3226
  this.connected = true;
3138
3227
  } catch (e) {
@@ -3194,6 +3283,14 @@ var init_vercel_kv = __esm({
3194
3283
  const result = await this.getConnectedClient().del(this.prefixKey(key));
3195
3284
  return result > 0;
3196
3285
  }
3286
+ async deleteIfEquals(key, expectedValue) {
3287
+ const deleted = await this.getConnectedClient().eval(
3288
+ COMPARE_AND_DELETE_SCRIPT,
3289
+ [this.prefixKey(key)],
3290
+ [expectedValue]
3291
+ );
3292
+ return deleted === 1;
3293
+ }
3197
3294
  async exists(key) {
3198
3295
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
3199
3296
  return result > 0;
@@ -3310,9 +3407,10 @@ var UpstashStorageAdapter;
3310
3407
  var init_upstash = __esm({
3311
3408
  "libs/utils/src/storage/adapters/upstash.ts"() {
3312
3409
  "use strict";
3313
- init_base();
3314
3410
  init_errors();
3411
+ init_compare_and_delete();
3315
3412
  init_ttl();
3413
+ init_base();
3316
3414
  PUBSUB_POLL_INTERVAL_MS = 100;
3317
3415
  UpstashStorageAdapter = class extends BaseStorageAdapter {
3318
3416
  backendName = "upstash";
@@ -3417,6 +3515,14 @@ var init_upstash = __esm({
3417
3515
  const result = await this.getConnectedClient().del(this.prefixKey(key));
3418
3516
  return result > 0;
3419
3517
  }
3518
+ async deleteIfEquals(key, expectedValue) {
3519
+ const deleted = await this.getConnectedClient().eval(
3520
+ COMPARE_AND_DELETE_SCRIPT,
3521
+ [this.prefixKey(key)],
3522
+ [expectedValue]
3523
+ );
3524
+ return deleted === 1;
3525
+ }
3420
3526
  async exists(key) {
3421
3527
  const result = await this.getConnectedClient().exists(this.prefixKey(key));
3422
3528
  return result > 0;
@@ -3605,7 +3711,9 @@ var init_cloudflare_kv = __esm({
3605
3711
  constructor(options) {
3606
3712
  super();
3607
3713
  if (!options?.namespace) {
3608
- throw new Error("CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`).");
3714
+ throw new Error(
3715
+ "CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`)."
3716
+ );
3609
3717
  }
3610
3718
  this.kv = options.namespace;
3611
3719
  this.keyPrefix = options.keyPrefix ?? "";
@@ -3699,6 +3807,13 @@ var init_cloudflare_kv = __esm({
3699
3807
  "Cloudflare KV has no atomic increment (a get+put is racy under eventual consistency). Use a Durable Object for atomic counters."
3700
3808
  );
3701
3809
  }
3810
+ async deleteIfEquals(_key, _expectedValue) {
3811
+ throw new StorageNotSupportedError(
3812
+ "deleteIfEquals",
3813
+ this.backendName,
3814
+ "Cloudflare KV has no compare-and-set; use a Durable Object for an atomic compare-and-delete."
3815
+ );
3816
+ }
3702
3817
  // ── Pub/Sub (unsupported) ────────────────────────────────────────────────
3703
3818
  getPubSubSuggestion() {
3704
3819
  return "Cloudflare KV does not support pub/sub. Use a Durable Object (or Queues) for fan-out.";
@@ -3812,18 +3927,10 @@ function compileBlocks(entries, parseNetwork) {
3812
3927
  }
3813
3928
  init_fs2();
3814
3929
  init_crypto();
3815
- var entryAvailabilitySchema = import_lazy_zod4.z.object({
3816
- os: import_lazy_zod4.z.array(import_lazy_zod4.z.string().min(1)).optional(),
3817
- platform: import_lazy_zod4.z.array(import_lazy_zod4.z.string().min(1)).optional(),
3818
- runtime: import_lazy_zod4.z.array(import_lazy_zod4.z.string().min(1)).optional(),
3819
- deployment: import_lazy_zod4.z.array(import_lazy_zod4.z.string().min(1)).optional(),
3820
- provider: import_lazy_zod4.z.array(import_lazy_zod4.z.string().min(1)).optional(),
3821
- target: import_lazy_zod4.z.array(import_lazy_zod4.z.string().min(1)).optional(),
3822
- surface: import_lazy_zod4.z.array(import_lazy_zod4.z.enum(["mcp", "cli", "http-trigger", "job", "agent"])).optional(),
3823
- env: import_lazy_zod4.z.array(import_lazy_zod4.z.string().min(1)).optional()
3824
- }).strict();
3930
+ init_errors();
3825
3931
  init_errors();
3826
3932
  init_memory();
3933
+ init_redis_error_listener();
3827
3934
  init_errors();
3828
3935
  init_crypto();
3829
3936
  init_errors();
@@ -4793,11 +4900,11 @@ GraphDataProvider = __decorateClass([
4793
4900
  ], GraphDataProvider);
4794
4901
 
4795
4902
  // plugins/plugin-dashboard/src/tools/graph.tool.ts
4796
- var import_lazy_zod5 = require("@frontmcp/lazy-zod");
4903
+ var import_lazy_zod4 = require("@frontmcp/lazy-zod");
4797
4904
  var import_sdk4 = require("@frontmcp/sdk");
4798
4905
  var graphToolInputSchema = {
4799
- includeSchemas: import_lazy_zod5.z.boolean().optional().default(false).describe("Include full input/output schemas in the response"),
4800
- refresh: import_lazy_zod5.z.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")
4906
+ includeSchemas: import_lazy_zod4.z.boolean().optional().default(false).describe("Include full input/output schemas in the response"),
4907
+ refresh: import_lazy_zod4.z.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")
4801
4908
  };
4802
4909
  var GraphTool = class extends import_sdk4.ToolContext {
4803
4910
  async execute(input) {
@@ -4835,7 +4942,7 @@ GraphTool = __decorateClass([
4835
4942
  ], GraphTool);
4836
4943
 
4837
4944
  // plugins/plugin-dashboard/src/tools/list-resources.tool.ts
4838
- var import_lazy_zod6 = require("@frontmcp/lazy-zod");
4945
+ var import_lazy_zod5 = require("@frontmcp/lazy-zod");
4839
4946
  var import_sdk5 = require("@frontmcp/sdk");
4840
4947
 
4841
4948
  // plugins/plugin-dashboard/src/shared/safe-regex.ts
@@ -4854,20 +4961,20 @@ function safeRegex(pattern) {
4854
4961
 
4855
4962
  // plugins/plugin-dashboard/src/tools/list-resources.tool.ts
4856
4963
  var listResourcesInputSchema = {
4857
- filter: import_lazy_zod6.z.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),
4858
- includeTemplates: import_lazy_zod6.z.boolean().default(true).describe("Include resource templates in the response")
4964
+ filter: import_lazy_zod5.z.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),
4965
+ includeTemplates: import_lazy_zod5.z.boolean().default(true).describe("Include resource templates in the response")
4859
4966
  };
4860
- var listResourcesOutputSchema = import_lazy_zod6.z.object({
4861
- resources: import_lazy_zod6.z.array(
4862
- import_lazy_zod6.z.object({
4863
- name: import_lazy_zod6.z.string(),
4864
- uri: import_lazy_zod6.z.string(),
4865
- description: import_lazy_zod6.z.string().optional(),
4866
- mimeType: import_lazy_zod6.z.string().optional(),
4867
- isTemplate: import_lazy_zod6.z.boolean()
4967
+ var listResourcesOutputSchema = import_lazy_zod5.z.object({
4968
+ resources: import_lazy_zod5.z.array(
4969
+ import_lazy_zod5.z.object({
4970
+ name: import_lazy_zod5.z.string(),
4971
+ uri: import_lazy_zod5.z.string(),
4972
+ description: import_lazy_zod5.z.string().optional(),
4973
+ mimeType: import_lazy_zod5.z.string().optional(),
4974
+ isTemplate: import_lazy_zod5.z.boolean()
4868
4975
  })
4869
4976
  ),
4870
- count: import_lazy_zod6.z.number()
4977
+ count: import_lazy_zod5.z.number()
4871
4978
  });
4872
4979
  var ListResourcesTool = class extends import_sdk5.ToolContext {
4873
4980
  async execute(input) {
@@ -4929,25 +5036,25 @@ ListResourcesTool = __decorateClass([
4929
5036
  ], ListResourcesTool);
4930
5037
 
4931
5038
  // plugins/plugin-dashboard/src/tools/list-tools.tool.ts
4932
- var import_lazy_zod7 = require("@frontmcp/lazy-zod");
5039
+ var import_lazy_zod6 = require("@frontmcp/lazy-zod");
4933
5040
  var import_sdk6 = require("@frontmcp/sdk");
4934
5041
  var listToolsInputSchema = {
4935
- filter: import_lazy_zod7.z.string().optional().describe("Filter tools by name pattern (regex supported)"),
4936
- includePlugins: import_lazy_zod7.z.boolean().default(true).describe("Include tools from plugins"),
4937
- includeSchemas: import_lazy_zod7.z.boolean().default(false).describe("Include input/output schemas in the response")
5042
+ filter: import_lazy_zod6.z.string().optional().describe("Filter tools by name pattern (regex supported)"),
5043
+ includePlugins: import_lazy_zod6.z.boolean().default(true).describe("Include tools from plugins"),
5044
+ includeSchemas: import_lazy_zod6.z.boolean().default(false).describe("Include input/output schemas in the response")
4938
5045
  };
4939
- var listToolsOutputSchema = import_lazy_zod7.z.object({
4940
- tools: import_lazy_zod7.z.array(
4941
- import_lazy_zod7.z.object({
4942
- name: import_lazy_zod7.z.string(),
4943
- fullName: import_lazy_zod7.z.string(),
4944
- description: import_lazy_zod7.z.string().optional(),
4945
- tags: import_lazy_zod7.z.array(import_lazy_zod7.z.string()).optional(),
4946
- inputSchema: import_lazy_zod7.z.unknown().optional(),
4947
- outputSchema: import_lazy_zod7.z.unknown().optional()
5046
+ var listToolsOutputSchema = import_lazy_zod6.z.object({
5047
+ tools: import_lazy_zod6.z.array(
5048
+ import_lazy_zod6.z.object({
5049
+ name: import_lazy_zod6.z.string(),
5050
+ fullName: import_lazy_zod6.z.string(),
5051
+ description: import_lazy_zod6.z.string().optional(),
5052
+ tags: import_lazy_zod6.z.array(import_lazy_zod6.z.string()).optional(),
5053
+ inputSchema: import_lazy_zod6.z.unknown().optional(),
5054
+ outputSchema: import_lazy_zod6.z.unknown().optional()
4948
5055
  })
4949
5056
  ),
4950
- count: import_lazy_zod7.z.number()
5057
+ count: import_lazy_zod6.z.number()
4951
5058
  });
4952
5059
  var ListToolsTool = class extends import_sdk6.ToolContext {
4953
5060
  async execute(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.8.6",
3
+ "version": "1.9.0",
4
4
  "description": "Dashboard plugin for FrontMCP - visual monitoring and introspection of MCP servers",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -47,12 +47,12 @@
47
47
  }
48
48
  },
49
49
  "dependencies": {
50
- "@frontmcp/sdk": "1.8.6"
50
+ "@frontmcp/sdk": "1.9.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.8.6"
56
+ "@frontmcp/lazy-zod": "1.9.0"
57
57
  }
58
58
  }