@fruggr/zendesk-mcp-server 3.3.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5198,7 +5198,7 @@ The above copyright notice and this permission notice shall be included in all c
5198
5198
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5199
5199
  ```
5200
5200
 
5201
- ## zod@4.6.5
5201
+ ## zod@4.6.6
5202
5202
 
5203
5203
  License: MIT
5204
5204
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { _ as __commonJSMin, a as verifyBearerToken, b as __toESM, c as zendeskGet, f as getOAuthUrls, h as errnoCode, i as WebStandardStreamableHTTPServerTransport, l as REQUEST_TIMEOUT_MS, m as createStartupError, o as OAuthError, p as STARTUP_DOCS, r as silentLogger, s as OAuthErrorCode, t as createMcpServer, u as deadlineSignal, v as __exportAll, y as __require } from "./index.js";
3
- import { d as escapeHtml, l as requestedScope, n as configDir, o as generateCodeChallenge, s as generateCodeVerifier, u as supportedScopes } from "./token-persistence-BUHh23BM.js";
2
+ import { C as __exportAll, S as __commonJSMin, T as __toESM, _ as OAuthErrorCode, a as silentLogger, b as errnoCode, c as deadlineSignal, d as DEFAULT_MAX_REQUEST_BODY_SIZE, f as WebStandardStreamableHTTPServerTransport, g as OAuthError, h as verifyBearerToken, m as isLegacyRequest, n as defaultServerFactory, o as zendeskGet, p as createMcpHandler, r as createRequestStateCodec, s as REQUEST_TIMEOUT_MS, t as createServerCaches, u as getOAuthUrls, v as STARTUP_DOCS, w as __require, y as createStartupError } from "./index.js";
3
+ import { d as escapeHtml, l as requestedScope, n as configDir, o as generateCodeChallenge, s as generateCodeVerifier, u as supportedScopes } from "./token-persistence-r0v9RO3q.js";
4
4
  import * as util$1 from "node:util";
5
5
  import { format, inspect } from "node:util";
6
6
  import * as crypto$2 from "node:crypto";
@@ -667,6 +667,185 @@ var NodeStreamableHTTPServerTransport = class {
667
667
  this._webStandardTransport.closeStandaloneSSEStream();
668
668
  }
669
669
  };
670
+ /**
671
+ * The rejection {@linkcode toWebRequest} produces for a body over the size
672
+ * limit, recognisable by `name` and `status` without matching message text.
673
+ */
674
+ var RequestBodyTooLargeError = class extends Error {
675
+ status = 413;
676
+ constructor(maxBytes) {
677
+ super(`Payload Too Large: Request body must not exceed ${maxBytes} bytes`);
678
+ this.name = "RequestBodyTooLargeError";
679
+ }
680
+ };
681
+ function resolveMaxRequestBodySize(value) {
682
+ if (value === void 0) return DEFAULT_MAX_REQUEST_BODY_SIZE;
683
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new RangeError(`maxRequestBodySize must be a positive number of bytes, got ${String(value)}`);
684
+ return value;
685
+ }
686
+ /**
687
+ * Adapts a web-standard MCP handler (`handler.fetch`) to a Node.js
688
+ * `(req, res, parsedBody?)` request handler. The returned function converts the
689
+ * Node request to a web-standard `Request`, calls `handler.fetch`, then writes
690
+ * the `Response` back to `res` (honoring write backpressure for streamed SSE
691
+ * responses).
692
+ *
693
+ * `req.auth` is forwarded as the handler's pass-through `authInfo`. A function
694
+ * third argument (Express's `next`) is ignored, never treated as a body.
695
+ *
696
+ * Pass `{ onerror }` to observe the adapter-level error fallback (request
697
+ * conversion / `handler.fetch` throw) before the `500` response is written.
698
+ */
699
+ function toNodeHandler(handler, opts) {
700
+ const maxRequestBodySize = resolveMaxRequestBodySize(opts?.maxRequestBodySize);
701
+ return async (req, res, parsedBody) => {
702
+ if (typeof parsedBody === "function") parsedBody = void 0;
703
+ let finished = false;
704
+ const abort = new AbortController();
705
+ res.on("close", () => {
706
+ if (!finished) abort.abort();
707
+ });
708
+ if (res.destroyed === true) abort.abort();
709
+ let response;
710
+ try {
711
+ const request = await toWebRequest(req, parsedBody, {
712
+ signal: abort.signal,
713
+ maxRequestBodySize
714
+ });
715
+ response = await handler.fetch(request, {
716
+ ...req.auth !== void 0 && { authInfo: req.auth },
717
+ ...parsedBody !== void 0 && { parsedBody }
718
+ });
719
+ } catch (error) {
720
+ if (error instanceof RequestBodyTooLargeError) response = Response.json({
721
+ jsonrpc: "2.0",
722
+ error: {
723
+ code: -32e3,
724
+ message: error.message
725
+ },
726
+ id: null
727
+ }, {
728
+ status: 413,
729
+ headers: { connection: "close" }
730
+ });
731
+ else {
732
+ try {
733
+ opts?.onerror?.(error instanceof Error ? error : new Error(String(error)));
734
+ } catch {}
735
+ response = internalServerErrorResponse(echoableRequestId(parsedBody));
736
+ }
737
+ }
738
+ const headers = {};
739
+ for (const [name, value] of response.headers) headers[name] = value;
740
+ res.writeHead(response.status, headers);
741
+ if (response.body === null) {
742
+ finished = true;
743
+ res.end();
744
+ return;
745
+ }
746
+ let drainResolve;
747
+ const releaseDrainWait = () => {
748
+ drainResolve?.();
749
+ drainResolve = void 0;
750
+ };
751
+ res.on("drain", releaseDrainWait);
752
+ const closed = new Promise((resolve) => {
753
+ abort.signal.addEventListener("abort", () => resolve(), { once: true });
754
+ });
755
+ try {
756
+ for await (const chunk of response.body) {
757
+ if (abort.signal.aborted) break;
758
+ if (res.write(chunk) === false) await Promise.race([new Promise((resolve) => {
759
+ drainResolve = resolve;
760
+ }), closed]);
761
+ }
762
+ } catch {}
763
+ finished = true;
764
+ res.end();
765
+ };
766
+ }
767
+ function singleHeaderValue(value) {
768
+ return Array.isArray(value) ? value[0] : value;
769
+ }
770
+ /**
771
+ * Convert a Node.js `IncomingMessage` (duck-typed — an Express `req` works) to
772
+ * the web-standard `Request` that `handler.fetch()` and `isLegacyRequest()`
773
+ * take. This is the conversion {@linkcode toNodeHandler} performs internally,
774
+ * exported for hand-wired compositions:
775
+ *
776
+ * ```ts
777
+ * const probe = await toWebRequest(req, req.body);
778
+ * await ((await isLegacyRequest(probe)) ? legacy(req, res) : modern(req, res, req.body));
779
+ * ```
780
+ *
781
+ * With no `parsedBody` the Node stream is read to completion (up to
782
+ * `maxRequestBodySize`, 4 MiB by default — a longer body rejects with an error
783
+ * carrying `name: 'RequestBodyTooLargeError'` and `status: 413`) — read the
784
+ * body from the returned `Request` afterwards, not from `req`. When a body
785
+ * parser already consumed the stream (`express.json()`), pass the parsed value
786
+ * as `parsedBody` and nothing is read from `req`.
787
+ */
788
+ async function toWebRequest(req, parsedBody, options) {
789
+ const maxRequestBodySize = resolveMaxRequestBodySize(options?.maxRequestBodySize);
790
+ const method = (req.method ?? "GET").toUpperCase();
791
+ const url = `http://${singleHeaderValue(req.headers["host"]) ?? singleHeaderValue(req.headers[":authority"]) ?? "localhost"}${req.url ?? "/"}`;
792
+ const headers = new Headers();
793
+ for (const [name, value] of Object.entries(req.headers)) {
794
+ if (value === void 0 || name.startsWith(":")) continue;
795
+ if (Array.isArray(value)) for (const item of value) headers.append(name, item);
796
+ else headers.set(name, value);
797
+ }
798
+ let body;
799
+ if (method !== "GET" && method !== "HEAD") if (parsedBody === void 0) {
800
+ if (Number(singleHeaderValue(req.headers["content-length"])) > maxRequestBodySize) throw new RequestBodyTooLargeError(maxRequestBodySize);
801
+ const decoder = new TextDecoder();
802
+ let collected = "";
803
+ let received = 0;
804
+ for await (const chunk of req) {
805
+ received += typeof chunk === "string" ? new TextEncoder().encode(chunk).byteLength : chunk.byteLength;
806
+ if (received > maxRequestBodySize) throw new RequestBodyTooLargeError(maxRequestBodySize);
807
+ collected += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
808
+ }
809
+ collected += decoder.decode();
810
+ if (collected.length > 0) body = collected;
811
+ } else {
812
+ const serialized = JSON.stringify(parsedBody);
813
+ headers.delete("content-encoding");
814
+ headers.delete("transfer-encoding");
815
+ if (serialized === void 0) headers.delete("content-length");
816
+ else {
817
+ body = serialized;
818
+ headers.set("content-length", String(new TextEncoder().encode(serialized).byteLength));
819
+ }
820
+ }
821
+ return new Request(url, {
822
+ method,
823
+ headers,
824
+ ...options?.signal !== void 0 && { signal: options.signal },
825
+ ...body !== void 0 && { body }
826
+ });
827
+ }
828
+ /**
829
+ * The JSON-RPC id to echo on an adapter-built error response: the body's `id`
830
+ * when the body is a single JSON-RPC request whose id is a string or number,
831
+ * `null` otherwise.
832
+ */
833
+ function echoableRequestId(body) {
834
+ if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
835
+ const { method, id } = body;
836
+ if (typeof method !== "string") return null;
837
+ return typeof id === "string" || typeof id === "number" ? id : null;
838
+ }
839
+ function internalServerErrorResponse(id) {
840
+ return Response.json({
841
+ jsonrpc: "2.0",
842
+ error: {
843
+ code: -32603,
844
+ message: "Internal server error"
845
+ },
846
+ id
847
+ }, { status: 500 });
848
+ }
670
849
  //#endregion
