@fruggr/zendesk-mcp-server 3.3.0 → 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.
@@ -30,9 +30,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
30
  SOFTWARE.
31
31
  ```
32
32
 
33
- ## @modelcontextprotocol/core@2.2.0
33
+ ## @modelcontextprotocol/core@2.3.1
34
34
 
35
- License: MIT
35
+ License: Apache-2.0
36
36
 
37
37
  ```
38
38
  The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
@@ -253,9 +253,9 @@ CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
253
253
  the full license text.
254
254
  ```
255
255
 
256
- ## @modelcontextprotocol/node@2.1.0
256
+ ## @modelcontextprotocol/node@2.1.1
257
257
 
258
- License: MIT
258
+ License: Apache-2.0
259
259
 
260
260
  ```
261
261
  The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
@@ -476,9 +476,9 @@ CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
476
476
  the full license text.
477
477
  ```
478
478
 
479
- ## @modelcontextprotocol/server@2.2.0
479
+ ## @modelcontextprotocol/server@2.3.1
480
480
 
481
- License: MIT
481
+ License: Apache-2.0
482
482
 
483
483
  ```
484
484
  The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
@@ -2629,7 +2629,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2629
2629
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2630
2630
  ```
2631
2631
 
2632
- ## mdast-util-find-and-replace@3.0.2
2632
+ ## mdast-util-find-and-replace@3.0.3
2633
2633
 
2634
2634
  License: MIT
2635
2635
 
@@ -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 __toESM, a as zendeskGet, d as createStartupError, f as errnoCode, g as __require, h as __exportAll, i as WebStandardStreamableHTTPServerTransport, l as getOAuthUrls, m as __commonJSMin, o as REQUEST_TIMEOUT_MS, r as silentLogger, s as deadlineSignal, t as createMcpServer, u as STARTUP_DOCS } 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-BeetFqPF.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";
@@ -16,7 +16,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
16
16
  import { accessSync, chmodSync, constants, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
17
17
  import { Http2ServerRequest, constants as constants$1 } from "http2";
18
18
  import crypto$1 from "crypto";
19
- //#region node_modules/.pnpm/@hono+node-server@1.19.17_hono@4.13.12/node_modules/@hono/node-server/dist/index.mjs
19
+ //#region node_modules/.pnpm/@hono+node-server@1.19.17_hono@4.13.13/node_modules/@hono/node-server/dist/index.mjs
20
20
  var RequestError = class extends Error {
21
21
  constructor(message, options) {
22
22
  super(message, options);
@@ -504,7 +504,7 @@ var getRequestListener = (fetchCallback, options = {}) => {
504
504
  };
505
505
  };
506
506
  //#endregion
507
- //#region node_modules/.pnpm/@modelcontextprotocol+node@2.1.0_@modelcontextprotocol+server@2.2.0_hono@4.13.12/node_modules/@modelcontextprotocol/node/dist/index.mjs
507
+ //#region node_modules/.pnpm/@modelcontextprotocol+node@2.1.1_@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/node/dist/index.mjs
508
508
  /**
509
509
  * Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
510
510
  * It supports both SSE streaming and direct HTTP responses.
@@ -543,8 +543,12 @@ var getRequestListener = (fetchCallback, options = {}) => {
543
543
  *
544
544
  * @example Using with a pre-parsed request body (e.g. Express)
545
545
  * ```ts source="./streamableHttp.examples.ts#NodeStreamableHTTPServerTransport_express"
