@frontmcp/plugin-remember 1.10.16 → 1.11.0-rc.1

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/README.md CHANGED
@@ -93,6 +93,10 @@ Every option is optional. `RememberPlugin.init()` and `plugins: [RememberPlugin]
93
93
  | `'vercel-kv'` | Vercel KV, through `@vercel/kv`. |
94
94
  | `'global-store'` | The store in `@FrontMcp({ redis })`: Redis, or Vercel KV. Without `redis`, the server doesn't start. |
95
95
 
96
+ A browser build (`create()` in a web page, WebMCP) has no TCP socket for Redis: there `'redis'` and `'redis-client'`
97
+ throw `BrowserStoreUnavailableError` from `init()`, and `'global-store'` on a Redis `redis` throws it when the server
98
+ starts, before anything connects. The message names `type: 'memory'`. Vercel KV, over HTTPS, is allowed.
99
+
96
100
  ## `this.remember`
97
101
 
98
102
  `this.remember` is available in tools, resources, prompts and agents. Every method takes a `scope` option, which
package/esm/index.mjs CHANGED
@@ -1,5 +1,8 @@
1
- var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __defNormalProp=(obj,key,value)=>key in obj?__defProp(obj,key,{enumerable:true,configurable:true,writable:true,value}):obj[key]=value;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 __publicField=(obj,key,value)=>__defNormalProp(obj,typeof key!=="symbol"?key+"":key,value);import{DynamicPlugin,FRONTMCP_CONTEXT,FrontMcpConfig,FrontMcpLogger,getGlobalStoreConfig,isVercelKvProvider,Plugin,ProviderScope as ProviderScope5}from"@frontmcp/sdk";import{getRunningTool,isAnonymousSubject,Provider,ProviderScope,STATELESS_SESSION_ID}from"@frontmcp/sdk";import{base64urlDecode,base64urlEncode as base64urlEncode2,decryptAesGcm,encryptAesGcm,hkdfSha256,randomBytes as randomBytes2}from"@frontmcp/utils";import*as path from"path";import{z}from"@frontmcp/lazy-zod";import{base64urlEncode,mkdir,randomBytes,readFile,rename,unlink,writeFile}from"@frontmcp/utils";var DEFAULT_SECRET_FILE_PATH=".frontmcp/remember-secret.json";var SECRET_BYTES=32;var rememberSecretDataSchema=z.object({secret:z.string().min(32),createdAt:z.number().positive().int(),version:z.literal(1)});function validateSecretData(data){const result=rememberSecretDataSchema.safeParse(data);if(!result.success){return{valid:false,error:result.error.issues[0]?.message??"Invalid secret structure"}}const parsed=result.data;const now=Date.now();if(parsed.createdAt>now+6e4){return{valid:false,error:"createdAt is in the future"}}const hundredYearsMs=100*365*24*60*60*1e3;if(parsed.createdAt<now-hundredYearsMs){return{valid:false,error:"createdAt is too old"}}return{valid:true}}function isSecretPersistenceEnabled(options){const isProduction=process.env["NODE_ENV"]==="production";if(isProduction){return options?.forceEnable===true}return true}function resolveSecretPath(options){const secretPath=options?.secretPath??DEFAULT_SECRET_FILE_PATH;if(path.isAbsolute(secretPath)){return secretPath}return path.resolve(process.cwd(),secretPath)}async function loadRememberSecret(options){if(!isSecretPersistenceEnabled(options)){return null}const secretPath=resolveSecretPath(options);try{const content=await readFile(secretPath,"utf8");const data=JSON.parse(content);const validation=validateSecretData(data);if(!validation.valid){console.warn(`[RememberSecretPersistence] Invalid secret file format at ${secretPath}: ${validation.error}, will regenerate`);return null}return data}catch(error){if(error.code==="ENOENT"){return null}console.warn(`[RememberSecretPersistence] Failed to load secret from ${secretPath}: ${error.message}`);return null}}async function saveRememberSecret(secretData,options){if(!isSecretPersistenceEnabled(options)){return true}const secretPath=resolveSecretPath(options);const dir=path.dirname(secretPath);const tempPath=`${secretPath}.tmp.${Date.now()}.${base64urlEncode(randomBytes(8))}`;try{await mkdir(dir,{recursive:true,mode:448});const content=JSON.stringify(secretData,null,2);await writeFile(tempPath,content,{mode:384});await rename(tempPath,secretPath);return true}catch(error){console.error(`[RememberSecretPersistence] Failed to save secret to ${secretPath}: ${error.message}`);try{await unlink(tempPath)}catch{}return false}}var cachedSecret=null;var secretGenerationPromise=null;function generateSecret(){return base64urlEncode(randomBytes(SECRET_BYTES))}async function getOrCreatePersistedSecret(options){if(cachedSecret){return cachedSecret}if(secretGenerationPromise){return secretGenerationPromise}secretGenerationPromise=(async()=>{try{const loaded=await loadRememberSecret(options);if(loaded){cachedSecret=loaded.secret;return cachedSecret}const newSecret=generateSecret();const secretData={secret:newSecret,createdAt:Date.now(),version:1};if(isSecretPersistenceEnabled(options)){await saveRememberSecret(secretData,options)}else{console.warn("[RememberSecretPersistence] No REMEMBER_SECRET (or MCP_MEMORY_SECRET / MCP_SESSION_SECRET) is set in production, so a random in-memory secret is used. Encrypted memory will be unreadable after a restart and by other instances sharing the store. Set REMEMBER_SECRET to the same value on every instance.")}cachedSecret=newSecret;return cachedSecret}finally{secretGenerationPromise=null}})();return secretGenerationPromise}async function getBaseSecret(){const envSecret=process.env["REMEMBER_SECRET"]||process.env["MCP_MEMORY_SECRET"]||process.env["MCP_SESSION_SECRET"];if(envSecret){return envSecret}return getOrCreatePersistedSecret()}async function secretFor(source){return source.secret?source.secret:getBaseSecret()}var textEncoder=new TextEncoder;async function deriveEncryptionKey(source){const salt=textEncoder.encode("remember-plugin-v1");let ikm;let context;switch(source.type){case"session":ikm=await secretFor(source)+source.sessionId;context=`remember:session:${source.sessionId}`;break;case"tool":ikm=await secretFor(source)+source.sessionId;context=`remember:tool:${source.toolName}:${source.sessionId}`;break;case"user":ikm=await secretFor(source)+source.userId;context=`remember:user:${source.userId}`;break;case"global":ikm=await secretFor(source);context="remember:global";break;case"custom":ikm=source.key;context=`remember:custom:${source.key}`;break}const ikmBytes=textEncoder.encode(ikm);const info=textEncoder.encode(context);return hkdfSha256(ikmBytes,salt,info,32)}function getKeySourceForScope(scope,context,secret){switch(scope){case"session":return{type:"session",sessionId:context.sessionId,secret};case"user":return{type:"user",userId:context.userId??"anonymous",secret};case"tool":return{type:"tool",toolName:context.toolName??"unknown",sessionId:context.sessionId,secret};case"global":return{type:"global",secret}}}var textDecoder=new TextDecoder;async function encryptValue(data,keySource){const key=await deriveEncryptionKey(keySource);const plaintext=textEncoder.encode(JSON.stringify(data));const iv=randomBytes2(12);const{ciphertext,tag}=encryptAesGcm(key,plaintext,iv);return{alg:"A256GCM",iv:base64urlEncode2(iv),tag:base64urlEncode2(tag),data:base64urlEncode2(ciphertext)}}async function decryptValue(blob,keySource){try{const key=await deriveEncryptionKey(keySource);const iv=base64urlDecode(blob.iv);const tag=base64urlDecode(blob.tag);const ciphertext=base64urlDecode(blob.data);const decrypted=decryptAesGcm(key,ciphertext,iv,tag);return JSON.parse(textDecoder.decode(decrypted))}catch{return null}}function serializeBlob(blob){return JSON.stringify(blob)}function deserializeBlob(str){try{const parsed=JSON.parse(str);if(parsed&&typeof parsed==="object"&&parsed.alg==="A256GCM"&&typeof parsed.iv==="string"&&typeof parsed.tag==="string"&&typeof parsed.data==="string"){return parsed}return null}catch{return null}}async function encryptAndSerialize(data,keySource){const blob=await encryptValue(data,keySource);return serializeBlob(blob)}async function deserializeAndDecrypt(str,keySource){const blob=deserializeBlob(str);if(!blob)return null;return decryptValue(blob,keySource)}import{InternalMcpError,PublicMcpError}from"@frontmcp/sdk";var RememberIdentityError=class extends PublicMcpError{name="RememberIdentityError";constructor(message){super(message,"REMEMBER_IDENTITY_REQUIRED",403)}};var RememberScopeNotAllowedError=class extends PublicMcpError{name="RememberScopeNotAllowedError";constructor(scope,allowedScopes){super(`Scope '${scope}' is not allowed. Allowed scopes: ${allowedScopes.join(", ")}`,"REMEMBER_SCOPE_NOT_ALLOWED",400)}};var RememberConfigurationError=class extends InternalMcpError{name="RememberConfigurationError";constructor(message){super(message,"REMEMBER_CONFIGURATION")}};var LEGACY_SCOPES=["session","tool","user"];var DEFAULT_LEGACY_PURGE_DELAY_MS=864e5;var LAYOUT_MARKER_KEY="__layout__";var LAYOUT_VERSION=2;var MAX_TIMER_MS=2147483647;var scheduled=new WeakSet;function parseFirstSeenAt(raw){if(!raw)return void 0;try{const parsed=typeof raw==="string"?JSON.parse(raw):raw;const firstSeenAt=parsed?.firstSeenAt;return typeof firstSeenAt==="number"&&Number.isFinite(firstSeenAt)?firstSeenAt:void 0}catch{return void 0}}async function readLayoutFirstSeenAt(store,keyPrefix,logger,markerTtlSeconds){const key=`${keyPrefix}${LAYOUT_MARKER_KEY}`;const standDown=(reason,extra={})=>{logger?.debug?.(`remember: ${reason}, legacy purge stood down`,{key,...extra});return void 0};try{const existing=await store.getValue(key);if(existing){return parseFirstSeenAt(existing)??standDown("layout marker is unreadable")}const marker={version:LAYOUT_VERSION,firstSeenAt:Date.now()};const serialized=JSON.stringify(marker);if(!store.setIfAbsent){await store.setValue(key,serialized,markerTtlSeconds);return marker.firstSeenAt}if(await store.setIfAbsent(key,serialized,markerTtlSeconds)){return marker.firstSeenAt}const winner=await store.getValue(key);return parseFirstSeenAt(winner)??standDown("layout marker vanished after a lost race")}catch(error){return standDown("could not establish the layout marker",{error:error instanceof Error?error.message:String(error)})}}async function purgeLegacyRememberEntries(store,keyPrefix,logger){let deleted=0;for(const scope of LEGACY_SCOPES){const pattern=`${keyPrefix}${scope}:*`;let keys;try{keys=await store.keys(pattern)}catch(error){logger?.debug?.("remember: legacy purge skipped, store could not list keys",{pattern,error:error instanceof Error?error.message:String(error)});continue}for(const key of keys){try{await store.delete(key);deleted+=1}catch{}}}if(deleted>0){logger?.warn?.(`remember: purged ${deleted} entr${deleted===1?"y":"ies"} left unreadable by the session/tool key-derivation change and the namespace encoding. This is a one-time upgrade step; set skipLegacyPurge to handle the migration yourself.`)}return deleted}function scheduleLegacyRememberPurge(store,keyPrefix,options={}){if(scheduled.has(store))return;scheduled.add(store);const delayMs=options.delayMs??DEFAULT_LEGACY_PURGE_DELAY_MS;const markerTtlSeconds=options.defaultTTL?Math.max(options.defaultTTL,Math.ceil(2*delayMs/1e3)):void 0;const arm=waitMs=>{const timer=setTimeout(()=>{void attempt().catch(()=>void 0)},Math.min(waitMs,MAX_TIMER_MS));timer.unref?.()};const attempt=async()=>{const firstSeenAt=await readLayoutFirstSeenAt(store,keyPrefix,options.logger,markerTtlSeconds);if(firstSeenAt===void 0)return;const remainingMs=firstSeenAt+delayMs-Date.now();if(remainingMs>0){arm(remainingMs);return}await purgeLegacyRememberEntries(store,keyPrefix,options.logger)};void attempt().catch(()=>void 0)}var STORAGE_LAYOUT_VERSION="v2";function encodeKeyPart(value){return encodeURIComponent(value)}function remainingSeconds(expiresAt,now){return Math.max(1,Math.ceil((expiresAt-now)/1e3))}function expiryAfter(ttlSeconds,now){return ttlSeconds?now+ttlSeconds*1e3:void 0}var RememberAccessor=class{store;ctx;config;keyPrefix;encryptionEnabled;constructor(store,ctx,config,logger){this.store=store;this.ctx=ctx;this.config=config;this.keyPrefix=config.keyPrefix??"remember:";this.encryptionEnabled=config.encryption?.enabled!==false;if(!config.skipLegacyPurge){scheduleLegacyRememberPurge(store,this.keyPrefix,{delayMs:config.legacyPurgeDelayMs,defaultTTL:config.defaultTTL,logger})}}async set(key,value,options={}){const scope=options.scope??"session";const storageKey=this.buildStorageKey(key,scope);const now=Date.now();const ttlSeconds=options.ttl??this.config.defaultTTL;const entry={value,brand:options.brand,createdAt:now,updatedAt:now,expiresAt:expiryAfter(ttlSeconds,now),metadata:options.metadata};const serialized=this.encryptionEnabled?await encryptAndSerialize(entry,this.getKeySource(scope)):JSON.stringify(entry);await this.store.setValue(storageKey,serialized,ttlSeconds);return entry}async get(key,options={}){const scope=options.scope??"session";const entry=await this.readEntry(this.buildStorageKey(key,scope),scope);return entry?entry.value:options.defaultValue}async getEntry(key,options={}){const scope=options.scope??"session";return this.readEntry(this.buildStorageKey(key,scope),scope)}async forget(key,options={}){const scope=options.scope??"session";const storageKey=this.buildStorageKey(key,scope);await this.store.delete(storageKey)}async knows(key,options={}){const scope=options.scope??"session";return await this.readEntry(this.buildStorageKey(key,scope),scope)!==void 0}async list(options={}){const scope=options.scope??"session";const scopePrefix=this.buildScopePrefix(scope);const storageKeys=await this.store.keys(scopePrefix+(options.pattern??"*"));const entries=await Promise.all(storageKeys.map(storageKey=>this.readEntry(storageKey,scope)));return storageKeys.filter((_storageKey,index)=>entries[index]!==void 0).map(storageKey=>storageKey.slice(scopePrefix.length))}async update(key,value,options={}){const scope=options.scope??"session";const existing=await this.getEntry(key,{scope});if(!existing)return false;const now=Date.now();const expiresAt=expiryAfter(options.ttl,now)??existing.expiresAt??expiryAfter(this.config.defaultTTL,now);const entry={...existing,value,updatedAt:now,expiresAt};const storageKey=this.buildStorageKey(key,scope);const serialized=this.encryptionEnabled?await encryptAndSerialize(entry,this.getKeySource(scope)):JSON.stringify(entry);const writtenAt=Date.now();if(expiresAt!==void 0&&expiresAt<=writtenAt)return false;const storageTtl=expiresAt!==void 0?remainingSeconds(expiresAt,writtenAt):void 0;await this.store.setValue(storageKey,serialized,storageTtl);return true}get sessionId(){return this.ctx.sessionId}get userId(){const authInfo=this.ctx.authInfo;return authInfo?.extra?.["userId"]??authInfo?.extra?.["sub"]??authInfo?.clientId??void 0}buildStorageKey(key,scope){return this.buildScopePrefix(scope)+key}get verifiedSessionId(){const authInfo=this.ctx.authInfo;return[authInfo?.sessionId,authInfo?.extra?.["sessionId"]].find(candidate=>typeof candidate==="string"&&candidate.length>0&&candidate!==STATELESS_SESSION_ID)}resolveSessionIdentity(){const sessionId=this.verifiedSessionId;if(sessionId){return sessionId}const userId=this.userId;if(userId&&!isAnonymousSubject(userId)){return`stateless-user:${userId}`}throw new RememberIdentityError(`Remember cannot use session or tool scope for an unauthenticated request without a verified session: a stateless request shares the session id "${STATELESS_SESSION_ID}", and an mcp-session-id the server did not verify could belong to any caller, so the data would be shared across clients. Authenticate the request, use a stateful transport, or choose the 'global' scope if the data really is shared.`)}get toolName(){return getRunningTool()?.fullName??"unknown"}buildScopePrefix(scope){switch(scope){case"session":return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:session:${encodeKeyPart(this.resolveSessionIdentity())}:`;case"user":{const userId=this.userId;if(!userId||isAnonymousSubject(userId)){throw new RememberIdentityError("Remember cannot use user scope without an authenticated user: all unauthenticated callers would share one namespace. Authenticate the request or use the 'global' scope.")}return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:user:${encodeKeyPart(userId)}:`}case"tool":return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:tool:${encodeKeyPart(this.toolName)}:${encodeKeyPart(this.resolveSessionIdentity())}:`;case"global":return`${this.keyPrefix}global:`}}getKeySource(scope){const needsSessionIdentity=scope==="session"||scope==="tool";return getKeySourceForScope(scope,{sessionId:needsSessionIdentity?this.resolveSessionIdentity():this.ctx.sessionId,userId:this.userId,toolName:scope==="tool"?this.toolName:void 0},this.config.encryption?.customKey)}async readEntry(storageKey,scope){const stored=await this.store.getValue(storageKey);if(!stored)return void 0;const raw=typeof stored==="string"?stored:JSON.stringify(stored);const entry=this.encryptionEnabled?await deserializeAndDecrypt(raw,this.getKeySource(scope)):this.parseEntry(raw);if(!entry)return void 0;if(entry.expiresAt&&Date.now()>entry.expiresAt){await this.store.delete(storageKey);return void 0}return entry}parseEntry(raw){try{return JSON.parse(raw)}catch{return null}}};RememberAccessor=__decorateClass([Provider({name:"provider:remember:accessor",description:"Context-scoped accessor for RememberPlugin storage",scope:ProviderScope.CONTEXT})],RememberAccessor);function createRememberAccessor(store,ctx,config,logger){return new RememberAccessor(store,ctx,config,logger)}import{Provider as Provider2,ProviderScope as ProviderScope2}from"@frontmcp/sdk";var MAX_TIMEOUT_MS=2**31-1;var RememberMemoryProvider=class{constructor(sweepIntervalSeconds=60,defaultTTL){this.defaultTTL=defaultTTL;this.sweeper=setInterval(()=>this.sweep(),sweepIntervalSeconds*1e3);this.sweeper.unref?.()}defaultTTL;memory=new Map;sweeper;async setValue(key,value,ttlSeconds){const strValue=JSON.stringify(value);const existing=this.memory.get(key);if(existing?.timeout)clearTimeout(existing.timeout);const entry={value:strValue};const ttl=ttlSeconds??this.defaultTTL;if(ttl&&ttl>0){const ttlMs=ttl*1e3;entry.expiresAt=Date.now()+ttlMs;if(ttlMs<=MAX_TIMEOUT_MS){entry.timeout=setTimeout(()=>{const e=this.memory.get(key);if(e&&e.expiresAt&&e.expiresAt<=Date.now()){this.memory.delete(key)}},ttlMs);entry.timeout.unref?.()}}this.memory.set(key,entry)}async setIfAbsent(key,value,ttlSeconds){const existing=this.memory.get(key);if(existing&&!this.isExpired(existing))return false;await this.setValue(key,value,ttlSeconds);return true}async getValue(key,defaultValue){const entry=this.memory.get(key);if(!entry)return defaultValue;if(this.isExpired(entry)){await this.delete(key);return defaultValue}try{return JSON.parse(entry.value)}catch{return entry.value}}async delete(key){const entry=this.memory.get(key);if(entry?.timeout)clearTimeout(entry.timeout);this.memory.delete(key)}async exists(key){const entry=this.memory.get(key);if(!entry)return false;if(this.isExpired(entry)){await this.delete(key);return false}return true}async keys(pattern){const result=[];const regex=pattern?this.patternToRegex(pattern):null;const expiredKeys=[];for(const[key,entry]of this.memory){if(this.isExpired(entry)){expiredKeys.push(key)}}for(const key of expiredKeys){await this.delete(key)}for(const[key,entry]of this.memory){if(this.isExpired(entry)){continue}if(regex&&!regex.test(key)){continue}result.push(key)}return result}async close(){if(this.sweeper)clearInterval(this.sweeper);for(const[,entry]of this.memory){if(entry.timeout)clearTimeout(entry.timeout)}this.memory.clear()}isExpired(entry){return entry.expiresAt!==void 0&&entry.expiresAt<=Date.now()}sweep(){const now=Date.now();for(const[key,entry]of this.memory){if(entry.expiresAt!==void 0&&entry.expiresAt<=now){if(entry.timeout)clearTimeout(entry.timeout);this.memory.delete(key)}}}patternToRegex(pattern){if(pattern.length>RememberMemoryProvider.MAX_PATTERN_LENGTH){throw new Error(`Pattern too long: ${pattern.length} chars (max ${RememberMemoryProvider.MAX_PATTERN_LENGTH})`)}const wildcardCount=(pattern.match(/[*?]/g)||[]).length;if(wildcardCount>RememberMemoryProvider.MAX_WILDCARDS){throw new Error(`Too many wildcards in pattern: ${wildcardCount} (max ${RememberMemoryProvider.MAX_WILDCARDS})`)}const escaped=pattern.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".");return new RegExp(`^${escaped}$`)}};__publicField(RememberMemoryProvider,"MAX_PATTERN_LENGTH",500);__publicField(RememberMemoryProvider,"MAX_WILDCARDS",20);RememberMemoryProvider=__decorateClass([Provider2({name:"provider:remember:memory",description:"In-memory storage provider for RememberPlugin",scope:ProviderScope2.GLOBAL})],RememberMemoryProvider);import Redis from"ioredis";import{Provider as Provider3,ProviderScope as ProviderScope3}from"@frontmcp/sdk";function prefixedStoreKey(keyPrefix,key){return keyPrefix&&key.startsWith(keyPrefix)?key:keyPrefix+key}function doubledPrefixKey(keyPrefix,key){return keyPrefix&&key.startsWith(keyPrefix)?keyPrefix+key:void 0}function callerKeyOf(keyPrefix,pattern,backendKey){return keyPrefix&&pattern.startsWith(keyPrefix)?backendKey:backendKey.slice(keyPrefix.length)}var SET_IF_NEITHER_KEY_EXISTS_SCRIPT=`
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 __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __defNormalProp=(obj,key,value)=>key in obj?__defProp(obj,key,{enumerable:true,configurable:true,writable:true,value}):obj[key]=value;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 __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 __publicField=(obj,key,value)=>__defNormalProp(obj,typeof key!=="symbol"?key+"":key,value);import{DynamicPlugin,FRONTMCP_CONTEXT,FrontMcpConfig,FrontMcpLogger,getGlobalStoreConfig,isVercelKvProvider,Plugin,ProviderScope as ProviderScope5,storeProvider}from"@frontmcp/sdk";import{getRunningTool,isAnonymousSubject,Provider,ProviderScope,STATELESS_SESSION_ID}from"@frontmcp/sdk";import{base64urlDecode,base64urlEncode as base64urlEncode2,decryptAesGcm,encryptAesGcm,hkdfSha256,randomBytes as randomBytes2}from"@frontmcp/utils";import{z}from"@frontmcp/lazy-zod";import{base64urlEncode,dirname,isAbsolute,mkdir,pathResolve,randomBytes,readFile,rename,unlink,writeFile}from"@frontmcp/utils";var DEFAULT_SECRET_FILE_PATH=".frontmcp/remember-secret.json";var SECRET_BYTES=32;var rememberSecretDataSchema=z.object({secret:z.string().min(32),createdAt:z.number().positive().int(),version:z.literal(1)});function validateSecretData(data){const result=rememberSecretDataSchema.safeParse(data);if(!result.success){return{valid:false,error:result.error.issues[0]?.message??"Invalid secret structure"}}const parsed=result.data;const now=Date.now();if(parsed.createdAt>now+6e4){return{valid:false,error:"createdAt is in the future"}}const hundredYearsMs=100*365*24*60*60*1e3;if(parsed.createdAt<now-hundredYearsMs){return{valid:false,error:"createdAt is too old"}}return{valid:true}}function isSecretPersistenceEnabled(options){const isProduction=process.env["NODE_ENV"]==="production";if(isProduction){return options?.forceEnable===true}return true}function resolveSecretPath(options){const secretPath=options?.secretPath??DEFAULT_SECRET_FILE_PATH;if(isAbsolute(secretPath)){return secretPath}return pathResolve(process.cwd(),secretPath)}async function loadRememberSecret(options){if(!isSecretPersistenceEnabled(options)){return null}const secretPath=resolveSecretPath(options);try{const content=await readFile(secretPath,"utf8");const data=JSON.parse(content);const validation=validateSecretData(data);if(!validation.valid){console.warn(`[RememberSecretPersistence] Invalid secret file format at ${secretPath}: ${validation.error}, will regenerate`);return null}return data}catch(error){if(error.code==="ENOENT"){return null}console.warn(`[RememberSecretPersistence] Failed to load secret from ${secretPath}: ${error.message}`);return null}}async function saveRememberSecret(secretData,options){if(!isSecretPersistenceEnabled(options)){return true}const secretPath=resolveSecretPath(options);const dir=dirname(secretPath);const tempPath=`${secretPath}.tmp.${Date.now()}.${base64urlEncode(randomBytes(8))}`;try{await mkdir(dir,{recursive:true,mode:448});const content=JSON.stringify(secretData,null,2);await writeFile(tempPath,content,{mode:384});await rename(tempPath,secretPath);return true}catch(error){console.error(`[RememberSecretPersistence] Failed to save secret to ${secretPath}: ${error.message}`);try{await unlink(tempPath)}catch{}return false}}var cachedSecret=null;var secretGenerationPromise=null;function generateSecret(){return base64urlEncode(randomBytes(SECRET_BYTES))}async function getOrCreatePersistedSecret(options){if(cachedSecret){return cachedSecret}if(secretGenerationPromise){return secretGenerationPromise}secretGenerationPromise=(async()=>{try{const loaded=await loadRememberSecret(options);if(loaded){cachedSecret=loaded.secret;return cachedSecret}const newSecret=generateSecret();const secretData={secret:newSecret,createdAt:Date.now(),version:1};if(isSecretPersistenceEnabled(options)){await saveRememberSecret(secretData,options)}else{console.warn("[RememberSecretPersistence] No REMEMBER_SECRET (or MCP_MEMORY_SECRET / MCP_SESSION_SECRET) is set in production, so a random in-memory secret is used. Encrypted memory will be unreadable after a restart and by other instances sharing the store. Set REMEMBER_SECRET to the same value on every instance.")}cachedSecret=newSecret;return cachedSecret}finally{secretGenerationPromise=null}})();return secretGenerationPromise}async function getBaseSecret(){const envSecret=process.env["REMEMBER_SECRET"]||process.env["MCP_MEMORY_SECRET"]||process.env["MCP_SESSION_SECRET"];if(envSecret){return envSecret}return getOrCreatePersistedSecret()}async function secretFor(source){return source.secret?source.secret:getBaseSecret()}var textEncoder=new TextEncoder;async function deriveEncryptionKey(source){const salt=textEncoder.encode("remember-plugin-v1");let ikm;let context;switch(source.type){case"session":ikm=await secretFor(source)+source.sessionId;context=`remember:session:${source.sessionId}`;break;case"tool":ikm=await secretFor(source)+source.sessionId;context=`remember:tool:${source.toolName}:${source.sessionId}`;break;case"user":ikm=await secretFor(source)+source.userId;context=`remember:user:${source.userId}`;break;case"global":ikm=await secretFor(source);context="remember:global";break;case"custom":ikm=source.key;context=`remember:custom:${source.key}`;break}const ikmBytes=textEncoder.encode(ikm);const info=textEncoder.encode(context);return hkdfSha256(ikmBytes,salt,info,32)}function getKeySourceForScope(scope,context,secret){switch(scope){case"session":return{type:"session",sessionId:context.sessionId,secret};case"user":return{type:"user",userId:context.userId??"anonymous",secret};case"tool":return{type:"tool",toolName:context.toolName??"unknown",sessionId:context.sessionId,secret};case"global":return{type:"global",secret}}}var textDecoder=new TextDecoder;async function encryptValue(data,keySource){const key=await deriveEncryptionKey(keySource);const plaintext=textEncoder.encode(JSON.stringify(data));const iv=randomBytes2(12);const{ciphertext,tag}=encryptAesGcm(key,plaintext,iv);return{alg:"A256GCM",iv:base64urlEncode2(iv),tag:base64urlEncode2(tag),data:base64urlEncode2(ciphertext)}}async function decryptValue(blob,keySource){try{const key=await deriveEncryptionKey(keySource);const iv=base64urlDecode(blob.iv);const tag=base64urlDecode(blob.tag);const ciphertext=base64urlDecode(blob.data);const decrypted=decryptAesGcm(key,ciphertext,iv,tag);return JSON.parse(textDecoder.decode(decrypted))}catch{return null}}function serializeBlob(blob){return JSON.stringify(blob)}function deserializeBlob(str){try{const parsed=JSON.parse(str);if(parsed&&typeof parsed==="object"&&parsed.alg==="A256GCM"&&typeof parsed.iv==="string"&&typeof parsed.tag==="string"&&typeof parsed.data==="string"){return parsed}return null}catch{return null}}async function encryptAndSerialize(data,keySource){const blob=await encryptValue(data,keySource);return serializeBlob(blob)}async function deserializeAndDecrypt(str,keySource){const blob=deserializeBlob(str);if(!blob)return null;return decryptValue(blob,keySource)}import{InternalMcpError,PublicMcpError}from"@frontmcp/sdk";var RememberIdentityError=class extends PublicMcpError{name="RememberIdentityError";constructor(message){super(message,"REMEMBER_IDENTITY_REQUIRED",403)}};var RememberScopeNotAllowedError=class extends PublicMcpError{name="RememberScopeNotAllowedError";constructor(scope,allowedScopes){super(`Scope '${scope}' is not allowed. Allowed scopes: ${allowedScopes.join(", ")}`,"REMEMBER_SCOPE_NOT_ALLOWED",400)}};var RememberConfigurationError=class extends InternalMcpError{name="RememberConfigurationError";constructor(message){super(message,"REMEMBER_CONFIGURATION")}};var LEGACY_SCOPES=["session","tool","user"];var DEFAULT_LEGACY_PURGE_DELAY_MS=864e5;var LAYOUT_MARKER_KEY="__layout__";var LAYOUT_VERSION=2;var MAX_TIMER_MS=2147483647;var scheduled=new WeakSet;function parseFirstSeenAt(raw){if(!raw)return void 0;try{const parsed=typeof raw==="string"?JSON.parse(raw):raw;const firstSeenAt=parsed?.firstSeenAt;return typeof firstSeenAt==="number"&&Number.isFinite(firstSeenAt)?firstSeenAt:void 0}catch{return void 0}}async function readLayoutFirstSeenAt(store,keyPrefix,logger,markerTtlSeconds){const key=`${keyPrefix}${LAYOUT_MARKER_KEY}`;const standDown=(reason,extra={})=>{logger?.debug?.(`remember: ${reason}, legacy purge stood down`,{key,...extra});return void 0};try{const existing=await store.getValue(key);if(existing){return parseFirstSeenAt(existing)??standDown("layout marker is unreadable")}const marker={version:LAYOUT_VERSION,firstSeenAt:Date.now()};const serialized=JSON.stringify(marker);if(!store.setIfAbsent){await store.setValue(key,serialized,markerTtlSeconds);return marker.firstSeenAt}if(await store.setIfAbsent(key,serialized,markerTtlSeconds)){return marker.firstSeenAt}const winner=await store.getValue(key);return parseFirstSeenAt(winner)??standDown("layout marker vanished after a lost race")}catch(error){return standDown("could not establish the layout marker",{error:error instanceof Error?error.message:String(error)})}}async function purgeLegacyRememberEntries(store,keyPrefix,logger){let deleted=0;for(const scope of LEGACY_SCOPES){const pattern=`${keyPrefix}${scope}:*`;let keys;try{keys=await store.keys(pattern)}catch(error){logger?.debug?.("remember: legacy purge skipped, store could not list keys",{pattern,error:error instanceof Error?error.message:String(error)});continue}for(const key of keys){try{await store.delete(key);deleted+=1}catch{}}}if(deleted>0){logger?.warn?.(`remember: purged ${deleted} entr${deleted===1?"y":"ies"} left unreadable by the session/tool key-derivation change and the namespace encoding. This is a one-time upgrade step; set skipLegacyPurge to handle the migration yourself.`)}return deleted}function scheduleLegacyRememberPurge(store,keyPrefix,options={}){if(scheduled.has(store))return;scheduled.add(store);const delayMs=options.delayMs??DEFAULT_LEGACY_PURGE_DELAY_MS;const markerTtlSeconds=options.defaultTTL?Math.max(options.defaultTTL,Math.ceil(2*delayMs/1e3)):void 0;const arm=waitMs=>{const timer=setTimeout(()=>{void attempt().catch(()=>void 0)},Math.min(waitMs,MAX_TIMER_MS));timer.unref?.()};const attempt=async()=>{const firstSeenAt=await readLayoutFirstSeenAt(store,keyPrefix,options.logger,markerTtlSeconds);if(firstSeenAt===void 0)return;const remainingMs=firstSeenAt+delayMs-Date.now();if(remainingMs>0){arm(remainingMs);return}await purgeLegacyRememberEntries(store,keyPrefix,options.logger)};void attempt().catch(()=>void 0)}var STORAGE_LAYOUT_VERSION="v2";function encodeKeyPart(value){return encodeURIComponent(value)}function remainingSeconds(expiresAt,now){return Math.max(1,Math.ceil((expiresAt-now)/1e3))}function expiryAfter(ttlSeconds,now){return ttlSeconds?now+ttlSeconds*1e3:void 0}var RememberAccessor=class{store;ctx;config;keyPrefix;encryptionEnabled;constructor(store,ctx,config,logger){this.store=store;this.ctx=ctx;this.config=config;this.keyPrefix=config.keyPrefix??"remember:";this.encryptionEnabled=config.encryption?.enabled!==false;if(!config.skipLegacyPurge){scheduleLegacyRememberPurge(store,this.keyPrefix,{delayMs:config.legacyPurgeDelayMs,defaultTTL:config.defaultTTL,logger})}}async set(key,value,options={}){const scope=options.scope??"session";const storageKey=this.buildStorageKey(key,scope);const now=Date.now();const ttlSeconds=options.ttl??this.config.defaultTTL;const entry={value,brand:options.brand,createdAt:now,updatedAt:now,expiresAt:expiryAfter(ttlSeconds,now),metadata:options.metadata};const serialized=this.encryptionEnabled?await encryptAndSerialize(entry,this.getKeySource(scope)):JSON.stringify(entry);await this.store.setValue(storageKey,serialized,ttlSeconds);return entry}async get(key,options={}){const scope=options.scope??"session";const entry=await this.readEntry(this.buildStorageKey(key,scope),scope);return entry?entry.value:options.defaultValue}async getEntry(key,options={}){const scope=options.scope??"session";return this.readEntry(this.buildStorageKey(key,scope),scope)}async forget(key,options={}){const scope=options.scope??"session";const storageKey=this.buildStorageKey(key,scope);await this.store.delete(storageKey)}async knows(key,options={}){const scope=options.scope??"session";return await this.readEntry(this.buildStorageKey(key,scope),scope)!==void 0}async list(options={}){const scope=options.scope??"session";const scopePrefix=this.buildScopePrefix(scope);const storageKeys=await this.store.keys(scopePrefix+(options.pattern??"*"));const entries=await Promise.all(storageKeys.map(storageKey=>this.readEntry(storageKey,scope)));return storageKeys.filter((_storageKey,index)=>entries[index]!==void 0).map(storageKey=>storageKey.slice(scopePrefix.length))}async update(key,value,options={}){const scope=options.scope??"session";const existing=await this.getEntry(key,{scope});if(!existing)return false;const now=Date.now();const expiresAt=expiryAfter(options.ttl,now)??existing.expiresAt??expiryAfter(this.config.defaultTTL,now);const entry={...existing,value,updatedAt:now,expiresAt};const storageKey=this.buildStorageKey(key,scope);const serialized=this.encryptionEnabled?await encryptAndSerialize(entry,this.getKeySource(scope)):JSON.stringify(entry);const writtenAt=Date.now();if(expiresAt!==void 0&&expiresAt<=writtenAt)return false;const storageTtl=expiresAt!==void 0?remainingSeconds(expiresAt,writtenAt):void 0;await this.store.setValue(storageKey,serialized,storageTtl);return true}get sessionId(){return this.ctx.sessionId}get userId(){const authInfo=this.ctx.authInfo;return authInfo?.extra?.["userId"]??authInfo?.extra?.["sub"]??authInfo?.clientId??void 0}buildStorageKey(key,scope){return this.buildScopePrefix(scope)+key}get verifiedSessionId(){const authInfo=this.ctx.authInfo;return[authInfo?.sessionId,authInfo?.extra?.["sessionId"]].find(candidate=>typeof candidate==="string"&&candidate.length>0&&candidate!==STATELESS_SESSION_ID)}resolveSessionIdentity(){const sessionId=this.verifiedSessionId;if(sessionId){return sessionId}const userId=this.userId;if(userId&&!isAnonymousSubject(userId)){return`stateless-user:${userId}`}throw new RememberIdentityError(`Remember cannot use session or tool scope for an unauthenticated request without a verified session: a stateless request shares the session id "${STATELESS_SESSION_ID}", and an mcp-session-id the server did not verify could belong to any caller, so the data would be shared across clients. Authenticate the request, use a stateful transport, or choose the 'global' scope if the data really is shared.`)}get toolName(){return getRunningTool()?.fullName??"unknown"}buildScopePrefix(scope){switch(scope){case"session":return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:session:${encodeKeyPart(this.resolveSessionIdentity())}:`;case"user":{const userId=this.userId;if(!userId||isAnonymousSubject(userId)){throw new RememberIdentityError("Remember cannot use user scope without an authenticated user: all unauthenticated callers would share one namespace. Authenticate the request or use the 'global' scope.")}return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:user:${encodeKeyPart(userId)}:`}case"tool":return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:tool:${encodeKeyPart(this.toolName)}:${encodeKeyPart(this.resolveSessionIdentity())}:`;case"global":return`${this.keyPrefix}global:`}}getKeySource(scope){const needsSessionIdentity=scope==="session"||scope==="tool";return getKeySourceForScope(scope,{sessionId:needsSessionIdentity?this.resolveSessionIdentity():this.ctx.sessionId,userId:this.userId,toolName:scope==="tool"?this.toolName:void 0},this.config.encryption?.customKey)}async readEntry(storageKey,scope){const stored=await this.store.getValue(storageKey);if(!stored)return void 0;const raw=typeof stored==="string"?stored:JSON.stringify(stored);const entry=this.encryptionEnabled?await deserializeAndDecrypt(raw,this.getKeySource(scope)):this.parseEntry(raw);if(!entry)return void 0;if(entry.expiresAt&&Date.now()>entry.expiresAt){await this.store.delete(storageKey);return void 0}return entry}parseEntry(raw){try{return JSON.parse(raw)}catch{return null}}};RememberAccessor=__decorateClass([Provider({name:"provider:remember:accessor",description:"Context-scoped accessor for RememberPlugin storage",scope:ProviderScope.CONTEXT})],RememberAccessor);function createRememberAccessor(store,ctx,config,logger){return new RememberAccessor(store,ctx,config,logger)}import{Provider as Provider2,ProviderScope as ProviderScope2}from"@frontmcp/sdk";var MAX_TIMEOUT_MS=2**31-1;var RememberMemoryProvider=class{constructor(sweepIntervalSeconds=60,defaultTTL){this.defaultTTL=defaultTTL;this.sweeper=setInterval(()=>this.sweep(),sweepIntervalSeconds*1e3);this.sweeper.unref?.()}defaultTTL;memory=new Map;sweeper;async setValue(key,value,ttlSeconds){const strValue=JSON.stringify(value);const existing=this.memory.get(key);if(existing?.timeout)clearTimeout(existing.timeout);const entry={value:strValue};const ttl=ttlSeconds??this.defaultTTL;if(ttl&&ttl>0){const ttlMs=ttl*1e3;entry.expiresAt=Date.now()+ttlMs;if(ttlMs<=MAX_TIMEOUT_MS){entry.timeout=setTimeout(()=>{const e=this.memory.get(key);if(e&&e.expiresAt&&e.expiresAt<=Date.now()){this.memory.delete(key)}},ttlMs);entry.timeout.unref?.()}}this.memory.set(key,entry)}async setIfAbsent(key,value,ttlSeconds){const existing=this.memory.get(key);if(existing&&!this.isExpired(existing))return false;await this.setValue(key,value,ttlSeconds);return true}async getValue(key,defaultValue){const entry=this.memory.get(key);if(!entry)return defaultValue;if(this.isExpired(entry)){await this.delete(key);return defaultValue}try{return JSON.parse(entry.value)}catch{return entry.value}}async delete(key){const entry=this.memory.get(key);if(entry?.timeout)clearTimeout(entry.timeout);this.memory.delete(key)}async exists(key){const entry=this.memory.get(key);if(!entry)return false;if(this.isExpired(entry)){await this.delete(key);return false}return true}async keys(pattern){const result=[];const regex=pattern?this.patternToRegex(pattern):null;const expiredKeys=[];for(const[key,entry]of this.memory){if(this.isExpired(entry)){expiredKeys.push(key)}}for(const key of expiredKeys){await this.delete(key)}for(const[key,entry]of this.memory){if(this.isExpired(entry)){continue}if(regex&&!regex.test(key)){continue}result.push(key)}return result}async close(){if(this.sweeper)clearInterval(this.sweeper);for(const[,entry]of this.memory){if(entry.timeout)clearTimeout(entry.timeout)}this.memory.clear()}isExpired(entry){return entry.expiresAt!==void 0&&entry.expiresAt<=Date.now()}sweep(){const now=Date.now();for(const[key,entry]of this.memory){if(entry.expiresAt!==void 0&&entry.expiresAt<=now){if(entry.timeout)clearTimeout(entry.timeout);this.memory.delete(key)}}}patternToRegex(pattern){if(pattern.length>RememberMemoryProvider.MAX_PATTERN_LENGTH){throw new Error(`Pattern too long: ${pattern.length} chars (max ${RememberMemoryProvider.MAX_PATTERN_LENGTH})`)}const wildcardCount=(pattern.match(/[*?]/g)||[]).length;if(wildcardCount>RememberMemoryProvider.MAX_WILDCARDS){throw new Error(`Too many wildcards in pattern: ${wildcardCount} (max ${RememberMemoryProvider.MAX_WILDCARDS})`)}const escaped=pattern.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".");return new RegExp(`^${escaped}$`)}};__publicField(RememberMemoryProvider,"MAX_PATTERN_LENGTH",500);__publicField(RememberMemoryProvider,"MAX_WILDCARDS",20);RememberMemoryProvider=__decorateClass([Provider2({name:"provider:remember:memory",description:"In-memory storage provider for RememberPlugin",scope:ProviderScope2.GLOBAL})],RememberMemoryProvider);import{Provider as Provider3,ProviderScope as ProviderScope3}from"@frontmcp/sdk";function prefixedStoreKey(keyPrefix,key){return keyPrefix&&key.startsWith(keyPrefix)?key:keyPrefix+key}function doubledPrefixKey(keyPrefix,key){return keyPrefix&&key.startsWith(keyPrefix)?keyPrefix+key:void 0}function callerKeyOf(keyPrefix,pattern,backendKey){return keyPrefix&&pattern.startsWith(keyPrefix)?backendKey:backendKey.slice(keyPrefix.length)}var SET_IF_NEITHER_KEY_EXISTS_SCRIPT=`
2
5
  if redis.call('EXISTS', KEYS[1], KEYS[2]) > 0 then return 0 end
3
6
  if ARGV[2] ~= '' then redis.call('SET', KEYS[1], ARGV[1], 'EX', ARGV[2]) else redis.call('SET', KEYS[1], ARGV[1]) end
4
7
  return 1
5
- `;var RememberRedisProvider=class{client;keyPrefix;defaultTTL;ownsClient;constructor(options){this.keyPrefix=options.keyPrefix??"";this.defaultTTL=options.defaultTTL;if(options.type==="redis-client"){this.client=options.client;this.ownsClient=false;return}this.ownsClient=true;this.client=new Redis({lazyConnect:false,maxRetriesPerRequest:3,...options.config});this.client.on("connect",()=>{if(process.env["DEBUG"]==="true"){console.log("[RememberPlugin:Redis] Connected")}});this.client.on("error",err=>{console.error("[RememberPlugin:Redis] Error:",err.message)})}async setValue(key,value,ttlSeconds){if(value===void 0){throw new Error("Cannot store undefined value. Use null or delete the key instead.")}if(ttlSeconds!==void 0){if(typeof ttlSeconds!=="number"||!Number.isFinite(ttlSeconds)){throw new Error(`Invalid TTL: expected a number, got ${typeof ttlSeconds}`)}if(ttlSeconds<=0){throw new Error(`Invalid TTL: must be positive, got ${ttlSeconds}`)}if(!Number.isInteger(ttlSeconds)){throw new Error(`Invalid TTL: must be an integer, got ${ttlSeconds}`)}}const fullKey=prefixedStoreKey(this.keyPrefix,key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;if(ttl!==void 0&&ttl>0){await this.client.set(fullKey,strValue,"EX",ttl)}else{await this.client.set(fullKey,strValue)}await this.deleteDoubledPrefixKey(key)}async setIfAbsent(key,value,ttlSeconds){const fullKey=prefixedStoreKey(this.keyPrefix,key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const hasTtl=ttl!==void 0&&ttl>0;const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(doubledKey){const ttlArgument=hasTtl?String(ttl):"";const created=await this.client.eval(SET_IF_NEITHER_KEY_EXISTS_SCRIPT,2,fullKey,doubledKey,strValue,ttlArgument);return created===1}const result=hasTtl?await this.client.set(fullKey,strValue,"EX",ttl,"NX"):await this.client.set(fullKey,strValue,"NX");return result==="OK"}async getValue(key,defaultValue){const raw=await this.client.get(prefixedStoreKey(this.keyPrefix,key))??await this.readDoubledPrefixValue(key);if(raw===null)return defaultValue;try{return JSON.parse(raw)}catch{return defaultValue}}async delete(key){await this.client.del(prefixedStoreKey(this.keyPrefix,key));await this.deleteDoubledPrefixKey(key)}async exists(key){if(await this.client.exists(prefixedStoreKey(this.keyPrefix,key))===1)return true;const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey!==void 0&&await this.client.exists(doubledKey)===1}async keys(pattern="*"){const result=new Set;for(const key of await this.scan(prefixedStoreKey(this.keyPrefix,pattern))){result.add(callerKeyOf(this.keyPrefix,pattern,key))}const doubledPattern=doubledPrefixKey(this.keyPrefix,pattern);if(doubledPattern){for(const key of await this.scan(doubledPattern))result.add(key.slice(this.keyPrefix.length))}return[...result]}async scan(match){const found=[];let cursor="0";do{const[nextCursor,keys]=await this.client.scan(cursor,"MATCH",match,"COUNT",100);cursor=nextCursor;found.push(...keys)}while(cursor!=="0");return found}async readDoubledPrefixValue(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey?this.client.get(doubledKey):null}async deleteDoubledPrefixKey(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(doubledKey)await this.client.del(doubledKey)}async close(){if(this.ownsClient){await this.client.quit()}}};RememberRedisProvider=__decorateClass([Provider3({name:"provider:remember:redis",description:"Redis-based storage provider for RememberPlugin",scope:ProviderScope3.GLOBAL})],RememberRedisProvider);import{Provider as Provider4,ProviderScope as ProviderScope4}from"@frontmcp/sdk";import{createVercelKvClient}from"@frontmcp/utils";var RememberVercelKvProvider=class{connection;client;keyPrefix;defaultTTL;constructor(options={}){const hasUrl=options.url!==void 0;const hasToken=options.token!==void 0;if(hasUrl!==hasToken){throw new Error(`RememberVercelKvProvider: Both 'url' and 'token' must be provided together, or neither. Received: url=${hasUrl?"provided":"missing"}, token=${hasToken?"provided":"missing"}`)}this.connection={url:options.url,token:options.token};this.keyPrefix=options.keyPrefix??"remember:";this.defaultTTL=options.defaultTTL}kv(){this.client??=createVercelKvClient(this.connection).catch(error=>{this.client=void 0;throw error});return this.client}prefixKey(key){return prefixedStoreKey(this.keyPrefix,key)}async setValue(key,value,ttlSeconds){const fullKey=this.prefixKey(key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const kv=await this.kv();if(ttl&&ttl>0){await kv.set(fullKey,strValue,{ex:ttl})}else{await kv.set(fullKey,strValue)}await this.deleteDoubledPrefixKey(key)}async setIfAbsent(key,value,ttlSeconds){const fullKey=this.prefixKey(key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const doubledKey=doubledPrefixKey(this.keyPrefix,key);const kv=await this.kv();if(doubledKey){const ttlArgument=ttl&&ttl>0?String(ttl):"";const created=await kv.eval(SET_IF_NEITHER_KEY_EXISTS_SCRIPT,[fullKey,doubledKey],[strValue,ttlArgument]);return Number(created)===1}const result=ttl&&ttl>0?await kv.set(fullKey,strValue,{nx:true,ex:ttl}):await kv.set(fullKey,strValue,{nx:true});return result==="OK"}async getValue(key,defaultValue){const kv=await this.kv();const raw=await kv.get(this.prefixKey(key))??await this.readDoubledPrefixValue(key);if(raw===null)return defaultValue;try{return JSON.parse(raw)}catch{return raw}}async delete(key){const kv=await this.kv();await kv.del(this.prefixKey(key));await this.deleteDoubledPrefixKey(key)}async exists(key){const kv=await this.kv();if(await kv.exists(this.prefixKey(key))===1)return true;const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey!==void 0&&await kv.exists(doubledKey)===1}async keys(pattern="*"){const result=new Set;for(const key of await this.scan(this.prefixKey(pattern))){result.add(callerKeyOf(this.keyPrefix,pattern,key))}const doubledPattern=doubledPrefixKey(this.keyPrefix,pattern);if(doubledPattern){for(const key of await this.scan(doubledPattern))result.add(key.slice(this.keyPrefix.length))}return[...result]}async scan(match){const found=[];const kv=await this.kv();try{let cursor=0;do{const[nextCursor,keys]=await kv.scan(cursor,{match,count:100});cursor=nextCursor;found.push(...keys)}while(String(cursor)!=="0")}catch{try{found.push(...await kv.keys(match))}catch{console.warn("[RememberPlugin:VercelKV] keys() operation not supported")}}return found}async readDoubledPrefixValue(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(!doubledKey)return null;const kv=await this.kv();return kv.get(doubledKey)}async deleteDoubledPrefixKey(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(!doubledKey)return;const kv=await this.kv();await kv.del(doubledKey)}async close(){}};RememberVercelKvProvider=__decorateClass([Provider4({name:"provider:remember:vercel-kv",description:"Vercel KV-based storage provider for RememberPlugin",scope:ProviderScope4.GLOBAL})],RememberVercelKvProvider);var RememberStoreToken=Symbol("plugin:remember:store");var RememberConfigToken=Symbol("plugin:remember:config");var RememberAccessorToken=Symbol("plugin:remember:accessor");import{Tool as Tool5}from"@frontmcp/sdk";import{z as z2}from"@frontmcp/lazy-zod";import{Tool,ToolContext}from"@frontmcp/sdk";var REMEMBER_SCOPE_DESCRIPTION="Whose memory to use (default: session). session: this session; without one (stateless HTTP, MCP 2026-07-28), the signed-in caller across its requests. user: the signed-in caller, across all of its sessions. tool: the tool running the call, for the same caller as session; each memory tool has its own, so the other memory tools do not see what one of them stores in tool scope. global: shared by every caller. An anonymous caller cannot use user scope, nor session or tool scope without a session.";function rememberToolNames(prefix=""){return{rememberThis:`${prefix}remember_this`,recall:`${prefix}recall`,forget:`${prefix}forget`,listMemories:`${prefix}list_memories`}}var forgetInputSchema={key:z2.string().min(1).describe("What memory to forget"),scope:z2.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION)};var forgetOutputSchema=z2.object({success:z2.boolean(),key:z2.string(),scope:z2.string(),existed:z2.boolean()});function forgetDescription(){return"Forget a previously remembered value. Use this when the user wants to delete stored preferences or information."}function forgetToolMetadata(names=rememberToolNames()){return{name:names.forget,description:forgetDescription(),inputSchema:forgetInputSchema,outputSchema:forgetOutputSchema,annotations:{readOnlyHint:false}}}var ForgetTool=class extends ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const existed=await remember.knows(input.key,{scope});await remember.forget(input.key,{scope});return{success:true,key:input.key,scope,existed}}};ForgetTool=__decorateClass([Tool(forgetToolMetadata())],ForgetTool);import{z as z3}from"@frontmcp/lazy-zod";import{Tool as Tool2,ToolContext as ToolContext2}from"@frontmcp/sdk";var listMemoriesInputSchema={scope:z3.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION),pattern:z3.string().optional().describe('Pattern to filter keys (e.g., "user_*")'),limit:z3.number().positive().max(100).optional().describe("Maximum number of keys to return (default: 50)")};var listMemoriesOutputSchema=z3.object({keys:z3.array(z3.string()),scope:z3.string(),count:z3.number(),truncated:z3.boolean()});function listMemoriesDescription(names=rememberToolNames()){return`List the remembered keys in one scope (default: session). Use this to see what memories are stored before you ${names.recall} or ${names.forget} one.`}function listMemoriesToolMetadata(names=rememberToolNames()){return{name:names.listMemories,description:listMemoriesDescription(names),inputSchema:listMemoriesInputSchema,outputSchema:listMemoriesOutputSchema,annotations:{readOnlyHint:true}}}var ListMemoriesTool=class extends ToolContext2{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const limit=input.limit??50;let keys=await remember.list({scope,pattern:input.pattern});const truncated=keys.length>limit;if(truncated){keys=keys.slice(0,limit)}return{keys,scope,count:keys.length,truncated}}};ListMemoriesTool=__decorateClass([Tool2(listMemoriesToolMetadata())],ListMemoriesTool);import{z as z4}from"@frontmcp/lazy-zod";import{Tool as Tool3,ToolContext as ToolContext3}from"@frontmcp/sdk";var recallInputSchema={key:z4.string().min(1).describe("What memory to recall"),scope:z4.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION)};var recallOutputSchema=z4.object({found:z4.boolean(),key:z4.string(),value:z4.unknown().optional(),scope:z4.string(),createdAt:z4.number().optional(),expiresAt:z4.number().optional()});function recallDescription(names=rememberToolNames()){return`Recall something that was previously remembered. Use this to retrieve stored preferences, settings, or any information that was saved with ${names.rememberThis}.`}function recallToolMetadata(names=rememberToolNames()){return{name:names.recall,description:recallDescription(names),inputSchema:recallInputSchema,outputSchema:recallOutputSchema,annotations:{readOnlyHint:true}}}var RecallTool=class extends ToolContext3{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const entry=await remember.getEntry(input.key,{scope});if(!entry){return{found:false,key:input.key,scope}}return{found:true,key:input.key,value:entry.value,scope,createdAt:entry.createdAt,expiresAt:entry.expiresAt}}};RecallTool=__decorateClass([Tool3(recallToolMetadata())],RecallTool);import{z as z5}from"@frontmcp/lazy-zod";import{Tool as Tool4,ToolContext as ToolContext4}from"@frontmcp/sdk";var rememberThisInputSchema={key:z5.string().min(1).describe('What to call this memory (e.g., "user_preference", "last_action")'),value:z5.unknown().describe("The value to remember (any JSON-serializable data)"),scope:z5.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION),ttl:z5.number().int().positive().optional().describe("Forget after this many whole seconds"),brand:z5.enum(["preference","cache","state","conversation","custom"]).optional().describe("Type hint for the stored value")};var rememberThisOutputSchema=z5.object({success:z5.boolean(),key:z5.string(),scope:z5.string(),expiresAt:z5.number().optional()});function rememberThisDescription(names=rememberToolNames()){return`Remember something for later: store a value under a key that ${names.recall} can read back, in the scope you choose (this session by default; user scope for the signed-in user across sessions). It lasts until it is forgotten or its ttl runs out. Use this when the user asks you to remember preferences, settings, or any information they want to persist.`}function rememberThisToolMetadata(names=rememberToolNames()){return{name:names.rememberThis,description:rememberThisDescription(names),inputSchema:rememberThisInputSchema,outputSchema:rememberThisOutputSchema,annotations:{readOnlyHint:false}}}var RememberThisTool=class extends ToolContext4{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const entry=await remember.set(input.key,input.value,{scope,ttl:input.ttl,brand:input.brand});return{success:true,key:input.key,scope,expiresAt:entry.expiresAt}}};RememberThisTool=__decorateClass([Tool4(rememberThisToolMetadata())],RememberThisTool);function createRememberTools(prefix=""){if(!prefix)return[RememberThisTool,RecallTool,ForgetTool,ListMemoriesTool];const names=rememberToolNames(prefix);let PrefixedRememberThisTool=class extends RememberThisTool{};PrefixedRememberThisTool=__decorateClass([Tool5(rememberThisToolMetadata(names))],PrefixedRememberThisTool);let PrefixedRecallTool=class extends RecallTool{};PrefixedRecallTool=__decorateClass([Tool5(recallToolMetadata(names))],PrefixedRecallTool);let PrefixedForgetTool=class extends ForgetTool{};PrefixedForgetTool=__decorateClass([Tool5(forgetToolMetadata(names))],PrefixedForgetTool);let PrefixedListMemoriesTool=class extends ListMemoriesTool{};PrefixedListMemoriesTool=__decorateClass([Tool5(listMemoriesToolMetadata(names))],PrefixedListMemoriesTool);return[PrefixedRememberThisTool,PrefixedRecallTool,PrefixedForgetTool,PrefixedListMemoriesTool]}function withCheckedOptions(options){const customKey=options.encryption?.customKey;if(customKey!==void 0&&customKey.trim()===""){throw new RememberConfigurationError("RememberPlugin encryption.customKey must not be empty; omit it to derive keys from the server secret")}const{defaultTTL}=options;if(defaultTTL===void 0)return options;if(defaultTTL===0)return{...options,defaultTTL:void 0};if(!Number.isInteger(defaultTTL)||defaultTTL<0){throw new RememberConfigurationError(`RememberPlugin defaultTTL must be a whole number of seconds (0 for no default expiry), got ${defaultTTL}`)}return options}var RememberPlugin=class extends DynamicPlugin{options;constructor(options={}){super();this.options={...RememberPlugin.defaultOptions,...withCheckedOptions(options)}}};__publicField(RememberPlugin,"defaultOptions",{type:"memory",keyPrefix:"remember:",encryption:{enabled:true}});__publicField(RememberPlugin,"dynamicTools",options=>options.tools?.enabled?createRememberTools(options.tools.prefix):[]);__publicField(RememberPlugin,"dynamicProviders",options=>{const providers=[];const config={...RememberPlugin.defaultOptions,...withCheckedOptions(options)};switch(config.type){case"global-store":providers.push({name:"remember:store:global",provide:RememberStoreToken,inject:()=>[FrontMcpConfig],useFactory:frontmcpConfig=>{const storeConfig=getGlobalStoreConfig("RememberPlugin",frontmcpConfig);if(isVercelKvProvider(storeConfig)){return new RememberVercelKvProvider({url:storeConfig.url,token:storeConfig.token,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}return new RememberRedisProvider({type:"redis",config:{host:storeConfig.host??"localhost",port:storeConfig.port??6379,password:storeConfig.password,db:storeConfig.db},keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}});break;case"redis":if("config"in config&&config.config){providers.push({name:"remember:store:redis",provide:RememberStoreToken,useValue:new RememberRedisProvider({type:"redis",config:config.config,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})})}break;case"redis-client":if("client"in config&&config.client){providers.push({name:"remember:store:redis-client",provide:RememberStoreToken,useValue:new RememberRedisProvider({type:"redis-client",client:config.client,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})})}break;case"vercel-kv":providers.push({name:"remember:store:vercel-kv",provide:RememberStoreToken,useValue:new RememberVercelKvProvider({url:"url"in config?config.url:void 0,token:"token"in config?config.token:void 0,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})});break;case"memory":default:providers.push({name:"remember:store:memory",provide:RememberStoreToken,inject:()=>[],useFactory:()=>new RememberMemoryProvider(void 0,config.defaultTTL)});break}providers.push({name:"remember:config",provide:RememberConfigToken,useValue:config});providers.push({name:"remember:accessor",provide:RememberAccessorToken,scope:ProviderScope5.CONTEXT,inject:()=>[RememberStoreToken,FRONTMCP_CONTEXT,RememberConfigToken,FrontMcpLogger],useFactory:(store,ctx,cfg,logger)=>createRememberAccessor(store,ctx,cfg,logger)});return providers});RememberPlugin=__decorateClass([Plugin({name:"remember",description:"Help your LLM remember things across sessions",providers:[{name:"remember:store:memory",provide:RememberStoreToken,inject:()=>[],useFactory:()=>new RememberMemoryProvider}],contextExtensions:[{property:"remember",token:RememberAccessorToken,errorMessage:"RememberPlugin is not installed. Add RememberPlugin.init() to your plugins array."}]})],RememberPlugin);import{z as z6}from"@frontmcp/lazy-zod";var rememberScopeSchema=z6.enum(["session","user","tool","global"]);function brand(data,_brand){return data}var rememberEntrySchema=z6.object({value:z6.unknown(),brand:z6.enum(["approval","preference","cache","state","conversation","custom"]).optional(),createdAt:z6.number(),updatedAt:z6.number(),expiresAt:z6.number().optional(),metadata:z6.record(z6.string(),z6.unknown()).optional()});function getRemember(ctx){return ctx.get(RememberAccessorToken)}function tryGetRemember(ctx){if(typeof ctx.tryGet==="function"){return ctx.tryGet(RememberAccessorToken)}return void 0}export{ForgetTool,ListMemoriesTool,RecallTool,RememberAccessor,RememberAccessorToken,RememberConfigToken,RememberConfigurationError,RememberIdentityError,RememberMemoryProvider,RememberPlugin,RememberRedisProvider,RememberScopeNotAllowedError,RememberStoreToken,RememberThisTool,RememberVercelKvProvider,brand,RememberPlugin as default,getRemember,tryGetRemember};
8
+ `;function createRedisClient(options){const ioredis=__require("ioredis");const Redis=ioredis.default??ioredis;return new Redis(options)}var RememberRedisProvider=class{client;keyPrefix;defaultTTL;ownsClient;constructor(options){this.keyPrefix=options.keyPrefix??"";this.defaultTTL=options.defaultTTL;if(options.type==="redis-client"){this.client=options.client;this.ownsClient=false;return}this.ownsClient=true;this.client=createRedisClient({lazyConnect:false,maxRetriesPerRequest:3,...options.config});this.client.on("connect",()=>{if(process.env["DEBUG"]==="true"){console.log("[RememberPlugin:Redis] Connected")}});this.client.on("error",err=>{console.error("[RememberPlugin:Redis] Error:",err.message)})}async setValue(key,value,ttlSeconds){if(value===void 0){throw new Error("Cannot store undefined value. Use null or delete the key instead.")}if(ttlSeconds!==void 0){if(typeof ttlSeconds!=="number"||!Number.isFinite(ttlSeconds)){throw new Error(`Invalid TTL: expected a number, got ${typeof ttlSeconds}`)}if(ttlSeconds<=0){throw new Error(`Invalid TTL: must be positive, got ${ttlSeconds}`)}if(!Number.isInteger(ttlSeconds)){throw new Error(`Invalid TTL: must be an integer, got ${ttlSeconds}`)}}const fullKey=prefixedStoreKey(this.keyPrefix,key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;if(ttl!==void 0&&ttl>0){await this.client.set(fullKey,strValue,"EX",ttl)}else{await this.client.set(fullKey,strValue)}await this.deleteDoubledPrefixKey(key)}async setIfAbsent(key,value,ttlSeconds){const fullKey=prefixedStoreKey(this.keyPrefix,key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const hasTtl=ttl!==void 0&&ttl>0;const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(doubledKey){const ttlArgument=hasTtl?String(ttl):"";const created=await this.client.eval(SET_IF_NEITHER_KEY_EXISTS_SCRIPT,2,fullKey,doubledKey,strValue,ttlArgument);return created===1}const result=hasTtl?await this.client.set(fullKey,strValue,"EX",ttl,"NX"):await this.client.set(fullKey,strValue,"NX");return result==="OK"}async getValue(key,defaultValue){const raw=await this.client.get(prefixedStoreKey(this.keyPrefix,key))??await this.readDoubledPrefixValue(key);if(raw===null)return defaultValue;try{return JSON.parse(raw)}catch{return defaultValue}}async delete(key){await this.client.del(prefixedStoreKey(this.keyPrefix,key));await this.deleteDoubledPrefixKey(key)}async exists(key){if(await this.client.exists(prefixedStoreKey(this.keyPrefix,key))===1)return true;const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey!==void 0&&await this.client.exists(doubledKey)===1}async keys(pattern="*"){const result=new Set;for(const key of await this.scan(prefixedStoreKey(this.keyPrefix,pattern))){result.add(callerKeyOf(this.keyPrefix,pattern,key))}const doubledPattern=doubledPrefixKey(this.keyPrefix,pattern);if(doubledPattern){for(const key of await this.scan(doubledPattern))result.add(key.slice(this.keyPrefix.length))}return[...result]}async scan(match){const found=[];let cursor="0";do{const[nextCursor,keys]=await this.client.scan(cursor,"MATCH",match,"COUNT",100);cursor=nextCursor;found.push(...keys)}while(cursor!=="0");return found}async readDoubledPrefixValue(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey?this.client.get(doubledKey):null}async deleteDoubledPrefixKey(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(doubledKey)await this.client.del(doubledKey)}async close(){if(this.ownsClient){await this.client.quit()}}};RememberRedisProvider=__decorateClass([Provider3({name:"provider:remember:redis",description:"Redis-based storage provider for RememberPlugin",scope:ProviderScope3.GLOBAL})],RememberRedisProvider);import{Provider as Provider4,ProviderScope as ProviderScope4}from"@frontmcp/sdk";import{createVercelKvClient}from"@frontmcp/utils";var RememberVercelKvProvider=class{connection;client;keyPrefix;defaultTTL;constructor(options={}){const hasUrl=options.url!==void 0;const hasToken=options.token!==void 0;if(hasUrl!==hasToken){throw new Error(`RememberVercelKvProvider: Both 'url' and 'token' must be provided together, or neither. Received: url=${hasUrl?"provided":"missing"}, token=${hasToken?"provided":"missing"}`)}this.connection={url:options.url,token:options.token};this.keyPrefix=options.keyPrefix??"remember:";this.defaultTTL=options.defaultTTL}kv(){this.client??=createVercelKvClient(this.connection).catch(error=>{this.client=void 0;throw error});return this.client}prefixKey(key){return prefixedStoreKey(this.keyPrefix,key)}async setValue(key,value,ttlSeconds){const fullKey=this.prefixKey(key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const kv=await this.kv();if(ttl&&ttl>0){await kv.set(fullKey,strValue,{ex:ttl})}else{await kv.set(fullKey,strValue)}await this.deleteDoubledPrefixKey(key)}async setIfAbsent(key,value,ttlSeconds){const fullKey=this.prefixKey(key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const doubledKey=doubledPrefixKey(this.keyPrefix,key);const kv=await this.kv();if(doubledKey){const ttlArgument=ttl&&ttl>0?String(ttl):"";const created=await kv.eval(SET_IF_NEITHER_KEY_EXISTS_SCRIPT,[fullKey,doubledKey],[strValue,ttlArgument]);return Number(created)===1}const result=ttl&&ttl>0?await kv.set(fullKey,strValue,{nx:true,ex:ttl}):await kv.set(fullKey,strValue,{nx:true});return result==="OK"}async getValue(key,defaultValue){const kv=await this.kv();const raw=await kv.get(this.prefixKey(key))??await this.readDoubledPrefixValue(key);if(raw===null)return defaultValue;try{return JSON.parse(raw)}catch{return raw}}async delete(key){const kv=await this.kv();await kv.del(this.prefixKey(key));await this.deleteDoubledPrefixKey(key)}async exists(key){const kv=await this.kv();if(await kv.exists(this.prefixKey(key))===1)return true;const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey!==void 0&&await kv.exists(doubledKey)===1}async keys(pattern="*"){const result=new Set;for(const key of await this.scan(this.prefixKey(pattern))){result.add(callerKeyOf(this.keyPrefix,pattern,key))}const doubledPattern=doubledPrefixKey(this.keyPrefix,pattern);if(doubledPattern){for(const key of await this.scan(doubledPattern))result.add(key.slice(this.keyPrefix.length))}return[...result]}async scan(match){const found=[];const kv=await this.kv();try{let cursor=0;do{const[nextCursor,keys]=await kv.scan(cursor,{match,count:100});cursor=nextCursor;found.push(...keys)}while(String(cursor)!=="0")}catch{try{found.push(...await kv.keys(match))}catch{console.warn("[RememberPlugin:VercelKV] keys() operation not supported")}}return found}async readDoubledPrefixValue(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(!doubledKey)return null;const kv=await this.kv();return kv.get(doubledKey)}async deleteDoubledPrefixKey(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(!doubledKey)return;const kv=await this.kv();await kv.del(doubledKey)}async close(){}};RememberVercelKvProvider=__decorateClass([Provider4({name:"provider:remember:vercel-kv",description:"Vercel KV-based storage provider for RememberPlugin",scope:ProviderScope4.GLOBAL})],RememberVercelKvProvider);var RememberStoreToken=Symbol("plugin:remember:store");var RememberConfigToken=Symbol("plugin:remember:config");var RememberAccessorToken=Symbol("plugin:remember:accessor");import{Tool as Tool5}from"@frontmcp/sdk";import{z as z2}from"@frontmcp/lazy-zod";import{Tool,ToolContext}from"@frontmcp/sdk";var REMEMBER_SCOPE_DESCRIPTION="Whose memory to use (default: session). session: this session; without one (stateless HTTP, MCP 2026-07-28), the signed-in caller across its requests. user: the signed-in caller, across all of its sessions. tool: the tool running the call, for the same caller as session; each memory tool has its own, so the other memory tools do not see what one of them stores in tool scope. global: shared by every caller. An anonymous caller cannot use user scope, nor session or tool scope without a session.";function rememberToolNames(prefix=""){return{rememberThis:`${prefix}remember_this`,recall:`${prefix}recall`,forget:`${prefix}forget`,listMemories:`${prefix}list_memories`}}var forgetInputSchema={key:z2.string().min(1).describe("What memory to forget"),scope:z2.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION)};var forgetOutputSchema=z2.object({success:z2.boolean(),key:z2.string(),scope:z2.string(),existed:z2.boolean()});function forgetDescription(){return"Forget a previously remembered value. Use this when the user wants to delete stored preferences or information."}function forgetToolMetadata(names=rememberToolNames()){return{name:names.forget,description:forgetDescription(),inputSchema:forgetInputSchema,outputSchema:forgetOutputSchema,annotations:{readOnlyHint:false}}}var ForgetTool=class extends ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const existed=await remember.knows(input.key,{scope});await remember.forget(input.key,{scope});return{success:true,key:input.key,scope,existed}}};ForgetTool=__decorateClass([Tool(forgetToolMetadata())],ForgetTool);import{z as z3}from"@frontmcp/lazy-zod";import{Tool as Tool2,ToolContext as ToolContext2}from"@frontmcp/sdk";var listMemoriesInputSchema={scope:z3.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION),pattern:z3.string().optional().describe('Pattern to filter keys (e.g., "user_*")'),limit:z3.number().positive().max(100).optional().describe("Maximum number of keys to return (default: 50)")};var listMemoriesOutputSchema=z3.object({keys:z3.array(z3.string()),scope:z3.string(),count:z3.number(),truncated:z3.boolean()});function listMemoriesDescription(names=rememberToolNames()){return`List the remembered keys in one scope (default: session). Use this to see what memories are stored before you ${names.recall} or ${names.forget} one.`}function listMemoriesToolMetadata(names=rememberToolNames()){return{name:names.listMemories,description:listMemoriesDescription(names),inputSchema:listMemoriesInputSchema,outputSchema:listMemoriesOutputSchema,annotations:{readOnlyHint:true}}}var ListMemoriesTool=class extends ToolContext2{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const limit=input.limit??50;let keys=await remember.list({scope,pattern:input.pattern});const truncated=keys.length>limit;if(truncated){keys=keys.slice(0,limit)}return{keys,scope,count:keys.length,truncated}}};ListMemoriesTool=__decorateClass([Tool2(listMemoriesToolMetadata())],ListMemoriesTool);import{z as z4}from"@frontmcp/lazy-zod";import{Tool as Tool3,ToolContext as ToolContext3}from"@frontmcp/sdk";var recallInputSchema={key:z4.string().min(1).describe("What memory to recall"),scope:z4.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION)};var recallOutputSchema=z4.object({found:z4.boolean(),key:z4.string(),value:z4.unknown().optional(),scope:z4.string(),createdAt:z4.number().optional(),expiresAt:z4.number().optional()});function recallDescription(names=rememberToolNames()){return`Recall something that was previously remembered. Use this to retrieve stored preferences, settings, or any information that was saved with ${names.rememberThis}.`}function recallToolMetadata(names=rememberToolNames()){return{name:names.recall,description:recallDescription(names),inputSchema:recallInputSchema,outputSchema:recallOutputSchema,annotations:{readOnlyHint:true}}}var RecallTool=class extends ToolContext3{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const entry=await remember.getEntry(input.key,{scope});if(!entry){return{found:false,key:input.key,scope}}return{found:true,key:input.key,value:entry.value,scope,createdAt:entry.createdAt,expiresAt:entry.expiresAt}}};RecallTool=__decorateClass([Tool3(recallToolMetadata())],RecallTool);import{z as z5}from"@frontmcp/lazy-zod";import{Tool as Tool4,ToolContext as ToolContext4}from"@frontmcp/sdk";var rememberThisInputSchema={key:z5.string().min(1).describe('What to call this memory (e.g., "user_preference", "last_action")'),value:z5.unknown().describe("The value to remember (any JSON-serializable data)"),scope:z5.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION),ttl:z5.number().int().positive().optional().describe("Forget after this many whole seconds"),brand:z5.enum(["preference","cache","state","conversation","custom"]).optional().describe("Type hint for the stored value")};var rememberThisOutputSchema=z5.object({success:z5.boolean(),key:z5.string(),scope:z5.string(),expiresAt:z5.number().optional()});function rememberThisDescription(names=rememberToolNames()){return`Remember something for later: store a value under a key that ${names.recall} can read back, in the scope you choose (this session by default; user scope for the signed-in user across sessions). It lasts until it is forgotten or its ttl runs out. Use this when the user asks you to remember preferences, settings, or any information they want to persist.`}function rememberThisToolMetadata(names=rememberToolNames()){return{name:names.rememberThis,description:rememberThisDescription(names),inputSchema:rememberThisInputSchema,outputSchema:rememberThisOutputSchema,annotations:{readOnlyHint:false}}}var RememberThisTool=class extends ToolContext4{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const entry=await remember.set(input.key,input.value,{scope,ttl:input.ttl,brand:input.brand});return{success:true,key:input.key,scope,expiresAt:entry.expiresAt}}};RememberThisTool=__decorateClass([Tool4(rememberThisToolMetadata())],RememberThisTool);function createRememberTools(prefix=""){if(!prefix)return[RememberThisTool,RecallTool,ForgetTool,ListMemoriesTool];const names=rememberToolNames(prefix);let PrefixedRememberThisTool=class extends RememberThisTool{};PrefixedRememberThisTool=__decorateClass([Tool5(rememberThisToolMetadata(names))],PrefixedRememberThisTool);let PrefixedRecallTool=class extends RecallTool{};PrefixedRecallTool=__decorateClass([Tool5(recallToolMetadata(names))],PrefixedRecallTool);let PrefixedForgetTool=class extends ForgetTool{};PrefixedForgetTool=__decorateClass([Tool5(forgetToolMetadata(names))],PrefixedForgetTool);let PrefixedListMemoriesTool=class extends ListMemoriesTool{};PrefixedListMemoriesTool=__decorateClass([Tool5(listMemoriesToolMetadata(names))],PrefixedListMemoriesTool);return[PrefixedRememberThisTool,PrefixedRecallTool,PrefixedForgetTool,PrefixedListMemoriesTool]}function withCheckedOptions(options){const customKey=options.encryption?.customKey;if(customKey!==void 0&&customKey.trim()===""){throw new RememberConfigurationError("RememberPlugin encryption.customKey must not be empty; omit it to derive keys from the server secret")}const{defaultTTL}=options;if(defaultTTL===void 0)return options;if(defaultTTL===0)return{...options,defaultTTL:void 0};if(!Number.isInteger(defaultTTL)||defaultTTL<0){throw new RememberConfigurationError(`RememberPlugin defaultTTL must be a whole number of seconds (0 for no default expiry), got ${defaultTTL}`)}return options}var RememberPlugin=class extends DynamicPlugin{options;constructor(options={}){super();this.options={...RememberPlugin.defaultOptions,...withCheckedOptions(options)}}};__publicField(RememberPlugin,"defaultOptions",{type:"memory",keyPrefix:"remember:",encryption:{enabled:true}});__publicField(RememberPlugin,"dynamicTools",options=>options.tools?.enabled?createRememberTools(options.tools.prefix):[]);__publicField(RememberPlugin,"browserStores",(options,server)=>{const type=options.type??RememberPlugin.defaultOptions.type;const onRedis=type==="redis"||type==="redis-client"||type==="global-store"&&server.redis!==void 0&&!isVercelKvProvider(server.redis);return[{plugin:"RememberPlugin",option:onRedis?`type: '${type}'`:void 0,memory:"type: 'memory'"}]});__publicField(RememberPlugin,"dynamicProviders",options=>{const providers=[];const config={...RememberPlugin.defaultOptions,...withCheckedOptions(options)};switch(config.type){case"global-store":providers.push(storeProvider({name:"remember:store:global",provide:RememberStoreToken,inject:()=>[FrontMcpConfig],useFactory:frontmcpConfig=>{const storeConfig=getGlobalStoreConfig("RememberPlugin",frontmcpConfig);if(isVercelKvProvider(storeConfig)){return new RememberVercelKvProvider({url:storeConfig.url,token:storeConfig.token,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}return new RememberRedisProvider({type:"redis",config:{host:storeConfig.host??"localhost",port:storeConfig.port??6379,password:storeConfig.password,db:storeConfig.db},keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}}));break;case"redis":if("config"in config&&config.config){const redisConfig=config.config;let store;providers.push(storeProvider({name:"remember:store:redis",provide:RememberStoreToken,inject:()=>[],useFactory:()=>store??=new RememberRedisProvider({type:"redis",config:redisConfig,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}))}break;case"redis-client":if("client"in config&&config.client){providers.push({name:"remember:store:redis-client",provide:RememberStoreToken,useValue:new RememberRedisProvider({type:"redis-client",client:config.client,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})})}break;case"vercel-kv":providers.push({name:"remember:store:vercel-kv",provide:RememberStoreToken,useValue:new RememberVercelKvProvider({url:"url"in config?config.url:void 0,token:"token"in config?config.token:void 0,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})});break;case"memory":default:providers.push({name:"remember:store:memory",provide:RememberStoreToken,inject:()=>[],useFactory:()=>new RememberMemoryProvider(void 0,config.defaultTTL)});break}providers.push({name:"remember:config",provide:RememberConfigToken,useValue:config});providers.push({name:"remember:accessor",provide:RememberAccessorToken,scope:ProviderScope5.CONTEXT,inject:()=>[RememberStoreToken,FRONTMCP_CONTEXT,RememberConfigToken,FrontMcpLogger],useFactory:(store,ctx,cfg,logger)=>createRememberAccessor(store,ctx,cfg,logger)});return providers});RememberPlugin=__decorateClass([Plugin({name:"remember",description:"Help your LLM remember things across sessions",providers:[{name:"remember:store:memory",provide:RememberStoreToken,inject:()=>[],useFactory:()=>new RememberMemoryProvider}],contextExtensions:[{property:"remember",token:RememberAccessorToken,errorMessage:"RememberPlugin is not installed. Add RememberPlugin.init() to your plugins array."}]})],RememberPlugin);import{z as z6}from"@frontmcp/lazy-zod";var rememberScopeSchema=z6.enum(["session","user","tool","global"]);function brand(data,_brand){return data}var rememberEntrySchema=z6.object({value:z6.unknown(),brand:z6.enum(["approval","preference","cache","state","conversation","custom"]).optional(),createdAt:z6.number(),updatedAt:z6.number(),expiresAt:z6.number().optional(),metadata:z6.record(z6.string(),z6.unknown()).optional()});function getRemember(ctx){return ctx.get(RememberAccessorToken)}function tryGetRemember(ctx){if(typeof ctx.tryGet==="function"){return ctx.tryGet(RememberAccessorToken)}return void 0}export{ForgetTool,ListMemoriesTool,RecallTool,RememberAccessor,RememberAccessorToken,RememberConfigToken,RememberConfigurationError,RememberIdentityError,RememberMemoryProvider,RememberPlugin,RememberRedisProvider,RememberScopeNotAllowedError,RememberStoreToken,RememberThisTool,RememberVercelKvProvider,brand,RememberPlugin as default,getRemember,tryGetRemember};
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-remember",
3
- "version": "1.10.16",
3
+ "version": "1.11.0-rc.1",
4
4
  "description": "Remember plugin for FrontMCP: encrypted key-value memory for tools (this.remember) in session, user, tool and global scopes, stored in memory, Redis or Vercel KV",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -52,12 +52,12 @@
52
52
  }
53
53
  },
54
54
  "dependencies": {
55
- "@frontmcp/utils": "1.10.16",
55
+ "@frontmcp/utils": "1.11.0-rc.1",
56
56
  "ioredis": "^5.8.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@frontmcp/lazy-zod": "1.10.16",
60
- "@frontmcp/sdk": "1.10.16",
59
+ "@frontmcp/lazy-zod": "1.11.0-rc.1",
60
+ "@frontmcp/sdk": "1.11.0-rc.1",
61
61
  "@vercel/kv": "^2.0.0 || ^3.0.0"
62
62
  },
63
63
  "peerDependenciesMeta": {
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  "devDependencies": {
69
- "@frontmcp/sdk": "1.10.16",
69
+ "@frontmcp/sdk": "1.11.0-rc.1",
70
70
  "reflect-metadata": "^0.2.2"
71
71
  }
72
72
  }
package/index.js CHANGED
@@ -1,5 +1,5 @@
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 __defNormalProp=(obj,key,value)=>key in obj?__defProp(obj,key,{enumerable:true,configurable:true,writable:true,value}):obj[key]=value;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 __publicField=(obj,key,value)=>__defNormalProp(obj,typeof key!=="symbol"?key+"":key,value);var index_exports={};__export(index_exports,{ForgetTool:()=>ForgetTool,ListMemoriesTool:()=>ListMemoriesTool,RecallTool:()=>RecallTool,RememberAccessor:()=>RememberAccessor,RememberAccessorToken:()=>RememberAccessorToken,RememberConfigToken:()=>RememberConfigToken,RememberConfigurationError:()=>RememberConfigurationError,RememberIdentityError:()=>RememberIdentityError,RememberMemoryProvider:()=>RememberMemoryProvider,RememberPlugin:()=>RememberPlugin,RememberRedisProvider:()=>RememberRedisProvider,RememberScopeNotAllowedError:()=>RememberScopeNotAllowedError,RememberStoreToken:()=>RememberStoreToken,RememberThisTool:()=>RememberThisTool,RememberVercelKvProvider:()=>RememberVercelKvProvider,brand:()=>brand,default:()=>RememberPlugin,getRemember:()=>getRemember,tryGetRemember:()=>tryGetRemember});module.exports=__toCommonJS(index_exports);var import_sdk11=require("@frontmcp/sdk");var import_sdk2=require("@frontmcp/sdk");var import_utils2=require("@frontmcp/utils");var path=__toESM(require("path"));var import_lazy_zod=require("@frontmcp/lazy-zod");var import_utils=require("@frontmcp/utils");var DEFAULT_SECRET_FILE_PATH=".frontmcp/remember-secret.json";var SECRET_BYTES=32;var rememberSecretDataSchema=import_lazy_zod.z.object({secret:import_lazy_zod.z.string().min(32),createdAt:import_lazy_zod.z.number().positive().int(),version:import_lazy_zod.z.literal(1)});function validateSecretData(data){const result=rememberSecretDataSchema.safeParse(data);if(!result.success){return{valid:false,error:result.error.issues[0]?.message??"Invalid secret structure"}}const parsed=result.data;const now=Date.now();if(parsed.createdAt>now+6e4){return{valid:false,error:"createdAt is in the future"}}const hundredYearsMs=100*365*24*60*60*1e3;if(parsed.createdAt<now-hundredYearsMs){return{valid:false,error:"createdAt is too old"}}return{valid:true}}function isSecretPersistenceEnabled(options){const isProduction=process.env["NODE_ENV"]==="production";if(isProduction){return options?.forceEnable===true}return true}function resolveSecretPath(options){const secretPath=options?.secretPath??DEFAULT_SECRET_FILE_PATH;if(path.isAbsolute(secretPath)){return secretPath}return path.resolve(process.cwd(),secretPath)}async function loadRememberSecret(options){if(!isSecretPersistenceEnabled(options)){return null}const secretPath=resolveSecretPath(options);try{const content=await(0,import_utils.readFile)(secretPath,"utf8");const data=JSON.parse(content);const validation=validateSecretData(data);if(!validation.valid){console.warn(`[RememberSecretPersistence] Invalid secret file format at ${secretPath}: ${validation.error}, will regenerate`);return null}return data}catch(error){if(error.code==="ENOENT"){return null}console.warn(`[RememberSecretPersistence] Failed to load secret from ${secretPath}: ${error.message}`);return null}}async function saveRememberSecret(secretData,options){if(!isSecretPersistenceEnabled(options)){return true}const secretPath=resolveSecretPath(options);const dir=path.dirname(secretPath);const tempPath=`${secretPath}.tmp.${Date.now()}.${(0,import_utils.base64urlEncode)((0,import_utils.randomBytes)(8))}`;try{await(0,import_utils.mkdir)(dir,{recursive:true,mode:448});const content=JSON.stringify(secretData,null,2);await(0,import_utils.writeFile)(tempPath,content,{mode:384});await(0,import_utils.rename)(tempPath,secretPath);return true}catch(error){console.error(`[RememberSecretPersistence] Failed to save secret to ${secretPath}: ${error.message}`);try{await(0,import_utils.unlink)(tempPath)}catch{}return false}}var cachedSecret=null;var secretGenerationPromise=null;function generateSecret(){return(0,import_utils.base64urlEncode)((0,import_utils.randomBytes)(SECRET_BYTES))}async function getOrCreatePersistedSecret(options){if(cachedSecret){return cachedSecret}if(secretGenerationPromise){return secretGenerationPromise}secretGenerationPromise=(async()=>{try{const loaded=await loadRememberSecret(options);if(loaded){cachedSecret=loaded.secret;return cachedSecret}const newSecret=generateSecret();const secretData={secret:newSecret,createdAt:Date.now(),version:1};if(isSecretPersistenceEnabled(options)){await saveRememberSecret(secretData,options)}else{console.warn("[RememberSecretPersistence] No REMEMBER_SECRET (or MCP_MEMORY_SECRET / MCP_SESSION_SECRET) is set in production, so a random in-memory secret is used. Encrypted memory will be unreadable after a restart and by other instances sharing the store. Set REMEMBER_SECRET to the same value on every instance.")}cachedSecret=newSecret;return cachedSecret}finally{secretGenerationPromise=null}})();return secretGenerationPromise}async function getBaseSecret(){const envSecret=process.env["REMEMBER_SECRET"]||process.env["MCP_MEMORY_SECRET"]||process.env["MCP_SESSION_SECRET"];if(envSecret){return envSecret}return getOrCreatePersistedSecret()}async function secretFor(source){return source.secret?source.secret:getBaseSecret()}var textEncoder=new TextEncoder;async function deriveEncryptionKey(source){const salt=textEncoder.encode("remember-plugin-v1");let ikm;let context;switch(source.type){case"session":ikm=await secretFor(source)+source.sessionId;context=`remember:session:${source.sessionId}`;break;case"tool":ikm=await secretFor(source)+source.sessionId;context=`remember:tool:${source.toolName}:${source.sessionId}`;break;case"user":ikm=await secretFor(source)+source.userId;context=`remember:user:${source.userId}`;break;case"global":ikm=await secretFor(source);context="remember:global";break;case"custom":ikm=source.key;context=`remember:custom:${source.key}`;break}const ikmBytes=textEncoder.encode(ikm);const info=textEncoder.encode(context);return(0,import_utils2.hkdfSha256)(ikmBytes,salt,info,32)}function getKeySourceForScope(scope,context,secret){switch(scope){case"session":return{type:"session",sessionId:context.sessionId,secret};case"user":return{type:"user",userId:context.userId??"anonymous",secret};case"tool":return{type:"tool",toolName:context.toolName??"unknown",sessionId:context.sessionId,secret};case"global":return{type:"global",secret}}}var textDecoder=new TextDecoder;async function encryptValue(data,keySource){const key=await deriveEncryptionKey(keySource);const plaintext=textEncoder.encode(JSON.stringify(data));const iv=(0,import_utils2.randomBytes)(12);const{ciphertext,tag}=(0,import_utils2.encryptAesGcm)(key,plaintext,iv);return{alg:"A256GCM",iv:(0,import_utils2.base64urlEncode)(iv),tag:(0,import_utils2.base64urlEncode)(tag),data:(0,import_utils2.base64urlEncode)(ciphertext)}}async function decryptValue(blob,keySource){try{const key=await deriveEncryptionKey(keySource);const iv=(0,import_utils2.base64urlDecode)(blob.iv);const tag=(0,import_utils2.base64urlDecode)(blob.tag);const ciphertext=(0,import_utils2.base64urlDecode)(blob.data);const decrypted=(0,import_utils2.decryptAesGcm)(key,ciphertext,iv,tag);return JSON.parse(textDecoder.decode(decrypted))}catch{return null}}function serializeBlob(blob){return JSON.stringify(blob)}function deserializeBlob(str){try{const parsed=JSON.parse(str);if(parsed&&typeof parsed==="object"&&parsed.alg==="A256GCM"&&typeof parsed.iv==="string"&&typeof parsed.tag==="string"&&typeof parsed.data==="string"){return parsed}return null}catch{return null}}async function encryptAndSerialize(data,keySource){const blob=await encryptValue(data,keySource);return serializeBlob(blob)}async function deserializeAndDecrypt(str,keySource){const blob=deserializeBlob(str);if(!blob)return null;return decryptValue(blob,keySource)}var import_sdk=require("@frontmcp/sdk");var RememberIdentityError=class extends import_sdk.PublicMcpError{name="RememberIdentityError";constructor(message){super(message,"REMEMBER_IDENTITY_REQUIRED",403)}};var RememberScopeNotAllowedError=class extends import_sdk.PublicMcpError{name="RememberScopeNotAllowedError";constructor(scope,allowedScopes){super(`Scope '${scope}' is not allowed. Allowed scopes: ${allowedScopes.join(", ")}`,"REMEMBER_SCOPE_NOT_ALLOWED",400)}};var RememberConfigurationError=class extends import_sdk.InternalMcpError{name="RememberConfigurationError";constructor(message){super(message,"REMEMBER_CONFIGURATION")}};var LEGACY_SCOPES=["session","tool","user"];var DEFAULT_LEGACY_PURGE_DELAY_MS=864e5;var LAYOUT_MARKER_KEY="__layout__";var LAYOUT_VERSION=2;var MAX_TIMER_MS=2147483647;var scheduled=new WeakSet;function parseFirstSeenAt(raw){if(!raw)return void 0;try{const parsed=typeof raw==="string"?JSON.parse(raw):raw;const firstSeenAt=parsed?.firstSeenAt;return typeof firstSeenAt==="number"&&Number.isFinite(firstSeenAt)?firstSeenAt:void 0}catch{return void 0}}async function readLayoutFirstSeenAt(store,keyPrefix,logger,markerTtlSeconds){const key=`${keyPrefix}${LAYOUT_MARKER_KEY}`;const standDown=(reason,extra={})=>{logger?.debug?.(`remember: ${reason}, legacy purge stood down`,{key,...extra});return void 0};try{const existing=await store.getValue(key);if(existing){return parseFirstSeenAt(existing)??standDown("layout marker is unreadable")}const marker={version:LAYOUT_VERSION,firstSeenAt:Date.now()};const serialized=JSON.stringify(marker);if(!store.setIfAbsent){await store.setValue(key,serialized,markerTtlSeconds);return marker.firstSeenAt}if(await store.setIfAbsent(key,serialized,markerTtlSeconds)){return marker.firstSeenAt}const winner=await store.getValue(key);return parseFirstSeenAt(winner)??standDown("layout marker vanished after a lost race")}catch(error){return standDown("could not establish the layout marker",{error:error instanceof Error?error.message:String(error)})}}async function purgeLegacyRememberEntries(store,keyPrefix,logger){let deleted=0;for(const scope of LEGACY_SCOPES){const pattern=`${keyPrefix}${scope}:*`;let keys;try{keys=await store.keys(pattern)}catch(error){logger?.debug?.("remember: legacy purge skipped, store could not list keys",{pattern,error:error instanceof Error?error.message:String(error)});continue}for(const key of keys){try{await store.delete(key);deleted+=1}catch{}}}if(deleted>0){logger?.warn?.(`remember: purged ${deleted} entr${deleted===1?"y":"ies"} left unreadable by the session/tool key-derivation change and the namespace encoding. This is a one-time upgrade step; set skipLegacyPurge to handle the migration yourself.`)}return deleted}function scheduleLegacyRememberPurge(store,keyPrefix,options={}){if(scheduled.has(store))return;scheduled.add(store);const delayMs=options.delayMs??DEFAULT_LEGACY_PURGE_DELAY_MS;const markerTtlSeconds=options.defaultTTL?Math.max(options.defaultTTL,Math.ceil(2*delayMs/1e3)):void 0;const arm=waitMs=>{const timer=setTimeout(()=>{void attempt().catch(()=>void 0)},Math.min(waitMs,MAX_TIMER_MS));timer.unref?.()};const attempt=async()=>{const firstSeenAt=await readLayoutFirstSeenAt(store,keyPrefix,options.logger,markerTtlSeconds);if(firstSeenAt===void 0)return;const remainingMs=firstSeenAt+delayMs-Date.now();if(remainingMs>0){arm(remainingMs);return}await purgeLegacyRememberEntries(store,keyPrefix,options.logger)};void attempt().catch(()=>void 0)}var STORAGE_LAYOUT_VERSION="v2";function encodeKeyPart(value){return encodeURIComponent(value)}function remainingSeconds(expiresAt,now){return Math.max(1,Math.ceil((expiresAt-now)/1e3))}function expiryAfter(ttlSeconds,now){return ttlSeconds?now+ttlSeconds*1e3:void 0}var RememberAccessor=class{store;ctx;config;keyPrefix;encryptionEnabled;constructor(store,ctx,config,logger){this.store=store;this.ctx=ctx;this.config=config;this.keyPrefix=config.keyPrefix??"remember:";this.encryptionEnabled=config.encryption?.enabled!==false;if(!config.skipLegacyPurge){scheduleLegacyRememberPurge(store,this.keyPrefix,{delayMs:config.legacyPurgeDelayMs,defaultTTL:config.defaultTTL,logger})}}async set(key,value,options={}){const scope=options.scope??"session";const storageKey=this.buildStorageKey(key,scope);const now=Date.now();const ttlSeconds=options.ttl??this.config.defaultTTL;const entry={value,brand:options.brand,createdAt:now,updatedAt:now,expiresAt:expiryAfter(ttlSeconds,now),metadata:options.metadata};const serialized=this.encryptionEnabled?await encryptAndSerialize(entry,this.getKeySource(scope)):JSON.stringify(entry);await this.store.setValue(storageKey,serialized,ttlSeconds);return entry}async get(key,options={}){const scope=options.scope??"session";const entry=await this.readEntry(this.buildStorageKey(key,scope),scope);return entry?entry.value:options.defaultValue}async getEntry(key,options={}){const scope=options.scope??"session";return this.readEntry(this.buildStorageKey(key,scope),scope)}async forget(key,options={}){const scope=options.scope??"session";const storageKey=this.buildStorageKey(key,scope);await this.store.delete(storageKey)}async knows(key,options={}){const scope=options.scope??"session";return await this.readEntry(this.buildStorageKey(key,scope),scope)!==void 0}async list(options={}){const scope=options.scope??"session";const scopePrefix=this.buildScopePrefix(scope);const storageKeys=await this.store.keys(scopePrefix+(options.pattern??"*"));const entries=await Promise.all(storageKeys.map(storageKey=>this.readEntry(storageKey,scope)));return storageKeys.filter((_storageKey,index)=>entries[index]!==void 0).map(storageKey=>storageKey.slice(scopePrefix.length))}async update(key,value,options={}){const scope=options.scope??"session";const existing=await this.getEntry(key,{scope});if(!existing)return false;const now=Date.now();const expiresAt=expiryAfter(options.ttl,now)??existing.expiresAt??expiryAfter(this.config.defaultTTL,now);const entry={...existing,value,updatedAt:now,expiresAt};const storageKey=this.buildStorageKey(key,scope);const serialized=this.encryptionEnabled?await encryptAndSerialize(entry,this.getKeySource(scope)):JSON.stringify(entry);const writtenAt=Date.now();if(expiresAt!==void 0&&expiresAt<=writtenAt)return false;const storageTtl=expiresAt!==void 0?remainingSeconds(expiresAt,writtenAt):void 0;await this.store.setValue(storageKey,serialized,storageTtl);return true}get sessionId(){return this.ctx.sessionId}get userId(){const authInfo=this.ctx.authInfo;return authInfo?.extra?.["userId"]??authInfo?.extra?.["sub"]??authInfo?.clientId??void 0}buildStorageKey(key,scope){return this.buildScopePrefix(scope)+key}get verifiedSessionId(){const authInfo=this.ctx.authInfo;return[authInfo?.sessionId,authInfo?.extra?.["sessionId"]].find(candidate=>typeof candidate==="string"&&candidate.length>0&&candidate!==import_sdk2.STATELESS_SESSION_ID)}resolveSessionIdentity(){const sessionId=this.verifiedSessionId;if(sessionId){return sessionId}const userId=this.userId;if(userId&&!(0,import_sdk2.isAnonymousSubject)(userId)){return`stateless-user:${userId}`}throw new RememberIdentityError(`Remember cannot use session or tool scope for an unauthenticated request without a verified session: a stateless request shares the session id "${import_sdk2.STATELESS_SESSION_ID}", and an mcp-session-id the server did not verify could belong to any caller, so the data would be shared across clients. Authenticate the request, use a stateful transport, or choose the 'global' scope if the data really is shared.`)}get toolName(){return(0,import_sdk2.getRunningTool)()?.fullName??"unknown"}buildScopePrefix(scope){switch(scope){case"session":return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:session:${encodeKeyPart(this.resolveSessionIdentity())}:`;case"user":{const userId=this.userId;if(!userId||(0,import_sdk2.isAnonymousSubject)(userId)){throw new RememberIdentityError("Remember cannot use user scope without an authenticated user: all unauthenticated callers would share one namespace. Authenticate the request or use the 'global' scope.")}return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:user:${encodeKeyPart(userId)}:`}case"tool":return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:tool:${encodeKeyPart(this.toolName)}:${encodeKeyPart(this.resolveSessionIdentity())}:`;case"global":return`${this.keyPrefix}global:`}}getKeySource(scope){const needsSessionIdentity=scope==="session"||scope==="tool";return getKeySourceForScope(scope,{sessionId:needsSessionIdentity?this.resolveSessionIdentity():this.ctx.sessionId,userId:this.userId,toolName:scope==="tool"?this.toolName:void 0},this.config.encryption?.customKey)}async readEntry(storageKey,scope){const stored=await this.store.getValue(storageKey);if(!stored)return void 0;const raw=typeof stored==="string"?stored:JSON.stringify(stored);const entry=this.encryptionEnabled?await deserializeAndDecrypt(raw,this.getKeySource(scope)):this.parseEntry(raw);if(!entry)return void 0;if(entry.expiresAt&&Date.now()>entry.expiresAt){await this.store.delete(storageKey);return void 0}return entry}parseEntry(raw){try{return JSON.parse(raw)}catch{return null}}};RememberAccessor=__decorateClass([(0,import_sdk2.Provider)({name:"provider:remember:accessor",description:"Context-scoped accessor for RememberPlugin storage",scope:import_sdk2.ProviderScope.CONTEXT})],RememberAccessor);function createRememberAccessor(store,ctx,config,logger){return new RememberAccessor(store,ctx,config,logger)}var import_sdk3=require("@frontmcp/sdk");var MAX_TIMEOUT_MS=2**31-1;var RememberMemoryProvider=class{constructor(sweepIntervalSeconds=60,defaultTTL){this.defaultTTL=defaultTTL;this.sweeper=setInterval(()=>this.sweep(),sweepIntervalSeconds*1e3);this.sweeper.unref?.()}defaultTTL;memory=new Map;sweeper;async setValue(key,value,ttlSeconds){const strValue=JSON.stringify(value);const existing=this.memory.get(key);if(existing?.timeout)clearTimeout(existing.timeout);const entry={value:strValue};const ttl=ttlSeconds??this.defaultTTL;if(ttl&&ttl>0){const ttlMs=ttl*1e3;entry.expiresAt=Date.now()+ttlMs;if(ttlMs<=MAX_TIMEOUT_MS){entry.timeout=setTimeout(()=>{const e=this.memory.get(key);if(e&&e.expiresAt&&e.expiresAt<=Date.now()){this.memory.delete(key)}},ttlMs);entry.timeout.unref?.()}}this.memory.set(key,entry)}async setIfAbsent(key,value,ttlSeconds){const existing=this.memory.get(key);if(existing&&!this.isExpired(existing))return false;await this.setValue(key,value,ttlSeconds);return true}async getValue(key,defaultValue){const entry=this.memory.get(key);if(!entry)return defaultValue;if(this.isExpired(entry)){await this.delete(key);return defaultValue}try{return JSON.parse(entry.value)}catch{return entry.value}}async delete(key){const entry=this.memory.get(key);if(entry?.timeout)clearTimeout(entry.timeout);this.memory.delete(key)}async exists(key){const entry=this.memory.get(key);if(!entry)return false;if(this.isExpired(entry)){await this.delete(key);return false}return true}async keys(pattern){const result=[];const regex=pattern?this.patternToRegex(pattern):null;const expiredKeys=[];for(const[key,entry]of this.memory){if(this.isExpired(entry)){expiredKeys.push(key)}}for(const key of expiredKeys){await this.delete(key)}for(const[key,entry]of this.memory){if(this.isExpired(entry)){continue}if(regex&&!regex.test(key)){continue}result.push(key)}return result}async close(){if(this.sweeper)clearInterval(this.sweeper);for(const[,entry]of this.memory){if(entry.timeout)clearTimeout(entry.timeout)}this.memory.clear()}isExpired(entry){return entry.expiresAt!==void 0&&entry.expiresAt<=Date.now()}sweep(){const now=Date.now();for(const[key,entry]of this.memory){if(entry.expiresAt!==void 0&&entry.expiresAt<=now){if(entry.timeout)clearTimeout(entry.timeout);this.memory.delete(key)}}}patternToRegex(pattern){if(pattern.length>RememberMemoryProvider.MAX_PATTERN_LENGTH){throw new Error(`Pattern too long: ${pattern.length} chars (max ${RememberMemoryProvider.MAX_PATTERN_LENGTH})`)}const wildcardCount=(pattern.match(/[*?]/g)||[]).length;if(wildcardCount>RememberMemoryProvider.MAX_WILDCARDS){throw new Error(`Too many wildcards in pattern: ${wildcardCount} (max ${RememberMemoryProvider.MAX_WILDCARDS})`)}const escaped=pattern.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".");return new RegExp(`^${escaped}$`)}};__publicField(RememberMemoryProvider,"MAX_PATTERN_LENGTH",500);__publicField(RememberMemoryProvider,"MAX_WILDCARDS",20);RememberMemoryProvider=__decorateClass([(0,import_sdk3.Provider)({name:"provider:remember:memory",description:"In-memory storage provider for RememberPlugin",scope:import_sdk3.ProviderScope.GLOBAL})],RememberMemoryProvider);var import_ioredis=__toESM(require("ioredis"));var import_sdk4=require("@frontmcp/sdk");function prefixedStoreKey(keyPrefix,key){return keyPrefix&&key.startsWith(keyPrefix)?key:keyPrefix+key}function doubledPrefixKey(keyPrefix,key){return keyPrefix&&key.startsWith(keyPrefix)?keyPrefix+key:void 0}function callerKeyOf(keyPrefix,pattern,backendKey){return keyPrefix&&pattern.startsWith(keyPrefix)?backendKey:backendKey.slice(keyPrefix.length)}var SET_IF_NEITHER_KEY_EXISTS_SCRIPT=`
1
+ "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __defNormalProp=(obj,key,value)=>key in obj?__defProp(obj,key,{enumerable:true,configurable:true,writable:true,value}):obj[key]=value;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 __publicField=(obj,key,value)=>__defNormalProp(obj,typeof key!=="symbol"?key+"":key,value);var index_exports={};__export(index_exports,{ForgetTool:()=>ForgetTool,ListMemoriesTool:()=>ListMemoriesTool,RecallTool:()=>RecallTool,RememberAccessor:()=>RememberAccessor,RememberAccessorToken:()=>RememberAccessorToken,RememberConfigToken:()=>RememberConfigToken,RememberConfigurationError:()=>RememberConfigurationError,RememberIdentityError:()=>RememberIdentityError,RememberMemoryProvider:()=>RememberMemoryProvider,RememberPlugin:()=>RememberPlugin,RememberRedisProvider:()=>RememberRedisProvider,RememberScopeNotAllowedError:()=>RememberScopeNotAllowedError,RememberStoreToken:()=>RememberStoreToken,RememberThisTool:()=>RememberThisTool,RememberVercelKvProvider:()=>RememberVercelKvProvider,brand:()=>brand,default:()=>RememberPlugin,getRemember:()=>getRemember,tryGetRemember:()=>tryGetRemember});module.exports=__toCommonJS(index_exports);var import_sdk11=require("@frontmcp/sdk");var import_sdk2=require("@frontmcp/sdk");var import_utils2=require("@frontmcp/utils");var import_lazy_zod=require("@frontmcp/lazy-zod");var import_utils=require("@frontmcp/utils");var DEFAULT_SECRET_FILE_PATH=".frontmcp/remember-secret.json";var SECRET_BYTES=32;var rememberSecretDataSchema=import_lazy_zod.z.object({secret:import_lazy_zod.z.string().min(32),createdAt:import_lazy_zod.z.number().positive().int(),version:import_lazy_zod.z.literal(1)});function validateSecretData(data){const result=rememberSecretDataSchema.safeParse(data);if(!result.success){return{valid:false,error:result.error.issues[0]?.message??"Invalid secret structure"}}const parsed=result.data;const now=Date.now();if(parsed.createdAt>now+6e4){return{valid:false,error:"createdAt is in the future"}}const hundredYearsMs=100*365*24*60*60*1e3;if(parsed.createdAt<now-hundredYearsMs){return{valid:false,error:"createdAt is too old"}}return{valid:true}}function isSecretPersistenceEnabled(options){const isProduction=process.env["NODE_ENV"]==="production";if(isProduction){return options?.forceEnable===true}return true}function resolveSecretPath(options){const secretPath=options?.secretPath??DEFAULT_SECRET_FILE_PATH;if((0,import_utils.isAbsolute)(secretPath)){return secretPath}return(0,import_utils.pathResolve)(process.cwd(),secretPath)}async function loadRememberSecret(options){if(!isSecretPersistenceEnabled(options)){return null}const secretPath=resolveSecretPath(options);try{const content=await(0,import_utils.readFile)(secretPath,"utf8");const data=JSON.parse(content);const validation=validateSecretData(data);if(!validation.valid){console.warn(`[RememberSecretPersistence] Invalid secret file format at ${secretPath}: ${validation.error}, will regenerate`);return null}return data}catch(error){if(error.code==="ENOENT"){return null}console.warn(`[RememberSecretPersistence] Failed to load secret from ${secretPath}: ${error.message}`);return null}}async function saveRememberSecret(secretData,options){if(!isSecretPersistenceEnabled(options)){return true}const secretPath=resolveSecretPath(options);const dir=(0,import_utils.dirname)(secretPath);const tempPath=`${secretPath}.tmp.${Date.now()}.${(0,import_utils.base64urlEncode)((0,import_utils.randomBytes)(8))}`;try{await(0,import_utils.mkdir)(dir,{recursive:true,mode:448});const content=JSON.stringify(secretData,null,2);await(0,import_utils.writeFile)(tempPath,content,{mode:384});await(0,import_utils.rename)(tempPath,secretPath);return true}catch(error){console.error(`[RememberSecretPersistence] Failed to save secret to ${secretPath}: ${error.message}`);try{await(0,import_utils.unlink)(tempPath)}catch{}return false}}var cachedSecret=null;var secretGenerationPromise=null;function generateSecret(){return(0,import_utils.base64urlEncode)((0,import_utils.randomBytes)(SECRET_BYTES))}async function getOrCreatePersistedSecret(options){if(cachedSecret){return cachedSecret}if(secretGenerationPromise){return secretGenerationPromise}secretGenerationPromise=(async()=>{try{const loaded=await loadRememberSecret(options);if(loaded){cachedSecret=loaded.secret;return cachedSecret}const newSecret=generateSecret();const secretData={secret:newSecret,createdAt:Date.now(),version:1};if(isSecretPersistenceEnabled(options)){await saveRememberSecret(secretData,options)}else{console.warn("[RememberSecretPersistence] No REMEMBER_SECRET (or MCP_MEMORY_SECRET / MCP_SESSION_SECRET) is set in production, so a random in-memory secret is used. Encrypted memory will be unreadable after a restart and by other instances sharing the store. Set REMEMBER_SECRET to the same value on every instance.")}cachedSecret=newSecret;return cachedSecret}finally{secretGenerationPromise=null}})();return secretGenerationPromise}async function getBaseSecret(){const envSecret=process.env["REMEMBER_SECRET"]||process.env["MCP_MEMORY_SECRET"]||process.env["MCP_SESSION_SECRET"];if(envSecret){return envSecret}return getOrCreatePersistedSecret()}async function secretFor(source){return source.secret?source.secret:getBaseSecret()}var textEncoder=new TextEncoder;async function deriveEncryptionKey(source){const salt=textEncoder.encode("remember-plugin-v1");let ikm;let context;switch(source.type){case"session":ikm=await secretFor(source)+source.sessionId;context=`remember:session:${source.sessionId}`;break;case"tool":ikm=await secretFor(source)+source.sessionId;context=`remember:tool:${source.toolName}:${source.sessionId}`;break;case"user":ikm=await secretFor(source)+source.userId;context=`remember:user:${source.userId}`;break;case"global":ikm=await secretFor(source);context="remember:global";break;case"custom":ikm=source.key;context=`remember:custom:${source.key}`;break}const ikmBytes=textEncoder.encode(ikm);const info=textEncoder.encode(context);return(0,import_utils2.hkdfSha256)(ikmBytes,salt,info,32)}function getKeySourceForScope(scope,context,secret){switch(scope){case"session":return{type:"session",sessionId:context.sessionId,secret};case"user":return{type:"user",userId:context.userId??"anonymous",secret};case"tool":return{type:"tool",toolName:context.toolName??"unknown",sessionId:context.sessionId,secret};case"global":return{type:"global",secret}}}var textDecoder=new TextDecoder;async function encryptValue(data,keySource){const key=await deriveEncryptionKey(keySource);const plaintext=textEncoder.encode(JSON.stringify(data));const iv=(0,import_utils2.randomBytes)(12);const{ciphertext,tag}=(0,import_utils2.encryptAesGcm)(key,plaintext,iv);return{alg:"A256GCM",iv:(0,import_utils2.base64urlEncode)(iv),tag:(0,import_utils2.base64urlEncode)(tag),data:(0,import_utils2.base64urlEncode)(ciphertext)}}async function decryptValue(blob,keySource){try{const key=await deriveEncryptionKey(keySource);const iv=(0,import_utils2.base64urlDecode)(blob.iv);const tag=(0,import_utils2.base64urlDecode)(blob.tag);const ciphertext=(0,import_utils2.base64urlDecode)(blob.data);const decrypted=(0,import_utils2.decryptAesGcm)(key,ciphertext,iv,tag);return JSON.parse(textDecoder.decode(decrypted))}catch{return null}}function serializeBlob(blob){return JSON.stringify(blob)}function deserializeBlob(str){try{const parsed=JSON.parse(str);if(parsed&&typeof parsed==="object"&&parsed.alg==="A256GCM"&&typeof parsed.iv==="string"&&typeof parsed.tag==="string"&&typeof parsed.data==="string"){return parsed}return null}catch{return null}}async function encryptAndSerialize(data,keySource){const blob=await encryptValue(data,keySource);return serializeBlob(blob)}async function deserializeAndDecrypt(str,keySource){const blob=deserializeBlob(str);if(!blob)return null;return decryptValue(blob,keySource)}var import_sdk=require("@frontmcp/sdk");var RememberIdentityError=class extends import_sdk.PublicMcpError{name="RememberIdentityError";constructor(message){super(message,"REMEMBER_IDENTITY_REQUIRED",403)}};var RememberScopeNotAllowedError=class extends import_sdk.PublicMcpError{name="RememberScopeNotAllowedError";constructor(scope,allowedScopes){super(`Scope '${scope}' is not allowed. Allowed scopes: ${allowedScopes.join(", ")}`,"REMEMBER_SCOPE_NOT_ALLOWED",400)}};var RememberConfigurationError=class extends import_sdk.InternalMcpError{name="RememberConfigurationError";constructor(message){super(message,"REMEMBER_CONFIGURATION")}};var LEGACY_SCOPES=["session","tool","user"];var DEFAULT_LEGACY_PURGE_DELAY_MS=864e5;var LAYOUT_MARKER_KEY="__layout__";var LAYOUT_VERSION=2;var MAX_TIMER_MS=2147483647;var scheduled=new WeakSet;function parseFirstSeenAt(raw){if(!raw)return void 0;try{const parsed=typeof raw==="string"?JSON.parse(raw):raw;const firstSeenAt=parsed?.firstSeenAt;return typeof firstSeenAt==="number"&&Number.isFinite(firstSeenAt)?firstSeenAt:void 0}catch{return void 0}}async function readLayoutFirstSeenAt(store,keyPrefix,logger,markerTtlSeconds){const key=`${keyPrefix}${LAYOUT_MARKER_KEY}`;const standDown=(reason,extra={})=>{logger?.debug?.(`remember: ${reason}, legacy purge stood down`,{key,...extra});return void 0};try{const existing=await store.getValue(key);if(existing){return parseFirstSeenAt(existing)??standDown("layout marker is unreadable")}const marker={version:LAYOUT_VERSION,firstSeenAt:Date.now()};const serialized=JSON.stringify(marker);if(!store.setIfAbsent){await store.setValue(key,serialized,markerTtlSeconds);return marker.firstSeenAt}if(await store.setIfAbsent(key,serialized,markerTtlSeconds)){return marker.firstSeenAt}const winner=await store.getValue(key);return parseFirstSeenAt(winner)??standDown("layout marker vanished after a lost race")}catch(error){return standDown("could not establish the layout marker",{error:error instanceof Error?error.message:String(error)})}}async function purgeLegacyRememberEntries(store,keyPrefix,logger){let deleted=0;for(const scope of LEGACY_SCOPES){const pattern=`${keyPrefix}${scope}:*`;let keys;try{keys=await store.keys(pattern)}catch(error){logger?.debug?.("remember: legacy purge skipped, store could not list keys",{pattern,error:error instanceof Error?error.message:String(error)});continue}for(const key of keys){try{await store.delete(key);deleted+=1}catch{}}}if(deleted>0){logger?.warn?.(`remember: purged ${deleted} entr${deleted===1?"y":"ies"} left unreadable by the session/tool key-derivation change and the namespace encoding. This is a one-time upgrade step; set skipLegacyPurge to handle the migration yourself.`)}return deleted}function scheduleLegacyRememberPurge(store,keyPrefix,options={}){if(scheduled.has(store))return;scheduled.add(store);const delayMs=options.delayMs??DEFAULT_LEGACY_PURGE_DELAY_MS;const markerTtlSeconds=options.defaultTTL?Math.max(options.defaultTTL,Math.ceil(2*delayMs/1e3)):void 0;const arm=waitMs=>{const timer=setTimeout(()=>{void attempt().catch(()=>void 0)},Math.min(waitMs,MAX_TIMER_MS));timer.unref?.()};const attempt=async()=>{const firstSeenAt=await readLayoutFirstSeenAt(store,keyPrefix,options.logger,markerTtlSeconds);if(firstSeenAt===void 0)return;const remainingMs=firstSeenAt+delayMs-Date.now();if(remainingMs>0){arm(remainingMs);return}await purgeLegacyRememberEntries(store,keyPrefix,options.logger)};void attempt().catch(()=>void 0)}var STORAGE_LAYOUT_VERSION="v2";function encodeKeyPart(value){return encodeURIComponent(value)}function remainingSeconds(expiresAt,now){return Math.max(1,Math.ceil((expiresAt-now)/1e3))}function expiryAfter(ttlSeconds,now){return ttlSeconds?now+ttlSeconds*1e3:void 0}var RememberAccessor=class{store;ctx;config;keyPrefix;encryptionEnabled;constructor(store,ctx,config,logger){this.store=store;this.ctx=ctx;this.config=config;this.keyPrefix=config.keyPrefix??"remember:";this.encryptionEnabled=config.encryption?.enabled!==false;if(!config.skipLegacyPurge){scheduleLegacyRememberPurge(store,this.keyPrefix,{delayMs:config.legacyPurgeDelayMs,defaultTTL:config.defaultTTL,logger})}}async set(key,value,options={}){const scope=options.scope??"session";const storageKey=this.buildStorageKey(key,scope);const now=Date.now();const ttlSeconds=options.ttl??this.config.defaultTTL;const entry={value,brand:options.brand,createdAt:now,updatedAt:now,expiresAt:expiryAfter(ttlSeconds,now),metadata:options.metadata};const serialized=this.encryptionEnabled?await encryptAndSerialize(entry,this.getKeySource(scope)):JSON.stringify(entry);await this.store.setValue(storageKey,serialized,ttlSeconds);return entry}async get(key,options={}){const scope=options.scope??"session";const entry=await this.readEntry(this.buildStorageKey(key,scope),scope);return entry?entry.value:options.defaultValue}async getEntry(key,options={}){const scope=options.scope??"session";return this.readEntry(this.buildStorageKey(key,scope),scope)}async forget(key,options={}){const scope=options.scope??"session";const storageKey=this.buildStorageKey(key,scope);await this.store.delete(storageKey)}async knows(key,options={}){const scope=options.scope??"session";return await this.readEntry(this.buildStorageKey(key,scope),scope)!==void 0}async list(options={}){const scope=options.scope??"session";const scopePrefix=this.buildScopePrefix(scope);const storageKeys=await this.store.keys(scopePrefix+(options.pattern??"*"));const entries=await Promise.all(storageKeys.map(storageKey=>this.readEntry(storageKey,scope)));return storageKeys.filter((_storageKey,index)=>entries[index]!==void 0).map(storageKey=>storageKey.slice(scopePrefix.length))}async update(key,value,options={}){const scope=options.scope??"session";const existing=await this.getEntry(key,{scope});if(!existing)return false;const now=Date.now();const expiresAt=expiryAfter(options.ttl,now)??existing.expiresAt??expiryAfter(this.config.defaultTTL,now);const entry={...existing,value,updatedAt:now,expiresAt};const storageKey=this.buildStorageKey(key,scope);const serialized=this.encryptionEnabled?await encryptAndSerialize(entry,this.getKeySource(scope)):JSON.stringify(entry);const writtenAt=Date.now();if(expiresAt!==void 0&&expiresAt<=writtenAt)return false;const storageTtl=expiresAt!==void 0?remainingSeconds(expiresAt,writtenAt):void 0;await this.store.setValue(storageKey,serialized,storageTtl);return true}get sessionId(){return this.ctx.sessionId}get userId(){const authInfo=this.ctx.authInfo;return authInfo?.extra?.["userId"]??authInfo?.extra?.["sub"]??authInfo?.clientId??void 0}buildStorageKey(key,scope){return this.buildScopePrefix(scope)+key}get verifiedSessionId(){const authInfo=this.ctx.authInfo;return[authInfo?.sessionId,authInfo?.extra?.["sessionId"]].find(candidate=>typeof candidate==="string"&&candidate.length>0&&candidate!==import_sdk2.STATELESS_SESSION_ID)}resolveSessionIdentity(){const sessionId=this.verifiedSessionId;if(sessionId){return sessionId}const userId=this.userId;if(userId&&!(0,import_sdk2.isAnonymousSubject)(userId)){return`stateless-user:${userId}`}throw new RememberIdentityError(`Remember cannot use session or tool scope for an unauthenticated request without a verified session: a stateless request shares the session id "${import_sdk2.STATELESS_SESSION_ID}", and an mcp-session-id the server did not verify could belong to any caller, so the data would be shared across clients. Authenticate the request, use a stateful transport, or choose the 'global' scope if the data really is shared.`)}get toolName(){return(0,import_sdk2.getRunningTool)()?.fullName??"unknown"}buildScopePrefix(scope){switch(scope){case"session":return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:session:${encodeKeyPart(this.resolveSessionIdentity())}:`;case"user":{const userId=this.userId;if(!userId||(0,import_sdk2.isAnonymousSubject)(userId)){throw new RememberIdentityError("Remember cannot use user scope without an authenticated user: all unauthenticated callers would share one namespace. Authenticate the request or use the 'global' scope.")}return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:user:${encodeKeyPart(userId)}:`}case"tool":return`${this.keyPrefix}${STORAGE_LAYOUT_VERSION}:tool:${encodeKeyPart(this.toolName)}:${encodeKeyPart(this.resolveSessionIdentity())}:`;case"global":return`${this.keyPrefix}global:`}}getKeySource(scope){const needsSessionIdentity=scope==="session"||scope==="tool";return getKeySourceForScope(scope,{sessionId:needsSessionIdentity?this.resolveSessionIdentity():this.ctx.sessionId,userId:this.userId,toolName:scope==="tool"?this.toolName:void 0},this.config.encryption?.customKey)}async readEntry(storageKey,scope){const stored=await this.store.getValue(storageKey);if(!stored)return void 0;const raw=typeof stored==="string"?stored:JSON.stringify(stored);const entry=this.encryptionEnabled?await deserializeAndDecrypt(raw,this.getKeySource(scope)):this.parseEntry(raw);if(!entry)return void 0;if(entry.expiresAt&&Date.now()>entry.expiresAt){await this.store.delete(storageKey);return void 0}return entry}parseEntry(raw){try{return JSON.parse(raw)}catch{return null}}};RememberAccessor=__decorateClass([(0,import_sdk2.Provider)({name:"provider:remember:accessor",description:"Context-scoped accessor for RememberPlugin storage",scope:import_sdk2.ProviderScope.CONTEXT})],RememberAccessor);function createRememberAccessor(store,ctx,config,logger){return new RememberAccessor(store,ctx,config,logger)}var import_sdk3=require("@frontmcp/sdk");var MAX_TIMEOUT_MS=2**31-1;var RememberMemoryProvider=class{constructor(sweepIntervalSeconds=60,defaultTTL){this.defaultTTL=defaultTTL;this.sweeper=setInterval(()=>this.sweep(),sweepIntervalSeconds*1e3);this.sweeper.unref?.()}defaultTTL;memory=new Map;sweeper;async setValue(key,value,ttlSeconds){const strValue=JSON.stringify(value);const existing=this.memory.get(key);if(existing?.timeout)clearTimeout(existing.timeout);const entry={value:strValue};const ttl=ttlSeconds??this.defaultTTL;if(ttl&&ttl>0){const ttlMs=ttl*1e3;entry.expiresAt=Date.now()+ttlMs;if(ttlMs<=MAX_TIMEOUT_MS){entry.timeout=setTimeout(()=>{const e=this.memory.get(key);if(e&&e.expiresAt&&e.expiresAt<=Date.now()){this.memory.delete(key)}},ttlMs);entry.timeout.unref?.()}}this.memory.set(key,entry)}async setIfAbsent(key,value,ttlSeconds){const existing=this.memory.get(key);if(existing&&!this.isExpired(existing))return false;await this.setValue(key,value,ttlSeconds);return true}async getValue(key,defaultValue){const entry=this.memory.get(key);if(!entry)return defaultValue;if(this.isExpired(entry)){await this.delete(key);return defaultValue}try{return JSON.parse(entry.value)}catch{return entry.value}}async delete(key){const entry=this.memory.get(key);if(entry?.timeout)clearTimeout(entry.timeout);this.memory.delete(key)}async exists(key){const entry=this.memory.get(key);if(!entry)return false;if(this.isExpired(entry)){await this.delete(key);return false}return true}async keys(pattern){const result=[];const regex=pattern?this.patternToRegex(pattern):null;const expiredKeys=[];for(const[key,entry]of this.memory){if(this.isExpired(entry)){expiredKeys.push(key)}}for(const key of expiredKeys){await this.delete(key)}for(const[key,entry]of this.memory){if(this.isExpired(entry)){continue}if(regex&&!regex.test(key)){continue}result.push(key)}return result}async close(){if(this.sweeper)clearInterval(this.sweeper);for(const[,entry]of this.memory){if(entry.timeout)clearTimeout(entry.timeout)}this.memory.clear()}isExpired(entry){return entry.expiresAt!==void 0&&entry.expiresAt<=Date.now()}sweep(){const now=Date.now();for(const[key,entry]of this.memory){if(entry.expiresAt!==void 0&&entry.expiresAt<=now){if(entry.timeout)clearTimeout(entry.timeout);this.memory.delete(key)}}}patternToRegex(pattern){if(pattern.length>RememberMemoryProvider.MAX_PATTERN_LENGTH){throw new Error(`Pattern too long: ${pattern.length} chars (max ${RememberMemoryProvider.MAX_PATTERN_LENGTH})`)}const wildcardCount=(pattern.match(/[*?]/g)||[]).length;if(wildcardCount>RememberMemoryProvider.MAX_WILDCARDS){throw new Error(`Too many wildcards in pattern: ${wildcardCount} (max ${RememberMemoryProvider.MAX_WILDCARDS})`)}const escaped=pattern.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".");return new RegExp(`^${escaped}$`)}};__publicField(RememberMemoryProvider,"MAX_PATTERN_LENGTH",500);__publicField(RememberMemoryProvider,"MAX_WILDCARDS",20);RememberMemoryProvider=__decorateClass([(0,import_sdk3.Provider)({name:"provider:remember:memory",description:"In-memory storage provider for RememberPlugin",scope:import_sdk3.ProviderScope.GLOBAL})],RememberMemoryProvider);var import_sdk4=require("@frontmcp/sdk");function prefixedStoreKey(keyPrefix,key){return keyPrefix&&key.startsWith(keyPrefix)?key:keyPrefix+key}function doubledPrefixKey(keyPrefix,key){return keyPrefix&&key.startsWith(keyPrefix)?keyPrefix+key:void 0}function callerKeyOf(keyPrefix,pattern,backendKey){return keyPrefix&&pattern.startsWith(keyPrefix)?backendKey:backendKey.slice(keyPrefix.length)}var SET_IF_NEITHER_KEY_EXISTS_SCRIPT=`
2
2
  if redis.call('EXISTS', KEYS[1], KEYS[2]) > 0 then return 0 end
3
3
  if ARGV[2] ~= '' then redis.call('SET', KEYS[1], ARGV[1], 'EX', ARGV[2]) else redis.call('SET', KEYS[1], ARGV[1]) end
4
4
  return 1
5
- `;var RememberRedisProvider=class{client;keyPrefix;defaultTTL;ownsClient;constructor(options){this.keyPrefix=options.keyPrefix??"";this.defaultTTL=options.defaultTTL;if(options.type==="redis-client"){this.client=options.client;this.ownsClient=false;return}this.ownsClient=true;this.client=new import_ioredis.default({lazyConnect:false,maxRetriesPerRequest:3,...options.config});this.client.on("connect",()=>{if(process.env["DEBUG"]==="true"){console.log("[RememberPlugin:Redis] Connected")}});this.client.on("error",err=>{console.error("[RememberPlugin:Redis] Error:",err.message)})}async setValue(key,value,ttlSeconds){if(value===void 0){throw new Error("Cannot store undefined value. Use null or delete the key instead.")}if(ttlSeconds!==void 0){if(typeof ttlSeconds!=="number"||!Number.isFinite(ttlSeconds)){throw new Error(`Invalid TTL: expected a number, got ${typeof ttlSeconds}`)}if(ttlSeconds<=0){throw new Error(`Invalid TTL: must be positive, got ${ttlSeconds}`)}if(!Number.isInteger(ttlSeconds)){throw new Error(`Invalid TTL: must be an integer, got ${ttlSeconds}`)}}const fullKey=prefixedStoreKey(this.keyPrefix,key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;if(ttl!==void 0&&ttl>0){await this.client.set(fullKey,strValue,"EX",ttl)}else{await this.client.set(fullKey,strValue)}await this.deleteDoubledPrefixKey(key)}async setIfAbsent(key,value,ttlSeconds){const fullKey=prefixedStoreKey(this.keyPrefix,key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const hasTtl=ttl!==void 0&&ttl>0;const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(doubledKey){const ttlArgument=hasTtl?String(ttl):"";const created=await this.client.eval(SET_IF_NEITHER_KEY_EXISTS_SCRIPT,2,fullKey,doubledKey,strValue,ttlArgument);return created===1}const result=hasTtl?await this.client.set(fullKey,strValue,"EX",ttl,"NX"):await this.client.set(fullKey,strValue,"NX");return result==="OK"}async getValue(key,defaultValue){const raw=await this.client.get(prefixedStoreKey(this.keyPrefix,key))??await this.readDoubledPrefixValue(key);if(raw===null)return defaultValue;try{return JSON.parse(raw)}catch{return defaultValue}}async delete(key){await this.client.del(prefixedStoreKey(this.keyPrefix,key));await this.deleteDoubledPrefixKey(key)}async exists(key){if(await this.client.exists(prefixedStoreKey(this.keyPrefix,key))===1)return true;const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey!==void 0&&await this.client.exists(doubledKey)===1}async keys(pattern="*"){const result=new Set;for(const key of await this.scan(prefixedStoreKey(this.keyPrefix,pattern))){result.add(callerKeyOf(this.keyPrefix,pattern,key))}const doubledPattern=doubledPrefixKey(this.keyPrefix,pattern);if(doubledPattern){for(const key of await this.scan(doubledPattern))result.add(key.slice(this.keyPrefix.length))}return[...result]}async scan(match){const found=[];let cursor="0";do{const[nextCursor,keys]=await this.client.scan(cursor,"MATCH",match,"COUNT",100);cursor=nextCursor;found.push(...keys)}while(cursor!=="0");return found}async readDoubledPrefixValue(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey?this.client.get(doubledKey):null}async deleteDoubledPrefixKey(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(doubledKey)await this.client.del(doubledKey)}async close(){if(this.ownsClient){await this.client.quit()}}};RememberRedisProvider=__decorateClass([(0,import_sdk4.Provider)({name:"provider:remember:redis",description:"Redis-based storage provider for RememberPlugin",scope:import_sdk4.ProviderScope.GLOBAL})],RememberRedisProvider);var import_sdk5=require("@frontmcp/sdk");var import_utils3=require("@frontmcp/utils");var RememberVercelKvProvider=class{connection;client;keyPrefix;defaultTTL;constructor(options={}){const hasUrl=options.url!==void 0;const hasToken=options.token!==void 0;if(hasUrl!==hasToken){throw new Error(`RememberVercelKvProvider: Both 'url' and 'token' must be provided together, or neither. Received: url=${hasUrl?"provided":"missing"}, token=${hasToken?"provided":"missing"}`)}this.connection={url:options.url,token:options.token};this.keyPrefix=options.keyPrefix??"remember:";this.defaultTTL=options.defaultTTL}kv(){this.client??=(0,import_utils3.createVercelKvClient)(this.connection).catch(error=>{this.client=void 0;throw error});return this.client}prefixKey(key){return prefixedStoreKey(this.keyPrefix,key)}async setValue(key,value,ttlSeconds){const fullKey=this.prefixKey(key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const kv=await this.kv();if(ttl&&ttl>0){await kv.set(fullKey,strValue,{ex:ttl})}else{await kv.set(fullKey,strValue)}await this.deleteDoubledPrefixKey(key)}async setIfAbsent(key,value,ttlSeconds){const fullKey=this.prefixKey(key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const doubledKey=doubledPrefixKey(this.keyPrefix,key);const kv=await this.kv();if(doubledKey){const ttlArgument=ttl&&ttl>0?String(ttl):"";const created=await kv.eval(SET_IF_NEITHER_KEY_EXISTS_SCRIPT,[fullKey,doubledKey],[strValue,ttlArgument]);return Number(created)===1}const result=ttl&&ttl>0?await kv.set(fullKey,strValue,{nx:true,ex:ttl}):await kv.set(fullKey,strValue,{nx:true});return result==="OK"}async getValue(key,defaultValue){const kv=await this.kv();const raw=await kv.get(this.prefixKey(key))??await this.readDoubledPrefixValue(key);if(raw===null)return defaultValue;try{return JSON.parse(raw)}catch{return raw}}async delete(key){const kv=await this.kv();await kv.del(this.prefixKey(key));await this.deleteDoubledPrefixKey(key)}async exists(key){const kv=await this.kv();if(await kv.exists(this.prefixKey(key))===1)return true;const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey!==void 0&&await kv.exists(doubledKey)===1}async keys(pattern="*"){const result=new Set;for(const key of await this.scan(this.prefixKey(pattern))){result.add(callerKeyOf(this.keyPrefix,pattern,key))}const doubledPattern=doubledPrefixKey(this.keyPrefix,pattern);if(doubledPattern){for(const key of await this.scan(doubledPattern))result.add(key.slice(this.keyPrefix.length))}return[...result]}async scan(match){const found=[];const kv=await this.kv();try{let cursor=0;do{const[nextCursor,keys]=await kv.scan(cursor,{match,count:100});cursor=nextCursor;found.push(...keys)}while(String(cursor)!=="0")}catch{try{found.push(...await kv.keys(match))}catch{console.warn("[RememberPlugin:VercelKV] keys() operation not supported")}}return found}async readDoubledPrefixValue(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(!doubledKey)return null;const kv=await this.kv();return kv.get(doubledKey)}async deleteDoubledPrefixKey(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(!doubledKey)return;const kv=await this.kv();await kv.del(doubledKey)}async close(){}};RememberVercelKvProvider=__decorateClass([(0,import_sdk5.Provider)({name:"provider:remember:vercel-kv",description:"Vercel KV-based storage provider for RememberPlugin",scope:import_sdk5.ProviderScope.GLOBAL})],RememberVercelKvProvider);var RememberStoreToken=Symbol("plugin:remember:store");var RememberConfigToken=Symbol("plugin:remember:config");var RememberAccessorToken=Symbol("plugin:remember:accessor");var import_sdk10=require("@frontmcp/sdk");var import_lazy_zod2=require("@frontmcp/lazy-zod");var import_sdk6=require("@frontmcp/sdk");var REMEMBER_SCOPE_DESCRIPTION="Whose memory to use (default: session). session: this session; without one (stateless HTTP, MCP 2026-07-28), the signed-in caller across its requests. user: the signed-in caller, across all of its sessions. tool: the tool running the call, for the same caller as session; each memory tool has its own, so the other memory tools do not see what one of them stores in tool scope. global: shared by every caller. An anonymous caller cannot use user scope, nor session or tool scope without a session.";function rememberToolNames(prefix=""){return{rememberThis:`${prefix}remember_this`,recall:`${prefix}recall`,forget:`${prefix}forget`,listMemories:`${prefix}list_memories`}}var forgetInputSchema={key:import_lazy_zod2.z.string().min(1).describe("What memory to forget"),scope:import_lazy_zod2.z.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION)};var forgetOutputSchema=import_lazy_zod2.z.object({success:import_lazy_zod2.z.boolean(),key:import_lazy_zod2.z.string(),scope:import_lazy_zod2.z.string(),existed:import_lazy_zod2.z.boolean()});function forgetDescription(){return"Forget a previously remembered value. Use this when the user wants to delete stored preferences or information."}function forgetToolMetadata(names=rememberToolNames()){return{name:names.forget,description:forgetDescription(),inputSchema:forgetInputSchema,outputSchema:forgetOutputSchema,annotations:{readOnlyHint:false}}}var ForgetTool=class extends import_sdk6.ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const existed=await remember.knows(input.key,{scope});await remember.forget(input.key,{scope});return{success:true,key:input.key,scope,existed}}};ForgetTool=__decorateClass([(0,import_sdk6.Tool)(forgetToolMetadata())],ForgetTool);var import_lazy_zod3=require("@frontmcp/lazy-zod");var import_sdk7=require("@frontmcp/sdk");var listMemoriesInputSchema={scope:import_lazy_zod3.z.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION),pattern:import_lazy_zod3.z.string().optional().describe('Pattern to filter keys (e.g., "user_*")'),limit:import_lazy_zod3.z.number().positive().max(100).optional().describe("Maximum number of keys to return (default: 50)")};var listMemoriesOutputSchema=import_lazy_zod3.z.object({keys:import_lazy_zod3.z.array(import_lazy_zod3.z.string()),scope:import_lazy_zod3.z.string(),count:import_lazy_zod3.z.number(),truncated:import_lazy_zod3.z.boolean()});function listMemoriesDescription(names=rememberToolNames()){return`List the remembered keys in one scope (default: session). Use this to see what memories are stored before you ${names.recall} or ${names.forget} one.`}function listMemoriesToolMetadata(names=rememberToolNames()){return{name:names.listMemories,description:listMemoriesDescription(names),inputSchema:listMemoriesInputSchema,outputSchema:listMemoriesOutputSchema,annotations:{readOnlyHint:true}}}var ListMemoriesTool=class extends import_sdk7.ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const limit=input.limit??50;let keys=await remember.list({scope,pattern:input.pattern});const truncated=keys.length>limit;if(truncated){keys=keys.slice(0,limit)}return{keys,scope,count:keys.length,truncated}}};ListMemoriesTool=__decorateClass([(0,import_sdk7.Tool)(listMemoriesToolMetadata())],ListMemoriesTool);var import_lazy_zod4=require("@frontmcp/lazy-zod");var import_sdk8=require("@frontmcp/sdk");var recallInputSchema={key:import_lazy_zod4.z.string().min(1).describe("What memory to recall"),scope:import_lazy_zod4.z.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION)};var recallOutputSchema=import_lazy_zod4.z.object({found:import_lazy_zod4.z.boolean(),key:import_lazy_zod4.z.string(),value:import_lazy_zod4.z.unknown().optional(),scope:import_lazy_zod4.z.string(),createdAt:import_lazy_zod4.z.number().optional(),expiresAt:import_lazy_zod4.z.number().optional()});function recallDescription(names=rememberToolNames()){return`Recall something that was previously remembered. Use this to retrieve stored preferences, settings, or any information that was saved with ${names.rememberThis}.`}function recallToolMetadata(names=rememberToolNames()){return{name:names.recall,description:recallDescription(names),inputSchema:recallInputSchema,outputSchema:recallOutputSchema,annotations:{readOnlyHint:true}}}var RecallTool=class extends import_sdk8.ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const entry=await remember.getEntry(input.key,{scope});if(!entry){return{found:false,key:input.key,scope}}return{found:true,key:input.key,value:entry.value,scope,createdAt:entry.createdAt,expiresAt:entry.expiresAt}}};RecallTool=__decorateClass([(0,import_sdk8.Tool)(recallToolMetadata())],RecallTool);var import_lazy_zod5=require("@frontmcp/lazy-zod");var import_sdk9=require("@frontmcp/sdk");var rememberThisInputSchema={key:import_lazy_zod5.z.string().min(1).describe('What to call this memory (e.g., "user_preference", "last_action")'),value:import_lazy_zod5.z.unknown().describe("The value to remember (any JSON-serializable data)"),scope:import_lazy_zod5.z.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION),ttl:import_lazy_zod5.z.number().int().positive().optional().describe("Forget after this many whole seconds"),brand:import_lazy_zod5.z.enum(["preference","cache","state","conversation","custom"]).optional().describe("Type hint for the stored value")};var rememberThisOutputSchema=import_lazy_zod5.z.object({success:import_lazy_zod5.z.boolean(),key:import_lazy_zod5.z.string(),scope:import_lazy_zod5.z.string(),expiresAt:import_lazy_zod5.z.number().optional()});function rememberThisDescription(names=rememberToolNames()){return`Remember something for later: store a value under a key that ${names.recall} can read back, in the scope you choose (this session by default; user scope for the signed-in user across sessions). It lasts until it is forgotten or its ttl runs out. Use this when the user asks you to remember preferences, settings, or any information they want to persist.`}function rememberThisToolMetadata(names=rememberToolNames()){return{name:names.rememberThis,description:rememberThisDescription(names),inputSchema:rememberThisInputSchema,outputSchema:rememberThisOutputSchema,annotations:{readOnlyHint:false}}}var RememberThisTool=class extends import_sdk9.ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const entry=await remember.set(input.key,input.value,{scope,ttl:input.ttl,brand:input.brand});return{success:true,key:input.key,scope,expiresAt:entry.expiresAt}}};RememberThisTool=__decorateClass([(0,import_sdk9.Tool)(rememberThisToolMetadata())],RememberThisTool);function createRememberTools(prefix=""){if(!prefix)return[RememberThisTool,RecallTool,ForgetTool,ListMemoriesTool];const names=rememberToolNames(prefix);let PrefixedRememberThisTool=class extends RememberThisTool{};PrefixedRememberThisTool=__decorateClass([(0,import_sdk10.Tool)(rememberThisToolMetadata(names))],PrefixedRememberThisTool);let PrefixedRecallTool=class extends RecallTool{};PrefixedRecallTool=__decorateClass([(0,import_sdk10.Tool)(recallToolMetadata(names))],PrefixedRecallTool);let PrefixedForgetTool=class extends ForgetTool{};PrefixedForgetTool=__decorateClass([(0,import_sdk10.Tool)(forgetToolMetadata(names))],PrefixedForgetTool);let PrefixedListMemoriesTool=class extends ListMemoriesTool{};PrefixedListMemoriesTool=__decorateClass([(0,import_sdk10.Tool)(listMemoriesToolMetadata(names))],PrefixedListMemoriesTool);return[PrefixedRememberThisTool,PrefixedRecallTool,PrefixedForgetTool,PrefixedListMemoriesTool]}function withCheckedOptions(options){const customKey=options.encryption?.customKey;if(customKey!==void 0&&customKey.trim()===""){throw new RememberConfigurationError("RememberPlugin encryption.customKey must not be empty; omit it to derive keys from the server secret")}const{defaultTTL}=options;if(defaultTTL===void 0)return options;if(defaultTTL===0)return{...options,defaultTTL:void 0};if(!Number.isInteger(defaultTTL)||defaultTTL<0){throw new RememberConfigurationError(`RememberPlugin defaultTTL must be a whole number of seconds (0 for no default expiry), got ${defaultTTL}`)}return options}var RememberPlugin=class extends import_sdk11.DynamicPlugin{options;constructor(options={}){super();this.options={...RememberPlugin.defaultOptions,...withCheckedOptions(options)}}};__publicField(RememberPlugin,"defaultOptions",{type:"memory",keyPrefix:"remember:",encryption:{enabled:true}});__publicField(RememberPlugin,"dynamicTools",options=>options.tools?.enabled?createRememberTools(options.tools.prefix):[]);__publicField(RememberPlugin,"dynamicProviders",options=>{const providers=[];const config={...RememberPlugin.defaultOptions,...withCheckedOptions(options)};switch(config.type){case"global-store":providers.push({name:"remember:store:global",provide:RememberStoreToken,inject:()=>[import_sdk11.FrontMcpConfig],useFactory:frontmcpConfig=>{const storeConfig=(0,import_sdk11.getGlobalStoreConfig)("RememberPlugin",frontmcpConfig);if((0,import_sdk11.isVercelKvProvider)(storeConfig)){return new RememberVercelKvProvider({url:storeConfig.url,token:storeConfig.token,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}return new RememberRedisProvider({type:"redis",config:{host:storeConfig.host??"localhost",port:storeConfig.port??6379,password:storeConfig.password,db:storeConfig.db},keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}});break;case"redis":if("config"in config&&config.config){providers.push({name:"remember:store:redis",provide:RememberStoreToken,useValue:new RememberRedisProvider({type:"redis",config:config.config,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})})}break;case"redis-client":if("client"in config&&config.client){providers.push({name:"remember:store:redis-client",provide:RememberStoreToken,useValue:new RememberRedisProvider({type:"redis-client",client:config.client,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})})}break;case"vercel-kv":providers.push({name:"remember:store:vercel-kv",provide:RememberStoreToken,useValue:new RememberVercelKvProvider({url:"url"in config?config.url:void 0,token:"token"in config?config.token:void 0,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})});break;case"memory":default:providers.push({name:"remember:store:memory",provide:RememberStoreToken,inject:()=>[],useFactory:()=>new RememberMemoryProvider(void 0,config.defaultTTL)});break}providers.push({name:"remember:config",provide:RememberConfigToken,useValue:config});providers.push({name:"remember:accessor",provide:RememberAccessorToken,scope:import_sdk11.ProviderScope.CONTEXT,inject:()=>[RememberStoreToken,import_sdk11.FRONTMCP_CONTEXT,RememberConfigToken,import_sdk11.FrontMcpLogger],useFactory:(store,ctx,cfg,logger)=>createRememberAccessor(store,ctx,cfg,logger)});return providers});RememberPlugin=__decorateClass([(0,import_sdk11.Plugin)({name:"remember",description:"Help your LLM remember things across sessions",providers:[{name:"remember:store:memory",provide:RememberStoreToken,inject:()=>[],useFactory:()=>new RememberMemoryProvider}],contextExtensions:[{property:"remember",token:RememberAccessorToken,errorMessage:"RememberPlugin is not installed. Add RememberPlugin.init() to your plugins array."}]})],RememberPlugin);var import_lazy_zod6=require("@frontmcp/lazy-zod");var rememberScopeSchema=import_lazy_zod6.z.enum(["session","user","tool","global"]);function brand(data,_brand){return data}var rememberEntrySchema=import_lazy_zod6.z.object({value:import_lazy_zod6.z.unknown(),brand:import_lazy_zod6.z.enum(["approval","preference","cache","state","conversation","custom"]).optional(),createdAt:import_lazy_zod6.z.number(),updatedAt:import_lazy_zod6.z.number(),expiresAt:import_lazy_zod6.z.number().optional(),metadata:import_lazy_zod6.z.record(import_lazy_zod6.z.string(),import_lazy_zod6.z.unknown()).optional()});function getRemember(ctx){return ctx.get(RememberAccessorToken)}function tryGetRemember(ctx){if(typeof ctx.tryGet==="function"){return ctx.tryGet(RememberAccessorToken)}return void 0}0&&(module.exports={ForgetTool,ListMemoriesTool,RecallTool,RememberAccessor,RememberAccessorToken,RememberConfigToken,RememberConfigurationError,RememberIdentityError,RememberMemoryProvider,RememberPlugin,RememberRedisProvider,RememberScopeNotAllowedError,RememberStoreToken,RememberThisTool,RememberVercelKvProvider,brand,getRemember,tryGetRemember});
5
+ `;function createRedisClient(options){const ioredis=require("ioredis");const Redis=ioredis.default??ioredis;return new Redis(options)}var RememberRedisProvider=class{client;keyPrefix;defaultTTL;ownsClient;constructor(options){this.keyPrefix=options.keyPrefix??"";this.defaultTTL=options.defaultTTL;if(options.type==="redis-client"){this.client=options.client;this.ownsClient=false;return}this.ownsClient=true;this.client=createRedisClient({lazyConnect:false,maxRetriesPerRequest:3,...options.config});this.client.on("connect",()=>{if(process.env["DEBUG"]==="true"){console.log("[RememberPlugin:Redis] Connected")}});this.client.on("error",err=>{console.error("[RememberPlugin:Redis] Error:",err.message)})}async setValue(key,value,ttlSeconds){if(value===void 0){throw new Error("Cannot store undefined value. Use null or delete the key instead.")}if(ttlSeconds!==void 0){if(typeof ttlSeconds!=="number"||!Number.isFinite(ttlSeconds)){throw new Error(`Invalid TTL: expected a number, got ${typeof ttlSeconds}`)}if(ttlSeconds<=0){throw new Error(`Invalid TTL: must be positive, got ${ttlSeconds}`)}if(!Number.isInteger(ttlSeconds)){throw new Error(`Invalid TTL: must be an integer, got ${ttlSeconds}`)}}const fullKey=prefixedStoreKey(this.keyPrefix,key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;if(ttl!==void 0&&ttl>0){await this.client.set(fullKey,strValue,"EX",ttl)}else{await this.client.set(fullKey,strValue)}await this.deleteDoubledPrefixKey(key)}async setIfAbsent(key,value,ttlSeconds){const fullKey=prefixedStoreKey(this.keyPrefix,key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const hasTtl=ttl!==void 0&&ttl>0;const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(doubledKey){const ttlArgument=hasTtl?String(ttl):"";const created=await this.client.eval(SET_IF_NEITHER_KEY_EXISTS_SCRIPT,2,fullKey,doubledKey,strValue,ttlArgument);return created===1}const result=hasTtl?await this.client.set(fullKey,strValue,"EX",ttl,"NX"):await this.client.set(fullKey,strValue,"NX");return result==="OK"}async getValue(key,defaultValue){const raw=await this.client.get(prefixedStoreKey(this.keyPrefix,key))??await this.readDoubledPrefixValue(key);if(raw===null)return defaultValue;try{return JSON.parse(raw)}catch{return defaultValue}}async delete(key){await this.client.del(prefixedStoreKey(this.keyPrefix,key));await this.deleteDoubledPrefixKey(key)}async exists(key){if(await this.client.exists(prefixedStoreKey(this.keyPrefix,key))===1)return true;const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey!==void 0&&await this.client.exists(doubledKey)===1}async keys(pattern="*"){const result=new Set;for(const key of await this.scan(prefixedStoreKey(this.keyPrefix,pattern))){result.add(callerKeyOf(this.keyPrefix,pattern,key))}const doubledPattern=doubledPrefixKey(this.keyPrefix,pattern);if(doubledPattern){for(const key of await this.scan(doubledPattern))result.add(key.slice(this.keyPrefix.length))}return[...result]}async scan(match){const found=[];let cursor="0";do{const[nextCursor,keys]=await this.client.scan(cursor,"MATCH",match,"COUNT",100);cursor=nextCursor;found.push(...keys)}while(cursor!=="0");return found}async readDoubledPrefixValue(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey?this.client.get(doubledKey):null}async deleteDoubledPrefixKey(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(doubledKey)await this.client.del(doubledKey)}async close(){if(this.ownsClient){await this.client.quit()}}};RememberRedisProvider=__decorateClass([(0,import_sdk4.Provider)({name:"provider:remember:redis",description:"Redis-based storage provider for RememberPlugin",scope:import_sdk4.ProviderScope.GLOBAL})],RememberRedisProvider);var import_sdk5=require("@frontmcp/sdk");var import_utils3=require("@frontmcp/utils");var RememberVercelKvProvider=class{connection;client;keyPrefix;defaultTTL;constructor(options={}){const hasUrl=options.url!==void 0;const hasToken=options.token!==void 0;if(hasUrl!==hasToken){throw new Error(`RememberVercelKvProvider: Both 'url' and 'token' must be provided together, or neither. Received: url=${hasUrl?"provided":"missing"}, token=${hasToken?"provided":"missing"}`)}this.connection={url:options.url,token:options.token};this.keyPrefix=options.keyPrefix??"remember:";this.defaultTTL=options.defaultTTL}kv(){this.client??=(0,import_utils3.createVercelKvClient)(this.connection).catch(error=>{this.client=void 0;throw error});return this.client}prefixKey(key){return prefixedStoreKey(this.keyPrefix,key)}async setValue(key,value,ttlSeconds){const fullKey=this.prefixKey(key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const kv=await this.kv();if(ttl&&ttl>0){await kv.set(fullKey,strValue,{ex:ttl})}else{await kv.set(fullKey,strValue)}await this.deleteDoubledPrefixKey(key)}async setIfAbsent(key,value,ttlSeconds){const fullKey=this.prefixKey(key);const strValue=JSON.stringify(value);const ttl=ttlSeconds??this.defaultTTL;const doubledKey=doubledPrefixKey(this.keyPrefix,key);const kv=await this.kv();if(doubledKey){const ttlArgument=ttl&&ttl>0?String(ttl):"";const created=await kv.eval(SET_IF_NEITHER_KEY_EXISTS_SCRIPT,[fullKey,doubledKey],[strValue,ttlArgument]);return Number(created)===1}const result=ttl&&ttl>0?await kv.set(fullKey,strValue,{nx:true,ex:ttl}):await kv.set(fullKey,strValue,{nx:true});return result==="OK"}async getValue(key,defaultValue){const kv=await this.kv();const raw=await kv.get(this.prefixKey(key))??await this.readDoubledPrefixValue(key);if(raw===null)return defaultValue;try{return JSON.parse(raw)}catch{return raw}}async delete(key){const kv=await this.kv();await kv.del(this.prefixKey(key));await this.deleteDoubledPrefixKey(key)}async exists(key){const kv=await this.kv();if(await kv.exists(this.prefixKey(key))===1)return true;const doubledKey=doubledPrefixKey(this.keyPrefix,key);return doubledKey!==void 0&&await kv.exists(doubledKey)===1}async keys(pattern="*"){const result=new Set;for(const key of await this.scan(this.prefixKey(pattern))){result.add(callerKeyOf(this.keyPrefix,pattern,key))}const doubledPattern=doubledPrefixKey(this.keyPrefix,pattern);if(doubledPattern){for(const key of await this.scan(doubledPattern))result.add(key.slice(this.keyPrefix.length))}return[...result]}async scan(match){const found=[];const kv=await this.kv();try{let cursor=0;do{const[nextCursor,keys]=await kv.scan(cursor,{match,count:100});cursor=nextCursor;found.push(...keys)}while(String(cursor)!=="0")}catch{try{found.push(...await kv.keys(match))}catch{console.warn("[RememberPlugin:VercelKV] keys() operation not supported")}}return found}async readDoubledPrefixValue(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(!doubledKey)return null;const kv=await this.kv();return kv.get(doubledKey)}async deleteDoubledPrefixKey(key){const doubledKey=doubledPrefixKey(this.keyPrefix,key);if(!doubledKey)return;const kv=await this.kv();await kv.del(doubledKey)}async close(){}};RememberVercelKvProvider=__decorateClass([(0,import_sdk5.Provider)({name:"provider:remember:vercel-kv",description:"Vercel KV-based storage provider for RememberPlugin",scope:import_sdk5.ProviderScope.GLOBAL})],RememberVercelKvProvider);var RememberStoreToken=Symbol("plugin:remember:store");var RememberConfigToken=Symbol("plugin:remember:config");var RememberAccessorToken=Symbol("plugin:remember:accessor");var import_sdk10=require("@frontmcp/sdk");var import_lazy_zod2=require("@frontmcp/lazy-zod");var import_sdk6=require("@frontmcp/sdk");var REMEMBER_SCOPE_DESCRIPTION="Whose memory to use (default: session). session: this session; without one (stateless HTTP, MCP 2026-07-28), the signed-in caller across its requests. user: the signed-in caller, across all of its sessions. tool: the tool running the call, for the same caller as session; each memory tool has its own, so the other memory tools do not see what one of them stores in tool scope. global: shared by every caller. An anonymous caller cannot use user scope, nor session or tool scope without a session.";function rememberToolNames(prefix=""){return{rememberThis:`${prefix}remember_this`,recall:`${prefix}recall`,forget:`${prefix}forget`,listMemories:`${prefix}list_memories`}}var forgetInputSchema={key:import_lazy_zod2.z.string().min(1).describe("What memory to forget"),scope:import_lazy_zod2.z.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION)};var forgetOutputSchema=import_lazy_zod2.z.object({success:import_lazy_zod2.z.boolean(),key:import_lazy_zod2.z.string(),scope:import_lazy_zod2.z.string(),existed:import_lazy_zod2.z.boolean()});function forgetDescription(){return"Forget a previously remembered value. Use this when the user wants to delete stored preferences or information."}function forgetToolMetadata(names=rememberToolNames()){return{name:names.forget,description:forgetDescription(),inputSchema:forgetInputSchema,outputSchema:forgetOutputSchema,annotations:{readOnlyHint:false}}}var ForgetTool=class extends import_sdk6.ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const existed=await remember.knows(input.key,{scope});await remember.forget(input.key,{scope});return{success:true,key:input.key,scope,existed}}};ForgetTool=__decorateClass([(0,import_sdk6.Tool)(forgetToolMetadata())],ForgetTool);var import_lazy_zod3=require("@frontmcp/lazy-zod");var import_sdk7=require("@frontmcp/sdk");var listMemoriesInputSchema={scope:import_lazy_zod3.z.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION),pattern:import_lazy_zod3.z.string().optional().describe('Pattern to filter keys (e.g., "user_*")'),limit:import_lazy_zod3.z.number().positive().max(100).optional().describe("Maximum number of keys to return (default: 50)")};var listMemoriesOutputSchema=import_lazy_zod3.z.object({keys:import_lazy_zod3.z.array(import_lazy_zod3.z.string()),scope:import_lazy_zod3.z.string(),count:import_lazy_zod3.z.number(),truncated:import_lazy_zod3.z.boolean()});function listMemoriesDescription(names=rememberToolNames()){return`List the remembered keys in one scope (default: session). Use this to see what memories are stored before you ${names.recall} or ${names.forget} one.`}function listMemoriesToolMetadata(names=rememberToolNames()){return{name:names.listMemories,description:listMemoriesDescription(names),inputSchema:listMemoriesInputSchema,outputSchema:listMemoriesOutputSchema,annotations:{readOnlyHint:true}}}var ListMemoriesTool=class extends import_sdk7.ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const limit=input.limit??50;let keys=await remember.list({scope,pattern:input.pattern});const truncated=keys.length>limit;if(truncated){keys=keys.slice(0,limit)}return{keys,scope,count:keys.length,truncated}}};ListMemoriesTool=__decorateClass([(0,import_sdk7.Tool)(listMemoriesToolMetadata())],ListMemoriesTool);var import_lazy_zod4=require("@frontmcp/lazy-zod");var import_sdk8=require("@frontmcp/sdk");var recallInputSchema={key:import_lazy_zod4.z.string().min(1).describe("What memory to recall"),scope:import_lazy_zod4.z.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION)};var recallOutputSchema=import_lazy_zod4.z.object({found:import_lazy_zod4.z.boolean(),key:import_lazy_zod4.z.string(),value:import_lazy_zod4.z.unknown().optional(),scope:import_lazy_zod4.z.string(),createdAt:import_lazy_zod4.z.number().optional(),expiresAt:import_lazy_zod4.z.number().optional()});function recallDescription(names=rememberToolNames()){return`Recall something that was previously remembered. Use this to retrieve stored preferences, settings, or any information that was saved with ${names.rememberThis}.`}function recallToolMetadata(names=rememberToolNames()){return{name:names.recall,description:recallDescription(names),inputSchema:recallInputSchema,outputSchema:recallOutputSchema,annotations:{readOnlyHint:true}}}var RecallTool=class extends import_sdk8.ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const entry=await remember.getEntry(input.key,{scope});if(!entry){return{found:false,key:input.key,scope}}return{found:true,key:input.key,value:entry.value,scope,createdAt:entry.createdAt,expiresAt:entry.expiresAt}}};RecallTool=__decorateClass([(0,import_sdk8.Tool)(recallToolMetadata())],RecallTool);var import_lazy_zod5=require("@frontmcp/lazy-zod");var import_sdk9=require("@frontmcp/sdk");var rememberThisInputSchema={key:import_lazy_zod5.z.string().min(1).describe('What to call this memory (e.g., "user_preference", "last_action")'),value:import_lazy_zod5.z.unknown().describe("The value to remember (any JSON-serializable data)"),scope:import_lazy_zod5.z.enum(["session","user","tool","global"]).optional().describe(REMEMBER_SCOPE_DESCRIPTION),ttl:import_lazy_zod5.z.number().int().positive().optional().describe("Forget after this many whole seconds"),brand:import_lazy_zod5.z.enum(["preference","cache","state","conversation","custom"]).optional().describe("Type hint for the stored value")};var rememberThisOutputSchema=import_lazy_zod5.z.object({success:import_lazy_zod5.z.boolean(),key:import_lazy_zod5.z.string(),scope:import_lazy_zod5.z.string(),expiresAt:import_lazy_zod5.z.number().optional()});function rememberThisDescription(names=rememberToolNames()){return`Remember something for later: store a value under a key that ${names.recall} can read back, in the scope you choose (this session by default; user scope for the signed-in user across sessions). It lasts until it is forgotten or its ttl runs out. Use this when the user asks you to remember preferences, settings, or any information they want to persist.`}function rememberThisToolMetadata(names=rememberToolNames()){return{name:names.rememberThis,description:rememberThisDescription(names),inputSchema:rememberThisInputSchema,outputSchema:rememberThisOutputSchema,annotations:{readOnlyHint:false}}}var RememberThisTool=class extends import_sdk9.ToolContext{async execute(input){const remember=this.get(RememberAccessorToken);const config=this.get(RememberConfigToken);const scope=input.scope??"session";const allowedScopes=config.tools?.allowedScopes??["session","user","tool","global"];if(!allowedScopes.includes(scope)){throw this.fail(new RememberScopeNotAllowedError(scope,allowedScopes))}const entry=await remember.set(input.key,input.value,{scope,ttl:input.ttl,brand:input.brand});return{success:true,key:input.key,scope,expiresAt:entry.expiresAt}}};RememberThisTool=__decorateClass([(0,import_sdk9.Tool)(rememberThisToolMetadata())],RememberThisTool);function createRememberTools(prefix=""){if(!prefix)return[RememberThisTool,RecallTool,ForgetTool,ListMemoriesTool];const names=rememberToolNames(prefix);let PrefixedRememberThisTool=class extends RememberThisTool{};PrefixedRememberThisTool=__decorateClass([(0,import_sdk10.Tool)(rememberThisToolMetadata(names))],PrefixedRememberThisTool);let PrefixedRecallTool=class extends RecallTool{};PrefixedRecallTool=__decorateClass([(0,import_sdk10.Tool)(recallToolMetadata(names))],PrefixedRecallTool);let PrefixedForgetTool=class extends ForgetTool{};PrefixedForgetTool=__decorateClass([(0,import_sdk10.Tool)(forgetToolMetadata(names))],PrefixedForgetTool);let PrefixedListMemoriesTool=class extends ListMemoriesTool{};PrefixedListMemoriesTool=__decorateClass([(0,import_sdk10.Tool)(listMemoriesToolMetadata(names))],PrefixedListMemoriesTool);return[PrefixedRememberThisTool,PrefixedRecallTool,PrefixedForgetTool,PrefixedListMemoriesTool]}function withCheckedOptions(options){const customKey=options.encryption?.customKey;if(customKey!==void 0&&customKey.trim()===""){throw new RememberConfigurationError("RememberPlugin encryption.customKey must not be empty; omit it to derive keys from the server secret")}const{defaultTTL}=options;if(defaultTTL===void 0)return options;if(defaultTTL===0)return{...options,defaultTTL:void 0};if(!Number.isInteger(defaultTTL)||defaultTTL<0){throw new RememberConfigurationError(`RememberPlugin defaultTTL must be a whole number of seconds (0 for no default expiry), got ${defaultTTL}`)}return options}var RememberPlugin=class extends import_sdk11.DynamicPlugin{options;constructor(options={}){super();this.options={...RememberPlugin.defaultOptions,...withCheckedOptions(options)}}};__publicField(RememberPlugin,"defaultOptions",{type:"memory",keyPrefix:"remember:",encryption:{enabled:true}});__publicField(RememberPlugin,"dynamicTools",options=>options.tools?.enabled?createRememberTools(options.tools.prefix):[]);__publicField(RememberPlugin,"browserStores",(options,server)=>{const type=options.type??RememberPlugin.defaultOptions.type;const onRedis=type==="redis"||type==="redis-client"||type==="global-store"&&server.redis!==void 0&&!(0,import_sdk11.isVercelKvProvider)(server.redis);return[{plugin:"RememberPlugin",option:onRedis?`type: '${type}'`:void 0,memory:"type: 'memory'"}]});__publicField(RememberPlugin,"dynamicProviders",options=>{const providers=[];const config={...RememberPlugin.defaultOptions,...withCheckedOptions(options)};switch(config.type){case"global-store":providers.push((0,import_sdk11.storeProvider)({name:"remember:store:global",provide:RememberStoreToken,inject:()=>[import_sdk11.FrontMcpConfig],useFactory:frontmcpConfig=>{const storeConfig=(0,import_sdk11.getGlobalStoreConfig)("RememberPlugin",frontmcpConfig);if((0,import_sdk11.isVercelKvProvider)(storeConfig)){return new RememberVercelKvProvider({url:storeConfig.url,token:storeConfig.token,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}return new RememberRedisProvider({type:"redis",config:{host:storeConfig.host??"localhost",port:storeConfig.port??6379,password:storeConfig.password,db:storeConfig.db},keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}}));break;case"redis":if("config"in config&&config.config){const redisConfig=config.config;let store;providers.push((0,import_sdk11.storeProvider)({name:"remember:store:redis",provide:RememberStoreToken,inject:()=>[],useFactory:()=>store??=new RememberRedisProvider({type:"redis",config:redisConfig,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})}))}break;case"redis-client":if("client"in config&&config.client){providers.push({name:"remember:store:redis-client",provide:RememberStoreToken,useValue:new RememberRedisProvider({type:"redis-client",client:config.client,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})})}break;case"vercel-kv":providers.push({name:"remember:store:vercel-kv",provide:RememberStoreToken,useValue:new RememberVercelKvProvider({url:"url"in config?config.url:void 0,token:"token"in config?config.token:void 0,keyPrefix:config.keyPrefix,defaultTTL:config.defaultTTL})});break;case"memory":default:providers.push({name:"remember:store:memory",provide:RememberStoreToken,inject:()=>[],useFactory:()=>new RememberMemoryProvider(void 0,config.defaultTTL)});break}providers.push({name:"remember:config",provide:RememberConfigToken,useValue:config});providers.push({name:"remember:accessor",provide:RememberAccessorToken,scope:import_sdk11.ProviderScope.CONTEXT,inject:()=>[RememberStoreToken,import_sdk11.FRONTMCP_CONTEXT,RememberConfigToken,import_sdk11.FrontMcpLogger],useFactory:(store,ctx,cfg,logger)=>createRememberAccessor(store,ctx,cfg,logger)});return providers});RememberPlugin=__decorateClass([(0,import_sdk11.Plugin)({name:"remember",description:"Help your LLM remember things across sessions",providers:[{name:"remember:store:memory",provide:RememberStoreToken,inject:()=>[],useFactory:()=>new RememberMemoryProvider}],contextExtensions:[{property:"remember",token:RememberAccessorToken,errorMessage:"RememberPlugin is not installed. Add RememberPlugin.init() to your plugins array."}]})],RememberPlugin);var import_lazy_zod6=require("@frontmcp/lazy-zod");var rememberScopeSchema=import_lazy_zod6.z.enum(["session","user","tool","global"]);function brand(data,_brand){return data}var rememberEntrySchema=import_lazy_zod6.z.object({value:import_lazy_zod6.z.unknown(),brand:import_lazy_zod6.z.enum(["approval","preference","cache","state","conversation","custom"]).optional(),createdAt:import_lazy_zod6.z.number(),updatedAt:import_lazy_zod6.z.number(),expiresAt:import_lazy_zod6.z.number().optional(),metadata:import_lazy_zod6.z.record(import_lazy_zod6.z.string(),import_lazy_zod6.z.unknown()).optional()});function getRemember(ctx){return ctx.get(RememberAccessorToken)}function tryGetRemember(ctx){if(typeof ctx.tryGet==="function"){return ctx.tryGet(RememberAccessorToken)}return void 0}0&&(module.exports={ForgetTool,ListMemoriesTool,RecallTool,RememberAccessor,RememberAccessorToken,RememberConfigToken,RememberConfigurationError,RememberIdentityError,RememberMemoryProvider,RememberPlugin,RememberRedisProvider,RememberScopeNotAllowedError,RememberStoreToken,RememberThisTool,RememberVercelKvProvider,brand,getRemember,tryGetRemember});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/plugin-remember",
3
- "version": "1.10.16",
3
+ "version": "1.11.0-rc.1",
4
4
  "description": "Remember plugin for FrontMCP: encrypted key-value memory for tools (this.remember) in session, user, tool and global scopes, stored in memory, Redis or Vercel KV",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
@@ -52,12 +52,12 @@
52
52
  }
53
53
  },
54
54
  "dependencies": {
55
- "@frontmcp/utils": "1.10.16",
55
+ "@frontmcp/utils": "1.11.0-rc.1",
56
56
  "ioredis": "^5.8.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@frontmcp/lazy-zod": "1.10.16",
60
- "@frontmcp/sdk": "1.10.16",
59
+ "@frontmcp/lazy-zod": "1.11.0-rc.1",
60
+ "@frontmcp/sdk": "1.11.0-rc.1",
61
61
  "@vercel/kv": "^2.0.0 || ^3.0.0"
62
62
  },
63
63
  "peerDependenciesMeta": {
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  "devDependencies": {
69
- "@frontmcp/sdk": "1.10.16",
69
+ "@frontmcp/sdk": "1.11.0-rc.1",
70
70
  "reflect-metadata": "^0.2.2"
71
71
  }
72
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"remember-redis.provider.d.ts","sourceRoot":"","sources":["../../src/providers/remember-redis.provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gCAAgC,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAOtG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,GAAG,gCAAgC,CAAC;AAEjG;;;GAGG;AAMH,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,sBAAsB;IAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,mGAAmG;IACnG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;gBAEzB,OAAO,EAAE,oBAAoB;IA8BzC;;;;;;;OAOG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B/E;;;;;;;OAOG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2BrF;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAclF;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAM3C;;;OAGG;IACG,IAAI,CAAC,OAAO,SAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAY9B,IAAI;IAWlB;;;;OAIG;YACW,sBAAsB;IAKpC,gHAAgH;YAClG,sBAAsB;IAKpC;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
1
+ {"version":3,"file":"remember-redis.provider.d.ts","sourceRoot":"","sources":["../../src/providers/remember-redis.provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gCAAgC,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAOtG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAWzE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,GAAG,gCAAgC,CAAC;AAEjG;;;GAGG;AAMH,MAAM,CAAC,OAAO,OAAO,qBAAsB,YAAW,sBAAsB;IAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,mGAAmG;IACnG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;gBAEzB,OAAO,EAAE,oBAAoB;IA8BzC;;;;;;;OAOG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B/E;;;;;;;OAOG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2BrF;;;;;;;;;;OAUG;IACG,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAclF;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAM3C;;;OAGG;IACG,IAAI,CAAC,OAAO,SAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;YAY9B,IAAI;IAWlB;;;;OAIG;YACW,sBAAsB;IAKpC,gHAAgH;YAClG,sBAAsB;IAKpC;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
@@ -1,4 +1,4 @@
1
- import { DynamicPlugin, type ProviderType, type ToolType } from '@frontmcp/sdk';
1
+ import { DynamicPlugin, type PluginStoreInBrowser, type PluginStoreServer, type ProviderType, type ToolType } from '@frontmcp/sdk';
2
2
  import type { RememberPluginOptions, RememberPluginOptionsInput } from './remember.types';
3
3
  /**
4
4
  * RememberPlugin - Stateful session memory for FrontMCP.
@@ -38,6 +38,8 @@ export default class RememberPlugin extends DynamicPlugin<RememberPluginOptions,
38
38
  * `list_memories`, under `tools.prefix`.
39
39
  */
40
40
  static dynamicTools: (options: RememberPluginOptionsInput) => readonly ToolType[];
41
+ /** A store on Redis, which a browser refuses at startup (#1033): Vercel KV, over HTTPS, is not one. */
42
+ static browserStores: (options: RememberPluginOptionsInput, server: PluginStoreServer) => PluginStoreInBrowser[];
41
43
  /**
42
44
  * Dynamic providers based on plugin options.
43
45
  */
@@ -1 +1 @@
1
- {"version":3,"file":"remember.plugin.d.ts","sourceRoot":"","sources":["../src/remember.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EASb,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AAQvB,OAAO,KAAK,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAyB1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAwBH,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,aAAa,CAAC,qBAAqB,EAAE,0BAA0B,CAAC;IAC1G,MAAM,CAAC,cAAc,EAAE,qBAAqB,CAI1C;IAEF,OAAO,EAAE,qBAAqB,CAAC;gBAEnB,OAAO,GAAE,0BAA+B;IAQpD;;;OAGG;IACH,OAAgB,YAAY,GAAI,SAAS,0BAA0B,KAAG,SAAS,QAAQ,EAAE,CACf;IAE1E;;OAEG;IACH,OAAgB,gBAAgB,GAAI,SAAS,0BAA0B,KAAG,YAAY,EAAE,CA2HtF;CACH"}
1
+ {"version":3,"file":"remember.plugin.d.ts","sourceRoot":"","sources":["../src/remember.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAUb,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AAQvB,OAAO,KAAK,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAyB1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAwBH,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,aAAa,CAAC,qBAAqB,EAAE,0BAA0B,CAAC;IAC1G,MAAM,CAAC,cAAc,EAAE,qBAAqB,CAI1C;IAEF,OAAO,EAAE,qBAAqB,CAAC;gBAEnB,OAAO,GAAE,0BAA+B;IAQpD;;;OAGG;IACH,OAAgB,YAAY,GAAI,SAAS,0BAA0B,KAAG,SAAS,QAAQ,EAAE,CACf;IAE1E,uGAAuG;IACvG,OAAgB,aAAa,GAC3B,SAAS,0BAA0B,EACnC,QAAQ,iBAAiB,KACxB,oBAAoB,EAAE,CAOvB;IAEF;;OAEG;IACH,OAAgB,gBAAgB,GAAI,SAAS,0BAA0B,KAAG,YAAY,EAAE,CAoItF;CACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"remember.secret-persistence.d.ts","sourceRoot":"","sources":["../src/remember.secret-persistence.ts"],"names":[],"mappings":"AAmBA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,EAAE,CAAC,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAuDD;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAUtF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,MAAM,CAU5E;AAMD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA8B/G;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,kBAAkB,EAC9B,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,OAAO,CAAC,CA+BlB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5F;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CpG;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
1
+ {"version":3,"file":"remember.secret-persistence.d.ts","sourceRoot":"","sources":["../src/remember.secret-persistence.ts"],"names":[],"mappings":"AA4BA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,EAAE,CAAC,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAuDD;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAUtF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,MAAM,CAU5E;AAMD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA8B/G;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,kBAAkB,EAC9B,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,OAAO,CAAC,CA+BlB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5F;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CpG;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}