@frontmcp/plugin-dashboard 1.10.16 → 1.11.0-rc.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/app/dashboard-page.route.d.ts +11 -0
- package/app/dashboard-page.route.d.ts.map +1 -0
- package/app/dashboard.app.d.ts +2 -2
- package/app/dashboard.app.d.ts.map +1 -1
- package/esm/index.mjs +2 -5
- package/esm/package.json +5 -5
- package/index.js +2 -2
- package/package.json +5 -5
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { HttpRouteContext, type HttpOutput } from '@frontmcp/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* The dashboard page, at `basePath`. It holds no data: its client reads the inventory from the dashboard's MCP
|
|
4
|
+
* endpoint, which the dashboard's `http:request` hook gates. With `auth` on, a page request that shows the token also
|
|
5
|
+
* gets an HttpOnly, `SameSite=Strict` cookie, which is how the page's own MCP client (an `EventSource`, which cannot
|
|
6
|
+
* send headers) authenticates without a token in a URL.
|
|
7
|
+
*/
|
|
8
|
+
export declare class DashboardPageRoute extends HttpRouteContext {
|
|
9
|
+
handle(): HttpOutput;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=dashboard-page.route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-page.route.d.ts","sourceRoot":"","sources":["../../src/app/dashboard-page.route.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,gBAAgB,EAAE,KAAK,UAAU,EAAsB,MAAM,eAAe,CAAC;AAwC9G;;;;;GAKG;AACH,qBACa,kBAAmB,SAAQ,gBAAgB;IACtD,MAAM,IAAI,UAAU;CAiBrB"}
|
package/app/dashboard.app.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type DashboardPluginOptions, type DashboardPluginOptionsInput } from '.
|
|
|
3
3
|
/**
|
|
4
4
|
* Internal Dashboard HTTP Plugin.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Gates the dashboard's MCP endpoint; `DashboardPageRoute` serves the page. The SSE transport
|
|
7
7
|
* and MCP protocol are handled by FrontMCP's built-in transport layer.
|
|
8
8
|
*/
|
|
9
9
|
declare class DashboardHttpPlugin extends DynamicPlugin<DashboardPluginOptions, DashboardPluginOptionsInput> {
|
|
@@ -24,7 +24,7 @@ declare class DashboardHttpPlugin extends DynamicPlugin<DashboardPluginOptions,
|
|
|
24
24
|
/** `this.get`, or `undefined` when the token isn't provided. */
|
|
25
25
|
private resolve;
|
|
26
26
|
/**
|
|
27
|
-
* Provide the dashboard config
|
|
27
|
+
* Provide the dashboard config via DI.
|
|
28
28
|
*/
|
|
29
29
|
static dynamicProviders(options: DashboardPluginOptionsInput): ProviderType[];
|
|
30
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.app.d.ts","sourceRoot":"","sources":["../../src/app/dashboard.app.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,
|
|
1
|
+
{"version":3,"file":"dashboard.app.d.ts","sourceRoot":"","sources":["../../src/app/dashboard.app.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAMb,KAAK,SAAS,EAEd,KAAK,YAAY,EAGlB,MAAM,eAAe,CAAC;AAQvB,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EACjC,MAAM,oBAAoB,CAAC;AAS5B;;;;;GAKG;AACH,cAIM,mBAAoB,SAAQ,aAAa,CAAC,sBAAsB,EAAE,2BAA2B,CAAC;IAClG,OAAO,EAAE,sBAAsB,CAAC;gBAEpB,OAAO,GAAE,2BAAgC;IAQrD;;;;;;;;;;OAUG;IAEG,gBAAgB,CAAC,OAAO,EAAE,SAAS,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBzE,gEAAgE;IAChE,OAAO,CAAC,OAAO;IAQf;;OAEG;WACa,gBAAgB,CAAC,OAAO,EAAE,2BAA2B,GAAG,YAAY,EAAE;CAkBvF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAoCa,YAAY;CAAG;AAG5B,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|
package/esm/index.mjs
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
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
|
|
4
|
-
var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf;var __hasOwnProp=Object.prototype.hasOwnProperty;var __require=(x=>typeof require!=="undefined"?require:typeof Proxy!=="undefined"?new Proxy(x,{get:(a,b)=>(typeof require!=="undefined"?require:a)[b]}):x)(function(x){if(typeof require!=="undefined")return require.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});var __commonJS=(cb,mod)=>function __require3(){try{return mod||(0,cb[__getOwnPropNames(cb)[0]])((mod={exports:{}}).exports,mod),mod.exports}catch(e){throw mod=0,e}};var __copyProps=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames(from))if(!__hasOwnProp.call(to,key)&&key!==except)__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable})}return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:true}):target,mod));var __decorateClass=(decorators,target,key,kind)=>{var result=kind>1?void 0:kind?__getOwnPropDesc(target,key):target;for(var i=decorators.length-1,decorator;i>=0;i--)if(decorator=decorators[i])result=(kind?decorator(target,key,result):decorator(result))||result;if(kind&&result)__defProp(target,key,result);return result};var require_node_event_emitter=__commonJS({"libs/utils/dist/event-emitter/node-event-emitter.js"(exports,module){"use strict";var __defProp3=Object.defineProperty;var __getOwnPropDesc2=Object.getOwnPropertyDescriptor;var __getOwnPropNames3=Object.getOwnPropertyNames;var __hasOwnProp2=Object.prototype.hasOwnProperty;var __export2=(target,all)=>{for(var name in all)__defProp3(target,name,{get:all[name],enumerable:true})};var __copyProps2=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames3(from))if(!__hasOwnProp2.call(to,key)&&key!==except)__defProp3(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc2(from,key))||desc.enumerable})}return to};var __toCommonJS=mod=>__copyProps2(__defProp3({},"__esModule",{value:true}),mod);var node_event_emitter_exports={};__export2(node_event_emitter_exports,{EventEmitter:()=>import_events.EventEmitter});module.exports=__toCommonJS(node_event_emitter_exports);var import_events=__require("events")}});var require_node=__commonJS({"libs/utils/dist/crypto/node.js"(exports,module){"use strict";var __create2=Object.create;var __defProp3=Object.defineProperty;var __getOwnPropDesc2=Object.getOwnPropertyDescriptor;var __getOwnPropNames3=Object.getOwnPropertyNames;var __getProtoOf2=Object.getPrototypeOf;var __hasOwnProp2=Object.prototype.hasOwnProperty;var __export2=(target,all)=>{for(var name in all)__defProp3(target,name,{get:all[name],enumerable:true})};var __copyProps2=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames3(from))if(!__hasOwnProp2.call(to,key)&&key!==except)__defProp3(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc2(from,key))||desc.enumerable})}return to};var __toESM2=(mod,isNodeMode,target)=>(target=mod!=null?__create2(__getProtoOf2(mod)):{},__copyProps2(isNodeMode||!mod||!mod.__esModule?__defProp3(target,"default",{value:mod,enumerable:true}):target,mod));var __toCommonJS=mod=>__copyProps2(__defProp3({},"__esModule",{value:true}),mod);var node_exports={};__export2(node_exports,{createSignedJwt:()=>createSignedJwt,cryptoProvider:()=>nodeCrypto,generateRsaKeyPair:()=>generateRsaKeyPair3,isRsaPssAlg:()=>isRsaPssAlg2,jwtAlgToNodeAlg:()=>jwtAlgToNodeAlg,nodeCrypto:()=>nodeCrypto,pemToPublicJwk:()=>pemToPublicJwk3,rsaSign:()=>rsaSign,rsaSignBase64Url:()=>rsaSignBase64Url3,rsaVerify:()=>rsaVerify3,rsaVerifySync:()=>rsaVerifySync3});module.exports=__toCommonJS(node_exports);var import_node_crypto=__toESM2(__require("node:crypto"));var JWT_ALG_TO_NODE_DIGEST2={RS256:"RSA-SHA256",RS384:"RSA-SHA384",RS512:"RSA-SHA512",PS256:"RSA-SHA256",PS384:"RSA-SHA384",PS512:"RSA-SHA512"};function jwtAlgToNodeAlg(jwtAlg){const nodeAlg=JWT_ALG_TO_NODE_DIGEST2[jwtAlg];if(!nodeAlg){throw new Error(`Unsupported JWT algorithm: ${jwtAlg}`)}return nodeAlg}function isRsaPssAlg2(jwtAlg){return jwtAlg.startsWith("PS")}function toUint8Array(buf){return new Uint8Array(buf.buffer,buf.byteOffset,buf.byteLength)}function toBuffer(data){if(typeof data==="string"){return Buffer.from(data,"utf8")}return Buffer.from(data)}var nodeCrypto={randomUUID(){return import_node_crypto.default.randomUUID()},randomBytes(length){return toUint8Array(import_node_crypto.default.randomBytes(length))},sha256(data){const hash=import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest();return toUint8Array(hash)},sha256Hex(data){return import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest("hex")},hmacSha256(key,data){const hmac2=import_node_crypto.default.createHmac("sha256",Buffer.from(key)).update(Buffer.from(data)).digest();return toUint8Array(hmac2)},hkdfSha256(ikm,salt,info,length){const ikmBuf=Buffer.from(ikm);const saltBuf=salt.length>0?Buffer.from(salt):Buffer.alloc(32);const prk=import_node_crypto.default.createHmac("sha256",saltBuf).update(ikmBuf).digest();const hashLen=32;const n=Math.ceil(length/hashLen);const chunks=[];let prev=Buffer.alloc(0);for(let i=1;i<=n;i++){prev=import_node_crypto.default.createHmac("sha256",prk).update(Buffer.concat([prev,Buffer.from(info),Buffer.from([i])])).digest();chunks.push(prev)}return toUint8Array(Buffer.concat(chunks).subarray(0,length))},encryptAesGcm(key,plaintext,iv){const cipher=import_node_crypto.default.createCipheriv("aes-256-gcm",Buffer.from(key),Buffer.from(iv));const encrypted=Buffer.concat([cipher.update(Buffer.from(plaintext)),cipher.final()]);const tag=cipher.getAuthTag();return{ciphertext:toUint8Array(encrypted),tag:toUint8Array(tag)}},decryptAesGcm(key,ciphertext,iv,tag){const decipher=import_node_crypto.default.createDecipheriv("aes-256-gcm",Buffer.from(key),Buffer.from(iv));decipher.setAuthTag(Buffer.from(tag));const decrypted=Buffer.concat([decipher.update(Buffer.from(ciphertext)),decipher.final()]);return toUint8Array(decrypted)},timingSafeEqual(a,b){if(a.length!==b.length)return false;return import_node_crypto.default.timingSafeEqual(Buffer.from(a),Buffer.from(b))}};function generateRsaKeyPair3(modulusLength=2048,alg="RS256"){const kid=`rsa-key-${Date.now()}-${import_node_crypto.default.randomBytes(8).toString("hex")}`;const{privateKey,publicKey}=import_node_crypto.default.generateKeyPairSync("rsa",{modulusLength});const exported=publicKey.export({format:"jwk"});const publicJwk={...exported,kid,alg,use:"sig",kty:"RSA"};return{privateKey,publicKey,publicJwk}}function rsaSign(algorithm,data,privateKey,options){const signingKey=options?{key:privateKey,...options}:privateKey;return import_node_crypto.default.sign(algorithm,data,signingKey)}function rsaVerify3(jwtAlg,data,publicJwk,signature){const publicKey=import_node_crypto.default.createPublicKey({key:publicJwk,format:"jwk"});const nodeAlgorithm=jwtAlgToNodeAlg(jwtAlg);const verifyKey=isRsaPssAlg2(jwtAlg)?{key:publicKey,padding:import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,saltLength:import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST}:publicKey;return import_node_crypto.default.verify(nodeAlgorithm,data,verifyKey,signature)}function rsaSignBase64Url3(jwtAlg,data,privateJwk){const privateKey=import_node_crypto.default.createPrivateKey({key:privateJwk,format:"jwk"});const nodeAlgorithm=jwtAlgToNodeAlg(jwtAlg);const buf=Buffer.isBuffer(data)?data:Buffer.from(data);const sig=isRsaPssAlg2(jwtAlg)?rsaSign(nodeAlgorithm,buf,privateKey,{padding:import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,saltLength:import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST}):rsaSign(nodeAlgorithm,buf,privateKey);return sig.toString("base64url")}function rsaVerifySync3(jwtAlg,data,publicJwk,signature){try{const publicKey=import_node_crypto.default.createPublicKey({key:publicJwk,format:"jwk"});const dataBuf=Buffer.isBuffer(data)?data:Buffer.from(data);const sigBuf=Buffer.isBuffer(signature)?signature:Buffer.from(signature);if(jwtAlg==="EdDSA"){return import_node_crypto.default.verify(null,dataBuf,publicKey,sigBuf)}const nodeAlgorithm=jwtAlgToNodeAlg(jwtAlg);const verifyKey=isRsaPssAlg2(jwtAlg)?{key:publicKey,padding:import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,saltLength:import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST}:publicKey;return import_node_crypto.default.verify(nodeAlgorithm,dataBuf,verifyKey,sigBuf)}catch{return false}}function pemToPublicJwk3(pem){const key=import_node_crypto.default.createPublicKey({key:pem,format:"pem"});return key.export({format:"jwk"})}function createSignedJwt(payload,privateKey,kid,alg="RS256"){const header={alg,typ:"JWT",kid};const headerB64=Buffer.from(JSON.stringify(header)).toString("base64url");const payloadB64=Buffer.from(JSON.stringify(payload)).toString("base64url");const signatureInput=`${headerB64}.${payloadB64}`;const nodeAlgorithm=jwtAlgToNodeAlg(alg);const signature=rsaSign(nodeAlgorithm,Buffer.from(signatureInput),privateKey,isRsaPssAlg2(alg)?{padding:import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,saltLength:import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST}:void 0);const signatureB64=signature.toString("base64url");return`${headerB64}.${payloadB64}.${signatureB64}`}}});import{DynamicPlugin,Plugin}from"@frontmcp/sdk";import{z}from"@frontmcp/lazy-zod";var cdnConfigSchema=z.object({entrypoint:z.string().optional(),react:z.string().default("https://esm.sh/react@19"),reactDom:z.string().default("https://esm.sh/react-dom@19"),reactDomClient:z.string().default("https://esm.sh/react-dom@19/client"),reactJsxRuntime:z.string().default("https://esm.sh/react@19/jsx-runtime"),reactRouter:z.string().default("https://esm.sh/react-router-dom@7"),xyflow:z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),dagre:z.string().default("https://esm.sh/dagre@0.8.5"),xyflowCss:z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")});var dashboardAuthSchema=z.object({enabled:z.boolean().default(false),token:z.string().min(1).optional()}).refine(value=>!value.enabled||Boolean(value.token),{message:"dashboard auth.enabled requires auth.token to be set",path:["token"]});var dashboardPluginOptionsSchema=z.object({enabled:z.boolean().optional(),basePath:z.string().default("/dashboard"),auth:dashboardAuthSchema.optional().transform(v=>dashboardAuthSchema.parse(v??{})),cdn:cdnConfigSchema.optional().transform(v=>cdnConfigSchema.parse(v??{}))});var defaultDashboardPluginOptions={basePath:"/dashboard",auth:{enabled:false},cdn:{}};function isDashboardEnabled(options){if(options.enabled!==void 0){return options.enabled}return process.env["NODE_ENV"]!=="production"}var publishedOptions;function publishDashboardOptions(options){if(publishedOptions&&!sameConfiguration(publishedOptions,options)){if(!sameAuth(publishedOptions,options)){throw new Error("[frontmcp:dashboard] A second, different dashboard AUTH configuration was registered in this process. Dashboard options are process-wide, so accepting it would apply one server's token to another. Run one dashboard per process, or give both servers the same auth configuration.")}console.warn("[frontmcp:dashboard] A second, different dashboard configuration was registered in this process. Dashboard options are process-wide, so the last one wins for every server here. Run one dashboard per process until per-instance configuration is supported.")}publishedOptions=options}function sameAuth(a,b){return a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token}function sameConfiguration(a,b){return a.basePath===b.basePath&&a.enabled===b.enabled&&a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token&&JSON.stringify(a.cdn??{})===JSON.stringify(b.cdn??{})}function resolveDashboardOptions(fallback={}){if(publishedOptions)return publishedOptions;return dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...fallback})}function resetDashboardOptions(){publishedOptions=void 0}var DashboardConfigToken=Symbol("DashboardConfig");var GraphDataProviderToken=Symbol("GraphDataProvider");var ParentScopeToken=Symbol("ParentScope");var DashboardPlugin=class extends DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(this.options)}static dynamicProviders(options){const parsedOptions=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(parsedOptions);return[{name:"dashboard:config",provide:DashboardConfigToken,useValue:parsedOptions}]}};DashboardPlugin=__decorateClass([Plugin({name:"dashboard",description:"Visual dashboard for FrontMCP server monitoring and visualization",providers:[]})],DashboardPlugin);import{App,DynamicPlugin as DynamicPlugin2,FrontMcpConfig,FrontMcpContextStorage,FrontMcpServer,HttpHook,Plugin as Plugin2,ScopeEntry as ScopeEntry2}from"@frontmcp/sdk";import{MCP_ERROR_CODES,PublicMcpError}from"@frontmcp/sdk";import{z as z2}from"@frontmcp/lazy-zod";var import_event_emitter=__toESM(require_node_event_emitter(),1);import{z as z22}from"@frontmcp/lazy-zod";var platformCrypto=__toESM(require_node(),1);import{gcm}from"@noble/ciphers/aes.js";import{hkdf}from"@noble/hashes/hkdf.js";import{hmac}from"@noble/hashes/hmac.js";import{sha256 as sha256Hash}from"@noble/hashes/sha2.js";import{randomBytes as nobleRandomBytes}from"@noble/hashes/utils.js";import{analyzeForReDoS,REDOS_THRESHOLDS}from"@enclave-vm/ast/redos";var __frontmcpModule=typeof process!=="undefined"&&typeof process.getBuiltinModule==="function"?process.getBuiltinModule("module"):void 0;var require2=__frontmcpModule?__frontmcpModule.createRequire(import.meta.url||"file:///"):void 0;var __defProp2=Object.defineProperty;var __getOwnPropNames2=Object.getOwnPropertyNames;var __require2=(x=>typeof require2!=="undefined"?require2:typeof Proxy!=="undefined"?new Proxy(x,{get:(a,b)=>(typeof require2!=="undefined"?require2:a)[b]}):x)(function(x){if(typeof require2!=="undefined")return require2.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});var __esm=(fn,res,err)=>function __init(){if(err)throw err[0];try{return fn&&(res=(0,fn[__getOwnPropNames2(fn)[0]])(fn=0)),res}catch(e){throw err=[e],e}};var __export=(target,all)=>{for(var name in all)__defProp2(target,name,{get:all[name],enumerable:true})};function isNode(){return typeof process!=="undefined"&&!!process.versions?.node}function assertNode(feature){if(!isNode()){throw new Error(`${feature} is not supported in the browser. Requires Node.js.`)}}var init_runtime=__esm({"libs/utils/src/crypto/runtime.ts"(){"use strict"}});function getFsp(){if(!_fsp){assertNode("File system operations");_fsp=__require2("fs").promises}return _fsp}async function writeFile(p,content,options){const fsp=getFsp();await fsp.writeFile(p,content,{encoding:"utf8",mode:options?.mode})}async function mkdir(p,options){const fsp=getFsp();await fsp.mkdir(p,options)}async function rename(oldPath,newPath){const fsp=getFsp();await fsp.rename(oldPath,newPath)}async function unlink(p){const fsp=getFsp();await fsp.unlink(p)}async function fileExists(p){try{const fsp=getFsp();await fsp.access(p,F_OK);return true}catch{return false}}async function readJSON(jsonPath){try{const fsp=getFsp();const buf=await fsp.readFile(jsonPath,"utf8");return JSON.parse(buf)}catch{return null}}async function ensureDir(p){const fsp=getFsp();await fsp.mkdir(p,{recursive:true})}async function readdir(p){const fsp=getFsp();return fsp.readdir(p)}var _fsp;var _fs;var _spawn;var F_OK;var init_fs=__esm({"libs/utils/src/fs/fs.ts"(){"use strict";init_runtime();_fsp=null;_fs=null;_spawn=null;F_OK=0}});var init_fs2=__esm({"libs/utils/src/fs/index.ts"(){"use strict";init_fs()}});function isRsaPssAlg(jwtAlg){return jwtAlg.startsWith("PS")}function jwtAlgToWebCryptoAlg(jwtAlg){const webAlg=JWT_ALG_TO_WEB_CRYPTO[jwtAlg];if(!webAlg){throw new Error(`Unsupported JWT algorithm for WebCrypto: ${jwtAlg}`)}return webAlg}var JWT_ALG_TO_NODE_DIGEST;var JWT_ALG_TO_WEB_CRYPTO;var init_jwt_alg=__esm({"libs/utils/src/crypto/jwt-alg.ts"(){"use strict";JWT_ALG_TO_NODE_DIGEST={RS256:"RSA-SHA256",RS384:"RSA-SHA384",RS512:"RSA-SHA512",PS256:"RSA-SHA256",PS384:"RSA-SHA384",PS512:"RSA-SHA512"};JWT_ALG_TO_WEB_CRYPTO={RS256:"SHA-256",RS384:"SHA-384",RS512:"SHA-512",PS256:"SHA-256",PS384:"SHA-384",PS512:"SHA-512"}}});var textEncoder;var textDecoder;var EncryptedBlobError;var init_encrypted_blob=__esm({"libs/utils/src/crypto/encrypted-blob.ts"(){"use strict";init_crypto();textEncoder=new TextEncoder;textDecoder=new TextDecoder;EncryptedBlobError=class extends Error{constructor(message){super(message);this.name="EncryptedBlobError"}}}});var MIN_CODE_VERIFIER_LENGTH;var MAX_CODE_VERIFIER_LENGTH;var DEFAULT_CODE_VERIFIER_LENGTH;var PkceError;var init_pkce=__esm({"libs/utils/src/crypto/pkce/pkce.ts"(){"use strict";init_crypto();MIN_CODE_VERIFIER_LENGTH=43;MAX_CODE_VERIFIER_LENGTH=128;DEFAULT_CODE_VERIFIER_LENGTH=64;PkceError=class extends Error{constructor(message){super(message);this.name="PkceError"}}}});var init_pkce2=__esm({"libs/utils/src/crypto/pkce/index.ts"(){"use strict";init_pkce()}});var secretDataSchema;var MAX_CLOCK_DRIFT_MS;var MAX_SECRET_AGE_MS;var init_schema=__esm({"libs/utils/src/crypto/secret-persistence/schema.ts"(){"use strict";secretDataSchema=z2.object({secret:z2.string().base64url().length(43),createdAt:z2.number().positive().int(),version:z2.number().positive().int()}).strict();MAX_CLOCK_DRIFT_MS=6e4;MAX_SECRET_AGE_MS=100*365*24*60*60*1e3}});var DEFAULT_SECRET_BYTES;var DEFAULT_SECRET_DIR;var noop;var secretCache;var generationPromises;var init_persistence=__esm({"libs/utils/src/crypto/secret-persistence/persistence.ts"(){"use strict";init_fs2();init_crypto();init_schema();DEFAULT_SECRET_BYTES=32;DEFAULT_SECRET_DIR=".frontmcp";noop=()=>{};secretCache=new Map;generationPromises=new Map}});var init_secret_persistence=__esm({"libs/utils/src/crypto/secret-persistence/index.ts"(){"use strict";init_schema();init_persistence()}});var init_hmac_signing=__esm({"libs/utils/src/crypto/hmac-signing.ts"(){"use strict";init_crypto();init_crypto();init_crypto()}});function validateKeyData(data){const result=anyKeyDataSchema.safeParse(data);if(!result.success){return{valid:false,error:result.error.issues[0]?.message??"Invalid key structure"}}const parsed=result.data;const now=Date.now();if(parsed.createdAt>now+MAX_CLOCK_DRIFT_MS2){return{valid:false,error:"createdAt is in the future"}}if(parsed.createdAt<now-MAX_KEY_AGE_MS){return{valid:false,error:"createdAt is too old"}}return{valid:true,data:parsed}}function isSecretKeyData(data){return data.type==="secret"}function isAsymmetricKeyData(data){return data.type==="asymmetric"}var MAX_CLOCK_DRIFT_MS2;var MAX_KEY_AGE_MS;var asymmetricAlgSchema;var baseKeyDataSchema;var jsonWebKeySchema;var secretKeyDataSchema;var asymmetricKeyDataSchema;var anyKeyDataSchema;var init_schemas=__esm({"libs/utils/src/crypto/key-persistence/schemas.ts"(){"use strict";MAX_CLOCK_DRIFT_MS2=6e4;MAX_KEY_AGE_MS=100*365*24*60*60*1e3;asymmetricAlgSchema=z22.enum(["RS256","RS384","RS512","ES256","ES384","ES512"]);baseKeyDataSchema=z22.object({kid:z22.string().min(1),createdAt:z22.number().positive().int(),version:z22.number().positive().int()});jsonWebKeySchema=z22.object({kty:z22.string().optional(),use:z22.string().optional(),alg:z22.string().optional(),kid:z22.string().optional(),n:z22.string().optional(),e:z22.string().optional(),d:z22.string().optional(),p:z22.string().optional(),q:z22.string().optional(),dp:z22.string().optional(),dq:z22.string().optional(),qi:z22.string().optional(),x:z22.string().optional(),y:z22.string().optional(),crv:z22.string().optional()}).passthrough();secretKeyDataSchema=baseKeyDataSchema.extend({type:z22.literal("secret"),secret:z22.string().min(1),bytes:z22.number().positive().int()});asymmetricKeyDataSchema=baseKeyDataSchema.extend({type:z22.literal("asymmetric"),alg:asymmetricAlgSchema,privateKey:jsonWebKeySchema,publicJwk:z22.object({keys:z22.array(jsonWebKeySchema).min(1)})});anyKeyDataSchema=z22.discriminatedUnion("type",[secretKeyDataSchema,asymmetricKeyDataSchema])}});var KeyPersistence;var init_key_persistence=__esm({"libs/utils/src/crypto/key-persistence/key-persistence.ts"(){"use strict";init_crypto();init_schemas();KeyPersistence=class{storage;cache=new Map;enableCache;throwOnInvalid;constructor(options){this.storage=options.storage;this.enableCache=options.enableCache??true;this.throwOnInvalid=options.throwOnInvalid??false}async get(kid){if(this.enableCache){const cached=this.cache.get(kid);if(cached)return cached}const raw=await this.storage.get(kid);if(!raw)return null;let parsed;try{parsed=JSON.parse(raw)}catch{if(this.throwOnInvalid){throw new Error(`Invalid JSON for key "${kid}"`)}return null}const validation=validateKeyData(parsed);if(!validation.valid){if(this.throwOnInvalid){throw new Error(`Invalid key data for "${kid}": ${validation.error}`)}return null}const key=validation.data;if(this.enableCache){this.cache.set(kid,key)}return key}async getSecret(kid){const key=await this.get(kid);if(!key||!isSecretKeyData(key))return null;return key}async getAsymmetric(kid){const key=await this.get(kid);if(!key||!isAsymmetricKeyData(key))return null;return key}async set(key){const validation=validateKeyData(key);if(!validation.valid){throw new Error(`Invalid key data: ${validation.error}`)}await this.storage.set(key.kid,JSON.stringify(key,null,2));if(this.enableCache){this.cache.set(key.kid,key)}}async delete(kid){this.cache.delete(kid);return this.storage.delete(kid)}async has(kid){if(this.enableCache&&this.cache.has(kid)){return true}return this.storage.exists(kid)}async list(){return this.storage.keys()}async getOrCreateSecret(kid,options){const existing=await this.getSecret(kid);if(existing){return existing}const bytes=options?.bytes??32;const secret={type:"secret",kid,secret:base64urlEncode(randomBytes(bytes)),bytes,createdAt:Date.now(),version:1};await this.set(secret);return secret}clearCache(){this.cache.clear()}clearCacheFor(kid){this.cache.delete(kid)}isCached(kid){return this.cache.has(kid)}getAdapter(){return this.storage}}}});var StorageError;var StorageConnectionError;var StorageOperationError;var StorageNotSupportedError;var StorageConfigError;var StorageTTLError;var StoragePatternError;var StorageNotConnectedError;var EncryptedStorageError;var init_errors=__esm({"libs/utils/src/storage/errors.ts"(){"use strict";StorageError=class extends Error{cause;constructor(message,cause){super(message);this.cause=cause;this.name="StorageError";Object.setPrototypeOf(this,new.target.prototype);if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}}};StorageConnectionError=class extends StorageError{constructor(message,cause,backend){const fullMessage=cause?`${message}: ${cause.message}`:message;super(fullMessage,cause);this.backend=backend;this.name="StorageConnectionError"}backend};StorageOperationError=class extends StorageError{constructor(operation,key,message,cause){super(`${operation} failed for key "${key}": ${message}`,cause);this.operation=operation;this.key=key;this.name="StorageOperationError"}operation;key};StorageNotSupportedError=class extends StorageError{constructor(operation,backend,suggestion){const msg=suggestion?`${operation} is not supported by ${backend}. ${suggestion}`:`${operation} is not supported by ${backend}`;super(msg);this.operation=operation;this.backend=backend;this.name="StorageNotSupportedError"}operation;backend};StorageConfigError=class extends StorageError{constructor(backend,message){super(`Invalid ${backend} configuration: ${message}`);this.backend=backend;this.name="StorageConfigError"}backend};StorageTTLError=class extends StorageError{constructor(ttl,message){super(message??`Invalid TTL value: ${ttl}. TTL must be a positive integer.`);this.ttl=ttl;this.name="StorageTTLError"}ttl};StoragePatternError=class extends StorageError{constructor(pattern,message){super(`Invalid pattern "${pattern}": ${message}`);this.pattern=pattern;this.name="StoragePatternError"}pattern};StorageNotConnectedError=class extends StorageError{constructor(backend){super(`Storage backend "${backend}" is not connected. Call connect() first.`);this.backend=backend;this.name="StorageNotConnectedError"}backend};EncryptedStorageError=class extends StorageError{constructor(message){super(message);this.name="EncryptedStorageError"}}}});function globToRegex(pattern){if(pattern.length>MAX_PATTERN_LENGTH){throw new StoragePatternError(pattern.substring(0,50)+"...",`Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`)}const wildcardCount=(pattern.match(/[*?]/g)||[]).length;if(wildcardCount>MAX_WILDCARDS){throw new StoragePatternError(pattern,`Pattern has too many wildcards (max: ${MAX_WILDCARDS})`)}if(pattern===""||pattern==="*"){return/^.*$/}let regexStr="^";let prevChar="";for(let i=0;i<pattern.length;i++){const char=pattern[i];switch(char){case"*":if(prevChar!=="*"){regexStr+=".*"}break;case"?":regexStr+=".";break;default:regexStr+=char.replace(REGEX_SPECIAL_CHARS,"\\$&")}prevChar=char}regexStr+="$";try{return new RegExp(regexStr)}catch{throw new StoragePatternError(pattern,"Failed to compile pattern to regex")}}function matchesPattern(key,pattern){const regex=globToRegex(pattern);return regex.test(key)}var MAX_PATTERN_LENGTH;var MAX_WILDCARDS;var REGEX_SPECIAL_CHARS;var init_pattern=__esm({"libs/utils/src/storage/utils/pattern.ts"(){"use strict";init_errors();MAX_PATTERN_LENGTH=500;MAX_WILDCARDS=20;REGEX_SPECIAL_CHARS=/[.+^${}()|[\]\\]/g}});function validateTTL(ttlSeconds){if(typeof ttlSeconds!=="number"){throw new StorageTTLError(ttlSeconds,`TTL must be a number, got ${typeof ttlSeconds}`)}if(!Number.isFinite(ttlSeconds)){throw new StorageTTLError(ttlSeconds,"TTL must be a finite number")}if(!Number.isInteger(ttlSeconds)){throw new StorageTTLError(ttlSeconds,`TTL must be an integer, got ${ttlSeconds}`)}if(ttlSeconds<=0){throw new StorageTTLError(ttlSeconds,`TTL must be positive, got ${ttlSeconds}`)}if(ttlSeconds>MAX_TTL_SECONDS){throw new StorageTTLError(ttlSeconds,`TTL exceeds maximum of ${MAX_TTL_SECONDS} seconds`)}}function validateOptionalTTL(ttlSeconds){if(ttlSeconds!==void 0){validateTTL(ttlSeconds)}}function ttlToExpiresAt(ttlSeconds){return Date.now()+ttlSeconds*1e3}function expiresAtToTTL(expiresAt){const remaining=Math.ceil((expiresAt-Date.now())/1e3);return Math.max(0,remaining)}function isExpired(expiresAt){if(expiresAt===void 0)return false;return Date.now()>=expiresAt}var MAX_TTL_SECONDS;var init_ttl=__esm({"libs/utils/src/storage/utils/ttl.ts"(){"use strict";init_errors();MAX_TTL_SECONDS=31536e5}});var BaseStorageAdapter;var init_base=__esm({"libs/utils/src/storage/adapters/base.ts"(){"use strict";init_errors();init_ttl();BaseStorageAdapter=class{connected=false;ensureConnected(){if(!this.connected){throw new StorageNotConnectedError(this.backendName)}}async set(key,value,options){this.ensureConnected();this.validateSetOptions(options);return this.doSet(key,value,options)}async mget(keys){this.ensureConnected();return Promise.all(keys.map(k=>this.get(k)))}async mset(entries){this.ensureConnected();for(const entry of entries){this.validateSetOptions(entry.options)}await Promise.all(entries.map(e=>this.doSet(e.key,e.value,e.options)))}async mdelete(keys){this.ensureConnected();const results=await Promise.all(keys.map(k=>this.delete(k)));return results.filter(Boolean).length}async count(pattern="*"){const matchedKeys=await this.keys(pattern);return matchedKeys.length}async deleteIfEquals(_key,_expectedValue){throw new StorageNotSupportedError("deleteIfEquals",this.backendName,"Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-delete.")}async expireIfEquals(_key,_expectedValue,_ttlSeconds){throw new StorageNotSupportedError("expireIfEquals",this.backendName,"Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-expire.")}supportsPubSub(){return false}async publish(_channel,_message){throw new StorageNotSupportedError("publish",this.backendName,this.getPubSubSuggestion())}async subscribe(_channel,_handler){throw new StorageNotSupportedError("subscribe",this.backendName,this.getPubSubSuggestion())}getPubSubSuggestion(){return"Use Redis or Upstash adapter for pub/sub support."}validateSetOptions(options){if(!options)return;validateOptionalTTL(options.ttlSeconds);if(options.ifNotExists&&options.ifExists){throw new Error("ifNotExists and ifExists are mutually exclusive")}}}}});var DEFAULT_SWEEP_INTERVAL_SECONDS;var MAX_TIMEOUT_MS;var MemoryStorageAdapter;var init_memory=__esm({"libs/utils/src/storage/adapters/memory.ts"(){"use strict";init_errors();init_pattern();init_ttl();init_base();DEFAULT_SWEEP_INTERVAL_SECONDS=60;MAX_TIMEOUT_MS=2147483647;MemoryStorageAdapter=class extends BaseStorageAdapter{backendName="memory";store=new Map;emitter=new import_event_emitter.EventEmitter;sweepInterval;options;accessOrder=[];constructor(options={}){super();this.options={enableSweeper:options.enableSweeper??true,sweepIntervalSeconds:options.sweepIntervalSeconds??DEFAULT_SWEEP_INTERVAL_SECONDS,maxEntries:options.maxEntries??0};this.emitter.setMaxListeners(1e3)}async connect(){if(this.connected)return;this.connected=true;if(this.options.enableSweeper&&this.options.sweepIntervalSeconds>0){this.startSweeper()}}async disconnect(){if(!this.connected)return;this.stopSweeper();this.clearAllTimeouts();this.store.clear();this.accessOrder=[];this.emitter.removeAllListeners();this.connected=false}async ping(){return this.connected}async get(key){this.ensureConnected();const entry=this.store.get(key);if(!entry)return null;if(isExpired(entry.expiresAt)){this.deleteEntry(key);return null}this.touchLRU(key);return entry.value}async doSet(key,value,options){const existingEntry=this.store.get(key);const exists=existingEntry!==void 0&&!isExpired(existingEntry.expiresAt);if(options?.ifNotExists&&exists){return}if(options?.ifExists&&!exists){return}this.clearEntryTimeout(key);const entry={value};if(options?.ttlSeconds){entry.expiresAt=ttlToExpiresAt(options.ttlSeconds);const ttlMs=options.ttlSeconds*1e3;if(ttlMs<MAX_TIMEOUT_MS){entry.timeout=setTimeout(()=>{this.deleteEntry(key)},ttlMs);if(entry.timeout.unref){entry.timeout.unref()}}}if(this.options.maxEntries>0&&!this.store.has(key)){while(this.store.size>=this.options.maxEntries){this.evictOldest()}}this.store.set(key,entry);this.touchLRU(key)}async delete(key){this.ensureConnected();return this.deleteEntry(key)}async deleteIfEquals(key,expectedValue){this.ensureConnected();const entry=this.store.get(key);if(!entry||isExpired(entry.expiresAt)||entry.value!==expectedValue)return false;return this.deleteEntry(key)}async expireIfEquals(key,expectedValue,ttlSeconds){this.ensureConnected();const entry=this.store.get(key);if(!entry||isExpired(entry.expiresAt)||entry.value!==expectedValue)return false;return this.expire(key,ttlSeconds)}async exists(key){this.ensureConnected();const entry=this.store.get(key);if(!entry)return false;if(isExpired(entry.expiresAt)){this.deleteEntry(key);return false}return true}async expire(key,ttlSeconds){this.ensureConnected();validateTTL(ttlSeconds);const entry=this.store.get(key);if(!entry||isExpired(entry.expiresAt)){return false}this.clearEntryTimeout(key);entry.expiresAt=ttlToExpiresAt(ttlSeconds);const ttlMs=ttlSeconds*1e3;if(ttlMs<MAX_TIMEOUT_MS){entry.timeout=setTimeout(()=>{this.deleteEntry(key)},ttlMs);if(entry.timeout.unref){entry.timeout.unref()}}return true}async ttl(key){this.ensureConnected();const entry=this.store.get(key);if(!entry)return null;if(isExpired(entry.expiresAt)){this.deleteEntry(key);return null}if(entry.expiresAt===void 0){return-1}return expiresAtToTTL(entry.expiresAt)}async keys(pattern="*"){this.ensureConnected();const regex=globToRegex(pattern);const result=[];for(const[key,entry]of this.store){if(isExpired(entry.expiresAt)){this.deleteEntry(key);continue}if(regex.test(key)){result.push(key)}}return result}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(key,amount){this.ensureConnected();const entry=this.store.get(key);let currentValue=0;if(entry&&!isExpired(entry.expiresAt)){const parsed=parseInt(entry.value,10);if(isNaN(parsed)){throw new StorageOperationError("incrBy",key,"Value is not an integer")}currentValue=parsed}const newValue=currentValue+amount;const newEntry={value:String(newValue)};if(entry?.expiresAt&&!isExpired(entry.expiresAt)){newEntry.expiresAt=entry.expiresAt;const remainingMs=entry.expiresAt-Date.now();if(remainingMs>0&&remainingMs<MAX_TIMEOUT_MS){this.clearEntryTimeout(key);newEntry.timeout=setTimeout(()=>{this.deleteEntry(key)},remainingMs);if(newEntry.timeout.unref){newEntry.timeout.unref()}}}this.store.set(key,newEntry);this.touchLRU(key);return newValue}supportsPubSub(){return true}async publish(channel,message){this.ensureConnected();return this.emitter.listenerCount(channel)>0?(this.emitter.emit(channel,message,channel),this.emitter.listenerCount(channel)):0}async subscribe(channel,handler){this.ensureConnected();const wrappedHandler=(message,ch)=>{handler(message,ch)};this.emitter.on(channel,wrappedHandler);return async()=>{this.emitter.off(channel,wrappedHandler)}}deleteEntry(key){const entry=this.store.get(key);if(!entry)return false;this.clearEntryTimeout(key);this.store.delete(key);this.removeLRU(key);return true}clearEntryTimeout(key){const entry=this.store.get(key);if(entry?.timeout){clearTimeout(entry.timeout);entry.timeout=void 0}}clearAllTimeouts(){for(const[key]of this.store){this.clearEntryTimeout(key)}}touchLRU(key){if(this.options.maxEntries<=0)return;const idx=this.accessOrder.indexOf(key);if(idx!==-1){this.accessOrder.splice(idx,1)}this.accessOrder.push(key)}removeLRU(key){if(this.options.maxEntries<=0)return;const idx=this.accessOrder.indexOf(key);if(idx!==-1){this.accessOrder.splice(idx,1)}}evictOldest(){if(this.accessOrder.length===0)return;const oldestKey=this.accessOrder.shift();if(oldestKey){this.deleteEntry(oldestKey)}}startSweeper(){this.sweepInterval=setInterval(()=>{this.sweep()},this.options.sweepIntervalSeconds*1e3);if(this.sweepInterval.unref){this.sweepInterval.unref()}}stopSweeper(){if(this.sweepInterval){clearInterval(this.sweepInterval);this.sweepInterval=void 0}}sweep(){const now=Date.now();for(const[key,entry]of this.store){if(entry.expiresAt&&now>=entry.expiresAt){this.deleteEntry(key)}}}getStats(){return{size:this.store.size,maxEntries:this.options.maxEntries,sweeperActive:this.sweepInterval!==void 0}}}}});var filesystem_exports={};__export(filesystem_exports,{FileSystemStorageAdapter:()=>FileSystemStorageAdapter});var FileSystemStorageAdapter;var init_filesystem=__esm({"libs/utils/src/storage/adapters/filesystem.ts"(){"use strict";init_base();init_errors();init_pattern();init_fs2();init_crypto();FileSystemStorageAdapter=class extends BaseStorageAdapter{backendName="filesystem";baseDir;extension;dirMode;fileMode;constructor(options){super();this.baseDir=options.baseDir;this.extension=options.extension??".json";this.dirMode=options.dirMode??448;this.fileMode=options.fileMode??384}async connect(){if(this.connected)return;await ensureDir(this.baseDir);try{await mkdir(this.baseDir,{recursive:true,mode:this.dirMode})}catch{}this.connected=true}async disconnect(){if(!this.connected)return;this.connected=false}async ping(){if(!this.connected)return false;try{return await fileExists(this.baseDir)}catch{return false}}async get(key){this.ensureConnected();const filePath=this.keyToPath(key);try{const entry=await readJSON(filePath);if(!entry)return null;if(entry.expiresAt&&Date.now()>=entry.expiresAt){await this.deleteFile(filePath);return null}return entry.value}catch(e){const error=e;if(error.code==="ENOENT"){return null}throw e}}async doSet(key,value,options){const filePath=this.keyToPath(key);const existingEntry=await this.readEntry(filePath);const exists=existingEntry!==null&&!this.isExpired(existingEntry);if(options?.ifNotExists&&exists){return}if(options?.ifExists&&!exists){return}const entry={value};if(options?.ttlSeconds){entry.expiresAt=Date.now()+options.ttlSeconds*1e3}await this.atomicWrite(filePath,entry)}async delete(key){this.ensureConnected();const filePath=this.keyToPath(key);return this.deleteFile(filePath)}async exists(key){this.ensureConnected();const filePath=this.keyToPath(key);try{const entry=await this.readEntry(filePath);if(!entry)return false;if(this.isExpired(entry)){await this.deleteFile(filePath);return false}return true}catch{return false}}async expire(key,ttlSeconds){this.ensureConnected();const filePath=this.keyToPath(key);const entry=await this.readEntry(filePath);if(!entry||this.isExpired(entry)){return false}entry.expiresAt=Date.now()+ttlSeconds*1e3;await this.atomicWrite(filePath,entry);return true}async ttl(key){this.ensureConnected();const filePath=this.keyToPath(key);const entry=await this.readEntry(filePath);if(!entry)return null;if(this.isExpired(entry)){await this.deleteFile(filePath);return null}if(entry.expiresAt===void 0){return-1}return Math.max(0,Math.ceil((entry.expiresAt-Date.now())/1e3))}async keys(pattern="*"){this.ensureConnected();const regex=globToRegex(pattern);const result=[];try{const files=await readdir(this.baseDir);for(const file of files){if(!file.endsWith(this.extension))continue;const key=this.pathToKey(file);if(!regex.test(key))continue;const filePath=this.keyToPath(key);const entry=await this.readEntry(filePath);if(entry&&!this.isExpired(entry)){result.push(key)}else if(entry&&this.isExpired(entry)){await this.deleteFile(filePath)}}}catch(e){const error=e;if(error.code==="ENOENT"){return[]}throw e}return result}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(key,amount){this.ensureConnected();const filePath=this.keyToPath(key);const entry=await this.readEntry(filePath);let currentValue=0;if(entry&&!this.isExpired(entry)){const parsed=parseInt(entry.value,10);if(isNaN(parsed)){throw new StorageOperationError("incrBy",key,"Value is not an integer")}currentValue=parsed}const newValue=currentValue+amount;const newEntry={value:String(newValue)};if(entry?.expiresAt&&!this.isExpired(entry)){newEntry.expiresAt=entry.expiresAt}await this.atomicWrite(filePath,newEntry);return newValue}keyToPath(key){const safeKey=this.encodeKey(key);return`${this.baseDir}/${safeKey}${this.extension}`}pathToKey(filename){const safeKey=filename.slice(0,-this.extension.length);return this.decodeKey(safeKey)}encodeKey(key){return encodeURIComponent(key).replace(/\./g,"%2E")}decodeKey(encoded){return decodeURIComponent(encoded)}async readEntry(filePath){try{return await readJSON(filePath)}catch{return null}}isExpired(entry){return entry.expiresAt!==void 0&&Date.now()>=entry.expiresAt}async deleteFile(filePath){try{await unlink(filePath);return true}catch(e){const error=e;if(error.code==="ENOENT"){return false}throw e}}async atomicWrite(filePath,entry){const tempSuffix=bytesToHex(randomBytes(8));const tempPath=`${filePath}.${tempSuffix}.tmp`;try{const content=JSON.stringify(entry,null,2);await writeFile(tempPath,content,{mode:this.fileMode});await rename(tempPath,filePath)}catch(e){try{await unlink(tempPath)}catch{}throw e}}getPubSubSuggestion(){return"FileSystem adapter does not support pub/sub. Use Redis or Memory adapter for pub/sub support."}}}});var indexeddb_exports={};__export(indexeddb_exports,{IndexedDBStorageAdapter:()=>IndexedDBStorageAdapter});var IndexedDBStorageAdapter;var init_indexeddb=__esm({"libs/utils/src/storage/adapters/indexeddb.ts"(){"use strict";init_base();init_pattern();IndexedDBStorageAdapter=class extends BaseStorageAdapter{backendName="indexeddb";dbName;storeName;prefix;db=null;constructor(options){super();this.dbName=options?.dbName??"frontmcp";this.storeName=options?.storeName??"kv";this.prefix=options?.prefix??"frontmcp:"}assertAvailable(){if(typeof indexedDB==="undefined"){throw new Error("IndexedDB is not available in this environment")}}key(k){return`${this.prefix}${k}`}stripPrefix(fullKey){return fullKey.slice(this.prefix.length)}async connect(){this.assertAvailable();if(this.db){this.connected=true;return}this.db=await new Promise((resolve2,reject)=>{const request=indexedDB.open(this.dbName,1);request.onupgradeneeded=()=>{const db=request.result;if(!db.objectStoreNames.contains(this.storeName)){db.createObjectStore(this.storeName,{keyPath:"k"})}};request.onsuccess=()=>resolve2(request.result);request.onerror=()=>reject(request.error)});this.connected=true}async disconnect(){if(this.db){this.db.close();this.db=null}this.connected=false}async ping(){return this.db!==null&&this.connected}getStore(mode){if(!this.db){throw new Error("IndexedDB adapter is not connected. Call connect() first.")}const tx=this.db.transaction(this.storeName,mode);return tx.objectStore(this.storeName)}idbRequest(request){return new Promise((resolve2,reject)=>{request.onsuccess=()=>resolve2(request.result);request.onerror=()=>reject(request.error)})}idbTransaction(store){return new Promise((resolve2,reject)=>{const tx=store.transaction;tx.oncomplete=()=>resolve2();tx.onerror=()=>reject(tx.error)})}async get(key){this.ensureConnected();const store=this.getStore("readonly");const entry=await this.idbRequest(store.get(this.key(key)));if(!entry)return null;if(entry.e&&entry.e<Date.now()){const writeStore=this.getStore("readwrite");writeStore.delete(this.key(key));return null}return entry.v}async doSet(key,value,options){if(options?.ifNotExists){const existing=await this.get(key);if(existing!==null)return}if(options?.ifExists){const existing=await this.get(key);if(existing===null)return}const entry={k:this.key(key),v:value};if(options?.ttlSeconds){entry.e=Date.now()+options.ttlSeconds*1e3}const store=this.getStore("readwrite");await this.idbRequest(store.put(entry))}async delete(key){this.ensureConnected();const existing=await this.get(key);if(existing===null)return false;const store=this.getStore("readwrite");await this.idbRequest(store.delete(this.key(key)));return true}async exists(key){return await this.get(key)!==null}async expire(key,ttlSeconds){this.ensureConnected();const value=await this.get(key);if(value===null)return false;await this.doSet(key,value,{ttlSeconds});return true}async ttl(key){this.ensureConnected();const store=this.getStore("readonly");const entry=await this.idbRequest(store.get(this.key(key)));if(!entry)return null;if(entry.e){const remaining=Math.ceil((entry.e-Date.now())/1e3);return remaining>0?remaining:null}return-1}async keys(pattern){this.ensureConnected();const store=this.getStore("readonly");const allKeys=await this.idbRequest(store.getAllKeys());const now=Date.now();const result=[];for(const fullKey of allKeys){const keyStr=String(fullKey);if(!keyStr.startsWith(this.prefix))continue;const key=this.stripPrefix(keyStr);if(pattern&&pattern!=="*"&&!this.matchPattern(key,pattern))continue;const entry=await this.idbRequest(store.get(keyStr));if(entry&&(!entry.e||entry.e>=now)){result.push(key)}}return result}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(key,amount){this.ensureConnected();const current=await this.get(key);const value=current!==null?parseInt(current,10)+amount:amount;if(Number.isNaN(value)){throw new Error(`Value at "${key}" is not an integer`)}await this.doSet(key,String(value));return value}matchPattern(key,pattern){return matchesPattern(key,pattern)}}}});var localstorage_exports={};__export(localstorage_exports,{LocalStorageAdapter:()=>LocalStorageAdapter});var LocalStorageAdapter;var init_localstorage=__esm({"libs/utils/src/storage/adapters/localstorage.ts"(){"use strict";init_base();init_crypto();init_pattern();LocalStorageAdapter=class extends BaseStorageAdapter{backendName="localstorage";prefix;encryptionKey;allowPlaintext;encoder=new TextEncoder;decoder=new TextDecoder;constructor(options){super();this.prefix=options?.prefix??"frontmcp:";this.allowPlaintext=options?.allowPlaintext??true;if(options?.encryptionKey){if(options.encryptionKey.length!==32){throw new Error(`encryptionKey must be exactly 32 bytes, got ${options.encryptionKey.length}`)}this.encryptionKey=options.encryptionKey}}assertAvailable(){if(typeof localStorage==="undefined"){throw new Error("localStorage is not available in this environment")}}key(k){return`${this.prefix}${k}`}stripPrefix(fullKey){return fullKey.slice(this.prefix.length)}async connect(){this.assertAvailable();const probeKey=this.key("__probe__");try{localStorage.setItem(probeKey,"1");const read=localStorage.getItem(probeKey);if(read!=="1"){throw new Error("localStorage probe: read-back mismatch")}}catch(err){throw new Error(`localStorage is not writable: ${err instanceof Error?err.message:String(err)}`)}finally{try{localStorage.removeItem(probeKey)}catch{}}this.connected=true}async disconnect(){this.connected=false}async ping(){const probeKey=this.key("__probe__");try{this.assertAvailable();localStorage.setItem(probeKey,"1");return localStorage.getItem(probeKey)==="1"}catch{return false}finally{try{localStorage.removeItem(probeKey)}catch{}}}async get(key){const raw=localStorage.getItem(this.key(key));if(raw===null)return null;try{const entry=JSON.parse(raw);if(entry.e&&entry.e<Date.now()){localStorage.removeItem(this.key(key));return null}if(entry._enc){if(!this.encryptionKey)return null;const iv=base64urlDecode(entry._enc.iv);const tag=base64urlDecode(entry._enc.tag);const ciphertext=base64urlDecode(entry._enc.data);const plainBytes=decryptAesGcm(this.encryptionKey,ciphertext,iv,tag);return this.decoder.decode(plainBytes)}return entry.v}catch{return null}}async set(key,value,options){if(options?.ifNotExists){const existing=await this.get(key);if(existing!==null)return}if(options?.ifExists){const existing=await this.get(key);if(existing===null)return}await this.doSet(key,value,options)}async doSet(key,value,options){const entry={v:""};if(options?.ttlSeconds){entry.e=Date.now()+options.ttlSeconds*1e3}if(this.encryptionKey){const iv=randomBytes(12);const plainBytes=this.encoder.encode(value);const{ciphertext,tag}=encryptAesGcm(this.encryptionKey,plainBytes,iv);entry._enc={alg:"A256GCM",iv:base64urlEncode(iv),tag:base64urlEncode(tag),data:base64urlEncode(ciphertext)}}else{if(!this.allowPlaintext){throw new Error("Plaintext storage is disabled. Provide an encryptionKey or set allowPlaintext: true.")}entry.v=value}localStorage.setItem(this.key(key),JSON.stringify(entry))}async delete(key){const existed=await this.get(key)!==null;localStorage.removeItem(this.key(key));return existed}async exists(key){return await this.get(key)!==null}async mget(keys){return Promise.all(keys.map(k=>this.get(k)))}async mset(entries){for(const entry of entries){await this.set(entry.key,entry.value,entry.options)}}async mdelete(keys){let count=0;for(const key of keys){if(await this.delete(key))count++}return count}async expire(key,ttlSeconds){const value=await this.get(key);if(value===null)return false;await this.set(key,value,{ttlSeconds});return true}async ttl(key){const raw=localStorage.getItem(this.key(key));if(raw===null)return null;try{const entry=JSON.parse(raw);if(!entry.e)return-1;const remaining=Math.ceil((entry.e-Date.now())/1e3);return remaining>0?remaining:null}catch{return null}}async keys(pattern){const result=[];const candidates=[];for(let i=0;i<localStorage.length;i++){const fullKey=localStorage.key(i);if(fullKey&&fullKey.startsWith(this.prefix)){candidates.push(this.stripPrefix(fullKey))}}for(const key of candidates){if(!pattern||pattern==="*"||this.matchPattern(key,pattern)){if(await this.get(key)!==null){result.push(key)}}}return result}async count(pattern){return(await this.keys(pattern)).length}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(key,amount){if(!Number.isInteger(amount)){throw new Error(`amount must be an integer, got ${amount}`)}const current=await this.get(key);if(current!==null&&!/^-?\d+$/.test(current)){throw new Error(`Value at "${key}" is not an integer`)}const value=current!==null?parseInt(current,10)+amount:amount;await this.set(key,String(value));return value}async publish(){throw new Error("LocalStorage adapter does not support pub/sub")}async subscribe(_channel,_handler){throw new Error("LocalStorage adapter does not support pub/sub")}supportsPubSub(){return false}matchPattern(key,pattern){return matchesPattern(key,pattern)}}}});var DEFAULT_BASE_DIR;var LS_HKDF_IKM;var INSTALL_SALT_KEY;var LEGACY_SALT_KEY;var init_factory=__esm({"libs/utils/src/crypto/key-persistence/factory.ts"(){"use strict";init_runtime();init_memory();init_key_persistence();init_crypto();DEFAULT_BASE_DIR=".frontmcp/keys";LS_HKDF_IKM=new TextEncoder().encode("frontmcp:localstorage:v1");INSTALL_SALT_KEY="__frontmcp_internal__:install_salt";LEGACY_SALT_KEY="frontmcp:_install_salt"}});var init_key_persistence2=__esm({"libs/utils/src/crypto/key-persistence/index.ts"(){"use strict";init_schemas();init_key_persistence();init_factory()}});var browser_exports={};__export(browser_exports,{browserCrypto:()=>browserCrypto,cryptoProvider:()=>browserCrypto,generateRsaKeyPair:()=>generateRsaKeyPair2,isRsaPssAlg:()=>isRsaPssAlg,jwtAlgToWebCryptoAlg:()=>jwtAlgToWebCryptoAlg,pemToPublicJwk:()=>pemToPublicJwk2,rsaSignBase64Url:()=>rsaSignBase64Url2,rsaVerify:()=>rsaVerify2,rsaVerifyBrowser:()=>rsaVerifyBrowser,rsaVerifySync:()=>rsaVerifySync2});function toBytes(data){if(typeof data==="string"){return new TextEncoder().encode(data)}return data}function toHex(bytes){return Array.from(bytes).map(b=>b.toString(16).padStart(2,"0")).join("")}function generateUUID(){if(typeof crypto!=="undefined"&&typeof crypto.randomUUID==="function"){return crypto.randomUUID()}const bytes=nobleRandomBytes(16);bytes[6]=bytes[6]&15|64;bytes[8]=bytes[8]&63|128;const hex=toHex(bytes);return`${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`}function constantTimeEqual(a,b){if(a.length!==b.length)return false;let result=0;for(let i=0;i<a.length;i++){result|=a[i]^b[i]}return result===0}function nodeOnly(name){throw new Error(`${name} is only available in Node.js runtimes`)}function rsaVerify2(){return nodeOnly("rsaVerify")}function rsaSignBase64Url2(){return nodeOnly("rsaSignBase64Url")}function rsaVerifySync2(){return nodeOnly("rsaVerifySync")}function pemToPublicJwk2(){return nodeOnly("pemToPublicJwk")}function generateRsaKeyPair2(){return nodeOnly("generateRsaKeyPair")}async function rsaVerifyBrowser(jwtAlg,data,publicJwk,signature){if(typeof globalThis.crypto?.subtle==="undefined"){throw new Error("WebCrypto API (crypto.subtle) is not available in this environment")}const webAlg=jwtAlgToWebCryptoAlg(jwtAlg);const isPss=isRsaPssAlg(jwtAlg);const algorithm={name:isPss?"RSA-PSS":"RSASSA-PKCS1-v1_5",hash:{name:webAlg}};const key=await globalThis.crypto.subtle.importKey("jwk",publicJwk,algorithm,false,["verify"]);const verifyAlgorithm=isPss?{name:"RSA-PSS",saltLength:getSaltLength(jwtAlg)}:{name:"RSASSA-PKCS1-v1_5"};const sigBuf=new Uint8Array(signature).buffer;const dataBuf=new Uint8Array(data).buffer;return globalThis.crypto.subtle.verify(verifyAlgorithm,key,sigBuf,dataBuf)}function getSaltLength(jwtAlg){switch(jwtAlg){case"PS256":return 32;case"PS384":return 48;case"PS512":return 64;default:return 32}}var browserCrypto;var init_browser=__esm({"libs/utils/src/crypto/browser.ts"(){"use strict";init_jwt_alg();init_jwt_alg();browserCrypto={randomUUID(){return generateUUID()},randomBytes(length){return nobleRandomBytes(length)},sha256(data){return sha256Hash(toBytes(data))},sha256Hex(data){return toHex(sha256Hash(toBytes(data)))},hmacSha256(key,data){return hmac(sha256Hash,key,data)},hkdfSha256(ikm,salt,info,length){const effectiveSalt=salt.length>0?salt:new Uint8Array(32);return hkdf(sha256Hash,ikm,effectiveSalt,info,length)},encryptAesGcm(key,plaintext,iv){const cipher=gcm(key,iv);const sealed=cipher.encrypt(plaintext);const ciphertext=sealed.slice(0,-16);const tag=sealed.slice(-16);return{ciphertext,tag}},decryptAesGcm(key,ciphertext,iv,tag){const cipher=gcm(key,iv);const sealed=new Uint8Array(ciphertext.length+tag.length);sealed.set(ciphertext);sealed.set(tag,ciphertext.length);return cipher.decrypt(sealed)},timingSafeEqual(a,b){return constantTimeEqual(a,b)}}}});function getCrypto(){return platformCrypto.cryptoProvider}function randomBytes(length){if(!Number.isInteger(length)||length<=0){throw new Error(`randomBytes length must be a positive integer, got ${length}`)}return getCrypto().randomBytes(length)}function sha256(data){return getCrypto().sha256(data)}function hmacSha256(key,data){return getCrypto().hmacSha256(key,data)}function encryptAesGcm(key,plaintext,iv){if(key.length!==32){throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`)}if(iv.length!==12){throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`)}return getCrypto().encryptAesGcm(key,plaintext,iv)}function decryptAesGcm(key,ciphertext,iv,tag){if(key.length!==32){throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`)}if(iv.length!==12){throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`)}if(tag.length!==16){throw new Error(`AES-GCM requires a 16-byte authentication tag, got ${tag.length} bytes`)}return getCrypto().decryptAesGcm(key,ciphertext,iv,tag)}function timingSafeEqual(a,b){if(a.length!==b.length){throw new Error(`timingSafeEqual requires equal-length arrays, got ${a.length} and ${b.length} bytes`)}return getCrypto().timingSafeEqual(a,b)}function bytesToHex(data){return Array.from(data).map(b=>b.toString(16).padStart(2,"0")).join("")}function base64urlEncode(data){let base64;if(typeof Buffer!=="undefined"){base64=Buffer.from(data).toString("base64")}else{const binString=Array.from(data,byte=>String.fromCodePoint(byte)).join("");base64=btoa(binString)}const result=base64.replace(/\+/g,"-").replace(/\//g,"_");let end=result.length;while(end>0&&result[end-1]==="="){end--}return result.slice(0,end)}function base64urlDecode(data){let base64=data.replace(/-/g,"+").replace(/_/g,"/");const pad=base64.length%4;if(pad){base64+="=".repeat(4-pad)}if(typeof Buffer!=="undefined"){return new Uint8Array(Buffer.from(base64,"base64"))}else{const binString=atob(base64);return Uint8Array.from(binString,c=>c.codePointAt(0)??0)}}var HKDF_SHA256_MAX_LENGTH;var init_crypto=__esm({"libs/utils/src/crypto/index.ts"(){"use strict";init_runtime();init_jwt_alg();init_runtime();init_encrypted_blob();init_pkce2();init_secret_persistence();init_hmac_signing();init_key_persistence2();HKDF_SHA256_MAX_LENGTH=255*32}});function attachRedisErrorListener(client,options={}){const label=options.label??"redis";const intervalMs=options.intervalMs??DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;let lastLoggedAt=0;let suppressed=0;const listener=error=>{const now=Date.now();if(lastLoggedAt!==0&&now-lastLoggedAt<intervalMs){suppressed++;return}const extra=suppressed>0?` (${suppressed} similar error(s) suppressed)`:"";suppressed=0;lastLoggedAt=now;const message=`[${label}] Redis connection error: ${error?.message??String(error)}${extra}`;if(options.logger){options.logger.warn(message)}else{console.warn(message)}};client.on("error",listener);return()=>{client.removeListener("error",listener)}}var DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;var init_redis_error_listener=__esm({"libs/utils/src/storage/redis-error-listener.ts"(){"use strict";DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS=3e4}});function decodeOrRaw(encoded){try{return decodeURIComponent(encoded)}catch{return encoded}}function integerOrUndefined(text){const value=Number.parseInt(text??"",10);return Number.isNaN(value)?void 0:value}function readRedisUrl(rawUrl){let url;try{url=new URL(rawUrl)}catch{return void 0}if(url.protocol!=="redis:"&&url.protocol!=="rediss:")return void 0;const query=Object.fromEntries(url.searchParams);const{host,port,username,password,db,...otherOptions}=query;const queryOptions={};for(const[name,value=""]of Object.entries(otherOptions)){queryOptions[name]=name==="family"?integerOrUndefined(value)??value:value}return{host:url.hostname.replace(/^\[(.*)\]$/,"$1")||host,port:integerOrUndefined(url.port||port),username:decodeOrRaw(url.username)||username||void 0,password:decodeOrRaw(url.password)||password||void 0,db:integerOrUndefined(url.pathname.replace(/^\/+/,"")||db),tls:url.protocol==="rediss:",queryOptions}}function mergeRedisUrlFields(rawUrl,fields){const connection=readRedisUrl(rawUrl);if(!connection)return void 0;const fillIns={};const conflicts=[];if(fields.host!==void 0&&fields.host!==connection.host)conflicts.push("host");if(fields.port!==void 0){if(connection.port===void 0)fillIns.port=fields.port;else if(fields.port!==connection.port)conflicts.push("port")}if(fields.password!==void 0){if(connection.password===void 0)fillIns.password=fields.password;else if(fields.password!==connection.password)conflicts.push("password")}if(fields.db!==void 0){if(connection.db===void 0)fillIns.db=fields.db;else if(fields.db!==connection.db)conflicts.push("db")}if(fields.tls===true&&!connection.tls)fillIns.tls=true;if(fields.tls===false&&connection.tls)conflicts.push("tls");return{connection,fillIns,conflicts}}function describeRedisUrlConflicts(conflicts){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.`}var init_redis_url=__esm({"libs/utils/src/storage/redis-url.ts"(){"use strict"}});var init_utils=__esm({"libs/utils/src/storage/utils/index.ts"(){"use strict";init_ttl()}});var COMPARE_AND_DELETE_SCRIPT;var COMPARE_AND_EXPIRE_SCRIPT;var init_compare_and_delete=__esm({"libs/utils/src/storage/utils/compare-and-delete.ts"(){"use strict";COMPARE_AND_DELETE_SCRIPT=`if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;COMPARE_AND_EXPIRE_SCRIPT=`if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) end return 0`}});var redis_exports={};__export(redis_exports,{RedisStorageAdapter:()=>RedisStorageAdapter,createRedisClient:()=>createRedisClient});function getRedisClass(){try{return __require2("ioredis").default||__require2("ioredis")}catch(error){const msg=error instanceof Error?error.message:String(error);if(msg.includes("Dynamic require")||msg.includes("require is not defined")){throw new Error(`Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,{cause:error})}throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis",{cause:error})}}function resolveUrlOptions(connectionUrl,options){const{url:configUrl,...siblingFields}={url:void 0,...options.config};if(options.url&&configUrl&&configUrl!==options.url){throw new StorageConfigError("redis","redis.url and redis.config.url name different servers; set one of them.")}return urlConnectionOptions(connectionUrl,siblingFields)}function urlConnectionOptions(url,fields){const merge=mergeRedisUrlFields(url,fields);if(!merge)return void 0;if(merge.conflicts.length>0){throw new StorageConfigError("redis",describeRedisUrlConflicts(merge.conflicts))}const{queryOptions,tls,...stated}=merge.connection;return{...queryOptions,...stated,...toFillInOptions(merge.fillIns),...tls?{tls:{}}:{}}}function toFillInOptions({port,password,db,tls}){return{...port!==void 0?{port}:{},...password!==void 0?{password}:{},...db!==void 0?{db}:{},...tls?{tls:{}}:{}}}function createRedisClient(options){const urlOptions=options.url?urlConnectionOptions(options.url,options):void 0;const RedisClass=getRedisClass();const baseOptions={lazyConnect:false,maxRetriesPerRequest:3};let client;if(urlOptions){client=new RedisClass({...baseOptions,...urlOptions})}else if(options.url){client=new RedisClass(options.url,baseOptions)}else{client=new RedisClass({...baseOptions,host:options.host??"localhost",port:options.port??6379,password:options.password,db:options.db??0,tls:options.tls?{}:void 0})}attachRedisErrorListener(client,{label:options.label??"redis",logger:options.logger});return client}var RedisStorageAdapter;var init_redis=__esm({"libs/utils/src/storage/adapters/redis.ts"(){"use strict";init_errors();init_redis_error_listener();init_redis_url();init_utils();init_compare_and_delete();init_base();RedisStorageAdapter=class extends BaseStorageAdapter{backendName="redis";client;subscriber;options;ownsClient;detachErrorListener;connecting;keyPrefix;subscriptionHandlers=new Map;connectionUrl;urlOptions;constructor(options={}){super();const hasClient=options.client!==void 0;const hasConfig=options.config!==void 0||options.url!==void 0;if(hasClient&&hasConfig){throw new StorageConfigError("redis",'Cannot specify both "client" and "config"/"url". Use one or the other.')}if(!hasClient&&!hasConfig){const envUrl=process.env["REDIS_URL"]||process.env["REDIS_HOST"];if(envUrl){options={...options,url:envUrl}}else{throw new StorageConfigError("redis",'Either "client", "config", "url", or REDIS_URL environment variable must be provided.')}}this.options=options;this.ownsClient=!hasClient;this.keyPrefix=options.keyPrefix??"";const configUrl=options.config?.url;this.connectionUrl=options.url??(typeof configUrl==="string"?configUrl:void 0);this.urlOptions=this.connectionUrl?resolveUrlOptions(this.connectionUrl,options):void 0}async connect(){if(this.connected)return;this.connecting??=this.openConnection().finally(()=>{this.connecting=void 0});return this.connecting}async openConnection(){let socketError;const recordSocketError=error=>{socketError=error};let client;try{if(this.options.client){client=this.options.client}else{client=this.newClient();client.on("error",recordSocketError)}await client.ping();this.client=client;this.connected=true;if(this.ownsClient){client.removeListener("error",recordSocketError);this.detachErrorListener=attachRedisErrorListener(client,{label:"RedisStorage"})}}catch(e){if(this.ownsClient&&client){try{client.disconnect()}catch{}}const cause=socketError??(e instanceof Error?e:void 0);throw new StorageConnectionError("Failed to connect to Redis",cause,"redis")}}async disconnect(){if(!this.connected)return;if(this.subscriber){await this.subscriber.quit();this.subscriber=void 0}if(this.ownsClient&&this.client){await this.client.quit()}this.detachErrorListener?.();this.detachErrorListener=void 0;this.client=void 0;this.connected=false;this.subscriptionHandlers.clear()}async ping(){if(!this.client)return false;try{const result=await this.client.ping();return result==="PONG"}catch{return false}}getConnectedClient(){this.ensureConnected();if(!this.client){throw new StorageConnectionError("Redis client not connected",void 0,"redis")}return this.client}getConnectedSubscriber(){if(!this.subscriber){throw new StorageConnectionError("Redis subscriber not created",void 0,"redis")}return this.subscriber}async get(key){return this.getConnectedClient().get(this.prefixKey(key))}async doSet(key,value,options){const client=this.getConnectedClient();const prefixedKey=this.prefixKey(key);if(options?.ttlSeconds){if(options.ifNotExists){await client.set(prefixedKey,value,"EX",options.ttlSeconds,"NX")}else if(options.ifExists){await client.set(prefixedKey,value,"EX",options.ttlSeconds,"XX")}else{await client.set(prefixedKey,value,"EX",options.ttlSeconds)}}else if(options?.ifNotExists){await client.set(prefixedKey,value,"NX")}else if(options?.ifExists){await client.set(prefixedKey,value,"XX")}else{await client.set(prefixedKey,value)}}async delete(key){const result=await this.getConnectedClient().del(this.prefixKey(key));return result>0}async deleteIfEquals(key,expectedValue){const deleted=await this.getConnectedClient().eval(COMPARE_AND_DELETE_SCRIPT,1,this.prefixKey(key),expectedValue);return deleted===1}async expireIfEquals(key,expectedValue,ttlSeconds){validateTTL(ttlSeconds);const extended=await this.getConnectedClient().eval(COMPARE_AND_EXPIRE_SCRIPT,1,this.prefixKey(key),expectedValue,String(ttlSeconds));return extended===1}async exists(key){const result=await this.getConnectedClient().exists(this.prefixKey(key));return result>0}async mget(keys){if(keys.length===0)return[];const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().mget(...prefixedKeys)}async mset(entries){if(entries.length===0)return;for(const entry of entries){this.validateSetOptions(entry.options)}const pipeline=this.getConnectedClient().pipeline();for(const entry of entries){const prefixedKey=this.prefixKey(entry.key);if(entry.options?.ttlSeconds){if(entry.options.ifNotExists){pipeline.set(prefixedKey,entry.value,"EX",entry.options.ttlSeconds,"NX")}else if(entry.options.ifExists){pipeline.set(prefixedKey,entry.value,"EX",entry.options.ttlSeconds,"XX")}else{pipeline.set(prefixedKey,entry.value,"EX",entry.options.ttlSeconds)}}else if(entry.options?.ifNotExists){pipeline.set(prefixedKey,entry.value,"NX")}else if(entry.options?.ifExists){pipeline.set(prefixedKey,entry.value,"XX")}else{pipeline.set(prefixedKey,entry.value)}}await pipeline.exec()}async mdelete(keys){if(keys.length===0)return 0;const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().del(...prefixedKeys)}async expire(key,ttlSeconds){validateTTL(ttlSeconds);const result=await this.getConnectedClient().expire(this.prefixKey(key),ttlSeconds);return result===1}async ttl(key){const result=await this.getConnectedClient().ttl(this.prefixKey(key));if(result===-2)return null;return result}async keys(pattern="*"){const client=this.getConnectedClient();const prefixedPattern=this.prefixKey(pattern);const result=[];let cursor="0";do{const[nextCursor,keys]=await client.scan(cursor,"MATCH",prefixedPattern,"COUNT",100);cursor=nextCursor;for(const key of keys){result.push(this.unprefixKey(key))}}while(cursor!=="0");return result}async incr(key){return this.getConnectedClient().incr(this.prefixKey(key))}async decr(key){return this.getConnectedClient().decr(this.prefixKey(key))}async incrBy(key,amount){return this.getConnectedClient().incrby(this.prefixKey(key),amount)}supportsPubSub(){return true}async publish(channel,message){const prefixedChannel=this.prefixKey(channel);return this.getConnectedClient().publish(prefixedChannel,message)}async subscribe(channel,handler){this.ensureConnected();const prefixedChannel=this.prefixKey(channel);if(!this.subscriber){await this.createSubscriber()}const subscriber=this.getConnectedSubscriber();if(!this.subscriptionHandlers.has(prefixedChannel)){this.subscriptionHandlers.set(prefixedChannel,new Set);await subscriber.subscribe(prefixedChannel)}const handlers=this.subscriptionHandlers.get(prefixedChannel);if(handlers){handlers.add(handler)}return async()=>{const handlers2=this.subscriptionHandlers.get(prefixedChannel);if(handlers2){handlers2.delete(handler);if(handlers2.size===0){this.subscriptionHandlers.delete(prefixedChannel);await this.subscriber?.unsubscribe(prefixedChannel)}}}}buildRedisOptions(){if(this.connectionUrl){return{lazyConnect:false,maxRetriesPerRequest:3,...this.urlOptions}}const config=this.options.config;if(!config||typeof config.url==="string"||config.host===void 0){throw new StorageConfigError("redis","Redis config is required when URL is not provided")}return{host:config.host,port:config.port??6379,password:config.password,db:config.db??0,tls:config.tls?{}:void 0,lazyConnect:false,maxRetriesPerRequest:3}}newClient(){const RedisClass=getRedisClass();if(this.connectionUrl&&!this.urlOptions){return new RedisClass(this.connectionUrl,this.buildRedisOptions())}return new RedisClass(this.buildRedisOptions())}async createSubscriber(){let subscriber;if(this.connectionUrl||this.options.config){subscriber=this.newClient()}else if(this.options.client){subscriber=this.options.client.duplicate()}else{throw new StorageConfigError("redis","Cannot create subscriber without url, config, or client")}attachRedisErrorListener(subscriber,{label:"RedisStorage:subscriber"});subscriber.on("message",(channel,message)=>{const handlers=this.subscriptionHandlers.get(channel);if(handlers){const unprefixedChannel=this.unprefixKey(channel);for(const handler of handlers){try{handler(message,unprefixedChannel)}catch{}}}});this.subscriber=subscriber}prefixKey(key){return this.keyPrefix+key}unprefixKey(key){if(this.keyPrefix&&key.startsWith(this.keyPrefix)){return key.slice(this.keyPrefix.length)}return key}getClient(){return this.client}}}});var vercel_kv_exports={};__export(vercel_kv_exports,{VercelKvStorageAdapter:()=>VercelKvStorageAdapter,createVercelKvClient:()=>createVercelKvClient});async function loadVercelKv(){try{const mod=await import("@vercel/kv");const resolved=typeof mod.createClient==="function"?mod:mod.default;if(!resolved||typeof resolved.createClient!=="function")throw new Error("createClient missing");return resolved}catch{throw new Error("@vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv")}}async function createVercelKvClient(connection={}){const url=connection.url??process.env["KV_REST_API_URL"];const token=connection.token??process.env["KV_REST_API_TOKEN"];if(!url||!token){throw new StorageConfigError("vercel-kv","Pass url and token, or set KV_REST_API_URL and KV_REST_API_TOKEN.")}const{createClient}=await loadVercelKv();return createClient({url,token,cache:void 0,automaticDeserialization:false})}var VercelKvStorageAdapter;var init_vercel_kv=__esm({"libs/utils/src/storage/adapters/vercel-kv.ts"(){"use strict";init_errors();init_compare_and_delete();init_ttl();init_base();VercelKvStorageAdapter=class extends BaseStorageAdapter{backendName="vercel-kv";client;options;keyPrefix;constructor(options={}){super();const url=options.url??process.env["KV_REST_API_URL"];const token=options.token??process.env["KV_REST_API_TOKEN"];if(!url||!token){throw new StorageConfigError("vercel-kv","KV_REST_API_URL and KV_REST_API_TOKEN must be provided via options or environment variables.")}this.options={...options,url,token};this.keyPrefix=options.keyPrefix??""}async connect(){if(this.connected)return;try{this.client=await createVercelKvClient({url:this.options.url,token:this.options.token});await this.client.exists("__healthcheck__");this.connected=true}catch(e){throw new StorageConnectionError("Failed to connect to Vercel KV",e instanceof Error?e:void 0,"vercel-kv")}}async disconnect(){this.client=void 0;this.connected=false}async ping(){if(!this.client)return false;try{await this.client.exists("__healthcheck__");return true}catch{return false}}getConnectedClient(){this.ensureConnected();if(!this.client){throw new StorageConnectionError("Vercel KV client not connected",void 0,"vercel-kv")}return this.client}async get(key){return this.getConnectedClient().get(this.prefixKey(key))}async doSet(key,value,options){const client=this.getConnectedClient();const prefixedKey=this.prefixKey(key);const setOptions={};if(options?.ttlSeconds){setOptions.ex=options.ttlSeconds}if(options?.ifNotExists){setOptions.nx=true}else if(options?.ifExists){setOptions.xx=true}await client.set(prefixedKey,value,Object.keys(setOptions).length>0?setOptions:void 0)}async delete(key){const result=await this.getConnectedClient().del(this.prefixKey(key));return result>0}async deleteIfEquals(key,expectedValue){const deleted=await this.getConnectedClient().eval(COMPARE_AND_DELETE_SCRIPT,[this.prefixKey(key)],[expectedValue]);return deleted===1}async expireIfEquals(key,expectedValue,ttlSeconds){validateTTL(ttlSeconds);const extended=await this.getConnectedClient().eval(COMPARE_AND_EXPIRE_SCRIPT,[this.prefixKey(key)],[expectedValue,String(ttlSeconds)]);return extended===1}async exists(key){const result=await this.getConnectedClient().exists(this.prefixKey(key));return result>0}async mget(keys){if(keys.length===0)return[];const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().mget(...prefixedKeys)}async mdelete(keys){if(keys.length===0)return 0;const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().del(...prefixedKeys)}async expire(key,ttlSeconds){validateTTL(ttlSeconds);const result=await this.getConnectedClient().expire(this.prefixKey(key),ttlSeconds);return result===1}async ttl(key){const result=await this.getConnectedClient().ttl(this.prefixKey(key));if(result===-2)return null;return result}async keys(pattern="*"){const client=this.getConnectedClient();const prefixedPattern=this.prefixKey(pattern);try{const result=[];let cursor=0;do{const[nextCursor,keys]=await client.scan(cursor,{match:prefixedPattern,count:100});const parsedCursor=typeof nextCursor==="string"?parseInt(nextCursor,10):nextCursor;cursor=Number.isNaN(parsedCursor)?0:parsedCursor;for(const key of keys){result.push(this.unprefixKey(key))}}while(cursor!==0);return result}catch{const allKeys=await client.keys(prefixedPattern);return allKeys.map(k=>this.unprefixKey(k))}}async incr(key){return this.getConnectedClient().incr(this.prefixKey(key))}async decr(key){return this.getConnectedClient().decr(this.prefixKey(key))}async incrBy(key,amount){return this.getConnectedClient().incrby(this.prefixKey(key),amount)}supportsPubSub(){return false}getPubSubSuggestion(){return"Vercel KV is REST-based and does not support pub/sub. Use Upstash adapter for pub/sub support."}prefixKey(key){return this.keyPrefix+key}unprefixKey(key){if(this.keyPrefix&&key.startsWith(this.keyPrefix)){return key.slice(this.keyPrefix.length)}return key}}}});var upstash_exports={};__export(upstash_exports,{UpstashStorageAdapter:()=>UpstashStorageAdapter});async function loadUpstashRedis(){try{const mod=await import("@upstash/redis");const resolved=typeof mod.Redis==="function"?mod:mod.default;if(!resolved||typeof resolved.Redis!=="function")throw new Error("Redis missing");return resolved}catch{throw new Error("@upstash/redis is required for Upstash storage adapter. Install it with: npm install @upstash/redis")}}var PUBSUB_POLL_INTERVAL_MS;var UpstashStorageAdapter;var init_upstash=__esm({"libs/utils/src/storage/adapters/upstash.ts"(){"use strict";init_errors();init_compare_and_delete();init_ttl();init_base();PUBSUB_POLL_INTERVAL_MS=100;UpstashStorageAdapter=class extends BaseStorageAdapter{backendName="upstash";client;options;keyPrefix;pubSubEnabled;subscriptionHandlers=new Map;pollingIntervals=new Map;constructor(options={}){super();const url=options.url??process.env["UPSTASH_REDIS_REST_URL"];const token=options.token??process.env["UPSTASH_REDIS_REST_TOKEN"];if(!url||!token){throw new StorageConfigError("upstash","UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN must be provided via options or environment variables.")}this.options={...options,url,token};this.keyPrefix=options.keyPrefix??"";this.pubSubEnabled=options.enablePubSub??false}async connect(){if(this.connected)return;try{const{Redis}=await loadUpstashRedis();const url=this.options.url;const token=this.options.token;if(!url||!token){throw new StorageConfigError("upstash","URL and token are required")}this.client=new Redis({url,token,automaticDeserialization:false});await this.client.exists("__healthcheck__");this.connected=true}catch(e){throw new StorageConnectionError("Failed to connect to Upstash Redis",e instanceof Error?e:void 0,"upstash")}}async disconnect(){if(!this.connected)return;for(const interval of this.pollingIntervals.values()){clearInterval(interval)}this.pollingIntervals.clear();this.subscriptionHandlers.clear();this.client=void 0;this.connected=false}async ping(){if(!this.client)return false;try{await this.client.exists("__healthcheck__");return true}catch{return false}}getConnectedClient(){this.ensureConnected();if(!this.client){throw new StorageConnectionError("Upstash client not connected",void 0,"upstash")}return this.client}async get(key){return this.getConnectedClient().get(this.prefixKey(key))}async doSet(key,value,options){const client=this.getConnectedClient();const prefixedKey=this.prefixKey(key);const setOptions={};if(options?.ttlSeconds){setOptions.ex=options.ttlSeconds}if(options?.ifNotExists){setOptions.nx=true}else if(options?.ifExists){setOptions.xx=true}await client.set(prefixedKey,value,Object.keys(setOptions).length>0?setOptions:void 0)}async delete(key){const result=await this.getConnectedClient().del(this.prefixKey(key));return result>0}async deleteIfEquals(key,expectedValue){const deleted=await this.getConnectedClient().eval(COMPARE_AND_DELETE_SCRIPT,[this.prefixKey(key)],[expectedValue]);return deleted===1}async expireIfEquals(key,expectedValue,ttlSeconds){validateTTL(ttlSeconds);const extended=await this.getConnectedClient().eval(COMPARE_AND_EXPIRE_SCRIPT,[this.prefixKey(key)],[expectedValue,String(ttlSeconds)]);return extended===1}async exists(key){const result=await this.getConnectedClient().exists(this.prefixKey(key));return result>0}async mget(keys){if(keys.length===0)return[];const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().mget(...prefixedKeys)}async mdelete(keys){if(keys.length===0)return 0;const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().del(...prefixedKeys)}async expire(key,ttlSeconds){validateTTL(ttlSeconds);const result=await this.getConnectedClient().expire(this.prefixKey(key),ttlSeconds);return result===1}async ttl(key){const result=await this.getConnectedClient().ttl(this.prefixKey(key));if(result===-2)return null;return result}async keys(pattern="*"){const client=this.getConnectedClient();const prefixedPattern=this.prefixKey(pattern);const result=[];let cursor=0;do{const[nextCursor,keys]=await client.scan(cursor,{match:prefixedPattern,count:100});const parsedCursor=typeof nextCursor==="string"?parseInt(nextCursor,10):nextCursor;cursor=Number.isNaN(parsedCursor)?0:parsedCursor;for(const key of keys){result.push(this.unprefixKey(key))}}while(cursor!==0);return result}async incr(key){return this.getConnectedClient().incr(this.prefixKey(key))}async decr(key){return this.getConnectedClient().decr(this.prefixKey(key))}async incrBy(key,amount){return this.getConnectedClient().incrby(this.prefixKey(key),amount)}supportsPubSub(){return this.pubSubEnabled}async publish(channel,message){if(!this.pubSubEnabled){return super.publish(channel,message)}const prefixedChannel=this.prefixKey(`__pubsub__:${channel}`);const listKey=`${prefixedChannel}:queue`;await this.getConnectedClient().lpush(listKey,message);return 1}async subscribe(channel,handler){const client=this.getConnectedClient();if(!this.pubSubEnabled){return super.subscribe(channel,handler)}const prefixedChannel=this.prefixKey(`__pubsub__:${channel}`);if(!this.subscriptionHandlers.has(prefixedChannel)){this.subscriptionHandlers.set(prefixedChannel,new Set);const listKey=`${prefixedChannel}:queue`;const interval=setInterval(async()=>{try{const message=await client.rpop(listKey);if(message){const handlers2=this.subscriptionHandlers.get(prefixedChannel);if(handlers2){for(const h of handlers2){try{h(message,channel)}catch{}}}}}catch{}},PUBSUB_POLL_INTERVAL_MS);if(interval.unref){interval.unref()}this.pollingIntervals.set(prefixedChannel,interval)}const handlers=this.subscriptionHandlers.get(prefixedChannel);if(handlers){handlers.add(handler)}return async()=>{const handlers2=this.subscriptionHandlers.get(prefixedChannel);if(handlers2){handlers2.delete(handler);if(handlers2.size===0){this.subscriptionHandlers.delete(prefixedChannel);const interval=this.pollingIntervals.get(prefixedChannel);if(interval){clearInterval(interval);this.pollingIntervals.delete(prefixedChannel)}}}}}async publishToQueue(channel,message){const prefixedChannel=this.prefixKey(`__pubsub__:${channel}`);const listKey=`${prefixedChannel}:queue`;await this.getConnectedClient().lpush(listKey,message)}getPubSubSuggestion(){return"Enable pub/sub by setting enablePubSub: true in the adapter options."}prefixKey(key){return this.keyPrefix+key}unprefixKey(key){if(this.keyPrefix&&key.startsWith(this.keyPrefix)){return key.slice(this.keyPrefix.length)}return key}}}});var cloudflare_kv_exports={};__export(cloudflare_kv_exports,{CloudflareKvStorageAdapter:()=>CloudflareKvStorageAdapter});function literalPrefix(pattern){const star=pattern.indexOf("*");const q=pattern.indexOf("?");const idx=star===-1?q:q===-1?star:Math.min(star,q);return idx===-1?pattern:pattern.slice(0,idx)}var CF_KV_MIN_TTL_SECONDS;var CloudflareKvStorageAdapter;var init_cloudflare_kv=__esm({"libs/utils/src/storage/adapters/cloudflare-kv.ts"(){"use strict";init_errors();init_pattern();init_ttl();init_base();CF_KV_MIN_TTL_SECONDS=60;CloudflareKvStorageAdapter=class extends BaseStorageAdapter{backendName="cloudflare-kv";kv;keyPrefix;constructor(options){super();if(!options?.namespace){throw new Error("CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`).")}this.kv=options.namespace;this.keyPrefix=options.keyPrefix??""}async connect(){this.connected=true}async disconnect(){this.connected=false}async ping(){if(!this.connected)return false;try{await this.kv.list({limit:1});return true}catch{return false}}async get(key){this.ensureConnected();return this.kv.get(this.prefixKey(key),"text")}async doSet(key,value,options){if(options?.ifNotExists||options?.ifExists){throw new StorageNotSupportedError("conditional set (ifNotExists/ifExists)",this.backendName,"Cloudflare KV has no compare-and-set; use a Durable Object for atomic conditional writes.")}const putOptions=options?.ttlSeconds!==void 0?{expirationTtl:this.toExpirationTtl(options.ttlSeconds,key)}:void 0;await this.kv.put(this.prefixKey(key),value,putOptions)}async delete(key){this.ensureConnected();const existed=await this.exists(key);await this.kv.delete(this.prefixKey(key));return existed}async exists(key){this.ensureConnected();return await this.kv.get(this.prefixKey(key),"text")!==null}async expire(key,ttlSeconds){this.ensureConnected();const ttl=this.toExpirationTtl(ttlSeconds,key);const value=await this.kv.get(this.prefixKey(key),"text");if(value===null)return false;await this.kv.put(this.prefixKey(key),value,{expirationTtl:ttl});return true}async ttl(_key){throw new StorageNotSupportedError("ttl",this.backendName,"Cloudflare KV does not expose a key's remaining TTL. Track expiry alongside the value if you need it.")}async keys(pattern="*"){this.ensureConnected();const prefixedPattern=this.prefixKey(pattern);const listPrefix=literalPrefix(prefixedPattern);const out=[];let cursor;do{const page=await this.kv.list({prefix:listPrefix||void 0,cursor});for(const{name}of page.keys){if(matchesPattern(name,prefixedPattern))out.push(this.unprefixKey(name))}cursor=page.list_complete?void 0:page.cursor}while(cursor);return out}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(_key,_amount){throw new StorageNotSupportedError("incr/decr/incrBy",this.backendName,"Cloudflare KV has no atomic increment (a get+put is racy under eventual consistency). Use a Durable Object for atomic counters.")}async deleteIfEquals(_key,_expectedValue){throw new StorageNotSupportedError("deleteIfEquals",this.backendName,"Cloudflare KV has no compare-and-set; use a Durable Object for an atomic compare-and-delete.")}getPubSubSuggestion(){return"Cloudflare KV does not support pub/sub. Use a Durable Object (or Queues) for fan-out."}toExpirationTtl(ttlSeconds,key){validateTTL(ttlSeconds);if(ttlSeconds<CF_KV_MIN_TTL_SECONDS){throw new StorageOperationError("set",key,`cloudflare-kv: expirationTtl must be >= ${CF_KV_MIN_TTL_SECONDS}s (Cloudflare KV minimum); got ${ttlSeconds}s.`)}return ttlSeconds}prefixKey(key){return this.keyPrefix+key}unprefixKey(key){return this.keyPrefix&&key.startsWith(this.keyPrefix)?key.slice(this.keyPrefix.length):key}}}});init_runtime();var IPV4_OCTET_PATTERN=/^(0|[1-9]\d{0,2})$/;var IPV6_GROUP_PATTERN=/^[0-9a-f]{1,4}$/;var IPV4_BLOCKS=compileBlocks([["0.0.0.0/8","this-network"],["10.0.0.0/8","private"],["100.64.0.0/10","carrier-grade-nat"],["127.0.0.0/8","loopback"],["169.254.0.0/16","link-local"],["172.16.0.0/12","private"],["192.0.0.0/24","ietf-protocol"],["192.168.0.0/16","private"],["198.18.0.0/15","benchmarking"],["224.0.0.0/4","multicast"],["255.255.255.255/32","reserved"],["240.0.0.0/4","reserved"]],parseIpv4);var IPV6_BLOCKS=compileBlocks([["::/128","unspecified"],["::1/128","loopback"],["fd00:ec2::254/128","cloud-metadata"],["fc00::/7","private"],["fe80::/10","link-local"],["fec0::/10","site-local"],["64:ff9b:1::/48","local-use-nat64"],["ff00::/8","multicast"]],expandIpv6);var IPV4_EMBEDDINGS=compileBlocks([["::ffff:0:0/96",6],["::ffff:0:0:0/96",6],["::/96",6],["64:ff9b::/96",6],["2002::/16",1]],expandIpv6);function parseIpv4(address){const parts=address.split(".");if(parts.length!==4||!parts.every(part=>IPV4_OCTET_PATTERN.test(part)))return void 0;const octets=parts.map(Number);return octets.every(octet=>octet<=255)?octets:void 0}function expandIpv6(address){const zoneIndex=address.indexOf("%");let text=(zoneIndex===-1?address:address.slice(0,zoneIndex)).toLowerCase();const lastColonIndex=text.lastIndexOf(":");const trailingSegment=text.slice(lastColonIndex+1);if(lastColonIndex!==-1&&trailingSegment.includes(".")){const octets=parseIpv4(trailingSegment);if(!octets)return void 0;const highGroup=(octets[0]<<8|octets[1]).toString(16);const lowGroup=(octets[2]<<8|octets[3]).toString(16);text=`${text.slice(0,lastColonIndex+1)}${highGroup}:${lowGroup}`}const halves=text.split("::");if(halves.length>2)return void 0;const head=parseIpv6Groups(halves[0]);if(!head)return void 0;if(halves.length===1)return head.length===8?head:void 0;const tail=parseIpv6Groups(halves[1]);if(!tail||head.length+tail.length>7)return void 0;return[...head,...new Array(8-head.length-tail.length).fill(0),...tail]}function parseIpv6Groups(part){if(part==="")return[];const groups=part.split(":");if(!groups.every(group=>IPV6_GROUP_PATTERN.test(group)))return void 0;return groups.map(group=>parseInt(group,16))}function compileBlocks(entries,parseNetwork){return entries.map(([cidr,value])=>{const[networkText,prefixText]=cidr.split("/");const network=parseNetwork(networkText);if(!network)throw new Error(`Invalid network in address block "${cidr}"`);return{cidr,network,prefixLength:Number(prefixText),value}})}init_fs2();var ordinaryHasInstance=Function.prototype[Symbol.hasInstance];init_crypto();init_memory();init_errors();init_errors();init_redis_error_listener();init_redis_url();init_errors();init_crypto();init_errors();var textEncoder2=new TextEncoder;var textDecoder2=new TextDecoder;init_base();init_memory();init_redis();init_vercel_kv();init_upstash();init_filesystem();init_pattern();init_ttl();init_crypto();init_runtime();var DASHBOARD_TOKEN_HEADER="x-frontmcp-dashboard-token";var DASHBOARD_SESSION_COOKIE="frontmcp_dashboard";var AUTHORIZED={authorized:true};function readHeader(headers,name){if(!headers)return void 0;const key=Object.keys(headers).find(k=>k.toLowerCase()===name);const value=key?headers[key]:void 0;return Array.isArray(value)?value[0]:value}function readQuery(query,name){const value=query?.[name];return Array.isArray(value)?value[0]:value}function tokenMatches(presented,expected){if(presented.length===0||expected.length===0)return false;return timingSafeEqual(sha256(presented),sha256(expected))}var encoder=new TextEncoder;function deriveDashboardCookieValue(token){return base64urlEncode(hmacSha256(encoder.encode(token),encoder.encode("frontmcp-dashboard-session:v1")))}function readCookie(header,name){if(!header)return void 0;for(const part of header.split(";")){const separator=part.indexOf("=");if(separator===-1)continue;if(part.slice(0,separator).trim()===name)return part.slice(separator+1).trim()}return void 0}function readBearerToken(header){if(!header)return void 0;const separator=header.indexOf(" ");if(separator===-1)return void 0;if(header.slice(0,separator).toLowerCase()!=="bearer")return void 0;const token=header.slice(separator+1).trim();return token.length>0?token:void 0}function createDashboardAuthValidator(auth,surface="page"){if(!auth?.enabled)return void 0;const expected=auth.token;const expectedCookie=expected?deriveDashboardCookieValue(expected):void 0;return req=>{if(!expected||!expectedCookie){return{authorized:false,status:401,message:"Dashboard authentication is misconfigured"}}const bearer=readBearerToken(readHeader(req.headers,"authorization"));if(bearer&&tokenMatches(bearer,expected))return AUTHORIZED;if(surface==="mcp"){const headerToken=readHeader(req.headers,DASHBOARD_TOKEN_HEADER);if(headerToken&&tokenMatches(headerToken,expected))return AUTHORIZED}else{const queryToken=readQuery(req.query,"token");if(queryToken&&tokenMatches(queryToken,expected))return AUTHORIZED}const cookie=readCookie(readHeader(req.headers,"cookie"),DASHBOARD_SESSION_COOKIE);if(cookie&&tokenMatches(cookie,expectedCookie))return AUTHORIZED;return{authorized:false,status:401,message:"Unauthorized"}}}var GATE_PASSED=Symbol("frontmcp:dashboard:gate-passed");function markDashboardGatePassed(ctx){ctx?.set(GATE_PASSED,true)}var DashboardDisabledError=class extends PublicMcpError{mcpErrorCode=MCP_ERROR_CODES.FORBIDDEN;constructor(){super("The FrontMCP dashboard is disabled.","DASHBOARD_DISABLED",404)}};var DashboardUnauthorizedError=class extends PublicMcpError{mcpErrorCode=MCP_ERROR_CODES.UNAUTHORIZED;constructor(){super(`The dashboard token is required. Send it in the ${DASHBOARD_TOKEN_HEADER} header.`,"DASHBOARD_UNAUTHORIZED",401,'Bearer realm="frontmcp-dashboard"')}};function assertDashboardAccess(options,ctx){if(!isDashboardEnabled(options)){throw new DashboardDisabledError}if(!options.auth?.enabled)return;if(ctx?.get(GATE_PASSED)===true)return;const expected=options.auth.token;const presented=ctx?.metadata?.customHeaders?.[DASHBOARD_TOKEN_HEADER];if(expected&&presented&&tokenMatches(presented,expected))return;throw new DashboardUnauthorizedError}function assertDashboardToolAccess(tool){const configured=tool.tryGet(DashboardConfigToken);assertDashboardAccess(configured??resolveDashboardOptions(),tool.tryGetContext())}function escapeForJs(str){return str.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/</g,"\\x3c").replace(/>/g,"\\x3e").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function generateDashboardHtml(options,mcpPath=options.basePath){if(options.cdn.entrypoint){return generateExternalEntrypointHtml(options,mcpPath)}return generateInlineDashboardHtml(options,mcpPath)}function generateExternalEntrypointHtml(options,mcpPath){const{cdn}=options;const safeBasePath=escapeForJs(options.basePath);const safeMcpPath=escapeForJs(mcpPath);const safeReact=escapeForJs(cdn.react);const safeReactDom=escapeForJs(cdn.reactDom);const safeReactDomClient=escapeForJs(cdn.reactDomClient);const safeReactJsxRuntime=escapeForJs(cdn.reactJsxRuntime);const safeXyflow=escapeForJs(cdn.xyflow);const safeDagre=escapeForJs(cdn.dagre);const safeXyflowCss=escapeForJs(cdn.xyflowCss);const safeEntrypoint=escapeForJs(cdn.entrypoint||"");return`<!DOCTYPE html>
|
|
1
|
+
var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __decorateClass=(decorators,target,key,kind)=>{var result=kind>1?void 0:kind?__getOwnPropDesc(target,key):target;for(var i=decorators.length-1,decorator;i>=0;i--)if(decorator=decorators[i])result=(kind?decorator(target,key,result):decorator(result))||result;if(kind&&result)__defProp(target,key,result);return result};import{DynamicPlugin,Plugin}from"@frontmcp/sdk";import{z}from"@frontmcp/lazy-zod";var cdnConfigSchema=z.object({entrypoint:z.string().optional(),react:z.string().default("https://esm.sh/react@19"),reactDom:z.string().default("https://esm.sh/react-dom@19"),reactDomClient:z.string().default("https://esm.sh/react-dom@19/client"),reactJsxRuntime:z.string().default("https://esm.sh/react@19/jsx-runtime"),reactRouter:z.string().default("https://esm.sh/react-router-dom@7"),xyflow:z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),dagre:z.string().default("https://esm.sh/dagre@0.8.5"),xyflowCss:z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")});var dashboardAuthSchema=z.object({enabled:z.boolean().default(false),token:z.string().min(1).optional()}).refine(value=>!value.enabled||Boolean(value.token),{message:"dashboard auth.enabled requires auth.token to be set",path:["token"]});var dashboardPluginOptionsSchema=z.object({enabled:z.boolean().optional(),basePath:z.string().default("/dashboard"),auth:dashboardAuthSchema.optional().transform(v=>dashboardAuthSchema.parse(v??{})),cdn:cdnConfigSchema.optional().transform(v=>cdnConfigSchema.parse(v??{}))});var defaultDashboardPluginOptions={basePath:"/dashboard",auth:{enabled:false},cdn:{}};function isDashboardEnabled(options){if(options.enabled!==void 0){return options.enabled}return process.env["NODE_ENV"]!=="production"}var publishedOptions;function publishDashboardOptions(options){if(publishedOptions&&!sameConfiguration(publishedOptions,options)){if(!sameAuth(publishedOptions,options)){throw new Error("[frontmcp:dashboard] A second, different dashboard AUTH configuration was registered in this process. Dashboard options are process-wide, so accepting it would apply one server's token to another. Run one dashboard per process, or give both servers the same auth configuration.")}console.warn("[frontmcp:dashboard] A second, different dashboard configuration was registered in this process. Dashboard options are process-wide, so the last one wins for every server here. Run one dashboard per process until per-instance configuration is supported.")}publishedOptions=options}function sameAuth(a,b){return a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token}function sameConfiguration(a,b){return a.basePath===b.basePath&&a.enabled===b.enabled&&a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token&&JSON.stringify(a.cdn??{})===JSON.stringify(b.cdn??{})}function resolveDashboardOptions(fallback={}){if(publishedOptions)return publishedOptions;return dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...fallback})}function resetDashboardOptions(){publishedOptions=void 0}var DashboardConfigToken=Symbol("DashboardConfig");var GraphDataProviderToken=Symbol("GraphDataProvider");var ParentScopeToken=Symbol("ParentScope");var DashboardPlugin=class extends DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(this.options)}static dynamicProviders(options){const parsedOptions=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(parsedOptions);return[{name:"dashboard:config",provide:DashboardConfigToken,useValue:parsedOptions}]}};DashboardPlugin=__decorateClass([Plugin({name:"dashboard",description:"Visual dashboard for FrontMCP server monitoring and visualization",providers:[]})],DashboardPlugin);import{App,DynamicPlugin as DynamicPlugin2,FrontMcpConfig,FrontMcpContextStorage,HttpHook,Plugin as Plugin2,ScopeEntry as ScopeEntry2}from"@frontmcp/sdk";import{MCP_ERROR_CODES,PublicMcpError}from"@frontmcp/sdk";import{base64urlEncode,hmacSha256,sha256,timingSafeEqual}from"@frontmcp/utils";var DASHBOARD_TOKEN_HEADER="x-frontmcp-dashboard-token";var DASHBOARD_SESSION_COOKIE="frontmcp_dashboard";var AUTHORIZED={authorized:true};function readHeader(headers,name){if(!headers)return void 0;const key=Object.keys(headers).find(k=>k.toLowerCase()===name);const value=key?headers[key]:void 0;return Array.isArray(value)?value[0]:value}function readQuery(query,name){const value=query?.[name];return Array.isArray(value)?value[0]:value}function tokenMatches(presented,expected){if(presented.length===0||expected.length===0)return false;return timingSafeEqual(sha256(presented),sha256(expected))}var encoder=new TextEncoder;function deriveDashboardCookieValue(token){return base64urlEncode(hmacSha256(encoder.encode(token),encoder.encode("frontmcp-dashboard-session:v1")))}function readCookie(header,name){if(!header)return void 0;for(const part of header.split(";")){const separator=part.indexOf("=");if(separator===-1)continue;if(part.slice(0,separator).trim()===name)return part.slice(separator+1).trim()}return void 0}function readBearerToken(header){if(!header)return void 0;const separator=header.indexOf(" ");if(separator===-1)return void 0;if(header.slice(0,separator).toLowerCase()!=="bearer")return void 0;const token=header.slice(separator+1).trim();return token.length>0?token:void 0}function createDashboardAuthValidator(auth,surface="page"){if(!auth?.enabled)return void 0;const expected=auth.token;const expectedCookie=expected?deriveDashboardCookieValue(expected):void 0;return req=>{if(!expected||!expectedCookie){return{authorized:false,status:401,message:"Dashboard authentication is misconfigured"}}const bearer=readBearerToken(readHeader(req.headers,"authorization"));if(bearer&&tokenMatches(bearer,expected))return AUTHORIZED;if(surface==="mcp"){const headerToken=readHeader(req.headers,DASHBOARD_TOKEN_HEADER);if(headerToken&&tokenMatches(headerToken,expected))return AUTHORIZED}else{const queryToken=readQuery(req.query,"token");if(queryToken&&tokenMatches(queryToken,expected))return AUTHORIZED}const cookie=readCookie(readHeader(req.headers,"cookie"),DASHBOARD_SESSION_COOKIE);if(cookie&&tokenMatches(cookie,expectedCookie))return AUTHORIZED;return{authorized:false,status:401,message:"Unauthorized"}}}var GATE_PASSED=Symbol("frontmcp:dashboard:gate-passed");function markDashboardGatePassed(ctx){ctx?.set(GATE_PASSED,true)}var DashboardDisabledError=class extends PublicMcpError{mcpErrorCode=MCP_ERROR_CODES.FORBIDDEN;constructor(){super("The FrontMCP dashboard is disabled.","DASHBOARD_DISABLED",404)}};var DashboardUnauthorizedError=class extends PublicMcpError{mcpErrorCode=MCP_ERROR_CODES.UNAUTHORIZED;constructor(){super(`The dashboard token is required. Send it in the ${DASHBOARD_TOKEN_HEADER} header.`,"DASHBOARD_UNAUTHORIZED",401,'Bearer realm="frontmcp-dashboard"')}};function assertDashboardAccess(options,ctx){if(!isDashboardEnabled(options)){throw new DashboardDisabledError}if(!options.auth?.enabled)return;if(ctx?.get(GATE_PASSED)===true)return;const expected=options.auth.token;const presented=ctx?.metadata?.customHeaders?.[DASHBOARD_TOKEN_HEADER];if(expected&&presented&&tokenMatches(presented,expected))return;throw new DashboardUnauthorizedError}function assertDashboardToolAccess(tool){const configured=tool.tryGet(DashboardConfigToken);assertDashboardAccess(configured??resolveDashboardOptions(),tool.tryGetContext())}import{Provider}from"@frontmcp/sdk";var GraphDataProvider=class{constructor(scope,serverName,serverVersion){this.scope=scope;this.serverName=serverName;this.serverVersion=serverVersion}scope;serverName;serverVersion;cachedData=null;cacheTimestamp=0;cacheTTL=5e3;async getGraphData(){const now=Date.now();if(this.cachedData&&now-this.cacheTimestamp<this.cacheTTL){return this.cachedData}this.cachedData=this.extractGraphData();this.cacheTimestamp=now;return this.cachedData}invalidateCache(){this.cachedData=null;this.cacheTimestamp=0}getMonitoredScopes(){try{let scopeRegistries=this.scope.providers.getRegistries("ScopeRegistry");if(!scopeRegistries||scopeRegistries.length===0){let currentProviders=this.scope.providers.parentProviders;while(currentProviders&&(!scopeRegistries||scopeRegistries.length===0)){scopeRegistries=currentProviders.getRegistries?.("ScopeRegistry")||[];currentProviders=currentProviders.parentProviders}}if(!scopeRegistries||scopeRegistries.length===0){return[this.scope]}const scopeRegistry=scopeRegistries[0];const allScopes=scopeRegistry.getScopes();return allScopes.filter(s=>s.id!=="dashboard")}catch{return[this.scope]}}extractGraphData(){const nodes=[];const edges=[];const addedNodeIds=new Set;const addNode=node=>{if(!addedNodeIds.has(node.id)){nodes.push(node);addedNodeIds.add(node.id)}};const serverId=`server:${this.serverName}`;addNode({id:serverId,type:"server",label:this.serverName,data:{name:this.serverName,description:`FrontMCP Server v${this.serverVersion||"unknown"}`}});const scopes=this.getMonitoredScopes();for(const scope of scopes){const scopeId=`scope:${scope.id}`;addNode({id:scopeId,type:"scope",label:scope.id,data:{name:scope.id}});edges.push({id:`${serverId}->${scopeId}`,source:serverId,target:scopeId,type:"contains"});this.extractToolsFromScope(scope,scopeId,edges,addNode);this.extractResourcesFromScope(scope,scopeId,edges,addNode);this.extractResourceTemplatesFromScope(scope,scopeId,edges,addNode);this.extractPromptsFromScope(scope,scopeId,edges,addNode);this.extractAppsFromScope(scope,scopeId,edges,addNode)}return{nodes,edges,metadata:this.createMetadata(nodes.length,edges.length)}}extractToolsFromScope(scope,scopeId,edges,addNode){try{const tools=scope.tools?.getTools?.(true)||[];for(const tool of tools){if(tool.fullName?.startsWith("dashboard:")){continue}const toolId=`tool:${tool.fullName}`;addNode({id:toolId,type:"tool",label:tool.name,data:{name:tool.fullName,description:tool.metadata?.description,inputSchema:tool.inputSchema,outputSchema:tool.outputSchema,tags:tool.metadata?.tags,annotations:tool.metadata?.annotations}});edges.push({id:`${scopeId}->${toolId}`,source:scopeId,target:toolId,type:"provides"})}}catch{}}extractResourcesFromScope(scope,scopeId,edges,addNode){try{const resources=scope.resources?.getResources?.(true)||[];for(const resource of resources){const resourceId=`resource:${resource.uri}`;addNode({id:resourceId,type:"resource",label:resource.name,data:{name:resource.name,description:resource.metadata?.description,uri:resource.uri,mimeType:resource.metadata?.mimeType}});edges.push({id:`${scopeId}->${resourceId}`,source:scopeId,target:resourceId,type:"provides"})}}catch{}}extractResourceTemplatesFromScope(scope,scopeId,edges,addNode){try{const templates=scope.resources?.getResourceTemplates?.()||[];for(const template of templates){const templateId=`resource-template:${template.uriTemplate}`;addNode({id:templateId,type:"resource-template",label:template.name,data:{name:template.name,description:template.metadata?.description,uri:template.uriTemplate,mimeType:template.metadata?.mimeType}});edges.push({id:`${scopeId}->${templateId}`,source:scopeId,target:templateId,type:"provides"})}}catch{}}extractPromptsFromScope(scope,scopeId,edges,addNode){try{const prompts=scope.prompts?.getPrompts?.(true)||[];for(const prompt of prompts){const promptId=`prompt:${prompt.name}`;addNode({id:promptId,type:"prompt",label:prompt.name,data:{name:prompt.name,description:prompt.metadata?.description,arguments:prompt.metadata?.arguments}});edges.push({id:`${scopeId}->${promptId}`,source:scopeId,target:promptId,type:"provides"})}}catch{}}extractAppsFromScope(scope,scopeId,edges,addNode){try{const apps=scope.apps?.getApps?.()||[];for(const app of apps){const appName=app.metadata?.name||"unknown";if(appName==="dashboard"){continue}const appId=`app:${appName}`;addNode({id:appId,type:"app",label:appName,data:{name:appName,description:app.metadata?.description}});edges.push({id:`${scopeId}->${appId}`,source:scopeId,target:appId,type:"contains"})}}catch{}}createMetadata(nodeCount,edgeCount){return{serverName:this.serverName,serverVersion:this.serverVersion,generatedAt:new Date().toISOString(),entryFile:"runtime",nodeCount,edgeCount}}};GraphDataProvider=__decorateClass([Provider({name:"graph-data-provider"})],GraphDataProvider);import{z as z2}from"@frontmcp/lazy-zod";import{Tool,ToolContext}from"@frontmcp/sdk";var graphToolInputSchema={includeSchemas:z2.boolean().optional().default(false).describe("Include full input/output schemas in the response"),refresh:z2.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")};var GraphTool=class extends ToolContext{async execute(input){assertDashboardToolAccess(this);const graphProvider=this.get(GraphDataProvider);if(input.refresh){graphProvider.invalidateCache()}const graphData=await graphProvider.getGraphData();if(!input.includeSchemas){return{...graphData,nodes:graphData.nodes.map(node=>({...node,data:{...node.data,inputSchema:void 0,outputSchema:void 0}}))}}return graphData}};GraphTool=__decorateClass([Tool({name:"dashboard:graph",description:"Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",inputSchema:graphToolInputSchema,annotations:{readOnlyHint:true}})],GraphTool);import{z as z3}from"@frontmcp/lazy-zod";import{Tool as Tool2,ToolContext as ToolContext2}from"@frontmcp/sdk";function safeRegex(pattern){if(pattern.length>100){return null}try{const regex=new RegExp(pattern,"i");regex.test("test");return regex}catch{return null}}var listResourcesInputSchema={filter:z3.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),includeTemplates:z3.boolean().default(true).describe("Include resource templates in the response")};var listResourcesOutputSchema=z3.object({resources:z3.array(z3.object({name:z3.string(),uri:z3.string(),description:z3.string().optional(),mimeType:z3.string().optional(),isTemplate:z3.boolean()})),count:z3.number()});var ListResourcesTool=class extends ToolContext2{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;const results=[];try{const resources=targetScope.resources?.getResources?.(true)||[];for(const resource of resources){results.push({name:resource.name||"unnamed",uri:resource.uri||"unknown",description:resource.metadata?.description,mimeType:resource.metadata?.mimeType,isTemplate:false})}}catch{}if(input.includeTemplates){try{const templates=targetScope.resources?.getResourceTemplates?.()||[];for(const template of templates){results.push({name:template.name||"unnamed",uri:template.uriTemplate||"unknown",description:template.metadata?.description,mimeType:template.metadata?.mimeType,isTemplate:true})}}catch{}}let filtered=results;if(input.filter){const pattern=safeRegex(input.filter);if(pattern){filtered=results.filter(r=>pattern.test(r.name)||pattern.test(r.uri))}}return{resources:filtered,count:filtered.length}}};ListResourcesTool=__decorateClass([Tool2({name:"dashboard:list-resources",description:"List all resources and resource templates registered in the monitored FrontMCP server.",inputSchema:listResourcesInputSchema,outputSchema:listResourcesOutputSchema,annotations:{readOnlyHint:true}})],ListResourcesTool);import{z as z4}from"@frontmcp/lazy-zod";import{Tool as Tool3,ToolContext as ToolContext3}from"@frontmcp/sdk";var listToolsInputSchema={filter:z4.string().optional().describe("Filter tools by name pattern (regex supported)"),includePlugins:z4.boolean().default(true).describe("Include tools from plugins"),includeSchemas:z4.boolean().default(false).describe("Include input/output schemas in the response")};var listToolsOutputSchema=z4.object({tools:z4.array(z4.object({name:z4.string(),fullName:z4.string(),description:z4.string().optional(),tags:z4.array(z4.string()).optional(),inputSchema:z4.unknown().optional(),outputSchema:z4.unknown().optional()})),count:z4.number()});var ListToolsTool=class extends ToolContext3{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;let allTools=[];try{allTools=targetScope.tools?.getTools?.(input.includePlugins??true)||[]}catch{}if(input.filter){const pattern=safeRegex(input.filter);if(pattern){allTools=allTools.filter(t=>pattern.test(t.name)||pattern.test(t.fullName))}}const tools=allTools.map(tool=>({name:tool.name,fullName:tool.fullName,description:tool.metadata?.description,tags:tool.metadata?.tags,...input.includeSchemas?{inputSchema:tool.inputSchema,outputSchema:tool.outputSchema}:{}}));return{tools,count:tools.length}}};ListToolsTool=__decorateClass([Tool3({name:"dashboard:list-tools",description:"List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",inputSchema:listToolsInputSchema,outputSchema:listToolsOutputSchema,annotations:{readOnlyHint:true}})],ListToolsTool);import{httpRespond,HttpRoute,HttpRouteContext}from"@frontmcp/sdk";function escapeForJs(str){return str.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/</g,"\\x3c").replace(/>/g,"\\x3e").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function generateDashboardHtml(options,mcpPath=options.basePath){if(options.cdn.entrypoint){return generateExternalEntrypointHtml(options,mcpPath)}return generateInlineDashboardHtml(options,mcpPath)}function generateExternalEntrypointHtml(options,mcpPath){const{cdn}=options;const safeBasePath=escapeForJs(options.basePath);const safeMcpPath=escapeForJs(mcpPath);const safeReact=escapeForJs(cdn.react);const safeReactDom=escapeForJs(cdn.reactDom);const safeReactDomClient=escapeForJs(cdn.reactDomClient);const safeReactJsxRuntime=escapeForJs(cdn.reactJsxRuntime);const safeXyflow=escapeForJs(cdn.xyflow);const safeDagre=escapeForJs(cdn.dagre);const safeXyflowCss=escapeForJs(cdn.xyflowCss);const safeEntrypoint=escapeForJs(cdn.entrypoint||"");return`<!DOCTYPE html>
|
|
5
2
|
<html lang="en">
|
|
6
3
|
<head>
|
|
7
4
|
<meta charset="UTF-8">
|
|
@@ -548,4 +545,4 @@ var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropD
|
|
|
548
545
|
);
|
|
549
546
|
</script>
|
|
550
547
|
</body>
|
|
551
|
-
</html>`}import{Provider}from"@frontmcp/sdk";var GraphDataProvider=class{constructor(scope,serverName,serverVersion){this.scope=scope;this.serverName=serverName;this.serverVersion=serverVersion}scope;serverName;serverVersion;cachedData=null;cacheTimestamp=0;cacheTTL=5e3;async getGraphData(){const now=Date.now();if(this.cachedData&&now-this.cacheTimestamp<this.cacheTTL){return this.cachedData}this.cachedData=this.extractGraphData();this.cacheTimestamp=now;return this.cachedData}invalidateCache(){this.cachedData=null;this.cacheTimestamp=0}getMonitoredScopes(){try{let scopeRegistries=this.scope.providers.getRegistries("ScopeRegistry");if(!scopeRegistries||scopeRegistries.length===0){let currentProviders=this.scope.providers.parentProviders;while(currentProviders&&(!scopeRegistries||scopeRegistries.length===0)){scopeRegistries=currentProviders.getRegistries?.("ScopeRegistry")||[];currentProviders=currentProviders.parentProviders}}if(!scopeRegistries||scopeRegistries.length===0){return[this.scope]}const scopeRegistry=scopeRegistries[0];const allScopes=scopeRegistry.getScopes();return allScopes.filter(s=>s.id!=="dashboard")}catch{return[this.scope]}}extractGraphData(){const nodes=[];const edges=[];const addedNodeIds=new Set;const addNode=node=>{if(!addedNodeIds.has(node.id)){nodes.push(node);addedNodeIds.add(node.id)}};const serverId=`server:${this.serverName}`;addNode({id:serverId,type:"server",label:this.serverName,data:{name:this.serverName,description:`FrontMCP Server v${this.serverVersion||"unknown"}`}});const scopes=this.getMonitoredScopes();for(const scope of scopes){const scopeId=`scope:${scope.id}`;addNode({id:scopeId,type:"scope",label:scope.id,data:{name:scope.id}});edges.push({id:`${serverId}->${scopeId}`,source:serverId,target:scopeId,type:"contains"});this.extractToolsFromScope(scope,scopeId,edges,addNode);this.extractResourcesFromScope(scope,scopeId,edges,addNode);this.extractResourceTemplatesFromScope(scope,scopeId,edges,addNode);this.extractPromptsFromScope(scope,scopeId,edges,addNode);this.extractAppsFromScope(scope,scopeId,edges,addNode)}return{nodes,edges,metadata:this.createMetadata(nodes.length,edges.length)}}extractToolsFromScope(scope,scopeId,edges,addNode){try{const tools=scope.tools?.getTools?.(true)||[];for(const tool of tools){if(tool.fullName?.startsWith("dashboard:")){continue}const toolId=`tool:${tool.fullName}`;addNode({id:toolId,type:"tool",label:tool.name,data:{name:tool.fullName,description:tool.metadata?.description,inputSchema:tool.inputSchema,outputSchema:tool.outputSchema,tags:tool.metadata?.tags,annotations:tool.metadata?.annotations}});edges.push({id:`${scopeId}->${toolId}`,source:scopeId,target:toolId,type:"provides"})}}catch{}}extractResourcesFromScope(scope,scopeId,edges,addNode){try{const resources=scope.resources?.getResources?.(true)||[];for(const resource of resources){const resourceId=`resource:${resource.uri}`;addNode({id:resourceId,type:"resource",label:resource.name,data:{name:resource.name,description:resource.metadata?.description,uri:resource.uri,mimeType:resource.metadata?.mimeType}});edges.push({id:`${scopeId}->${resourceId}`,source:scopeId,target:resourceId,type:"provides"})}}catch{}}extractResourceTemplatesFromScope(scope,scopeId,edges,addNode){try{const templates=scope.resources?.getResourceTemplates?.()||[];for(const template of templates){const templateId=`resource-template:${template.uriTemplate}`;addNode({id:templateId,type:"resource-template",label:template.name,data:{name:template.name,description:template.metadata?.description,uri:template.uriTemplate,mimeType:template.metadata?.mimeType}});edges.push({id:`${scopeId}->${templateId}`,source:scopeId,target:templateId,type:"provides"})}}catch{}}extractPromptsFromScope(scope,scopeId,edges,addNode){try{const prompts=scope.prompts?.getPrompts?.(true)||[];for(const prompt of prompts){const promptId=`prompt:${prompt.name}`;addNode({id:promptId,type:"prompt",label:prompt.name,data:{name:prompt.name,description:prompt.metadata?.description,arguments:prompt.metadata?.arguments}});edges.push({id:`${scopeId}->${promptId}`,source:scopeId,target:promptId,type:"provides"})}}catch{}}extractAppsFromScope(scope,scopeId,edges,addNode){try{const apps=scope.apps?.getApps?.()||[];for(const app of apps){const appName=app.metadata?.name||"unknown";if(appName==="dashboard"){continue}const appId=`app:${appName}`;addNode({id:appId,type:"app",label:appName,data:{name:appName,description:app.metadata?.description}});edges.push({id:`${scopeId}->${appId}`,source:scopeId,target:appId,type:"contains"})}}catch{}}createMetadata(nodeCount,edgeCount){return{serverName:this.serverName,serverVersion:this.serverVersion,generatedAt:new Date().toISOString(),entryFile:"runtime",nodeCount,edgeCount}}};GraphDataProvider=__decorateClass([Provider({name:"graph-data-provider"})],GraphDataProvider);import{z as z3}from"@frontmcp/lazy-zod";import{Tool,ToolContext}from"@frontmcp/sdk";var graphToolInputSchema={includeSchemas:z3.boolean().optional().default(false).describe("Include full input/output schemas in the response"),refresh:z3.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")};var GraphTool=class extends ToolContext{async execute(input){assertDashboardToolAccess(this);const graphProvider=this.get(GraphDataProvider);if(input.refresh){graphProvider.invalidateCache()}const graphData=await graphProvider.getGraphData();if(!input.includeSchemas){return{...graphData,nodes:graphData.nodes.map(node=>({...node,data:{...node.data,inputSchema:void 0,outputSchema:void 0}}))}}return graphData}};GraphTool=__decorateClass([Tool({name:"dashboard:graph",description:"Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",inputSchema:graphToolInputSchema,annotations:{readOnlyHint:true}})],GraphTool);import{z as z4}from"@frontmcp/lazy-zod";import{Tool as Tool2,ToolContext as ToolContext2}from"@frontmcp/sdk";function safeRegex(pattern){if(pattern.length>100){return null}try{const regex=new RegExp(pattern,"i");regex.test("test");return regex}catch{return null}}var listResourcesInputSchema={filter:z4.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),includeTemplates:z4.boolean().default(true).describe("Include resource templates in the response")};var listResourcesOutputSchema=z4.object({resources:z4.array(z4.object({name:z4.string(),uri:z4.string(),description:z4.string().optional(),mimeType:z4.string().optional(),isTemplate:z4.boolean()})),count:z4.number()});var ListResourcesTool=class extends ToolContext2{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;const results=[];try{const resources=targetScope.resources?.getResources?.(true)||[];for(const resource of resources){results.push({name:resource.name||"unnamed",uri:resource.uri||"unknown",description:resource.metadata?.description,mimeType:resource.metadata?.mimeType,isTemplate:false})}}catch{}if(input.includeTemplates){try{const templates=targetScope.resources?.getResourceTemplates?.()||[];for(const template of templates){results.push({name:template.name||"unnamed",uri:template.uriTemplate||"unknown",description:template.metadata?.description,mimeType:template.metadata?.mimeType,isTemplate:true})}}catch{}}let filtered=results;if(input.filter){const pattern=safeRegex(input.filter);if(pattern){filtered=results.filter(r=>pattern.test(r.name)||pattern.test(r.uri))}}return{resources:filtered,count:filtered.length}}};ListResourcesTool=__decorateClass([Tool2({name:"dashboard:list-resources",description:"List all resources and resource templates registered in the monitored FrontMCP server.",inputSchema:listResourcesInputSchema,outputSchema:listResourcesOutputSchema,annotations:{readOnlyHint:true}})],ListResourcesTool);import{z as z5}from"@frontmcp/lazy-zod";import{Tool as Tool3,ToolContext as ToolContext3}from"@frontmcp/sdk";var listToolsInputSchema={filter:z5.string().optional().describe("Filter tools by name pattern (regex supported)"),includePlugins:z5.boolean().default(true).describe("Include tools from plugins"),includeSchemas:z5.boolean().default(false).describe("Include input/output schemas in the response")};var listToolsOutputSchema=z5.object({tools:z5.array(z5.object({name:z5.string(),fullName:z5.string(),description:z5.string().optional(),tags:z5.array(z5.string()).optional(),inputSchema:z5.unknown().optional(),outputSchema:z5.unknown().optional()})),count:z5.number()});var ListToolsTool=class extends ToolContext3{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;let allTools=[];try{allTools=targetScope.tools?.getTools?.(input.includePlugins??true)||[]}catch{}if(input.filter){const pattern=safeRegex(input.filter);if(pattern){allTools=allTools.filter(t=>pattern.test(t.name)||pattern.test(t.fullName))}}const tools=allTools.map(tool=>({name:tool.name,fullName:tool.fullName,description:tool.metadata?.description,tags:tool.metadata?.tags,...input.includeSchemas?{inputSchema:tool.inputSchema,outputSchema:tool.outputSchema}:{}}));return{tools,count:tools.length}}};ListToolsTool=__decorateClass([Tool3({name:"dashboard:list-tools",description:"List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",inputSchema:listToolsInputSchema,outputSchema:listToolsOutputSchema,annotations:{readOnlyHint:true}})],ListToolsTool);var DashboardMiddlewareToken=Symbol("dashboard:middleware");function sessionCookies(options,paths,secure){const token=options.auth.token;if(!options.auth.enabled||!token)return[];const value=deriveDashboardCookieValue(token);return paths.map(path=>`${DASHBOARD_SESSION_COOKIE}=${value}; Path=${path}; HttpOnly; SameSite=Strict${secure?"; Secure":""}`)}function isHttps(req){const socket=req.socket;const forwarded=req.headers?.["x-forwarded-proto"];return socket?.encrypted===true||typeof forwarded==="string"&&forwarded.split(",")[0]?.trim()==="https"}function createDashboardMiddleware(options,mcpPath){const html=generateDashboardHtml(options,mcpPath);const authorize=createDashboardAuthValidator(options.auth,"page");const cookiePaths=[...new Set([mcpPath,options.basePath])];return async(req,res,next)=>{if(!isDashboardEnabled(options)){return next()}const urlPath=req.path||req.url||"/";const method=(req.method||"GET").toUpperCase();const isPageRequest=method==="GET"&&(urlPath==="/"||urlPath==="");if(!isPageRequest){return next()}if(authorize){const result=authorize({headers:req.headers,query:req.query});if(!result.authorized){res.status(result.status??401).json({error:"Unauthorized",message:result.message??"Unauthorized"});return}}const writer=res;writer.setHeader?.("Content-Type","text/html");const cookies=sessionCookies(options,cookiePaths,isHttps(req));if(cookies.length>0)writer.setHeader?.("Set-Cookie",cookies);res.status(200).send(html)}}var DashboardHttpPlugin=class extends DynamicPlugin2{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options})}async gateDashboardMcp(flowCtx){const options=this.resolve(DashboardConfigToken)??resolveDashboardOptions();if(!isDashboardEnabled(options)){throw new DashboardDisabledError}const authorize=createDashboardAuthValidator(options.auth,"mcp");if(!authorize)return;const request=flowCtx.rawInput.request;const result=authorize({headers:request.headers,query:request.query});if(!result.authorized){throw new DashboardUnauthorizedError}markDashboardGatePassed(this.resolve(FrontMcpContextStorage)?.getStore())}resolve(token){try{return this.get(token)}catch{return void 0}}static dynamicProviders(options){return[{name:"dashboard:config",provide:DashboardConfigToken,inject:()=>[],useFactory:()=>resolveDashboardOptions(options)},{name:"dashboard:middleware",provide:DashboardMiddlewareToken,inject:()=>[FrontMcpServer,ScopeEntry2],useFactory:(server,scope)=>{const effectiveOptions=resolveDashboardOptions(options);const mcpPath=scope.fullPath;server.registerMiddleware(effectiveOptions.basePath,createDashboardMiddleware(effectiveOptions,mcpPath));return{registered:true,mcpPath}}}]}};__decorateClass([HttpHook.Will("handleMcp2026",{priority:1e3})],DashboardHttpPlugin.prototype,"gateDashboardMcp",1);DashboardHttpPlugin=__decorateClass([Plugin2({name:"dashboard:http",description:"Dashboard HTTP handler for serving UI HTML"})],DashboardHttpPlugin);var DashboardApp=class{};DashboardApp=__decorateClass([App({name:"dashboard",description:"FrontMCP Dashboard for visualization and monitoring",providers:[{name:"dashboard:parent-scope",provide:ParentScopeToken,inject:()=>[ScopeEntry2],useFactory:scope=>{return scope}},{name:"dashboard:graph-data",provide:GraphDataProvider,inject:()=>[ScopeEntry2,FrontMcpConfig],useFactory:(scope,config)=>{const serverName=config.info?.name||"FrontMCP Server";const serverVersion=config.info?.version;return new GraphDataProvider(scope,serverName,serverVersion)}}],plugins:[DashboardHttpPlugin.init({})],tools:[GraphTool,ListToolsTool,ListResourcesTool],standalone:true})],DashboardApp);export{DashboardApp,DashboardConfigToken,DashboardPlugin,GraphDataProvider,GraphDataProviderToken,GraphTool,ListResourcesTool,ListToolsTool,ParentScopeToken,dashboardPluginOptionsSchema,DashboardPlugin as default,defaultDashboardPluginOptions,isDashboardEnabled,resetDashboardOptions};
|
|
548
|
+
</html>`}function isHttps(request){const socket=request.socket;const forwarded=request.headers["x-forwarded-proto"];const protocol=request.protocol;return socket?.encrypted===true||protocol==="https"||typeof forwarded==="string"&&forwarded.split(",")[0]?.trim()==="https"}function sessionCookies(options,paths,secure){const token=options.auth.token;if(!options.auth.enabled||!token)return[];const value=deriveDashboardCookieValue(token);return paths.map(path=>({name:DASHBOARD_SESSION_COOKIE,value,path,httpOnly:true,sameSite:"strict",secure}))}var DashboardPageRoute=class extends HttpRouteContext{handle(){const options=resolveDashboardOptions();if(!isDashboardEnabled(options))throw new DashboardDisabledError;const authorize=createDashboardAuthValidator(options.auth,"page");const result=authorize?.({headers:this.request.headers,query:this.request.query});if(result&&!result.authorized){return httpRespond.json({error:"Unauthorized",message:result.message??"Unauthorized"},{status:result.status??401})}const mcpPath=this.scope.fullPath;const cookies=sessionCookies(options,[...new Set([mcpPath,options.basePath])],isHttps(this.request));return{...httpRespond.html(generateDashboardHtml(options,mcpPath)),cookies}}};DashboardPageRoute=__decorateClass([HttpRoute({method:"GET",path:()=>resolveDashboardOptions().basePath,auth:"public"})],DashboardPageRoute);var DashboardHttpPlugin=class extends DynamicPlugin2{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options})}async gateDashboardMcp(flowCtx){const options=this.resolve(DashboardConfigToken)??resolveDashboardOptions();if(!isDashboardEnabled(options)){throw new DashboardDisabledError}const authorize=createDashboardAuthValidator(options.auth,"mcp");if(!authorize)return;const request=flowCtx.rawInput.request;const result=authorize({headers:request.headers,query:request.query});if(!result.authorized){throw new DashboardUnauthorizedError}markDashboardGatePassed(this.resolve(FrontMcpContextStorage)?.getStore())}resolve(token){try{return this.get(token)}catch{return void 0}}static dynamicProviders(options){return[{name:"dashboard:config",provide:DashboardConfigToken,inject:()=>[],useFactory:()=>resolveDashboardOptions(options)}]}};__decorateClass([HttpHook.Will("handleMcp2026",{priority:1e3})],DashboardHttpPlugin.prototype,"gateDashboardMcp",1);DashboardHttpPlugin=__decorateClass([Plugin2({name:"dashboard:http",description:"Dashboard HTTP handler for serving UI HTML"})],DashboardHttpPlugin);var DashboardApp=class{};DashboardApp=__decorateClass([App({name:"dashboard",description:"FrontMCP Dashboard for visualization and monitoring",providers:[{name:"dashboard:parent-scope",provide:ParentScopeToken,inject:()=>[ScopeEntry2],useFactory:scope=>{return scope}},{name:"dashboard:graph-data",provide:GraphDataProvider,inject:()=>[ScopeEntry2,FrontMcpConfig],useFactory:(scope,config)=>{const serverName=config.info?.name||"FrontMCP Server";const serverVersion=config.info?.version;return new GraphDataProvider(scope,serverName,serverVersion)}}],plugins:[DashboardHttpPlugin.init({})],tools:[GraphTool,ListToolsTool,ListResourcesTool],routes:[DashboardPageRoute],standalone:true})],DashboardApp);export{DashboardApp,DashboardConfigToken,DashboardPlugin,GraphDataProvider,GraphDataProviderToken,GraphTool,ListResourcesTool,ListToolsTool,ParentScopeToken,dashboardPluginOptionsSchema,DashboardPlugin as default,defaultDashboardPluginOptions,isDashboardEnabled,resetDashboardOptions};
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0-rc.2",
|
|
4
4
|
"description": "Dashboard plugin for FrontMCP: a web page and MCP endpoint that show a server's apps, tools, resources and prompts",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,14 +49,14 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.
|
|
52
|
+
"@frontmcp/sdk": "1.11.0-rc.2",
|
|
53
53
|
"reflect-metadata": "^0.2.2"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@
|
|
56
|
+
"@frontmcp/utils": "1.11.0-rc.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@frontmcp/lazy-zod": "1.
|
|
60
|
-
"@frontmcp/sdk": "1.
|
|
59
|
+
"@frontmcp/lazy-zod": "1.11.0-rc.2",
|
|
60
|
+
"@frontmcp/sdk": "1.11.0-rc.2"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf;var __hasOwnProp=Object.prototype.hasOwnProperty;var __commonJS=(cb,mod)=>function __require2(){try{return mod||(0,cb[__getOwnPropNames(cb)[0]])((mod={exports:{}}).exports,mod),mod.exports}catch(e){throw mod=0,e}};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var __copyProps=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames(from))if(!__hasOwnProp.call(to,key)&&key!==except)__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable})}return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:true}):target,mod));var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:true}),mod);var __decorateClass=(decorators,target,key,kind)=>{var result=kind>1?void 0:kind?__getOwnPropDesc(target,key):target;for(var i=decorators.length-1,decorator;i>=0;i--)if(decorator=decorators[i])result=(kind?decorator(target,key,result):decorator(result))||result;if(kind&&result)__defProp(target,key,result);return result};var require_node_event_emitter=__commonJS({"libs/utils/dist/event-emitter/node-event-emitter.js"(exports2,module2){"use strict";var __defProp3=Object.defineProperty;var __getOwnPropDesc2=Object.getOwnPropertyDescriptor;var __getOwnPropNames3=Object.getOwnPropertyNames;var __hasOwnProp2=Object.prototype.hasOwnProperty;var __export3=(target,all)=>{for(var name in all)__defProp3(target,name,{get:all[name],enumerable:true})};var __copyProps2=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames3(from))if(!__hasOwnProp2.call(to,key)&&key!==except)__defProp3(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc2(from,key))||desc.enumerable})}return to};var __toCommonJS2=mod=>__copyProps2(__defProp3({},"__esModule",{value:true}),mod);var node_event_emitter_exports={};__export3(node_event_emitter_exports,{EventEmitter:()=>import_events.EventEmitter});module2.exports=__toCommonJS2(node_event_emitter_exports);var import_events=require("events")}});var require_node=__commonJS({"libs/utils/dist/crypto/node.js"(exports2,module2){"use strict";var __create2=Object.create;var __defProp3=Object.defineProperty;var __getOwnPropDesc2=Object.getOwnPropertyDescriptor;var __getOwnPropNames3=Object.getOwnPropertyNames;var __getProtoOf2=Object.getPrototypeOf;var __hasOwnProp2=Object.prototype.hasOwnProperty;var __export3=(target,all)=>{for(var name in all)__defProp3(target,name,{get:all[name],enumerable:true})};var __copyProps2=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames3(from))if(!__hasOwnProp2.call(to,key)&&key!==except)__defProp3(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc2(from,key))||desc.enumerable})}return to};var __toESM2=(mod,isNodeMode,target)=>(target=mod!=null?__create2(__getProtoOf2(mod)):{},__copyProps2(isNodeMode||!mod||!mod.__esModule?__defProp3(target,"default",{value:mod,enumerable:true}):target,mod));var __toCommonJS2=mod=>__copyProps2(__defProp3({},"__esModule",{value:true}),mod);var node_exports={};__export3(node_exports,{createSignedJwt:()=>createSignedJwt,cryptoProvider:()=>nodeCrypto,generateRsaKeyPair:()=>generateRsaKeyPair3,isRsaPssAlg:()=>isRsaPssAlg2,jwtAlgToNodeAlg:()=>jwtAlgToNodeAlg,nodeCrypto:()=>nodeCrypto,pemToPublicJwk:()=>pemToPublicJwk3,rsaSign:()=>rsaSign,rsaSignBase64Url:()=>rsaSignBase64Url3,rsaVerify:()=>rsaVerify3,rsaVerifySync:()=>rsaVerifySync3});module2.exports=__toCommonJS2(node_exports);var import_node_crypto=__toESM2(require("node:crypto"));var JWT_ALG_TO_NODE_DIGEST2={RS256:"RSA-SHA256",RS384:"RSA-SHA384",RS512:"RSA-SHA512",PS256:"RSA-SHA256",PS384:"RSA-SHA384",PS512:"RSA-SHA512"};function jwtAlgToNodeAlg(jwtAlg){const nodeAlg=JWT_ALG_TO_NODE_DIGEST2[jwtAlg];if(!nodeAlg){throw new Error(`Unsupported JWT algorithm: ${jwtAlg}`)}return nodeAlg}function isRsaPssAlg2(jwtAlg){return jwtAlg.startsWith("PS")}function toUint8Array(buf){return new Uint8Array(buf.buffer,buf.byteOffset,buf.byteLength)}function toBuffer(data){if(typeof data==="string"){return Buffer.from(data,"utf8")}return Buffer.from(data)}var nodeCrypto={randomUUID(){return import_node_crypto.default.randomUUID()},randomBytes(length){return toUint8Array(import_node_crypto.default.randomBytes(length))},sha256(data){const hash=import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest();return toUint8Array(hash)},sha256Hex(data){return import_node_crypto.default.createHash("sha256").update(toBuffer(data)).digest("hex")},hmacSha256(key,data){const hmac2=import_node_crypto.default.createHmac("sha256",Buffer.from(key)).update(Buffer.from(data)).digest();return toUint8Array(hmac2)},hkdfSha256(ikm,salt,info,length){const ikmBuf=Buffer.from(ikm);const saltBuf=salt.length>0?Buffer.from(salt):Buffer.alloc(32);const prk=import_node_crypto.default.createHmac("sha256",saltBuf).update(ikmBuf).digest();const hashLen=32;const n=Math.ceil(length/hashLen);const chunks=[];let prev=Buffer.alloc(0);for(let i=1;i<=n;i++){prev=import_node_crypto.default.createHmac("sha256",prk).update(Buffer.concat([prev,Buffer.from(info),Buffer.from([i])])).digest();chunks.push(prev)}return toUint8Array(Buffer.concat(chunks).subarray(0,length))},encryptAesGcm(key,plaintext,iv){const cipher=import_node_crypto.default.createCipheriv("aes-256-gcm",Buffer.from(key),Buffer.from(iv));const encrypted=Buffer.concat([cipher.update(Buffer.from(plaintext)),cipher.final()]);const tag=cipher.getAuthTag();return{ciphertext:toUint8Array(encrypted),tag:toUint8Array(tag)}},decryptAesGcm(key,ciphertext,iv,tag){const decipher=import_node_crypto.default.createDecipheriv("aes-256-gcm",Buffer.from(key),Buffer.from(iv));decipher.setAuthTag(Buffer.from(tag));const decrypted=Buffer.concat([decipher.update(Buffer.from(ciphertext)),decipher.final()]);return toUint8Array(decrypted)},timingSafeEqual(a,b){if(a.length!==b.length)return false;return import_node_crypto.default.timingSafeEqual(Buffer.from(a),Buffer.from(b))}};function generateRsaKeyPair3(modulusLength=2048,alg="RS256"){const kid=`rsa-key-${Date.now()}-${import_node_crypto.default.randomBytes(8).toString("hex")}`;const{privateKey,publicKey}=import_node_crypto.default.generateKeyPairSync("rsa",{modulusLength});const exported=publicKey.export({format:"jwk"});const publicJwk={...exported,kid,alg,use:"sig",kty:"RSA"};return{privateKey,publicKey,publicJwk}}function rsaSign(algorithm,data,privateKey,options){const signingKey=options?{key:privateKey,...options}:privateKey;return import_node_crypto.default.sign(algorithm,data,signingKey)}function rsaVerify3(jwtAlg,data,publicJwk,signature){const publicKey=import_node_crypto.default.createPublicKey({key:publicJwk,format:"jwk"});const nodeAlgorithm=jwtAlgToNodeAlg(jwtAlg);const verifyKey=isRsaPssAlg2(jwtAlg)?{key:publicKey,padding:import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,saltLength:import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST}:publicKey;return import_node_crypto.default.verify(nodeAlgorithm,data,verifyKey,signature)}function rsaSignBase64Url3(jwtAlg,data,privateJwk){const privateKey=import_node_crypto.default.createPrivateKey({key:privateJwk,format:"jwk"});const nodeAlgorithm=jwtAlgToNodeAlg(jwtAlg);const buf=Buffer.isBuffer(data)?data:Buffer.from(data);const sig=isRsaPssAlg2(jwtAlg)?rsaSign(nodeAlgorithm,buf,privateKey,{padding:import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,saltLength:import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST}):rsaSign(nodeAlgorithm,buf,privateKey);return sig.toString("base64url")}function rsaVerifySync3(jwtAlg,data,publicJwk,signature){try{const publicKey=import_node_crypto.default.createPublicKey({key:publicJwk,format:"jwk"});const dataBuf=Buffer.isBuffer(data)?data:Buffer.from(data);const sigBuf=Buffer.isBuffer(signature)?signature:Buffer.from(signature);if(jwtAlg==="EdDSA"){return import_node_crypto.default.verify(null,dataBuf,publicKey,sigBuf)}const nodeAlgorithm=jwtAlgToNodeAlg(jwtAlg);const verifyKey=isRsaPssAlg2(jwtAlg)?{key:publicKey,padding:import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,saltLength:import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST}:publicKey;return import_node_crypto.default.verify(nodeAlgorithm,dataBuf,verifyKey,sigBuf)}catch{return false}}function pemToPublicJwk3(pem){const key=import_node_crypto.default.createPublicKey({key:pem,format:"pem"});return key.export({format:"jwk"})}function createSignedJwt(payload,privateKey,kid,alg="RS256"){const header={alg,typ:"JWT",kid};const headerB64=Buffer.from(JSON.stringify(header)).toString("base64url");const payloadB64=Buffer.from(JSON.stringify(payload)).toString("base64url");const signatureInput=`${headerB64}.${payloadB64}`;const nodeAlgorithm=jwtAlgToNodeAlg(alg);const signature=rsaSign(nodeAlgorithm,Buffer.from(signatureInput),privateKey,isRsaPssAlg2(alg)?{padding:import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,saltLength:import_node_crypto.default.constants.RSA_PSS_SALTLEN_DIGEST}:void 0);const signatureB64=signature.toString("base64url");return`${headerB64}.${payloadB64}.${signatureB64}`}}});var index_exports={};__export(index_exports,{DashboardApp:()=>DashboardApp,DashboardConfigToken:()=>DashboardConfigToken,DashboardPlugin:()=>DashboardPlugin,GraphDataProvider:()=>GraphDataProvider,GraphDataProviderToken:()=>GraphDataProviderToken,GraphTool:()=>GraphTool,ListResourcesTool:()=>ListResourcesTool,ListToolsTool:()=>ListToolsTool,ParentScopeToken:()=>ParentScopeToken,dashboardPluginOptionsSchema:()=>dashboardPluginOptionsSchema,default:()=>DashboardPlugin,defaultDashboardPluginOptions:()=>defaultDashboardPluginOptions,isDashboardEnabled:()=>isDashboardEnabled,resetDashboardOptions:()=>resetDashboardOptions});module.exports=__toCommonJS(index_exports);var import_sdk=require("@frontmcp/sdk");var import_lazy_zod=require("@frontmcp/lazy-zod");var cdnConfigSchema=import_lazy_zod.z.object({entrypoint:import_lazy_zod.z.string().optional(),react:import_lazy_zod.z.string().default("https://esm.sh/react@19"),reactDom:import_lazy_zod.z.string().default("https://esm.sh/react-dom@19"),reactDomClient:import_lazy_zod.z.string().default("https://esm.sh/react-dom@19/client"),reactJsxRuntime:import_lazy_zod.z.string().default("https://esm.sh/react@19/jsx-runtime"),reactRouter:import_lazy_zod.z.string().default("https://esm.sh/react-router-dom@7"),xyflow:import_lazy_zod.z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),dagre:import_lazy_zod.z.string().default("https://esm.sh/dagre@0.8.5"),xyflowCss:import_lazy_zod.z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")});var dashboardAuthSchema=import_lazy_zod.z.object({enabled:import_lazy_zod.z.boolean().default(false),token:import_lazy_zod.z.string().min(1).optional()}).refine(value=>!value.enabled||Boolean(value.token),{message:"dashboard auth.enabled requires auth.token to be set",path:["token"]});var dashboardPluginOptionsSchema=import_lazy_zod.z.object({enabled:import_lazy_zod.z.boolean().optional(),basePath:import_lazy_zod.z.string().default("/dashboard"),auth:dashboardAuthSchema.optional().transform(v=>dashboardAuthSchema.parse(v??{})),cdn:cdnConfigSchema.optional().transform(v=>cdnConfigSchema.parse(v??{}))});var defaultDashboardPluginOptions={basePath:"/dashboard",auth:{enabled:false},cdn:{}};function isDashboardEnabled(options){if(options.enabled!==void 0){return options.enabled}return process.env["NODE_ENV"]!=="production"}var publishedOptions;function publishDashboardOptions(options){if(publishedOptions&&!sameConfiguration(publishedOptions,options)){if(!sameAuth(publishedOptions,options)){throw new Error("[frontmcp:dashboard] A second, different dashboard AUTH configuration was registered in this process. Dashboard options are process-wide, so accepting it would apply one server's token to another. Run one dashboard per process, or give both servers the same auth configuration.")}console.warn("[frontmcp:dashboard] A second, different dashboard configuration was registered in this process. Dashboard options are process-wide, so the last one wins for every server here. Run one dashboard per process until per-instance configuration is supported.")}publishedOptions=options}function sameAuth(a,b){return a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token}function sameConfiguration(a,b){return a.basePath===b.basePath&&a.enabled===b.enabled&&a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token&&JSON.stringify(a.cdn??{})===JSON.stringify(b.cdn??{})}function resolveDashboardOptions(fallback={}){if(publishedOptions)return publishedOptions;return dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...fallback})}function resetDashboardOptions(){publishedOptions=void 0}var DashboardConfigToken=Symbol("DashboardConfig");var GraphDataProviderToken=Symbol("GraphDataProvider");var ParentScopeToken=Symbol("ParentScope");var DashboardPlugin=class extends import_sdk.DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(this.options)}static dynamicProviders(options){const parsedOptions=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(parsedOptions);return[{name:"dashboard:config",provide:DashboardConfigToken,useValue:parsedOptions}]}};DashboardPlugin=__decorateClass([(0,import_sdk.Plugin)({name:"dashboard",description:"Visual dashboard for FrontMCP server monitoring and visualization",providers:[]})],DashboardPlugin);var import_sdk7=require("@frontmcp/sdk");var import_sdk2=require("@frontmcp/sdk");var import_lazy_zod2=require("@frontmcp/lazy-zod");var import_lazy_zod3=require("@frontmcp/lazy-zod");var import_event_emitter=__toESM(require_node_event_emitter(),1);var import_aes=require("@noble/ciphers/aes.js");var import_hkdf=require("@noble/hashes/hkdf.js");var import_hmac=require("@noble/hashes/hmac.js");var import_sha2=require("@noble/hashes/sha2.js");var import_utils=require("@noble/hashes/utils.js");var platformCrypto=__toESM(require_node(),1);var import_redos=require("@enclave-vm/ast/redos");var import_meta={};var __frontmcpModule=typeof process!=="undefined"&&typeof process.getBuiltinModule==="function"?process.getBuiltinModule("module"):void 0;var require2=__frontmcpModule?__frontmcpModule.createRequire(import_meta.url||"file:///"):void 0;var __defProp2=Object.defineProperty;var __getOwnPropNames2=Object.getOwnPropertyNames;var __require=(x=>typeof require2!=="undefined"?require2:typeof Proxy!=="undefined"?new Proxy(x,{get:(a,b)=>(typeof require2!=="undefined"?require2:a)[b]}):x)(function(x){if(typeof require2!=="undefined")return require2.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});var __esm=(fn,res,err)=>function __init(){if(err)throw err[0];try{return fn&&(res=(0,fn[__getOwnPropNames2(fn)[0]])(fn=0)),res}catch(e){throw err=[e],e}};var __export2=(target,all)=>{for(var name in all)__defProp2(target,name,{get:all[name],enumerable:true})};function isNode(){return typeof process!=="undefined"&&!!process.versions?.node}function assertNode(feature){if(!isNode()){throw new Error(`${feature} is not supported in the browser. Requires Node.js.`)}}var init_runtime=__esm({"libs/utils/src/crypto/runtime.ts"(){"use strict"}});function getFsp(){if(!_fsp){assertNode("File system operations");_fsp=__require("fs").promises}return _fsp}async function writeFile(p,content,options){const fsp=getFsp();await fsp.writeFile(p,content,{encoding:"utf8",mode:options?.mode})}async function mkdir(p,options){const fsp=getFsp();await fsp.mkdir(p,options)}async function rename(oldPath,newPath){const fsp=getFsp();await fsp.rename(oldPath,newPath)}async function unlink(p){const fsp=getFsp();await fsp.unlink(p)}async function fileExists(p){try{const fsp=getFsp();await fsp.access(p,F_OK);return true}catch{return false}}async function readJSON(jsonPath){try{const fsp=getFsp();const buf=await fsp.readFile(jsonPath,"utf8");return JSON.parse(buf)}catch{return null}}async function ensureDir(p){const fsp=getFsp();await fsp.mkdir(p,{recursive:true})}async function readdir(p){const fsp=getFsp();return fsp.readdir(p)}var _fsp;var _fs;var _spawn;var F_OK;var init_fs=__esm({"libs/utils/src/fs/fs.ts"(){"use strict";init_runtime();_fsp=null;_fs=null;_spawn=null;F_OK=0}});var init_fs2=__esm({"libs/utils/src/fs/index.ts"(){"use strict";init_fs()}});function isRsaPssAlg(jwtAlg){return jwtAlg.startsWith("PS")}function jwtAlgToWebCryptoAlg(jwtAlg){const webAlg=JWT_ALG_TO_WEB_CRYPTO[jwtAlg];if(!webAlg){throw new Error(`Unsupported JWT algorithm for WebCrypto: ${jwtAlg}`)}return webAlg}var JWT_ALG_TO_NODE_DIGEST;var JWT_ALG_TO_WEB_CRYPTO;var init_jwt_alg=__esm({"libs/utils/src/crypto/jwt-alg.ts"(){"use strict";JWT_ALG_TO_NODE_DIGEST={RS256:"RSA-SHA256",RS384:"RSA-SHA384",RS512:"RSA-SHA512",PS256:"RSA-SHA256",PS384:"RSA-SHA384",PS512:"RSA-SHA512"};JWT_ALG_TO_WEB_CRYPTO={RS256:"SHA-256",RS384:"SHA-384",RS512:"SHA-512",PS256:"SHA-256",PS384:"SHA-384",PS512:"SHA-512"}}});var textEncoder;var textDecoder;var EncryptedBlobError;var init_encrypted_blob=__esm({"libs/utils/src/crypto/encrypted-blob.ts"(){"use strict";init_crypto();textEncoder=new TextEncoder;textDecoder=new TextDecoder;EncryptedBlobError=class extends Error{constructor(message){super(message);this.name="EncryptedBlobError"}}}});var MIN_CODE_VERIFIER_LENGTH;var MAX_CODE_VERIFIER_LENGTH;var DEFAULT_CODE_VERIFIER_LENGTH;var PkceError;var init_pkce=__esm({"libs/utils/src/crypto/pkce/pkce.ts"(){"use strict";init_crypto();MIN_CODE_VERIFIER_LENGTH=43;MAX_CODE_VERIFIER_LENGTH=128;DEFAULT_CODE_VERIFIER_LENGTH=64;PkceError=class extends Error{constructor(message){super(message);this.name="PkceError"}}}});var init_pkce2=__esm({"libs/utils/src/crypto/pkce/index.ts"(){"use strict";init_pkce()}});var secretDataSchema;var MAX_CLOCK_DRIFT_MS;var MAX_SECRET_AGE_MS;var init_schema=__esm({"libs/utils/src/crypto/secret-persistence/schema.ts"(){"use strict";secretDataSchema=import_lazy_zod2.z.object({secret:import_lazy_zod2.z.string().base64url().length(43),createdAt:import_lazy_zod2.z.number().positive().int(),version:import_lazy_zod2.z.number().positive().int()}).strict();MAX_CLOCK_DRIFT_MS=6e4;MAX_SECRET_AGE_MS=100*365*24*60*60*1e3}});var DEFAULT_SECRET_BYTES;var DEFAULT_SECRET_DIR;var noop;var secretCache;var generationPromises;var init_persistence=__esm({"libs/utils/src/crypto/secret-persistence/persistence.ts"(){"use strict";init_fs2();init_crypto();init_schema();DEFAULT_SECRET_BYTES=32;DEFAULT_SECRET_DIR=".frontmcp";noop=()=>{};secretCache=new Map;generationPromises=new Map}});var init_secret_persistence=__esm({"libs/utils/src/crypto/secret-persistence/index.ts"(){"use strict";init_schema();init_persistence()}});var init_hmac_signing=__esm({"libs/utils/src/crypto/hmac-signing.ts"(){"use strict";init_crypto();init_crypto();init_crypto()}});function validateKeyData(data){const result=anyKeyDataSchema.safeParse(data);if(!result.success){return{valid:false,error:result.error.issues[0]?.message??"Invalid key structure"}}const parsed=result.data;const now=Date.now();if(parsed.createdAt>now+MAX_CLOCK_DRIFT_MS2){return{valid:false,error:"createdAt is in the future"}}if(parsed.createdAt<now-MAX_KEY_AGE_MS){return{valid:false,error:"createdAt is too old"}}return{valid:true,data:parsed}}function isSecretKeyData(data){return data.type==="secret"}function isAsymmetricKeyData(data){return data.type==="asymmetric"}var MAX_CLOCK_DRIFT_MS2;var MAX_KEY_AGE_MS;var asymmetricAlgSchema;var baseKeyDataSchema;var jsonWebKeySchema;var secretKeyDataSchema;var asymmetricKeyDataSchema;var anyKeyDataSchema;var init_schemas=__esm({"libs/utils/src/crypto/key-persistence/schemas.ts"(){"use strict";MAX_CLOCK_DRIFT_MS2=6e4;MAX_KEY_AGE_MS=100*365*24*60*60*1e3;asymmetricAlgSchema=import_lazy_zod3.z.enum(["RS256","RS384","RS512","ES256","ES384","ES512"]);baseKeyDataSchema=import_lazy_zod3.z.object({kid:import_lazy_zod3.z.string().min(1),createdAt:import_lazy_zod3.z.number().positive().int(),version:import_lazy_zod3.z.number().positive().int()});jsonWebKeySchema=import_lazy_zod3.z.object({kty:import_lazy_zod3.z.string().optional(),use:import_lazy_zod3.z.string().optional(),alg:import_lazy_zod3.z.string().optional(),kid:import_lazy_zod3.z.string().optional(),n:import_lazy_zod3.z.string().optional(),e:import_lazy_zod3.z.string().optional(),d:import_lazy_zod3.z.string().optional(),p:import_lazy_zod3.z.string().optional(),q:import_lazy_zod3.z.string().optional(),dp:import_lazy_zod3.z.string().optional(),dq:import_lazy_zod3.z.string().optional(),qi:import_lazy_zod3.z.string().optional(),x:import_lazy_zod3.z.string().optional(),y:import_lazy_zod3.z.string().optional(),crv:import_lazy_zod3.z.string().optional()}).passthrough();secretKeyDataSchema=baseKeyDataSchema.extend({type:import_lazy_zod3.z.literal("secret"),secret:import_lazy_zod3.z.string().min(1),bytes:import_lazy_zod3.z.number().positive().int()});asymmetricKeyDataSchema=baseKeyDataSchema.extend({type:import_lazy_zod3.z.literal("asymmetric"),alg:asymmetricAlgSchema,privateKey:jsonWebKeySchema,publicJwk:import_lazy_zod3.z.object({keys:import_lazy_zod3.z.array(jsonWebKeySchema).min(1)})});anyKeyDataSchema=import_lazy_zod3.z.discriminatedUnion("type",[secretKeyDataSchema,asymmetricKeyDataSchema])}});var KeyPersistence;var init_key_persistence=__esm({"libs/utils/src/crypto/key-persistence/key-persistence.ts"(){"use strict";init_crypto();init_schemas();KeyPersistence=class{storage;cache=new Map;enableCache;throwOnInvalid;constructor(options){this.storage=options.storage;this.enableCache=options.enableCache??true;this.throwOnInvalid=options.throwOnInvalid??false}async get(kid){if(this.enableCache){const cached=this.cache.get(kid);if(cached)return cached}const raw=await this.storage.get(kid);if(!raw)return null;let parsed;try{parsed=JSON.parse(raw)}catch{if(this.throwOnInvalid){throw new Error(`Invalid JSON for key "${kid}"`)}return null}const validation=validateKeyData(parsed);if(!validation.valid){if(this.throwOnInvalid){throw new Error(`Invalid key data for "${kid}": ${validation.error}`)}return null}const key=validation.data;if(this.enableCache){this.cache.set(kid,key)}return key}async getSecret(kid){const key=await this.get(kid);if(!key||!isSecretKeyData(key))return null;return key}async getAsymmetric(kid){const key=await this.get(kid);if(!key||!isAsymmetricKeyData(key))return null;return key}async set(key){const validation=validateKeyData(key);if(!validation.valid){throw new Error(`Invalid key data: ${validation.error}`)}await this.storage.set(key.kid,JSON.stringify(key,null,2));if(this.enableCache){this.cache.set(key.kid,key)}}async delete(kid){this.cache.delete(kid);return this.storage.delete(kid)}async has(kid){if(this.enableCache&&this.cache.has(kid)){return true}return this.storage.exists(kid)}async list(){return this.storage.keys()}async getOrCreateSecret(kid,options){const existing=await this.getSecret(kid);if(existing){return existing}const bytes=options?.bytes??32;const secret={type:"secret",kid,secret:base64urlEncode(randomBytes(bytes)),bytes,createdAt:Date.now(),version:1};await this.set(secret);return secret}clearCache(){this.cache.clear()}clearCacheFor(kid){this.cache.delete(kid)}isCached(kid){return this.cache.has(kid)}getAdapter(){return this.storage}}}});var StorageError;var StorageConnectionError;var StorageOperationError;var StorageNotSupportedError;var StorageConfigError;var StorageTTLError;var StoragePatternError;var StorageNotConnectedError;var EncryptedStorageError;var init_errors=__esm({"libs/utils/src/storage/errors.ts"(){"use strict";StorageError=class extends Error{cause;constructor(message,cause){super(message);this.cause=cause;this.name="StorageError";Object.setPrototypeOf(this,new.target.prototype);if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}}};StorageConnectionError=class extends StorageError{constructor(message,cause,backend){const fullMessage=cause?`${message}: ${cause.message}`:message;super(fullMessage,cause);this.backend=backend;this.name="StorageConnectionError"}backend};StorageOperationError=class extends StorageError{constructor(operation,key,message,cause){super(`${operation} failed for key "${key}": ${message}`,cause);this.operation=operation;this.key=key;this.name="StorageOperationError"}operation;key};StorageNotSupportedError=class extends StorageError{constructor(operation,backend,suggestion){const msg=suggestion?`${operation} is not supported by ${backend}. ${suggestion}`:`${operation} is not supported by ${backend}`;super(msg);this.operation=operation;this.backend=backend;this.name="StorageNotSupportedError"}operation;backend};StorageConfigError=class extends StorageError{constructor(backend,message){super(`Invalid ${backend} configuration: ${message}`);this.backend=backend;this.name="StorageConfigError"}backend};StorageTTLError=class extends StorageError{constructor(ttl,message){super(message??`Invalid TTL value: ${ttl}. TTL must be a positive integer.`);this.ttl=ttl;this.name="StorageTTLError"}ttl};StoragePatternError=class extends StorageError{constructor(pattern,message){super(`Invalid pattern "${pattern}": ${message}`);this.pattern=pattern;this.name="StoragePatternError"}pattern};StorageNotConnectedError=class extends StorageError{constructor(backend){super(`Storage backend "${backend}" is not connected. Call connect() first.`);this.backend=backend;this.name="StorageNotConnectedError"}backend};EncryptedStorageError=class extends StorageError{constructor(message){super(message);this.name="EncryptedStorageError"}}}});function globToRegex(pattern){if(pattern.length>MAX_PATTERN_LENGTH){throw new StoragePatternError(pattern.substring(0,50)+"...",`Pattern exceeds maximum length of ${MAX_PATTERN_LENGTH} characters`)}const wildcardCount=(pattern.match(/[*?]/g)||[]).length;if(wildcardCount>MAX_WILDCARDS){throw new StoragePatternError(pattern,`Pattern has too many wildcards (max: ${MAX_WILDCARDS})`)}if(pattern===""||pattern==="*"){return/^.*$/}let regexStr="^";let prevChar="";for(let i=0;i<pattern.length;i++){const char=pattern[i];switch(char){case"*":if(prevChar!=="*"){regexStr+=".*"}break;case"?":regexStr+=".";break;default:regexStr+=char.replace(REGEX_SPECIAL_CHARS,"\\$&")}prevChar=char}regexStr+="$";try{return new RegExp(regexStr)}catch{throw new StoragePatternError(pattern,"Failed to compile pattern to regex")}}function matchesPattern(key,pattern){const regex=globToRegex(pattern);return regex.test(key)}var MAX_PATTERN_LENGTH;var MAX_WILDCARDS;var REGEX_SPECIAL_CHARS;var init_pattern=__esm({"libs/utils/src/storage/utils/pattern.ts"(){"use strict";init_errors();MAX_PATTERN_LENGTH=500;MAX_WILDCARDS=20;REGEX_SPECIAL_CHARS=/[.+^${}()|[\]\\]/g}});function validateTTL(ttlSeconds){if(typeof ttlSeconds!=="number"){throw new StorageTTLError(ttlSeconds,`TTL must be a number, got ${typeof ttlSeconds}`)}if(!Number.isFinite(ttlSeconds)){throw new StorageTTLError(ttlSeconds,"TTL must be a finite number")}if(!Number.isInteger(ttlSeconds)){throw new StorageTTLError(ttlSeconds,`TTL must be an integer, got ${ttlSeconds}`)}if(ttlSeconds<=0){throw new StorageTTLError(ttlSeconds,`TTL must be positive, got ${ttlSeconds}`)}if(ttlSeconds>MAX_TTL_SECONDS){throw new StorageTTLError(ttlSeconds,`TTL exceeds maximum of ${MAX_TTL_SECONDS} seconds`)}}function validateOptionalTTL(ttlSeconds){if(ttlSeconds!==void 0){validateTTL(ttlSeconds)}}function ttlToExpiresAt(ttlSeconds){return Date.now()+ttlSeconds*1e3}function expiresAtToTTL(expiresAt){const remaining=Math.ceil((expiresAt-Date.now())/1e3);return Math.max(0,remaining)}function isExpired(expiresAt){if(expiresAt===void 0)return false;return Date.now()>=expiresAt}var MAX_TTL_SECONDS;var init_ttl=__esm({"libs/utils/src/storage/utils/ttl.ts"(){"use strict";init_errors();MAX_TTL_SECONDS=31536e5}});var BaseStorageAdapter;var init_base=__esm({"libs/utils/src/storage/adapters/base.ts"(){"use strict";init_errors();init_ttl();BaseStorageAdapter=class{connected=false;ensureConnected(){if(!this.connected){throw new StorageNotConnectedError(this.backendName)}}async set(key,value,options){this.ensureConnected();this.validateSetOptions(options);return this.doSet(key,value,options)}async mget(keys){this.ensureConnected();return Promise.all(keys.map(k=>this.get(k)))}async mset(entries){this.ensureConnected();for(const entry of entries){this.validateSetOptions(entry.options)}await Promise.all(entries.map(e=>this.doSet(e.key,e.value,e.options)))}async mdelete(keys){this.ensureConnected();const results=await Promise.all(keys.map(k=>this.delete(k)));return results.filter(Boolean).length}async count(pattern="*"){const matchedKeys=await this.keys(pattern);return matchedKeys.length}async deleteIfEquals(_key,_expectedValue){throw new StorageNotSupportedError("deleteIfEquals",this.backendName,"Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-delete.")}async expireIfEquals(_key,_expectedValue,_ttlSeconds){throw new StorageNotSupportedError("expireIfEquals",this.backendName,"Use the memory, Redis, Upstash or Vercel KV adapter for an atomic compare-and-expire.")}supportsPubSub(){return false}async publish(_channel,_message){throw new StorageNotSupportedError("publish",this.backendName,this.getPubSubSuggestion())}async subscribe(_channel,_handler){throw new StorageNotSupportedError("subscribe",this.backendName,this.getPubSubSuggestion())}getPubSubSuggestion(){return"Use Redis or Upstash adapter for pub/sub support."}validateSetOptions(options){if(!options)return;validateOptionalTTL(options.ttlSeconds);if(options.ifNotExists&&options.ifExists){throw new Error("ifNotExists and ifExists are mutually exclusive")}}}}});var DEFAULT_SWEEP_INTERVAL_SECONDS;var MAX_TIMEOUT_MS;var MemoryStorageAdapter;var init_memory=__esm({"libs/utils/src/storage/adapters/memory.ts"(){"use strict";init_errors();init_pattern();init_ttl();init_base();DEFAULT_SWEEP_INTERVAL_SECONDS=60;MAX_TIMEOUT_MS=2147483647;MemoryStorageAdapter=class extends BaseStorageAdapter{backendName="memory";store=new Map;emitter=new import_event_emitter.EventEmitter;sweepInterval;options;accessOrder=[];constructor(options={}){super();this.options={enableSweeper:options.enableSweeper??true,sweepIntervalSeconds:options.sweepIntervalSeconds??DEFAULT_SWEEP_INTERVAL_SECONDS,maxEntries:options.maxEntries??0};this.emitter.setMaxListeners(1e3)}async connect(){if(this.connected)return;this.connected=true;if(this.options.enableSweeper&&this.options.sweepIntervalSeconds>0){this.startSweeper()}}async disconnect(){if(!this.connected)return;this.stopSweeper();this.clearAllTimeouts();this.store.clear();this.accessOrder=[];this.emitter.removeAllListeners();this.connected=false}async ping(){return this.connected}async get(key){this.ensureConnected();const entry=this.store.get(key);if(!entry)return null;if(isExpired(entry.expiresAt)){this.deleteEntry(key);return null}this.touchLRU(key);return entry.value}async doSet(key,value,options){const existingEntry=this.store.get(key);const exists=existingEntry!==void 0&&!isExpired(existingEntry.expiresAt);if(options?.ifNotExists&&exists){return}if(options?.ifExists&&!exists){return}this.clearEntryTimeout(key);const entry={value};if(options?.ttlSeconds){entry.expiresAt=ttlToExpiresAt(options.ttlSeconds);const ttlMs=options.ttlSeconds*1e3;if(ttlMs<MAX_TIMEOUT_MS){entry.timeout=setTimeout(()=>{this.deleteEntry(key)},ttlMs);if(entry.timeout.unref){entry.timeout.unref()}}}if(this.options.maxEntries>0&&!this.store.has(key)){while(this.store.size>=this.options.maxEntries){this.evictOldest()}}this.store.set(key,entry);this.touchLRU(key)}async delete(key){this.ensureConnected();return this.deleteEntry(key)}async deleteIfEquals(key,expectedValue){this.ensureConnected();const entry=this.store.get(key);if(!entry||isExpired(entry.expiresAt)||entry.value!==expectedValue)return false;return this.deleteEntry(key)}async expireIfEquals(key,expectedValue,ttlSeconds){this.ensureConnected();const entry=this.store.get(key);if(!entry||isExpired(entry.expiresAt)||entry.value!==expectedValue)return false;return this.expire(key,ttlSeconds)}async exists(key){this.ensureConnected();const entry=this.store.get(key);if(!entry)return false;if(isExpired(entry.expiresAt)){this.deleteEntry(key);return false}return true}async expire(key,ttlSeconds){this.ensureConnected();validateTTL(ttlSeconds);const entry=this.store.get(key);if(!entry||isExpired(entry.expiresAt)){return false}this.clearEntryTimeout(key);entry.expiresAt=ttlToExpiresAt(ttlSeconds);const ttlMs=ttlSeconds*1e3;if(ttlMs<MAX_TIMEOUT_MS){entry.timeout=setTimeout(()=>{this.deleteEntry(key)},ttlMs);if(entry.timeout.unref){entry.timeout.unref()}}return true}async ttl(key){this.ensureConnected();const entry=this.store.get(key);if(!entry)return null;if(isExpired(entry.expiresAt)){this.deleteEntry(key);return null}if(entry.expiresAt===void 0){return-1}return expiresAtToTTL(entry.expiresAt)}async keys(pattern="*"){this.ensureConnected();const regex=globToRegex(pattern);const result=[];for(const[key,entry]of this.store){if(isExpired(entry.expiresAt)){this.deleteEntry(key);continue}if(regex.test(key)){result.push(key)}}return result}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(key,amount){this.ensureConnected();const entry=this.store.get(key);let currentValue=0;if(entry&&!isExpired(entry.expiresAt)){const parsed=parseInt(entry.value,10);if(isNaN(parsed)){throw new StorageOperationError("incrBy",key,"Value is not an integer")}currentValue=parsed}const newValue=currentValue+amount;const newEntry={value:String(newValue)};if(entry?.expiresAt&&!isExpired(entry.expiresAt)){newEntry.expiresAt=entry.expiresAt;const remainingMs=entry.expiresAt-Date.now();if(remainingMs>0&&remainingMs<MAX_TIMEOUT_MS){this.clearEntryTimeout(key);newEntry.timeout=setTimeout(()=>{this.deleteEntry(key)},remainingMs);if(newEntry.timeout.unref){newEntry.timeout.unref()}}}this.store.set(key,newEntry);this.touchLRU(key);return newValue}supportsPubSub(){return true}async publish(channel,message){this.ensureConnected();return this.emitter.listenerCount(channel)>0?(this.emitter.emit(channel,message,channel),this.emitter.listenerCount(channel)):0}async subscribe(channel,handler){this.ensureConnected();const wrappedHandler=(message,ch)=>{handler(message,ch)};this.emitter.on(channel,wrappedHandler);return async()=>{this.emitter.off(channel,wrappedHandler)}}deleteEntry(key){const entry=this.store.get(key);if(!entry)return false;this.clearEntryTimeout(key);this.store.delete(key);this.removeLRU(key);return true}clearEntryTimeout(key){const entry=this.store.get(key);if(entry?.timeout){clearTimeout(entry.timeout);entry.timeout=void 0}}clearAllTimeouts(){for(const[key]of this.store){this.clearEntryTimeout(key)}}touchLRU(key){if(this.options.maxEntries<=0)return;const idx=this.accessOrder.indexOf(key);if(idx!==-1){this.accessOrder.splice(idx,1)}this.accessOrder.push(key)}removeLRU(key){if(this.options.maxEntries<=0)return;const idx=this.accessOrder.indexOf(key);if(idx!==-1){this.accessOrder.splice(idx,1)}}evictOldest(){if(this.accessOrder.length===0)return;const oldestKey=this.accessOrder.shift();if(oldestKey){this.deleteEntry(oldestKey)}}startSweeper(){this.sweepInterval=setInterval(()=>{this.sweep()},this.options.sweepIntervalSeconds*1e3);if(this.sweepInterval.unref){this.sweepInterval.unref()}}stopSweeper(){if(this.sweepInterval){clearInterval(this.sweepInterval);this.sweepInterval=void 0}}sweep(){const now=Date.now();for(const[key,entry]of this.store){if(entry.expiresAt&&now>=entry.expiresAt){this.deleteEntry(key)}}}getStats(){return{size:this.store.size,maxEntries:this.options.maxEntries,sweeperActive:this.sweepInterval!==void 0}}}}});var filesystem_exports={};__export2(filesystem_exports,{FileSystemStorageAdapter:()=>FileSystemStorageAdapter});var FileSystemStorageAdapter;var init_filesystem=__esm({"libs/utils/src/storage/adapters/filesystem.ts"(){"use strict";init_base();init_errors();init_pattern();init_fs2();init_crypto();FileSystemStorageAdapter=class extends BaseStorageAdapter{backendName="filesystem";baseDir;extension;dirMode;fileMode;constructor(options){super();this.baseDir=options.baseDir;this.extension=options.extension??".json";this.dirMode=options.dirMode??448;this.fileMode=options.fileMode??384}async connect(){if(this.connected)return;await ensureDir(this.baseDir);try{await mkdir(this.baseDir,{recursive:true,mode:this.dirMode})}catch{}this.connected=true}async disconnect(){if(!this.connected)return;this.connected=false}async ping(){if(!this.connected)return false;try{return await fileExists(this.baseDir)}catch{return false}}async get(key){this.ensureConnected();const filePath=this.keyToPath(key);try{const entry=await readJSON(filePath);if(!entry)return null;if(entry.expiresAt&&Date.now()>=entry.expiresAt){await this.deleteFile(filePath);return null}return entry.value}catch(e){const error=e;if(error.code==="ENOENT"){return null}throw e}}async doSet(key,value,options){const filePath=this.keyToPath(key);const existingEntry=await this.readEntry(filePath);const exists=existingEntry!==null&&!this.isExpired(existingEntry);if(options?.ifNotExists&&exists){return}if(options?.ifExists&&!exists){return}const entry={value};if(options?.ttlSeconds){entry.expiresAt=Date.now()+options.ttlSeconds*1e3}await this.atomicWrite(filePath,entry)}async delete(key){this.ensureConnected();const filePath=this.keyToPath(key);return this.deleteFile(filePath)}async exists(key){this.ensureConnected();const filePath=this.keyToPath(key);try{const entry=await this.readEntry(filePath);if(!entry)return false;if(this.isExpired(entry)){await this.deleteFile(filePath);return false}return true}catch{return false}}async expire(key,ttlSeconds){this.ensureConnected();const filePath=this.keyToPath(key);const entry=await this.readEntry(filePath);if(!entry||this.isExpired(entry)){return false}entry.expiresAt=Date.now()+ttlSeconds*1e3;await this.atomicWrite(filePath,entry);return true}async ttl(key){this.ensureConnected();const filePath=this.keyToPath(key);const entry=await this.readEntry(filePath);if(!entry)return null;if(this.isExpired(entry)){await this.deleteFile(filePath);return null}if(entry.expiresAt===void 0){return-1}return Math.max(0,Math.ceil((entry.expiresAt-Date.now())/1e3))}async keys(pattern="*"){this.ensureConnected();const regex=globToRegex(pattern);const result=[];try{const files=await readdir(this.baseDir);for(const file of files){if(!file.endsWith(this.extension))continue;const key=this.pathToKey(file);if(!regex.test(key))continue;const filePath=this.keyToPath(key);const entry=await this.readEntry(filePath);if(entry&&!this.isExpired(entry)){result.push(key)}else if(entry&&this.isExpired(entry)){await this.deleteFile(filePath)}}}catch(e){const error=e;if(error.code==="ENOENT"){return[]}throw e}return result}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(key,amount){this.ensureConnected();const filePath=this.keyToPath(key);const entry=await this.readEntry(filePath);let currentValue=0;if(entry&&!this.isExpired(entry)){const parsed=parseInt(entry.value,10);if(isNaN(parsed)){throw new StorageOperationError("incrBy",key,"Value is not an integer")}currentValue=parsed}const newValue=currentValue+amount;const newEntry={value:String(newValue)};if(entry?.expiresAt&&!this.isExpired(entry)){newEntry.expiresAt=entry.expiresAt}await this.atomicWrite(filePath,newEntry);return newValue}keyToPath(key){const safeKey=this.encodeKey(key);return`${this.baseDir}/${safeKey}${this.extension}`}pathToKey(filename){const safeKey=filename.slice(0,-this.extension.length);return this.decodeKey(safeKey)}encodeKey(key){return encodeURIComponent(key).replace(/\./g,"%2E")}decodeKey(encoded){return decodeURIComponent(encoded)}async readEntry(filePath){try{return await readJSON(filePath)}catch{return null}}isExpired(entry){return entry.expiresAt!==void 0&&Date.now()>=entry.expiresAt}async deleteFile(filePath){try{await unlink(filePath);return true}catch(e){const error=e;if(error.code==="ENOENT"){return false}throw e}}async atomicWrite(filePath,entry){const tempSuffix=bytesToHex(randomBytes(8));const tempPath=`${filePath}.${tempSuffix}.tmp`;try{const content=JSON.stringify(entry,null,2);await writeFile(tempPath,content,{mode:this.fileMode});await rename(tempPath,filePath)}catch(e){try{await unlink(tempPath)}catch{}throw e}}getPubSubSuggestion(){return"FileSystem adapter does not support pub/sub. Use Redis or Memory adapter for pub/sub support."}}}});var indexeddb_exports={};__export2(indexeddb_exports,{IndexedDBStorageAdapter:()=>IndexedDBStorageAdapter});var IndexedDBStorageAdapter;var init_indexeddb=__esm({"libs/utils/src/storage/adapters/indexeddb.ts"(){"use strict";init_base();init_pattern();IndexedDBStorageAdapter=class extends BaseStorageAdapter{backendName="indexeddb";dbName;storeName;prefix;db=null;constructor(options){super();this.dbName=options?.dbName??"frontmcp";this.storeName=options?.storeName??"kv";this.prefix=options?.prefix??"frontmcp:"}assertAvailable(){if(typeof indexedDB==="undefined"){throw new Error("IndexedDB is not available in this environment")}}key(k){return`${this.prefix}${k}`}stripPrefix(fullKey){return fullKey.slice(this.prefix.length)}async connect(){this.assertAvailable();if(this.db){this.connected=true;return}this.db=await new Promise((resolve2,reject)=>{const request=indexedDB.open(this.dbName,1);request.onupgradeneeded=()=>{const db=request.result;if(!db.objectStoreNames.contains(this.storeName)){db.createObjectStore(this.storeName,{keyPath:"k"})}};request.onsuccess=()=>resolve2(request.result);request.onerror=()=>reject(request.error)});this.connected=true}async disconnect(){if(this.db){this.db.close();this.db=null}this.connected=false}async ping(){return this.db!==null&&this.connected}getStore(mode){if(!this.db){throw new Error("IndexedDB adapter is not connected. Call connect() first.")}const tx=this.db.transaction(this.storeName,mode);return tx.objectStore(this.storeName)}idbRequest(request){return new Promise((resolve2,reject)=>{request.onsuccess=()=>resolve2(request.result);request.onerror=()=>reject(request.error)})}idbTransaction(store){return new Promise((resolve2,reject)=>{const tx=store.transaction;tx.oncomplete=()=>resolve2();tx.onerror=()=>reject(tx.error)})}async get(key){this.ensureConnected();const store=this.getStore("readonly");const entry=await this.idbRequest(store.get(this.key(key)));if(!entry)return null;if(entry.e&&entry.e<Date.now()){const writeStore=this.getStore("readwrite");writeStore.delete(this.key(key));return null}return entry.v}async doSet(key,value,options){if(options?.ifNotExists){const existing=await this.get(key);if(existing!==null)return}if(options?.ifExists){const existing=await this.get(key);if(existing===null)return}const entry={k:this.key(key),v:value};if(options?.ttlSeconds){entry.e=Date.now()+options.ttlSeconds*1e3}const store=this.getStore("readwrite");await this.idbRequest(store.put(entry))}async delete(key){this.ensureConnected();const existing=await this.get(key);if(existing===null)return false;const store=this.getStore("readwrite");await this.idbRequest(store.delete(this.key(key)));return true}async exists(key){return await this.get(key)!==null}async expire(key,ttlSeconds){this.ensureConnected();const value=await this.get(key);if(value===null)return false;await this.doSet(key,value,{ttlSeconds});return true}async ttl(key){this.ensureConnected();const store=this.getStore("readonly");const entry=await this.idbRequest(store.get(this.key(key)));if(!entry)return null;if(entry.e){const remaining=Math.ceil((entry.e-Date.now())/1e3);return remaining>0?remaining:null}return-1}async keys(pattern){this.ensureConnected();const store=this.getStore("readonly");const allKeys=await this.idbRequest(store.getAllKeys());const now=Date.now();const result=[];for(const fullKey of allKeys){const keyStr=String(fullKey);if(!keyStr.startsWith(this.prefix))continue;const key=this.stripPrefix(keyStr);if(pattern&&pattern!=="*"&&!this.matchPattern(key,pattern))continue;const entry=await this.idbRequest(store.get(keyStr));if(entry&&(!entry.e||entry.e>=now)){result.push(key)}}return result}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(key,amount){this.ensureConnected();const current=await this.get(key);const value=current!==null?parseInt(current,10)+amount:amount;if(Number.isNaN(value)){throw new Error(`Value at "${key}" is not an integer`)}await this.doSet(key,String(value));return value}matchPattern(key,pattern){return matchesPattern(key,pattern)}}}});var localstorage_exports={};__export2(localstorage_exports,{LocalStorageAdapter:()=>LocalStorageAdapter});var LocalStorageAdapter;var init_localstorage=__esm({"libs/utils/src/storage/adapters/localstorage.ts"(){"use strict";init_base();init_crypto();init_pattern();LocalStorageAdapter=class extends BaseStorageAdapter{backendName="localstorage";prefix;encryptionKey;allowPlaintext;encoder=new TextEncoder;decoder=new TextDecoder;constructor(options){super();this.prefix=options?.prefix??"frontmcp:";this.allowPlaintext=options?.allowPlaintext??true;if(options?.encryptionKey){if(options.encryptionKey.length!==32){throw new Error(`encryptionKey must be exactly 32 bytes, got ${options.encryptionKey.length}`)}this.encryptionKey=options.encryptionKey}}assertAvailable(){if(typeof localStorage==="undefined"){throw new Error("localStorage is not available in this environment")}}key(k){return`${this.prefix}${k}`}stripPrefix(fullKey){return fullKey.slice(this.prefix.length)}async connect(){this.assertAvailable();const probeKey=this.key("__probe__");try{localStorage.setItem(probeKey,"1");const read=localStorage.getItem(probeKey);if(read!=="1"){throw new Error("localStorage probe: read-back mismatch")}}catch(err){throw new Error(`localStorage is not writable: ${err instanceof Error?err.message:String(err)}`)}finally{try{localStorage.removeItem(probeKey)}catch{}}this.connected=true}async disconnect(){this.connected=false}async ping(){const probeKey=this.key("__probe__");try{this.assertAvailable();localStorage.setItem(probeKey,"1");return localStorage.getItem(probeKey)==="1"}catch{return false}finally{try{localStorage.removeItem(probeKey)}catch{}}}async get(key){const raw=localStorage.getItem(this.key(key));if(raw===null)return null;try{const entry=JSON.parse(raw);if(entry.e&&entry.e<Date.now()){localStorage.removeItem(this.key(key));return null}if(entry._enc){if(!this.encryptionKey)return null;const iv=base64urlDecode(entry._enc.iv);const tag=base64urlDecode(entry._enc.tag);const ciphertext=base64urlDecode(entry._enc.data);const plainBytes=decryptAesGcm(this.encryptionKey,ciphertext,iv,tag);return this.decoder.decode(plainBytes)}return entry.v}catch{return null}}async set(key,value,options){if(options?.ifNotExists){const existing=await this.get(key);if(existing!==null)return}if(options?.ifExists){const existing=await this.get(key);if(existing===null)return}await this.doSet(key,value,options)}async doSet(key,value,options){const entry={v:""};if(options?.ttlSeconds){entry.e=Date.now()+options.ttlSeconds*1e3}if(this.encryptionKey){const iv=randomBytes(12);const plainBytes=this.encoder.encode(value);const{ciphertext,tag}=encryptAesGcm(this.encryptionKey,plainBytes,iv);entry._enc={alg:"A256GCM",iv:base64urlEncode(iv),tag:base64urlEncode(tag),data:base64urlEncode(ciphertext)}}else{if(!this.allowPlaintext){throw new Error("Plaintext storage is disabled. Provide an encryptionKey or set allowPlaintext: true.")}entry.v=value}localStorage.setItem(this.key(key),JSON.stringify(entry))}async delete(key){const existed=await this.get(key)!==null;localStorage.removeItem(this.key(key));return existed}async exists(key){return await this.get(key)!==null}async mget(keys){return Promise.all(keys.map(k=>this.get(k)))}async mset(entries){for(const entry of entries){await this.set(entry.key,entry.value,entry.options)}}async mdelete(keys){let count=0;for(const key of keys){if(await this.delete(key))count++}return count}async expire(key,ttlSeconds){const value=await this.get(key);if(value===null)return false;await this.set(key,value,{ttlSeconds});return true}async ttl(key){const raw=localStorage.getItem(this.key(key));if(raw===null)return null;try{const entry=JSON.parse(raw);if(!entry.e)return-1;const remaining=Math.ceil((entry.e-Date.now())/1e3);return remaining>0?remaining:null}catch{return null}}async keys(pattern){const result=[];const candidates=[];for(let i=0;i<localStorage.length;i++){const fullKey=localStorage.key(i);if(fullKey&&fullKey.startsWith(this.prefix)){candidates.push(this.stripPrefix(fullKey))}}for(const key of candidates){if(!pattern||pattern==="*"||this.matchPattern(key,pattern)){if(await this.get(key)!==null){result.push(key)}}}return result}async count(pattern){return(await this.keys(pattern)).length}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(key,amount){if(!Number.isInteger(amount)){throw new Error(`amount must be an integer, got ${amount}`)}const current=await this.get(key);if(current!==null&&!/^-?\d+$/.test(current)){throw new Error(`Value at "${key}" is not an integer`)}const value=current!==null?parseInt(current,10)+amount:amount;await this.set(key,String(value));return value}async publish(){throw new Error("LocalStorage adapter does not support pub/sub")}async subscribe(_channel,_handler){throw new Error("LocalStorage adapter does not support pub/sub")}supportsPubSub(){return false}matchPattern(key,pattern){return matchesPattern(key,pattern)}}}});var DEFAULT_BASE_DIR;var LS_HKDF_IKM;var INSTALL_SALT_KEY;var LEGACY_SALT_KEY;var init_factory=__esm({"libs/utils/src/crypto/key-persistence/factory.ts"(){"use strict";init_runtime();init_memory();init_key_persistence();init_crypto();DEFAULT_BASE_DIR=".frontmcp/keys";LS_HKDF_IKM=new TextEncoder().encode("frontmcp:localstorage:v1");INSTALL_SALT_KEY="__frontmcp_internal__:install_salt";LEGACY_SALT_KEY="frontmcp:_install_salt"}});var init_key_persistence2=__esm({"libs/utils/src/crypto/key-persistence/index.ts"(){"use strict";init_schemas();init_key_persistence();init_factory()}});var browser_exports={};__export2(browser_exports,{browserCrypto:()=>browserCrypto,cryptoProvider:()=>browserCrypto,generateRsaKeyPair:()=>generateRsaKeyPair2,isRsaPssAlg:()=>isRsaPssAlg,jwtAlgToWebCryptoAlg:()=>jwtAlgToWebCryptoAlg,pemToPublicJwk:()=>pemToPublicJwk2,rsaSignBase64Url:()=>rsaSignBase64Url2,rsaVerify:()=>rsaVerify2,rsaVerifyBrowser:()=>rsaVerifyBrowser,rsaVerifySync:()=>rsaVerifySync2});function toBytes(data){if(typeof data==="string"){return new TextEncoder().encode(data)}return data}function toHex(bytes){return Array.from(bytes).map(b=>b.toString(16).padStart(2,"0")).join("")}function generateUUID(){if(typeof crypto!=="undefined"&&typeof crypto.randomUUID==="function"){return crypto.randomUUID()}const bytes=(0,import_utils.randomBytes)(16);bytes[6]=bytes[6]&15|64;bytes[8]=bytes[8]&63|128;const hex=toHex(bytes);return`${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`}function constantTimeEqual(a,b){if(a.length!==b.length)return false;let result=0;for(let i=0;i<a.length;i++){result|=a[i]^b[i]}return result===0}function nodeOnly(name){throw new Error(`${name} is only available in Node.js runtimes`)}function rsaVerify2(){return nodeOnly("rsaVerify")}function rsaSignBase64Url2(){return nodeOnly("rsaSignBase64Url")}function rsaVerifySync2(){return nodeOnly("rsaVerifySync")}function pemToPublicJwk2(){return nodeOnly("pemToPublicJwk")}function generateRsaKeyPair2(){return nodeOnly("generateRsaKeyPair")}async function rsaVerifyBrowser(jwtAlg,data,publicJwk,signature){if(typeof globalThis.crypto?.subtle==="undefined"){throw new Error("WebCrypto API (crypto.subtle) is not available in this environment")}const webAlg=jwtAlgToWebCryptoAlg(jwtAlg);const isPss=isRsaPssAlg(jwtAlg);const algorithm={name:isPss?"RSA-PSS":"RSASSA-PKCS1-v1_5",hash:{name:webAlg}};const key=await globalThis.crypto.subtle.importKey("jwk",publicJwk,algorithm,false,["verify"]);const verifyAlgorithm=isPss?{name:"RSA-PSS",saltLength:getSaltLength(jwtAlg)}:{name:"RSASSA-PKCS1-v1_5"};const sigBuf=new Uint8Array(signature).buffer;const dataBuf=new Uint8Array(data).buffer;return globalThis.crypto.subtle.verify(verifyAlgorithm,key,sigBuf,dataBuf)}function getSaltLength(jwtAlg){switch(jwtAlg){case"PS256":return 32;case"PS384":return 48;case"PS512":return 64;default:return 32}}var browserCrypto;var init_browser=__esm({"libs/utils/src/crypto/browser.ts"(){"use strict";init_jwt_alg();init_jwt_alg();browserCrypto={randomUUID(){return generateUUID()},randomBytes(length){return(0,import_utils.randomBytes)(length)},sha256(data){return(0,import_sha2.sha256)(toBytes(data))},sha256Hex(data){return toHex((0,import_sha2.sha256)(toBytes(data)))},hmacSha256(key,data){return(0,import_hmac.hmac)(import_sha2.sha256,key,data)},hkdfSha256(ikm,salt,info,length){const effectiveSalt=salt.length>0?salt:new Uint8Array(32);return(0,import_hkdf.hkdf)(import_sha2.sha256,ikm,effectiveSalt,info,length)},encryptAesGcm(key,plaintext,iv){const cipher=(0,import_aes.gcm)(key,iv);const sealed=cipher.encrypt(plaintext);const ciphertext=sealed.slice(0,-16);const tag=sealed.slice(-16);return{ciphertext,tag}},decryptAesGcm(key,ciphertext,iv,tag){const cipher=(0,import_aes.gcm)(key,iv);const sealed=new Uint8Array(ciphertext.length+tag.length);sealed.set(ciphertext);sealed.set(tag,ciphertext.length);return cipher.decrypt(sealed)},timingSafeEqual(a,b){return constantTimeEqual(a,b)}}}});function getCrypto(){return platformCrypto.cryptoProvider}function randomBytes(length){if(!Number.isInteger(length)||length<=0){throw new Error(`randomBytes length must be a positive integer, got ${length}`)}return getCrypto().randomBytes(length)}function sha256(data){return getCrypto().sha256(data)}function hmacSha256(key,data){return getCrypto().hmacSha256(key,data)}function encryptAesGcm(key,plaintext,iv){if(key.length!==32){throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`)}if(iv.length!==12){throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`)}return getCrypto().encryptAesGcm(key,plaintext,iv)}function decryptAesGcm(key,ciphertext,iv,tag){if(key.length!==32){throw new Error(`AES-256-GCM requires a 32-byte key, got ${key.length} bytes`)}if(iv.length!==12){throw new Error(`AES-GCM requires a 12-byte IV, got ${iv.length} bytes`)}if(tag.length!==16){throw new Error(`AES-GCM requires a 16-byte authentication tag, got ${tag.length} bytes`)}return getCrypto().decryptAesGcm(key,ciphertext,iv,tag)}function timingSafeEqual(a,b){if(a.length!==b.length){throw new Error(`timingSafeEqual requires equal-length arrays, got ${a.length} and ${b.length} bytes`)}return getCrypto().timingSafeEqual(a,b)}function bytesToHex(data){return Array.from(data).map(b=>b.toString(16).padStart(2,"0")).join("")}function base64urlEncode(data){let base64;if(typeof Buffer!=="undefined"){base64=Buffer.from(data).toString("base64")}else{const binString=Array.from(data,byte=>String.fromCodePoint(byte)).join("");base64=btoa(binString)}const result=base64.replace(/\+/g,"-").replace(/\//g,"_");let end=result.length;while(end>0&&result[end-1]==="="){end--}return result.slice(0,end)}function base64urlDecode(data){let base64=data.replace(/-/g,"+").replace(/_/g,"/");const pad=base64.length%4;if(pad){base64+="=".repeat(4-pad)}if(typeof Buffer!=="undefined"){return new Uint8Array(Buffer.from(base64,"base64"))}else{const binString=atob(base64);return Uint8Array.from(binString,c=>c.codePointAt(0)??0)}}var HKDF_SHA256_MAX_LENGTH;var init_crypto=__esm({"libs/utils/src/crypto/index.ts"(){"use strict";init_runtime();init_jwt_alg();init_runtime();init_encrypted_blob();init_pkce2();init_secret_persistence();init_hmac_signing();init_key_persistence2();HKDF_SHA256_MAX_LENGTH=255*32}});function attachRedisErrorListener(client,options={}){const label=options.label??"redis";const intervalMs=options.intervalMs??DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;let lastLoggedAt=0;let suppressed=0;const listener=error=>{const now=Date.now();if(lastLoggedAt!==0&&now-lastLoggedAt<intervalMs){suppressed++;return}const extra=suppressed>0?` (${suppressed} similar error(s) suppressed)`:"";suppressed=0;lastLoggedAt=now;const message=`[${label}] Redis connection error: ${error?.message??String(error)}${extra}`;if(options.logger){options.logger.warn(message)}else{console.warn(message)}};client.on("error",listener);return()=>{client.removeListener("error",listener)}}var DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS;var init_redis_error_listener=__esm({"libs/utils/src/storage/redis-error-listener.ts"(){"use strict";DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS=3e4}});function decodeOrRaw(encoded){try{return decodeURIComponent(encoded)}catch{return encoded}}function integerOrUndefined(text){const value=Number.parseInt(text??"",10);return Number.isNaN(value)?void 0:value}function readRedisUrl(rawUrl){let url;try{url=new URL(rawUrl)}catch{return void 0}if(url.protocol!=="redis:"&&url.protocol!=="rediss:")return void 0;const query=Object.fromEntries(url.searchParams);const{host,port,username,password,db,...otherOptions}=query;const queryOptions={};for(const[name,value=""]of Object.entries(otherOptions)){queryOptions[name]=name==="family"?integerOrUndefined(value)??value:value}return{host:url.hostname.replace(/^\[(.*)\]$/,"$1")||host,port:integerOrUndefined(url.port||port),username:decodeOrRaw(url.username)||username||void 0,password:decodeOrRaw(url.password)||password||void 0,db:integerOrUndefined(url.pathname.replace(/^\/+/,"")||db),tls:url.protocol==="rediss:",queryOptions}}function mergeRedisUrlFields(rawUrl,fields){const connection=readRedisUrl(rawUrl);if(!connection)return void 0;const fillIns={};const conflicts=[];if(fields.host!==void 0&&fields.host!==connection.host)conflicts.push("host");if(fields.port!==void 0){if(connection.port===void 0)fillIns.port=fields.port;else if(fields.port!==connection.port)conflicts.push("port")}if(fields.password!==void 0){if(connection.password===void 0)fillIns.password=fields.password;else if(fields.password!==connection.password)conflicts.push("password")}if(fields.db!==void 0){if(connection.db===void 0)fillIns.db=fields.db;else if(fields.db!==connection.db)conflicts.push("db")}if(fields.tls===true&&!connection.tls)fillIns.tls=true;if(fields.tls===false&&connection.tls)conflicts.push("tls");return{connection,fillIns,conflicts}}function describeRedisUrlConflicts(conflicts){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.`}var init_redis_url=__esm({"libs/utils/src/storage/redis-url.ts"(){"use strict"}});var init_utils=__esm({"libs/utils/src/storage/utils/index.ts"(){"use strict";init_ttl()}});var COMPARE_AND_DELETE_SCRIPT;var COMPARE_AND_EXPIRE_SCRIPT;var init_compare_and_delete=__esm({"libs/utils/src/storage/utils/compare-and-delete.ts"(){"use strict";COMPARE_AND_DELETE_SCRIPT=`if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;COMPARE_AND_EXPIRE_SCRIPT=`if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) end return 0`}});var redis_exports={};__export2(redis_exports,{RedisStorageAdapter:()=>RedisStorageAdapter,createRedisClient:()=>createRedisClient});function getRedisClass(){try{return __require("ioredis").default||__require("ioredis")}catch(error){const msg=error instanceof Error?error.message:String(error);if(msg.includes("Dynamic require")||msg.includes("require is not defined")){throw new Error(`Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,{cause:error})}throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis",{cause:error})}}function resolveUrlOptions(connectionUrl,options){const{url:configUrl,...siblingFields}={url:void 0,...options.config};if(options.url&&configUrl&&configUrl!==options.url){throw new StorageConfigError("redis","redis.url and redis.config.url name different servers; set one of them.")}return urlConnectionOptions(connectionUrl,siblingFields)}function urlConnectionOptions(url,fields){const merge=mergeRedisUrlFields(url,fields);if(!merge)return void 0;if(merge.conflicts.length>0){throw new StorageConfigError("redis",describeRedisUrlConflicts(merge.conflicts))}const{queryOptions,tls,...stated}=merge.connection;return{...queryOptions,...stated,...toFillInOptions(merge.fillIns),...tls?{tls:{}}:{}}}function toFillInOptions({port,password,db,tls}){return{...port!==void 0?{port}:{},...password!==void 0?{password}:{},...db!==void 0?{db}:{},...tls?{tls:{}}:{}}}function createRedisClient(options){const urlOptions=options.url?urlConnectionOptions(options.url,options):void 0;const RedisClass=getRedisClass();const baseOptions={lazyConnect:false,maxRetriesPerRequest:3};let client;if(urlOptions){client=new RedisClass({...baseOptions,...urlOptions})}else if(options.url){client=new RedisClass(options.url,baseOptions)}else{client=new RedisClass({...baseOptions,host:options.host??"localhost",port:options.port??6379,password:options.password,db:options.db??0,tls:options.tls?{}:void 0})}attachRedisErrorListener(client,{label:options.label??"redis",logger:options.logger});return client}var RedisStorageAdapter;var init_redis=__esm({"libs/utils/src/storage/adapters/redis.ts"(){"use strict";init_errors();init_redis_error_listener();init_redis_url();init_utils();init_compare_and_delete();init_base();RedisStorageAdapter=class extends BaseStorageAdapter{backendName="redis";client;subscriber;options;ownsClient;detachErrorListener;connecting;keyPrefix;subscriptionHandlers=new Map;connectionUrl;urlOptions;constructor(options={}){super();const hasClient=options.client!==void 0;const hasConfig=options.config!==void 0||options.url!==void 0;if(hasClient&&hasConfig){throw new StorageConfigError("redis",'Cannot specify both "client" and "config"/"url". Use one or the other.')}if(!hasClient&&!hasConfig){const envUrl=process.env["REDIS_URL"]||process.env["REDIS_HOST"];if(envUrl){options={...options,url:envUrl}}else{throw new StorageConfigError("redis",'Either "client", "config", "url", or REDIS_URL environment variable must be provided.')}}this.options=options;this.ownsClient=!hasClient;this.keyPrefix=options.keyPrefix??"";const configUrl=options.config?.url;this.connectionUrl=options.url??(typeof configUrl==="string"?configUrl:void 0);this.urlOptions=this.connectionUrl?resolveUrlOptions(this.connectionUrl,options):void 0}async connect(){if(this.connected)return;this.connecting??=this.openConnection().finally(()=>{this.connecting=void 0});return this.connecting}async openConnection(){let socketError;const recordSocketError=error=>{socketError=error};let client;try{if(this.options.client){client=this.options.client}else{client=this.newClient();client.on("error",recordSocketError)}await client.ping();this.client=client;this.connected=true;if(this.ownsClient){client.removeListener("error",recordSocketError);this.detachErrorListener=attachRedisErrorListener(client,{label:"RedisStorage"})}}catch(e){if(this.ownsClient&&client){try{client.disconnect()}catch{}}const cause=socketError??(e instanceof Error?e:void 0);throw new StorageConnectionError("Failed to connect to Redis",cause,"redis")}}async disconnect(){if(!this.connected)return;if(this.subscriber){await this.subscriber.quit();this.subscriber=void 0}if(this.ownsClient&&this.client){await this.client.quit()}this.detachErrorListener?.();this.detachErrorListener=void 0;this.client=void 0;this.connected=false;this.subscriptionHandlers.clear()}async ping(){if(!this.client)return false;try{const result=await this.client.ping();return result==="PONG"}catch{return false}}getConnectedClient(){this.ensureConnected();if(!this.client){throw new StorageConnectionError("Redis client not connected",void 0,"redis")}return this.client}getConnectedSubscriber(){if(!this.subscriber){throw new StorageConnectionError("Redis subscriber not created",void 0,"redis")}return this.subscriber}async get(key){return this.getConnectedClient().get(this.prefixKey(key))}async doSet(key,value,options){const client=this.getConnectedClient();const prefixedKey=this.prefixKey(key);if(options?.ttlSeconds){if(options.ifNotExists){await client.set(prefixedKey,value,"EX",options.ttlSeconds,"NX")}else if(options.ifExists){await client.set(prefixedKey,value,"EX",options.ttlSeconds,"XX")}else{await client.set(prefixedKey,value,"EX",options.ttlSeconds)}}else if(options?.ifNotExists){await client.set(prefixedKey,value,"NX")}else if(options?.ifExists){await client.set(prefixedKey,value,"XX")}else{await client.set(prefixedKey,value)}}async delete(key){const result=await this.getConnectedClient().del(this.prefixKey(key));return result>0}async deleteIfEquals(key,expectedValue){const deleted=await this.getConnectedClient().eval(COMPARE_AND_DELETE_SCRIPT,1,this.prefixKey(key),expectedValue);return deleted===1}async expireIfEquals(key,expectedValue,ttlSeconds){validateTTL(ttlSeconds);const extended=await this.getConnectedClient().eval(COMPARE_AND_EXPIRE_SCRIPT,1,this.prefixKey(key),expectedValue,String(ttlSeconds));return extended===1}async exists(key){const result=await this.getConnectedClient().exists(this.prefixKey(key));return result>0}async mget(keys){if(keys.length===0)return[];const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().mget(...prefixedKeys)}async mset(entries){if(entries.length===0)return;for(const entry of entries){this.validateSetOptions(entry.options)}const pipeline=this.getConnectedClient().pipeline();for(const entry of entries){const prefixedKey=this.prefixKey(entry.key);if(entry.options?.ttlSeconds){if(entry.options.ifNotExists){pipeline.set(prefixedKey,entry.value,"EX",entry.options.ttlSeconds,"NX")}else if(entry.options.ifExists){pipeline.set(prefixedKey,entry.value,"EX",entry.options.ttlSeconds,"XX")}else{pipeline.set(prefixedKey,entry.value,"EX",entry.options.ttlSeconds)}}else if(entry.options?.ifNotExists){pipeline.set(prefixedKey,entry.value,"NX")}else if(entry.options?.ifExists){pipeline.set(prefixedKey,entry.value,"XX")}else{pipeline.set(prefixedKey,entry.value)}}await pipeline.exec()}async mdelete(keys){if(keys.length===0)return 0;const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().del(...prefixedKeys)}async expire(key,ttlSeconds){validateTTL(ttlSeconds);const result=await this.getConnectedClient().expire(this.prefixKey(key),ttlSeconds);return result===1}async ttl(key){const result=await this.getConnectedClient().ttl(this.prefixKey(key));if(result===-2)return null;return result}async keys(pattern="*"){const client=this.getConnectedClient();const prefixedPattern=this.prefixKey(pattern);const result=[];let cursor="0";do{const[nextCursor,keys]=await client.scan(cursor,"MATCH",prefixedPattern,"COUNT",100);cursor=nextCursor;for(const key of keys){result.push(this.unprefixKey(key))}}while(cursor!=="0");return result}async incr(key){return this.getConnectedClient().incr(this.prefixKey(key))}async decr(key){return this.getConnectedClient().decr(this.prefixKey(key))}async incrBy(key,amount){return this.getConnectedClient().incrby(this.prefixKey(key),amount)}supportsPubSub(){return true}async publish(channel,message){const prefixedChannel=this.prefixKey(channel);return this.getConnectedClient().publish(prefixedChannel,message)}async subscribe(channel,handler){this.ensureConnected();const prefixedChannel=this.prefixKey(channel);if(!this.subscriber){await this.createSubscriber()}const subscriber=this.getConnectedSubscriber();if(!this.subscriptionHandlers.has(prefixedChannel)){this.subscriptionHandlers.set(prefixedChannel,new Set);await subscriber.subscribe(prefixedChannel)}const handlers=this.subscriptionHandlers.get(prefixedChannel);if(handlers){handlers.add(handler)}return async()=>{const handlers2=this.subscriptionHandlers.get(prefixedChannel);if(handlers2){handlers2.delete(handler);if(handlers2.size===0){this.subscriptionHandlers.delete(prefixedChannel);await this.subscriber?.unsubscribe(prefixedChannel)}}}}buildRedisOptions(){if(this.connectionUrl){return{lazyConnect:false,maxRetriesPerRequest:3,...this.urlOptions}}const config=this.options.config;if(!config||typeof config.url==="string"||config.host===void 0){throw new StorageConfigError("redis","Redis config is required when URL is not provided")}return{host:config.host,port:config.port??6379,password:config.password,db:config.db??0,tls:config.tls?{}:void 0,lazyConnect:false,maxRetriesPerRequest:3}}newClient(){const RedisClass=getRedisClass();if(this.connectionUrl&&!this.urlOptions){return new RedisClass(this.connectionUrl,this.buildRedisOptions())}return new RedisClass(this.buildRedisOptions())}async createSubscriber(){let subscriber;if(this.connectionUrl||this.options.config){subscriber=this.newClient()}else if(this.options.client){subscriber=this.options.client.duplicate()}else{throw new StorageConfigError("redis","Cannot create subscriber without url, config, or client")}attachRedisErrorListener(subscriber,{label:"RedisStorage:subscriber"});subscriber.on("message",(channel,message)=>{const handlers=this.subscriptionHandlers.get(channel);if(handlers){const unprefixedChannel=this.unprefixKey(channel);for(const handler of handlers){try{handler(message,unprefixedChannel)}catch{}}}});this.subscriber=subscriber}prefixKey(key){return this.keyPrefix+key}unprefixKey(key){if(this.keyPrefix&&key.startsWith(this.keyPrefix)){return key.slice(this.keyPrefix.length)}return key}getClient(){return this.client}}}});var vercel_kv_exports={};__export2(vercel_kv_exports,{VercelKvStorageAdapter:()=>VercelKvStorageAdapter,createVercelKvClient:()=>createVercelKvClient});async function loadVercelKv(){try{const mod=await import("@vercel/kv");const resolved=typeof mod.createClient==="function"?mod:mod.default;if(!resolved||typeof resolved.createClient!=="function")throw new Error("createClient missing");return resolved}catch{throw new Error("@vercel/kv is required for Vercel KV storage adapter. Install it with: npm install @vercel/kv")}}async function createVercelKvClient(connection={}){const url=connection.url??process.env["KV_REST_API_URL"];const token=connection.token??process.env["KV_REST_API_TOKEN"];if(!url||!token){throw new StorageConfigError("vercel-kv","Pass url and token, or set KV_REST_API_URL and KV_REST_API_TOKEN.")}const{createClient}=await loadVercelKv();return createClient({url,token,cache:void 0,automaticDeserialization:false})}var VercelKvStorageAdapter;var init_vercel_kv=__esm({"libs/utils/src/storage/adapters/vercel-kv.ts"(){"use strict";init_errors();init_compare_and_delete();init_ttl();init_base();VercelKvStorageAdapter=class extends BaseStorageAdapter{backendName="vercel-kv";client;options;keyPrefix;constructor(options={}){super();const url=options.url??process.env["KV_REST_API_URL"];const token=options.token??process.env["KV_REST_API_TOKEN"];if(!url||!token){throw new StorageConfigError("vercel-kv","KV_REST_API_URL and KV_REST_API_TOKEN must be provided via options or environment variables.")}this.options={...options,url,token};this.keyPrefix=options.keyPrefix??""}async connect(){if(this.connected)return;try{this.client=await createVercelKvClient({url:this.options.url,token:this.options.token});await this.client.exists("__healthcheck__");this.connected=true}catch(e){throw new StorageConnectionError("Failed to connect to Vercel KV",e instanceof Error?e:void 0,"vercel-kv")}}async disconnect(){this.client=void 0;this.connected=false}async ping(){if(!this.client)return false;try{await this.client.exists("__healthcheck__");return true}catch{return false}}getConnectedClient(){this.ensureConnected();if(!this.client){throw new StorageConnectionError("Vercel KV client not connected",void 0,"vercel-kv")}return this.client}async get(key){return this.getConnectedClient().get(this.prefixKey(key))}async doSet(key,value,options){const client=this.getConnectedClient();const prefixedKey=this.prefixKey(key);const setOptions={};if(options?.ttlSeconds){setOptions.ex=options.ttlSeconds}if(options?.ifNotExists){setOptions.nx=true}else if(options?.ifExists){setOptions.xx=true}await client.set(prefixedKey,value,Object.keys(setOptions).length>0?setOptions:void 0)}async delete(key){const result=await this.getConnectedClient().del(this.prefixKey(key));return result>0}async deleteIfEquals(key,expectedValue){const deleted=await this.getConnectedClient().eval(COMPARE_AND_DELETE_SCRIPT,[this.prefixKey(key)],[expectedValue]);return deleted===1}async expireIfEquals(key,expectedValue,ttlSeconds){validateTTL(ttlSeconds);const extended=await this.getConnectedClient().eval(COMPARE_AND_EXPIRE_SCRIPT,[this.prefixKey(key)],[expectedValue,String(ttlSeconds)]);return extended===1}async exists(key){const result=await this.getConnectedClient().exists(this.prefixKey(key));return result>0}async mget(keys){if(keys.length===0)return[];const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().mget(...prefixedKeys)}async mdelete(keys){if(keys.length===0)return 0;const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().del(...prefixedKeys)}async expire(key,ttlSeconds){validateTTL(ttlSeconds);const result=await this.getConnectedClient().expire(this.prefixKey(key),ttlSeconds);return result===1}async ttl(key){const result=await this.getConnectedClient().ttl(this.prefixKey(key));if(result===-2)return null;return result}async keys(pattern="*"){const client=this.getConnectedClient();const prefixedPattern=this.prefixKey(pattern);try{const result=[];let cursor=0;do{const[nextCursor,keys]=await client.scan(cursor,{match:prefixedPattern,count:100});const parsedCursor=typeof nextCursor==="string"?parseInt(nextCursor,10):nextCursor;cursor=Number.isNaN(parsedCursor)?0:parsedCursor;for(const key of keys){result.push(this.unprefixKey(key))}}while(cursor!==0);return result}catch{const allKeys=await client.keys(prefixedPattern);return allKeys.map(k=>this.unprefixKey(k))}}async incr(key){return this.getConnectedClient().incr(this.prefixKey(key))}async decr(key){return this.getConnectedClient().decr(this.prefixKey(key))}async incrBy(key,amount){return this.getConnectedClient().incrby(this.prefixKey(key),amount)}supportsPubSub(){return false}getPubSubSuggestion(){return"Vercel KV is REST-based and does not support pub/sub. Use Upstash adapter for pub/sub support."}prefixKey(key){return this.keyPrefix+key}unprefixKey(key){if(this.keyPrefix&&key.startsWith(this.keyPrefix)){return key.slice(this.keyPrefix.length)}return key}}}});var upstash_exports={};__export2(upstash_exports,{UpstashStorageAdapter:()=>UpstashStorageAdapter});async function loadUpstashRedis(){try{const mod=await import("@upstash/redis");const resolved=typeof mod.Redis==="function"?mod:mod.default;if(!resolved||typeof resolved.Redis!=="function")throw new Error("Redis missing");return resolved}catch{throw new Error("@upstash/redis is required for Upstash storage adapter. Install it with: npm install @upstash/redis")}}var PUBSUB_POLL_INTERVAL_MS;var UpstashStorageAdapter;var init_upstash=__esm({"libs/utils/src/storage/adapters/upstash.ts"(){"use strict";init_errors();init_compare_and_delete();init_ttl();init_base();PUBSUB_POLL_INTERVAL_MS=100;UpstashStorageAdapter=class extends BaseStorageAdapter{backendName="upstash";client;options;keyPrefix;pubSubEnabled;subscriptionHandlers=new Map;pollingIntervals=new Map;constructor(options={}){super();const url=options.url??process.env["UPSTASH_REDIS_REST_URL"];const token=options.token??process.env["UPSTASH_REDIS_REST_TOKEN"];if(!url||!token){throw new StorageConfigError("upstash","UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN must be provided via options or environment variables.")}this.options={...options,url,token};this.keyPrefix=options.keyPrefix??"";this.pubSubEnabled=options.enablePubSub??false}async connect(){if(this.connected)return;try{const{Redis}=await loadUpstashRedis();const url=this.options.url;const token=this.options.token;if(!url||!token){throw new StorageConfigError("upstash","URL and token are required")}this.client=new Redis({url,token,automaticDeserialization:false});await this.client.exists("__healthcheck__");this.connected=true}catch(e){throw new StorageConnectionError("Failed to connect to Upstash Redis",e instanceof Error?e:void 0,"upstash")}}async disconnect(){if(!this.connected)return;for(const interval of this.pollingIntervals.values()){clearInterval(interval)}this.pollingIntervals.clear();this.subscriptionHandlers.clear();this.client=void 0;this.connected=false}async ping(){if(!this.client)return false;try{await this.client.exists("__healthcheck__");return true}catch{return false}}getConnectedClient(){this.ensureConnected();if(!this.client){throw new StorageConnectionError("Upstash client not connected",void 0,"upstash")}return this.client}async get(key){return this.getConnectedClient().get(this.prefixKey(key))}async doSet(key,value,options){const client=this.getConnectedClient();const prefixedKey=this.prefixKey(key);const setOptions={};if(options?.ttlSeconds){setOptions.ex=options.ttlSeconds}if(options?.ifNotExists){setOptions.nx=true}else if(options?.ifExists){setOptions.xx=true}await client.set(prefixedKey,value,Object.keys(setOptions).length>0?setOptions:void 0)}async delete(key){const result=await this.getConnectedClient().del(this.prefixKey(key));return result>0}async deleteIfEquals(key,expectedValue){const deleted=await this.getConnectedClient().eval(COMPARE_AND_DELETE_SCRIPT,[this.prefixKey(key)],[expectedValue]);return deleted===1}async expireIfEquals(key,expectedValue,ttlSeconds){validateTTL(ttlSeconds);const extended=await this.getConnectedClient().eval(COMPARE_AND_EXPIRE_SCRIPT,[this.prefixKey(key)],[expectedValue,String(ttlSeconds)]);return extended===1}async exists(key){const result=await this.getConnectedClient().exists(this.prefixKey(key));return result>0}async mget(keys){if(keys.length===0)return[];const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().mget(...prefixedKeys)}async mdelete(keys){if(keys.length===0)return 0;const prefixedKeys=keys.map(k=>this.prefixKey(k));return this.getConnectedClient().del(...prefixedKeys)}async expire(key,ttlSeconds){validateTTL(ttlSeconds);const result=await this.getConnectedClient().expire(this.prefixKey(key),ttlSeconds);return result===1}async ttl(key){const result=await this.getConnectedClient().ttl(this.prefixKey(key));if(result===-2)return null;return result}async keys(pattern="*"){const client=this.getConnectedClient();const prefixedPattern=this.prefixKey(pattern);const result=[];let cursor=0;do{const[nextCursor,keys]=await client.scan(cursor,{match:prefixedPattern,count:100});const parsedCursor=typeof nextCursor==="string"?parseInt(nextCursor,10):nextCursor;cursor=Number.isNaN(parsedCursor)?0:parsedCursor;for(const key of keys){result.push(this.unprefixKey(key))}}while(cursor!==0);return result}async incr(key){return this.getConnectedClient().incr(this.prefixKey(key))}async decr(key){return this.getConnectedClient().decr(this.prefixKey(key))}async incrBy(key,amount){return this.getConnectedClient().incrby(this.prefixKey(key),amount)}supportsPubSub(){return this.pubSubEnabled}async publish(channel,message){if(!this.pubSubEnabled){return super.publish(channel,message)}const prefixedChannel=this.prefixKey(`__pubsub__:${channel}`);const listKey=`${prefixedChannel}:queue`;await this.getConnectedClient().lpush(listKey,message);return 1}async subscribe(channel,handler){const client=this.getConnectedClient();if(!this.pubSubEnabled){return super.subscribe(channel,handler)}const prefixedChannel=this.prefixKey(`__pubsub__:${channel}`);if(!this.subscriptionHandlers.has(prefixedChannel)){this.subscriptionHandlers.set(prefixedChannel,new Set);const listKey=`${prefixedChannel}:queue`;const interval=setInterval(async()=>{try{const message=await client.rpop(listKey);if(message){const handlers2=this.subscriptionHandlers.get(prefixedChannel);if(handlers2){for(const h of handlers2){try{h(message,channel)}catch{}}}}}catch{}},PUBSUB_POLL_INTERVAL_MS);if(interval.unref){interval.unref()}this.pollingIntervals.set(prefixedChannel,interval)}const handlers=this.subscriptionHandlers.get(prefixedChannel);if(handlers){handlers.add(handler)}return async()=>{const handlers2=this.subscriptionHandlers.get(prefixedChannel);if(handlers2){handlers2.delete(handler);if(handlers2.size===0){this.subscriptionHandlers.delete(prefixedChannel);const interval=this.pollingIntervals.get(prefixedChannel);if(interval){clearInterval(interval);this.pollingIntervals.delete(prefixedChannel)}}}}}async publishToQueue(channel,message){const prefixedChannel=this.prefixKey(`__pubsub__:${channel}`);const listKey=`${prefixedChannel}:queue`;await this.getConnectedClient().lpush(listKey,message)}getPubSubSuggestion(){return"Enable pub/sub by setting enablePubSub: true in the adapter options."}prefixKey(key){return this.keyPrefix+key}unprefixKey(key){if(this.keyPrefix&&key.startsWith(this.keyPrefix)){return key.slice(this.keyPrefix.length)}return key}}}});var cloudflare_kv_exports={};__export2(cloudflare_kv_exports,{CloudflareKvStorageAdapter:()=>CloudflareKvStorageAdapter});function literalPrefix(pattern){const star=pattern.indexOf("*");const q=pattern.indexOf("?");const idx=star===-1?q:q===-1?star:Math.min(star,q);return idx===-1?pattern:pattern.slice(0,idx)}var CF_KV_MIN_TTL_SECONDS;var CloudflareKvStorageAdapter;var init_cloudflare_kv=__esm({"libs/utils/src/storage/adapters/cloudflare-kv.ts"(){"use strict";init_errors();init_pattern();init_ttl();init_base();CF_KV_MIN_TTL_SECONDS=60;CloudflareKvStorageAdapter=class extends BaseStorageAdapter{backendName="cloudflare-kv";kv;keyPrefix;constructor(options){super();if(!options?.namespace){throw new Error("CloudflareKvStorageAdapter: a bound KV `namespace` is required (resolve it from the Worker `env`).")}this.kv=options.namespace;this.keyPrefix=options.keyPrefix??""}async connect(){this.connected=true}async disconnect(){this.connected=false}async ping(){if(!this.connected)return false;try{await this.kv.list({limit:1});return true}catch{return false}}async get(key){this.ensureConnected();return this.kv.get(this.prefixKey(key),"text")}async doSet(key,value,options){if(options?.ifNotExists||options?.ifExists){throw new StorageNotSupportedError("conditional set (ifNotExists/ifExists)",this.backendName,"Cloudflare KV has no compare-and-set; use a Durable Object for atomic conditional writes.")}const putOptions=options?.ttlSeconds!==void 0?{expirationTtl:this.toExpirationTtl(options.ttlSeconds,key)}:void 0;await this.kv.put(this.prefixKey(key),value,putOptions)}async delete(key){this.ensureConnected();const existed=await this.exists(key);await this.kv.delete(this.prefixKey(key));return existed}async exists(key){this.ensureConnected();return await this.kv.get(this.prefixKey(key),"text")!==null}async expire(key,ttlSeconds){this.ensureConnected();const ttl=this.toExpirationTtl(ttlSeconds,key);const value=await this.kv.get(this.prefixKey(key),"text");if(value===null)return false;await this.kv.put(this.prefixKey(key),value,{expirationTtl:ttl});return true}async ttl(_key){throw new StorageNotSupportedError("ttl",this.backendName,"Cloudflare KV does not expose a key's remaining TTL. Track expiry alongside the value if you need it.")}async keys(pattern="*"){this.ensureConnected();const prefixedPattern=this.prefixKey(pattern);const listPrefix=literalPrefix(prefixedPattern);const out=[];let cursor;do{const page=await this.kv.list({prefix:listPrefix||void 0,cursor});for(const{name}of page.keys){if(matchesPattern(name,prefixedPattern))out.push(this.unprefixKey(name))}cursor=page.list_complete?void 0:page.cursor}while(cursor);return out}async incr(key){return this.incrBy(key,1)}async decr(key){return this.incrBy(key,-1)}async incrBy(_key,_amount){throw new StorageNotSupportedError("incr/decr/incrBy",this.backendName,"Cloudflare KV has no atomic increment (a get+put is racy under eventual consistency). Use a Durable Object for atomic counters.")}async deleteIfEquals(_key,_expectedValue){throw new StorageNotSupportedError("deleteIfEquals",this.backendName,"Cloudflare KV has no compare-and-set; use a Durable Object for an atomic compare-and-delete.")}getPubSubSuggestion(){return"Cloudflare KV does not support pub/sub. Use a Durable Object (or Queues) for fan-out."}toExpirationTtl(ttlSeconds,key){validateTTL(ttlSeconds);if(ttlSeconds<CF_KV_MIN_TTL_SECONDS){throw new StorageOperationError("set",key,`cloudflare-kv: expirationTtl must be >= ${CF_KV_MIN_TTL_SECONDS}s (Cloudflare KV minimum); got ${ttlSeconds}s.`)}return ttlSeconds}prefixKey(key){return this.keyPrefix+key}unprefixKey(key){return this.keyPrefix&&key.startsWith(this.keyPrefix)?key.slice(this.keyPrefix.length):key}}}});init_runtime();var IPV4_OCTET_PATTERN=/^(0|[1-9]\d{0,2})$/;var IPV6_GROUP_PATTERN=/^[0-9a-f]{1,4}$/;var IPV4_BLOCKS=compileBlocks([["0.0.0.0/8","this-network"],["10.0.0.0/8","private"],["100.64.0.0/10","carrier-grade-nat"],["127.0.0.0/8","loopback"],["169.254.0.0/16","link-local"],["172.16.0.0/12","private"],["192.0.0.0/24","ietf-protocol"],["192.168.0.0/16","private"],["198.18.0.0/15","benchmarking"],["224.0.0.0/4","multicast"],["255.255.255.255/32","reserved"],["240.0.0.0/4","reserved"]],parseIpv4);var IPV6_BLOCKS=compileBlocks([["::/128","unspecified"],["::1/128","loopback"],["fd00:ec2::254/128","cloud-metadata"],["fc00::/7","private"],["fe80::/10","link-local"],["fec0::/10","site-local"],["64:ff9b:1::/48","local-use-nat64"],["ff00::/8","multicast"]],expandIpv6);var IPV4_EMBEDDINGS=compileBlocks([["::ffff:0:0/96",6],["::ffff:0:0:0/96",6],["::/96",6],["64:ff9b::/96",6],["2002::/16",1]],expandIpv6);function parseIpv4(address){const parts=address.split(".");if(parts.length!==4||!parts.every(part=>IPV4_OCTET_PATTERN.test(part)))return void 0;const octets=parts.map(Number);return octets.every(octet=>octet<=255)?octets:void 0}function expandIpv6(address){const zoneIndex=address.indexOf("%");let text=(zoneIndex===-1?address:address.slice(0,zoneIndex)).toLowerCase();const lastColonIndex=text.lastIndexOf(":");const trailingSegment=text.slice(lastColonIndex+1);if(lastColonIndex!==-1&&trailingSegment.includes(".")){const octets=parseIpv4(trailingSegment);if(!octets)return void 0;const highGroup=(octets[0]<<8|octets[1]).toString(16);const lowGroup=(octets[2]<<8|octets[3]).toString(16);text=`${text.slice(0,lastColonIndex+1)}${highGroup}:${lowGroup}`}const halves=text.split("::");if(halves.length>2)return void 0;const head=parseIpv6Groups(halves[0]);if(!head)return void 0;if(halves.length===1)return head.length===8?head:void 0;const tail=parseIpv6Groups(halves[1]);if(!tail||head.length+tail.length>7)return void 0;return[...head,...new Array(8-head.length-tail.length).fill(0),...tail]}function parseIpv6Groups(part){if(part==="")return[];const groups=part.split(":");if(!groups.every(group=>IPV6_GROUP_PATTERN.test(group)))return void 0;return groups.map(group=>parseInt(group,16))}function compileBlocks(entries,parseNetwork){return entries.map(([cidr,value])=>{const[networkText,prefixText]=cidr.split("/");const network=parseNetwork(networkText);if(!network)throw new Error(`Invalid network in address block "${cidr}"`);return{cidr,network,prefixLength:Number(prefixText),value}})}init_fs2();var ordinaryHasInstance=Function.prototype[Symbol.hasInstance];init_crypto();init_memory();init_errors();init_errors();init_redis_error_listener();init_redis_url();init_errors();init_crypto();init_errors();var textEncoder2=new TextEncoder;var textDecoder2=new TextDecoder;init_base();init_memory();init_redis();init_vercel_kv();init_upstash();init_filesystem();init_pattern();init_ttl();init_crypto();init_runtime();var DASHBOARD_TOKEN_HEADER="x-frontmcp-dashboard-token";var DASHBOARD_SESSION_COOKIE="frontmcp_dashboard";var AUTHORIZED={authorized:true};function readHeader(headers,name){if(!headers)return void 0;const key=Object.keys(headers).find(k=>k.toLowerCase()===name);const value=key?headers[key]:void 0;return Array.isArray(value)?value[0]:value}function readQuery(query,name){const value=query?.[name];return Array.isArray(value)?value[0]:value}function tokenMatches(presented,expected){if(presented.length===0||expected.length===0)return false;return timingSafeEqual(sha256(presented),sha256(expected))}var encoder=new TextEncoder;function deriveDashboardCookieValue(token){return base64urlEncode(hmacSha256(encoder.encode(token),encoder.encode("frontmcp-dashboard-session:v1")))}function readCookie(header,name){if(!header)return void 0;for(const part of header.split(";")){const separator=part.indexOf("=");if(separator===-1)continue;if(part.slice(0,separator).trim()===name)return part.slice(separator+1).trim()}return void 0}function readBearerToken(header){if(!header)return void 0;const separator=header.indexOf(" ");if(separator===-1)return void 0;if(header.slice(0,separator).toLowerCase()!=="bearer")return void 0;const token=header.slice(separator+1).trim();return token.length>0?token:void 0}function createDashboardAuthValidator(auth,surface="page"){if(!auth?.enabled)return void 0;const expected=auth.token;const expectedCookie=expected?deriveDashboardCookieValue(expected):void 0;return req=>{if(!expected||!expectedCookie){return{authorized:false,status:401,message:"Dashboard authentication is misconfigured"}}const bearer=readBearerToken(readHeader(req.headers,"authorization"));if(bearer&&tokenMatches(bearer,expected))return AUTHORIZED;if(surface==="mcp"){const headerToken=readHeader(req.headers,DASHBOARD_TOKEN_HEADER);if(headerToken&&tokenMatches(headerToken,expected))return AUTHORIZED}else{const queryToken=readQuery(req.query,"token");if(queryToken&&tokenMatches(queryToken,expected))return AUTHORIZED}const cookie=readCookie(readHeader(req.headers,"cookie"),DASHBOARD_SESSION_COOKIE);if(cookie&&tokenMatches(cookie,expectedCookie))return AUTHORIZED;return{authorized:false,status:401,message:"Unauthorized"}}}var GATE_PASSED=Symbol("frontmcp:dashboard:gate-passed");function markDashboardGatePassed(ctx){ctx?.set(GATE_PASSED,true)}var DashboardDisabledError=class extends import_sdk2.PublicMcpError{mcpErrorCode=import_sdk2.MCP_ERROR_CODES.FORBIDDEN;constructor(){super("The FrontMCP dashboard is disabled.","DASHBOARD_DISABLED",404)}};var DashboardUnauthorizedError=class extends import_sdk2.PublicMcpError{mcpErrorCode=import_sdk2.MCP_ERROR_CODES.UNAUTHORIZED;constructor(){super(`The dashboard token is required. Send it in the ${DASHBOARD_TOKEN_HEADER} header.`,"DASHBOARD_UNAUTHORIZED",401,'Bearer realm="frontmcp-dashboard"')}};function assertDashboardAccess(options,ctx){if(!isDashboardEnabled(options)){throw new DashboardDisabledError}if(!options.auth?.enabled)return;if(ctx?.get(GATE_PASSED)===true)return;const expected=options.auth.token;const presented=ctx?.metadata?.customHeaders?.[DASHBOARD_TOKEN_HEADER];if(expected&&presented&&tokenMatches(presented,expected))return;throw new DashboardUnauthorizedError}function assertDashboardToolAccess(tool){const configured=tool.tryGet(DashboardConfigToken);assertDashboardAccess(configured??resolveDashboardOptions(),tool.tryGetContext())}function escapeForJs(str){return str.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/</g,"\\x3c").replace(/>/g,"\\x3e").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function generateDashboardHtml(options,mcpPath=options.basePath){if(options.cdn.entrypoint){return generateExternalEntrypointHtml(options,mcpPath)}return generateInlineDashboardHtml(options,mcpPath)}function generateExternalEntrypointHtml(options,mcpPath){const{cdn}=options;const safeBasePath=escapeForJs(options.basePath);const safeMcpPath=escapeForJs(mcpPath);const safeReact=escapeForJs(cdn.react);const safeReactDom=escapeForJs(cdn.reactDom);const safeReactDomClient=escapeForJs(cdn.reactDomClient);const safeReactJsxRuntime=escapeForJs(cdn.reactJsxRuntime);const safeXyflow=escapeForJs(cdn.xyflow);const safeDagre=escapeForJs(cdn.dagre);const safeXyflowCss=escapeForJs(cdn.xyflowCss);const safeEntrypoint=escapeForJs(cdn.entrypoint||"");return`<!DOCTYPE html>
|
|
1
|
+
"use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var __copyProps=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames(from))if(!__hasOwnProp.call(to,key)&&key!==except)__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable})}return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:true}),mod);var __decorateClass=(decorators,target,key,kind)=>{var result=kind>1?void 0:kind?__getOwnPropDesc(target,key):target;for(var i=decorators.length-1,decorator;i>=0;i--)if(decorator=decorators[i])result=(kind?decorator(target,key,result):decorator(result))||result;if(kind&&result)__defProp(target,key,result);return result};var index_exports={};__export(index_exports,{DashboardApp:()=>DashboardApp,DashboardConfigToken:()=>DashboardConfigToken,DashboardPlugin:()=>DashboardPlugin,GraphDataProvider:()=>GraphDataProvider,GraphDataProviderToken:()=>GraphDataProviderToken,GraphTool:()=>GraphTool,ListResourcesTool:()=>ListResourcesTool,ListToolsTool:()=>ListToolsTool,ParentScopeToken:()=>ParentScopeToken,dashboardPluginOptionsSchema:()=>dashboardPluginOptionsSchema,default:()=>DashboardPlugin,defaultDashboardPluginOptions:()=>defaultDashboardPluginOptions,isDashboardEnabled:()=>isDashboardEnabled,resetDashboardOptions:()=>resetDashboardOptions});module.exports=__toCommonJS(index_exports);var import_sdk=require("@frontmcp/sdk");var import_lazy_zod=require("@frontmcp/lazy-zod");var cdnConfigSchema=import_lazy_zod.z.object({entrypoint:import_lazy_zod.z.string().optional(),react:import_lazy_zod.z.string().default("https://esm.sh/react@19"),reactDom:import_lazy_zod.z.string().default("https://esm.sh/react-dom@19"),reactDomClient:import_lazy_zod.z.string().default("https://esm.sh/react-dom@19/client"),reactJsxRuntime:import_lazy_zod.z.string().default("https://esm.sh/react@19/jsx-runtime"),reactRouter:import_lazy_zod.z.string().default("https://esm.sh/react-router-dom@7"),xyflow:import_lazy_zod.z.string().default("https://esm.sh/@xyflow/react@12?external=react,react-dom"),dagre:import_lazy_zod.z.string().default("https://esm.sh/dagre@0.8.5"),xyflowCss:import_lazy_zod.z.string().default("https://esm.sh/@xyflow/react@12/dist/style.css")});var dashboardAuthSchema=import_lazy_zod.z.object({enabled:import_lazy_zod.z.boolean().default(false),token:import_lazy_zod.z.string().min(1).optional()}).refine(value=>!value.enabled||Boolean(value.token),{message:"dashboard auth.enabled requires auth.token to be set",path:["token"]});var dashboardPluginOptionsSchema=import_lazy_zod.z.object({enabled:import_lazy_zod.z.boolean().optional(),basePath:import_lazy_zod.z.string().default("/dashboard"),auth:dashboardAuthSchema.optional().transform(v=>dashboardAuthSchema.parse(v??{})),cdn:cdnConfigSchema.optional().transform(v=>cdnConfigSchema.parse(v??{}))});var defaultDashboardPluginOptions={basePath:"/dashboard",auth:{enabled:false},cdn:{}};function isDashboardEnabled(options){if(options.enabled!==void 0){return options.enabled}return process.env["NODE_ENV"]!=="production"}var publishedOptions;function publishDashboardOptions(options){if(publishedOptions&&!sameConfiguration(publishedOptions,options)){if(!sameAuth(publishedOptions,options)){throw new Error("[frontmcp:dashboard] A second, different dashboard AUTH configuration was registered in this process. Dashboard options are process-wide, so accepting it would apply one server's token to another. Run one dashboard per process, or give both servers the same auth configuration.")}console.warn("[frontmcp:dashboard] A second, different dashboard configuration was registered in this process. Dashboard options are process-wide, so the last one wins for every server here. Run one dashboard per process until per-instance configuration is supported.")}publishedOptions=options}function sameAuth(a,b){return a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token}function sameConfiguration(a,b){return a.basePath===b.basePath&&a.enabled===b.enabled&&a.auth?.enabled===b.auth?.enabled&&a.auth?.token===b.auth?.token&&JSON.stringify(a.cdn??{})===JSON.stringify(b.cdn??{})}function resolveDashboardOptions(fallback={}){if(publishedOptions)return publishedOptions;return dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...fallback})}function resetDashboardOptions(){publishedOptions=void 0}var DashboardConfigToken=Symbol("DashboardConfig");var GraphDataProviderToken=Symbol("GraphDataProvider");var ParentScopeToken=Symbol("ParentScope");var DashboardPlugin=class extends import_sdk.DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(this.options)}static dynamicProviders(options){const parsedOptions=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options});publishDashboardOptions(parsedOptions);return[{name:"dashboard:config",provide:DashboardConfigToken,useValue:parsedOptions}]}};DashboardPlugin=__decorateClass([(0,import_sdk.Plugin)({name:"dashboard",description:"Visual dashboard for FrontMCP server monitoring and visualization",providers:[]})],DashboardPlugin);var import_sdk8=require("@frontmcp/sdk");var import_sdk2=require("@frontmcp/sdk");var import_utils=require("@frontmcp/utils");var DASHBOARD_TOKEN_HEADER="x-frontmcp-dashboard-token";var DASHBOARD_SESSION_COOKIE="frontmcp_dashboard";var AUTHORIZED={authorized:true};function readHeader(headers,name){if(!headers)return void 0;const key=Object.keys(headers).find(k=>k.toLowerCase()===name);const value=key?headers[key]:void 0;return Array.isArray(value)?value[0]:value}function readQuery(query,name){const value=query?.[name];return Array.isArray(value)?value[0]:value}function tokenMatches(presented,expected){if(presented.length===0||expected.length===0)return false;return(0,import_utils.timingSafeEqual)((0,import_utils.sha256)(presented),(0,import_utils.sha256)(expected))}var encoder=new TextEncoder;function deriveDashboardCookieValue(token){return(0,import_utils.base64urlEncode)((0,import_utils.hmacSha256)(encoder.encode(token),encoder.encode("frontmcp-dashboard-session:v1")))}function readCookie(header,name){if(!header)return void 0;for(const part of header.split(";")){const separator=part.indexOf("=");if(separator===-1)continue;if(part.slice(0,separator).trim()===name)return part.slice(separator+1).trim()}return void 0}function readBearerToken(header){if(!header)return void 0;const separator=header.indexOf(" ");if(separator===-1)return void 0;if(header.slice(0,separator).toLowerCase()!=="bearer")return void 0;const token=header.slice(separator+1).trim();return token.length>0?token:void 0}function createDashboardAuthValidator(auth,surface="page"){if(!auth?.enabled)return void 0;const expected=auth.token;const expectedCookie=expected?deriveDashboardCookieValue(expected):void 0;return req=>{if(!expected||!expectedCookie){return{authorized:false,status:401,message:"Dashboard authentication is misconfigured"}}const bearer=readBearerToken(readHeader(req.headers,"authorization"));if(bearer&&tokenMatches(bearer,expected))return AUTHORIZED;if(surface==="mcp"){const headerToken=readHeader(req.headers,DASHBOARD_TOKEN_HEADER);if(headerToken&&tokenMatches(headerToken,expected))return AUTHORIZED}else{const queryToken=readQuery(req.query,"token");if(queryToken&&tokenMatches(queryToken,expected))return AUTHORIZED}const cookie=readCookie(readHeader(req.headers,"cookie"),DASHBOARD_SESSION_COOKIE);if(cookie&&tokenMatches(cookie,expectedCookie))return AUTHORIZED;return{authorized:false,status:401,message:"Unauthorized"}}}var GATE_PASSED=Symbol("frontmcp:dashboard:gate-passed");function markDashboardGatePassed(ctx){ctx?.set(GATE_PASSED,true)}var DashboardDisabledError=class extends import_sdk2.PublicMcpError{mcpErrorCode=import_sdk2.MCP_ERROR_CODES.FORBIDDEN;constructor(){super("The FrontMCP dashboard is disabled.","DASHBOARD_DISABLED",404)}};var DashboardUnauthorizedError=class extends import_sdk2.PublicMcpError{mcpErrorCode=import_sdk2.MCP_ERROR_CODES.UNAUTHORIZED;constructor(){super(`The dashboard token is required. Send it in the ${DASHBOARD_TOKEN_HEADER} header.`,"DASHBOARD_UNAUTHORIZED",401,'Bearer realm="frontmcp-dashboard"')}};function assertDashboardAccess(options,ctx){if(!isDashboardEnabled(options)){throw new DashboardDisabledError}if(!options.auth?.enabled)return;if(ctx?.get(GATE_PASSED)===true)return;const expected=options.auth.token;const presented=ctx?.metadata?.customHeaders?.[DASHBOARD_TOKEN_HEADER];if(expected&&presented&&tokenMatches(presented,expected))return;throw new DashboardUnauthorizedError}function assertDashboardToolAccess(tool){const configured=tool.tryGet(DashboardConfigToken);assertDashboardAccess(configured??resolveDashboardOptions(),tool.tryGetContext())}var import_sdk3=require("@frontmcp/sdk");var GraphDataProvider=class{constructor(scope,serverName,serverVersion){this.scope=scope;this.serverName=serverName;this.serverVersion=serverVersion}scope;serverName;serverVersion;cachedData=null;cacheTimestamp=0;cacheTTL=5e3;async getGraphData(){const now=Date.now();if(this.cachedData&&now-this.cacheTimestamp<this.cacheTTL){return this.cachedData}this.cachedData=this.extractGraphData();this.cacheTimestamp=now;return this.cachedData}invalidateCache(){this.cachedData=null;this.cacheTimestamp=0}getMonitoredScopes(){try{let scopeRegistries=this.scope.providers.getRegistries("ScopeRegistry");if(!scopeRegistries||scopeRegistries.length===0){let currentProviders=this.scope.providers.parentProviders;while(currentProviders&&(!scopeRegistries||scopeRegistries.length===0)){scopeRegistries=currentProviders.getRegistries?.("ScopeRegistry")||[];currentProviders=currentProviders.parentProviders}}if(!scopeRegistries||scopeRegistries.length===0){return[this.scope]}const scopeRegistry=scopeRegistries[0];const allScopes=scopeRegistry.getScopes();return allScopes.filter(s=>s.id!=="dashboard")}catch{return[this.scope]}}extractGraphData(){const nodes=[];const edges=[];const addedNodeIds=new Set;const addNode=node=>{if(!addedNodeIds.has(node.id)){nodes.push(node);addedNodeIds.add(node.id)}};const serverId=`server:${this.serverName}`;addNode({id:serverId,type:"server",label:this.serverName,data:{name:this.serverName,description:`FrontMCP Server v${this.serverVersion||"unknown"}`}});const scopes=this.getMonitoredScopes();for(const scope of scopes){const scopeId=`scope:${scope.id}`;addNode({id:scopeId,type:"scope",label:scope.id,data:{name:scope.id}});edges.push({id:`${serverId}->${scopeId}`,source:serverId,target:scopeId,type:"contains"});this.extractToolsFromScope(scope,scopeId,edges,addNode);this.extractResourcesFromScope(scope,scopeId,edges,addNode);this.extractResourceTemplatesFromScope(scope,scopeId,edges,addNode);this.extractPromptsFromScope(scope,scopeId,edges,addNode);this.extractAppsFromScope(scope,scopeId,edges,addNode)}return{nodes,edges,metadata:this.createMetadata(nodes.length,edges.length)}}extractToolsFromScope(scope,scopeId,edges,addNode){try{const tools=scope.tools?.getTools?.(true)||[];for(const tool of tools){if(tool.fullName?.startsWith("dashboard:")){continue}const toolId=`tool:${tool.fullName}`;addNode({id:toolId,type:"tool",label:tool.name,data:{name:tool.fullName,description:tool.metadata?.description,inputSchema:tool.inputSchema,outputSchema:tool.outputSchema,tags:tool.metadata?.tags,annotations:tool.metadata?.annotations}});edges.push({id:`${scopeId}->${toolId}`,source:scopeId,target:toolId,type:"provides"})}}catch{}}extractResourcesFromScope(scope,scopeId,edges,addNode){try{const resources=scope.resources?.getResources?.(true)||[];for(const resource of resources){const resourceId=`resource:${resource.uri}`;addNode({id:resourceId,type:"resource",label:resource.name,data:{name:resource.name,description:resource.metadata?.description,uri:resource.uri,mimeType:resource.metadata?.mimeType}});edges.push({id:`${scopeId}->${resourceId}`,source:scopeId,target:resourceId,type:"provides"})}}catch{}}extractResourceTemplatesFromScope(scope,scopeId,edges,addNode){try{const templates=scope.resources?.getResourceTemplates?.()||[];for(const template of templates){const templateId=`resource-template:${template.uriTemplate}`;addNode({id:templateId,type:"resource-template",label:template.name,data:{name:template.name,description:template.metadata?.description,uri:template.uriTemplate,mimeType:template.metadata?.mimeType}});edges.push({id:`${scopeId}->${templateId}`,source:scopeId,target:templateId,type:"provides"})}}catch{}}extractPromptsFromScope(scope,scopeId,edges,addNode){try{const prompts=scope.prompts?.getPrompts?.(true)||[];for(const prompt of prompts){const promptId=`prompt:${prompt.name}`;addNode({id:promptId,type:"prompt",label:prompt.name,data:{name:prompt.name,description:prompt.metadata?.description,arguments:prompt.metadata?.arguments}});edges.push({id:`${scopeId}->${promptId}`,source:scopeId,target:promptId,type:"provides"})}}catch{}}extractAppsFromScope(scope,scopeId,edges,addNode){try{const apps=scope.apps?.getApps?.()||[];for(const app of apps){const appName=app.metadata?.name||"unknown";if(appName==="dashboard"){continue}const appId=`app:${appName}`;addNode({id:appId,type:"app",label:appName,data:{name:appName,description:app.metadata?.description}});edges.push({id:`${scopeId}->${appId}`,source:scopeId,target:appId,type:"contains"})}}catch{}}createMetadata(nodeCount,edgeCount){return{serverName:this.serverName,serverVersion:this.serverVersion,generatedAt:new Date().toISOString(),entryFile:"runtime",nodeCount,edgeCount}}};GraphDataProvider=__decorateClass([(0,import_sdk3.Provider)({name:"graph-data-provider"})],GraphDataProvider);var import_lazy_zod2=require("@frontmcp/lazy-zod");var import_sdk4=require("@frontmcp/sdk");var graphToolInputSchema={includeSchemas:import_lazy_zod2.z.boolean().optional().default(false).describe("Include full input/output schemas in the response"),refresh:import_lazy_zod2.z.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")};var GraphTool=class extends import_sdk4.ToolContext{async execute(input){assertDashboardToolAccess(this);const graphProvider=this.get(GraphDataProvider);if(input.refresh){graphProvider.invalidateCache()}const graphData=await graphProvider.getGraphData();if(!input.includeSchemas){return{...graphData,nodes:graphData.nodes.map(node=>({...node,data:{...node.data,inputSchema:void 0,outputSchema:void 0}}))}}return graphData}};GraphTool=__decorateClass([(0,import_sdk4.Tool)({name:"dashboard:graph",description:"Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",inputSchema:graphToolInputSchema,annotations:{readOnlyHint:true}})],GraphTool);var import_lazy_zod3=require("@frontmcp/lazy-zod");var import_sdk5=require("@frontmcp/sdk");function safeRegex(pattern){if(pattern.length>100){return null}try{const regex=new RegExp(pattern,"i");regex.test("test");return regex}catch{return null}}var listResourcesInputSchema={filter:import_lazy_zod3.z.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),includeTemplates:import_lazy_zod3.z.boolean().default(true).describe("Include resource templates in the response")};var listResourcesOutputSchema=import_lazy_zod3.z.object({resources:import_lazy_zod3.z.array(import_lazy_zod3.z.object({name:import_lazy_zod3.z.string(),uri:import_lazy_zod3.z.string(),description:import_lazy_zod3.z.string().optional(),mimeType:import_lazy_zod3.z.string().optional(),isTemplate:import_lazy_zod3.z.boolean()})),count:import_lazy_zod3.z.number()});var ListResourcesTool=class extends import_sdk5.ToolContext{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;const results=[];try{const resources=targetScope.resources?.getResources?.(true)||[];for(const resource of resources){results.push({name:resource.name||"unnamed",uri:resource.uri||"unknown",description:resource.metadata?.description,mimeType:resource.metadata?.mimeType,isTemplate:false})}}catch{}if(input.includeTemplates){try{const templates=targetScope.resources?.getResourceTemplates?.()||[];for(const template of templates){results.push({name:template.name||"unnamed",uri:template.uriTemplate||"unknown",description:template.metadata?.description,mimeType:template.metadata?.mimeType,isTemplate:true})}}catch{}}let filtered=results;if(input.filter){const pattern=safeRegex(input.filter);if(pattern){filtered=results.filter(r=>pattern.test(r.name)||pattern.test(r.uri))}}return{resources:filtered,count:filtered.length}}};ListResourcesTool=__decorateClass([(0,import_sdk5.Tool)({name:"dashboard:list-resources",description:"List all resources and resource templates registered in the monitored FrontMCP server.",inputSchema:listResourcesInputSchema,outputSchema:listResourcesOutputSchema,annotations:{readOnlyHint:true}})],ListResourcesTool);var import_lazy_zod4=require("@frontmcp/lazy-zod");var import_sdk6=require("@frontmcp/sdk");var listToolsInputSchema={filter:import_lazy_zod4.z.string().optional().describe("Filter tools by name pattern (regex supported)"),includePlugins:import_lazy_zod4.z.boolean().default(true).describe("Include tools from plugins"),includeSchemas:import_lazy_zod4.z.boolean().default(false).describe("Include input/output schemas in the response")};var listToolsOutputSchema=import_lazy_zod4.z.object({tools:import_lazy_zod4.z.array(import_lazy_zod4.z.object({name:import_lazy_zod4.z.string(),fullName:import_lazy_zod4.z.string(),description:import_lazy_zod4.z.string().optional(),tags:import_lazy_zod4.z.array(import_lazy_zod4.z.string()).optional(),inputSchema:import_lazy_zod4.z.unknown().optional(),outputSchema:import_lazy_zod4.z.unknown().optional()})),count:import_lazy_zod4.z.number()});var ListToolsTool=class extends import_sdk6.ToolContext{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;let allTools=[];try{allTools=targetScope.tools?.getTools?.(input.includePlugins??true)||[]}catch{}if(input.filter){const pattern=safeRegex(input.filter);if(pattern){allTools=allTools.filter(t=>pattern.test(t.name)||pattern.test(t.fullName))}}const tools=allTools.map(tool=>({name:tool.name,fullName:tool.fullName,description:tool.metadata?.description,tags:tool.metadata?.tags,...input.includeSchemas?{inputSchema:tool.inputSchema,outputSchema:tool.outputSchema}:{}}));return{tools,count:tools.length}}};ListToolsTool=__decorateClass([(0,import_sdk6.Tool)({name:"dashboard:list-tools",description:"List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",inputSchema:listToolsInputSchema,outputSchema:listToolsOutputSchema,annotations:{readOnlyHint:true}})],ListToolsTool);var import_sdk7=require("@frontmcp/sdk");function escapeForJs(str){return str.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/</g,"\\x3c").replace(/>/g,"\\x3e").replace(/\n/g,"\\n").replace(/\r/g,"\\r")}function generateDashboardHtml(options,mcpPath=options.basePath){if(options.cdn.entrypoint){return generateExternalEntrypointHtml(options,mcpPath)}return generateInlineDashboardHtml(options,mcpPath)}function generateExternalEntrypointHtml(options,mcpPath){const{cdn}=options;const safeBasePath=escapeForJs(options.basePath);const safeMcpPath=escapeForJs(mcpPath);const safeReact=escapeForJs(cdn.react);const safeReactDom=escapeForJs(cdn.reactDom);const safeReactDomClient=escapeForJs(cdn.reactDomClient);const safeReactJsxRuntime=escapeForJs(cdn.reactJsxRuntime);const safeXyflow=escapeForJs(cdn.xyflow);const safeDagre=escapeForJs(cdn.dagre);const safeXyflowCss=escapeForJs(cdn.xyflowCss);const safeEntrypoint=escapeForJs(cdn.entrypoint||"");return`<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
@@ -545,4 +545,4 @@
|
|
|
545
545
|
);
|
|
546
546
|
</script>
|
|
547
547
|
</body>
|
|
548
|
-
</html>`}var import_sdk3=require("@frontmcp/sdk");var GraphDataProvider=class{constructor(scope,serverName,serverVersion){this.scope=scope;this.serverName=serverName;this.serverVersion=serverVersion}scope;serverName;serverVersion;cachedData=null;cacheTimestamp=0;cacheTTL=5e3;async getGraphData(){const now=Date.now();if(this.cachedData&&now-this.cacheTimestamp<this.cacheTTL){return this.cachedData}this.cachedData=this.extractGraphData();this.cacheTimestamp=now;return this.cachedData}invalidateCache(){this.cachedData=null;this.cacheTimestamp=0}getMonitoredScopes(){try{let scopeRegistries=this.scope.providers.getRegistries("ScopeRegistry");if(!scopeRegistries||scopeRegistries.length===0){let currentProviders=this.scope.providers.parentProviders;while(currentProviders&&(!scopeRegistries||scopeRegistries.length===0)){scopeRegistries=currentProviders.getRegistries?.("ScopeRegistry")||[];currentProviders=currentProviders.parentProviders}}if(!scopeRegistries||scopeRegistries.length===0){return[this.scope]}const scopeRegistry=scopeRegistries[0];const allScopes=scopeRegistry.getScopes();return allScopes.filter(s=>s.id!=="dashboard")}catch{return[this.scope]}}extractGraphData(){const nodes=[];const edges=[];const addedNodeIds=new Set;const addNode=node=>{if(!addedNodeIds.has(node.id)){nodes.push(node);addedNodeIds.add(node.id)}};const serverId=`server:${this.serverName}`;addNode({id:serverId,type:"server",label:this.serverName,data:{name:this.serverName,description:`FrontMCP Server v${this.serverVersion||"unknown"}`}});const scopes=this.getMonitoredScopes();for(const scope of scopes){const scopeId=`scope:${scope.id}`;addNode({id:scopeId,type:"scope",label:scope.id,data:{name:scope.id}});edges.push({id:`${serverId}->${scopeId}`,source:serverId,target:scopeId,type:"contains"});this.extractToolsFromScope(scope,scopeId,edges,addNode);this.extractResourcesFromScope(scope,scopeId,edges,addNode);this.extractResourceTemplatesFromScope(scope,scopeId,edges,addNode);this.extractPromptsFromScope(scope,scopeId,edges,addNode);this.extractAppsFromScope(scope,scopeId,edges,addNode)}return{nodes,edges,metadata:this.createMetadata(nodes.length,edges.length)}}extractToolsFromScope(scope,scopeId,edges,addNode){try{const tools=scope.tools?.getTools?.(true)||[];for(const tool of tools){if(tool.fullName?.startsWith("dashboard:")){continue}const toolId=`tool:${tool.fullName}`;addNode({id:toolId,type:"tool",label:tool.name,data:{name:tool.fullName,description:tool.metadata?.description,inputSchema:tool.inputSchema,outputSchema:tool.outputSchema,tags:tool.metadata?.tags,annotations:tool.metadata?.annotations}});edges.push({id:`${scopeId}->${toolId}`,source:scopeId,target:toolId,type:"provides"})}}catch{}}extractResourcesFromScope(scope,scopeId,edges,addNode){try{const resources=scope.resources?.getResources?.(true)||[];for(const resource of resources){const resourceId=`resource:${resource.uri}`;addNode({id:resourceId,type:"resource",label:resource.name,data:{name:resource.name,description:resource.metadata?.description,uri:resource.uri,mimeType:resource.metadata?.mimeType}});edges.push({id:`${scopeId}->${resourceId}`,source:scopeId,target:resourceId,type:"provides"})}}catch{}}extractResourceTemplatesFromScope(scope,scopeId,edges,addNode){try{const templates=scope.resources?.getResourceTemplates?.()||[];for(const template of templates){const templateId=`resource-template:${template.uriTemplate}`;addNode({id:templateId,type:"resource-template",label:template.name,data:{name:template.name,description:template.metadata?.description,uri:template.uriTemplate,mimeType:template.metadata?.mimeType}});edges.push({id:`${scopeId}->${templateId}`,source:scopeId,target:templateId,type:"provides"})}}catch{}}extractPromptsFromScope(scope,scopeId,edges,addNode){try{const prompts=scope.prompts?.getPrompts?.(true)||[];for(const prompt of prompts){const promptId=`prompt:${prompt.name}`;addNode({id:promptId,type:"prompt",label:prompt.name,data:{name:prompt.name,description:prompt.metadata?.description,arguments:prompt.metadata?.arguments}});edges.push({id:`${scopeId}->${promptId}`,source:scopeId,target:promptId,type:"provides"})}}catch{}}extractAppsFromScope(scope,scopeId,edges,addNode){try{const apps=scope.apps?.getApps?.()||[];for(const app of apps){const appName=app.metadata?.name||"unknown";if(appName==="dashboard"){continue}const appId=`app:${appName}`;addNode({id:appId,type:"app",label:appName,data:{name:appName,description:app.metadata?.description}});edges.push({id:`${scopeId}->${appId}`,source:scopeId,target:appId,type:"contains"})}}catch{}}createMetadata(nodeCount,edgeCount){return{serverName:this.serverName,serverVersion:this.serverVersion,generatedAt:new Date().toISOString(),entryFile:"runtime",nodeCount,edgeCount}}};GraphDataProvider=__decorateClass([(0,import_sdk3.Provider)({name:"graph-data-provider"})],GraphDataProvider);var import_lazy_zod4=require("@frontmcp/lazy-zod");var import_sdk4=require("@frontmcp/sdk");var graphToolInputSchema={includeSchemas:import_lazy_zod4.z.boolean().optional().default(false).describe("Include full input/output schemas in the response"),refresh:import_lazy_zod4.z.boolean().optional().default(false).describe("Force refresh the graph data (bypass cache)")};var GraphTool=class extends import_sdk4.ToolContext{async execute(input){assertDashboardToolAccess(this);const graphProvider=this.get(GraphDataProvider);if(input.refresh){graphProvider.invalidateCache()}const graphData=await graphProvider.getGraphData();if(!input.includeSchemas){return{...graphData,nodes:graphData.nodes.map(node=>({...node,data:{...node.data,inputSchema:void 0,outputSchema:void 0}}))}}return graphData}};GraphTool=__decorateClass([(0,import_sdk4.Tool)({name:"dashboard:graph",description:"Get the server graph showing all registered tools, resources, prompts, apps, and their relationships. Returns nodes and edges that can be visualized.",inputSchema:graphToolInputSchema,annotations:{readOnlyHint:true}})],GraphTool);var import_lazy_zod5=require("@frontmcp/lazy-zod");var import_sdk5=require("@frontmcp/sdk");function safeRegex(pattern){if(pattern.length>100){return null}try{const regex=new RegExp(pattern,"i");regex.test("test");return regex}catch{return null}}var listResourcesInputSchema={filter:import_lazy_zod5.z.string().optional().describe("Filter resources by name or URI pattern (regex supported)"),includeTemplates:import_lazy_zod5.z.boolean().default(true).describe("Include resource templates in the response")};var listResourcesOutputSchema=import_lazy_zod5.z.object({resources:import_lazy_zod5.z.array(import_lazy_zod5.z.object({name:import_lazy_zod5.z.string(),uri:import_lazy_zod5.z.string(),description:import_lazy_zod5.z.string().optional(),mimeType:import_lazy_zod5.z.string().optional(),isTemplate:import_lazy_zod5.z.boolean()})),count:import_lazy_zod5.z.number()});var ListResourcesTool=class extends import_sdk5.ToolContext{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;const results=[];try{const resources=targetScope.resources?.getResources?.(true)||[];for(const resource of resources){results.push({name:resource.name||"unnamed",uri:resource.uri||"unknown",description:resource.metadata?.description,mimeType:resource.metadata?.mimeType,isTemplate:false})}}catch{}if(input.includeTemplates){try{const templates=targetScope.resources?.getResourceTemplates?.()||[];for(const template of templates){results.push({name:template.name||"unnamed",uri:template.uriTemplate||"unknown",description:template.metadata?.description,mimeType:template.metadata?.mimeType,isTemplate:true})}}catch{}}let filtered=results;if(input.filter){const pattern=safeRegex(input.filter);if(pattern){filtered=results.filter(r=>pattern.test(r.name)||pattern.test(r.uri))}}return{resources:filtered,count:filtered.length}}};ListResourcesTool=__decorateClass([(0,import_sdk5.Tool)({name:"dashboard:list-resources",description:"List all resources and resource templates registered in the monitored FrontMCP server.",inputSchema:listResourcesInputSchema,outputSchema:listResourcesOutputSchema,annotations:{readOnlyHint:true}})],ListResourcesTool);var import_lazy_zod6=require("@frontmcp/lazy-zod");var import_sdk6=require("@frontmcp/sdk");var listToolsInputSchema={filter:import_lazy_zod6.z.string().optional().describe("Filter tools by name pattern (regex supported)"),includePlugins:import_lazy_zod6.z.boolean().default(true).describe("Include tools from plugins"),includeSchemas:import_lazy_zod6.z.boolean().default(false).describe("Include input/output schemas in the response")};var listToolsOutputSchema=import_lazy_zod6.z.object({tools:import_lazy_zod6.z.array(import_lazy_zod6.z.object({name:import_lazy_zod6.z.string(),fullName:import_lazy_zod6.z.string(),description:import_lazy_zod6.z.string().optional(),tags:import_lazy_zod6.z.array(import_lazy_zod6.z.string()).optional(),inputSchema:import_lazy_zod6.z.unknown().optional(),outputSchema:import_lazy_zod6.z.unknown().optional()})),count:import_lazy_zod6.z.number()});var ListToolsTool=class extends import_sdk6.ToolContext{async execute(input){assertDashboardToolAccess(this);const parentScope=this.tryGet(ParentScopeToken);const targetScope=parentScope||this.scope;let allTools=[];try{allTools=targetScope.tools?.getTools?.(input.includePlugins??true)||[]}catch{}if(input.filter){const pattern=safeRegex(input.filter);if(pattern){allTools=allTools.filter(t=>pattern.test(t.name)||pattern.test(t.fullName))}}const tools=allTools.map(tool=>({name:tool.name,fullName:tool.fullName,description:tool.metadata?.description,tags:tool.metadata?.tags,...input.includeSchemas?{inputSchema:tool.inputSchema,outputSchema:tool.outputSchema}:{}}));return{tools,count:tools.length}}};ListToolsTool=__decorateClass([(0,import_sdk6.Tool)({name:"dashboard:list-tools",description:"List all tools registered in the monitored FrontMCP server. Returns tool names, descriptions, and optionally schemas.",inputSchema:listToolsInputSchema,outputSchema:listToolsOutputSchema,annotations:{readOnlyHint:true}})],ListToolsTool);var DashboardMiddlewareToken=Symbol("dashboard:middleware");function sessionCookies(options,paths,secure){const token=options.auth.token;if(!options.auth.enabled||!token)return[];const value=deriveDashboardCookieValue(token);return paths.map(path=>`${DASHBOARD_SESSION_COOKIE}=${value}; Path=${path}; HttpOnly; SameSite=Strict${secure?"; Secure":""}`)}function isHttps(req){const socket=req.socket;const forwarded=req.headers?.["x-forwarded-proto"];return socket?.encrypted===true||typeof forwarded==="string"&&forwarded.split(",")[0]?.trim()==="https"}function createDashboardMiddleware(options,mcpPath){const html=generateDashboardHtml(options,mcpPath);const authorize=createDashboardAuthValidator(options.auth,"page");const cookiePaths=[...new Set([mcpPath,options.basePath])];return async(req,res,next)=>{if(!isDashboardEnabled(options)){return next()}const urlPath=req.path||req.url||"/";const method=(req.method||"GET").toUpperCase();const isPageRequest=method==="GET"&&(urlPath==="/"||urlPath==="");if(!isPageRequest){return next()}if(authorize){const result=authorize({headers:req.headers,query:req.query});if(!result.authorized){res.status(result.status??401).json({error:"Unauthorized",message:result.message??"Unauthorized"});return}}const writer=res;writer.setHeader?.("Content-Type","text/html");const cookies=sessionCookies(options,cookiePaths,isHttps(req));if(cookies.length>0)writer.setHeader?.("Set-Cookie",cookies);res.status(200).send(html)}}var DashboardHttpPlugin=class extends import_sdk7.DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options})}async gateDashboardMcp(flowCtx){const options=this.resolve(DashboardConfigToken)??resolveDashboardOptions();if(!isDashboardEnabled(options)){throw new DashboardDisabledError}const authorize=createDashboardAuthValidator(options.auth,"mcp");if(!authorize)return;const request=flowCtx.rawInput.request;const result=authorize({headers:request.headers,query:request.query});if(!result.authorized){throw new DashboardUnauthorizedError}markDashboardGatePassed(this.resolve(import_sdk7.FrontMcpContextStorage)?.getStore())}resolve(token){try{return this.get(token)}catch{return void 0}}static dynamicProviders(options){return[{name:"dashboard:config",provide:DashboardConfigToken,inject:()=>[],useFactory:()=>resolveDashboardOptions(options)},{name:"dashboard:middleware",provide:DashboardMiddlewareToken,inject:()=>[import_sdk7.FrontMcpServer,import_sdk7.ScopeEntry],useFactory:(server,scope)=>{const effectiveOptions=resolveDashboardOptions(options);const mcpPath=scope.fullPath;server.registerMiddleware(effectiveOptions.basePath,createDashboardMiddleware(effectiveOptions,mcpPath));return{registered:true,mcpPath}}}]}};__decorateClass([import_sdk7.HttpHook.Will("handleMcp2026",{priority:1e3})],DashboardHttpPlugin.prototype,"gateDashboardMcp",1);DashboardHttpPlugin=__decorateClass([(0,import_sdk7.Plugin)({name:"dashboard:http",description:"Dashboard HTTP handler for serving UI HTML"})],DashboardHttpPlugin);var DashboardApp=class{};DashboardApp=__decorateClass([(0,import_sdk7.App)({name:"dashboard",description:"FrontMCP Dashboard for visualization and monitoring",providers:[{name:"dashboard:parent-scope",provide:ParentScopeToken,inject:()=>[import_sdk7.ScopeEntry],useFactory:scope=>{return scope}},{name:"dashboard:graph-data",provide:GraphDataProvider,inject:()=>[import_sdk7.ScopeEntry,import_sdk7.FrontMcpConfig],useFactory:(scope,config)=>{const serverName=config.info?.name||"FrontMCP Server";const serverVersion=config.info?.version;return new GraphDataProvider(scope,serverName,serverVersion)}}],plugins:[DashboardHttpPlugin.init({})],tools:[GraphTool,ListToolsTool,ListResourcesTool],standalone:true})],DashboardApp);0&&(module.exports={DashboardApp,DashboardConfigToken,DashboardPlugin,GraphDataProvider,GraphDataProviderToken,GraphTool,ListResourcesTool,ListToolsTool,ParentScopeToken,dashboardPluginOptionsSchema,defaultDashboardPluginOptions,isDashboardEnabled,resetDashboardOptions});
|
|
548
|
+
</html>`}function isHttps(request){const socket=request.socket;const forwarded=request.headers["x-forwarded-proto"];const protocol=request.protocol;return socket?.encrypted===true||protocol==="https"||typeof forwarded==="string"&&forwarded.split(",")[0]?.trim()==="https"}function sessionCookies(options,paths,secure){const token=options.auth.token;if(!options.auth.enabled||!token)return[];const value=deriveDashboardCookieValue(token);return paths.map(path=>({name:DASHBOARD_SESSION_COOKIE,value,path,httpOnly:true,sameSite:"strict",secure}))}var DashboardPageRoute=class extends import_sdk7.HttpRouteContext{handle(){const options=resolveDashboardOptions();if(!isDashboardEnabled(options))throw new DashboardDisabledError;const authorize=createDashboardAuthValidator(options.auth,"page");const result=authorize?.({headers:this.request.headers,query:this.request.query});if(result&&!result.authorized){return import_sdk7.httpRespond.json({error:"Unauthorized",message:result.message??"Unauthorized"},{status:result.status??401})}const mcpPath=this.scope.fullPath;const cookies=sessionCookies(options,[...new Set([mcpPath,options.basePath])],isHttps(this.request));return{...import_sdk7.httpRespond.html(generateDashboardHtml(options,mcpPath)),cookies}}};DashboardPageRoute=__decorateClass([(0,import_sdk7.HttpRoute)({method:"GET",path:()=>resolveDashboardOptions().basePath,auth:"public"})],DashboardPageRoute);var DashboardHttpPlugin=class extends import_sdk8.DynamicPlugin{options;constructor(options={}){super();this.options=dashboardPluginOptionsSchema.parse({...defaultDashboardPluginOptions,...options})}async gateDashboardMcp(flowCtx){const options=this.resolve(DashboardConfigToken)??resolveDashboardOptions();if(!isDashboardEnabled(options)){throw new DashboardDisabledError}const authorize=createDashboardAuthValidator(options.auth,"mcp");if(!authorize)return;const request=flowCtx.rawInput.request;const result=authorize({headers:request.headers,query:request.query});if(!result.authorized){throw new DashboardUnauthorizedError}markDashboardGatePassed(this.resolve(import_sdk8.FrontMcpContextStorage)?.getStore())}resolve(token){try{return this.get(token)}catch{return void 0}}static dynamicProviders(options){return[{name:"dashboard:config",provide:DashboardConfigToken,inject:()=>[],useFactory:()=>resolveDashboardOptions(options)}]}};__decorateClass([import_sdk8.HttpHook.Will("handleMcp2026",{priority:1e3})],DashboardHttpPlugin.prototype,"gateDashboardMcp",1);DashboardHttpPlugin=__decorateClass([(0,import_sdk8.Plugin)({name:"dashboard:http",description:"Dashboard HTTP handler for serving UI HTML"})],DashboardHttpPlugin);var DashboardApp=class{};DashboardApp=__decorateClass([(0,import_sdk8.App)({name:"dashboard",description:"FrontMCP Dashboard for visualization and monitoring",providers:[{name:"dashboard:parent-scope",provide:ParentScopeToken,inject:()=>[import_sdk8.ScopeEntry],useFactory:scope=>{return scope}},{name:"dashboard:graph-data",provide:GraphDataProvider,inject:()=>[import_sdk8.ScopeEntry,import_sdk8.FrontMcpConfig],useFactory:(scope,config)=>{const serverName=config.info?.name||"FrontMCP Server";const serverVersion=config.info?.version;return new GraphDataProvider(scope,serverName,serverVersion)}}],plugins:[DashboardHttpPlugin.init({})],tools:[GraphTool,ListToolsTool,ListResourcesTool],routes:[DashboardPageRoute],standalone:true})],DashboardApp);0&&(module.exports={DashboardApp,DashboardConfigToken,DashboardPlugin,GraphDataProvider,GraphDataProviderToken,GraphTool,ListResourcesTool,ListToolsTool,ParentScopeToken,dashboardPluginOptionsSchema,defaultDashboardPluginOptions,isDashboardEnabled,resetDashboardOptions});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/plugin-dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0-rc.2",
|
|
4
4
|
"description": "Dashboard plugin for FrontMCP: a web page and MCP endpoint that show a server's apps, tools, resources and prompts",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -49,14 +49,14 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@frontmcp/sdk": "1.
|
|
52
|
+
"@frontmcp/sdk": "1.11.0-rc.2",
|
|
53
53
|
"reflect-metadata": "^0.2.2"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@
|
|
56
|
+
"@frontmcp/utils": "1.11.0-rc.2"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@frontmcp/lazy-zod": "1.
|
|
60
|
-
"@frontmcp/sdk": "1.
|
|
59
|
+
"@frontmcp/lazy-zod": "1.11.0-rc.2",
|
|
60
|
+
"@frontmcp/sdk": "1.11.0-rc.2"
|
|
61
61
|
}
|
|
62
62
|
}
|