@frontmcp/plugin-dashboard 1.8.5 → 1.8.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/esm/index.mjs CHANGED
@@ -432,7 +432,6 @@ import {
432
432
  import { MCP_ERROR_CODES, PublicMcpError } from "@frontmcp/sdk";
433
433
 
434
434
  // libs/utils/dist/esm/index.mjs
435
- import { createRequire as __frontmcpCreateRequire } from "module";
436
435
  import { z as z2 } from "@frontmcp/lazy-zod";
437
436
  var import_event_emitter = __toESM(require_node_event_emitter(), 1);
438
437
  import { z as z22 } from "@frontmcp/lazy-zod";
@@ -445,7 +444,8 @@ import { randomBytes as nobleRandomBytes } from "@noble/hashes/utils.js";
445
444
  import { gcm } from "@noble/ciphers/aes.js";
446
445
  import { analyzeForReDoS, REDOS_THRESHOLDS } from "@enclave-vm/ast";
447
446
  import { z as z3 } from "@frontmcp/lazy-zod";
448
- var require2 = __frontmcpCreateRequire(import.meta.url || "file:///");
447
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : void 0;
448
+ var require2 = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || "file:///") : void 0;
449
449
  var __defProp2 = Object.defineProperty;
450
450
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
451
451
  var __require2 = /* @__PURE__ */ ((x) => typeof require2 !== "undefined" ? require2 : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -2687,6 +2687,39 @@ var init_crypto = __esm({
2687
2687
  HKDF_SHA256_MAX_LENGTH = 255 * 32;
2688
2688
  }
2689
2689
  });
