@frontmcp/plugin-dashboard 1.9.1-rc.3 → 1.9.2

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
@@ -2739,6 +2739,53 @@ var init_redis_error_listener = __esm({
2739
2739
  DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 3e4;
2740
2740
  }
2741
2741
  });
2742
+ function decodeOrRaw(encoded) {
2743
+ try {
2744
+ return decodeURIComponent(encoded);
2745
+ } catch {
2746
+ return encoded;
2747
+ }
2748
+ }
2749
+ function mergeRedisUrlFields(rawUrl, fields) {
2750
+ let url;
2751
+ try {
2752
+ url = new URL(rawUrl);
2753
+ } catch {
2754
+ return void 0;
2755
+ }
2756
+ const urlHost = url.hostname.replace(/^\[(.*)\]$/, "$1");
2757
+ const urlPort = url.port ? Number(url.port) : void 0;
2758
+ const urlPassword = url.password ? decodeOrRaw(url.password) : void 0;
2759
+ const urlDbText = url.pathname.replace(/^\/+/, "") || url.searchParams.get("db") || void 0;
2760
+ const urlDb = urlDbText === void 0 ? void 0 : Number(urlDbText);
2761
+ const urlUsesTls = url.protocol === "rediss:";
2762
+ const fillIns = {};
2763
+ const conflicts = [];
2764
+ if (fields.host !== void 0 && fields.host !== urlHost) conflicts.push("host");
2765
+ if (fields.port !== void 0) {
2766
+ if (urlPort === void 0) fillIns.port = fields.port;
2767
+ else if (fields.port !== urlPort) conflicts.push("port");
2768
+ }
2769
+ if (fields.password !== void 0) {
2770
+ if (urlPassword === void 0) fillIns.password = fields.password;
2771
+ else if (fields.password !== urlPassword) conflicts.push("password");
2772
+ }
2773
+ if (fields.db !== void 0) {
2774
+ if (urlDb === void 0) fillIns.db = fields.db;
2775
+ else if (fields.db !== urlDb) conflicts.push("db");
2776
+ }
2777
+ if (fields.tls === true && !urlUsesTls) fillIns.tls = true;
2778
+ if (fields.tls === false && urlUsesTls) conflicts.push("tls");
2779
+ return { fillIns, conflicts };
2780
+ }
2781
+ function describeRedisUrlConflicts(conflicts) {
2782
+ return `redis ${conflicts.join(", ")} contradict${conflicts.length === 1 ? "s" : ""} redis.url. Fields beside a url only fill in what the URL leaves out (port, password, db, tls); put the value in the URL or drop the field.`;
2783
+ }
2784
+ var init_redis_url = __esm({
2785
+ "libs/utils/src/storage/redis-url.ts"() {
2786
+ "use strict";
2787
+ }
2788
+ });
2742
2789
  var init_utils = __esm({
2743
2790
  "libs/utils/src/storage/utils/index.ts"() {
2744
2791
  "use strict";
@@ -2773,6 +2820,17 @@ function getRedisClass() {
2773
2820
  });
2774
2821
  }
2775
2822
  }