671
850
  //#region node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/buffer_utils.js
672
851
  const encoder$1 = new TextEncoder();
@@ -2824,7 +3003,8 @@ const deriveKeySet = (master) => {
2824
3003
  kid: kidFor(master, "at-rest"),
2825
3004
  key: createSecretKey(derive(master, "at-rest"))
2826
3005
  },
2827
- cookieKey: derive(master, "cookie").toString("base64url")
3006
+ cookieKey: derive(master, "cookie").toString("base64url"),
3007
+ requestState: derive(master, "request-state")
2828
3008
  };
2829
3009
  };
2830
3010
  const deriveKeyRing = (raw) => {
@@ -36261,6 +36441,7 @@ const createAuthorizationServer = (options) => {
36261
36441
  return {
36262
36442
  issuer,
36263
36443
  resource,
36444
+ requestStateKey: ring[0].requestState,
36264
36445
  protectedResourceMetadata: {
36265
36446
  resource,
36266
36447
  authorization_servers: [issuer],
@@ -36326,6 +36507,47 @@ const prepareAuthorizationServer = async (config, logger = silentLogger) => {
36326
36507
  });
36327
36508
  };
36328
36509
  //#endregion
36510
+ //#region src/utils/grant-registry.ts
36511
+ const createGrantRegistry = ({ idleTimeoutMs, now = Date.now, onCloseError = () => void 0 }) => {
36512
+ const slots = /* @__PURE__ */ new Map();
36513
+ const drop = async (grantId, slot) => {
36514
+ slots.delete(grantId);
36515
+ try {
36516
+ await slot.entry.close();
36517
+ } catch (error) {
36518
+ onCloseError(grantId, error);
36519
+ }
36520
+ };
36521
+ return {
36522
+ acquire: (grantId, subject, create) => {
36523
+ const existing = slots.get(grantId);
36524
+ if (existing && existing.subject !== subject) return void 0;
36525
+ const slot = existing ?? {
36526
+ subject,
36527
+ entry: create(),
36528
+ lastActivityAt: now()
36529
+ };
36530
+ slot.lastActivityAt = now();
36531
+ slots.set(grantId, slot);
36532
+ return slot.entry;
36533
+ },
36534
+ close: async (grantId, entry) => {
36535
+ const slot = slots.get(grantId);
36536
+ if (slot && (entry === void 0 || slot.entry === entry)) await drop(grantId, slot);
36537
+ },
36538
+ sweep: async () => {
36539
+ const cutoff = now() - idleTimeoutMs;
36540
+ for (const [grantId, slot] of [...slots]) if (slots.get(grantId) === slot && slot.lastActivityAt <= cutoff) await drop(grantId, slot);
36541
+ },
36542
+ closeAll: async () => {
36543
+ await Promise.all([...slots].map(([grantId, slot]) => drop(grantId, slot)));
36544
+ },
36545
+ get size() {
36546
+ return slots.size;
36547
+ }
36548
+ };
36549
+ };
36550
+ //#endregion
36329
36551
  //#region src/transports/http.ts
36330
36552
  const WILDCARD_HOSTS = /* @__PURE__ */ new Set([
36331
36553
  "0.0.0.0",
@@ -36344,7 +36566,7 @@ const DEFAULT_BROWSER_MCP_CLIENT_ORIGINS = [
36344
36566
  "https://grok.com"
36345
36567
  ];
36346
36568
  const CORS_ALLOWED_METHODS = "GET, POST, DELETE, OPTIONS";
36347
- const CORS_ALLOWED_HEADERS = "Authorization, Content-Type, Accept, mcp-session-id, mcp-protocol-version, last-event-id";
36569
+ const CORS_ALLOWED_HEADERS = "Authorization, Content-Type, Accept, mcp-session-id, mcp-protocol-version, last-event-id, mcp-method, mcp-name";
36348
36570
  const CORS_EXPOSE_HEADERS = "mcp-session-id";
36349
36571
  const CORS_MAX_AGE = "600";
36350
36572
  const LOCALHOST_HOSTNAMES = /* @__PURE__ */ new Set([
@@ -36480,6 +36702,8 @@ const sendUnauthorized = (res, issuer) => {
36480
36702
  const wwwAuthenticate = `Bearer resource_metadata="${issuer}${PROTECTED_RESOURCE_PATHS[0]}", error="invalid_token", error_description="${MISSING_BEARER_MESSAGE}"`;
36481
36703
  sendJsonRpcError(res, 401, -32e3, MISSING_BEARER_MESSAGE, { "WWW-Authenticate": wwwAuthenticate });
36482
36704
  };
36705
+ const asNodeRequest = (req) => req;
36706
+ const isListenRequest = (body) => typeof body === "object" && body !== null && body.method === "subscriptions/listen";
36483
36707
  const MAX_BODY_BYTES = 4194304;
36484
36708
  const readJsonBody = (req, maxBodyBytes) => new Promise((resolve) => {
36485
36709
  const chunks = [];
@@ -36550,22 +36774,26 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36550
36774
  const sessions = /* @__PURE__ */ new Map();
36551
36775
  const idleTimeoutMs = options.sessionIdleTimeoutMs ?? SESSION_IDLE_TIMEOUT_MS;
36552
36776
  const maxBodyBytes = options.maxBodyBytes ?? 4194304;
36553
- const dispatchToSession = async (req, res, sessionId, token) => {
36777
+ const serverFactory = options.serverFactory ?? defaultServerFactory;
36778
+ const grants = createGrantRegistry({
36779
+ idleTimeoutMs,
36780
+ onCloseError: (grantId, err) => logger.warn("grant_close_failed", {
36781
+ grantId,
36782
+ error: err instanceof Error ? err.message : String(err)
36783
+ })
36784
+ });
36785
+ const effectiveConfig = (token) => !config.readOnly && token.canWrite ? config : {
36786
+ ...config,
36787
+ readOnly: true
36788
+ };
36789
+ const dispatchToSession = async (req, res, sessionId, token, body) => {
36554
36790
  const session = sessions.get(sessionId);
36555
36791
  if (!session || session.subject !== token.subject) return false;
36556
36792
  if (session.canWrite && !token.canWrite) return false;
36557
36793
  session.auth.zendeskToken = token.zendeskAccessToken;
36558
36794
  session.auth.grantId = token.grantId;
36559
36795
  session.lastActivityAt = Date.now();
36560
- const body = req.method === "POST" ? await readJsonBody(req, maxBodyBytes) : {
36561
- ok: true,
36562
- value: void 0
36563
- };
36564
- if (!body.ok) {
36565
- respondBodyError(req, res, body);
36566
- return true;
36567
- }
36568
- await session.transport.handleRequest(req, res, body.value);
36796
+ await session.transport.handleRequest(req, res, body);
36569
36797
  return true;
36570
36798
  };
36571
36799
  const openSession = async (req, res, body, token, as) => {
@@ -36573,11 +36801,20 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36573
36801
  zendeskToken: token.zendeskAccessToken,
36574
36802
  grantId: token.grantId
36575
36803
  };
36576
- const canWrite = !config.readOnly && token.canWrite;
36577
- const server = createMcpServer(canWrite ? config : {
36578
- ...config,
36579
- readOnly: true
36580
- }, () => auth.zendeskToken, logger, () => void as.revokeGrant(auth.grantId));
36804
+ const sessionConfig = effectiveConfig(token);
36805
+ const server = serverFactory({
36806
+ config: sessionConfig,
36807
+ getToken: () => auth.zendeskToken,
36808
+ logger,
36809
+ onUnauthorized: () => void as.revokeGrant(auth.grantId),
36810
+ requestStateCodec: createRequestStateCodec({
36811
+ key: as.requestStateKey,
36812
+ principal: () => auth.grantId
36813
+ }),
36814
+ notifyResourceUpdated: (uri) => {
36815
+ server.server.sendResourceUpdated({ uri }).catch(() => void 0);
36816
+ }
36817
+ });
36581
36818
  const transport = new NodeStreamableHTTPServerTransport({
36582
36819
  sessionIdGenerator: () => randomUUID(),
36583
36820
  onsessioninitialized: (newId) => {
@@ -36585,7 +36822,7 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36585
36822
  transport,
36586
36823
  auth,
36587
36824
  subject: token.subject,
36588
- canWrite,
36825
+ canWrite: !sessionConfig.readOnly,
36589
36826
  lastActivityAt: Date.now(),
36590
36827
  close: async () => {
36591
36828
  await transport.close();
@@ -36600,24 +36837,97 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36600
36837
  await server.connect(transport);
36601
36838
  await transport.handleRequest(req, res, body);
36602
36839
  };
36603
- const handleMcpRequest = async (req, res, as) => {
36604
- const token = await authenticate(req, as);
36605
- if (!token) {
36840
+ const createGrantEntry = (token, as) => {
36841
+ const { grantId } = token;
36842
+ const auth = { zendeskToken: token.zendeskAccessToken };
36843
+ const getToken = () => auth.zendeskToken;
36844
+ const onUnauthorized = () => {
36845
+ entry.revoked = true;
36846
+ as.revokeGrant(grantId);
36847
+ };
36848
+ const caches = createServerCaches(config, getToken, onUnauthorized);
36849
+ const requestStateCodec = createRequestStateCodec({
36850
+ key: as.requestStateKey,
36851
+ principal: () => grantId
36852
+ });
36853
+ const handler = createMcpHandler(({ authInfo }) => {
36854
+ const verified = authInfo?.extra?.["verified"];
36855
+ if (!verified) throw new Error("2026-07-28 request without a verified token");
36856
+ return serverFactory({
36857
+ config: effectiveConfig(verified),
36858
+ getToken,
36859
+ logger,
36860
+ onUnauthorized,
36861
+ caches,
36862
+ requestStateCodec,
36863
+ notifyResourceUpdated: (uri) => handler.notify.resourceUpdated(uri)
36864
+ });
36865
+ }, {
36866
+ legacy: "reject",
36867
+ onerror: (err) => logger.debug("mcp_request_rejected", { error: err.message })
36868
+ });
36869
+ const entry = {
36870
+ auth,
36871
+ revoked: false,
36872
+ inFlight: 0,
36873
+ handle: toNodeHandler(handler),
36874
+ close: () => handler.close()
36875
+ };
36876
+ return entry;
36877
+ };
36878
+ const serveModern = async (req, res, body, token, as) => {
36879
+ const entry = grants.acquire(token.grantId, token.subject, () => createGrantEntry(token, as));
36880
+ if (!entry) {
36606
36881
  sendUnauthorized(res, as.issuer);
36607
36882
  return;
36608
36883
  }
36884
+ entry.auth.zendeskToken = token.zendeskAccessToken;
36885
+ const request = asNodeRequest(req);
36886
+ request.auth = {
36887
+ token: "",
36888
+ clientId: token.clientId,
36889
+ scopes: [],
36890
+ expiresAt: token.expiresAt,
36891
+ extra: { verified: token }
36892
+ };
36893
+ const counted = !isListenRequest(body);
36894
+ if (counted) entry.inFlight += 1;
36895
+ try {
36896
+ await entry.handle(request, res, body);
36897
+ } finally {
36898
+ if (counted) entry.inFlight -= 1;
36899
+ if (entry.revoked && entry.inFlight === 0) await grants.close(token.grantId, entry);
36900
+ }
36901
+ };
36902
+ const serveLegacy = async (req, res, body, token, as) => {
36609
36903
  const sessionId = typeof req.headers["mcp-session-id"] === "string" ? req.headers["mcp-session-id"] : void 0;
36610
- if (sessionId && await dispatchToSession(req, res, sessionId, token)) return;
36904
+ if (sessionId && await dispatchToSession(req, res, sessionId, token, body)) return;
36611
36905
  if (req.method !== "POST") {
36612
36906
  sendJsonRpcError(res, 400, -32e3, "No active session; initialize via POST first.");
36613
36907
  return;
36614
36908
  }
36615
- const body = await readJsonBody(req, maxBodyBytes);
36616
- if (!body.ok) {
36617
- respondBodyError(req, res, body);
36909
+ await openSession(req, res, body, token, as);
36910
+ };
36911
+ const handleMcpRequest = async (req, res, as) => {
36912
+ const token = await authenticate(req, as);
36913
+ if (!token) {
36914
+ sendUnauthorized(res, as.issuer);
36915
+ return;
36916
+ }
36917
+ let body;
36918
+ if (req.method === "POST") {
36919
+ const read = await readJsonBody(req, maxBodyBytes);
36920
+ if (!read.ok) {
36921
+ respondBodyError(req, res, read);
36922
+ return;
36923
+ }
36924
+ body = read.value;
36925
+ }
36926
+ if (body !== void 0 && !await isLegacyRequest(await toWebRequest(asNodeRequest(req), body), body)) {
36927
+ await serveModern(req, res, body, token, as);
36618
36928
  return;
36619
36929
  }
36620
- await openSession(req, res, body.value, token, as);
36930
+ await serveLegacy(req, res, body, token, as);
36621
36931
  };
36622
36932
  const route = async (req, res, as) => {
36623
36933
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
@@ -36686,6 +36996,7 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36686
36996
  });
36687
36997
  }
36688
36998
  }
36999
+ await grants.sweep();
36689
37000
  };
36690
37001
  const sweeper = setInterval(() => void sweepIdleSessions(), options.sweepIntervalMs ?? SESSION_SWEEP_INTERVAL_MS);
36691
37002
  sweeper.unref();
@@ -36695,6 +37006,7 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36695
37006
  clearInterval(sweeper);
36696
37007
  await Promise.all([...sessions.values()].map((session) => session.close()));
36697
37008
  sessions.clear();
37009
+ await grants.closeAll();
36698
37010
  httpServer.closeAllConnections();
36699
37011
  await new Promise((resolve, reject) => {
36700
37012
  httpServer.close((err) => err ? reject(err) : resolve());