546
- * app.post('/mcp', (req, res) => {
547
- * transport.handleRequest(req, res, req.body);
546
+ * app.post('/mcp', async (req, res) => {
547
+ * // Stateless example: create a server and a transport per request.
548
+ * const server = new McpServer({ name: 'my-server', version: '1.0.0' });
549
+ * const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
550
+ * await server.connect(transport);
551
+ * await transport.handleRequest(req, res, req.body);
548
552
  * });
549
553
  * ```
550
554
  */
@@ -663,6 +667,185 @@ var NodeStreamableHTTPServerTransport = class {
663
667
  this._webStandardTransport.closeStandaloneSSEStream();
664
668
  }
665
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
+ }
666
849
  //#endregion
667
850
  //#region node_modules/.pnpm/jose@6.2.12/node_modules/jose/dist/webapi/lib/buffer_utils.js
668
851
  const encoder$1 = new TextEncoder();
@@ -2820,7 +3003,8 @@ const deriveKeySet = (master) => {
2820
3003
  kid: kidFor(master, "at-rest"),
2821
3004
  key: createSecretKey(derive(master, "at-rest"))
2822
3005
  },
2823
- cookieKey: derive(master, "cookie").toString("base64url")
3006
+ cookieKey: derive(master, "cookie").toString("base64url"),
3007
+ requestState: derive(master, "request-state")
2824
3008
  };
2825
3009
  };
2826
3010
  const deriveKeyRing = (raw) => {
@@ -36178,16 +36362,16 @@ const resolveMasterSecret = (input, logger = silentLogger) => {
36178
36362
  //#endregion
36179
36363
  //#region src/auth/server/authorization-server.ts
36180
36364
  const decoder = new TextDecoder();
36181
- const asVerified = (claims, issuer, resource) => {
36182
- const audiences = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
36183
- if (!(claims.iss === issuer && audiences.includes(resource) && typeof claims.exp === "number" && claims.exp * 1e3 > Date.now() && typeof claims.zd === "string" && typeof claims.gid === "string")) return void 0;
36365
+ const asVerified = (claims, issuer) => {
36366
+ if (!(claims.iss === issuer && typeof claims.exp === "number" && typeof claims.zd === "string" && typeof claims.gid === "string")) return void 0;
36184
36367
  return {
36185
36368
  zendeskAccessToken: claims.zd,
36186
36369
  grantId: claims.gid,
36187
36370
  clientId: String(claims.client_id),
36188
36371
  subject: String(claims.sub),
36189
36372
  canWrite: typeof claims.scope === "string" && claims.scope.split(" ").includes("write"),
36190
- expiresAt: claims.exp
36373
+ expiresAt: claims.exp,
36374
+ resource: typeof claims.aud === "string" ? claims.aud : void 0
36191
36375
  };
36192
36376
  };
36193
36377
  const createAuthorizationServer = (options) => {
@@ -36252,11 +36436,12 @@ const createAuthorizationServer = (options) => {
36252
36436
  const keySet = ring.find((set) => set.accessToken.kid === kid);
36253
36437
  if (!keySet) return void 0;
36254
36438
  const { plaintext } = await compactDecrypt(bearer, keySet.accessToken.key);
36255
- return asVerified(JSON.parse(decoder.decode(plaintext)), issuer, resource);
36439
+ return asVerified(JSON.parse(decoder.decode(plaintext)), issuer);
36256
36440
  };
36257
36441
  return {
36258
36442
  issuer,
36259
36443
  resource,
36444
+ requestStateKey: ring[0].requestState,
36260
36445
  protectedResourceMetadata: {
36261
36446
  resource,
36262
36447
  authorization_servers: [issuer],
@@ -36322,6 +36507,47 @@ const prepareAuthorizationServer = async (config, logger = silentLogger) => {
36322
36507
  });
36323
36508
  };
36324
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
36325
36551
  //#region src/transports/http.ts
36326
36552
  const WILDCARD_HOSTS = /* @__PURE__ */ new Set([
36327
36553
  "0.0.0.0",
@@ -36340,7 +36566,7 @@ const DEFAULT_BROWSER_MCP_CLIENT_ORIGINS = [
36340
36566
  "https://grok.com"
36341
36567
  ];
36342
36568
  const CORS_ALLOWED_METHODS = "GET, POST, DELETE, OPTIONS";
36343
- 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";
36344
36570
  const CORS_EXPOSE_HEADERS = "mcp-session-id";
36345
36571
  const CORS_MAX_AGE = "600";
36346
36572
  const LOCALHOST_HOSTNAMES = /* @__PURE__ */ new Set([
@@ -36414,11 +36640,36 @@ const resolvePublicUrl = (config, logger = silentLogger) => {
36414
36640
  return `http://${config.host}:${config.port}`;
36415
36641
  };
36416
36642
  const MISSING_BEARER_MESSAGE = "Missing or invalid access token. Sign in through this server: its OAuth authorization server is named in the protected resource metadata.";
36417
- const extractBearer = (request) => {
36643
+ const SPACES = / +/;
36644
+ /**
36645
+ * The access token on this request, or `undefined` when it must be refused.
36646
+ * The SDK parses the header and checks the audience against our `/mcp`
36647
+ * resource (`expectedResource`) and the expiry; decrypting and every other
36648
+ * claim stay with the authorization server.
36649
+ */
36650
+ const authenticate = async (request, as) => {
36418
36651
  const header = request.headers["authorization"];
36419
- if (typeof header !== "string") return void 0;
36420
- if (!header.toLowerCase().startsWith("bearer ")) return void 0;
36421
- return header.slice(7).trim();
36652
+ const verifier = { verifyAccessToken: async (bearer) => {
36653
+ const token = await as.verifyAccessToken(bearer);
36654
+ if (!token) throw new OAuthError(OAuthErrorCode.InvalidToken, "Invalid access token");
36655
+ return {
36656
+ token: bearer,
36657
+ clientId: token.clientId,
36658
+ scopes: [],
36659
+ expiresAt: token.expiresAt,
36660
+ ...token.resource && URL.canParse(token.resource) && { resource: new URL(token.resource) },
36661
+ extra: { verified: token }
36662
+ };
36663
+ } };
36664
+ try {
36665
+ const normalized = typeof header === "string" ? header.replace(SPACES, " ") : void 0;
36666
+ return (await verifyBearerToken(normalized, {
36667
+ verifier,
36668
+ expectedResource: new URL(as.resource)
36669
+ })).extra?.["verified"];
36670
+ } catch {
36671
+ return;
36672
+ }
36422
36673
  };
36423
36674
  const PROTECTED_RESOURCE_PATHS = ["/.well-known/oauth-protected-resource/mcp", "/.well-known/oauth-protected-resource"];
36424
36675
  const sendJson = (res, status, body) => {
@@ -36451,6 +36702,8 @@ const sendUnauthorized = (res, issuer) => {
36451
36702
  const wwwAuthenticate = `Bearer resource_metadata="${issuer}${PROTECTED_RESOURCE_PATHS[0]}", error="invalid_token", error_description="${MISSING_BEARER_MESSAGE}"`;
36452
36703
  sendJsonRpcError(res, 401, -32e3, MISSING_BEARER_MESSAGE, { "WWW-Authenticate": wwwAuthenticate });
36453
36704
  };
36705
+ const asNodeRequest = (req) => req;
36706
+ const isListenRequest = (body) => typeof body === "object" && body !== null && body.method === "subscriptions/listen";
36454
36707
  const MAX_BODY_BYTES = 4194304;
36455
36708
  const readJsonBody = (req, maxBodyBytes) => new Promise((resolve) => {
36456
36709
  const chunks = [];
@@ -36521,22 +36774,26 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36521
36774
  const sessions = /* @__PURE__ */ new Map();
36522
36775
  const idleTimeoutMs = options.sessionIdleTimeoutMs ?? SESSION_IDLE_TIMEOUT_MS;
36523
36776
  const maxBodyBytes = options.maxBodyBytes ?? 4194304;
36524
- 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) => {
36525
36790
  const session = sessions.get(sessionId);
36526
36791
  if (!session || session.subject !== token.subject) return false;
36527
36792
  if (session.canWrite && !token.canWrite) return false;
36528
36793
  session.auth.zendeskToken = token.zendeskAccessToken;
36529
36794
  session.auth.grantId = token.grantId;
36530
36795
  session.lastActivityAt = Date.now();
36531
- const body = req.method === "POST" ? await readJsonBody(req, maxBodyBytes) : {
36532
- ok: true,
36533
- value: void 0
36534
- };
36535
- if (!body.ok) {
36536
- respondBodyError(req, res, body);
36537
- return true;
36538
- }
36539
- await session.transport.handleRequest(req, res, body.value);
36796
+ await session.transport.handleRequest(req, res, body);
36540
36797
  return true;
36541
36798
  };
36542
36799
  const openSession = async (req, res, body, token, as) => {
@@ -36544,11 +36801,20 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36544
36801
  zendeskToken: token.zendeskAccessToken,
36545
36802
  grantId: token.grantId
36546
36803
  };
36547
- const canWrite = !config.readOnly && token.canWrite;
36548
- const server = createMcpServer(canWrite ? config : {
36549
- ...config,
36550
- readOnly: true
36551
- }, () => 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
+ });
36552
36818
  const transport = new NodeStreamableHTTPServerTransport({
36553
36819
  sessionIdGenerator: () => randomUUID(),
36554
36820
  onsessioninitialized: (newId) => {
@@ -36556,7 +36822,7 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36556
36822
  transport,
36557
36823
  auth,
36558
36824
  subject: token.subject,
36559
- canWrite,
36825
+ canWrite: !sessionConfig.readOnly,
36560
36826
  lastActivityAt: Date.now(),
36561
36827
  close: async () => {
36562
36828
  await transport.close();
@@ -36571,25 +36837,97 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36571
36837
  await server.connect(transport);
36572
36838
  await transport.handleRequest(req, res, body);
36573
36839
  };
36574
- const handleMcpRequest = async (req, res, as) => {
36575
- const bearer = extractBearer(req);
36576
- const token = bearer ? await as.verifyAccessToken(bearer) : void 0;
36577
- 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) {
36578
36881
  sendUnauthorized(res, as.issuer);
36579
36882
  return;
36580
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) => {
36581
36903
  const sessionId = typeof req.headers["mcp-session-id"] === "string" ? req.headers["mcp-session-id"] : void 0;
36582
- if (sessionId && await dispatchToSession(req, res, sessionId, token)) return;
36904
+ if (sessionId && await dispatchToSession(req, res, sessionId, token, body)) return;
36583
36905
  if (req.method !== "POST") {
36584
36906
  sendJsonRpcError(res, 400, -32e3, "No active session; initialize via POST first.");
36585
36907
  return;
36586
36908
  }
36587
- const body = await readJsonBody(req, maxBodyBytes);
36588
- if (!body.ok) {
36589
- 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);
36590
36928
  return;
36591
36929
  }
36592
- await openSession(req, res, body.value, token, as);
36930
+ await serveLegacy(req, res, body, token, as);
36593
36931
  };
36594
36932
  const route = async (req, res, as) => {
36595
36933
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
@@ -36658,6 +36996,7 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36658
36996
  });
36659
36997
  }
36660
36998
  }
36999
+ await grants.sweep();
36661
37000
  };
36662
37001
  const sweeper = setInterval(() => void sweepIdleSessions(), options.sweepIntervalMs ?? SESSION_SWEEP_INTERVAL_MS);
36663
37002
  sweeper.unref();
@@ -36667,6 +37006,7 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
36667
37006
  clearInterval(sweeper);
36668
37007
  await Promise.all([...sessions.values()].map((session) => session.close()));
36669
37008
  sessions.clear();
37009
+ await grants.closeAll();
36670
37010
  httpServer.closeAllConnections();
36671
37011
  await new Promise((resolve, reject) => {
36672
37012
  httpServer.close((err) => err ? reject(err) : resolve());