2690
+ function attachRedisErrorListener(client, options = {}) {
2691
+ const label = options.label ?? "redis";
2692
+ const intervalMs = options.intervalMs ?? DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
2693
+ let lastLoggedAt = 0;
2694
+ let suppressed = 0;
2695
+ const listener = (error) => {
2696
+ const now = Date.now();
2697
+ if (lastLoggedAt !== 0 && now - lastLoggedAt < intervalMs) {
2698
+ suppressed++;
2699
+ return;
2700
+ }
2701
+ const extra = suppressed > 0 ? ` (${suppressed} similar error(s) suppressed)` : "";
2702
+ suppressed = 0;
2703
+ lastLoggedAt = now;
2704
+ const message = `[${label}] Redis connection error: ${error?.message ?? String(error)}${extra}`;
2705
+ if (options.logger) {
2706
+ options.logger.warn(message);
2707
+ } else {
2708
+ console.warn(message);
2709
+ }
2710
+ };
2711
+ client.on("error", listener);
2712
+ return () => {
2713
+ client.removeListener("error", listener);
2714
+ };
2715
+ }
2716
+ var DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
2717
+ var init_redis_error_listener = __esm({
2718
+ "libs/utils/src/storage/redis-error-listener.ts"() {
2719
+ "use strict";
2720
+ DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 3e4;
2721
+ }
2722
+ });
2690
2723
  var init_utils = __esm({
2691
2724
  "libs/utils/src/storage/utils/index.ts"() {
2692
2725
  "use strict";
@@ -2695,7 +2728,8 @@ var init_utils = __esm({
2695
2728
  });
2696
2729
  var redis_exports = {};
2697
2730
  __export(redis_exports, {
2698
- RedisStorageAdapter: () => RedisStorageAdapter
2731
+ RedisStorageAdapter: () => RedisStorageAdapter,
2732
+ createRedisClient: () => createRedisClient
2699
2733
  });
2700
2734
  function getRedisClass() {
2701
2735
  try {
@@ -2704,25 +2738,45 @@ function getRedisClass() {
2704
2738
  const msg = error instanceof Error ? error.message : String(error);
2705
2739
  if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
2706
2740
  throw new Error(
2707
- `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`
2741
+ `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,
2742
+ { cause: error }
2708
2743
  );
2709
2744
  }
2710
- throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis");
2745
+ throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis", {
2746
+ cause: error
2747
+ });
2711
2748
  }
2712
2749
  }
2750
+ function createRedisClient(options) {
2751
+ const RedisClass = getRedisClass();
2752
+ const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
2753
+ const client = options.url ? new RedisClass(options.url, baseOptions) : new RedisClass({
2754
+ ...baseOptions,
2755
+ host: options.host ?? "localhost",
2756
+ port: options.port ?? 6379,
2757
+ password: options.password,
2758
+ db: options.db ?? 0,
2759
+ tls: options.tls ? {} : void 0
2760
+ });
2761
+ attachRedisErrorListener(client, { label: options.label ?? "redis", logger: options.logger });
2762
+ return client;
2763
+ }
2713
2764
  var RedisStorageAdapter;
2714
2765
  var init_redis = __esm({
2715
2766
  "libs/utils/src/storage/adapters/redis.ts"() {
2716
2767
  "use strict";
2717
- init_base();
2718
2768
  init_errors();
2769
+ init_redis_error_listener();
2719
2770
  init_utils();
2771
+ init_base();
2720
2772
  RedisStorageAdapter = class extends BaseStorageAdapter {
2721
2773
  backendName = "redis";
2722
2774
  client;
2723
2775
  subscriber;
2724
2776
  options;
2725
2777
  ownsClient;
2778
+ detachErrorListener;
2779
+ connecting;
2726
2780
  keyPrefix;
2727
2781
  subscriptionHandlers = /* @__PURE__ */ new Map();
2728
2782
  constructor(options = {}) {
@@ -2752,21 +2806,45 @@ var init_redis = __esm({
2752
2806
  // ============================================
2753
2807
  async connect() {
2754
2808
  if (this.connected) return;
2809
+ this.connecting ??= this.openConnection().finally(() => {
2810
+ this.connecting = void 0;
2811
+ });
2812
+ return this.connecting;
2813
+ }
2814
+ async openConnection() {
2815
+ let socketError;
2816
+ const recordSocketError = (error) => {
2817
+ socketError = error;
2818
+ };
2819
+ let client;
2755
2820
  try {
2756
2821
  if (this.options.client) {
2757
- this.client = this.options.client;
2822
+ client = this.options.client;
2758
2823
  } else {
2759
2824
  const RedisClass = getRedisClass();
2760
2825
  if (this.options.url) {
2761
- this.client = new RedisClass(this.options.url, this.buildRedisOptions());
2826
+ client = new RedisClass(this.options.url, this.buildRedisOptions());
2762
2827
  } else {
2763
- this.client = new RedisClass(this.buildRedisOptions());
2828
+ client = new RedisClass(this.buildRedisOptions());
2764
2829
  }
2830
+ client.on("error", recordSocketError);
2765
2831
  }
2766
- await this.client.ping();
2832
+ await client.ping();
2833
+ this.client = client;
2767
2834
  this.connected = true;
2835
+ if (this.ownsClient) {
2836
+ client.removeListener("error", recordSocketError);
2837
+ this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
2838
+ }
2768
2839
  } catch (e) {
2769
- throw new StorageConnectionError("Failed to connect to Redis", e instanceof Error ? e : void 0, "redis");
2840
+ if (this.ownsClient && client) {
2841
+ try {
2842
+ client.disconnect();
2843
+ } catch {
2844
+ }
2845
+ }
2846
+ const cause = socketError ?? (e instanceof Error ? e : void 0);
2847
+ throw new StorageConnectionError("Failed to connect to Redis", cause, "redis");
2770
2848
  }
2771
2849
  }
2772
2850
  async disconnect() {
@@ -2778,6 +2856,8 @@ var init_redis = __esm({
2778
2856
  if (this.ownsClient && this.client) {
2779
2857
  await this.client.quit();
2780
2858
  }
2859
+ this.detachErrorListener?.();
2860
+ this.detachErrorListener = void 0;
2781
2861
  this.client = void 0;
2782
2862
  this.connected = false;
2783
2863
  this.subscriptionHandlers.clear();
@@ -3005,6 +3085,7 @@ var init_redis = __esm({
3005
3085
  } else {
3006
3086
  throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
3007
3087
  }
3088
+ attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
3008
3089
  subscriber.on("message", (channel, message) => {
3009
3090
  const handlers = this.subscriptionHandlers.get(channel);
3010
3091
  if (handlers) {
@@ -3787,6 +3868,7 @@ var entryAvailabilitySchema = z3.object({
3787
3868
  }).strict();
3788
3869
  init_errors();
3789
3870
  init_memory();
3871
+ init_redis_error_listener();
3790
3872
  init_errors();
3791
3873
  init_crypto();
3792
3874
  init_errors();
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.8.5",
3
+ "version": "1.8.7",
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.5"
50
+ "@frontmcp/sdk": "1.8.7"
51
51
  },
52
52
  "devDependencies": {
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.8.5"
56
+ "@frontmcp/lazy-zod": "1.8.7"
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);
@@ -457,7 +456,8 @@ var import_crypto_provider = __toESM(require_node(), 1);
457
456
  var import_ast = require("@enclave-vm/ast");
458
457
  var import_lazy_zod4 = require("@frontmcp/lazy-zod");
459
458
  var import_meta = {};
460
- var require2 = (0, import_module.createRequire)(import_meta.url || "file:///");
459
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : void 0;
460
+ var require2 = __frontmcpModule ? __frontmcpModule.createRequire(import_meta.url || "file:///") : void 0;
461
461
  var __defProp2 = Object.defineProperty;
462
462
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
463
463
  var __require = /* @__PURE__ */ ((x) => typeof require2 !== "undefined" ? require2 : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -2699,6 +2699,39 @@ var init_crypto = __esm({
2699
2699
  HKDF_SHA256_MAX_LENGTH = 255 * 32;
2700
2700
  }
2701
2701
  });
2702
+ function attachRedisErrorListener(client, options = {}) {
2703
+ const label = options.label ?? "redis";
2704
+ const intervalMs = options.intervalMs ?? DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
2705
+ let lastLoggedAt = 0;
2706
+ let suppressed = 0;
2707
+ const listener = (error) => {
2708
+ const now = Date.now();
2709
+ if (lastLoggedAt !== 0 && now - lastLoggedAt < intervalMs) {
2710
+ suppressed++;
2711
+ return;
2712
+ }
2713
+ const extra = suppressed > 0 ? ` (${suppressed} similar error(s) suppressed)` : "";
2714
+ suppressed = 0;
2715
+ lastLoggedAt = now;
2716
+ const message = `[${label}] Redis connection error: ${error?.message ?? String(error)}${extra}`;
2717
+ if (options.logger) {
2718
+ options.logger.warn(message);
2719
+ } else {
2720
+ console.warn(message);
2721
+ }
2722
+ };
2723
+ client.on("error", listener);
2724
+ return () => {
2725
+ client.removeListener("error", listener);
2726
+ };
2727
+ }
2728
+ var DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;
2729
+ var init_redis_error_listener = __esm({
2730
+ "libs/utils/src/storage/redis-error-listener.ts"() {
2731
+ "use strict";
2732
+ DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 3e4;
2733
+ }
2734
+ });
2702
2735
  var init_utils = __esm({
2703
2736
  "libs/utils/src/storage/utils/index.ts"() {
2704
2737
  "use strict";
@@ -2707,7 +2740,8 @@ var init_utils = __esm({
2707
2740
  });
2708
2741
  var redis_exports = {};
2709
2742
  __export2(redis_exports, {
2710
- RedisStorageAdapter: () => RedisStorageAdapter
2743
+ RedisStorageAdapter: () => RedisStorageAdapter,
2744
+ createRedisClient: () => createRedisClient
2711
2745
  });
2712
2746
  function getRedisClass() {
2713
2747
  try {
@@ -2716,25 +2750,45 @@ function getRedisClass() {
2716
2750
  const msg = error instanceof Error ? error.message : String(error);
2717
2751
  if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
2718
2752
  throw new Error(
2719
- `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`
2753
+ `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,
2754
+ { cause: error }
2720
2755
  );
2721
2756
  }
2722
- throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis");
2757
+ throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis", {
2758
+ cause: error
2759
+ });
2723
2760
  }
2724
2761
  }
2762
+ function createRedisClient(options) {
2763
+ const RedisClass = getRedisClass();
2764
+ const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
2765
+ const client = options.url ? new RedisClass(options.url, baseOptions) : new RedisClass({
2766
+ ...baseOptions,
2767
+ host: options.host ?? "localhost",
2768
+ port: options.port ?? 6379,
2769
+ password: options.password,
2770
+ db: options.db ?? 0,
2771
+ tls: options.tls ? {} : void 0
2772
+ });
2773
+ attachRedisErrorListener(client, { label: options.label ?? "redis", logger: options.logger });
2774
+ return client;
2775
+ }
2725
2776
  var RedisStorageAdapter;
2726
2777
  var init_redis = __esm({
2727
2778
  "libs/utils/src/storage/adapters/redis.ts"() {
2728
2779
  "use strict";
2729
- init_base();
2730
2780
  init_errors();
2781
+ init_redis_error_listener();
2731
2782
  init_utils();
2783
+ init_base();
2732
2784
  RedisStorageAdapter = class extends BaseStorageAdapter {
2733
2785
  backendName = "redis";
2734
2786
  client;
2735
2787
  subscriber;
2736
2788
  options;
2737
2789
  ownsClient;
2790
+ detachErrorListener;
2791
+ connecting;
2738
2792
  keyPrefix;
2739
2793
  subscriptionHandlers = /* @__PURE__ */ new Map();
2740
2794
  constructor(options = {}) {
@@ -2764,21 +2818,45 @@ var init_redis = __esm({
2764
2818
  // ============================================
2765
2819
  async connect() {
2766
2820
  if (this.connected) return;
2821
+ this.connecting ??= this.openConnection().finally(() => {
2822
+ this.connecting = void 0;
2823
+ });
2824
+ return this.connecting;
2825
+ }
2826
+ async openConnection() {
2827
+ let socketError;
2828
+ const recordSocketError = (error) => {
2829
+ socketError = error;
2830
+ };
2831
+ let client;
2767
2832
  try {
2768
2833
  if (this.options.client) {
2769
- this.client = this.options.client;
2834
+ client = this.options.client;
2770
2835
  } else {
2771
2836
  const RedisClass = getRedisClass();
2772
2837
  if (this.options.url) {
2773
- this.client = new RedisClass(this.options.url, this.buildRedisOptions());
2838
+ client = new RedisClass(this.options.url, this.buildRedisOptions());
2774
2839
  } else {
2775
- this.client = new RedisClass(this.buildRedisOptions());
2840
+ client = new RedisClass(this.buildRedisOptions());
2776
2841
  }
2842
+ client.on("error", recordSocketError);
2777
2843
  }
2778
- await this.client.ping();
2844
+ await client.ping();
2845
+ this.client = client;
2779
2846
  this.connected = true;
2847
+ if (this.ownsClient) {
2848
+ client.removeListener("error", recordSocketError);
2849
+ this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
2850
+ }
2780
2851
  } catch (e) {
2781
- throw new StorageConnectionError("Failed to connect to Redis", e instanceof Error ? e : void 0, "redis");
2852
+ if (this.ownsClient && client) {
2853
+ try {
2854
+ client.disconnect();
2855
+ } catch {
2856
+ }
2857
+ }
2858
+ const cause = socketError ?? (e instanceof Error ? e : void 0);
2859
+ throw new StorageConnectionError("Failed to connect to Redis", cause, "redis");
2782
2860
  }
2783
2861
  }
2784
2862
  async disconnect() {
@@ -2790,6 +2868,8 @@ var init_redis = __esm({
2790
2868
  if (this.ownsClient && this.client) {
2791
2869
  await this.client.quit();
2792
2870
  }
2871
+ this.detachErrorListener?.();
2872
+ this.detachErrorListener = void 0;
2793
2873
  this.client = void 0;
2794
2874
  this.connected = false;
2795
2875
  this.subscriptionHandlers.clear();
@@ -3017,6 +3097,7 @@ var init_redis = __esm({
3017
3097
  } else {
3018
3098
  throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
3019
3099
  }
3100
+ attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
3020
3101
  subscriber.on("message", (channel, message) => {
3021
3102
  const handlers = this.subscriptionHandlers.get(channel);
3022
3103
  if (handlers) {
@@ -3799,6 +3880,7 @@ var entryAvailabilitySchema = import_lazy_zod4.z.object({
3799
3880
  }).strict();
3800
3881
  init_errors();
3801
3882
  init_memory();
3883
+ init_redis_error_listener();
3802
3884
  init_errors();
3803
3885
  init_crypto();
3804
3886
  init_errors();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.8.5",
3
+ "version": "1.8.7",
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.5"
50
+ "@frontmcp/sdk": "1.8.7"
51
51
  },
52
52
  "devDependencies": {
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.8.5"
56
+ "@frontmcp/lazy-zod": "1.8.7"
57
57
  }
58
58
  }