@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 +227 -117
- package/esm/package.json +3 -3
- package/index.js +224 -117
- package/package.json +3 -3
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
|
-
|
|
448
|
-
var require2 =
|
|
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
|
|
785
|
-
if (
|
|
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)
|
|
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
|
|
3167
|
+
async function loadVercelKv() {
|
|
3076
3168
|
try {
|
|
3077
|
-
|
|
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
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
4894
|
+
import { z as z3 } from "@frontmcp/lazy-zod";
|
|
4785
4895
|
import { Tool, ToolContext } from "@frontmcp/sdk";
|
|
4786
4896
|
var graphToolInputSchema = {
|
|
4787
|
-
includeSchemas:
|
|
4788
|
-
refresh:
|
|
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
|
|
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:
|
|
4846
|
-
includeTemplates:
|
|
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 =
|
|
4849
|
-
resources:
|
|
4850
|
-
|
|
4851
|
-
name:
|
|
4852
|
-
uri:
|
|
4853
|
-
description:
|
|
4854
|
-
mimeType:
|
|
4855
|
-
isTemplate:
|
|
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:
|
|
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
|
|
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:
|
|
4924
|
-
includePlugins:
|
|
4925
|
-
includeSchemas:
|
|
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 =
|
|
4928
|
-
tools:
|
|
4929
|
-
|
|
4930
|
-
name:
|
|
4931
|
-
fullName:
|
|
4932
|
-
description:
|
|
4933
|
-
tags:
|
|
4934
|
-
inputSchema:
|
|
4935
|
-
outputSchema:
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
797
|
-
if (
|
|
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)
|
|
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
|
|
3176
|
+
async function loadVercelKv() {
|
|
3088
3177
|
try {
|
|
3089
|
-
|
|
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
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
4903
|
+
var import_lazy_zod4 = require("@frontmcp/lazy-zod");
|
|
4797
4904
|
var import_sdk4 = require("@frontmcp/sdk");
|
|
4798
4905
|
var graphToolInputSchema = {
|
|
4799
|
-
includeSchemas:
|
|
4800
|
-
refresh:
|
|
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
|
|
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:
|
|
4858
|
-
includeTemplates:
|
|
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 =
|
|
4861
|
-
resources:
|
|
4862
|
-
|
|
4863
|
-
name:
|
|
4864
|
-
uri:
|
|
4865
|
-
description:
|
|
4866
|
-
mimeType:
|
|
4867
|
-
isTemplate:
|
|
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:
|
|
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
|
|
5039
|
+
var import_lazy_zod6 = require("@frontmcp/lazy-zod");
|
|
4933
5040
|
var import_sdk6 = require("@frontmcp/sdk");
|
|
4934
5041
|
var listToolsInputSchema = {
|
|
4935
|
-
filter:
|
|
4936
|
-
includePlugins:
|
|
4937
|
-
includeSchemas:
|
|
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 =
|
|
4940
|
-
tools:
|
|
4941
|
-
|
|
4942
|
-
name:
|
|
4943
|
-
fullName:
|
|
4944
|
-
description:
|
|
4945
|
-
tags:
|
|
4946
|
-
inputSchema:
|
|
4947
|
-
outputSchema:
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
56
|
+
"@frontmcp/lazy-zod": "1.9.0"
|
|
57
57
|
}
|
|
58
58
|
}
|