2823
+ function resolveUrlFillIns(connectionUrl, options) {
2824
+ const { url: configUrl, ...siblingFields } = { url: void 0, ...options.config };
2825
+ if (options.url && configUrl && configUrl !== options.url) {
2826
+ throw new StorageConfigError("redis", "redis.url and redis.config.url name different servers; set one of them.");
2827
+ }
2828
+ const merge = mergeRedisUrlFields(connectionUrl, siblingFields);
2829
+ if (merge && merge.conflicts.length > 0) {
2830
+ throw new StorageConfigError("redis", describeRedisUrlConflicts(merge.conflicts));
2831
+ }
2832
+ return merge?.fillIns ?? {};
2833
+ }
2776
2834
  function createRedisClient(options) {
2777
2835
  const RedisClass = getRedisClass();
2778
2836
  const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
@@ -2793,6 +2851,7 @@ var init_redis = __esm({
2793
2851
  "use strict";
2794
2852
  init_errors();
2795
2853
  init_redis_error_listener();
2854
+ init_redis_url();
2796
2855
  init_utils();
2797
2856
  init_compare_and_delete();
2798
2857
  init_base();
@@ -2806,6 +2865,8 @@ var init_redis = __esm({
2806
2865
  connecting;
2807
2866
  keyPrefix;
2808
2867
  subscriptionHandlers = /* @__PURE__ */ new Map();
2868
+ connectionUrl;
2869
+ urlFillIns;
2809
2870
  constructor(options = {}) {
2810
2871
  super();
2811
2872
  const hasClient = options.client !== void 0;
@@ -2827,6 +2888,9 @@ var init_redis = __esm({
2827
2888
  this.options = options;
2828
2889
  this.ownsClient = !hasClient;
2829
2890
  this.keyPrefix = options.keyPrefix ?? "";
2891
+ const configUrl = options.config?.url;
2892
+ this.connectionUrl = options.url ?? (typeof configUrl === "string" ? configUrl : void 0);
2893
+ this.urlFillIns = this.connectionUrl ? resolveUrlFillIns(this.connectionUrl, options) : {};
2830
2894
  }
2831
2895
  // ============================================
2832
2896
  // Connection Lifecycle
@@ -2849,8 +2913,8 @@ var init_redis = __esm({
2849
2913
  client = this.options.client;
2850
2914
  } else {
2851
2915
  const RedisClass = getRedisClass();
2852
- if (this.options.url) {
2853
- client = new RedisClass(this.options.url, this.buildRedisOptions());
2916
+ if (this.connectionUrl) {
2917
+ client = new RedisClass(this.connectionUrl, this.buildRedisOptions());
2854
2918
  } else {
2855
2919
  client = new RedisClass(this.buildRedisOptions());
2856
2920
  }
@@ -3086,14 +3150,19 @@ var init_redis = __esm({
3086
3150
  * Build Redis options from config.
3087
3151
  */
3088
3152
  buildRedisOptions() {
3089
- if (this.options.url) {
3153
+ if (this.connectionUrl) {
3154
+ const { port, password, db, tls } = this.urlFillIns;
3090
3155
  return {
3156
+ ...port !== void 0 ? { port } : {},
3157
+ ...password !== void 0 ? { password } : {},
3158
+ ...db !== void 0 ? { db } : {},
3159
+ ...tls ? { tls: {} } : {},
3091
3160
  lazyConnect: false,
3092
3161
  maxRetriesPerRequest: 3
3093
3162
  };
3094
3163
  }
3095
3164
  const config = this.options.config;
3096
- if (!config) {
3165
+ if (!config || typeof config.url === "string" || config.host === void 0) {
3097
3166
  throw new StorageConfigError("redis", "Redis config is required when URL is not provided");
3098
3167
  }
3099
3168
  return {
@@ -3112,8 +3181,8 @@ var init_redis = __esm({
3112
3181
  async createSubscriber() {
3113
3182
  const RedisClass = getRedisClass();
3114
3183
  let subscriber;
3115
- if (this.options.url) {
3116
- subscriber = new RedisClass(this.options.url);
3184
+ if (this.connectionUrl) {
3185
+ subscriber = new RedisClass(this.connectionUrl, this.buildRedisOptions());
3117
3186
  } else if (this.options.config) {
3118
3187
  subscriber = new RedisClass(this.buildRedisOptions());
3119
3188
  } else if (this.options.client) {
@@ -3162,7 +3231,8 @@ var init_redis = __esm({
3162
3231
  });
3163
3232
  var vercel_kv_exports = {};
3164
3233
  __export(vercel_kv_exports, {
3165
- VercelKvStorageAdapter: () => VercelKvStorageAdapter
3234
+ VercelKvStorageAdapter: () => VercelKvStorageAdapter,
3235
+ createVercelKvClient: () => createVercelKvClient
3166
3236
  });
3167
3237
  async function loadVercelKv() {
3168
3238
  try {
@@ -3174,6 +3244,15 @@ async function loadVercelKv() {
3174
3244
  throw new Error("@vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv");
3175
3245
  }
3176
3246
  }
3247
+ async function createVercelKvClient(connection = {}) {
3248
+ const url = connection.url ?? process.env["KV_REST_API_URL"];
3249
+ const token = connection.token ?? process.env["KV_REST_API_TOKEN"];
3250
+ if (!url || !token) {
3251
+ throw new StorageConfigError("vercel-kv", "Pass url and token, or set KV_REST_API_URL and KV_REST_API_TOKEN.");
3252
+ }
3253
+ const { createClient } = await loadVercelKv();
3254
+ return createClient({ url, token, cache: void 0, automaticDeserialization: false });
3255
+ }
3177
3256
  var VercelKvStorageAdapter;
3178
3257
  var init_vercel_kv = __esm({
3179
3258
  "libs/utils/src/storage/adapters/vercel-kv.ts"() {
@@ -3206,13 +3285,7 @@ var init_vercel_kv = __esm({
3206
3285
  async connect() {
3207
3286
  if (this.connected) return;
3208
3287
  try {
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");
3214
- }
3215
- this.client = createClient({ url, token, cache: void 0, automaticDeserialization: false });
3288
+ this.client = await createVercelKvClient({ url: this.options.url, token: this.options.token });
3216
3289
  await this.client.exists("__healthcheck__");
3217
3290
  this.connected = true;
3218
3291
  } catch (e) {
@@ -3383,9 +3456,12 @@ var upstash_exports = {};
3383
3456
  __export(upstash_exports, {
3384
3457
  UpstashStorageAdapter: () => UpstashStorageAdapter
3385
3458
  });
3386
- function getUpstashRedis() {
3459
+ async function loadUpstashRedis() {
3387
3460
  try {
3388
- return __require2("@upstash/redis");
3461
+ const mod = await import("@upstash/redis");
3462
+ const resolved = typeof mod.Redis === "function" ? mod : mod.default;
3463
+ if (!resolved || typeof resolved.Redis !== "function") throw new Error("Redis missing");
3464
+ return resolved;
3389
3465
  } catch {
3390
3466
  throw new Error(
3391
3467
  "@upstash/redis is required for Upstash storage adapter. Install it with: npm install @upstash/redis"
@@ -3431,7 +3507,7 @@ var init_upstash = __esm({
3431
3507
  async connect() {
3432
3508
  if (this.connected) return;
3433
3509
  try {
3434
- const { Redis } = getUpstashRedis();
3510
+ const { Redis } = await loadUpstashRedis();
3435
3511
  const url = this.options.url;
3436
3512
  const token = this.options.token;
3437
3513
  if (!url || !token) {
@@ -3920,6 +3996,7 @@ init_errors();
3920
3996
  init_errors();
3921
3997
  init_memory();
3922
3998
  init_redis_error_listener();
3999
+ init_redis_url();
3923
4000
  init_errors();
3924
4001
  init_crypto();
3925
4002
  init_errors();
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.9.1-rc.3",
3
+ "version": "1.9.2",
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.9.1-rc.3"
50
+ "@frontmcp/sdk": "1.9.2"
51
51
  },
52
52
  "devDependencies": {
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.9.1-rc.3"
56
+ "@frontmcp/lazy-zod": "1.9.2"
57
57
  }
58
58
  }
package/index.js CHANGED
@@ -2748,6 +2748,53 @@ var init_redis_error_listener = __esm({
2748
2748
  DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS = 3e4;
2749
2749
  }
2750
2750
  });
2751
+ function decodeOrRaw(encoded) {
2752
+ try {
2753
+ return decodeURIComponent(encoded);
2754
+ } catch {
2755
+ return encoded;
2756
+ }
2757
+ }
2758
+ function mergeRedisUrlFields(rawUrl, fields) {
2759
+ let url;
2760
+ try {
2761
+ url = new URL(rawUrl);
2762
+ } catch {
2763
+ return void 0;
2764
+ }
2765
+ const urlHost = url.hostname.replace(/^\[(.*)\]$/, "$1");
2766
+ const urlPort = url.port ? Number(url.port) : void 0;
2767
+ const urlPassword = url.password ? decodeOrRaw(url.password) : void 0;
2768
+ const urlDbText = url.pathname.replace(/^\/+/, "") || url.searchParams.get("db") || void 0;
2769
+ const urlDb = urlDbText === void 0 ? void 0 : Number(urlDbText);
2770
+ const urlUsesTls = url.protocol === "rediss:";
2771
+ const fillIns = {};
2772
+ const conflicts = [];
2773
+ if (fields.host !== void 0 && fields.host !== urlHost) conflicts.push("host");
2774
+ if (fields.port !== void 0) {
2775
+ if (urlPort === void 0) fillIns.port = fields.port;
2776
+ else if (fields.port !== urlPort) conflicts.push("port");
2777
+ }
2778
+ if (fields.password !== void 0) {
2779
+ if (urlPassword === void 0) fillIns.password = fields.password;
2780
+ else if (fields.password !== urlPassword) conflicts.push("password");
2781
+ }
2782
+ if (fields.db !== void 0) {
2783
+ if (urlDb === void 0) fillIns.db = fields.db;
2784
+ else if (fields.db !== urlDb) conflicts.push("db");
2785
+ }
2786
+ if (fields.tls === true && !urlUsesTls) fillIns.tls = true;
2787
+ if (fields.tls === false && urlUsesTls) conflicts.push("tls");
2788
+ return { fillIns, conflicts };
2789
+ }
2790
+ function describeRedisUrlConflicts(conflicts) {
2791
+ return `redis ${conflicts.join(", ")} contradict${conflicts.length === 1 ? "s" : ""} redis.url. Fields beside a url only fill in what the URL leaves out (port, password, db, tls); put the value in the URL or drop the field.`;
2792
+ }
2793
+ var init_redis_url = __esm({
2794
+ "libs/utils/src/storage/redis-url.ts"() {
2795
+ "use strict";
2796
+ }
2797
+ });
2751
2798
  var init_utils = __esm({
2752
2799
  "libs/utils/src/storage/utils/index.ts"() {
2753
2800
  "use strict";
@@ -2782,6 +2829,17 @@ function getRedisClass() {
2782
2829
  });
2783
2830
  }
2784
2831
  }
2832
+ function resolveUrlFillIns(connectionUrl, options) {
2833
+ const { url: configUrl, ...siblingFields } = { url: void 0, ...options.config };
2834
+ if (options.url && configUrl && configUrl !== options.url) {
2835
+ throw new StorageConfigError("redis", "redis.url and redis.config.url name different servers; set one of them.");
2836
+ }
2837
+ const merge = mergeRedisUrlFields(connectionUrl, siblingFields);
2838
+ if (merge && merge.conflicts.length > 0) {
2839
+ throw new StorageConfigError("redis", describeRedisUrlConflicts(merge.conflicts));
2840
+ }
2841
+ return merge?.fillIns ?? {};
2842
+ }
2785
2843
  function createRedisClient(options) {
2786
2844
  const RedisClass = getRedisClass();
2787
2845
  const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
@@ -2802,6 +2860,7 @@ var init_redis = __esm({
2802
2860
  "use strict";
2803
2861
  init_errors();
2804
2862
  init_redis_error_listener();
2863
+ init_redis_url();
2805
2864
  init_utils();
2806
2865
  init_compare_and_delete();
2807
2866
  init_base();
@@ -2815,6 +2874,8 @@ var init_redis = __esm({
2815
2874
  connecting;
2816
2875
  keyPrefix;
2817
2876
  subscriptionHandlers = /* @__PURE__ */ new Map();
2877
+ connectionUrl;
2878
+ urlFillIns;
2818
2879
  constructor(options = {}) {
2819
2880
  super();
2820
2881
  const hasClient = options.client !== void 0;
@@ -2836,6 +2897,9 @@ var init_redis = __esm({
2836
2897
  this.options = options;
2837
2898
  this.ownsClient = !hasClient;
2838
2899
  this.keyPrefix = options.keyPrefix ?? "";
2900
+ const configUrl = options.config?.url;
2901
+ this.connectionUrl = options.url ?? (typeof configUrl === "string" ? configUrl : void 0);
2902
+ this.urlFillIns = this.connectionUrl ? resolveUrlFillIns(this.connectionUrl, options) : {};
2839
2903
  }
2840
2904
  // ============================================
2841
2905
  // Connection Lifecycle
@@ -2858,8 +2922,8 @@ var init_redis = __esm({
2858
2922
  client = this.options.client;
2859
2923
  } else {
2860
2924
  const RedisClass = getRedisClass();
2861
- if (this.options.url) {
2862
- client = new RedisClass(this.options.url, this.buildRedisOptions());
2925
+ if (this.connectionUrl) {
2926
+ client = new RedisClass(this.connectionUrl, this.buildRedisOptions());
2863
2927
  } else {
2864
2928
  client = new RedisClass(this.buildRedisOptions());
2865
2929
  }
@@ -3095,14 +3159,19 @@ var init_redis = __esm({
3095
3159
  * Build Redis options from config.
3096
3160
  */
3097
3161
  buildRedisOptions() {
3098
- if (this.options.url) {
3162
+ if (this.connectionUrl) {
3163
+ const { port, password, db, tls } = this.urlFillIns;
3099
3164
  return {
3165
+ ...port !== void 0 ? { port } : {},
3166
+ ...password !== void 0 ? { password } : {},
3167
+ ...db !== void 0 ? { db } : {},
3168
+ ...tls ? { tls: {} } : {},
3100
3169
  lazyConnect: false,
3101
3170
  maxRetriesPerRequest: 3
3102
3171
  };
3103
3172
  }
3104
3173
  const config = this.options.config;
3105
- if (!config) {
3174
+ if (!config || typeof config.url === "string" || config.host === void 0) {
3106
3175
  throw new StorageConfigError("redis", "Redis config is required when URL is not provided");
3107
3176
  }
3108
3177
  return {
@@ -3121,8 +3190,8 @@ var init_redis = __esm({
3121
3190
  async createSubscriber() {
3122
3191
  const RedisClass = getRedisClass();
3123
3192
  let subscriber;
3124
- if (this.options.url) {
3125
- subscriber = new RedisClass(this.options.url);
3193
+ if (this.connectionUrl) {
3194
+ subscriber = new RedisClass(this.connectionUrl, this.buildRedisOptions());
3126
3195
  } else if (this.options.config) {
3127
3196
  subscriber = new RedisClass(this.buildRedisOptions());
3128
3197
  } else if (this.options.client) {
@@ -3171,7 +3240,8 @@ var init_redis = __esm({
3171
3240
  });
3172
3241
  var vercel_kv_exports = {};
3173
3242
  __export2(vercel_kv_exports, {
3174
- VercelKvStorageAdapter: () => VercelKvStorageAdapter
3243
+ VercelKvStorageAdapter: () => VercelKvStorageAdapter,
3244
+ createVercelKvClient: () => createVercelKvClient
3175
3245
  });
3176
3246
  async function loadVercelKv() {
3177
3247
  try {
@@ -3183,6 +3253,15 @@ async function loadVercelKv() {
3183
3253
  throw new Error("@vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv");
3184
3254
  }
3185
3255
  }
3256
+ async function createVercelKvClient(connection = {}) {
3257
+ const url = connection.url ?? process.env["KV_REST_API_URL"];
3258
+ const token = connection.token ?? process.env["KV_REST_API_TOKEN"];
3259
+ if (!url || !token) {
3260
+ throw new StorageConfigError("vercel-kv", "Pass url and token, or set KV_REST_API_URL and KV_REST_API_TOKEN.");
3261
+ }
3262
+ const { createClient } = await loadVercelKv();
3263
+ return createClient({ url, token, cache: void 0, automaticDeserialization: false });
3264
+ }
3186
3265
  var VercelKvStorageAdapter;
3187
3266
  var init_vercel_kv = __esm({
3188
3267
  "libs/utils/src/storage/adapters/vercel-kv.ts"() {
@@ -3215,13 +3294,7 @@ var init_vercel_kv = __esm({
3215
3294
  async connect() {
3216
3295
  if (this.connected) return;
3217
3296
  try {
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");
3223
- }
3224
- this.client = createClient({ url, token, cache: void 0, automaticDeserialization: false });
3297
+ this.client = await createVercelKvClient({ url: this.options.url, token: this.options.token });
3225
3298
  await this.client.exists("__healthcheck__");
3226
3299
  this.connected = true;
3227
3300
  } catch (e) {
@@ -3392,9 +3465,12 @@ var upstash_exports = {};
3392
3465
  __export2(upstash_exports, {
3393
3466
  UpstashStorageAdapter: () => UpstashStorageAdapter
3394
3467
  });
3395
- function getUpstashRedis() {
3468
+ async function loadUpstashRedis() {
3396
3469
  try {
3397
- return __require("@upstash/redis");
3470
+ const mod = await import("@upstash/redis");
3471
+ const resolved = typeof mod.Redis === "function" ? mod : mod.default;
3472
+ if (!resolved || typeof resolved.Redis !== "function") throw new Error("Redis missing");
3473
+ return resolved;
3398
3474
  } catch {
3399
3475
  throw new Error(
3400
3476
  "@upstash/redis is required for Upstash storage adapter. Install it with: npm install @upstash/redis"
@@ -3440,7 +3516,7 @@ var init_upstash = __esm({
3440
3516
  async connect() {
3441
3517
  if (this.connected) return;
3442
3518
  try {
3443
- const { Redis } = getUpstashRedis();
3519
+ const { Redis } = await loadUpstashRedis();
3444
3520
  const url = this.options.url;
3445
3521
  const token = this.options.token;
3446
3522
  if (!url || !token) {
@@ -3929,6 +4005,7 @@ init_errors();
3929
4005
  init_errors();
3930
4006
  init_memory();
3931
4007
  init_redis_error_listener();
4008
+ init_redis_url();
3932
4009
  init_errors();
3933
4010
  init_crypto();
3934
4011
  init_errors();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-dashboard",
3
- "version": "1.9.1-rc.3",
3
+ "version": "1.9.2",
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.9.1-rc.3"
50
+ "@frontmcp/sdk": "1.9.2"
51
51
  },
52
52
  "devDependencies": {
53
53
  "reflect-metadata": "^0.2.2"
54
54
  },
55
55
  "peerDependencies": {
56
- "@frontmcp/lazy-zod": "1.9.1-rc.3"
56
+ "@frontmcp/lazy-zod": "1.9.2"
57
57
  }
58
58
  }