{"version":3,"sources":["/home/runner/work/fastmcp/fastmcp/dist/chunk-DZYT6QAH.cjs","../src/auth/helpers.ts","../src/auth/OAuthProxy.ts","../src/auth/OAuthProxyStateStore.ts","../src/auth/types.ts","../src/auth/utils/claimsExtractor.ts","../src/auth/utils/consent.ts","../src/auth/utils/jwtIssuer.ts","../src/auth/utils/pkce.ts","../src/auth/utils/tokenStore.ts","../src/auth/providers/AuthProvider.ts","../src/auth/providers/AzureProvider.ts","../src/auth/providers/GitHubProvider.ts","../src/auth/providers/GoogleProvider.ts","../src/auth/providers/OAuthProvider.ts","../src/auth/utils/diskStore.ts","../src/auth/utils/jwks.ts"],"names":[],"mappings":"AAAA;ACaO,SAAS,cAAA,CACd,OAAA,EACG;AACH,EAAA,GAAA,CAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,KAAA,CAAM,8BAA8B,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,OAAA;AACT;AAKO,SAAS,UAAA,CAAA,GACX,MAAA,EACmB;AACtB,EAAA,OAAO,CAAC,IAAA,EAAA,GACN,MAAA,CAAO,KAAA;AAAA,IAAM,CAAC,KAAA,EAAA,GACZ,OAAO,MAAA,IAAU,WAAA,EAAa,KAAA,CAAM,IAAI,EAAA,EAAI;AAAA,EAC9C,CAAA;AACJ;AAKO,SAAS,UAAA,CAAA,GACX,MAAA,EACmB;AACtB,EAAA,OAAO,CAAC,IAAA,EAAA,GACN,MAAA,CAAO,IAAA,CAAK,CAAC,KAAA,EAAA,GAAW,OAAO,MAAA,IAAU,WAAA,EAAa,KAAA,CAAM,IAAI,EAAA,EAAI,KAAM,CAAA;AAC9E;AAKO,SAAS,WAAA,CAAmC,IAAA,EAAkB;AACnE,EAAA,OAAO,KAAA,IAAS,KAAA,EAAA,GAAa,KAAA,IAAS,IAAA;AACxC;AAKO,SAAS,WAAA,CAAA,GACX,YAAA,EACmB;AACtB,EAAA,OAAO,CAAC,IAAA,EAAA,GAAqB;AAC3B,IAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,KAAA;AAClB,IAAA,MAAM,KAAA,EAAQ,IAAA,CAAiC,IAAA;AAC/C,IAAA,OAAO,OAAO,KAAA,IAAS,SAAA,GAAY,YAAA,CAAa,QAAA,CAAS,IAAI,CAAA;AAAA,EAC/D,CAAA;AACF;AAKO,SAAS,aAAA,CAAA,GACX,cAAA,EACmB;AACtB,EAAA,OAAO,CAAC,IAAA,EAAA,GAAqB;AAC3B,IAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,KAAA;AAClB,IAAA,MAAM,WAAA,EAAc,IAAA,CAAiC,MAAA;AACrD,IAAA,GAAA,CAAI,CAAC,UAAA,EAAY,OAAO,KAAA;AAExB,IAAA,MAAM,SAAA,EAAW,KAAA,CAAM,OAAA,CAAQ,UAAU,EAAA,EACrC,IAAI,GAAA,CAAI,UAAU,EAAA,EAClB,WAAA,WAAsB,IAAA,EACpB,WAAA,kBACA,IAAI,GAAA,CAAI,CAAA;AAEd,IAAA,OAAO,cAAA,CAAe,KAAA,CAAM,CAAC,KAAA,EAAA,GAAU,QAAA,CAAS,GAAA,CAAI,KAAK,CAAC,CAAA;AAAA,EAC5D,CAAA;AACF;ADjDA;AACA;AE9BA,gCAA4B;AAC5B,0BAAkB;AFgClB;AACA;AGvCA;AAUA,IAAM,mBAAA,EAAqB;AAAA,EACzB,MAAA,EAAQ,SAAA;AAAA,EACR,IAAA,EAAM,OAAA;AAAA,EACN,WAAA,EAAa;AACf,CAAA;AAEA,IAAM,iBAAA,EAAmB,MAAA,CACtB,KAAA,CAAM,CAAC,MAAA,CAAE,IAAA,CAAK,CAAA,EAAG,MAAA,CAAE,MAAA,CAAO,CAAC,CAAC,CAAA,CAC5B,SAAA,CAAU,CAAC,KAAA,EAAO,OAAA,EAAA,GAAY;AAC7B,EAAA,MAAM,KAAA,EAAO,MAAA,WAAiB,KAAA,EAAO,MAAA,EAAQ,IAAI,IAAA,CAAK,KAAK,CAAA;AAE3D,EAAA,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,EAAG;AAChC,IAAA,OAAA,CAAQ,QAAA,CAAS;AAAA,MACf,IAAA,EAAM,QAAA;AAAA,MACN,OAAA,EAAS;AAAA,IACX,CAAC,CAAA;AACD,IAAA,OAAO,MAAA,CAAE,KAAA;AAAA,EACX;AAEA,EAAA,OAAO,IAAA;AACT,CAAC,CAAA;AAEH,IAAM,wBAAA,EAA0B,MAAA,CAAE,MAAA,CAAO;AAAA,EACvC,WAAA,EAAa,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EACjC,UAAA,EAAY,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EAChC,QAAA,EAAU,MAAA,CAAE,KAAA,CAAM,MAAA,CAAE,MAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EACvC,IAAA,EAAM,MAAA,CAAE,MAAA,CAAO,MAAA,CAAE,MAAA,CAAO,CAAA,EAAG,MAAA,CAAE,OAAA,CAAQ,CAAC,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EACjD,QAAA,EAAU,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EAC9B,QAAA,EAAU,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EAC9B,UAAA,EAAY,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EAChC,KAAA,EAAO,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EAC3B,WAAA,EAAa,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EACjC,gBAAA,EAAkB,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EACtC,OAAA,EAAS,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS;AAC/B,CAAC,CAAA;AAED,IAAM,4BAAA,EAAyD,MAAA,CAAE,MAAA,CAAO;AAAA,EACtE,WAAA,EAAa,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACtB,QAAA,EAAU,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACnB,YAAA,EAAc,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EAClC,QAAA,EAAU,uBAAA,CAAwB,QAAA,CAAS,CAAA;AAAA,EAC3C,YAAA,EAAc,MAAA,CAAE,KAAA,CAAM,MAAA,CAAE,MAAA,CAAO,CAAC,CAAA;AAAA,EAChC,YAAA,EAAc;AAChB,CAAC,CAAA;AAED,IAAM,8BAAA,EAA6D,MAAA,CAAE,MAAA,CAAO;AAAA,EAC1E,WAAA,EAAa,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACtB,SAAA,EAAW,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACpB,OAAA,EAAS,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EAC7B,QAAA,EAAU,gBAAA;AAAA,EACV,gBAAA,EAAkB,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EACtC,YAAA,EAAc,MAAA,CAAE,MAAA,CAAO,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EAClC,KAAA,EAAO,MAAA,CAAE,KAAA,CAAM,MAAA,CAAE,MAAA,CAAO,CAAC,CAAA;AAAA,EACzB,SAAA,EAAW,MAAA,CAAE,MAAA,CAAO;AACtB,CAAC,CAAA;AAED,IAAM,wBAAA,EAAiD,MAAA,CAAE,MAAA,CAAO;AAAA,EAC9D,QAAA,EAAU,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACnB,IAAA,EAAM,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACf,aAAA,EAAe,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACxB,mBAAA,EAAqB,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EAC9B,SAAA,EAAW,gBAAA;AAAA,EACX,SAAA,EAAW,gBAAA;AAAA,EACX,aAAA,EAAe,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACxB,cAAA,EAAgB,6BAAA;AAAA,EAChB,IAAA,EAAM,MAAA,CAAE,OAAA,CAAQ,CAAA,CAAE,QAAA,CAAS;AAC7B,CAAC,CAAA;AAUD,IAAM,6BAAA,EAA+B,MAAA,CAAE,MAAA,CAAO;AAAA,EAC5C,SAAA,EAAW,gBAAA;AAAA,EACX,IAAA,EAAM,MAAA,CAAE,OAAA,CAAQ,IAAI;AACtB,CAAC,CAAA;AAED,IAAM,8BAAA,EAA6D,MAAA,CAAE,MAAA,CAAO;AAAA,EAC1E,iBAAA,EAAmB,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EAC5B,mBAAA,EAAqB,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EAC9B,yBAAA,EAA2B,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACpC,QAAA,EAAU,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACnB,YAAA,EAAc,MAAA,CAAE,OAAA,CAAQ,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EACnC,SAAA,EAAW,gBAAA;AAAA,EACX,SAAA,EAAW,gBAAA;AAAA,EACX,EAAA,EAAI,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EACb,QAAA,EAAU,MAAA,CAAE,MAAA,CAAO,MAAA,CAAE,MAAA,CAAO,CAAA,EAAG,MAAA,CAAE,OAAA,CAAQ,CAAC,CAAA,CAAE,QAAA,CAAS,CAAA;AAAA,EACrD,kBAAA,EAAoB,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EAC7B,iBAAA,EAAmB,MAAA,CAAE,MAAA,CAAO,CAAA;AAAA,EAC5B,KAAA,EAAO,MAAA,CAAE,KAAA,CAAM,MAAA,CAAE,MAAA,CAAO,CAAC,CAAA;AAAA,EACzB,KAAA,EAAO,MAAA,CAAE,MAAA,CAAO;AAClB,CAAC,CAAA;AA2BM,IAAM,qBAAA,EAAN,MAA2B;AAAA,EACf;AAAA,EACA;AAAA,EAEjB,WAAA,CAAY,MAAA,EAAoC;AAC9C,IAAA,IAAA,CAAK,4BAAA,EAA8B,MAAA,CAAO,2BAAA;AAC1C,IAAA,IAAA,CAAK,aAAA,EAAe,MAAA,CAAO,YAAA;AAAA,EAC7B;AAAA,EAEA,qBAAA,CAAsB,MAAA,EAA8B;AAClD,IAAA,IAAA,CAAK,2BAAA,CAA4B,GAAA,CAAI,MAAA,CAAO,QAAA,EAAU,MAAM,CAAA;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,iBAAA,CAAkB,IAAA,EAAkD;AACxE,IAAA,MAAM,OAAA,EAAS,MAAM,IAAA,CAAK,eAAA;AAAA,MACxB,CAAA,EAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACE,MAAA;AAGF,IAAA;AAEA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AAGE,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AAGE,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AAGE,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAEA,EAAA;AAGE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAIF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEQ,EAAA;AACN,IAAA;AACA,IAAA;AACF,EAAA;AAEQ,EAAA;AACN,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,EAAA;AACE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACF,EAAA;AACF;AH3DO;AACA;AItQM;AACA;AACA;AACA;AACA;AJwQN;AACA;AK9QM;AACH,EAAA;AAAA;AAGS,iBAAA;AACf,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,EAAA;AAED,EAAA;AAEE,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAC0D;AAC1D,MAAA;AAA4C;AAC5C,MAAA;AAAoC;AACpC,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAKE,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AAEE,MAAA;AACF,IAAA;AAIA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AAGA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AAGN,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACN,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AACF,MAAA;AAGA,MAAA;AACA,MAAA;AACF,IAAA;AAEE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AAGN,IAAA;AAEA,IAAA;AAEE,MAAA;AACE,QAAA;AACF,MAAA;AAGA,MAAA;AACE,QAAA;AACF,MAAA;AAGA,MAAA;AAIE,QAAA;AACF,MAAA;AAGA,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AAEE,MAAA;AACE,QAAA;AACF,MAAA;AAGA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AAEE,QAAA;AACF,MAAA;AACF,IAAA;AAGA,IAAA;AACF,EAAA;AACF;ALoNO;AACA;AM7ZP;AAOa;AACH,EAAA;AAER,EAAA;AACE,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAIE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACE,MAAA;AAAS,QAAA;AAET,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AAEA,IAAA;AAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAA;AAgKmC;AAAA;AAAA;AAAA;AAAA,uCAAA;AAK0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAA;AAOW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8DAAA;AAaL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBxF,IAAA;AACJ,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACA,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AAEA,MAAA;AACA,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AAEA,MAAA;AAGA,MAAA;AACA,MAAA;AACE,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AAEN,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKa,EAAA;AACX,IAAA;AACF,EAAA;AACF;AN+XO;AACA;AOjtBP;AACA;AAOM;AAgEO;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAER,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AAIE,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAME,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAA;AAEA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAME,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAA;AAEA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AAAO,UAAA;AACE,UAAA;AACA,QAAA;AAEX,MAAA;AAEA,MAAA;AAGA,MAAA;AACA,MAAA;AACE,QAAA;AAAO,UAAA;AACE,UAAA;AACA,QAAA;AAEX,MAAA;AAGA,MAAA;AAA+B,QAAA;AAE/B,MAAA;AAGA,MAAA;AAEA,MAAA;AACE,QAAA;AAAO,UAAA;AACL,UAAA;AACO,UAAA;AACA,QAAA;AAEX,MAAA;AAEA,MAAA;AACE,QAAA;AAAO,UAAA;AACL,UAAA;AACO,UAAA;AACA,QAAA;AAEX,MAAA;AAEA,MAAA;AACE,QAAA;AAAO,UAAA;AACL,UAAA;AACO,UAAA;AACA,QAAA;AAEX,MAAA;AAEA,MAAA;AAAO,QAAA;AACL,QAAA;AAEF,MAAA;AACF,IAAA;AACE,MAAA;AAAO,QAAA;AAC2C,QAAA;AAElD,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKa,EAAA;AACX,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACF,EAAA;AACF;APumBO;AACA;AQx2BP;AAOa;AAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAOrB,EAAA;AAIE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,EAAA;AACE,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,EAAA;AACE,IAAA;AACE,MAAA;AACF,IAAA;AAIA,IAAA;AACA,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,EAAA;AAKE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,EAAA;AACE,IAAA;AAKF,EAAA;AACF;AR00BO;AACA;ASp7BP;AACE;AACA;AACA;AACA;AACK;AAaM;AACH,kBAAA;AACA,EAAA;AACA,EAAA;AAER,EAAA;AACE,IAAA;AAEA,IAAA;AACA,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AACE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACD,IAAA;AAEA,IAAA;AACC,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AAEA,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AAA6B,QAAA;AAC3B,QAAA;AAEF,MAAA;AACA,MAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACA,IAAA;AACE,MAAA;AACD,IAAA;AAED,IAAA;AACA,IAAA;AAGA,IAAA;AAKA,IAAA;AACF,EAAA;AACF;AAKa;AACH,kBAAA;AACA,kBAAA;AAER,EAAA;AAEE,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AACE,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AACF;AT03BO;AACA;AE9iCD;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAMY;AACH,kBAAA;AACA,kBAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,kBAAA;AACA,EAAA;AACA,EAAA;AAER,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AAAiB;AACjB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AAIA,IAAA;AAGA,IAAA;AAEE,MAAA;AAKA,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACD,IAAA;AACD,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AAEE,MAAA;AAEA,MAAA;AAA+B,QAAA;AACP,QAAA;AACF,QAAA;AAErB,MAAA;AACH,IAAA;AAGA,IAAA;AAKA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAEE,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAKA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAMA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAGA,IAAA;AAGA,IAAA;AACE,MAAA;AAA2B,QAAA;AACzB,QAAA;AAEF,MAAA;AACF,IAAA;AAGA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAGE,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAKA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAKA,IAAA;AACA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAOA,IAAA;AAEA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAEA,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACE,QAAA;AAAU,UAAA;AACR,UAAA;AACA,QAAA;AAEJ,MAAA;AAEA,MAAA;AAAwB,QAAA;AACd,QAAA;AACG,QAAA;AAEb,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AAGA,IAAA;AAEE,MAAA;AAAkB,QAAA;AACL,QAAA;AAEb,MAAA;AACF,IAAA;AAEE,MAAA;AAAgC,QAAA;AACU,QAAA;AACF,QAAA;AAExC,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAqBE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAmC,QAAA;AACjC,QAAA;AAEF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAKA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAKA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAGA,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AACA,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AAAS,QAAA;AAET,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAKA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAEA,IAAA;AAEE,MAAA;AAEA,MAAA;AAGE,QAAA;AAAU,UAAA;AACR,UAAA;AACA,QAAA;AAEJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAyB,QAAA;AACvB,QAAA;AAEF,MAAA;AACA,MAAA;AAEA,MAAA;AAA0B,QAAA;AACf,UAAA;AACwB,QAAA;AACjC,QAAA;AAED,MAAA;AACH,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAGE,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAIA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAEE,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACE,QAAA;AAAU,UAAA;AACR,UAAA;AAC4B,QAAA;AAEhC,MAAA;AACF,IAAA;AAMA,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAU,QAAA;AACa,QAAA;AACD,QAAA;AACF,QAAA;AACJ,QAAA;AACI,QAAA;AACA,QAAA;AACE,QAAA;AACL,QAAA;AACM,QAAA;AACK,QAAA;AAE5B,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AAAiD;AAEjD,MAAA;AACA,MAAA;AACA,MAAA;AAA0B;AAC1B,MAAA;AACA,MAAA;AACA,MAAA;AAAoC,QAAA;AAClC,QAAA;AAEF,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AAKN,IAAA;AACE,MAAA;AAAQ,QAAA;AACN,QAAA;AAEF,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAGE,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAe,QAAA;AAEf,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAIE,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAED,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AAIA,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AAGE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AAA4C,QAAA;AAC3B,QAAA;AAEjB,MAAA;AACA,MAAA;AAEE,QAAA;AACE,UAAA;AACE,YAAA;AAAiB,UAAA;AACnB,QAAA;AAEJ,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAIE,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAe,QAAA;AAEf,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACN,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAGE,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAED,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AAIA,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAGE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAMA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AAAU,QAAA;AACR,QAAA;AAEF,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAIA,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AAIE,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AAGA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AAIA,IAAA;AAOA,IAAA;AACA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AAAA,QAAA;AACE,QAAA;AACoB,QAAA;AACkC,QAAA;AACjD,QAAA;AACiB,QAAA;AAExB,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AAAoC,QAAA;AAClC,QAAA;AACe,QAAA;AACC,QAAA;AAElB,MAAA;AACA,MAAA;AAGA,MAAA;AAAwB,QAAA;AACD,QAAA;AACrB,UAAA;AACE,UAAA;AACoB,UAAA;AACmC,UAAA;AAClD,UAAA;AACiB,UAAA;AACtB,QAAA;AACF,QAAA;AAEF,MAAA;AAEA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAME,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAEA,IAAA;AAEA,IAAA;AACA,IAAA;AAMA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AAAA,QAAA;AACE,QAAA;AACoB,QAAA;AACkC,QAAA;AACjD,QAAA;AACiB,QAAA;AAExB,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AAAoC,QAAA;AAClC,QAAA;AACe,QAAA;AACC,QAAA;AAElB,MAAA;AACA,MAAA;AAEA,MAAA;AAAwB,QAAA;AACD,QAAA;AACrB,UAAA;AACE,UAAA;AACoB,UAAA;AACmC,UAAA;AAClD,UAAA;AACiB,UAAA;AACtB,QAAA;AACF,QAAA;AAEF,MAAA;AAEA,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AASE,IAAA;AAKA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAGD,IAAA;AACE,MAAA;AACA,MAAA;AAEA,MAAA;AAAgB,QAAA;AAC8B,QAAA;AAGxC,QAAA;AACgC,QAAA;AAGhC,QAAA;AAC0C,QAAA;AAChB,QAAA;AAEhC,MAAA;AAEA,MAAA;AACF,IAAA;AAGA,IAAA;AACA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AAIA,IAAA;AACE,MAAA;AAAkC,QAAA;AAElC,MAAA;AACE,QAAA;AACE,UAAA;AAAmC,QAAA;AAEvC,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AAAqB,QAAA;AACnB,QAAA;AAEF,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AAAS,QAAA;AAET,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAIE,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAED,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AAIA,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AAIA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACN,IAAA;AACE,MAAA;AACC,IAAA;AACL,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBQ,EAAA;AACN,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AAGA,IAAA;AACE,MAAA;AACF,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AAA+B,MAAA;AAE/B,IAAA;AACF,EAAA;AACF;AAKa;AACX,EAAA;AAKE,IAAA;AAJO,IAAA;AACA,IAAA;AACA,IAAA;AAGP,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AACF;AF4qBO;AACA;AUzhEe;AAGV,EAAA;AAAA;AAAA;AAAA;AAII,EAAA;AACZ,IAAA;AACE,MAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AAEQ,EAAA;AAER,EAAA;AACE,IAAA;AAEF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AAGE,IAAA;AAEE,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAYE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AAAmB,QAAA;AACyB,QAAA;AACpB,QAAA;AAExB,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AASU,EAAA;AACR,IAAA;AACE,MAAA;AACA,MAAA;AAGA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAUF;AVk/DO;AACA;AWnpEM;AACH,EAAA;AAER,EAAA;AACE,IAAA;AACA,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AAAsB;AAAA;AAAA;AAIpB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AACF;AXgpEO;AACA;AY/rEM;AACX,EAAA;AACE,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AAAsB;AAAA;AAAA;AAIpB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AACF;AZ6rEO;AACA;AajuEM;AACX,EAAA;AACE,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AAAsB;AAAA;AAAA;AAIpB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AACF;Ab+tEO;AACA;Ac1wEM;AAGD,EAAA;AAEV,EAAA;AACE,IAAA;AACA,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AAAsB;AAAA;AAAA;AAIpB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAED,IAAA;AACH,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AAEU,EAAA;AACR,IAAA;AACF,EAAA;AACF;AdowEO;AACA;Ae1zEP;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACK;AACP;AAgCa;AACH,kBAAA;AACA,EAAA;AACA,EAAA;AAER,EAAA;AACE,IAAA;AACA,IAAA;AAGA,IAAA;AAGA,IAAA;AACA,IAAA;AACE,MAAA;AACC,IAAA;AACL,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACE,QAAA;AACE,UAAA;AAAA,QAAA;AAGF,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AAEA,UAAA;AACE,YAAA;AAAiB,UAAA;AACnB,QAAA;AAGA,UAAA;AACA,UAAA;AACE,YAAA;AAAmC,UAAA;AAC7B,UAAA;AAER,QAAA;AAEJ,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACA,IAAA;AACE,MAAA;AACF,IAAA;AAEE,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AAGA,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AAEE,MAAA;AACE,QAAA;AACF,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AAEA,IAAA;AACA,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,EAAA;AACE,IAAA;AAIA,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACE,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AAEA,MAAA;AACF,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACE,MAAA;AACE,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AAEN,IAAA;AACA,IAAA;AACF,EAAA;AACF;Af0vEO;AACA;AgB56EM;AACH,EAAA;AACA,EAAA;AACA,kBAAA;AACA,EAAA;AAER,EAAA;AACE,IAAA;AACE,MAAA;AAAe;AACf,MAAA;AAAkB;AAClB,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACE,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AACE,IAAA;AAGA,IAAA;AACE,MAAA;AACA,MAAA;AAAA,QAAA;AAC2B,QAAA;AAE3B,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBA,EAAA;AACE,IAAA;AAEE,MAAA;AAGA,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AAEA,MAAA;AACE,QAAA;AACF,MAAA;AAEA,MAAA;AAAoC,QAAA;AAClC,QAAA;AACK,QAAA;AAEP,MAAA;AAIA,MAAA;AAAwC,QAAA;AACzB,QAAA;AAC2B,QAAA;AAC3B,QAAA;AACA,QAAA;AACA,QAAA;AACO,QAAA;AACgB,QAAA;AACjC;AACL,MAAA;AAEA,MAAA;AAAO,QAAA;AACL,QAAA;AAEF,MAAA;AACF,IAAA;AACE,MAAA;AAAO,QAAA;AACmB,QAAA;AAE1B,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AACE,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACA,MAAA;AAGA,MAAA;AAA2B,QAAA;AACE,QAAA;AAC3B,UAAA;AAC2B,UAAA;AACK,QAAA;AAElC,MAAA;AACF,IAAA;AACE,MAAA;AAAU,QAAA;AACR;AAAA;AAAA;AAAA;AAAA,gBAAA;AAIF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACN,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AACF;AhB25EO;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/fastmcp/fastmcp/dist/chunk-DZYT6QAH.cjs","sourcesContent":[null,"/**\n * Authentication Helper Functions\n * Utility functions for use with canAccess on tools, resources, and prompts\n */\n\nimport type { OAuthSession } from \"./providers/AuthProvider.js\";\n\ntype SessionAuth = Record<string, unknown> | undefined;\n\n/**\n * Extract and type-cast OAuth session from canAccess context.\n * Throws if session is undefined (use with canAccess: requireAuth).\n */\nexport function getAuthSession<T extends OAuthSession = OAuthSession>(\n  session: SessionAuth,\n): T {\n  if (!session) {\n    throw new Error(\"Session is not authenticated\");\n  }\n  return session as T;\n}\n\n/**\n * Combines multiple canAccess checks with AND logic.\n */\nexport function requireAll<T extends SessionAuth>(\n  ...checks: Array<((auth: T) => boolean) | boolean>\n): (auth: T) => boolean {\n  return (auth: T): boolean =>\n    checks.every((check) =>\n      typeof check === \"function\" ? check(auth) : check,\n    );\n}\n\n/**\n * Combines multiple canAccess checks with OR logic.\n */\nexport function requireAny<T extends SessionAuth>(\n  ...checks: Array<((auth: T) => boolean) | boolean>\n): (auth: T) => boolean {\n  return (auth: T): boolean =>\n    checks.some((check) => (typeof check === \"function\" ? check(auth) : check));\n}\n\n/**\n * Requires any authenticated session.\n */\nexport function requireAuth<T extends SessionAuth>(auth: T): boolean {\n  return auth !== undefined && auth !== null;\n}\n\n/**\n * Requires session to have a specific role (OR logic for multiple roles).\n */\nexport function requireRole<T extends SessionAuth>(\n  ...allowedRoles: string[]\n): (auth: T) => boolean {\n  return (auth: T): boolean => {\n    if (!auth) return false;\n    const role = (auth as Record<string, unknown>).role;\n    return typeof role === \"string\" && allowedRoles.includes(role);\n  };\n}\n\n/**\n * Requires session to have specific scopes.\n */\nexport function requireScopes<T extends SessionAuth>(\n  ...requiredScopes: string[]\n): (auth: T) => boolean {\n  return (auth: T): boolean => {\n    if (!auth) return false;\n    const authScopes = (auth as Record<string, unknown>).scopes;\n    if (!authScopes) return false;\n\n    const scopeSet = Array.isArray(authScopes)\n      ? new Set(authScopes)\n      : authScopes instanceof Set\n        ? authScopes\n        : new Set();\n\n    return requiredScopes.every((scope) => scopeSet.has(scope));\n  };\n}\n","/**\n * OAuth 2.1 Proxy Implementation\n * Provides DCR-compatible interface for non-DCR OAuth providers\n */\n\nimport { randomBytes } from \"crypto\";\nimport { z } from \"zod\";\n\nimport type {\n  AuthorizationParams,\n  ClientCode,\n  DCRRequest,\n  DCRResponse,\n  OAuthError,\n  OAuthProxyConfig,\n  OAuthTransaction,\n  ProxyDCRClient,\n  RefreshRequest,\n  TokenRequest,\n  TokenResponse,\n  TokenStorage,\n  UpstreamTokenSet,\n} from \"./types.js\";\n\nimport { OAuthProxyStateStore } from \"./OAuthProxyStateStore.js\";\nimport {\n  DEFAULT_ACCESS_TOKEN_TTL,\n  DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH,\n  DEFAULT_AUTHORIZATION_CODE_TTL,\n  DEFAULT_REFRESH_TOKEN_TTL,\n  DEFAULT_TRANSACTION_TTL,\n} from \"./types.js\";\nimport { ClaimsExtractor } from \"./utils/claimsExtractor.js\";\nimport { ConsentManager } from \"./utils/consent.js\";\nimport { JWTIssuer } from \"./utils/jwtIssuer.js\";\nimport { PKCEUtils } from \"./utils/pkce.js\";\nimport {\n  EncryptedTokenStorage,\n  MemoryTokenStorage,\n} from \"./utils/tokenStore.js\";\n\n/**\n * Authorization request parameters owned by the proxy. Entries in\n * `extraAuthorizationParams` with these keys are ignored so configuration\n * can never override the security-critical core of the upstream request.\n */\nconst RESERVED_AUTHORIZATION_PARAMS: ReadonlySet<string> = new Set([\n  \"client_id\",\n  \"code_challenge\",\n  \"code_challenge_method\",\n  \"redirect_uri\",\n  \"response_type\",\n  \"scope\",\n  \"state\",\n]);\n\n/**\n * OAuth 2.1 Proxy\n * Acts as transparent intermediary between MCP clients and upstream OAuth providers\n */\nexport class OAuthProxy {\n  private claimsExtractor: ClaimsExtractor | null = null;\n  private cleanupInterval: NodeJS.Timeout | null = null;\n  private config: OAuthProxyConfig;\n  private consentManager: ConsentManager;\n  private jwtIssuer?: JWTIssuer;\n  /**\n   * Keyed by proxy-issued client_id for authorize/token-exchange lookups and\n   * for the defence-in-depth callback checks. A registration never changes\n   * after it is written, so caching it locally cannot go stale; it is also\n   * persisted, so another instance can hydrate it.\n   */\n  private registeredClientsByClientId: Map<string, ProxyDCRClient> = new Map();\n  private stateStore: OAuthProxyStateStore;\n  private tokenStorage: TokenStorage;\n\n  constructor(config: OAuthProxyConfig) {\n    this.config = {\n      authorizationCodeTtl: DEFAULT_AUTHORIZATION_CODE_TTL,\n      consentRequired: true,\n      enableTokenSwap: true, // Enabled by default for security\n      redirectPath: \"/oauth/callback\",\n      transactionTtl: DEFAULT_TRANSACTION_TTL,\n      upstreamTokenEndpointAuthMethod: \"client_secret_basic\",\n      ...config,\n    };\n\n    // Set up token storage with encryption by default (matches Python's secure defaults)\n    let storage = config.tokenStorage || new MemoryTokenStorage();\n\n    // Wrap storage with encryption if not already encrypted\n    // Check if it's already an EncryptedTokenStorage instance\n    const isAlreadyEncrypted =\n      storage.constructor.name === \"EncryptedTokenStorage\";\n\n    if (!isAlreadyEncrypted && config.encryptionKey !== false) {\n      // Auto-generate encryption key if not provided\n      const encryptionKey =\n        typeof config.encryptionKey === \"string\"\n          ? config.encryptionKey\n          : this.generateSigningKey();\n\n      storage = new EncryptedTokenStorage(storage, encryptionKey);\n    }\n\n    this.tokenStorage = storage;\n    this.stateStore = new OAuthProxyStateStore({\n      registeredClientsByClientId: this.registeredClientsByClientId,\n      tokenStorage: this.tokenStorage,\n    });\n    this.consentManager = new ConsentManager(\n      config.consentSigningKey || this.generateSigningKey(),\n    );\n\n    // Initialize JWT issuer if token swap is enabled\n    if (this.config.enableTokenSwap) {\n      // Auto-generate signing key if not provided\n      const signingKey = this.config.jwtSigningKey || this.generateSigningKey();\n\n      this.jwtIssuer = new JWTIssuer({\n        audience: this.config.baseUrl,\n        issuer: this.config.baseUrl,\n        signingKey: signingKey,\n      });\n    }\n\n    // Initialize claims extractor (enabled by default)\n    const claimsConfig =\n      config.customClaimsPassthrough !== undefined\n        ? config.customClaimsPassthrough\n        : true; // Default: enabled\n\n    if (claimsConfig !== false) {\n      this.claimsExtractor = new ClaimsExtractor(claimsConfig);\n    }\n\n    // Start periodic cleanup\n    this.startCleanup();\n  }\n\n  /**\n   * OAuth authorization endpoint\n   */\n  async authorize(params: AuthorizationParams): Promise<Response> {\n    // Validate parameters\n    if (!params.client_id || !params.redirect_uri || !params.response_type) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"Missing required parameters\",\n      );\n    }\n\n    if (params.response_type !== \"code\") {\n      throw new OAuthProxyError(\n        \"unsupported_response_type\",\n        \"Only 'code' response type is supported\",\n      );\n    }\n\n    // RFC 6749 §5.2 - reject unknown clients with invalid_client.\n    // MCP clients receive a proxy-issued client_id during DCR (not the upstream\n    // provider's credentials), so we look up by that proxy client_id.\n    const registeredClient =\n      await this.stateStore.getRegisteredClientByClientId(params.client_id);\n    if (!registeredClient) {\n      throw new OAuthProxyError(\"invalid_client\", \"Unknown client_id\");\n    }\n\n    // RFC 6749 §3.1.2.3 / RFC 6819 §4.1.5 - the redirect_uri MUST be one\n    // that was registered by this specific client. Skipping this check is\n    // CWE-601: an attacker can steal an authorization code by passing their\n    // own URL as redirect_uri.\n    if (!registeredClient.redirectUris.includes(params.redirect_uri)) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"redirect_uri is not registered for this client\",\n      );\n    }\n\n    // Validate PKCE if provided\n    if (params.code_challenge && !params.code_challenge_method) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"code_challenge_method required when code_challenge is present\",\n      );\n    }\n\n    // Create transaction\n    const transaction = await this.createTransaction(params);\n\n    // If consent required, show consent screen\n    if (this.config.consentRequired && !transaction.consentGiven) {\n      return this.consentManager.createConsentResponse(\n        transaction,\n        this.getProviderName(),\n      );\n    }\n\n    // Redirect to upstream provider\n    return this.redirectToUpstream(transaction);\n  }\n\n  /**\n   * Stop cleanup interval and destroy resources\n   */\n  destroy(): void {\n    if (this.cleanupInterval) {\n      clearInterval(this.cleanupInterval);\n      this.cleanupInterval = null;\n    }\n\n    this.registeredClientsByClientId.clear();\n  }\n\n  /**\n   * Token endpoint - exchange authorization code for tokens\n   */\n  async exchangeAuthorizationCode(\n    request: TokenRequest,\n  ): Promise<TokenResponse> {\n    if (request.grant_type !== \"authorization_code\") {\n      throw new OAuthProxyError(\n        \"unsupported_grant_type\",\n        \"Only authorization_code grant type is supported\",\n      );\n    }\n\n    // RFC 6749 §5.2 - reject unknown clients. Only proxy-issued client_ids\n    // (obtained via DCR) are accepted, so stolen codes cannot be exchanged by\n    // arbitrary callers.\n    const registeredClient =\n      await this.stateStore.getRegisteredClientByClientId(request.client_id);\n    if (!registeredClient) {\n      throw new OAuthProxyError(\"invalid_client\", \"Unknown client_id\");\n    }\n\n    // Consume the code atomically: whoever takes it owns this exchange, so two\n    // concurrent requests — on this instance or another one sharing the\n    // storage — cannot both redeem it (RFC 6749 §4.1.2).\n    const consumed = await this.stateStore.consumeClientCode(request.code);\n    if (!consumed) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Invalid or expired authorization code\",\n      );\n    }\n\n    // Put a tombstone back — whether the code was live or already spent — so\n    // later attempts get the precise \"already used\" error rather than looking\n    // like an unknown code. It replaces the record instead of amending it, so\n    // the upstream tokens stop being stored the moment they are handed over,\n    // and a spent code can never be resurrected.\n    await this.stateStore.markClientCodeSpent(request.code, consumed.expiresAt);\n\n    if (consumed.status === \"spent\") {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Authorization code already used\",\n      );\n    }\n\n    const clientCode = consumed.clientCode;\n\n    // Validate client\n    if (clientCode.clientId !== request.client_id) {\n      throw new OAuthProxyError(\"invalid_client\", \"Client ID mismatch\");\n    }\n\n    // Validate PKCE if used\n    if (clientCode.codeChallenge) {\n      if (!request.code_verifier) {\n        throw new OAuthProxyError(\n          \"invalid_request\",\n          \"code_verifier required for PKCE\",\n        );\n      }\n\n      const valid = PKCEUtils.validateChallenge(\n        request.code_verifier,\n        clientCode.codeChallenge,\n        clientCode.codeChallengeMethod,\n      );\n\n      if (!valid) {\n        throw new OAuthProxyError(\"invalid_grant\", \"Invalid PKCE verifier\");\n      }\n    }\n\n    // Return tokens based on token swap setting\n    if (this.config.enableTokenSwap && this.jwtIssuer) {\n      // Token swap pattern: issue short-lived JWTs and store upstream tokens\n      return await this.issueSwappedTokens(\n        clientCode.clientId,\n        clientCode.upstreamTokens,\n      );\n    } else {\n      // Pass-through pattern: return upstream tokens directly\n      const response: TokenResponse = {\n        access_token: clientCode.upstreamTokens.accessToken,\n        expires_in: clientCode.upstreamTokens.expiresIn,\n        token_type: clientCode.upstreamTokens.tokenType,\n      };\n\n      if (clientCode.upstreamTokens.refreshToken) {\n        response.refresh_token = clientCode.upstreamTokens.refreshToken;\n      }\n\n      if (clientCode.upstreamTokens.idToken) {\n        response.id_token = clientCode.upstreamTokens.idToken;\n      }\n\n      if (clientCode.upstreamTokens.scope.length > 0) {\n        response.scope = clientCode.upstreamTokens.scope.join(\" \");\n      }\n\n      return response;\n    }\n  }\n\n  /**\n   * Token endpoint - refresh access token\n   */\n  async exchangeRefreshToken(request: RefreshRequest): Promise<TokenResponse> {\n    if (request.grant_type !== \"refresh_token\") {\n      throw new OAuthProxyError(\n        \"unsupported_grant_type\",\n        \"Only refresh_token grant type is supported\",\n      );\n    }\n\n    // Check for swap mode\n    if (this.config.enableTokenSwap && this.jwtIssuer) {\n      return await this.handleSwapModeRefresh(request);\n    }\n\n    // Passthrough mode: forward refresh token directly to upstream\n    return await this.handlePassthroughRefresh(request);\n  }\n\n  /**\n   * Get OAuth discovery metadata\n   */\n  getAuthorizationServerMetadata(): {\n    authorizationEndpoint: string;\n    codeChallengeMethodsSupported?: string[];\n    dpopSigningAlgValuesSupported?: string[];\n    grantTypesSupported?: string[];\n    introspectionEndpoint?: string;\n    issuer: string;\n    jwksUri?: string;\n    opPolicyUri?: string;\n    opTosUri?: string;\n    registrationEndpoint?: string;\n    responseModesSupported?: string[];\n    responseTypesSupported: string[];\n    revocationEndpoint?: string;\n    scopesSupported?: string[];\n    serviceDocumentation?: string;\n    tokenEndpoint: string;\n    tokenEndpointAuthMethodsSupported?: string[];\n    tokenEndpointAuthSigningAlgValuesSupported?: string[];\n    uiLocalesSupported?: string[];\n  } {\n    return {\n      authorizationEndpoint: `${this.config.baseUrl}/oauth/authorize`,\n      codeChallengeMethodsSupported: [\"S256\", \"plain\"],\n      grantTypesSupported: [\"authorization_code\", \"refresh_token\"],\n      issuer: this.config.baseUrl,\n      registrationEndpoint: `${this.config.baseUrl}/oauth/register`,\n      responseTypesSupported: [\"code\"],\n      scopesSupported: this.config.scopes || [],\n      tokenEndpoint: `${this.config.baseUrl}/oauth/token`,\n      tokenEndpointAuthMethodsSupported: [\n        \"client_secret_basic\",\n        \"client_secret_post\",\n      ],\n    };\n  }\n\n  /**\n   * Handle OAuth callback from upstream provider\n   */\n  async handleCallback(request: Request): Promise<Response> {\n    const url = new URL(request.url);\n    const code = url.searchParams.get(\"code\");\n    const state = url.searchParams.get(\"state\");\n    const error = url.searchParams.get(\"error\");\n\n    // Check for errors from upstream\n    if (error) {\n      const errorDescription = url.searchParams.get(\"error_description\");\n      throw new OAuthProxyError(error, errorDescription || undefined);\n    }\n\n    if (!code || !state) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"Missing code or state parameter\",\n      );\n    }\n\n    // Consume the transaction: a callback is single-use, so taking it here\n    // prevents the same state from being replayed against this or any other\n    // instance sharing the storage.\n    const transaction = await this.stateStore.consumeTransaction(state);\n    if (!transaction) {\n      throw new OAuthProxyError(\"invalid_request\", \"Invalid or expired state\");\n    }\n\n    // Defense-in-depth: the transaction's stored callback URL must still be\n    // registered. Guards against any code path that could persist an\n    // unvalidated URI, and against registration being revoked mid-flow.\n    if (!(await this.stateStore.isTransactionCallbackRegistered(transaction))) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"Transaction callback URL is not registered\",\n      );\n    }\n\n    // Exchange code with upstream provider\n    const upstreamTokens = await this.exchangeUpstreamCode(code, transaction);\n\n    // Generate authorization code for client\n    const clientCode = await this.generateAuthorizationCode(\n      transaction,\n      upstreamTokens,\n    );\n\n    // Redirect to client callback with code\n    const redirectUrl = new URL(transaction.clientCallbackUrl);\n    redirectUrl.searchParams.set(\"code\", clientCode);\n    redirectUrl.searchParams.set(\"state\", transaction.state);\n\n    return new Response(null, {\n      headers: {\n        Location: redirectUrl.toString(),\n      },\n      status: 302,\n    });\n  }\n\n  /**\n   * Handle consent form submission\n   */\n  async handleConsent(request: Request): Promise<Response> {\n    const formData = await request.formData();\n    const transactionId = formData.get(\"transaction_id\");\n    const action = formData.get(\"action\");\n\n    if (typeof transactionId !== \"string\" || !transactionId) {\n      throw new OAuthProxyError(\"invalid_request\", \"Missing transaction_id\");\n    }\n\n    // Require the choice to be explicit. Anything else used to fall through to\n    // the approve branch, so a submission that lost the field — or never sent\n    // one — granted access the user never clicked for.\n    if (action !== \"approve\" && action !== \"deny\") {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"action must be 'approve' or 'deny'\",\n      );\n    }\n\n    const transaction = await this.stateStore.getTransaction(transactionId);\n    if (!transaction) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"Invalid or expired transaction\",\n      );\n    }\n\n    if (action === \"deny\") {\n      // User denied consent\n      await this.stateStore.deleteTransaction(transactionId);\n      // Defense-in-depth: never redirect to an unregistered URI.\n      if (\n        !(await this.stateStore.isTransactionCallbackRegistered(transaction))\n      ) {\n        throw new OAuthProxyError(\n          \"invalid_request\",\n          \"Transaction callback URL is not registered\",\n        );\n      }\n      const redirectUrl = new URL(transaction.clientCallbackUrl);\n      redirectUrl.searchParams.set(\"error\", \"access_denied\");\n      redirectUrl.searchParams.set(\n        \"error_description\",\n        \"User denied authorization\",\n      );\n      redirectUrl.searchParams.set(\"state\", transaction.state);\n\n      return new Response(null, {\n        headers: {\n          Location: redirectUrl.toString(),\n        },\n        status: 302,\n      });\n    }\n\n    // User approved, mark consent and redirect to upstream\n    const approvedTransaction = {\n      ...transaction,\n      consentGiven: true,\n    };\n    await this.stateStore.saveTransaction(approvedTransaction);\n\n    return this.redirectToUpstream(approvedTransaction);\n  }\n\n  /**\n   * Load upstream tokens from a FastMCP JWT\n   */\n  async loadUpstreamTokens(\n    fastmcpToken: string,\n  ): Promise<null | UpstreamTokenSet> {\n    if (!this.jwtIssuer) {\n      return null;\n    }\n\n    // Verify FastMCP JWT\n    const result = await this.jwtIssuer.verify(fastmcpToken);\n    if (!result.valid || !result.claims?.jti) {\n      return null;\n    }\n\n    // Look up token mapping\n    const mapping = (await this.tokenStorage.get(\n      `mapping:${result.claims.jti}`,\n    )) as {\n      upstreamTokenKey: string;\n    } | null;\n\n    if (!mapping) {\n      return null;\n    }\n\n    // Retrieve upstream tokens\n    const upstreamTokens = (await this.tokenStorage.get(\n      `upstream:${mapping.upstreamTokenKey}`,\n    )) as null | UpstreamTokenSet;\n\n    return upstreamTokens;\n  }\n\n  /**\n   * RFC 7591 Dynamic Client Registration\n   */\n  async registerClient(request: DCRRequest): Promise<DCRResponse> {\n    // Validate required fields\n    if (!request.redirect_uris || request.redirect_uris.length === 0) {\n      throw new OAuthProxyError(\n        \"invalid_client_metadata\",\n        \"redirect_uris is required\",\n      );\n    }\n\n    // Validate redirect URIs\n    for (const uri of request.redirect_uris) {\n      if (!this.validateRedirectUri(uri)) {\n        throw new OAuthProxyError(\n          \"invalid_redirect_uri\",\n          `Invalid redirect URI: ${uri}`,\n        );\n      }\n    }\n\n    // Generate proxy-specific credentials for this MCP client.\n    // We deliberately do NOT return the upstream provider's client_id/secret here:\n    // exposing those would (a) leak credentials to every MCP client and (b) let a\n    // client bypass the proxy and talk directly to the upstream provider.\n    const proxyClientId = randomBytes(16).toString(\"hex\");\n    const proxyClientSecret = randomBytes(32).toString(\"base64url\");\n\n    const client: ProxyDCRClient = {\n      callbackUrl: request.redirect_uris[0],\n      clientId: proxyClientId,\n      clientSecret: proxyClientSecret,\n      metadata: {\n        client_name: request.client_name,\n        client_uri: request.client_uri,\n        contacts: request.contacts,\n        jwks: request.jwks,\n        jwks_uri: request.jwks_uri,\n        logo_uri: request.logo_uri,\n        policy_uri: request.policy_uri,\n        scope: request.scope,\n        software_id: request.software_id,\n        software_version: request.software_version,\n        tos_uri: request.tos_uri,\n      },\n      redirectUris: request.redirect_uris,\n      registeredAt: new Date(),\n    };\n\n    this.stateStore.cacheRegisteredClient(client);\n    await this.stateStore.saveRegisteredClient(client);\n\n    // Return RFC 7591 compliant response with proxy-issued credentials.\n    const response: DCRResponse = {\n      client_id: proxyClientId,\n      client_id_issued_at: Math.floor(Date.now() / 1000),\n      // Echo back optional metadata\n      client_name: request.client_name,\n      client_secret: proxyClientSecret,\n      client_secret_expires_at: 0, // Never expires\n      client_uri: request.client_uri,\n      contacts: request.contacts,\n      grant_types: request.grant_types || [\n        \"authorization_code\",\n        \"refresh_token\",\n      ],\n      jwks: request.jwks,\n      jwks_uri: request.jwks_uri,\n      logo_uri: request.logo_uri,\n      policy_uri: request.policy_uri,\n      redirect_uris: request.redirect_uris,\n      response_types: request.response_types || [\"code\"],\n      scope: request.scope,\n      software_id: request.software_id,\n      software_version: request.software_version,\n      token_endpoint_auth_method:\n        request.token_endpoint_auth_method || \"client_secret_basic\",\n      tos_uri: request.tos_uri,\n    };\n\n    return response;\n  }\n\n  /**\n   * Calculate access token TTL from upstream tokens\n   */\n  private calculateAccessTokenTtl(upstreamTokens: UpstreamTokenSet): number {\n    if (upstreamTokens.expiresIn > 0) {\n      return upstreamTokens.expiresIn;\n    } else if (this.config.accessTokenTtl) {\n      return this.config.accessTokenTtl;\n    } else if (upstreamTokens.refreshToken) {\n      return DEFAULT_ACCESS_TOKEN_TTL;\n    } else {\n      return DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH;\n    }\n  }\n\n  /**\n   * Periodic maintenance hook. Expiry is enforced by the token storage.\n   */\n  private cleanup(): void {\n    // Transactions and codes live in the token storage with a TTL derived from\n    // their expiry, so sweeping them is the storage's job. Failures here must\n    // not become unhandled rejections: this runs on a timer, and a transient\n    // backend error would otherwise take the process down.\n    void this.tokenStorage.cleanup().catch((error: unknown) => {\n      console.error(\n        \"[FastMCP] OAuth proxy token storage cleanup failed:\",\n        error instanceof Error ? error.message : String(error),\n      );\n    });\n  }\n\n  /**\n   * Create a new OAuth transaction\n   */\n  private async createTransaction(\n    params: AuthorizationParams,\n  ): Promise<OAuthTransaction> {\n    const transactionId = this.generateId();\n    const proxyPkce = PKCEUtils.generatePair(\"S256\");\n\n    const transaction: OAuthTransaction = {\n      clientCallbackUrl: params.redirect_uri,\n      clientCodeChallenge: params.code_challenge || \"\",\n      clientCodeChallengeMethod: params.code_challenge_method || \"plain\",\n      clientId: params.client_id,\n      createdAt: new Date(),\n      expiresAt: new Date(\n        Date.now() + (this.config.transactionTtl || 600) * 1000,\n      ),\n      id: transactionId,\n      proxyCodeChallenge: proxyPkce.challenge,\n      proxyCodeVerifier: proxyPkce.verifier,\n      scope: params.scope ? params.scope.split(\" \") : this.config.scopes || [],\n      state: params.state || this.generateId(),\n    };\n\n    await this.stateStore.saveTransaction(transaction);\n\n    return transaction;\n  }\n\n  /**\n   * Exchange authorization code with upstream provider\n   */\n  private async exchangeUpstreamCode(\n    code: string,\n    transaction: OAuthTransaction,\n  ): Promise<UpstreamTokenSet> {\n    const useBasicAuth =\n      this.config.upstreamTokenEndpointAuthMethod === \"client_secret_basic\";\n\n    const bodyParams: Record<string, string> = {\n      code,\n      code_verifier: transaction.proxyCodeVerifier,\n      grant_type: \"authorization_code\",\n      redirect_uri: `${this.config.baseUrl}${this.config.redirectPath}`,\n    };\n\n    // Include client credentials in body only for client_secret_post\n    if (!useBasicAuth) {\n      bodyParams.client_id = this.config.upstreamClientId;\n      bodyParams.client_secret = this.config.upstreamClientSecret;\n    }\n\n    const headers: Record<string, string> = {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n    };\n\n    // Add Basic Auth header for client_secret_basic\n    if (useBasicAuth) {\n      headers[\"Authorization\"] = this.getBasicAuthHeader();\n    }\n\n    const tokenResponse = await fetch(this.config.upstreamTokenEndpoint, {\n      body: new URLSearchParams(bodyParams),\n      headers,\n      method: \"POST\",\n    });\n\n    if (!tokenResponse.ok) {\n      let errorCode = \"server_error\";\n      let errorDescription: string | undefined;\n      try {\n        const error = (await tokenResponse.json()) as {\n          error?: string;\n          error_description?: string;\n        };\n        errorCode = error.error || \"server_error\";\n        errorDescription = error.error_description;\n      } catch {\n        errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;\n      }\n      throw new OAuthProxyError(errorCode, errorDescription);\n    }\n\n    const tokens = await this.parseTokenResponse(tokenResponse);\n\n    return {\n      accessToken: tokens.access_token,\n      expiresIn: tokens.expires_in || 3600,\n      idToken: tokens.id_token,\n      issuedAt: new Date(),\n      refreshExpiresIn: tokens.refresh_expires_in,\n      refreshToken: tokens.refresh_token,\n      scope: tokens.scope ? tokens.scope.split(\" \") : transaction.scope,\n      tokenType: tokens.token_type || \"Bearer\",\n    };\n  }\n\n  /**\n   * Extract JTI from a JWT token\n   */\n  private async extractJti(token: string): Promise<string> {\n    if (!this.jwtIssuer) {\n      throw new Error(\"JWT issuer not initialized\");\n    }\n\n    const result = await this.jwtIssuer.verify(token);\n    if (!result.valid || !result.claims?.jti) {\n      throw new Error(\"Failed to extract JTI from token\");\n    }\n\n    return result.claims.jti;\n  }\n\n  /**\n   * Extract custom claims from upstream tokens\n   * Combines claims from access token and ID token (if present)\n   */\n  private async extractUpstreamClaims(\n    upstreamTokens: UpstreamTokenSet,\n  ): Promise<null | Record<string, unknown>> {\n    if (!this.claimsExtractor) {\n      return null;\n    }\n\n    const allClaims: Record<string, unknown> = {};\n\n    // Extract from access token (if JWT format)\n    const accessClaims = await this.claimsExtractor.extract(\n      upstreamTokens.accessToken,\n      \"access\",\n    );\n    if (accessClaims) {\n      Object.assign(allClaims, accessClaims);\n    }\n\n    // Extract from ID token (if present and JWT format)\n    if (upstreamTokens.idToken) {\n      const idClaims = await this.claimsExtractor.extract(\n        upstreamTokens.idToken,\n        \"id\",\n      );\n      if (idClaims) {\n        // Access token claims take precedence over ID token claims\n        for (const [key, value] of Object.entries(idClaims)) {\n          if (!(key in allClaims)) {\n            allClaims[key] = value;\n          }\n        }\n      }\n    }\n\n    return Object.keys(allClaims).length > 0 ? allClaims : null;\n  }\n\n  /**\n   * Generate authorization code for client\n   */\n  private async generateAuthorizationCode(\n    transaction: OAuthTransaction,\n    upstreamTokens: UpstreamTokenSet,\n  ): Promise<string> {\n    const code = this.generateId();\n\n    const clientCode: ClientCode = {\n      clientId: transaction.clientId,\n      code,\n      codeChallenge: transaction.clientCodeChallenge,\n      codeChallengeMethod: transaction.clientCodeChallengeMethod,\n      createdAt: new Date(),\n      expiresAt: new Date(\n        Date.now() + (this.config.authorizationCodeTtl || 300) * 1000,\n      ),\n      transactionId: transaction.id,\n      upstreamTokens,\n    };\n\n    await this.stateStore.saveClientCode(clientCode);\n\n    return code;\n  }\n\n  /**\n   * Generate secure random ID\n   */\n  private generateId(): string {\n    return randomBytes(32).toString(\"base64url\");\n  }\n\n  /**\n   * Generate signing key for consent cookies\n   */\n  private generateSigningKey(): string {\n    return randomBytes(32).toString(\"hex\");\n  }\n\n  /**\n   * Generate Basic auth header value for upstream token endpoint\n   * Per RFC 6749 Section 2.3.1, credentials must be URL-encoded before base64 encoding\n   */\n  private getBasicAuthHeader(): string {\n    const encodedClientId = encodeURIComponent(this.config.upstreamClientId);\n    const encodedClientSecret = encodeURIComponent(\n      this.config.upstreamClientSecret,\n    );\n    return `Basic ${Buffer.from(`${encodedClientId}:${encodedClientSecret}`).toString(\"base64\")}`;\n  }\n\n  /**\n   * Get provider name for display\n   */\n  private getProviderName(): string {\n    const url = new URL(this.config.upstreamAuthorizationEndpoint);\n    return url.hostname;\n  }\n\n  /**\n   * Handle passthrough mode refresh - forward refresh token directly to upstream\n   */\n  private async handlePassthroughRefresh(\n    request: RefreshRequest,\n  ): Promise<TokenResponse> {\n    const useBasicAuth =\n      this.config.upstreamTokenEndpointAuthMethod === \"client_secret_basic\";\n\n    const bodyParams: Record<string, string> = {\n      grant_type: \"refresh_token\",\n      refresh_token: request.refresh_token,\n      ...(request.scope && { scope: request.scope }),\n    };\n\n    // Include client credentials in body only for client_secret_post\n    if (!useBasicAuth) {\n      bodyParams.client_id = this.config.upstreamClientId;\n      bodyParams.client_secret = this.config.upstreamClientSecret;\n    }\n\n    const headers: Record<string, string> = {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n    };\n\n    // Add Basic Auth header for client_secret_basic\n    if (useBasicAuth) {\n      headers[\"Authorization\"] = this.getBasicAuthHeader();\n    }\n\n    // Exchange refresh token with upstream provider\n    const tokenResponse = await fetch(this.config.upstreamTokenEndpoint, {\n      body: new URLSearchParams(bodyParams),\n      headers,\n      method: \"POST\",\n    });\n\n    if (!tokenResponse.ok) {\n      let errorCode = \"invalid_grant\";\n      let errorDescription: string | undefined;\n      try {\n        const error = (await tokenResponse.json()) as {\n          error?: string;\n          error_description?: string;\n        };\n        errorCode = error.error || \"invalid_grant\";\n        errorDescription = error.error_description;\n      } catch {\n        errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;\n      }\n      throw new OAuthProxyError(errorCode, errorDescription);\n    }\n\n    const tokens = await this.parseTokenResponse(tokenResponse);\n\n    return {\n      access_token: tokens.access_token,\n      expires_in: tokens.expires_in || 3600,\n      id_token: tokens.id_token,\n      refresh_token: tokens.refresh_token,\n      scope: tokens.scope,\n      token_type: tokens.token_type || \"Bearer\",\n    };\n  }\n\n  /**\n   * Handle swap mode refresh - verify FastMCP JWT and issue new tokens\n   */\n  private async handleSwapModeRefresh(\n    request: RefreshRequest,\n  ): Promise<TokenResponse> {\n    if (!this.jwtIssuer) {\n      throw new Error(\"JWT issuer not initialized\");\n    }\n\n    const verifyResult = await this.jwtIssuer.verify(request.refresh_token);\n    if (!verifyResult.valid) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Invalid or expired refresh token\",\n      );\n    }\n\n    const jti = verifyResult.claims?.jti;\n    if (!jti) {\n      throw new OAuthProxyError(\"invalid_grant\", \"Refresh token missing JTI\");\n    }\n\n    const mapping = (await this.tokenStorage.get(`mapping:${jti}`)) as {\n      clientId: string;\n      scope: string[];\n      upstreamTokenKey: string;\n    } | null;\n\n    if (!mapping) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Refresh token already used or expired\",\n      );\n    }\n\n    const upstreamTokens = (await this.tokenStorage.get(\n      `upstream:${mapping.upstreamTokenKey}`,\n    )) as null | UpstreamTokenSet;\n\n    if (!upstreamTokens) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Upstream tokens not found or expired\",\n      );\n    }\n\n    if (!upstreamTokens.refreshToken) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"No upstream refresh token available\",\n      );\n    }\n\n    const refreshedUpstreamTokens = await this.refreshUpstreamTokens(\n      upstreamTokens.refreshToken,\n      request.scope,\n    );\n\n    if (refreshedUpstreamTokens.scope.length === 0) {\n      refreshedUpstreamTokens.scope = upstreamTokens.scope;\n    }\n\n    const refreshTokenTtl =\n      refreshedUpstreamTokens.refreshExpiresIn ??\n      this.config.refreshTokenTtl ??\n      DEFAULT_REFRESH_TOKEN_TTL;\n    const accessTokenTtl = this.calculateAccessTokenTtl(\n      refreshedUpstreamTokens,\n    );\n    const upstreamStorageTtl = Math.max(accessTokenTtl, refreshTokenTtl, 1);\n\n    await this.tokenStorage.save(\n      `upstream:${mapping.upstreamTokenKey}`,\n      refreshedUpstreamTokens,\n      upstreamStorageTtl,\n    );\n\n    return await this.issueSwappedTokensForRefresh(\n      mapping.clientId,\n      refreshedUpstreamTokens,\n      mapping.upstreamTokenKey,\n      jti,\n    );\n  }\n\n  /**\n   * Issue swapped tokens (JWT pattern)\n   * Issues short-lived FastMCP JWTs and stores upstream tokens securely\n   */\n  private async issueSwappedTokens(\n    clientId: string,\n    upstreamTokens: UpstreamTokenSet,\n  ): Promise<TokenResponse> {\n    if (!this.jwtIssuer) {\n      throw new Error(\"JWT issuer not initialized\");\n    }\n\n    // Extract custom claims from upstream tokens\n    const customClaims = await this.extractUpstreamClaims(upstreamTokens);\n\n    // Determine access token TTL (hierarchical: upstream → config → default)\n    let accessTokenTtl: number;\n    if (upstreamTokens.expiresIn > 0) {\n      accessTokenTtl = upstreamTokens.expiresIn;\n    } else if (this.config.accessTokenTtl) {\n      accessTokenTtl = this.config.accessTokenTtl;\n    } else if (upstreamTokens.refreshToken) {\n      accessTokenTtl = DEFAULT_ACCESS_TOKEN_TTL;\n    } else {\n      accessTokenTtl = DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH;\n    }\n\n    // Determine refresh token TTL early (needed for upstream storage TTL)\n    // Use upstream's refresh_expires_in if provided, otherwise fall back to config/default\n    const refreshTokenTtl = upstreamTokens.refreshToken\n      ? (upstreamTokens.refreshExpiresIn ??\n        this.config.refreshTokenTtl ??\n        DEFAULT_REFRESH_TOKEN_TTL)\n      : 0;\n\n    // Store upstream tokens with longest-lived token TTL (min 1s for safety)\n    const upstreamStorageTtl = Math.max(accessTokenTtl, refreshTokenTtl, 1);\n    const upstreamTokenKey = this.generateId();\n    await this.tokenStorage.save(\n      `upstream:${upstreamTokenKey}`,\n      upstreamTokens,\n      upstreamStorageTtl,\n    );\n\n    // Issue FastMCP access token with custom claims\n    const accessToken = this.jwtIssuer.issueAccessToken(\n      clientId,\n      upstreamTokens.scope,\n      customClaims || undefined,\n      accessTokenTtl,\n    );\n\n    // Decode JWT to get JTI\n    const accessJti = await this.extractJti(accessToken);\n\n    // Store token mapping\n    await this.tokenStorage.save(\n      `mapping:${accessJti}`,\n      {\n        clientId,\n        createdAt: new Date(),\n        expiresAt: new Date(Date.now() + accessTokenTtl * 1000),\n        jti: accessJti,\n        scope: upstreamTokens.scope,\n        upstreamTokenKey,\n      },\n      accessTokenTtl,\n    );\n\n    const response: TokenResponse = {\n      access_token: accessToken,\n      expires_in: accessTokenTtl,\n      scope: upstreamTokens.scope.join(\" \"),\n      token_type: \"Bearer\",\n    };\n\n    // Issue refresh token if upstream provided one\n    if (upstreamTokens.refreshToken) {\n      const refreshToken = this.jwtIssuer.issueRefreshToken(\n        clientId,\n        upstreamTokens.scope,\n        customClaims || undefined,\n        refreshTokenTtl,\n      );\n      const refreshJti = await this.extractJti(refreshToken);\n\n      // Store refresh token mapping\n      await this.tokenStorage.save(\n        `mapping:${refreshJti}`,\n        {\n          clientId,\n          createdAt: new Date(),\n          expiresAt: new Date(Date.now() + refreshTokenTtl * 1000),\n          jti: refreshJti,\n          scope: upstreamTokens.scope,\n          upstreamTokenKey,\n        },\n        refreshTokenTtl,\n      );\n\n      response.refresh_token = refreshToken;\n    }\n\n    return response;\n  }\n\n  /**\n   * Issue swapped tokens for refresh flow\n   */\n  private async issueSwappedTokensForRefresh(\n    clientId: string,\n    upstreamTokens: UpstreamTokenSet,\n    upstreamTokenKey: string,\n    oldJti: string,\n  ): Promise<TokenResponse> {\n    if (!this.jwtIssuer) {\n      throw new Error(\"JWT issuer not initialized\");\n    }\n\n    await this.tokenStorage.delete(`mapping:${oldJti}`);\n\n    const customClaims = await this.extractUpstreamClaims(upstreamTokens);\n\n    const accessTokenTtl = this.calculateAccessTokenTtl(upstreamTokens);\n    const refreshTokenTtl = upstreamTokens.refreshToken\n      ? (upstreamTokens.refreshExpiresIn ??\n        this.config.refreshTokenTtl ??\n        DEFAULT_REFRESH_TOKEN_TTL)\n      : 0;\n\n    const accessToken = this.jwtIssuer.issueAccessToken(\n      clientId,\n      upstreamTokens.scope,\n      customClaims || undefined,\n      accessTokenTtl,\n    );\n\n    const accessJti = await this.extractJti(accessToken);\n    await this.tokenStorage.save(\n      `mapping:${accessJti}`,\n      {\n        clientId,\n        createdAt: new Date(),\n        expiresAt: new Date(Date.now() + accessTokenTtl * 1000),\n        jti: accessJti,\n        scope: upstreamTokens.scope,\n        upstreamTokenKey,\n      },\n      accessTokenTtl,\n    );\n\n    const response: TokenResponse = {\n      access_token: accessToken,\n      expires_in: accessTokenTtl,\n      scope: upstreamTokens.scope.join(\" \"),\n      token_type: \"Bearer\",\n    };\n\n    if (upstreamTokens.refreshToken) {\n      const refreshToken = this.jwtIssuer.issueRefreshToken(\n        clientId,\n        upstreamTokens.scope,\n        customClaims || undefined,\n        refreshTokenTtl,\n      );\n      const refreshJti = await this.extractJti(refreshToken);\n\n      await this.tokenStorage.save(\n        `mapping:${refreshJti}`,\n        {\n          clientId,\n          createdAt: new Date(),\n          expiresAt: new Date(Date.now() + refreshTokenTtl * 1000),\n          jti: refreshJti,\n          scope: upstreamTokens.scope,\n          upstreamTokenKey,\n        },\n        refreshTokenTtl,\n      );\n\n      response.refresh_token = refreshToken;\n    }\n\n    return response;\n  }\n\n  /**\n   * Match URI against pattern (supports wildcards)\n   */\n  private matchesPattern(uri: string, pattern: string): boolean {\n    const regex = new RegExp(\n      \"^\" + pattern.replace(/\\*/g, \".*\").replace(/\\?/g, \".\") + \"$\",\n    );\n    return regex.test(uri);\n  }\n\n  /**\n   * Parse token response that can be either JSON or URL-encoded\n   * GitHub Apps return URL-encoded format, most providers return JSON\n   */\n  private async parseTokenResponse(response: Response): Promise<{\n    access_token: string;\n    expires_in?: number;\n    id_token?: string;\n    refresh_expires_in?: number;\n    refresh_token?: string;\n    scope?: string;\n    token_type?: string;\n  }> {\n    const contentType = (\n      response.headers.get(\"content-type\") || \"\"\n    ).toLowerCase();\n\n    // Define Zod schema for token response validation\n    const tokenResponseSchema = z.object({\n      access_token: z.string().min(1, \"access_token cannot be empty\"),\n      expires_in: z.coerce.number().int().positive().optional(),\n      id_token: z.string().optional(),\n      refresh_expires_in: z.coerce.number().int().positive().optional(),\n      refresh_token: z.string().optional(),\n      scope: z.string().optional(),\n      token_type: z.string().optional(),\n    });\n\n    // Check if response is URL-encoded (e.g., GitHub Apps)\n    if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n      const text = await response.text();\n      const params = new URLSearchParams(text);\n\n      const rawData = {\n        access_token: params.get(\"access_token\") || \"\",\n        expires_in: params.get(\"expires_in\")\n          ? parseInt(params.get(\"expires_in\")!)\n          : undefined,\n        id_token: params.get(\"id_token\") || undefined,\n        refresh_expires_in: params.get(\"refresh_expires_in\")\n          ? parseInt(params.get(\"refresh_expires_in\")!)\n          : undefined,\n        refresh_token: params.get(\"refresh_token\") || undefined,\n        scope: params.get(\"scope\") || undefined,\n        token_type: params.get(\"token_type\") || undefined,\n      };\n\n      return tokenResponseSchema.parse(rawData);\n    }\n\n    // Default to JSON parsing\n    const rawJson = await response.json();\n    return tokenResponseSchema.parse(rawJson);\n  }\n\n  /**\n   * Redirect to upstream OAuth provider\n   */\n  private redirectToUpstream(transaction: OAuthTransaction): Response {\n    const authUrl = new URL(this.config.upstreamAuthorizationEndpoint);\n\n    // Provider-specific extras (e.g. Google's access_type=offline) go first\n    // so the proxy-controlled core parameters below always win on conflict.\n    if (this.config.extraAuthorizationParams) {\n      for (const [key, value] of Object.entries(\n        this.config.extraAuthorizationParams,\n      )) {\n        if (!RESERVED_AUTHORIZATION_PARAMS.has(key)) {\n          authUrl.searchParams.set(key, value);\n        }\n      }\n    }\n\n    authUrl.searchParams.set(\"client_id\", this.config.upstreamClientId);\n    authUrl.searchParams.set(\n      \"redirect_uri\",\n      `${this.config.baseUrl}${this.config.redirectPath}`,\n    );\n    authUrl.searchParams.set(\"response_type\", \"code\");\n    authUrl.searchParams.set(\"state\", transaction.id);\n\n    if (transaction.scope.length > 0) {\n      authUrl.searchParams.set(\"scope\", transaction.scope.join(\" \"));\n    }\n\n    // Add PKCE if not forwarding client PKCE\n    if (!this.config.forwardPkce) {\n      authUrl.searchParams.set(\n        \"code_challenge\",\n        transaction.proxyCodeChallenge,\n      );\n      authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n    }\n\n    return new Response(null, {\n      headers: {\n        Location: authUrl.toString(),\n      },\n      status: 302,\n    });\n  }\n\n  /**\n   * Refresh upstream tokens with provider\n   */\n  private async refreshUpstreamTokens(\n    upstreamRefreshToken: string,\n    requestedScope?: string,\n  ): Promise<UpstreamTokenSet> {\n    const useBasicAuth =\n      this.config.upstreamTokenEndpointAuthMethod === \"client_secret_basic\";\n\n    const bodyParams: Record<string, string> = {\n      grant_type: \"refresh_token\",\n      refresh_token: upstreamRefreshToken,\n      ...(requestedScope && { scope: requestedScope }),\n    };\n\n    // Include client credentials in body only for client_secret_post\n    if (!useBasicAuth) {\n      bodyParams.client_id = this.config.upstreamClientId;\n      bodyParams.client_secret = this.config.upstreamClientSecret;\n    }\n\n    const headers: Record<string, string> = {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n    };\n\n    // Add Basic Auth header for client_secret_basic\n    if (useBasicAuth) {\n      headers[\"Authorization\"] = this.getBasicAuthHeader();\n    }\n\n    // Exchange refresh token with upstream provider\n    const tokenResponse = await fetch(this.config.upstreamTokenEndpoint, {\n      body: new URLSearchParams(bodyParams),\n      headers,\n      method: \"POST\",\n    });\n\n    if (!tokenResponse.ok) {\n      let errorCode = \"invalid_grant\";\n      let errorDescription: string | undefined = \"Upstream refresh failed\";\n      try {\n        const error = (await tokenResponse.json()) as {\n          error?: string;\n          error_description?: string;\n        };\n        errorCode = error.error || \"invalid_grant\";\n        errorDescription = error.error_description || \"Upstream refresh failed\";\n      } catch {\n        errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;\n      }\n      throw new OAuthProxyError(errorCode, errorDescription);\n    }\n\n    const tokens = await this.parseTokenResponse(tokenResponse);\n\n    // Handle token rotation: if upstream doesn't return new refresh token,\n    // preserve the original one\n    return {\n      accessToken: tokens.access_token,\n      expiresIn: tokens.expires_in || 3600,\n      idToken: tokens.id_token,\n      issuedAt: new Date(),\n      refreshExpiresIn: tokens.refresh_expires_in,\n      refreshToken: tokens.refresh_token || upstreamRefreshToken,\n      scope: tokens.scope ? tokens.scope.split(\" \") : [],\n      tokenType: tokens.token_type || \"Bearer\",\n    };\n  }\n\n  /**\n   * Start periodic cleanup of expired transactions and codes\n   */\n  private startCleanup(): void {\n    this.cleanupInterval = setInterval(() => {\n      this.cleanup();\n    }, 60000); // Run every minute\n  }\n\n  /**\n   * Validate a redirect URI against the configured allow-list.\n   *\n   * Behaviour by configuration value:\n   *   - `undefined` (not set): allow localhost/127.0.0.1 only — safe default\n   *     that covers the common MCP use-case of dynamic loopback ports without\n   *     opening the proxy to arbitrary redirect URIs.\n   *   - `[]` (empty array): reject every URI — opt-in strict mode for deployments\n   *     that want full control and will configure patterns explicitly.\n   *   - `[\"pattern\", ...]`: accept URIs matching any of the glob patterns.\n   *\n   * Prior versions defaulted to `[\"https://*\", \"http://localhost:*\"]` which\n   * matched any https URL, enabling CWE-601 open-redirect / authorization-code\n   * theft. Do not loosen the default beyond loopback addresses.\n   */\n  private validateRedirectUri(uri: string): boolean {\n    try {\n      new URL(uri); // syntactic check only — throws on malformed input\n    } catch {\n      return false;\n    }\n\n    const patterns = this.config.allowedRedirectUriPatterns;\n\n    // Explicitly set to empty array → strict mode, reject everything.\n    if (Array.isArray(patterns) && patterns.length === 0) {\n      return false;\n    }\n\n    // Not configured → localhost-only default (covers MCP dynamic loopback ports).\n    const effectivePatterns = patterns ?? [\n      \"http://localhost:*\",\n      \"http://127.0.0.1:*\",\n    ];\n\n    return effectivePatterns.some((pattern) =>\n      this.matchesPattern(uri, pattern),\n    );\n  }\n}\n\n/**\n * OAuth Proxy Error\n */\nexport class OAuthProxyError extends Error {\n  constructor(\n    public code: string,\n    public description?: string,\n    public statusCode: number = 400,\n  ) {\n    super(code);\n    this.name = \"OAuthProxyError\";\n  }\n\n  toJSON(): OAuthError {\n    return {\n      error: this.code,\n      error_description: this.description,\n    };\n  }\n\n  toResponse(): Response {\n    return new Response(JSON.stringify(this.toJSON()), {\n      headers: { \"Content-Type\": \"application/json\" },\n      status: this.statusCode,\n    });\n  }\n}\n","import { z } from \"zod\";\n\nimport type {\n  ClientCode,\n  OAuthTransaction,\n  ProxyDCRClient,\n  TokenStorage,\n  UpstreamTokenSet,\n} from \"./types.js\";\n\nconst STORAGE_KEY_PREFIX = {\n  client: \"client:\",\n  code: \"code:\",\n  transaction: \"transaction:\",\n} as const;\n\nconst storedDateSchema = z\n  .union([z.date(), z.string()])\n  .transform((value, context) => {\n    const date = value instanceof Date ? value : new Date(value);\n\n    if (Number.isNaN(date.getTime())) {\n      context.addIssue({\n        code: \"custom\",\n        message: \"Invalid stored date\",\n      });\n      return z.NEVER;\n    }\n\n    return date;\n  });\n\nconst dcrClientMetadataSchema = z.object({\n  client_name: z.string().optional(),\n  client_uri: z.string().optional(),\n  contacts: z.array(z.string()).optional(),\n  jwks: z.record(z.string(), z.unknown()).optional(),\n  jwks_uri: z.string().optional(),\n  logo_uri: z.string().optional(),\n  policy_uri: z.string().optional(),\n  scope: z.string().optional(),\n  software_id: z.string().optional(),\n  software_version: z.string().optional(),\n  tos_uri: z.string().optional(),\n});\n\nconst proxyDcrClientStorageSchema: z.ZodType<ProxyDCRClient> = z.object({\n  callbackUrl: z.string(),\n  clientId: z.string(),\n  clientSecret: z.string().optional(),\n  metadata: dcrClientMetadataSchema.optional(),\n  redirectUris: z.array(z.string()),\n  registeredAt: storedDateSchema,\n});\n\nconst upstreamTokenSetStorageSchema: z.ZodType<UpstreamTokenSet> = z.object({\n  accessToken: z.string(),\n  expiresIn: z.number(),\n  idToken: z.string().optional(),\n  issuedAt: storedDateSchema,\n  refreshExpiresIn: z.number().optional(),\n  refreshToken: z.string().optional(),\n  scope: z.array(z.string()),\n  tokenType: z.string(),\n});\n\nconst clientCodeStorageSchema: z.ZodType<ClientCode> = z.object({\n  clientId: z.string(),\n  code: z.string(),\n  codeChallenge: z.string(),\n  codeChallengeMethod: z.string(),\n  createdAt: storedDateSchema,\n  expiresAt: storedDateSchema,\n  transactionId: z.string(),\n  upstreamTokens: upstreamTokenSetStorageSchema,\n  used: z.boolean().optional(),\n});\n\n/**\n * What is left behind once a code has been redeemed. Deliberately not a\n * `ClientCode`: the tombstone carries only what a later attempt needs, so the\n * upstream tokens do not linger in storage after they have been handed over.\n *\n * A full record written by an older version had `used: true` set on it, and\n * still matches this shape, so those are read back as spent too.\n */\nconst spentClientCodeStorageSchema = z.object({\n  expiresAt: storedDateSchema,\n  used: z.literal(true),\n});\n\nconst oauthTransactionStorageSchema: z.ZodType<OAuthTransaction> = z.object({\n  clientCallbackUrl: z.string(),\n  clientCodeChallenge: z.string(),\n  clientCodeChallengeMethod: z.string(),\n  clientId: z.string(),\n  consentGiven: z.boolean().optional(),\n  createdAt: storedDateSchema,\n  expiresAt: storedDateSchema,\n  id: z.string(),\n  metadata: z.record(z.string(), z.unknown()).optional(),\n  proxyCodeChallenge: z.string(),\n  proxyCodeVerifier: z.string(),\n  scope: z.array(z.string()),\n  state: z.string(),\n});\n\n/**\n * The result of taking an authorization code out of storage. Distinct from the\n * `null` that `consumeClientCode` returns for a code that was never issued or\n * has expired: `\"spent\"` means the code existed and a previous exchange\n * already redeemed it, which the caller reports differently.\n */\nexport type ConsumedClientCode =\n  | { clientCode: ClientCode; expiresAt: Date; status: \"active\" }\n  | { expiresAt: Date; status: \"spent\" };\n\ninterface OAuthProxyStateStoreConfig {\n  readonly registeredClientsByClientId: Map<string, ProxyDCRClient>;\n  readonly tokenStorage: TokenStorage;\n}\n\n/**\n * Backs OAuth proxy state with the configured TokenStorage so that several\n * proxy instances can serve one flow.\n *\n * Client registrations are immutable once written, so they are cached locally.\n * Transactions and authorization codes are *not* cached: they are single-use\n * and mutated by whichever instance handles the next leg of the flow, so a\n * local copy would go stale and let a consumed code or transaction be redeemed\n * a second time.\n */\nexport class OAuthProxyStateStore {\n  private readonly registeredClientsByClientId: Map<string, ProxyDCRClient>;\n  private readonly tokenStorage: TokenStorage;\n\n  constructor(config: OAuthProxyStateStoreConfig) {\n    this.registeredClientsByClientId = config.registeredClientsByClientId;\n    this.tokenStorage = config.tokenStorage;\n  }\n\n  cacheRegisteredClient(client: ProxyDCRClient): void {\n    this.registeredClientsByClientId.set(client.clientId, client);\n  }\n\n  /**\n   * Atomically consume an authorization code. At most one caller — across all\n   * processes sharing the storage — can receive a given code, which is what\n   * makes single use enforceable (RFC 6749 §4.1.2).\n   *\n   * Spent codes are taken too, so the caller must write the tombstone back\n   * with `markClientCodeSpent` to keep reporting the precise error on the\n   * attempt after this one.\n   */\n  async consumeClientCode(code: string): Promise<ConsumedClientCode | null> {\n    const stored = await this.takeFromStorage(\n      `${STORAGE_KEY_PREFIX.code}${code}`,\n    );\n\n    const spent = spentClientCodeStorageSchema.safeParse(stored);\n\n    if (spent.success) {\n      return this.isExpired(spent.data.expiresAt)\n        ? null\n        : { expiresAt: spent.data.expiresAt, status: \"spent\" };\n    }\n\n    const parsed = clientCodeStorageSchema.safeParse(stored);\n\n    if (!parsed.success || this.isExpired(parsed.data.expiresAt)) {\n      return null;\n    }\n\n    return {\n      clientCode: parsed.data,\n      expiresAt: parsed.data.expiresAt,\n      status: \"active\",\n    };\n  }\n\n  /**\n   * Atomically consume a transaction, so a callback can only be redeemed once\n   * no matter which instance it lands on.\n   */\n  async consumeTransaction(\n    transactionId: string,\n  ): Promise<null | OAuthTransaction> {\n    const stored = await this.takeFromStorage(\n      `${STORAGE_KEY_PREFIX.transaction}${transactionId}`,\n    );\n    const parsed = oauthTransactionStorageSchema.safeParse(stored);\n\n    if (!parsed.success || this.isExpired(parsed.data.expiresAt)) {\n      return null;\n    }\n\n    return parsed.data;\n  }\n\n  async deleteTransaction(transactionId: string): Promise<void> {\n    await this.tokenStorage.delete(\n      `${STORAGE_KEY_PREFIX.transaction}${transactionId}`,\n    );\n  }\n\n  async getRegisteredClientByClientId(\n    clientId: string,\n  ): Promise<null | ProxyDCRClient> {\n    const cached = this.registeredClientsByClientId.get(clientId);\n    if (cached) {\n      return cached;\n    }\n\n    const stored = await this.tokenStorage.get(\n      `${STORAGE_KEY_PREFIX.client}${clientId}`,\n    );\n    const parsed = proxyDcrClientStorageSchema.safeParse(stored);\n\n    if (!parsed.success) {\n      return null;\n    }\n\n    this.cacheRegisteredClient(parsed.data);\n    return parsed.data;\n  }\n\n  /**\n   * Read a transaction without consuming it. Used by the consent screen, which\n   * has to hand the same transaction back to the upstream redirect.\n   */\n  async getTransaction(\n    transactionId: string,\n  ): Promise<null | OAuthTransaction> {\n    const stored = await this.tokenStorage.get(\n      `${STORAGE_KEY_PREFIX.transaction}${transactionId}`,\n    );\n    const parsed = oauthTransactionStorageSchema.safeParse(stored);\n\n    if (!parsed.success) {\n      return null;\n    }\n\n    if (this.isExpired(parsed.data.expiresAt)) {\n      await this.deleteTransaction(transactionId);\n      return null;\n    }\n\n    return parsed.data;\n  }\n\n  async isTransactionCallbackRegistered(\n    transaction: OAuthTransaction,\n  ): Promise<boolean> {\n    const registeredClient = await this.getRegisteredClientByClientId(\n      transaction.clientId,\n    );\n\n    return (\n      registeredClient?.redirectUris.includes(transaction.clientCallbackUrl) ??\n      false\n    );\n  }\n\n  /**\n   * Record that an authorization code has been redeemed, so a later attempt\n   * gets \"already used\" rather than looking like an unknown code. Keyed and\n   * expiring exactly like the code it replaces, so it cannot outlive it.\n   */\n  async markClientCodeSpent(code: string, expiresAt: Date): Promise<void> {\n    await this.tokenStorage.save(\n      `${STORAGE_KEY_PREFIX.code}${code}`,\n      { expiresAt, used: true },\n      this.getTtlSeconds(expiresAt),\n    );\n  }\n\n  async saveClientCode(clientCode: ClientCode): Promise<void> {\n    await this.tokenStorage.save(\n      `${STORAGE_KEY_PREFIX.code}${clientCode.code}`,\n      clientCode,\n      this.getTtlSeconds(clientCode.expiresAt),\n    );\n  }\n\n  async saveRegisteredClient(client: ProxyDCRClient): Promise<void> {\n    await this.tokenStorage.save(\n      `${STORAGE_KEY_PREFIX.client}${client.clientId}`,\n      client,\n    );\n  }\n\n  async saveTransaction(transaction: OAuthTransaction): Promise<void> {\n    await this.tokenStorage.save(\n      `${STORAGE_KEY_PREFIX.transaction}${transaction.id}`,\n      transaction,\n      this.getTtlSeconds(transaction.expiresAt),\n    );\n  }\n\n  private getTtlSeconds(expiresAt: Date): number {\n    const millisecondsUntilExpiry = expiresAt.getTime() - Date.now();\n    return Math.max(Math.ceil(millisecondsUntilExpiry / 1000), 1);\n  }\n\n  private isExpired(expiresAt: Date): boolean {\n    return expiresAt.getTime() < Date.now();\n  }\n\n  /**\n   * Prefer the storage's atomic take. Falling back to get + delete leaves a\n   * window in which two processes can both observe the same value, so\n   * multi-process deployments should implement `TokenStorage.take`.\n   */\n  private async takeFromStorage(key: string): Promise<null | unknown> {\n    if (this.tokenStorage.take) {\n      return this.tokenStorage.take(key);\n    }\n\n    const stored = await this.tokenStorage.get(key);\n\n    if (stored === null) {\n      return null;\n    }\n\n    await this.tokenStorage.delete(key);\n\n    return stored;\n  }\n}\n","/**\n * OAuth Proxy Types\n * Type definitions for the OAuth 2.1 Proxy implementation\n */\n\n/**\n * Default TTL values for token expiration (in seconds)\n */\nexport const DEFAULT_ACCESS_TOKEN_TTL = 3600; // 1 hour\nexport const DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = 31536000; // 1 year\nexport const DEFAULT_REFRESH_TOKEN_TTL = 2592000; // 30 days\nexport const DEFAULT_AUTHORIZATION_CODE_TTL = 300; // 5 minutes\nexport const DEFAULT_TRANSACTION_TTL = 600; // 10 minutes\n\n/**\n * OAuth authorization request parameters\n */\nexport interface AuthorizationParams {\n  [key: string]: unknown;\n  client_id: string;\n  code_challenge?: string;\n  code_challenge_method?: string;\n  redirect_uri: string;\n  response_type: string;\n  scope?: string;\n  state?: string;\n}\n\n/**\n * Authorization code storage with PKCE validation\n */\nexport interface ClientCode {\n  /** Client ID that owns this code */\n  clientId: string;\n  /** Authorization code */\n  code: string;\n  /** PKCE code challenge for validation */\n  codeChallenge: string;\n  /** PKCE code challenge method */\n  codeChallengeMethod: string;\n  /** Code creation timestamp */\n  createdAt: Date;\n  /** Code expiration timestamp */\n  expiresAt: Date;\n  /** Associated transaction ID */\n  transactionId: string;\n  /** Upstream tokens obtained from provider */\n  upstreamTokens: UpstreamTokenSet;\n  /** Whether code has been used */\n  used?: boolean;\n}\n\n/**\n * Consent data for user approval\n */\nexport interface ConsentData {\n  clientName: string;\n  provider: string;\n  scope: string[];\n  timestamp: number;\n  transactionId: string;\n}\n\n/**\n * Custom claims passthrough configuration\n */\nexport interface CustomClaimsPassthroughConfig {\n  /** Allow nested objects/arrays in claims. Default: false (only primitives) */\n  allowComplexClaims?: boolean;\n\n  /** Only passthrough these specific claims (allowlist). Default: undefined (allow all non-protected) */\n  allowedClaims?: string[];\n\n  /** Never passthrough these claims (blocklist, in addition to protected claims). Default: [] */\n  blockedClaims?: string[];\n\n  /** Prefix upstream claims to prevent collisions. Default: false (no prefix) */\n  claimPrefix?: false | string;\n\n  /** Enable passthrough from upstream access token (if JWT format). Default: true */\n  fromAccessToken?: boolean;\n\n  /** Enable passthrough from upstream ID token. Default: true */\n  fromIdToken?: boolean;\n\n  /** Maximum length for claim values. Default: 2000 */\n  maxClaimValueSize?: number;\n}\n\n/**\n * Client metadata for storage\n */\nexport interface DCRClientMetadata {\n  client_name?: string;\n  client_uri?: string;\n  contacts?: string[];\n  jwks?: Record<string, unknown>;\n  jwks_uri?: string;\n  logo_uri?: string;\n  policy_uri?: string;\n  scope?: string;\n  software_id?: string;\n  software_version?: string;\n  tos_uri?: string;\n}\n\n/**\n * RFC 7591 Dynamic Client Registration Request\n */\nexport interface DCRRequest {\n  /** Client name */\n  client_name?: string;\n  /** Client homepage URL */\n  client_uri?: string;\n  /** Contact email addresses */\n  contacts?: string[];\n  /** Allowed grant types */\n  grant_types?: string[];\n  /** JWKS object */\n  jwks?: Record<string, unknown>;\n  /** JWKS URI */\n  jwks_uri?: string;\n  /** Client logo URL */\n  logo_uri?: string;\n  /** Privacy policy URL */\n  policy_uri?: string;\n  /** REQUIRED: Array of redirect URIs */\n  redirect_uris: string[];\n  /** Allowed response types */\n  response_types?: string[];\n  /** Requested scope */\n  scope?: string;\n  /** Software identifier */\n  software_id?: string;\n  /** Software version */\n  software_version?: string;\n  /** Token endpoint authentication method */\n  token_endpoint_auth_method?: string;\n  /** Terms of service URL */\n  tos_uri?: string;\n}\n\n/**\n * RFC 7591 Dynamic Client Registration Response\n */\nexport interface DCRResponse {\n  /** REQUIRED: Client identifier */\n  client_id: string;\n  /** Client ID issued timestamp */\n  client_id_issued_at?: number;\n  client_name?: string;\n  /** Client secret */\n  client_secret?: string;\n  /** Client secret expiration (0 = never) */\n  client_secret_expires_at?: number;\n  client_uri?: string;\n  contacts?: string[];\n  grant_types?: string[];\n  jwks?: Record<string, unknown>;\n  jwks_uri?: string;\n  logo_uri?: string;\n  policy_uri?: string;\n  /** Echo back all registered metadata */\n  redirect_uris: string[];\n  /** Registration access token */\n  registration_access_token?: string;\n  /** Registration client URI */\n  registration_client_uri?: string;\n  response_types?: string[];\n  scope?: string;\n  software_id?: string;\n  software_version?: string;\n  token_endpoint_auth_method?: string;\n  tos_uri?: string;\n}\n\n/**\n * OAuth error response\n */\nexport interface OAuthError {\n  error: string;\n  error_description?: string;\n  error_uri?: string;\n}\n\n/**\n * OAuth Proxy provider for pre-configured providers\n */\nexport interface OAuthProviderConfig {\n  baseUrl: string;\n  clientId: string;\n  clientSecret: string;\n  consentRequired?: boolean;\n  scopes?: string[];\n}\n\n/**\n * Configuration for the OAuth Proxy\n */\nexport interface OAuthProxyConfig {\n  /** Access token TTL in seconds (default: 3600) */\n  accessTokenTtl?: number;\n  /**\n   * Allow-list of redirect URI patterns accepted by Dynamic Client Registration.\n   *\n   * A client calling POST /oauth/register must present a `redirect_uri` that\n   * matches one of these patterns (exact string or glob with `*` / `?`);\n   * otherwise the registration is rejected with `invalid_redirect_uri`. Once\n   * registered, the same exact URI must be echoed back at /oauth/authorize —\n   * the proxy performs an exact per-client match per RFC 6749 §3.1.2.3.\n   *\n   * Behaviour by value:\n   *   - `undefined` (default): allow `http://localhost:*` and `http://127.0.0.1:*`\n   *     only. Covers the standard MCP use-case of dynamic loopback ports.\n   *   - `[]` (empty array): DCR rejects every URI — use for deployments that\n   *     configure patterns explicitly and want no implicit fallback.\n   *   - `[\"pattern\", ...]`: accept URIs matching any glob pattern in the list.\n   *\n   * Do not widen the default beyond loopback addresses — allowing arbitrary\n   * https URLs enables CWE-601 open-redirect / authorization-code theft.\n   */\n  allowedRedirectUriPatterns?: string[];\n  /** Authorization code TTL in seconds (default: 300) */\n  authorizationCodeTtl?: number;\n  /** Base URL of this proxy server */\n  baseUrl: string;\n  /** Require user consent (default: true) */\n  consentRequired?: boolean;\n  /** Secret key for signing consent cookies */\n  consentSigningKey?: string;\n  /**\n   * Custom claims passthrough configuration.\n   * When enabled (default), extracts custom claims from upstream access token and ID token\n   * and includes them in the proxy's issued JWT tokens.\n   * This enables authorization based on upstream roles, permissions, etc.\n   * Set to false to disable claims passthrough entirely.\n   * Default: true (enabled with default settings)\n   */\n  customClaimsPassthrough?: boolean | CustomClaimsPassthroughConfig;\n  /** Enable token swap pattern (default: true) - issues short-lived JWTs instead of passing through upstream tokens */\n  enableTokenSwap?: boolean;\n  /** Encryption key for token storage (default: auto-generated). Set to false to disable encryption. */\n  encryptionKey?: false | string;\n  /**\n   * Extra query parameters appended to the upstream authorization URL.\n   * Required by providers such as Google, which only issues a refresh_token\n   * when the authorization request carries `access_type=offline` (and\n   * re-issues it on re-auth with `prompt=consent`). Without these, access\n   * expires after the upstream token TTL and can never be renewed.\n   *\n   * Core OAuth parameters managed by the proxy (client_id, redirect_uri,\n   * response_type, state, scope, code_challenge, code_challenge_method)\n   * cannot be overridden — entries with those keys are ignored.\n   */\n  extraAuthorizationParams?: Record<string, string>;\n  /** Forward client's PKCE to upstream (default: false) */\n  forwardPkce?: boolean;\n  /** Secret key for signing JWTs when token swap is enabled */\n  jwtSigningKey?: string;\n  /** OAuth callback path (default: /oauth/callback) */\n  redirectPath?: string;\n  /** Refresh token TTL in seconds (default: 2592000) */\n  refreshTokenTtl?: number;\n  /** Scopes to request from upstream provider */\n  scopes?: string[];\n  /** Custom token storage backend */\n  tokenStorage?: TokenStorage;\n  /** Custom token verifier for validating upstream tokens */\n  tokenVerifier?: TokenVerifier;\n  /** Transaction TTL in seconds (default: 600) */\n  transactionTtl?: number;\n  /** Upstream provider's authorization endpoint URL */\n  upstreamAuthorizationEndpoint: string;\n  /** Pre-registered client ID with upstream provider */\n  upstreamClientId: string;\n  /** Pre-registered client secret with upstream provider */\n  upstreamClientSecret: string;\n  /** Upstream provider's token endpoint URL */\n  upstreamTokenEndpoint: string;\n  /** Upstream token endpoint authentication method (default: \"client_secret_basic\") */\n  upstreamTokenEndpointAuthMethod?:\n    | \"client_secret_basic\"\n    | \"client_secret_post\";\n}\n\n/**\n * OAuth transaction tracking active authorization flows\n */\nexport interface OAuthTransaction {\n  /** Client's callback URL */\n  clientCallbackUrl: string;\n  /** Client's PKCE code challenge */\n  clientCodeChallenge: string;\n  /** Client's PKCE code challenge method (S256 or plain) */\n  clientCodeChallengeMethod: string;\n  /** Client ID from registration */\n  clientId: string;\n  /** Whether user consent was given */\n  consentGiven?: boolean;\n  /** Transaction creation timestamp */\n  createdAt: Date;\n  /** Transaction expiration timestamp */\n  expiresAt: Date;\n  /** Unique transaction ID */\n  id: string;\n  /** Additional state data */\n  metadata?: Record<string, unknown>;\n  /** Proxy-generated PKCE challenge for upstream */\n  proxyCodeChallenge: string;\n  /** Proxy-generated PKCE verifier for upstream */\n  proxyCodeVerifier: string;\n  /** Requested scopes */\n  scope: string[];\n  /** OAuth state parameter */\n  state: string;\n}\n\n/**\n * PKCE pair\n */\nexport interface PKCEPair {\n  challenge: string;\n  verifier: string;\n}\n\n/**\n * Dynamic client registration data\n */\nexport interface ProxyDCRClient {\n  /** Primary (first) registered callback URL */\n  callbackUrl: string;\n  /** Proxy-issued client ID (not the upstream provider's client_id) */\n  clientId: string;\n  /** Proxy-issued client secret (not the upstream provider's client_secret) */\n  clientSecret?: string;\n  /** Client metadata from registration request */\n  metadata?: DCRClientMetadata;\n  /** All redirect URIs registered by this client */\n  redirectUris: string[];\n  /** Client registration timestamp */\n  registeredAt: Date;\n}\n\n/**\n * OAuth refresh token request\n */\nexport interface RefreshRequest {\n  client_id: string;\n  client_secret?: string;\n  grant_type: \"refresh_token\";\n  refresh_token: string;\n  scope?: string;\n}\n\n/**\n * Token mapping for JWT swap pattern\n * Maps JTI to upstream token reference\n */\nexport interface TokenMapping {\n  /** Client ID */\n  clientId: string;\n  /** Creation timestamp */\n  createdAt: Date;\n  /** Expiration timestamp */\n  expiresAt: Date;\n  /** JTI from FastMCP JWT */\n  jti: string;\n  /** Scopes */\n  scope: string[];\n  /** Reference to upstream token set */\n  upstreamTokenKey: string;\n}\n\n/**\n * OAuth token request\n */\nexport interface TokenRequest {\n  client_id: string;\n  client_secret?: string;\n  code: string;\n  code_verifier?: string;\n  grant_type: \"authorization_code\";\n  redirect_uri: string;\n}\n\n/**\n * OAuth token response\n */\nexport interface TokenResponse {\n  access_token: string;\n  expires_in: number;\n  id_token?: string;\n  refresh_token?: string;\n  scope?: string;\n  token_type: string;\n}\n\n/**\n * Token storage interface\n */\nexport interface TokenStorage {\n  /** Clean up expired entries */\n  cleanup(): Promise<void>;\n  /** Delete a value */\n  delete(key: string): Promise<void>;\n  /** Retrieve a value */\n  get(key: string): Promise<null | unknown>;\n  /** Save a value with optional TTL */\n  save(key: string, value: unknown, ttl?: number): Promise<void>;\n  /**\n   * Atomically retrieve a value and delete it, returning `null` if the key was\n   * absent. At most one caller may observe a given value.\n   *\n   * Optional, but **required for correctness when more than one process shares\n   * this storage**: the OAuth proxy consumes authorization codes and\n   * transactions through it, and single-use enforcement depends on the\n   * atomicity. Without it the proxy falls back to a non-atomic get + delete,\n   * where two concurrent requests can both redeem the same authorization code.\n   *\n   * Most backends expose a suitable primitive: Redis `GETDEL`, DynamoDB\n   * `DeleteItem` with `ReturnValues: \"ALL_OLD\"`, or SQL\n   * `DELETE ... RETURNING *`.\n   */\n  take?(key: string): Promise<null | unknown>;\n}\n\n/**\n * Token verification result\n */\nexport interface TokenVerificationResult {\n  claims?: Record<string, unknown>;\n  error?: string;\n  valid: boolean;\n}\n\n/**\n * Token verifier for validating upstream tokens\n */\nexport interface TokenVerifier {\n  verify(token: string): Promise<TokenVerificationResult>;\n}\n\n/**\n * Token set from upstream OAuth provider\n */\nexport interface UpstreamTokenSet {\n  /** Access token */\n  accessToken: string;\n  /** Token expiration in seconds */\n  expiresIn: number;\n  /** ID token (for OIDC) */\n  idToken?: string;\n  /** Token issuance timestamp */\n  issuedAt: Date;\n  /** Refresh token expiration in seconds (if provided by upstream) */\n  refreshExpiresIn?: number;\n  /** Refresh token (if provided) */\n  refreshToken?: string;\n  /** Granted scopes */\n  scope: string[];\n  /** Token type (usually \"Bearer\") */\n  tokenType: string;\n}\n","/**\n * ClaimsExtractor\n * Securely extracts and filters custom claims from upstream OAuth tokens\n */\n\nimport type { CustomClaimsPassthroughConfig } from \"../types.js\";\n\nexport class ClaimsExtractor {\n  private config: CustomClaimsPassthroughConfig;\n\n  // Claims that MUST NOT be copied from upstream (protect proxy's JWT integrity)\n  private readonly PROTECTED_CLAIMS = new Set([\n    \"aud\",\n    \"client_id\",\n    \"exp\",\n    \"iat\",\n    \"iss\",\n    \"jti\",\n    \"nbf\",\n  ]);\n\n  constructor(config: boolean | CustomClaimsPassthroughConfig) {\n    // Handle boolean shorthand: true = default config, false = disabled\n    if (typeof config === \"boolean\") {\n      config = config ? {} : { fromAccessToken: false, fromIdToken: false };\n    }\n\n    // Apply defaults\n    this.config = {\n      allowComplexClaims: config.allowComplexClaims || false,\n      allowedClaims: config.allowedClaims,\n      blockedClaims: config.blockedClaims || [],\n      claimPrefix:\n        config.claimPrefix !== undefined ? config.claimPrefix : false, // Default: no prefix\n      fromAccessToken: config.fromAccessToken !== false, // Default: true\n      fromIdToken: config.fromIdToken !== false, // Default: true\n      maxClaimValueSize: config.maxClaimValueSize || 2000,\n    };\n  }\n\n  /**\n   * Extract claims from a token (access token or ID token)\n   */\n  async extract(\n    token: string,\n    tokenType: \"access\" | \"id\",\n  ): Promise<null | Record<string, unknown>> {\n    // Check if this token type is enabled\n    if (tokenType === \"access\" && !this.config.fromAccessToken) {\n      return null;\n    }\n    if (tokenType === \"id\" && !this.config.fromIdToken) {\n      return null;\n    }\n\n    // Detect if token is JWT format (3 parts separated by dots)\n    if (!this.isJWT(token)) {\n      // Opaque token - no claims to extract\n      return null;\n    }\n\n    // Decode JWT payload (base64url decode only, no signature verification)\n    // We trust the token because it came from upstream via server-to-server exchange\n    const payload = this.decodeJWTPayload(token);\n    if (!payload) {\n      return null;\n    }\n\n    // Filter and validate claims\n    const filtered = this.filterClaims(payload);\n\n    // Apply prefix if configured\n    return this.applyPrefix(filtered);\n  }\n\n  /**\n   * Apply prefix to claim names (if configured)\n   */\n  private applyPrefix(\n    claims: Record<string, unknown>,\n  ): Record<string, unknown> {\n    const prefix = this.config.claimPrefix;\n\n    // No prefix configured or explicitly disabled\n    if (prefix === false || prefix === \"\" || prefix === undefined) {\n      return claims;\n    }\n\n    // Apply prefix to all claim names\n    const result: Record<string, unknown> = {};\n    for (const [key, value] of Object.entries(claims)) {\n      result[`${prefix}${key}`] = value;\n    }\n\n    return result;\n  }\n\n  /**\n   * Decode JWT payload without signature verification\n   * Safe because token came from trusted upstream via server-to-server exchange\n   */\n  private decodeJWTPayload(token: string): null | Record<string, unknown> {\n    try {\n      const parts = token.split(\".\");\n      if (parts.length !== 3) {\n        return null;\n      }\n\n      // Decode the payload (middle part)\n      const payload = Buffer.from(parts[1], \"base64url\").toString(\"utf-8\");\n      return JSON.parse(payload) as Record<string, unknown>;\n    } catch (error) {\n      // Invalid JWT format or JSON\n      console.warn(`Failed to decode JWT payload: ${error}`);\n      return null;\n    }\n  }\n\n  /**\n   * Filter claims based on security rules\n   */\n  private filterClaims(\n    claims: Record<string, unknown>,\n  ): Record<string, unknown> {\n    const result: Record<string, unknown> = {};\n\n    for (const [key, value] of Object.entries(claims)) {\n      // RULE 1: Skip protected claims (ALWAYS enforced)\n      if (this.PROTECTED_CLAIMS.has(key)) {\n        continue;\n      }\n\n      // RULE 2: Skip blocked claims\n      if (this.config.blockedClaims?.includes(key)) {\n        continue;\n      }\n\n      // RULE 3: If allowlist exists, only include allowed claims\n      if (\n        this.config.allowedClaims &&\n        !this.config.allowedClaims.includes(key)\n      ) {\n        continue;\n      }\n\n      // RULE 4: Validate claim value\n      if (!this.isValidClaimValue(value)) {\n        console.warn(`Skipping claim '${key}' due to invalid value`);\n        continue;\n      }\n\n      result[key] = value;\n    }\n\n    return result;\n  }\n\n  /**\n   * Check if a token is in JWT format\n   */\n  private isJWT(token: string): boolean {\n    return token.split(\".\").length === 3;\n  }\n\n  /**\n   * Validate a claim value (type and size checks)\n   */\n  private isValidClaimValue(value: unknown): boolean {\n    if (value === null || value === undefined) {\n      return false;\n    }\n\n    const type = typeof value;\n\n    // Primitive types (string, number, boolean) are always allowed\n    if (type === \"string\") {\n      const maxSize = this.config.maxClaimValueSize ?? 2000;\n      return (value as string).length <= maxSize;\n    }\n\n    if (type === \"number\" || type === \"boolean\") {\n      return true;\n    }\n\n    // Arrays and objects only if explicitly allowed\n    if (Array.isArray(value) || type === \"object\") {\n      // Complex types not allowed by default (security)\n      if (!this.config.allowComplexClaims) {\n        return false;\n      }\n\n      // Check serialized size\n      try {\n        const stringified = JSON.stringify(value);\n        const maxSize = this.config.maxClaimValueSize ?? 2000;\n        return stringified.length <= maxSize;\n      } catch {\n        // Can't serialize - reject\n        return false;\n      }\n    }\n\n    // Unknown type - reject\n    return false;\n  }\n}\n","/**\n * Consent Management\n * Handles user consent flow for OAuth authorization\n */\n\nimport { createHmac } from \"crypto\";\n\nimport type { ConsentData, OAuthTransaction } from \"../types.js\";\n\n/**\n * Manages consent screens and cookie signing\n */\nexport class ConsentManager {\n  private signingKey: string;\n\n  constructor(signingKey: string) {\n    this.signingKey = signingKey || this.generateDefaultKey();\n  }\n\n  /**\n   * Create HTTP response with consent screen\n   */\n  createConsentResponse(\n    transaction: OAuthTransaction,\n    provider: string,\n  ): Response {\n    const consentData: ConsentData = {\n      clientName: \"MCP Client\",\n      provider,\n      scope: transaction.scope,\n      timestamp: Date.now(),\n      transactionId: transaction.id,\n    };\n\n    const html = this.generateConsentScreen(consentData);\n\n    return new Response(html, {\n      headers: {\n        \"Content-Type\": \"text/html; charset=utf-8\",\n      },\n      status: 200,\n    });\n  }\n\n  /**\n   * Generate HTML for consent screen\n   */\n  generateConsentScreen(data: ConsentData): string {\n    const { clientName, provider, scope, transactionId } = data;\n\n    return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Authorization Request</title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n            min-height: 100vh;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            padding: 20px;\n        }\n\n        .consent-container {\n            background: white;\n            border-radius: 12px;\n            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n            max-width: 480px;\n            width: 100%;\n            padding: 40px;\n        }\n\n        .header {\n            text-align: center;\n            margin-bottom: 30px;\n        }\n\n        .header h1 {\n            color: #1a202c;\n            font-size: 24px;\n            margin-bottom: 8px;\n        }\n\n        .header p {\n            color: #718096;\n            font-size: 14px;\n        }\n\n        .app-info {\n            background: #f7fafc;\n            border-radius: 8px;\n            padding: 20px;\n            margin-bottom: 24px;\n        }\n\n        .app-info h2 {\n            color: #2d3748;\n            font-size: 18px;\n            margin-bottom: 12px;\n        }\n\n        .app-name {\n            color: #667eea;\n            font-weight: 600;\n        }\n\n        .permissions {\n            margin-top: 16px;\n        }\n\n        .permissions h3 {\n            color: #4a5568;\n            font-size: 14px;\n            margin-bottom: 8px;\n            font-weight: 600;\n        }\n\n        .permissions ul {\n            list-style: none;\n        }\n\n        .permissions li {\n            color: #718096;\n            font-size: 14px;\n            padding: 6px 0;\n            padding-left: 24px;\n            position: relative;\n        }\n\n        .permissions li:before {\n            content: \"✓\";\n            position: absolute;\n            left: 0;\n            color: #48bb78;\n            font-weight: bold;\n        }\n\n        .warning {\n            background: #fffaf0;\n            border-left: 4px solid #ed8936;\n            padding: 12px 16px;\n            margin-bottom: 24px;\n            border-radius: 4px;\n        }\n\n        .warning p {\n            color: #744210;\n            font-size: 13px;\n            line-height: 1.5;\n        }\n\n        .actions {\n            display: flex;\n            gap: 12px;\n        }\n\n        button {\n            flex: 1;\n            padding: 14px 24px;\n            border: none;\n            border-radius: 6px;\n            font-size: 16px;\n            font-weight: 600;\n            cursor: pointer;\n            transition: all 0.2s;\n        }\n\n        .approve {\n            background: #667eea;\n            color: white;\n        }\n\n        .approve:hover {\n            background: #5a67d8;\n            transform: translateY(-1px);\n            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n        }\n\n        .deny {\n            background: #e2e8f0;\n            color: #4a5568;\n        }\n\n        .deny:hover {\n            background: #cbd5e0;\n        }\n\n        .footer {\n            margin-top: 24px;\n            text-align: center;\n            color: #a0aec0;\n            font-size: 12px;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"consent-container\">\n        <div class=\"header\">\n            <h1>🔐 Authorization Request</h1>\n            <p>via ${this.escapeHtml(provider)}</p>\n        </div>\n\n        <div class=\"app-info\">\n            <h2>\n                <span class=\"app-name\">${this.escapeHtml(clientName || \"An application\")}</span>\n                requests access\n            </h2>\n\n            <div class=\"permissions\">\n                <h3>This will allow the app to:</h3>\n                <ul>\n                    ${scope.map((s) => `<li>${this.escapeHtml(this.formatScope(s))}</li>`).join(\"\")}\n                </ul>\n            </div>\n        </div>\n\n        <div class=\"warning\">\n            <p>\n                <strong>⚠️ Important:</strong> Only approve if you trust this application.\n                By approving, you authorize it to access your account information.\n            </p>\n        </div>\n\n        <form method=\"POST\" action=\"/oauth/consent\">\n            <input type=\"hidden\" name=\"transaction_id\" value=\"${this.escapeHtml(transactionId)}\">\n            <div class=\"actions\">\n                <button type=\"submit\" name=\"action\" value=\"deny\" class=\"deny\">\n                    Deny\n                </button>\n                <button type=\"submit\" name=\"action\" value=\"approve\" class=\"approve\">\n                    Approve\n                </button>\n            </div>\n        </form>\n\n        <div class=\"footer\">\n            <p>This consent is required to prevent unauthorized access.</p>\n        </div>\n    </div>\n</body>\n</html>\n    `.trim();\n  }\n\n  /**\n   * Sign consent data for cookie\n   */\n  signConsentCookie(data: ConsentData): string {\n    const payload = JSON.stringify(data);\n    const signature = this.sign(payload);\n\n    return `${Buffer.from(payload).toString(\"base64\")}.${signature}`;\n  }\n\n  /**\n   * Validate and parse consent cookie\n   */\n  validateConsentCookie(cookie: string): ConsentData | null {\n    try {\n      const [payloadB64, signature] = cookie.split(\".\");\n\n      if (!payloadB64 || !signature) {\n        return null;\n      }\n\n      const payload = Buffer.from(payloadB64, \"base64\").toString(\"utf8\");\n      const expectedSignature = this.sign(payload);\n\n      if (signature !== expectedSignature) {\n        return null;\n      }\n\n      const data = JSON.parse(payload) as ConsentData;\n\n      // Check if consent is still valid (5 minutes)\n      const age = Date.now() - data.timestamp;\n      if (age > 5 * 60 * 1000) {\n        return null;\n      }\n\n      return data;\n    } catch {\n      return null;\n    }\n  }\n\n  /**\n   * Escape HTML to prevent XSS\n   */\n  private escapeHtml(text: string): string {\n    const map: Record<string, string> = {\n      \"'\": \"&#x27;\",\n      '\"': \"&quot;\",\n      \"/\": \"&#x2F;\",\n      \"&\": \"&amp;\",\n      \"<\": \"&lt;\",\n      \">\": \"&gt;\",\n    };\n\n    return text.replace(/[&<>\"'/]/g, (char) => map[char] || char);\n  }\n\n  /**\n   * Format scope for display\n   */\n  private formatScope(scope: string): string {\n    // Convert scope names to readable format\n    const scopeMap: Record<string, string> = {\n      email: \"Access your email address\",\n      openid: \"Verify your identity\",\n      profile: \"View your basic profile information\",\n      \"read:user\": \"Read your user information\",\n      \"write:user\": \"Modify your user information\",\n    };\n\n    return scopeMap[scope] || scope.replace(/_/g, \" \").replace(/:/g, \" - \");\n  }\n\n  /**\n   * Generate default signing key if none provided\n   */\n  private generateDefaultKey(): string {\n    return `fastmcp-consent-${Date.now()}-${Math.random()}`;\n  }\n\n  /**\n   * Sign a payload using HMAC-SHA256\n   */\n  private sign(payload: string): string {\n    return createHmac(\"sha256\", this.signingKey).update(payload).digest(\"hex\");\n  }\n}\n","/**\n * JWT Issuer for OAuth Proxy\n * Issues and validates short-lived JWTs that reference upstream provider tokens\n */\n\nimport { createHmac, pbkdf2, randomBytes } from \"crypto\";\nimport { promisify } from \"util\";\n\nimport {\n  DEFAULT_ACCESS_TOKEN_TTL,\n  DEFAULT_REFRESH_TOKEN_TTL,\n} from \"../types.js\";\n\nconst pbkdf2Async = promisify(pbkdf2);\n\n/**\n * JWT Claims for FastMCP tokens\n */\nexport interface JWTClaims {\n  /** Additional custom claims from upstream tokens */\n  [key: string]: unknown;\n  /** Audience */\n  aud: string;\n  /** Client ID */\n  client_id: string;\n  /** Expiration time (seconds since epoch) */\n  exp: number;\n  /** Issued at time (seconds since epoch) */\n  iat: number;\n  /** Issuer */\n  iss: string;\n  /** JWT ID (unique identifier) */\n  jti: string;\n  /** Scopes */\n  scope: string[];\n}\n\n/**\n * JWT Issuer configuration\n */\nexport interface JWTIssuerConfig {\n  /** Token expiration in seconds (default: 3600 = 1 hour) */\n  accessTokenTtl?: number;\n  /** Audience for issued tokens */\n  audience: string;\n  /** Issuer identifier */\n  issuer: string;\n  /** Refresh token expiration in seconds (default: 2592000 = 30 days) */\n  refreshTokenTtl?: number;\n  /** Secret key for signing tokens */\n  signingKey: string;\n}\n\n/**\n * Token validation result\n */\nexport interface TokenValidationResult {\n  /** Decoded claims if valid */\n  claims?: JWTClaims;\n  /** Error message if invalid */\n  error?: string;\n  /** Whether token is valid */\n  valid: boolean;\n}\n\n/**\n * JWT Header\n */\ninterface JWTHeader {\n  alg: string;\n  typ: string;\n}\n\n/**\n * JWT Issuer\n * Issues and validates HS256-signed JWTs for the OAuth proxy\n */\nexport class JWTIssuer {\n  private accessTokenTtl: number;\n  private audience: string;\n  private issuer: string;\n  private refreshTokenTtl: number;\n  private signingKey: Buffer;\n\n  constructor(config: JWTIssuerConfig) {\n    this.issuer = config.issuer;\n    this.audience = config.audience;\n    this.accessTokenTtl = config.accessTokenTtl || DEFAULT_ACCESS_TOKEN_TTL;\n    this.refreshTokenTtl = config.refreshTokenTtl || DEFAULT_REFRESH_TOKEN_TTL;\n    this.signingKey = Buffer.from(config.signingKey);\n  }\n\n  /**\n   * Derive a signing key from a secret\n   * Uses PBKDF2 for key derivation\n   */\n  static async deriveKey(\n    secret: string,\n    iterations: number = 100000,\n  ): Promise<string> {\n    const salt = Buffer.from(\"fastmcp-oauth-proxy\");\n    const key = await pbkdf2Async(secret, salt, iterations, 32, \"sha256\");\n    return key.toString(\"base64\");\n  }\n\n  /**\n   * Issue an access token\n   */\n  issueAccessToken(\n    clientId: string,\n    scope: string[],\n    additionalClaims?: Record<string, unknown>,\n    expiresIn?: number,\n  ): string {\n    const now = Math.floor(Date.now() / 1000);\n    const jti = this.generateJti();\n\n    const claims: JWTClaims = {\n      aud: this.audience,\n      client_id: clientId,\n      exp: now + (expiresIn ?? this.accessTokenTtl),\n      iat: now,\n      iss: this.issuer,\n      jti,\n      scope,\n      // Merge additional claims (custom claims from upstream)\n      ...(additionalClaims || {}),\n    };\n\n    return this.signToken(claims);\n  }\n\n  /**\n   * Issue a refresh token\n   */\n  issueRefreshToken(\n    clientId: string,\n    scope: string[],\n    additionalClaims?: Record<string, unknown>,\n    expiresIn?: number,\n  ): string {\n    const now = Math.floor(Date.now() / 1000);\n    const jti = this.generateJti();\n\n    const claims: JWTClaims = {\n      aud: this.audience,\n      client_id: clientId,\n      exp: now + (expiresIn ?? this.refreshTokenTtl),\n      iat: now,\n      iss: this.issuer,\n      jti,\n      scope,\n      // Merge additional claims (custom claims from upstream)\n      ...(additionalClaims || {}),\n    };\n\n    return this.signToken(claims);\n  }\n\n  /**\n   * Validate a JWT token\n   */\n  async verify(token: string): Promise<TokenValidationResult> {\n    try {\n      const parts = token.split(\".\");\n      if (parts.length !== 3) {\n        return {\n          error: \"Invalid token format\",\n          valid: false,\n        };\n      }\n\n      const [headerB64, payloadB64, signatureB64] = parts;\n\n      // Verify signature\n      const expectedSignature = this.sign(`${headerB64}.${payloadB64}`);\n      if (signatureB64 !== expectedSignature) {\n        return {\n          error: \"Invalid signature\",\n          valid: false,\n        };\n      }\n\n      // Decode claims\n      const claims: JWTClaims = JSON.parse(\n        Buffer.from(payloadB64, \"base64url\").toString(\"utf-8\"),\n      );\n\n      // Validate claims\n      const now = Math.floor(Date.now() / 1000);\n\n      if (claims.exp <= now) {\n        return {\n          claims,\n          error: \"Token expired\",\n          valid: false,\n        };\n      }\n\n      if (claims.iss !== this.issuer) {\n        return {\n          claims,\n          error: \"Invalid issuer\",\n          valid: false,\n        };\n      }\n\n      if (claims.aud !== this.audience) {\n        return {\n          claims,\n          error: \"Invalid audience\",\n          valid: false,\n        };\n      }\n\n      return {\n        claims,\n        valid: true,\n      };\n    } catch (error) {\n      return {\n        error: error instanceof Error ? error.message : \"Validation failed\",\n        valid: false,\n      };\n    }\n  }\n\n  /**\n   * Generate unique JWT ID\n   */\n  private generateJti(): string {\n    return randomBytes(16).toString(\"base64url\");\n  }\n\n  /**\n   * Sign data with HMAC-SHA256\n   */\n  private sign(data: string): string {\n    const hmac = createHmac(\"sha256\", this.signingKey);\n    hmac.update(data);\n    return hmac.digest(\"base64url\");\n  }\n\n  /**\n   * Sign a JWT token\n   */\n  private signToken(claims: JWTClaims): string {\n    const header: JWTHeader = {\n      alg: \"HS256\",\n      typ: \"JWT\",\n    };\n\n    const headerB64 = Buffer.from(JSON.stringify(header)).toString(\"base64url\");\n    const payloadB64 = Buffer.from(JSON.stringify(claims)).toString(\n      \"base64url\",\n    );\n\n    const signature = this.sign(`${headerB64}.${payloadB64}`);\n\n    return `${headerB64}.${payloadB64}.${signature}`;\n  }\n}\n","/**\n * PKCE (Proof Key for Code Exchange) Utilities\n * Implements RFC 7636 for OAuth 2.0 public clients\n */\n\nimport { createHash, randomBytes } from \"crypto\";\n\nimport type { PKCEPair } from \"../types.js\";\n\n/**\n * PKCE utility class for generating and validating code challenges\n */\nexport class PKCEUtils {\n  /**\n   * Generate a code challenge from a verifier\n   * @param verifier The code verifier\n   * @param method Challenge method: 'S256' or 'plain' (default: 'S256')\n   * @returns Base64URL-encoded challenge string\n   */\n  static generateChallenge(\n    verifier: string,\n    method: \"plain\" | \"S256\" = \"S256\",\n  ): string {\n    if (method === \"plain\") {\n      return verifier;\n    }\n\n    if (method === \"S256\") {\n      const hash = createHash(\"sha256\");\n      hash.update(verifier);\n      return PKCEUtils.base64URLEncode(hash.digest());\n    }\n\n    throw new Error(`Unsupported challenge method: ${method}`);\n  }\n\n  /**\n   * Generate a complete PKCE pair (verifier + challenge)\n   * @param method Challenge method: 'S256' or 'plain' (default: 'S256')\n   * @returns Object containing verifier and challenge\n   */\n  static generatePair(method: \"plain\" | \"S256\" = \"S256\"): PKCEPair {\n    const verifier = PKCEUtils.generateVerifier();\n    const challenge = PKCEUtils.generateChallenge(verifier, method);\n\n    return {\n      challenge,\n      verifier,\n    };\n  }\n\n  /**\n   * Generate a cryptographically secure code verifier\n   * @param length Length of verifier (43-128 characters, default: 128)\n   * @returns Base64URL-encoded verifier string\n   */\n  static generateVerifier(length: number = 128): string {\n    if (length < 43 || length > 128) {\n      throw new Error(\"PKCE verifier length must be between 43 and 128\");\n    }\n\n    // Generate random bytes and encode as base64url\n    // Need more bytes because base64 encoding expands data\n    const byteLength = Math.ceil((length * 3) / 4);\n    const randomBytesBuffer = randomBytes(byteLength);\n\n    return PKCEUtils.base64URLEncode(randomBytesBuffer).slice(0, length);\n  }\n\n  /**\n   * Validate a code verifier against a challenge\n   * @param verifier The code verifier to validate\n   * @param challenge The expected challenge\n   * @param method The challenge method used\n   * @returns True if verifier matches challenge\n   */\n  static validateChallenge(\n    verifier: string,\n    challenge: string,\n    method: string,\n  ): boolean {\n    if (!verifier || !challenge) {\n      return false;\n    }\n\n    if (method === \"plain\") {\n      return verifier === challenge;\n    }\n\n    if (method === \"S256\") {\n      const computedChallenge = PKCEUtils.generateChallenge(verifier, \"S256\");\n      return computedChallenge === challenge;\n    }\n\n    // Unknown method\n    return false;\n  }\n\n  /**\n   * Encode a buffer as base64url (RFC 4648)\n   * @param buffer Buffer to encode\n   * @returns Base64URL-encoded string\n   */\n  private static base64URLEncode(buffer: Buffer): string {\n    return buffer\n      .toString(\"base64\")\n      .replace(/\\+/g, \"-\")\n      .replace(/\\//g, \"_\")\n      .replace(/=/g, \"\");\n  }\n}\n","/**\n * Token Storage Implementations\n * Secure storage for OAuth tokens and transaction state\n */\n\nimport {\n  createCipheriv,\n  createDecipheriv,\n  randomBytes,\n  scryptSync,\n} from \"crypto\";\n\nimport type { TokenStorage } from \"../types.js\";\n\ninterface StorageEntry {\n  expiresAt: number;\n  value: unknown;\n}\n\n/**\n * Encrypted token storage wrapper\n * Encrypts values using AES-256-GCM before storing\n */\nexport class EncryptedTokenStorage implements TokenStorage {\n  private algorithm: import(\"crypto\").CipherGCMTypes = \"aes-256-gcm\";\n  private backend: TokenStorage;\n  private encryptionKey: Buffer;\n\n  constructor(backend: TokenStorage, encryptionKey: string) {\n    this.backend = backend;\n    // Synchronously derive key using scrypt\n    const salt = Buffer.from(\"fastmcp-oauth-proxy-salt\");\n    this.encryptionKey = scryptSync(encryptionKey, salt, 32);\n  }\n\n  async cleanup(): Promise<void> {\n    await this.backend.cleanup();\n  }\n\n  async delete(key: string): Promise<void> {\n    await this.backend.delete(key);\n  }\n\n  async get(key: string): Promise<null | unknown> {\n    return this.decryptStored(await this.backend.get(key));\n  }\n\n  async save(key: string, value: unknown, ttl?: number): Promise<void> {\n    const encrypted = await this.encrypt(\n      JSON.stringify(value),\n      this.encryptionKey,\n    );\n    await this.backend.save(key, encrypted, ttl);\n  }\n\n  /**\n   * Delegates to the backend so that the atomicity of the underlying store is\n   * preserved; falls back to get + delete when the backend cannot do better.\n   */\n  async take(key: string): Promise<null | unknown> {\n    if (this.backend.take) {\n      return this.decryptStored(await this.backend.take(key));\n    }\n\n    const value = await this.get(key);\n\n    if (value === null) {\n      return null;\n    }\n\n    await this.backend.delete(key);\n\n    return value;\n  }\n\n  private async decrypt(ciphertext: string, key: Buffer): Promise<string> {\n    const parts = ciphertext.split(\":\");\n    if (parts.length !== 3) {\n      throw new Error(\"Invalid encrypted data format\");\n    }\n\n    const [ivHex, authTagHex, encrypted] = parts;\n    const iv = Buffer.from(ivHex, \"hex\");\n    const authTag = Buffer.from(authTagHex, \"hex\");\n\n    const decipher = createDecipheriv(this.algorithm, key, iv, {\n      authTagLength: 16,\n    });\n    // Use type assertion for GCM-specific method\n    (decipher as unknown as { setAuthTag(buffer: Buffer): void }).setAuthTag(\n      authTag,\n    );\n\n    let decrypted = decipher.update(encrypted, \"hex\", \"utf8\");\n    decrypted += decipher.final(\"utf8\");\n\n    return decrypted;\n  }\n\n  private async decryptStored(encrypted: unknown): Promise<null | unknown> {\n    if (!encrypted) {\n      return null;\n    }\n\n    try {\n      const decrypted = await this.decrypt(\n        encrypted as string,\n        this.encryptionKey,\n      );\n      return JSON.parse(decrypted);\n    } catch (error) {\n      console.error(\"Failed to decrypt value:\", error);\n      return null;\n    }\n  }\n\n  private async encrypt(plaintext: string, key: Buffer): Promise<string> {\n    const iv = randomBytes(16);\n    const cipher = createCipheriv(this.algorithm, key, iv, {\n      authTagLength: 16,\n    });\n\n    let encrypted = cipher.update(plaintext, \"utf8\", \"hex\");\n    encrypted += cipher.final(\"hex\");\n\n    // Use type assertion for GCM-specific method\n    const authTag = (\n      cipher as unknown as { getAuthTag(): Buffer }\n    ).getAuthTag();\n\n    // Return format: iv:authTag:encrypted\n    return `${iv.toString(\"hex\")}:${authTag.toString(\"hex\")}:${encrypted}`;\n  }\n}\n\n/**\n * In-memory token storage with TTL support\n */\nexport class MemoryTokenStorage implements TokenStorage {\n  private cleanupInterval: NodeJS.Timeout | null = null;\n  private store: Map<string, StorageEntry> = new Map();\n\n  constructor(cleanupIntervalMs: number = 60000) {\n    // Run cleanup every minute by default\n    this.cleanupInterval = setInterval(\n      () => void this.cleanup(),\n      cleanupIntervalMs,\n    );\n  }\n\n  async cleanup(): Promise<void> {\n    const now = Date.now();\n    const keysToDelete: string[] = [];\n\n    for (const [key, entry] of this.store.entries()) {\n      if (entry.expiresAt < now) {\n        keysToDelete.push(key);\n      }\n    }\n\n    for (const key of keysToDelete) {\n      this.store.delete(key);\n    }\n  }\n\n  async delete(key: string): Promise<void> {\n    this.store.delete(key);\n  }\n\n  /**\n   * Destroy the storage and clear cleanup interval\n   */\n  destroy(): void {\n    if (this.cleanupInterval) {\n      clearInterval(this.cleanupInterval);\n      this.cleanupInterval = null;\n    }\n    this.store.clear();\n  }\n\n  async get(key: string): Promise<null | unknown> {\n    const entry = this.store.get(key);\n\n    if (!entry) {\n      return null;\n    }\n\n    if (entry.expiresAt < Date.now()) {\n      this.store.delete(key);\n      return null;\n    }\n\n    return entry.value;\n  }\n\n  async save(key: string, value: unknown, ttl?: number): Promise<void> {\n    const expiresAt = ttl ? Date.now() + ttl * 1000 : Number.MAX_SAFE_INTEGER;\n\n    this.store.set(key, {\n      expiresAt,\n      value,\n    });\n  }\n\n  /**\n   * Get the number of stored items\n   */\n  size(): number {\n    return this.store.size;\n  }\n\n  /**\n   * Atomic by construction: the read and the delete happen in a single\n   * synchronous block, so no other task can observe the value in between.\n   */\n  async take(key: string): Promise<null | unknown> {\n    const entry = this.store.get(key);\n    this.store.delete(key);\n\n    if (!entry || entry.expiresAt < Date.now()) {\n      return null;\n    }\n\n    return entry.value;\n  }\n}\n","/**\n * AuthProvider Base Class\n * High-level abstraction for OAuth authentication that simplifies configuration\n */\n\nimport type { IncomingMessage } from \"node:http\";\n\nimport type { TokenStorage, UpstreamTokenSet } from \"../types.js\";\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\n\n/**\n * Configuration common to all OAuth providers.\n */\nexport interface AuthProviderConfig {\n  /**\n   * Allow-list of redirect URI patterns accepted by Dynamic Client\n   * Registration. Required for any deployment that exposes /oauth/register\n   * or /oauth/authorize — an empty/unset list rejects every URI.\n   *\n   * Example: `[\"https://yourapp.example.com/*\"]`\n   *\n   * Prior versions defaulted to `[\"http://localhost:*\", \"https://*\"]`, which\n   * enabled CWE-601 open-redirect / authorization-code theft. See the\n   * SECURITY advisory before loosening this.\n   */\n  allowedRedirectUriPatterns?: string[];\n  /** Base URL where the MCP server is accessible */\n  baseUrl: string;\n  /** OAuth client ID */\n  clientId: string;\n  /** OAuth client secret */\n  clientSecret: string;\n  /** Require user consent screen (default: true) */\n  consentRequired?: boolean;\n  /** Encryption key for token storage (auto-generated if not provided, set to false to disable) */\n  encryptionKey?: false | string;\n  /** JWT signing key (auto-generated if not provided) */\n  jwtSigningKey?: string;\n  /** Scopes to request (defaults vary by provider) */\n  scopes?: string[];\n  /** Token storage backend (default: MemoryTokenStorage) */\n  tokenStorage?: TokenStorage;\n}\n\n/**\n * Configuration for generic OAuth provider (user-specified endpoints).\n */\nexport interface GenericOAuthProviderConfig extends AuthProviderConfig {\n  /** OAuth authorization endpoint URL */\n  authorizationEndpoint: string;\n  /** OAuth token endpoint URL */\n  tokenEndpoint: string;\n  /** Token endpoint auth method (default: \"client_secret_basic\") */\n  tokenEndpointAuthMethod?: \"client_secret_basic\" | \"client_secret_post\";\n}\n\n/**\n * Standard session type for OAuth providers.\n * Contains the upstream access token and optional metadata.\n */\nexport interface OAuthSession {\n  /** The upstream OAuth access token */\n  accessToken: string;\n  /** Additional claims extracted from the token (if customClaimsPassthrough enabled) */\n  claims?: Record<string, unknown>;\n  /** Token expiration time (Unix timestamp in seconds) */\n  expiresAt?: number;\n  /** ID token from OIDC providers */\n  idToken?: string;\n  /** Refresh token (if available) */\n  refreshToken?: string;\n  /** Scopes granted by the OAuth provider */\n  scopes?: string[];\n}\n\n/**\n * Abstract base class for OAuth providers.\n * Encapsulates OAuthProxy creation, authenticate function, and oauth config.\n *\n * Subclasses only need to implement the endpoint and default scope methods.\n */\nexport abstract class AuthProvider<\n  TSession extends OAuthSession = OAuthSession,\n> {\n  protected config: AuthProviderConfig;\n  /**\n   * Get the proxy, creating it lazily if needed.\n   */\n  protected get proxy(): OAuthProxy {\n    if (!this._proxy) {\n      this._proxy = this.createProxy();\n    }\n    return this._proxy;\n  }\n\n  private _proxy: OAuthProxy | undefined;\n\n  constructor(config: AuthProviderConfig) {\n    this.config = config;\n    // Note: proxy is created lazily to allow subclass constructors to run first\n  }\n\n  /**\n   * Authenticate function to be used by FastMCP.\n   * Extracts Bearer token, validates it, and returns session with upstream access token.\n   */\n  async authenticate(\n    request: IncomingMessage | undefined,\n  ): Promise<TSession | undefined> {\n    if (!request) {\n      // stdio transport - no HTTP authentication\n      return undefined;\n    }\n\n    const authHeader = request.headers?.authorization;\n    if (!authHeader || !authHeader.startsWith(\"Bearer \")) {\n      return undefined;\n    }\n\n    const token = authHeader.slice(7);\n    const upstreamTokens = await this.proxy.loadUpstreamTokens(token);\n\n    if (!upstreamTokens) {\n      return undefined;\n    }\n\n    return this.createSession(upstreamTokens);\n  }\n\n  /**\n   * Get the OAuth configuration object for FastMCP ServerOptions.\n   */\n  getOAuthConfig(): {\n    authorizationServer: ReturnType<\n      OAuthProxy[\"getAuthorizationServerMetadata\"]\n    >;\n    enabled: true;\n    protectedResource: {\n      authorizationServers: string[];\n      resource: string;\n      scopesSupported: string[];\n    };\n    proxy: OAuthProxy;\n  } {\n    return {\n      authorizationServer: this.proxy.getAuthorizationServerMetadata(),\n      enabled: true,\n      protectedResource: {\n        authorizationServers: [this.config.baseUrl],\n        resource: this.config.baseUrl,\n        scopesSupported: this.config.scopes ?? this.getDefaultScopes(),\n      },\n      proxy: this.proxy,\n    };\n  }\n\n  /**\n   * Get the OAuthProxy instance (for advanced use cases).\n   */\n  getProxy(): OAuthProxy {\n    return this.proxy;\n  }\n\n  /** Create the underlying OAuthProxy with provider-specific configuration */\n  protected abstract createProxy(): OAuthProxy;\n\n  /**\n   * Create a session object from upstream tokens.\n   * Override in subclasses to add provider-specific session data.\n   */\n  protected createSession(upstreamTokens: UpstreamTokenSet): TSession {\n    return {\n      accessToken: upstreamTokens.accessToken,\n      expiresAt: upstreamTokens.expiresIn\n        ? Math.floor(Date.now() / 1000) + upstreamTokens.expiresIn\n        : undefined,\n      idToken: upstreamTokens.idToken,\n      refreshToken: upstreamTokens.refreshToken,\n      scopes: upstreamTokens.scope,\n    } as TSession;\n  }\n\n  /** Get the authorization endpoint for this provider */\n  protected abstract getAuthorizationEndpoint(): string;\n\n  /** Default scopes for this provider */\n  protected abstract getDefaultScopes(): string[];\n\n  /** Get the token endpoint for this provider */\n  protected abstract getTokenEndpoint(): string;\n}\n","/**\n * Microsoft Azure/Entra ID OAuth Provider\n * Pre-configured OAuth provider for Microsoft Identity Platform\n */\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\nimport {\n  AuthProvider,\n  type AuthProviderConfig,\n  type OAuthSession,\n} from \"./AuthProvider.js\";\n\n/**\n * Azure-specific configuration\n */\nexport interface AzureProviderConfig extends AuthProviderConfig {\n  /** Tenant ID or 'common', 'organizations', 'consumers' (default: 'common') */\n  tenantId?: string;\n}\n\n/**\n * Azure-specific session with additional user info\n */\nexport interface AzureSession extends OAuthSession {\n  upn?: string;\n}\n\n/**\n * Microsoft Azure AD / Entra ID OAuth 2.0 Provider\n * Callback URL: {baseUrl}/oauth/callback\n */\nexport class AzureProvider extends AuthProvider<AzureSession> {\n  private tenantId: string;\n\n  constructor(config: AzureProviderConfig) {\n    super(config);\n    this.tenantId = config.tenantId ?? \"common\";\n  }\n\n  protected createProxy(): OAuthProxy {\n    return new OAuthProxy({\n      // No fallback default: framework users must explicitly list the URIs\n      // they trust. A previous default of [\"http://localhost:*\", \"https://*\"]\n      // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.\n      allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,\n      baseUrl: this.config.baseUrl,\n      consentRequired: this.config.consentRequired ?? true,\n      encryptionKey: this.config.encryptionKey,\n      jwtSigningKey: this.config.jwtSigningKey,\n      scopes: this.config.scopes ?? this.getDefaultScopes(),\n      tokenStorage: this.config.tokenStorage,\n      upstreamAuthorizationEndpoint: this.getAuthorizationEndpoint(),\n      upstreamClientId: this.config.clientId,\n      upstreamClientSecret: this.config.clientSecret,\n      upstreamTokenEndpoint: this.getTokenEndpoint(),\n    });\n  }\n\n  protected getAuthorizationEndpoint(): string {\n    return `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/authorize`;\n  }\n\n  protected getDefaultScopes(): string[] {\n    return [\"openid\", \"profile\", \"email\"];\n  }\n\n  protected getTokenEndpoint(): string {\n    return `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/token`;\n  }\n}\n","/**\n * GitHub OAuth Provider\n * Pre-configured OAuth provider for GitHub OAuth Apps\n */\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\nimport {\n  AuthProvider,\n  type AuthProviderConfig,\n  type OAuthSession,\n} from \"./AuthProvider.js\";\n\n/**\n * GitHub-specific session with additional user info\n */\nexport interface GitHubSession extends OAuthSession {\n  username?: string;\n}\n\n/**\n * GitHub OAuth 2.0 Provider\n * Callback URL: {baseUrl}/oauth/callback\n */\nexport class GitHubProvider extends AuthProvider<GitHubSession> {\n  constructor(config: AuthProviderConfig) {\n    super(config);\n  }\n\n  protected createProxy(): OAuthProxy {\n    return new OAuthProxy({\n      // No fallback default: framework users must explicitly list the URIs\n      // they trust. A previous default of [\"http://localhost:*\", \"https://*\"]\n      // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.\n      allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,\n      baseUrl: this.config.baseUrl,\n      consentRequired: this.config.consentRequired ?? true,\n      encryptionKey: this.config.encryptionKey,\n      jwtSigningKey: this.config.jwtSigningKey,\n      scopes: this.config.scopes ?? this.getDefaultScopes(),\n      tokenStorage: this.config.tokenStorage,\n      upstreamAuthorizationEndpoint: this.getAuthorizationEndpoint(),\n      upstreamClientId: this.config.clientId,\n      upstreamClientSecret: this.config.clientSecret,\n      upstreamTokenEndpoint: this.getTokenEndpoint(),\n    });\n  }\n\n  protected getAuthorizationEndpoint(): string {\n    return \"https://github.com/login/oauth/authorize\";\n  }\n\n  protected getDefaultScopes(): string[] {\n    return [\"read:user\", \"user:email\"];\n  }\n\n  protected getTokenEndpoint(): string {\n    return \"https://github.com/login/oauth/access_token\";\n  }\n}\n","/**\n * Google OAuth Provider\n * Pre-configured OAuth provider for Google Identity Platform\n */\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\nimport {\n  AuthProvider,\n  type AuthProviderConfig,\n  type OAuthSession,\n} from \"./AuthProvider.js\";\n\n/**\n * Google-specific session with additional user info\n */\nexport interface GoogleSession extends OAuthSession {\n  email?: string;\n}\n\n/**\n * Google OAuth 2.0 Provider\n * Callback URL: {baseUrl}/oauth/callback\n */\nexport class GoogleProvider extends AuthProvider<GoogleSession> {\n  constructor(config: AuthProviderConfig) {\n    super(config);\n  }\n\n  protected createProxy(): OAuthProxy {\n    return new OAuthProxy({\n      // No fallback default: framework users must explicitly list the URIs\n      // they trust. A previous default of [\"http://localhost:*\", \"https://*\"]\n      // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.\n      allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,\n      baseUrl: this.config.baseUrl,\n      consentRequired: this.config.consentRequired ?? true,\n      encryptionKey: this.config.encryptionKey,\n      jwtSigningKey: this.config.jwtSigningKey,\n      scopes: this.config.scopes ?? this.getDefaultScopes(),\n      tokenStorage: this.config.tokenStorage,\n      upstreamAuthorizationEndpoint: this.getAuthorizationEndpoint(),\n      upstreamClientId: this.config.clientId,\n      upstreamClientSecret: this.config.clientSecret,\n      upstreamTokenEndpoint: this.getTokenEndpoint(),\n    });\n  }\n\n  protected getAuthorizationEndpoint(): string {\n    return \"https://accounts.google.com/o/oauth2/v2/auth\";\n  }\n\n  protected getDefaultScopes(): string[] {\n    return [\"openid\", \"profile\", \"email\"];\n  }\n\n  protected getTokenEndpoint(): string {\n    return \"https://oauth2.googleapis.com/token\";\n  }\n}\n","/**\n * Generic OAuth Provider\n * For any OAuth 2.0 compliant authorization server\n */\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\nimport {\n  AuthProvider,\n  type GenericOAuthProviderConfig,\n  type OAuthSession,\n} from \"./AuthProvider.js\";\n\n/**\n * Generic OAuth provider for any OAuth 2.0 compliant authorization server.\n * Use when there's no built-in provider for your identity provider.\n */\nexport class OAuthProvider<\n  TSession extends OAuthSession = OAuthSession,\n> extends AuthProvider<TSession> {\n  protected genericConfig: GenericOAuthProviderConfig;\n\n  constructor(config: GenericOAuthProviderConfig) {\n    super(config);\n    this.genericConfig = config;\n  }\n\n  protected createProxy(): OAuthProxy {\n    return new OAuthProxy({\n      // No fallback default: framework users must explicitly list the URIs\n      // they trust. A previous default of [\"http://localhost:*\", \"https://*\"]\n      // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.\n      allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,\n      baseUrl: this.config.baseUrl,\n      consentRequired: this.config.consentRequired ?? true,\n      encryptionKey: this.config.encryptionKey,\n      jwtSigningKey: this.config.jwtSigningKey,\n      scopes: this.config.scopes ?? this.getDefaultScopes(),\n      tokenStorage: this.config.tokenStorage,\n      upstreamAuthorizationEndpoint: this.getAuthorizationEndpoint(),\n      upstreamClientId: this.config.clientId,\n      upstreamClientSecret: this.config.clientSecret,\n      upstreamTokenEndpoint: this.getTokenEndpoint(),\n      upstreamTokenEndpointAuthMethod:\n        this.genericConfig.tokenEndpointAuthMethod ?? \"client_secret_basic\",\n    });\n  }\n\n  protected getAuthorizationEndpoint(): string {\n    return this.genericConfig.authorizationEndpoint;\n  }\n\n  protected getDefaultScopes(): string[] {\n    return [\"openid\"];\n  }\n\n  protected getTokenEndpoint(): string {\n    return this.genericConfig.tokenEndpoint;\n  }\n}\n","/**\n * Disk-based Token Storage Implementation\n * Provides persistent file-based storage for OAuth tokens and transaction state\n */\n\nimport { randomUUID } from \"crypto\";\nimport {\n  mkdir,\n  readdir,\n  readFile,\n  rename,\n  rm,\n  stat,\n  writeFile,\n} from \"fs/promises\";\nimport { join } from \"path\";\n\nimport type { TokenStorage } from \"../types.js\";\n\nexport interface DiskStoreOptions {\n  /**\n   * How often to run cleanup (in milliseconds)\n   * @default 60000 (1 minute)\n   */\n  cleanupIntervalMs?: number;\n\n  /**\n   * Directory path for storing data\n   */\n  directory: string;\n\n  /**\n   * File extension for stored files\n   * @default \".json\"\n   */\n  fileExtension?: string;\n}\n\ninterface StorageEntry {\n  expiresAt: number;\n  value: unknown;\n}\n\n/**\n * Disk-based token storage with TTL support\n * Persists tokens to filesystem for survival across server restarts\n */\nexport class DiskStore implements TokenStorage {\n  private cleanupInterval: NodeJS.Timeout | null = null;\n  private directory: string;\n  private fileExtension: string;\n\n  constructor(options: DiskStoreOptions) {\n    this.directory = options.directory;\n    this.fileExtension = options.fileExtension || \".json\";\n\n    // Ensure directory exists\n    void this.ensureDirectory();\n\n    // Start periodic cleanup\n    const cleanupIntervalMs = options.cleanupIntervalMs || 60000;\n    this.cleanupInterval = setInterval(() => {\n      void this.cleanup();\n    }, cleanupIntervalMs);\n  }\n\n  /**\n   * Clean up expired entries\n   */\n  async cleanup(): Promise<void> {\n    try {\n      await this.ensureDirectory();\n      const files = await readdir(this.directory);\n      const now = Date.now();\n\n      for (const file of files) {\n        if (!file.endsWith(this.fileExtension)) {\n          continue;\n        }\n\n        try {\n          const filePath = join(this.directory, file);\n          const content = await readFile(filePath, \"utf-8\");\n          const entry: StorageEntry = JSON.parse(content);\n\n          if (entry.expiresAt < now) {\n            await rm(filePath);\n          }\n        } catch (error) {\n          // If file is corrupted or can't be read, delete it\n          console.warn(`Failed to read/parse file ${file}, deleting:`, error);\n          try {\n            await rm(join(this.directory, file));\n          } catch {\n            // Ignore deletion errors\n          }\n        }\n      }\n    } catch (error) {\n      console.error(\"Cleanup failed:\", error);\n    }\n  }\n\n  /**\n   * Delete a value\n   */\n  async delete(key: string): Promise<void> {\n    const filePath = this.getFilePath(key);\n    try {\n      await rm(filePath);\n    } catch (error) {\n      // File might not exist, which is fine\n      if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n        console.error(`Failed to delete key ${key}:`, error);\n      }\n    }\n  }\n\n  /**\n   * Destroy the storage and clear cleanup interval\n   */\n  destroy(): void {\n    if (this.cleanupInterval) {\n      clearInterval(this.cleanupInterval);\n      this.cleanupInterval = null;\n    }\n  }\n\n  /**\n   * Retrieve a value\n   */\n  async get(key: string): Promise<null | unknown> {\n    const filePath = this.getFilePath(key);\n\n    try {\n      const content = await readFile(filePath, \"utf-8\");\n      const entry: StorageEntry = JSON.parse(content);\n\n      // Check if expired\n      if (entry.expiresAt < Date.now()) {\n        await rm(filePath);\n        return null;\n      }\n\n      return entry.value;\n    } catch (error) {\n      // File doesn't exist or is corrupted\n      if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n        return null;\n      }\n      console.error(`Failed to read key ${key}:`, error);\n      return null;\n    }\n  }\n\n  /**\n   * Save a value with optional TTL\n   */\n  async save(key: string, value: unknown, ttl?: number): Promise<void> {\n    await this.ensureDirectory();\n\n    const filePath = this.getFilePath(key);\n    const expiresAt = ttl ? Date.now() + ttl * 1000 : Number.MAX_SAFE_INTEGER;\n\n    const entry: StorageEntry = {\n      expiresAt,\n      value,\n    };\n\n    try {\n      await writeFile(filePath, JSON.stringify(entry, null, 2), \"utf-8\");\n    } catch (error) {\n      console.error(`Failed to save key ${key}:`, error);\n      throw error;\n    }\n  }\n\n  /**\n   * Get the number of stored items\n   */\n  async size(): Promise<number> {\n    try {\n      await this.ensureDirectory();\n      const files = await readdir(this.directory);\n      return files.filter((f) => f.endsWith(this.fileExtension)).length;\n    } catch {\n      return 0;\n    }\n  }\n\n  /**\n   * Atomically retrieve a value and delete it.\n   *\n   * rename() is atomic on POSIX filesystems, so when several processes race\n   * for the same key exactly one rename succeeds and the losers see ENOENT.\n   * That is what makes single-use authorization codes safe when more than one\n   * process shares the directory.\n   */\n  async take(key: string): Promise<null | unknown> {\n    const filePath = this.getFilePath(key);\n    // Keep the configured extension on the claim file so that if the process\n    // dies between the rename and the unlink, cleanup() still reaps it once\n    // the entry expires rather than leaving it on disk forever.\n    const claimPath = `${filePath}.${randomUUID()}.claim${this.fileExtension}`;\n\n    try {\n      await rename(filePath, claimPath);\n    } catch (error) {\n      if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n        console.error(`Failed to take key ${key}:`, error);\n      }\n      return null;\n    }\n\n    try {\n      const content = await readFile(claimPath, \"utf-8\");\n      const entry: StorageEntry = JSON.parse(content);\n\n      return entry.expiresAt < Date.now() ? null : entry.value;\n    } catch (error) {\n      console.error(`Failed to read key ${key}:`, error);\n      return null;\n    } finally {\n      await rm(claimPath, { force: true });\n    }\n  }\n\n  /**\n   * Ensure storage directory exists\n   */\n  private async ensureDirectory(): Promise<void> {\n    try {\n      const stats = await stat(this.directory);\n      if (!stats.isDirectory()) {\n        throw new Error(`Path ${this.directory} exists but is not a directory`);\n      }\n    } catch (error) {\n      if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n        await mkdir(this.directory, { recursive: true });\n      } else {\n        throw error;\n      }\n    }\n  }\n\n  /**\n   * Get file path for a key\n   */\n  private getFilePath(key: string): string {\n    // Sanitize key to prevent directory traversal\n    const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n    return join(this.directory, `${sanitizedKey}${this.fileExtension}`);\n  }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * JWKS (JSON Web Key Set) Verifier\n * Provides JWT verification using public keys from JWKS endpoints\n *\n * Requires the 'jose' package as an optional peer dependency.\n * Install with: npm install jose\n */\n\nimport type { TokenVerificationResult, TokenVerifier } from \"../types.js\";\nimport type { JWTClaims } from \"./jwtIssuer.js\";\n\n/**\n * Token verification result\n */\nexport interface JWKSVerificationResult {\n  claims?: JWTClaims;\n  error?: string;\n  valid: boolean;\n}\n\n/**\n * JWKS configuration options\n */\nexport interface JWKSVerifierConfig {\n  /**\n   * Expected token audience\n   */\n  audience?: string;\n\n  /**\n   * Cache duration for JWKS keys in milliseconds\n   * @default 3600000 (1 hour)\n   */\n  cacheDuration?: number;\n\n  /**\n   * Cooldown duration between JWKS refetches in milliseconds\n   * @default 30000 (30 seconds)\n   */\n  cooldownDuration?: number;\n\n  /**\n   * Expected token issuer\n   */\n  issuer?: string;\n\n  /**\n   * JWKS endpoint URL (e.g., https://provider.com/.well-known/jwks.json)\n   */\n  jwksUri: string;\n}\n\n/**\n * JWKS Verifier\n * Verifies JWTs using public keys from a JWKS endpoint\n *\n * This class requires the 'jose' package to be installed:\n * ```bash\n * npm install jose\n * ```\n *\n * @example\n * ```typescript\n * const verifier = new JWKSVerifier({\n *   jwksUri: 'https://accounts.google.com/.well-known/jwks.json',\n *   audience: 'your-client-id',\n *   issuer: 'https://accounts.google.com'\n * });\n *\n * const result = await verifier.verify(token);\n * if (result.valid) {\n *   console.log('Token claims:', result.claims);\n * }\n * ```\n */\nexport class JWKSVerifier implements TokenVerifier {\n  private config: Required<JWKSVerifierConfig>;\n  private jose: any;\n  private joseLoaded = false;\n  private jwksCache: any;\n\n  constructor(config: JWKSVerifierConfig) {\n    this.config = {\n      cacheDuration: 3600000, // 1 hour\n      cooldownDuration: 30000, // 30 seconds\n      ...config,\n      audience: config.audience || \"\",\n      issuer: config.issuer || \"\",\n    };\n  }\n\n  /**\n   * Get the JWKS URI being used\n   */\n  getJwksUri(): string {\n    return this.config.jwksUri;\n  }\n\n  /**\n   * Refresh the JWKS cache\n   * Useful if you need to force a key refresh\n   */\n  async refreshKeys(): Promise<void> {\n    await this.loadJose();\n\n    // Recreate the JWKS cache to force a refresh\n    this.jwksCache = this.jose.createRemoteJWKSet(\n      new URL(this.config.jwksUri),\n      {\n        cacheMaxAge: this.config.cacheDuration,\n        cooldownDuration: this.config.cooldownDuration,\n      },\n    );\n  }\n\n  /**\n   * Verify a JWT token using JWKS\n   *\n   * @param token - The JWT token to verify\n   * @returns Verification result with claims if valid\n   *\n   * @example\n   * ```typescript\n   * const result = await verifier.verify(token);\n   * if (result.valid) {\n   *   console.log('User:', result.claims?.client_id);\n   * } else {\n   *   console.error('Invalid token:', result.error);\n   * }\n   * ```\n   */\n  async verify(token: string): Promise<TokenVerificationResult> {\n    try {\n      // Ensure jose is loaded\n      await this.loadJose();\n\n      // Verify the token using JWKS\n      const verifyOptions: any = {};\n\n      if (this.config.audience) {\n        verifyOptions.audience = this.config.audience;\n      }\n\n      if (this.config.issuer) {\n        verifyOptions.issuer = this.config.issuer;\n      }\n\n      const { payload } = await this.jose.jwtVerify(\n        token,\n        this.jwksCache,\n        verifyOptions,\n      );\n\n      // Map jose claims to TokenVerificationResult format\n      // Store all claims as Record<string, unknown> for compatibility\n      const claims: Record<string, unknown> = {\n        aud: payload.aud,\n        client_id: payload.client_id || payload.sub,\n        exp: payload.exp,\n        iat: payload.iat,\n        iss: payload.iss,\n        jti: payload.jti || \"\",\n        scope: this.parseScope(payload.scope),\n        ...payload, // Include all other claims\n      };\n\n      return {\n        claims,\n        valid: true,\n      };\n    } catch (error: any) {\n      return {\n        error: error.message || \"Token verification failed\",\n        valid: false,\n      };\n    }\n  }\n\n  /**\n   * Lazy load the jose library\n   * Only loads when verification is first attempted\n   */\n  private async loadJose(): Promise<void> {\n    if (this.joseLoaded) {\n      return;\n    }\n\n    try {\n      this.jose = await import(\"jose\");\n      this.joseLoaded = true;\n\n      // Create the JWKS cache with the configured URI\n      this.jwksCache = this.jose.createRemoteJWKSet(\n        new URL(this.config.jwksUri),\n        {\n          cacheMaxAge: this.config.cacheDuration,\n          cooldownDuration: this.config.cooldownDuration,\n        },\n      );\n    } catch (error: any) {\n      throw new Error(\n        `JWKS verification requires the 'jose' package.\\n` +\n          `Install it with: npm install jose\\n\\n` +\n          `If you don't need JWKS support, use HS256 signing instead (default).\\n\\n` +\n          `Original error: ${error.message}`,\n      );\n    }\n  }\n\n  /**\n   * Parse scope from token payload\n   * Handles both string (space-separated) and array formats\n   */\n  private parseScope(scope: unknown): string[] {\n    if (!scope) {\n      return [];\n    }\n\n    if (typeof scope === \"string\") {\n      return scope.split(\" \").filter(Boolean);\n    }\n\n    if (Array.isArray(scope)) {\n      return scope;\n    }\n\n    return [];\n  }\n}\n"]}