@fruggr/zendesk-mcp-server 3.2.1 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7546,6 +7546,92 @@ const isStartupError = (err) => err instanceof Error && err.name === "StartupErr
7546
7546
  /** The errno code of a failed filesystem call, for the message. */
7547
7547
  const errnoCode = (err) => err?.code ?? "unknown error";
7548
7548
  //#endregion
7549
+ //#region src/utils/validation.ts
7550
+ const formatPath = (path) => path.map(String).join(".");
7551
+ const formatIssue$1 = (issue) => issue.path.length > 0 ? `${formatPath(issue.path)}: ${issue.message}` : issue.message;
7552
+ /**
7553
+ * `path: message` per issue, joined by `; ` since zod messages carry commas.
7554
+ * Ours rather than the SDK's (not exported): one wording for every mode and for
7555
+ * startup config errors, stable across SDK bumps.
7556
+ */
7557
+ const formatIssues = (issues) => issues.map(formatIssue$1).join("; ");
7558
+ const unwrap$1 = (node) => node._zod.def.innerType ? unwrap$1(node._zod.def.innerType) : node;
7559
+ /**
7560
+ * Keys declared by the object at `path`, or undefined when no plain object sits
7561
+ * there (a record, say, whose keys zod checks against an enum).
7562
+ */
7563
+ const keysAt = (node, path) => {
7564
+ const { def } = unwrap$1(node)._zod;
7565
+ if (path.length === 0) return def.shape && Object.keys(def.shape);
7566
+ const [head, ...rest] = path;
7567
+ const child = typeof head === "number" ? def.element : def.shape?.[String(head)];
7568
+ return child && keysAt(child, rest);
7569
+ };
7570
+ const keyList = (keys) => [...keys].sort().join(", ") || "(none)";
7571
+ /** One sentence per object holding unknown keys, the top level first. */
7572
+ const unknownKeysMessage = (schema, issues, topLevelKeys) => [...issues].sort((a, b) => a.path.length - b.path.length).map(({ path, keys }) => {
7573
+ if (path.length === 0) return `Unknown parameter(s): ${keys.join(", ")}. Valid parameters: ${topLevelKeys}.`;
7574
+ const parent = formatPath(path);
7575
+ const unknown = `Unknown parameter(s): ${keys.map((key) => `${parent}.${key}`).join(", ")}.`;
7576
+ const valid = keysAt(schema, path);
7577
+ return valid ? `${unknown} Valid parameters for ${parent}: ${keyList(valid)}.` : unknown;
7578
+ }).join(" ");
7579
+ /**
7580
+ * Build a strict validator for a tool's input schema, computing the strict
7581
+ * schema and the valid-key list once at construction rather than per call.
7582
+ *
7583
+ * Zod objects default to `strip`, which silently drops unknown keys. That hid
7584
+ * #100: a caller passing `per_page` to list_tickets (whose parameter is
7585
+ * `page_size`) had the key dropped, so a large unpaginated page came back. The
7586
+ * validator rejects unknown keys and names both the offending ones and the
7587
+ * valid parameters, so a misremembered name fails loudly. Any other failure is
7588
+ * one `path: message` per issue (#206). When both occur only the unknown keys
7589
+ * are reported: a guessed name is the one error a caller cannot spot by
7590
+ * re-reading its own arguments.
7591
+ */
7592
+ const createStrictValidator = (schema, strict = schema.strict()) => {
7593
+ const topLevelKeys = keyList(Object.keys(schema.shape));
7594
+ return (params) => {
7595
+ const result = strict.safeParse(params);
7596
+ if (result.success) return {
7597
+ success: true,
7598
+ data: result.data
7599
+ };
7600
+ const unknownKeyIssues = result.error.issues.filter((issue) => issue.code === "unrecognized_keys");
7601
+ return {
7602
+ success: false,
7603
+ message: unknownKeyIssues.length > 0 ? unknownKeysMessage(schema, unknownKeyIssues, topLevelKeys) : `Invalid parameter(s): ${formatIssues(result.error.issues)}`
7604
+ };
7605
+ };
7606
+ };
7607
+ /** The proxy dispatch path (namespace/single modes): throws the validator's message. */
7608
+ const createStrictParamsParser = (schema) => {
7609
+ const validate = createStrictValidator(schema);
7610
+ return (params) => {
7611
+ const result = validate(params);
7612
+ if (!result.success) throw new Error(result.message);
7613
+ return result.data;
7614
+ };
7615
+ };
7616
+ /**
7617
+ * The same validator as a Standard Schema, for `all` mode: the SDK calls this
7618
+ * `validate` and prints our pathless issue after its own prefix, so all three
7619
+ * modes word an error alike. The JSON Schema is zod's own, untouched.
7620
+ */
7621
+ const toStrictStandardSchema = (schema) => {
7622
+ const strict = schema.strict();
7623
+ const validate = createStrictValidator(schema, strict);
7624
+ return { "~standard": {
7625
+ version: 1,
7626
+ vendor: "zendesk-mcp-server",
7627
+ validate: (value) => {
7628
+ const result = validate(value);
7629
+ return result.success ? { value: result.data } : { issues: [{ message: result.message }] };
7630
+ },
7631
+ jsonSchema: strict["~standard"].jsonSchema
7632
+ } };
7633
+ };
7634
+ //#endregion
7549
7635
  //#region src/config.ts
7550
7636
  const ToolMode = _enum([
7551
7637
  "single",
@@ -7583,6 +7669,14 @@ const Transport = _enum(["stdio", "http"]);
7583
7669
  const ConfigSchema = object({
7584
7670
  subdomain: string$2().min(1, "ZENDESK_SUBDOMAIN is required"),
7585
7671
  oauthClientId: string$2().min(1),
7672
+ /**
7673
+ * Help Center brand allow-list (`--brand-ids`): brand ids or subdomains, or
7674
+ * 'all'. Unset: account default brand, surface unchanged. One entry: hard
7675
+ * lock, no `brand_id` field. Several or 'all': `list_brands` exposed and
7676
+ * `brand_id` required on brand-scoped tools. Entries resolve lazily, on first
7677
+ * use. Modes and rationale: docs/configuration.md.
7678
+ */
7679
+ brandIds: array(string$2().min(1)).min(1).optional(),
7586
7680
  logLevel: LogLevel,
7587
7681
  mode: ToolMode,
7588
7682
  readOnly: boolean$1(),
@@ -7610,14 +7704,12 @@ const ConfigSchema = object({
7610
7704
  topology: boolean$1().default(true),
7611
7705
  /**
7612
7706
  * Whether to PRE-LIST the promoted ("featured") Help Center articles: the
7613
- * `<scheme>://article/{id}` resource's `list` callback (which scans `/articles`
7614
- * to enumerate the promoted set for `resources/list`) AND the
7615
- * `list_promoted_articles` tool. On by default; an operator disables the
7616
- * pre-listing with `--no-promoted-articles` (e.g. on a very large Help Center
7617
- * where scanning is costly) so the server issues zero preloading requests. This
7618
- * does NOT disable reading a known article by id (`<scheme>://article/{id}` stays
7619
- * registered) — that is cheap and on-demand. Only ever active when the
7620
- * `help_center` namespace itself is active.
7707
+ * article resource's `list` callback, which scans `/articles` to enumerate the
7708
+ * promoted set for `resources/list`. On by default; `--no-promoted-articles`
7709
+ * turns it off (e.g. on a very large or multi-brand Help Center) so the server
7710
+ * issues zero preloading requests. Reading a known article by id and the
7711
+ * on-demand `list_promoted_articles` tool stay available. Only ever active
7712
+ * when the `help_center` namespace itself is active.
7621
7713
  */
7622
7714
  promotedArticles: boolean$1().default(true),
7623
7715
  /**
@@ -7696,8 +7788,25 @@ const portEnv = (name) => {
7696
7788
  const env = readNonEmptyEnv(name);
7697
7789
  return env.value === void 0 ? void 0 : parsePort(env.value, env.name);
7698
7790
  };
7791
+ /**
7792
+ * Split a raw `--brand-ids` / `ZENDESK_BRAND_IDS` value into its entries.
7793
+ * Comma-separated, entries trimmed; an empty entry (from `a,,b`, a leading/
7794
+ * trailing comma, or a whitespace-only value) is a misconfiguration, not an
7795
+ * ignored blank: it fails at startup naming the knob. Format validation only —
7796
+ * whether an id/subdomain exists is decided lazily, on first use (no token is
7797
+ * available at startup on either transport).
7798
+ */
7799
+ const parseBrandIds = (raw) => {
7800
+ if (raw === void 0) return void 0;
7801
+ const entries = raw.split(",").map((entry) => entry.trim());
7802
+ if (entries.some((entry) => entry.length === 0)) throw createStartupError("Invalid --brand-ids / ZENDESK_BRAND_IDS value: empty entry. Expected a comma-separated list of brand ids or subdomains, or \"all\".", STARTUP_DOCS.environment);
7803
+ const normalized = entries.map((entry) => entry.toLowerCase());
7804
+ if (normalized.length > 1 && normalized.includes("all")) throw createStartupError("Invalid --brand-ids / ZENDESK_BRAND_IDS value: \"all\" cannot be combined with brand ids or subdomains.", STARTUP_DOCS.environment);
7805
+ return [...new Set(normalized)];
7806
+ };
7699
7807
  const CLI_OPTIONS = {
7700
7808
  mode: { type: "string" },
7809
+ "brand-ids": { type: "string" },
7701
7810
  namespace: {
7702
7811
  type: "string",
7703
7812
  multiple: true
@@ -7767,7 +7876,7 @@ const parseStrict = (args) => {
7767
7876
  const validated = (input) => {
7768
7877
  const result = ConfigSchema.safeParse(input);
7769
7878
  if (result.success) return result.data;
7770
- throw createStartupError(`${result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}.`, STARTUP_DOCS.cliReference, result.error);
7879
+ throw createStartupError(`${formatIssues(result.error.issues)}.`, STARTUP_DOCS.cliReference, result.error);
7771
7880
  };
7772
7881
  const parseCliArgs = (args) => {
7773
7882
  const { values, positionals } = parseStrict(args);
@@ -7785,6 +7894,7 @@ const parseCliArgs = (args) => {
7785
7894
  }
7786
7895
  if (values.port !== void 0) result.port = parsePort(values.port, "--port");
7787
7896
  if (values["callback-port"] !== void 0) result.callbackPort = parsePort(values["callback-port"], "--callback-port");
7897
+ if (values["brand-ids"] !== void 0) result.brandIds = values["brand-ids"];
7788
7898
  return result;
7789
7899
  };
7790
7900
  const splitList = (raw) => (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -7812,10 +7922,12 @@ const loadConfig = (argv = process.argv.slice(2)) => {
7812
7922
  const mode = cli.tools?.length ? "all" : cli.mode ?? "namespace";
7813
7923
  const namespaces = cli.namespaces ?? (cli.tools?.length ? [...Namespace.options] : void 0);
7814
7924
  const callbackPort = cli.callbackPort ?? portEnv("OAUTH_CALLBACK_PORT");
7925
+ const brandIds = cli.brandIds ?? requireNonEmptyEnv("ZENDESK_BRAND_IDS");
7815
7926
  const hcResourceScheme = cli.hcResourceScheme ?? requireNonEmptyEnv("HC_RESOURCE_SCHEME");
7816
7927
  return validated({
7817
7928
  subdomain,
7818
7929
  oauthClientId,
7930
+ brandIds: parseBrandIds(brandIds),
7819
7931
  logLevel: cli.logLevel ?? requireNonEmptyEnv("LOG_LEVEL") ?? "info",
7820
7932
  mode,
7821
7933
  readOnly: cli.readOnly ?? false,
@@ -7832,7 +7944,389 @@ const loadConfig = (argv = process.argv.slice(2)) => {
7832
7944
  });
7833
7945
  };
7834
7946
  //#endregion
7835
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
7947
+ //#region src/constants.ts
7948
+ const positiveIntEnv = (name, fallback) => {
7949
+ const raw = readEnv(name).value;
7950
+ if (raw === void 0 || raw.trim() === "") return fallback;
7951
+ const parsed = Number(raw);
7952
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
7953
+ };
7954
+ const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
7955
+ const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
7956
+ const DEFAULT_CALLBACK_PORT = 27439;
7957
+ const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
7958
+ const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
7959
+ const STDIO_MAX_MESSAGE_BYTES = 10485760;
7960
+ const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
7961
+ const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
7962
+ const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
7963
+ const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
7964
+ const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
7965
+ const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
7966
+ const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
7967
+ const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
7968
+ const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
7969
+ const getOAuthUrls = (subdomain) => ({
7970
+ authorizeUrl: `https://${subdomain}.zendesk.com/oauth/authorizations/new`,
7971
+ tokenUrl: `https://${subdomain}.zendesk.com/oauth/tokens`
7972
+ });
7973
+ //#endregion
7974
+ //#region src/client/retry.ts
7975
+ const MAX_ATTEMPTS = 3;
7976
+ const BASE_DELAY_MS = 250;
7977
+ const MAX_DELAY_MS = 4e3;
7978
+ const REQUEST_TIMEOUT_MS = 3e4;
7979
+ const TRANSFER_TIMEOUT_MS = 12e4;
7980
+ /**
7981
+ * A deadline for one attempt. The abort surfaces as a `TimeoutError` whose `code`
7982
+ * is numeric, not a syscall string, so it classifies as `unknown`: a GET is
7983
+ * retried, a write is not — the request may have arrived before the deadline.
7984
+ */
7985
+ const deadlineSignal = (timeoutMs) => AbortSignal.timeout(timeoutMs);
7986
+ const MAX_RETRY_AFTER_MS = 5e3;
7987
+ const POLICIES = {
7988
+ GET: {
7989
+ network: "any",
7990
+ serverErrors: true
7991
+ },
7992
+ DELETE: {
7993
+ network: "pre-send",
7994
+ serverErrors: false
7995
+ },
7996
+ POST: {
7997
+ network: "pre-send",
7998
+ serverErrors: false
7999
+ },
8000
+ PUT: {
8001
+ network: "pre-send",
8002
+ serverErrors: false
8003
+ }
8004
+ };
8005
+ const DELAY_SECONDS = /^\d+$/;
8006
+ const HTTP_DATE_START = /^[A-Za-z]/;
8007
+ const NON_ASCII = /[^ -~]/g;
8008
+ const TOKEN_SEGMENT = /\/token\/[^/]+/;
8009
+ const URL_IN_TEXT = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
8010
+ const PRE_SEND_CODES = /* @__PURE__ */ new Set([
8011
+ "ENOTFOUND",
8012
+ "EAI_AGAIN",
8013
+ "ECONNREFUSED",
8014
+ "UND_ERR_CONNECT_TIMEOUT"
8015
+ ]);
8016
+ const defaultRetryDeps = {
8017
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
8018
+ random: Math.random
8019
+ };
8020
+ /** Non-ASCII bytes break `node:http` headers, so client error text stays ASCII. */
8021
+ const toAscii = (text) => text.replace(NON_ASCII, "?");
8022
+ /**
8023
+ * Identifies the request without leaking a credential: uploads carry an upload
8024
+ * token in the query string, and an attachment `content_url` carries a download
8025
+ * token as a path segment (`/attachments/token/<token>/`).
8026
+ */
8027
+ const describeTarget = (url) => {
8028
+ const { origin, pathname } = new URL(url);
8029
+ return `${origin}${pathname.replace(TOKEN_SEGMENT, "/token/[redacted]")}`;
8030
+ };
8031
+ /** First `code` in the cause chain, inspecting 5 levels so a cycle cannot hang. */
8032
+ const errorCode = (err) => {
8033
+ let current = err;
8034
+ for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth += 1) {
8035
+ const { code, cause } = current;
8036
+ if (typeof code === "string") return code;
8037
+ current = cause;
8038
+ }
8039
+ };
8040
+ const classifyNetworkError = (err) => PRE_SEND_CODES.has(errorCode(err)) ? "pre-send" : "unknown";
8041
+ const UNINFORMATIVE_NAMES = /* @__PURE__ */ new Set(["Error", "TypeError"]);
8042
+ /**
8043
+ * The client refuses to replay a write that may have landed — but the caller is
8044
+ * an LLM whose reflex on a failed tool call is to try again, which would undo
8045
+ * that. So a failed write says whether it may have taken effect. ASCII only,
8046
+ * same rule as the rest of the message.
8047
+ */
8048
+ const MAY_HAVE_APPLIED_NOTE = "The write may already have been applied. Check the current state before retrying, or you may duplicate it.";
8049
+ const NEVER_SENT_NOTE = "The request never reached Zendesk, so nothing was applied. Retrying is safe.";
8050
+ const REFUSED_NOTE = "Zendesk refused the request, so nothing was applied. Retrying is safe.";
8051
+ /** Only a write can be duplicated by a replay, so only a write carries a note. */
8052
+ const writeNote = (method, note) => method === "GET" ? "" : ` ${note}`;
8053
+ /**
8054
+ * A cause message can quote the URL it failed on — Node's `fetch` refuses a URL
8055
+ * carrying credentials by printing the whole thing, query string included — which
8056
+ * would smuggle back exactly what `describeTarget` drops. Same treatment for both,
8057
+ * so one function owns what a URL may look like in our output.
8058
+ */
8059
+ const redactUrls = (text) => text.replace(URL_IN_TEXT, (found) => {
8060
+ try {
8061
+ return new URL(found).origin === "null" ? "[url]" : describeTarget(found);
8062
+ } catch {
8063
+ return "[url]";
8064
+ }
8065
+ });
8066
+ const failureDetail = (cause) => {
8067
+ if (!(cause instanceof Error)) return redactUrls(String(cause));
8068
+ const message = redactUrls(cause.message);
8069
+ const code = errorCode(cause);
8070
+ if (code !== void 0) return `${code}: ${message}`;
8071
+ return UNINFORMATIVE_NAMES.has(cause.name) ? message : `${cause.name}: ${message}`;
8072
+ };
8073
+ const networkErrorMessage = (method, target, attempts, cause) => {
8074
+ const base = `Network error on ${method} ${target} after ${attempts === 1 ? "1 attempt" : `${attempts} attempts`}: ${failureDetail(cause)}`;
8075
+ const note = classifyNetworkError(cause) === "pre-send" ? NEVER_SENT_NOTE : MAY_HAVE_APPLIED_NOTE;
8076
+ return toAscii(method === "GET" ? base : `${base}.${writeNote(method, note)}`);
8077
+ };
8078
+ const createZendeskNetworkError = (method, target, attempts, cause) => Object.assign(new Error(networkErrorMessage(method, target, attempts, cause), { cause }), {
8079
+ name: "ZendeskNetworkError",
8080
+ method,
8081
+ target,
8082
+ attempts
8083
+ });
8084
+ /** Exponential backoff with equal jitter: half the window fixed, half random. */
8085
+ const computeBackoffMs = (attempt, random) => {
8086
+ const window = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
8087
+ return Math.round(window / 2 + window * random() / 2);
8088
+ };
8089
+ /** `Retry-After` in ms — delay-seconds or HTTP-date. */
8090
+ const parseRetryAfter = (header, now = Date.now()) => {
8091
+ if (header === null) return void 0;
8092
+ const value = header.trim();
8093
+ if (DELAY_SECONDS.test(value)) return Number(value) * 1e3;
8094
+ if (!HTTP_DATE_START.test(value)) return void 0;
8095
+ const date = Date.parse(value);
8096
+ if (Number.isNaN(date)) return void 0;
8097
+ return Math.max(0, date - now);
8098
+ };
8099
+ /**
8100
+ * How long to wait before replaying this response, or undefined to accept it.
8101
+ * `Retry-After` wins over backoff wherever it appears — Zendesk sends it on a 503
8102
+ * during maintenance as well as on a 429 — and a value past the cap means the
8103
+ * response is surfaced rather than parking the call for that long.
8104
+ */
8105
+ const retryDelayFor = (response, policy, attempt, random) => {
8106
+ if (!(response.status === 429 || response.status >= 500 && policy.serverErrors)) return void 0;
8107
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
8108
+ if (retryAfter === void 0) return computeBackoffMs(attempt, random);
8109
+ return retryAfter > MAX_RETRY_AFTER_MS ? void 0 : retryAfter;
8110
+ };
8111
+ /**
8112
+ * Runs `attempt` until it succeeds, hits a terminal outcome, or spends the
8113
+ * attempt budget. Returns the last response for the caller to inspect (a
8114
+ * non-ok status is still the caller's to turn into a `ZendeskApiError`), and
8115
+ * throws `ZendeskNetworkError` when no response was ever obtained.
8116
+ */
8117
+ const fetchWithRetry = async (attempt, method, target, deps = defaultRetryDeps) => {
8118
+ const policy = POLICIES[method];
8119
+ for (let tries = 1;; tries += 1) {
8120
+ const last = tries >= MAX_ATTEMPTS;
8121
+ let response;
8122
+ try {
8123
+ response = await attempt();
8124
+ } catch (err) {
8125
+ const replayable = policy.network === "any" || classifyNetworkError(err) === policy.network;
8126
+ if (last || !replayable) throw createZendeskNetworkError(method, target, tries, err);
8127
+ await deps.sleep(computeBackoffMs(tries, deps.random));
8128
+ continue;
8129
+ }
8130
+ if (last) return response;
8131
+ const delayMs = retryDelayFor(response, policy, tries, deps.random);
8132
+ if (delayMs === void 0) return response;
8133
+ await response.text().catch(() => void 0);
8134
+ await deps.sleep(delayMs);
8135
+ }
8136
+ };
8137
+ //#endregion
8138
+ //#region src/client/zendesk-api.ts
8139
+ var ZendeskApiError = class ZendeskApiError extends Error {
8140
+ status;
8141
+ statusText;
8142
+ body;
8143
+ method;
8144
+ constructor(status, statusText, body, method) {
8145
+ super(ZendeskApiError.buildMessage(status, statusText, body, method));
8146
+ this.status = status;
8147
+ this.statusText = statusText;
8148
+ this.body = body;
8149
+ this.method = method;
8150
+ this.name = "ZendeskApiError";
8151
+ }
8152
+ static buildMessage(status, statusText, body, method) {
8153
+ switch (status) {
8154
+ case 401: return "Authentication failed. Your Zendesk token may be expired or invalid. Re-authenticate to get a new token.";
8155
+ case 403: return "Permission denied. Your Zendesk account does not have access to this resource.";
8156
+ case 404: return `Resource not found. Please verify the ID is correct. (${statusText})`;
8157
+ case 422: return `Validation error: ${body}`;
8158
+ case 429: return `Rate limit exceeded. Please wait before making more requests.${writeNote(method, REFUSED_NOTE)}`;
8159
+ default: {
8160
+ const message = `Zendesk API error ${status}: ${statusText}. ${body}`;
8161
+ return status >= 500 ? `${message}${writeNote(method, MAY_HAVE_APPLIED_NOTE)}` : message;
8162
+ }
8163
+ }
8164
+ }
8165
+ };
8166
+ const buildAuthHeader = (token) => `Bearer ${token}`;
8167
+ const buildUrl = (base, path, params) => {
8168
+ const url = new URL(`${base}${path}`);
8169
+ if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
8170
+ return url.toString();
8171
+ };
8172
+ const performFetch = (method, url, init, timeoutMs = REQUEST_TIMEOUT_MS) => fetchWithRetry(() => fetch(url, {
8173
+ ...init,
8174
+ method,
8175
+ signal: deadlineSignal(timeoutMs)
8176
+ }), method, describeTarget(url));
8177
+ const executeRequest = async (url, token, options = {}) => {
8178
+ const { method = "GET", body } = options;
8179
+ const headers = {
8180
+ Authorization: buildAuthHeader(token),
8181
+ Accept: "application/json"
8182
+ };
8183
+ if (body) headers["Content-Type"] = "application/json";
8184
+ const init = { headers };
8185
+ if (body) init.body = JSON.stringify(body);
8186
+ const response = await performFetch(method, url, init);
8187
+ if (!response.ok) {
8188
+ const responseBody = await response.text();
8189
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, method);
8190
+ }
8191
+ if (response.status === 204) return {};
8192
+ return response.json();
8193
+ };
8194
+ const zendeskGet = (subdomain, token, path, params) => {
8195
+ const url = buildUrl(getBaseUrl(subdomain), path, params);
8196
+ return executeRequest(url, token);
8197
+ };
8198
+ const zendeskPost = (subdomain, token, path, body) => {
8199
+ const url = buildUrl(getBaseUrl(subdomain), path);
8200
+ return executeRequest(url, token, {
8201
+ method: "POST",
8202
+ body
8203
+ });
8204
+ };
8205
+ const zendeskPut = (subdomain, token, path, body) => {
8206
+ const url = buildUrl(getBaseUrl(subdomain), path);
8207
+ return executeRequest(url, token, {
8208
+ method: "PUT",
8209
+ body
8210
+ });
8211
+ };
8212
+ const helpCenterGet = (subdomain, token, path, params) => {
8213
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
8214
+ return executeRequest(url, token);
8215
+ };
8216
+ const helpCenterPost = (subdomain, token, path, body) => {
8217
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8218
+ return executeRequest(url, token, {
8219
+ method: "POST",
8220
+ body
8221
+ });
8222
+ };
8223
+ const helpCenterPut = (subdomain, token, path, body) => {
8224
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8225
+ return executeRequest(url, token, {
8226
+ method: "PUT",
8227
+ body
8228
+ });
8229
+ };
8230
+ const helpCenterDelete = (subdomain, token, path) => {
8231
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8232
+ return executeRequest(url, token, { method: "DELETE" });
8233
+ };
8234
+ const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
8235
+ const expectedHost = `${subdomain}.zendesk.com`;
8236
+ const headers = {};
8237
+ if (new URL(contentUrl).hostname === expectedHost) headers["Authorization"] = buildAuthHeader(token);
8238
+ const response = await performFetch("GET", contentUrl, { headers }, TRANSFER_TIMEOUT_MS);
8239
+ if (!response.ok) {
8240
+ const body = await response.text();
8241
+ throw new ZendeskApiError(response.status, response.statusText, body, "GET");
8242
+ }
8243
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
8244
+ const arrayBuffer = await response.arrayBuffer();
8245
+ return {
8246
+ data: Buffer.from(arrayBuffer),
8247
+ contentType
8248
+ };
8249
+ };
8250
+ const zendeskUpload = async (subdomain, token, filename, data, contentType, uploadToken) => {
8251
+ const params = { filename };
8252
+ if (uploadToken) params["token"] = uploadToken;
8253
+ const url = buildUrl(getBaseUrl(subdomain), "/uploads", params);
8254
+ const response = await performFetch("POST", url, {
8255
+ headers: {
8256
+ Authorization: buildAuthHeader(token),
8257
+ "Content-Type": contentType
8258
+ },
8259
+ body: data
8260
+ }, TRANSFER_TIMEOUT_MS);
8261
+ if (!response.ok) {
8262
+ const responseBody = await response.text();
8263
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
8264
+ }
8265
+ return response.json();
8266
+ };
8267
+ const helpCenterUpload = async (subdomain, token, path, formData) => {
8268
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8269
+ const response = await performFetch("POST", url, {
8270
+ headers: { Authorization: buildAuthHeader(token) },
8271
+ body: formData
8272
+ }, TRANSFER_TIMEOUT_MS);
8273
+ if (!response.ok) {
8274
+ const responseBody = await response.text();
8275
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
8276
+ }
8277
+ return response.json();
8278
+ };
8279
+ //#endregion
8280
+ //#region src/client/brands.ts
8281
+ const BRAND_SUBDOMAIN = /^[a-z0-9][a-z0-9-]*$/;
8282
+ /**
8283
+ * Fetch every brand of the account (cursor-paginated). `page[size]` is sent on
8284
+ * the FIRST request too: without it Zendesk falls back to OFFSET pagination,
8285
+ * which answers without `meta.after_cursor`, and the loop below would stop
8286
+ * after page 1 — a brand past the first 100 would resolve as Unknown.
8287
+ */
8288
+ const fetchAllBrands = async (subdomain, token) => {
8289
+ const brands = [];
8290
+ let cursor;
8291
+ do {
8292
+ const response = await zendeskGet(subdomain, token, "/brands", {
8293
+ "page[size]": String(100),
8294
+ ...cursor ? { "page[after]": cursor } : {}
8295
+ });
8296
+ brands.push(...response.brands ?? []);
8297
+ const meta = response.meta;
8298
+ if (meta?.has_more) {
8299
+ if (!meta.after_cursor || meta.after_cursor === cursor) throw new Error("GET /brands reported more pages but returned no usable after_cursor (missing or identical to the cursor just sent). Cannot continue pagination safely.");
8300
+ cursor = meta.after_cursor;
8301
+ } else cursor = void 0;
8302
+ } while (cursor);
8303
+ return brands;
8304
+ };
8305
+ const createBrandSubdomainResolver = (subdomain, getToken) => {
8306
+ let cached;
8307
+ const brands = () => {
8308
+ if (!cached || Date.now() - cached.at >= 3e5) {
8309
+ const promise = (async () => fetchAllBrands(subdomain, await getToken()))();
8310
+ cached = {
8311
+ at: Date.now(),
8312
+ promise
8313
+ };
8314
+ promise.catch(() => {
8315
+ cached = void 0;
8316
+ });
8317
+ }
8318
+ return cached.promise;
8319
+ };
8320
+ return async (idOrSubdomain) => {
8321
+ const all = await brands();
8322
+ const brand = all.find((b) => String(b.id) === idOrSubdomain) ?? all.find((b) => b.subdomain.toLowerCase() === idOrSubdomain.toLowerCase());
8323
+ if (!brand) throw new Error(`Unknown brand "${idOrSubdomain}": no brand with that id or subdomain exists on this account; call list_brands to see the available brands.`);
8324
+ if (!BRAND_SUBDOMAIN.test(brand.subdomain)) throw new Error(`Brand ${brand.id} has an unexpected subdomain; refusing to send a request to it.`);
8325
+ return brand.subdomain;
8326
+ };
8327
+ };
8328
+ //#endregion
8329
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
7836
8330
  var __create = Object.create;
7837
8331
  var __defProp = Object.defineProperty;
7838
8332
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -7864,7 +8358,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
7864
8358
  enumerable: true
7865
8359
  }) : target, mod));
7866
8360
  //#endregion
7867
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/dialects-DoSzNhcb.mjs
8361
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/dialects-DoSzNhcb.mjs
7868
8362
  /**
7869
8363
  * Canonical `$schema` URIs per supported dialect (http + https variants, trailing-`#` stripped).
7870
8364
  */
@@ -9527,7 +10021,7 @@ const OAuthTokenRevocationRequestSchema = object({
9527
10021
  token_type_hint: string$2().optional()
9528
10022
  }).strip();
9529
10023
  //#endregion
9530
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/src-BHSMhZ_W.mjs
10024
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs
9531
10025
  /**
9532
10026
  * Cross-bundle `instanceof` support for the SDK error classes.
9533
10027
  *
@@ -12582,7 +13076,7 @@ function build() {
12582
13076
  const subscriptionsListenParamsShape = { notifications: SubscriptionFilterSchema$1 };
12583
13077
  const SubscriptionsListenRequestSchema$1 = wireRequest("subscriptions/listen", subscriptionsListenParamsShape);
12584
13078
  /**
12585
- * Anchor SubscriptionsListenResultMeta — required subscriptionId stamp on
13079
+ * Anchor SubscriptionsListenResultMetaObject — required subscriptionId stamp on
12586
13080
  * the graceful-close result. Extends `ResultMetaObject` since spec PR
12587
13081
  * #3002 (composed, so the serverInfo key and its leniency stay single-sourced).
12588
13082
  */
@@ -12855,7 +13349,8 @@ function build() {
12855
13349
  ListResourceTemplatesResultResponseSchema: wireResultResponse(ListResourceTemplatesResultSchema$1),
12856
13350
  ReadResourceResultResponseSchema: wireResultResponse(union([ReadResourceResultSchema$1, InputRequiredResultSchema])),
12857
13351
  CompleteResultResponseSchema: wireResultResponse(CompleteResultSchema$1),
12858
- DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1)
13352
+ DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1),
13353
+ SubscriptionsListenResultResponseSchema: wireResultResponse(SubscriptionsListenResultSchema$1)
12859
13354
  };
12860
13355
  }
12861
13356
  let memo;
@@ -13599,6 +14094,10 @@ function isSpecRequestMethod(method) {
13599
14094
  function isSpecNotificationMethod(method) {
13600
14095
  return ALL_CODECS.some((codec) => codec.hasNotificationMethod(method));
13601
14096
  }
14097
+ /** The names SEP-2663 re-defines for the Tasks extension on the era that removed them from core. */
14098
+ function isExtensionReusedRequestMethod(method) {
14099
+ return method === "tasks/get" || method === "tasks/cancel";
14100
+ }
13602
14101
  const ALL_CODECS = [rev2025Codec, rev2026Codec];
13603
14102
  var schemas_exports = /* @__PURE__ */ __exportAll({
13604
14103
  AnnotationsSchema: () => AnnotationsSchema,
@@ -14631,6 +15130,8 @@ var Protocol = class {
14631
15130
  _transport;
14632
15131
  _requestMessageId = 0;
14633
15132
  _requestHandlers = /* @__PURE__ */ new Map();
15133
+ /** Methods registered with an explicit schema; the era gate in `_onrequest` reads it for the Tasks extension names. */
15134
+ _customSchemaRequestMethods = /* @__PURE__ */ new Set();
14634
15135
  _requestHandlerAbortControllers = /* @__PURE__ */ new Map();
14635
15136
  _notificationHandlers = /* @__PURE__ */ new Map();
14636
15137
  _responseHandlers = /* @__PURE__ */ new Map();
@@ -14908,7 +15409,7 @@ var Protocol = class {
14908
15409
  return;
14909
15410
  }
14910
15411
  }
14911
- if (isSpecRequestMethod(request.method) && !codec.hasRequestMethod(request.method)) {
15412
+ if (isSpecRequestMethod(request.method) && !codec.hasRequestMethod(request.method) && !(isExtensionReusedRequestMethod(request.method) && this._customSchemaRequestMethods.has(request.method))) {
14912
15413
  sendErrorResponse(ProtocolErrorCode.MethodNotFound, "Method not found");
14913
15414
  return;
14914
15415
  }
@@ -15041,8 +15542,11 @@ var Protocol = class {
15041
15542
  }
15042
15543
  request(request, schemaOrOptions, maybeOptions) {
15043
15544
  const codec = this._resolveOutboundCodec(request.method);
15545
+ if (isStandardSchema(schemaOrOptions)) {
15546
+ if (!isExtensionReusedRequestMethod(request.method)) this._assertOutboundRequestInEra(codec, request.method);
15547
+ return this._requestWithSchemaViaCodec(codec, request, schemaOrOptions, maybeOptions);
15548
+ }
15044
15549
  this._assertOutboundRequestInEra(codec, request.method);
15045
- if (isStandardSchema(schemaOrOptions)) return this._requestWithSchemaViaCodec(codec, request, schemaOrOptions, maybeOptions);
15046
15550
  const validate = codecResultValidator(codec, request.method);
15047
15551
  if (validate === void 0) throw new TypeError(`'${request.method}' is not a spec method; pass a result schema as the second argument to request().`);
15048
15552
  return this._requestWithSchemaViaCodec(codec, request, validate, schemaOrOptions);
@@ -15286,6 +15790,8 @@ var Protocol = class {
15286
15790
  return maybeHandler(parsed.data, ctx);
15287
15791
  };
15288
15792
  else throw new TypeError("setRequestHandler: handler is required");
15793
+ if (typeof schemasOrHandler === "function") this._customSchemaRequestMethods.delete(method);
15794
+ else this._customSchemaRequestMethods.add(method);
15289
15795
  this._requestHandlers.set(method, this._wrapHandler(method, stored));
15290
15796
  }
15291
15797
  /**
@@ -15314,6 +15820,7 @@ var Protocol = class {
15314
15820
  */
15315
15821
  removeRequestHandler(method) {
15316
15822
  this._requestHandlers.delete(method);
15823
+ this._customSchemaRequestMethods.delete(method);
15317
15824
  }
15318
15825
  /**
15319
15826
  * Asserts that a request handler has not already been set for the given method, in preparation for a new one being automatically installed.
@@ -15895,7 +16402,7 @@ function normalizeRawShapeSchema(schema) {
15895
16402
  return schema;
15896
16403
  }
15897
16404
  //#endregion
15898
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/ajvProvider-CEoC__sr.mjs
16405
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/ajvProvider-CEoC__sr.mjs
15899
16406
  var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
15900
16407
  Object.defineProperty(exports, "__esModule", { value: true });
15901
16408
  exports.regexpCode = exports.getEsmExportName = exports.getProperty = exports.safeStringify = exports.stringify = exports.strConcat = exports.addCodeArg = exports.str = exports._ = exports.nil = exports._Code = exports.Name = exports.IDENTIFIER = exports._CodeOrName = void 0;
@@ -23030,7 +23537,7 @@ var AjvJsonSchemaValidator = class {
23030
23537
  };
23031
23538
  import_ajv.Ajv;
23032
23539
  //#endregion
23033
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/mcp-DYuW2ZSs.mjs
23540
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/mcp-DIH4cS6P.mjs
23034
23541
  const COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
23035
23542
  /**
23036
23543
  * Checks if a schema is completable (has completion metadata).
@@ -23052,6 +23559,15 @@ function armSseKeepAlive(intervalMs, onTick) {
23052
23559
  timer.unref?.();
23053
23560
  return timer;
23054
23561
  }
23562
+ function comparableResource(value) {
23563
+ const text = String(value);
23564
+ const hash = text.indexOf("#");
23565
+ return (hash === -1 ? text : text.slice(0, hash)).replace(/\/$/, "");
23566
+ }
23567
+ function sameResource(reported, expected) {
23568
+ if (!reported) return false;
23569
+ return comparableResource(reported) === comparableResource(expected);
23570
+ }
23055
23571
  function headerQuotedValue(value) {
23056
23572
  return value.replaceAll(/[\\"]/g, String.raw`\$&`).replaceAll(/[^\u0020-\u007E]/g, " ");
23057
23573
  }
@@ -23071,6 +23587,38 @@ function buildWwwAuthenticateHeader(errorCode, description, requiredScopes, reso
23071
23587
  return header;
23072
23588
  }
23073
23589
  /**
23590
+ * Validate a raw `Authorization` header value as a Bearer token and return
23591
+ * the verified {@link AuthInfo}.
23592
+ *
23593
+ * The runtime-neutral core of Bearer authentication: it parses the header,
23594
+ * runs the verifier, compares the token's resource with `expectedResource`,
23595
+ * enforces `requiredScopes`, and rejects tokens without an
23596
+ * expiration or past it. On any failure it throws an {@link OAuthError} —
23597
+ * pass that to {@link bearerAuthChallengeResponse} for the matching HTTP
23598
+ * answer, or use {@link requireBearerAuth} to get both steps as one call.
23599
+ *
23600
+ * Framework adapters build on this: `requireBearerAuth` from
23601
+ * `@modelcontextprotocol/express` feeds it `req.headers.authorization`.
23602
+ */
23603
+ async function verifyBearerToken(authorizationHeader, options) {
23604
+ const { verifier, requiredScopes = [], expectedResource } = options;
23605
+ if (!authorizationHeader) throw new OAuthError(OAuthErrorCode.InvalidToken, "Missing Authorization header");
23606
+ const [type, token] = authorizationHeader.split(" ");
23607
+ if (type?.toLowerCase() !== "bearer" || !token) throw new OAuthError(OAuthErrorCode.InvalidToken, "Invalid Authorization header format, expected 'Bearer TOKEN'");
23608
+ const authInfo = await verifier.verifyAccessToken(token);
23609
+ if (expectedResource !== void 0 && !sameResource(authInfo.resource, expectedResource)) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token was not issued for this resource");
23610
+ if (requiredScopes.length > 0) {
23611
+ if (!requiredScopes.every((scope) => authInfo.scopes.includes(scope))) throw new OAuthError(OAuthErrorCode.InsufficientScope, "Insufficient scope");
23612
+ }
23613
+ if (typeof authInfo.expiresAt !== "number" || Number.isNaN(authInfo.expiresAt)) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token has no expiration time");
23614
+ else if (authInfo.expiresAt < Date.now() / 1e3) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token has expired");
23615
+ if (options.resourceMetadataUrl !== void 0 && authInfo.resourceMetadataUrl === void 0) return {
23616
+ ...authInfo,
23617
+ resourceMetadataUrl: options.resourceMetadataUrl
23618
+ };
23619
+ return authInfo;
23620
+ }
23621
+ /**
23074
23622
  * Build the HTTP answer for a Bearer authentication failure.
23075
23623
  *
23076
23624
  * Maps an {@link OAuthError} to its status — `401` for `invalid_token` and
@@ -23383,6 +23931,11 @@ var Server = class extends Protocol {
23383
23931
  if (modernProtocolVersions(this._supportedProtocolVersions).length > 0) this.setRequestHandler("server/discover", () => this._ondiscover());
23384
23932
  if (this._capabilities.logging) this._registerLoggingHandler();
23385
23933
  }
23934
+ /** Attaches to the given transport; rejects while this instance is connected to another one. */
23935
+ async connect(transport) {
23936
+ if (this.transport !== void 0) throw new SdkError(SdkErrorCode.AlreadyConnected, "Use a separate Server instance per connection: this instance is already connected to a transport. To connect it to a new transport, call close() first.");
23937
+ await super.connect(transport);
23938
+ }
23386
23939
  /**
23387
23940
  * Registers the built-in `logging/setLevel` request handler.
23388
23941
  *
@@ -23928,6 +24481,30 @@ var Server = class extends Protocol {
23928
24481
  function discoverAdvertisedCapabilities(capabilities) {
23929
24482
  return { ...capabilities };
23930
24483
  }
24484
+ function toolInputElementCount(value, max) {
24485
+ let count = 0;
24486
+ const stack = [value];
24487
+ while (stack.length > 0) {
24488
+ const node = stack.pop();
24489
+ if (node === null || typeof node !== "object") continue;
24490
+ if (Array.isArray(node)) for (const child of node) {
24491
+ if (++count > max) return count;
24492
+ if (child !== null && typeof child === "object") stack.push(child);
24493
+ }
24494
+ else for (const key in node) {
24495
+ if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
24496
+ if (++count > max) return count;
24497
+ const child = node[key];
24498
+ if (child !== null && typeof child === "object") stack.push(child);
24499
+ }
24500
+ }
24501
+ return count;
24502
+ }
24503
+ function resolveMaxToolInputElements(value) {
24504
+ if (value === void 0 || value === Infinity) return void 0;
24505
+ if (typeof value !== "number" || Number.isNaN(value) || value < 1) throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
24506
+ return value;
24507
+ }
23931
24508
  /**
23932
24509
  * High-level MCP server that provides a simpler API for working with resources, tools, and prompts.
23933
24510
  * For advanced usage (like sending notifications or setting custom request handlers), use the underlying
@@ -23946,16 +24523,12 @@ var McpServer = class {
23946
24523
  * The underlying {@linkcode Server} instance, useful for advanced operations like sending notifications.
23947
24524
  */
23948
24525
  server;
24526
+ _maxToolInputElements;
23949
24527
  _registeredResources = {};
23950
24528
  _registeredResourceTemplates = {};
23951
24529
  _registeredTools = {};
23952
24530
  _registeredPrompts = {};
23953
- /**
23954
- * Per-tool JSON-converted `inputSchema`, memoized so the SEP-2243
23955
- * registration-time scan and the pre-dispatch validation step share one
23956
- * conversion instead of paying it twice per request under the
23957
- * per-request-factory `createMcpHandler` model.
23958
- */
24531
+ /** Per-tool JSON-converted `inputSchema`, filled on first use by `toolInputSchemaJson()`. */
23959
24532
  _toolInputSchemaJson = {};
23960
24533
  /**
23961
24534
  * The JSON-serialized `inputSchema` of a registered tool, or `undefined`
@@ -23980,6 +24553,7 @@ var McpServer = class {
23980
24553
  }
23981
24554
  constructor(serverInfo, options) {
23982
24555
  this.server = new Server(serverInfo, options);
24556
+ this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
23983
24557
  if (options?.capabilities?.tools) this.setToolRequestHandlers();
23984
24558
  if (options?.capabilities?.resources) this.setResourceRequestHandlers();
23985
24559
  if (options?.capabilities?.prompts) this.setPromptRequestHandlers();
@@ -24051,7 +24625,7 @@ var McpServer = class {
24051
24625
  name,
24052
24626
  title: tool.title,
24053
24627
  description: tool.description,
24054
- inputSchema: tool.inputSchema ? standardSchemaToJsonSchema(tool.inputSchema, "input") : EMPTY_OBJECT_JSON_SCHEMA,
24628
+ inputSchema: tool.inputSchema ? convertListedInputSchema(name, tool.inputSchema) : EMPTY_OBJECT_JSON_SCHEMA,
24055
24629
  annotations: tool.annotations,
24056
24630
  icons: tool.icons,
24057
24631
  execution: tool.execution,
@@ -24096,6 +24670,7 @@ var McpServer = class {
24096
24670
  * Validates tool input arguments against the tool's input schema.
24097
24671
  */
24098
24672
  async validateToolInput(tool, args, toolName) {
24673
+ if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
24099
24674
  if (!tool.inputSchema) return;
24100
24675
  const parseResult = await validateStandardSchema(tool.inputSchema, args ?? {});
24101
24676
  if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${parseResult.error}`);
@@ -24369,19 +24944,19 @@ var McpServer = class {
24369
24944
  }
24370
24945
  _createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, scopeChallenge, _meta, handler) {
24371
24946
  validateAndWarnToolName(name);
24372
- if (inputSchema !== void 0) try {
24373
- const json = standardSchemaToJsonSchema(inputSchema, "input");
24374
- this._toolInputSchemaJson[name] = json;
24375
- const scan = scanXMcpHeaderDeclarations(json);
24376
- if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
24377
- } catch {}
24378
24947
  let currentHandler = handler;
24948
+ let outputSchemaJson;
24379
24949
  const registeredTool = {
24380
24950
  title,
24381
24951
  description,
24382
24952
  inputSchema,
24383
24953
  outputSchema,
24384
- outputSchemaJson: convertOutputSchemaJson(outputSchema),
24954
+ get outputSchemaJson() {
24955
+ return outputSchemaJson ??= convertOutputSchemaJson(registeredTool.outputSchema);
24956
+ },
24957
+ set outputSchemaJson(value) {
24958
+ outputSchemaJson = value;
24959
+ },
24385
24960
  annotations,
24386
24961
  icons,
24387
24962
  execution,
@@ -24542,6 +25117,13 @@ const EMPTY_OBJECT_JSON_SCHEMA = {
24542
25117
  type: "object",
24543
25118
  properties: {}
24544
25119
  };
25120
+ /** Converts a tool's `inputSchema` for `tools/list` and warns on an invalid SEP-2243 `x-mcp-header` declaration. */
25121
+ function convertListedInputSchema(name, inputSchema) {
25122
+ const json = standardSchemaToJsonSchema(inputSchema, "input");
25123
+ const scan = scanXMcpHeaderDeclarations(json);
25124
+ if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
25125
+ return json;
25126
+ }
24545
25127
  /**
24546
25128
  * Convert a registered `outputSchema` to JSON Schema, memoised on {@link RegisteredTool.outputSchemaJson}
24547
25129
  * so `tools/call` passes the SAME advertised schema to the wire codec's `projectCallToolResult` that
@@ -24564,7 +25146,7 @@ function createPromptHandler(name, argsSchema, callback) {
24564
25146
  if (argsSchema) {
24565
25147
  const typedCallback = callback;
24566
25148
  return async (args, ctx) => {
24567
- const parseResult = await validateStandardSchema(argsSchema, args);
25149
+ const parseResult = await validateStandardSchema(argsSchema, args ?? {});
24568
25150
  if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for prompt ${name}: ${parseResult.error}`);
24569
25151
  return typedCallback(parseResult.data, ctx);
24570
25152
  };
@@ -24601,7 +25183,7 @@ function unwrapOptionalSchema(schema) {
24601
25183
  return schema.def?.innerType ?? schema;
24602
25184
  }
24603
25185
  //#endregion
24604
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/index.mjs
25186
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/index.mjs
24605
25187
  /** Default upper bound, in bytes, on a request body read by the HTTP entry points (4 MiB). */
24606
25188
  const DEFAULT_MAX_REQUEST_BODY_SIZE = 4194304;
24607
25189
  /** Upper bound on the number of messages accepted in one JSON-RPC batch array. */
@@ -24690,6 +25272,10 @@ async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE
24690
25272
  * @example Hono.js
24691
25273
  * ```ts source="./streamableHttp.examples.ts#WebStandardStreamableHTTPServerTransport_hono"
24692
25274
  * app.all('/mcp', async c => {
25275
+ * // Stateless example: create a server and a transport per request.
25276
+ * const server = new McpServer({ name: 'my-server', version: '1.0.0' });
25277
+ * const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
25278
+ * await server.connect(transport);
24693
25279
  * return transport.handleRequest(c.req.raw);
24694
25280
  * });
24695
25281
  * ```
@@ -24698,6 +25284,10 @@ async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE
24698
25284
  * ```ts source="./streamableHttp.examples.ts#WebStandardStreamableHTTPServerTransport_workers"
24699
25285
  * const worker = {
24700
25286
  * async fetch(request: Request): Promise<Response> {
25287
+ * // Stateless example: create a server and a transport per request.
25288
+ * const server = new McpServer({ name: 'my-server', version: '1.0.0' });
25289
+ * const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
25290
+ * await server.connect(transport);
24701
25291
  * return transport.handleRequest(request);
24702
25292
  * }
24703
25293
  * };
@@ -24706,6 +25296,7 @@ async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE
24706
25296
  var WebStandardStreamableHTTPServerTransport = class {
24707
25297
  sessionIdGenerator;
24708
25298
  _started = false;
25299
+ _hasHandledRequest = false;
24709
25300
  _closed = false;
24710
25301
  _streamMapping = /* @__PURE__ */ new Map();
24711
25302
  _requestToStreamMapping = /* @__PURE__ */ new Map();
@@ -24827,6 +25418,8 @@ var WebStandardStreamableHTTPServerTransport = class {
24827
25418
  */
24828
25419
  async handleRequest(req, options) {
24829
25420
  if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
25421
+ if (!this.sessionIdGenerator && this._hasHandledRequest) throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
25422
+ this._hasHandledRequest = true;
24830
25423
  const validationError = this.validateRequestHeaders(req);
24831
25424
  if (validationError) return validationError;
24832
25425
  switch (req.method) {
@@ -25342,339 +25935,6 @@ var WebStandardStreamableHTTPServerTransport = class {
25342
25935
  }
25343
25936
  };
25344
25937
  //#endregion
25345
- //#region src/constants.ts
25346
- const positiveIntEnv = (name, fallback) => {
25347
- const raw = readEnv(name).value;
25348
- if (raw === void 0 || raw.trim() === "") return fallback;
25349
- const parsed = Number(raw);
25350
- return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
25351
- };
25352
- const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
25353
- const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
25354
- const DEFAULT_CALLBACK_PORT = 27439;
25355
- const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
25356
- const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
25357
- const STDIO_MAX_MESSAGE_BYTES = 10485760;
25358
- const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
25359
- const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
25360
- const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
25361
- const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
25362
- const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
25363
- const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
25364
- const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
25365
- const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
25366
- const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
25367
- const getOAuthUrls = (subdomain) => ({
25368
- authorizeUrl: `https://${subdomain}.zendesk.com/oauth/authorizations/new`,
25369
- tokenUrl: `https://${subdomain}.zendesk.com/oauth/tokens`
25370
- });
25371
- //#endregion
25372
- //#region src/client/retry.ts
25373
- const MAX_ATTEMPTS = 3;
25374
- const BASE_DELAY_MS = 250;
25375
- const MAX_DELAY_MS = 4e3;
25376
- const REQUEST_TIMEOUT_MS = 3e4;
25377
- const TRANSFER_TIMEOUT_MS = 12e4;
25378
- /**
25379
- * A deadline for one attempt. The abort surfaces as a `TimeoutError` whose `code`
25380
- * is numeric, not a syscall string, so it classifies as `unknown`: a GET is
25381
- * retried, a write is not — the request may have arrived before the deadline.
25382
- */
25383
- const deadlineSignal = (timeoutMs) => AbortSignal.timeout(timeoutMs);
25384
- const MAX_RETRY_AFTER_MS = 5e3;
25385
- const POLICIES = {
25386
- GET: {
25387
- network: "any",
25388
- serverErrors: true
25389
- },
25390
- DELETE: {
25391
- network: "pre-send",
25392
- serverErrors: false
25393
- },
25394
- POST: {
25395
- network: "pre-send",
25396
- serverErrors: false
25397
- },
25398
- PUT: {
25399
- network: "pre-send",
25400
- serverErrors: false
25401
- }
25402
- };
25403
- const DELAY_SECONDS = /^\d+$/;
25404
- const HTTP_DATE_START = /^[A-Za-z]/;
25405
- const NON_ASCII = /[^ -~]/g;
25406
- const TOKEN_SEGMENT = /\/token\/[^/]+/;
25407
- const URL_IN_TEXT = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
25408
- const PRE_SEND_CODES = /* @__PURE__ */ new Set([
25409
- "ENOTFOUND",
25410
- "EAI_AGAIN",
25411
- "ECONNREFUSED",
25412
- "UND_ERR_CONNECT_TIMEOUT"
25413
- ]);
25414
- const defaultRetryDeps = {
25415
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
25416
- random: Math.random
25417
- };
25418
- /** Non-ASCII bytes break `node:http` headers, so client error text stays ASCII. */
25419
- const toAscii = (text) => text.replace(NON_ASCII, "?");
25420
- /**
25421
- * Identifies the request without leaking a credential: uploads carry an upload
25422
- * token in the query string, and an attachment `content_url` carries a download
25423
- * token as a path segment (`/attachments/token/<token>/`).
25424
- */
25425
- const describeTarget = (url) => {
25426
- const { origin, pathname } = new URL(url);
25427
- return `${origin}${pathname.replace(TOKEN_SEGMENT, "/token/[redacted]")}`;
25428
- };
25429
- /** First `code` in the cause chain, inspecting 5 levels so a cycle cannot hang. */
25430
- const errorCode = (err) => {
25431
- let current = err;
25432
- for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth += 1) {
25433
- const { code, cause } = current;
25434
- if (typeof code === "string") return code;
25435
- current = cause;
25436
- }
25437
- };
25438
- const classifyNetworkError = (err) => PRE_SEND_CODES.has(errorCode(err)) ? "pre-send" : "unknown";
25439
- const UNINFORMATIVE_NAMES = /* @__PURE__ */ new Set(["Error", "TypeError"]);
25440
- /**
25441
- * The client refuses to replay a write that may have landed — but the caller is
25442
- * an LLM whose reflex on a failed tool call is to try again, which would undo
25443
- * that. So a failed write says whether it may have taken effect. ASCII only,
25444
- * same rule as the rest of the message.
25445
- */
25446
- const MAY_HAVE_APPLIED_NOTE = "The write may already have been applied. Check the current state before retrying, or you may duplicate it.";
25447
- const NEVER_SENT_NOTE = "The request never reached Zendesk, so nothing was applied. Retrying is safe.";
25448
- const REFUSED_NOTE = "Zendesk refused the request, so nothing was applied. Retrying is safe.";
25449
- /** Only a write can be duplicated by a replay, so only a write carries a note. */
25450
- const writeNote = (method, note) => method === "GET" ? "" : ` ${note}`;
25451
- /**
25452
- * A cause message can quote the URL it failed on — Node's `fetch` refuses a URL
25453
- * carrying credentials by printing the whole thing, query string included — which
25454
- * would smuggle back exactly what `describeTarget` drops. Same treatment for both,
25455
- * so one function owns what a URL may look like in our output.
25456
- */
25457
- const redactUrls = (text) => text.replace(URL_IN_TEXT, (found) => {
25458
- try {
25459
- return new URL(found).origin === "null" ? "[url]" : describeTarget(found);
25460
- } catch {
25461
- return "[url]";
25462
- }
25463
- });
25464
- const failureDetail = (cause) => {
25465
- if (!(cause instanceof Error)) return redactUrls(String(cause));
25466
- const message = redactUrls(cause.message);
25467
- const code = errorCode(cause);
25468
- if (code !== void 0) return `${code}: ${message}`;
25469
- return UNINFORMATIVE_NAMES.has(cause.name) ? message : `${cause.name}: ${message}`;
25470
- };
25471
- const networkErrorMessage = (method, target, attempts, cause) => {
25472
- const base = `Network error on ${method} ${target} after ${attempts === 1 ? "1 attempt" : `${attempts} attempts`}: ${failureDetail(cause)}`;
25473
- const note = classifyNetworkError(cause) === "pre-send" ? NEVER_SENT_NOTE : MAY_HAVE_APPLIED_NOTE;
25474
- return toAscii(method === "GET" ? base : `${base}.${writeNote(method, note)}`);
25475
- };
25476
- const createZendeskNetworkError = (method, target, attempts, cause) => Object.assign(new Error(networkErrorMessage(method, target, attempts, cause), { cause }), {
25477
- name: "ZendeskNetworkError",
25478
- method,
25479
- target,
25480
- attempts
25481
- });
25482
- /** Exponential backoff with equal jitter: half the window fixed, half random. */
25483
- const computeBackoffMs = (attempt, random) => {
25484
- const window = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
25485
- return Math.round(window / 2 + window * random() / 2);
25486
- };
25487
- /** `Retry-After` in ms — delay-seconds or HTTP-date. */
25488
- const parseRetryAfter = (header, now = Date.now()) => {
25489
- if (header === null) return void 0;
25490
- const value = header.trim();
25491
- if (DELAY_SECONDS.test(value)) return Number(value) * 1e3;
25492
- if (!HTTP_DATE_START.test(value)) return void 0;
25493
- const date = Date.parse(value);
25494
- if (Number.isNaN(date)) return void 0;
25495
- return Math.max(0, date - now);
25496
- };
25497
- /**
25498
- * How long to wait before replaying this response, or undefined to accept it.
25499
- * `Retry-After` wins over backoff wherever it appears — Zendesk sends it on a 503
25500
- * during maintenance as well as on a 429 — and a value past the cap means the
25501
- * response is surfaced rather than parking the call for that long.
25502
- */
25503
- const retryDelayFor = (response, policy, attempt, random) => {
25504
- if (!(response.status === 429 || response.status >= 500 && policy.serverErrors)) return void 0;
25505
- const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
25506
- if (retryAfter === void 0) return computeBackoffMs(attempt, random);
25507
- return retryAfter > MAX_RETRY_AFTER_MS ? void 0 : retryAfter;
25508
- };
25509
- /**
25510
- * Runs `attempt` until it succeeds, hits a terminal outcome, or spends the
25511
- * attempt budget. Returns the last response for the caller to inspect (a
25512
- * non-ok status is still the caller's to turn into a `ZendeskApiError`), and
25513
- * throws `ZendeskNetworkError` when no response was ever obtained.
25514
- */
25515
- const fetchWithRetry = async (attempt, method, target, deps = defaultRetryDeps) => {
25516
- const policy = POLICIES[method];
25517
- for (let tries = 1;; tries += 1) {
25518
- const last = tries >= MAX_ATTEMPTS;
25519
- let response;
25520
- try {
25521
- response = await attempt();
25522
- } catch (err) {
25523
- const replayable = policy.network === "any" || classifyNetworkError(err) === policy.network;
25524
- if (last || !replayable) throw createZendeskNetworkError(method, target, tries, err);
25525
- await deps.sleep(computeBackoffMs(tries, deps.random));
25526
- continue;
25527
- }
25528
- if (last) return response;
25529
- const delayMs = retryDelayFor(response, policy, tries, deps.random);
25530
- if (delayMs === void 0) return response;
25531
- await response.text().catch(() => void 0);
25532
- await deps.sleep(delayMs);
25533
- }
25534
- };
25535
- //#endregion
25536
- //#region src/client/zendesk-api.ts
25537
- var ZendeskApiError = class ZendeskApiError extends Error {
25538
- status;
25539
- statusText;
25540
- body;
25541
- method;
25542
- constructor(status, statusText, body, method) {
25543
- super(ZendeskApiError.buildMessage(status, statusText, body, method));
25544
- this.status = status;
25545
- this.statusText = statusText;
25546
- this.body = body;
25547
- this.method = method;
25548
- this.name = "ZendeskApiError";
25549
- }
25550
- static buildMessage(status, statusText, body, method) {
25551
- switch (status) {
25552
- case 401: return "Authentication failed. Your Zendesk token may be expired or invalid. Re-authenticate to get a new token.";
25553
- case 403: return "Permission denied. Your Zendesk account does not have access to this resource.";
25554
- case 404: return `Resource not found. Please verify the ID is correct. (${statusText})`;
25555
- case 422: return `Validation error: ${body}`;
25556
- case 429: return `Rate limit exceeded. Please wait before making more requests.${writeNote(method, REFUSED_NOTE)}`;
25557
- default: {
25558
- const message = `Zendesk API error ${status}: ${statusText}. ${body}`;
25559
- return status >= 500 ? `${message}${writeNote(method, MAY_HAVE_APPLIED_NOTE)}` : message;
25560
- }
25561
- }
25562
- }
25563
- };
25564
- const buildAuthHeader = (token) => `Bearer ${token}`;
25565
- const buildUrl = (base, path, params) => {
25566
- const url = new URL(`${base}${path}`);
25567
- if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
25568
- return url.toString();
25569
- };
25570
- const performFetch = (method, url, init, timeoutMs = REQUEST_TIMEOUT_MS) => fetchWithRetry(() => fetch(url, {
25571
- ...init,
25572
- method,
25573
- signal: deadlineSignal(timeoutMs)
25574
- }), method, describeTarget(url));
25575
- const executeRequest = async (url, token, options = {}) => {
25576
- const { method = "GET", body } = options;
25577
- const headers = {
25578
- Authorization: buildAuthHeader(token),
25579
- Accept: "application/json"
25580
- };
25581
- if (body) headers["Content-Type"] = "application/json";
25582
- const init = { headers };
25583
- if (body) init.body = JSON.stringify(body);
25584
- const response = await performFetch(method, url, init);
25585
- if (!response.ok) {
25586
- const responseBody = await response.text();
25587
- throw new ZendeskApiError(response.status, response.statusText, responseBody, method);
25588
- }
25589
- if (response.status === 204) return {};
25590
- return response.json();
25591
- };
25592
- const zendeskGet = (subdomain, token, path, params) => {
25593
- const url = buildUrl(getBaseUrl(subdomain), path, params);
25594
- return executeRequest(url, token);
25595
- };
25596
- const zendeskPost = (subdomain, token, path, body) => {
25597
- const url = buildUrl(getBaseUrl(subdomain), path);
25598
- return executeRequest(url, token, {
25599
- method: "POST",
25600
- body
25601
- });
25602
- };
25603
- const zendeskPut = (subdomain, token, path, body) => {
25604
- const url = buildUrl(getBaseUrl(subdomain), path);
25605
- return executeRequest(url, token, {
25606
- method: "PUT",
25607
- body
25608
- });
25609
- };
25610
- const helpCenterGet = (subdomain, token, path, params) => {
25611
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
25612
- return executeRequest(url, token);
25613
- };
25614
- const helpCenterPost = (subdomain, token, path, body) => {
25615
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
25616
- return executeRequest(url, token, {
25617
- method: "POST",
25618
- body
25619
- });
25620
- };
25621
- const helpCenterPut = (subdomain, token, path, body) => {
25622
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
25623
- return executeRequest(url, token, {
25624
- method: "PUT",
25625
- body
25626
- });
25627
- };
25628
- const helpCenterDelete = (subdomain, token, path) => {
25629
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
25630
- return executeRequest(url, token, { method: "DELETE" });
25631
- };
25632
- const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
25633
- const expectedHost = `${subdomain}.zendesk.com`;
25634
- const headers = {};
25635
- if (new URL(contentUrl).hostname === expectedHost) headers["Authorization"] = buildAuthHeader(token);
25636
- const response = await performFetch("GET", contentUrl, { headers }, TRANSFER_TIMEOUT_MS);
25637
- if (!response.ok) {
25638
- const body = await response.text();
25639
- throw new ZendeskApiError(response.status, response.statusText, body, "GET");
25640
- }
25641
- const contentType = response.headers.get("content-type") ?? "application/octet-stream";
25642
- const arrayBuffer = await response.arrayBuffer();
25643
- return {
25644
- data: Buffer.from(arrayBuffer),
25645
- contentType
25646
- };
25647
- };
25648
- const zendeskUpload = async (subdomain, token, filename, data, contentType, uploadToken) => {
25649
- const params = { filename };
25650
- if (uploadToken) params["token"] = uploadToken;
25651
- const url = buildUrl(getBaseUrl(subdomain), "/uploads", params);
25652
- const response = await performFetch("POST", url, {
25653
- headers: {
25654
- Authorization: buildAuthHeader(token),
25655
- "Content-Type": contentType
25656
- },
25657
- body: data
25658
- }, TRANSFER_TIMEOUT_MS);
25659
- if (!response.ok) {
25660
- const responseBody = await response.text();
25661
- throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
25662
- }
25663
- return response.json();
25664
- };
25665
- const helpCenterUpload = async (subdomain, token, path, formData) => {
25666
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
25667
- const response = await performFetch("POST", url, {
25668
- headers: { Authorization: buildAuthHeader(token) },
25669
- body: formData
25670
- }, TRANSFER_TIMEOUT_MS);
25671
- if (!response.ok) {
25672
- const responseBody = await response.text();
25673
- throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
25674
- }
25675
- return response.json();
25676
- };
25677
- //#endregion
25678
25938
  //#region node_modules/.pnpm/cheerio@1.2.0/node_modules/cheerio/dist/esm/options.js
25679
25939
  const defaultOpts$2 = { _useHtmlParser2: false };
25680
25940
  /**
@@ -83005,7 +83265,7 @@ function escapeStringRegexp(string) {
83005
83265
  return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
83006
83266
  }
83007
83267
  //#endregion
83008
- //#region node_modules/.pnpm/mdast-util-find-and-replace@3.0.2/node_modules/mdast-util-find-and-replace/lib/index.js
83268
+ //#region node_modules/.pnpm/mdast-util-find-and-replace@3.0.3/node_modules/mdast-util-find-and-replace/lib/index.js
83009
83269
  /**
83010
83270
  * @import {Nodes, Parents, PhrasingContent, Root, Text} from 'mdast'
83011
83271
  * @import {BuildVisitor, Test, VisitorResult} from 'unist-util-visit-parents'
@@ -83082,7 +83342,11 @@ function findAndReplace(tree, list, options) {
83082
83342
  const pairs = toPairs(list);
83083
83343
  let pairIndex = -1;
83084
83344
  while (++pairIndex < pairs.length) visitParents(tree, "text", visitor);
83085
- /** @type {BuildVisitor<Root, 'text'>} */
83345
+ /**
83346
+ * Visit a text node, and handle it if it is not in an ignored parent.
83347
+ *
83348
+ * @type {BuildVisitor<Root, 'text'>}
83349
+ */
83086
83350
  function visitor(node, parents) {
83087
83351
  let index = -1;
83088
83352
  /** @type {Parents | undefined} */
@@ -83100,11 +83364,11 @@ function findAndReplace(tree, list, options) {
83100
83364
  * Handle a text node which is not in an ignored parent.
83101
83365
  *
83102
83366
  * @param {Text} node
83103
- * Text node.
83367
+ * Text node to search in.
83104
83368
  * @param {Array<Parents>} parents
83105
- * Parents.
83369
+ * Ancestors of `node`.
83106
83370
  * @returns {VisitorResult}
83107
- * Result.
83371
+ * Where to continue visiting.
83108
83372
  */
83109
83373
  function handler(node, parents) {
83110
83374
  const parent = parents[parents.length - 1];
@@ -83132,12 +83396,12 @@ function findAndReplace(tree, list, options) {
83132
83396
  } : void 0;
83133
83397
  if (value === false) find.lastIndex = position + 1;
83134
83398
  else {
83135
- if (start !== position) nodes.push({
83399
+ if (start !== position) add({
83136
83400
  type: "text",
83137
83401
  value: node.value.slice(start, position)
83138
83402
  });
83139
- if (Array.isArray(value)) nodes.push(...value);
83140
- else if (value) nodes.push(value);
83403
+ if (Array.isArray(value)) for (const child of value) add(child);
83404
+ else if (value) add(value);
83141
83405
  start = position + match[0].length;
83142
83406
  change = true;
83143
83407
  }
@@ -83145,13 +83409,26 @@ function findAndReplace(tree, list, options) {
83145
83409
  match = find.exec(node.value);
83146
83410
  }
83147
83411
  if (change) {
83148
- if (start < node.value.length) nodes.push({
83412
+ if (start < node.value.length) add({
83149
83413
  type: "text",
83150
83414
  value: node.value.slice(start)
83151
83415
  });
83152
83416
  parent.children.splice(index, 1, ...nodes);
83153
83417
  } else nodes = [node];
83154
83418
  return index + nodes.length;
83419
+ /**
83420
+ * Add a node.
83421
+ *
83422
+ * @param {PhrasingContent} child
83423
+ * Node to add.
83424
+ * @returns {undefined}
83425
+ * Nothing.
83426
+ */
83427
+ function add(child) {
83428
+ const previous = nodes[nodes.length - 1];
83429
+ if (previous && previous.type === "text" && child.type === "text") previous.value += child.value;
83430
+ else nodes.push(child);
83431
+ }
83155
83432
  }
83156
83433
  }
83157
83434
  /**
@@ -83163,9 +83440,9 @@ function findAndReplace(tree, list, options) {
83163
83440
  * Clean pairs.
83164
83441
  */
83165
83442
  function toPairs(tupleOrList) {
83443
+ if (!Array.isArray(tupleOrList)) throw new TypeError("Expected find and replace tuple or list of tuples");
83166
83444
  /** @type {Pairs} */
83167
83445
  const result = [];
83168
- if (!Array.isArray(tupleOrList)) throw new TypeError("Expected find and replace tuple or list of tuples");
83169
83446
  /** @type {FindAndReplaceList} */
83170
83447
  const list = !tupleOrList[0] || Array.isArray(tupleOrList[0]) ? tupleOrList : [tupleOrList];
83171
83448
  let index = -1;
@@ -83179,9 +83456,9 @@ function toPairs(tupleOrList) {
83179
83456
  * Turn a find into an expression.
83180
83457
  *
83181
83458
  * @param {Find} find
83182
- * Find.
83459
+ * Pattern to find.
83183
83460
  * @returns {RegExp}
83184
- * Expression.
83461
+ * Global expression.
83185
83462
  */
83186
83463
  function toExpression(find) {
83187
83464
  return typeof find === "string" ? new RegExp(escapeStringRegexp(find), "g") : find;
@@ -83190,9 +83467,9 @@ function toExpression(find) {
83190
83467
  * Turn a replace into a function.
83191
83468
  *
83192
83469
  * @param {Replace} replace
83193
- * Replace.
83470
+ * Thing to replace with.
83194
83471
  * @returns {ReplaceFunction}
83195
- * Function.
83472
+ * Function that returns that thing.
83196
83473
  */
83197
83474
  function toFunction(replace) {
83198
83475
  return typeof replace === "function" ? replace : function() {
@@ -84056,7 +84333,7 @@ function toAlignment(value) {
84056
84333
  return code === 67 || code === 99 ? 99 : code === 76 || code === 108 ? 108 : code === 82 || code === 114 ? 114 : 0;
84057
84334
  }
84058
84335
  //#endregion
84059
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/configure.js
84336
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/configure.js
84060
84337
  /**
84061
84338
  * @import {Options, State} from './types.js'
84062
84339
  */
@@ -84123,7 +84400,7 @@ function map$2(left, right) {
84123
84400
  if (right) Object.assign(left, right);
84124
84401
  }
84125
84402
  //#endregion
84126
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js
84403
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js
84127
84404
  /**
84128
84405
  * @import {Blockquote, Parents} from 'mdast'
84129
84406
  * @import {Info, Map, State} from 'mdast-util-to-markdown'
@@ -84160,7 +84437,7 @@ function map$1(line, _, blank) {
84160
84437
  return ">" + (blank ? "" : " ") + line;
84161
84438
  }
84162
84439
  //#endregion
84163
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js
84440
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js
84164
84441
  /**
84165
84442
  * @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
84166
84443
  */
@@ -84197,7 +84474,7 @@ function listInScope(stack, list, none) {
84197
84474
  return false;
84198
84475
  }
84199
84476
  //#endregion
84200
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/break.js
84477
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/break.js
84201
84478
  /**
84202
84479
  * @import {Break, Parents} from 'mdast'
84203
84480
  * @import {Info, State} from 'mdast-util-to-markdown'
@@ -84253,7 +84530,7 @@ function longestStreak(value, substring) {
84253
84530
  return max;
84254
84531
  }
84255
84532
  //#endregion
84256
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js
84533
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/format-code-as-indented.js
84257
84534
  /**
84258
84535
  * @import {State} from 'mdast-util-to-markdown'
84259
84536
  * @import {Code} from 'mdast'
@@ -84272,7 +84549,7 @@ function formatCodeAsIndented(node, state) {
84272
84549
  return Boolean(state.options.fences === false && node.value && !node.lang && /[^\n\r ]/.test(node.value) && !/^[\t ]*(?:[\n\r]|$)|(?:^|[\n\r])[\t ]*$/.test(node.value));
84273
84550
  }
84274
84551
  //#endregion
84275
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-fence.js
84552
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-fence.js
84276
84553
  /**
84277
84554
  * @import {Options, State} from 'mdast-util-to-markdown'
84278
84555
  */
@@ -84290,7 +84567,7 @@ function checkFence(state) {
84290
84567
  return marker;
84291
84568
  }
84292
84569
  //#endregion
84293
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/code.js
84570
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/code.js
84294
84571
  /**
84295
84572
  * @import {Info, Map, State} from 'mdast-util-to-markdown'
84296
84573
  * @import {Code, Parents} from 'mdast'
@@ -84359,7 +84636,7 @@ function map(line, _, blank) {
84359
84636
  return (blank ? "" : " ") + line;
84360
84637
  }
84361
84638
  //#endregion
84362
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-quote.js
84639
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-quote.js
84363
84640
  /**
84364
84641
  * @import {Options, State} from 'mdast-util-to-markdown'
84365
84642
  */
@@ -84377,7 +84654,7 @@ function checkQuote(state) {
84377
84654
  return marker;
84378
84655
  }
84379
84656
  //#endregion
84380
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/definition.js
84657
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/definition.js
84381
84658
  /**
84382
84659
  * @import {Info, State} from 'mdast-util-to-markdown'
84383
84660
  * @import {Definition, Parents} from 'mdast'
@@ -84443,7 +84720,7 @@ function definition$1(node, _, state, info) {
84443
84720
  return value;
84444
84721
  }
84445
84722
  //#endregion
84446
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js
84723
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js
84447
84724
  /**
84448
84725
  * @import {Options, State} from 'mdast-util-to-markdown'
84449
84726
  */
@@ -84461,7 +84738,7 @@ function checkEmphasis(state) {
84461
84738
  return marker;
84462
84739
  }
84463
84740
  //#endregion
84464
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js
84741
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js
84465
84742
  /**
84466
84743
  * @import {AttentionInfo, Info, State} from 'mdast-util-to-markdown'
84467
84744
  * @import {Emphasis, Parents} from 'mdast'
@@ -84526,7 +84803,7 @@ function peek$1(_, _1, state) {
84526
84803
  return state.options.emphasis || "*";
84527
84804
  }
84528
84805
  //#endregion
84529
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js
84806
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js
84530
84807
  /**
84531
84808
  * Encode a code point as a character reference.
84532
84809
  *
@@ -84539,7 +84816,7 @@ function encodeCharacterReference(code) {
84539
84816
  return "&#x" + code.toString(16).toUpperCase() + ";";
84540
84817
  }
84541
84818
  //#endregion
84542
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js
84819
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/format-heading-as-setext.js
84543
84820
  /**
84544
84821
  * @import {State} from 'mdast-util-to-markdown'
84545
84822
  * @import {Heading} from 'mdast'
@@ -84565,7 +84842,7 @@ function formatHeadingAsSetext(node, state) {
84565
84842
  return Boolean((!node.depth || node.depth < 3) && toString(node) && (state.options.setext || literalWithBreak));
84566
84843
  }
84567
84844
  //#endregion
84568
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/heading.js
84845
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/heading.js
84569
84846
  /**
84570
84847
  * @import {Info, State} from 'mdast-util-to-markdown'
84571
84848
  * @import {Heading, Parents} from 'mdast'
@@ -84617,7 +84894,7 @@ function heading$1(node, _, state, info) {
84617
84894
  return value;
84618
84895
  }
84619
84896
  //#endregion
84620
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/html.js
84897
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/html.js
84621
84898
  /**
84622
84899
  * @import {Html} from 'mdast'
84623
84900
  */
@@ -84643,7 +84920,7 @@ function htmlPeek() {
84643
84920
  return "<";
84644
84921
  }
84645
84922
  //#endregion
84646
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/image.js
84923
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/image.js
84647
84924
  /**
84648
84925
  * @import {Info, State} from 'mdast-util-to-markdown'
84649
84926
  * @import {Image, Parents} from 'mdast'
@@ -84720,7 +84997,7 @@ function imagePeek() {
84720
84997
  return "!";
84721
84998
  }
84722
84999
  //#endregion
84723
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js
85000
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js
84724
85001
  /**
84725
85002
  * @import {Info, State} from 'mdast-util-to-markdown'
84726
85003
  * @import {ImageReference, Parents} from 'mdast'
@@ -84781,7 +85058,7 @@ function imageReferencePeek() {
84781
85058
  return "!";
84782
85059
  }
84783
85060
  //#endregion
84784
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js
85061
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js
84785
85062
  /**
84786
85063
  * @import {State} from 'mdast-util-to-markdown'
84787
85064
  * @import {InlineCode, Parents} from 'mdast'
@@ -84829,7 +85106,7 @@ function inlineCodePeek() {
84829
85106
  return "`";
84830
85107
  }
84831
85108
  //#endregion
84832
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js
85109
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/format-link-as-autolink.js
84833
85110
  /**
84834
85111
  * @import {State} from 'mdast-util-to-markdown'
84835
85112
  * @import {Link} from 'mdast'
@@ -84849,7 +85126,7 @@ function formatLinkAsAutolink(node, state) {
84849
85126
  return Boolean(!state.options.resourceLink && node.url && !node.title && node.children && node.children.length === 1 && node.children[0].type === "text" && (raw === node.url || "mailto:" + raw === node.url) && /^[a-z][+\-.a-z]+:/i.test(node.url) && !/[\0- <>\u007F]/.test(node.url));
84850
85127
  }
84851
85128
  //#endregion
84852
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/link.js
85129
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/link.js
84853
85130
  /**
84854
85131
  * @import {Info, State} from 'mdast-util-to-markdown'
84855
85132
  * @import {Link, Parents} from 'mdast'
@@ -84950,7 +85227,7 @@ function linkPeek(node, _, state) {
84950
85227
  return formatLinkAsAutolink(node, state) ? "<" : "[";
84951
85228
  }
84952
85229
  //#endregion
84953
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js
85230
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js
84954
85231
  /**
84955
85232
  * @import {Info, State} from 'mdast-util-to-markdown'
84956
85233
  * @import {LinkReference, Parents} from 'mdast'
@@ -85011,7 +85288,7 @@ function linkReferencePeek() {
85011
85288
  return "[";
85012
85289
  }
85013
85290
  //#endregion
85014
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js
85291
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js
85015
85292
  /**
85016
85293
  * @import {Options, State} from 'mdast-util-to-markdown'
85017
85294
  */
@@ -85029,7 +85306,7 @@ function checkBullet(state) {
85029
85306
  return marker;
85030
85307
  }
85031
85308
  //#endregion
85032
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js
85309
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js
85033
85310
  /**
85034
85311
  * @import {Options, State} from 'mdast-util-to-markdown'
85035
85312
  */
@@ -85050,7 +85327,7 @@ function checkBulletOther(state) {
85050
85327
  return bulletOther;
85051
85328
  }
85052
85329
  //#endregion
85053
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js
85330
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js
85054
85331
  /**
85055
85332
  * @import {Options, State} from 'mdast-util-to-markdown'
85056
85333
  */
@@ -85068,7 +85345,7 @@ function checkBulletOrdered(state) {
85068
85345
  return marker;
85069
85346
  }
85070
85347
  //#endregion
85071
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-rule.js
85348
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-rule.js
85072
85349
  /**
85073
85350
  * @import {Options, State} from 'mdast-util-to-markdown'
85074
85351
  */
@@ -85086,7 +85363,7 @@ function checkRule(state) {
85086
85363
  return marker;
85087
85364
  }
85088
85365
  //#endregion
85089
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/list.js
85366
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/list.js
85090
85367
  /**
85091
85368
  * @import {Info, State} from 'mdast-util-to-markdown'
85092
85369
  * @import {List, Parents} from 'mdast'
@@ -85136,7 +85413,7 @@ function list$2(node, parent, state, info) {
85136
85413
  return value;
85137
85414
  }
85138
85415
  //#endregion
85139
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js
85416
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-list-item-indent.js
85140
85417
  /**
85141
85418
  * @import {Options, State} from 'mdast-util-to-markdown'
85142
85419
  */
@@ -85154,7 +85431,7 @@ function checkListItemIndent(state) {
85154
85431
  return style;
85155
85432
  }
85156
85433
  //#endregion
85157
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/list-item.js
85434
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/list-item.js
85158
85435
  /**
85159
85436
  * @import {Info, Map, State} from 'mdast-util-to-markdown'
85160
85437
  * @import {ListItem, Parents} from 'mdast'
@@ -85197,7 +85474,7 @@ function listItem$1(node, parent, state, info) {
85197
85474
  }
85198
85475
  }
85199
85476
  //#endregion
85200
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js
85477
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js
85201
85478
  /**
85202
85479
  * @import {Info, State} from 'mdast-util-to-markdown'
85203
85480
  * @import {Paragraph, Parents} from 'mdast'
@@ -85225,7 +85502,7 @@ function paragraph$1(node, _, state, info) {
85225
85502
  return value;
85226
85503
  }
85227
85504
  //#endregion
85228
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/root.js
85505
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/root.js
85229
85506
  /**
85230
85507
  * @import {Info, State} from 'mdast-util-to-markdown'
85231
85508
  * @import {Parents, Root} from 'mdast'
@@ -85254,7 +85531,7 @@ function root$1(node, _, state, info) {
85254
85531
  return value;
85255
85532
  }
85256
85533
  //#endregion
85257
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-strong.js
85534
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-strong.js
85258
85535
  /**
85259
85536
  * @import {Options, State} from 'mdast-util-to-markdown'
85260
85537
  */
@@ -85272,7 +85549,7 @@ function checkStrong(state) {
85272
85549
  return marker;
85273
85550
  }
85274
85551
  //#endregion
85275
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/strong.js
85552
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/strong.js
85276
85553
  /**
85277
85554
  * @import {AttentionInfo, Info, State} from 'mdast-util-to-markdown'
85278
85555
  * @import {Parents, Strong} from 'mdast'
@@ -85337,7 +85614,7 @@ function peek(_, _1, state) {
85337
85614
  return state.options.strong || "*";
85338
85615
  }
85339
85616
  //#endregion
85340
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/text.js
85617
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/text.js
85341
85618
  /**
85342
85619
  * @import {Info, State} from 'mdast-util-to-markdown'
85343
85620
  * @import {Parents, Text} from 'mdast'
@@ -85347,7 +85624,7 @@ function peek(_, _1, state) {
85347
85624
  *
85348
85625
  * @param {Text} node
85349
85626
  * Node to serialize.
85350
- * @param {Parents | undefined} _
85627
+ * @param {Parents | undefined} parent
85351
85628
  * Parent node.
85352
85629
  * @param {State} state
85353
85630
  * Info passed around.
@@ -85356,11 +85633,18 @@ function peek(_, _1, state) {
85356
85633
  * @returns {string}
85357
85634
  * Serialized markdown.
85358
85635
  */
85359
- function text$4(node, _, state, info) {
85360
- return state.safe(node.value, info);
85636
+ function text$4(node, parent, state, info) {
85637
+ /* c8 ignore next -- parent always passed */
85638
+ const siblings = parent ? parent.children : [];
85639
+ const index = siblings.indexOf(node);
85640
+ return state.safe(node.value, {
85641
+ ...info,
85642
+ afterNode: siblings[index + 1],
85643
+ beforeNode: siblings[index - 1]
85644
+ });
85361
85645
  }
85362
85646
  //#endregion
85363
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js
85647
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js
85364
85648
  /**
85365
85649
  * @import {Options, State} from 'mdast-util-to-markdown'
85366
85650
  */
@@ -85378,7 +85662,7 @@ function checkRuleRepetition(state) {
85378
85662
  return repetition;
85379
85663
  }
85380
85664
  //#endregion
85381
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js
85665
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js
85382
85666
  /**
85383
85667
  * @import {State} from 'mdast-util-to-markdown'
85384
85668
  * @import {Parents, ThematicBreak} from 'mdast'
@@ -85400,7 +85684,7 @@ function thematicBreak$2(_, _1, state) {
85400
85684
  return state.options.ruleSpaces ? value.slice(0, -1) : value;
85401
85685
  }
85402
85686
  //#endregion
85403
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/index.js
85687
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/index.js
85404
85688
  /**
85405
85689
  * Default (CommonMark) handlers.
85406
85690
  */
@@ -85526,7 +85810,7 @@ const htmlRawNames = [
85526
85810
  "textarea"
85527
85811
  ];
85528
85812
  //#endregion
85529
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/html-kind.js
85813
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/html-kind.js
85530
85814
  /**
85531
85815
  * @typedef {1 | 2 | 3 | 4 | 5 | 6 | 7} Kind
85532
85816
  * Kind of HTML (flow) element.
@@ -85574,7 +85858,7 @@ function htmlKind(value) {
85574
85858
  return htmlBlockNames.includes(name) ? 6 : 7;
85575
85859
  }
85576
85860
  //#endregion
85577
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/join.js
85861
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/join.js
85578
85862
  /**
85579
85863
  * @import {Join} from 'mdast-util-to-markdown'
85580
85864
  */
@@ -85601,7 +85885,7 @@ function joinDefaults(left, right, parent, state) {
85601
85885
  }
85602
85886
  }
85603
85887
  //#endregion
85604
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/unsafe.js
85888
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/unsafe.js
85605
85889
  /**
85606
85890
  * @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
85607
85891
  */
@@ -88069,7 +88353,7 @@ function decode($0, $1, $2) {
88069
88353
  return decodeNamedCharacterReference($2) || $0;
88070
88354
  }
88071
88355
  //#endregion
88072
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/association.js
88356
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/association.js
88073
88357
  /**
88074
88358
  * @import {AssociationId} from '../types.js'
88075
88359
  */
@@ -88098,7 +88382,7 @@ function association(node) {
88098
88382
  return decodeString(node.identifier);
88099
88383
  }
88100
88384
  //#endregion
88101
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js
88385
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js
88102
88386
  /**
88103
88387
  * @import {CompilePattern} from '../types.js'
88104
88388
  */
@@ -88110,7 +88394,7 @@ function association(node) {
88110
88394
  function compilePattern(pattern) {
88111
88395
  if (!pattern._compiled) {
88112
88396
  const before = (pattern.atBreak ? "[\\r\\n][\\t ]*" : "") + (pattern.before ? "(?:" + pattern.before + ")" : "");
88113
- pattern._compiled = new RegExp((before ? "(" + before + ")" : "") + (/[$()*+\-.?[\\\]^{|}]/.test(pattern.character) ? "\\" : "") + pattern.character + (pattern.after ? "(?:" + pattern.after + ")" : ""), "g");
88397
+ pattern._compiled = new RegExp((pattern.beforeNode ? "^" : before ? "(" + before + ")" : "") + (/[$()*+.?[\\\]^{|}]/.test(pattern.character) ? "\\" : "") + pattern.character + (pattern.afterNode ? "$" : pattern.after ? "(?:" + pattern.after + ")" : ""), pattern.unicode ? "gu" : "g");
88114
88398
  }
88115
88399
  return pattern._compiled;
88116
88400
  }
@@ -88138,7 +88422,7 @@ function classifyCharacter(code) {
88138
88422
  if (unicodePunctuation(code)) return 2;
88139
88423
  }
88140
88424
  //#endregion
88141
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/character.js
88425
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/character.js
88142
88426
  /**
88143
88427
  * Get the first character of a value.
88144
88428
  *
@@ -88168,7 +88452,7 @@ function lastCharacter(value) {
88168
88452
  return code !== void 0 && code > 65535 ? value.slice(-2) : value.slice(-1);
88169
88453
  }
88170
88454
  //#endregion
88171
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/encode-info.js
88455
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/encode-info.js
88172
88456
  /**
88173
88457
  * @import {EncodeSides} from '../types.js'
88174
88458
  */
@@ -88240,7 +88524,7 @@ function encodeInfo(outside, inside, marker) {
88240
88524
  };
88241
88525
  }
88242
88526
  //#endregion
88243
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js
88527
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js
88244
88528
  /**
88245
88529
  * @import {Attention, Handle, Info, State} from 'mdast-util-to-markdown'
88246
88530
  * @import {PhrasingParents} from '../types.js'
@@ -88733,7 +89017,7 @@ function serialize(items, before, after) {
88733
89017
  return result;
88734
89018
  }
88735
89019
  //#endregion
88736
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/container-flow.js
89020
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/container-flow.js
88737
89021
  /**
88738
89022
  * @import {State} from 'mdast-util-to-markdown'
88739
89023
  * @import {FlowChildren, FlowParents, TrackFields} from '../types.js'
@@ -88797,7 +89081,7 @@ function between(left, right, parent, state) {
88797
89081
  return "\n\n";
88798
89082
  }
88799
89083
  //#endregion
88800
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js
89084
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js
88801
89085
  /**
88802
89086
  * @import {IndentLines} from '../types.js'
88803
89087
  */
@@ -88833,9 +89117,10 @@ function indentLines(value, map) {
88833
89117
  }
88834
89118
  }
88835
89119
  //#endregion
88836
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/safe.js
89120
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/safe.js
88837
89121
  /**
88838
- * @import {SafeConfig, State} from 'mdast-util-to-markdown'
89122
+ * @import {Nodes} from 'mdast'
89123
+ * @import {SafeConfig, State, Unsafe} from 'mdast-util-to-markdown'
88839
89124
  */
88840
89125
  const own$3 = {}.hasOwnProperty;
88841
89126
  /**
@@ -88872,17 +89157,21 @@ function safe(state, input, config) {
88872
89157
  /** @type {Record<number, {before: boolean, after: boolean}>} */
88873
89158
  const infos = {};
88874
89159
  const percentEncode = state.stack.includes("autolink");
89160
+ const offset = config.before ? config.before.length : 0;
89161
+ const end = value.length - (config.after ? config.after.length : 0);
88875
89162
  let index = -1;
88876
89163
  while (++index < state.unsafe.length) {
88877
89164
  const pattern = state.unsafe[index];
88878
- if (!patternInScope(state.stack, pattern)) continue;
89165
+ if (!patternInScope(state.stack, pattern) || !nodeInScope(pattern.beforeNode, config.beforeNode) || !nodeInScope(pattern.afterNode, config.afterNode)) continue;
88879
89166
  const expression = state.compilePattern(pattern);
89167
+ const from = pattern.beforeNode ? offset : 0;
89168
+ const subject = value.slice(from, pattern.afterNode ? end : void 0);
88880
89169
  /** @type {RegExpExecArray | null} */
88881
89170
  let match;
88882
- while (match = expression.exec(value)) {
89171
+ while (match = expression.exec(subject)) {
88883
89172
  const before = "before" in pattern || Boolean(pattern.atBreak);
88884
89173
  const after = "after" in pattern;
88885
- const position = match.index + (before ? match[1].length : 0);
89174
+ const position = from + match.index + (before ? match[1].length : 0);
88886
89175
  if (own$3.call(infos, position)) {
88887
89176
  if (infos[position].before && !before) infos[position].before = false;
88888
89177
  if (infos[position].after && !after) infos[position].after = false;
@@ -88896,8 +89185,6 @@ function safe(state, input, config) {
88896
89185
  }
88897
89186
  }
88898
89187
  positions.sort(numerical);
88899
- const offset = config.before ? config.before.length : 0;
88900
- const end = value.length - (config.after ? config.after.length : 0);
88901
89188
  let start = offset;
88902
89189
  index = -1;
88903
89190
  while (++index < positions.length) {
@@ -88932,6 +89219,21 @@ function safe(state, input, config) {
88932
89219
  return result.join("");
88933
89220
  }
88934
89221
  /**
89222
+ * Check if a node matches the node types of a pattern.
89223
+ *
89224
+ * @param {Array<Nodes['type']> | Nodes['type'] | null | undefined} types
89225
+ * Node types.
89226
+ * @param {Nodes | undefined} node
89227
+ * Node.
89228
+ * @returns {boolean}
89229
+ * Whether `node` matches `types`.
89230
+ */
89231
+ function nodeInScope(types, node) {
89232
+ if (!types) return true;
89233
+ if (!node) return false;
89234
+ return typeof types === "string" ? types === node.type : types.includes(node.type);
89235
+ }
89236
+ /**
88935
89237
  * Compare two numbers.
88936
89238
  *
88937
89239
  * @param {number} a
@@ -88975,7 +89277,7 @@ function escapeBackslashes(value, after) {
88975
89277
  return results.join("");
88976
89278
  }
88977
89279
  //#endregion
88978
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/track.js
89280
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/track.js
88979
89281
  /**
88980
89282
  * @import {CreateTracker, TrackCurrent, TrackMove, TrackShift} from '../types.js'
88981
89283
  */
@@ -89033,7 +89335,7 @@ function track(config) {
89033
89335
  }
89034
89336
  }
89035
89337
  //#endregion
89036
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/index.js
89338
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/index.js
89037
89339
  /**
89038
89340
  * @import {Info, Join, Options, SafeConfig, State} from 'mdast-util-to-markdown'
89039
89341
  * @import {Nodes} from 'mdast'
@@ -99040,7 +99342,7 @@ function preprocess() {
99040
99342
  }
99041
99343
  }
99042
99344
  //#endregion
99043
- //#region node_modules/.pnpm/mdast-util-from-markdown@2.0.3_supports-color@7.2.0/node_modules/mdast-util-from-markdown/lib/index.js
99345
+ //#region node_modules/.pnpm/mdast-util-from-markdown@2.1.0_supports-color@7.2.0/node_modules/mdast-util-from-markdown/lib/index.js
99044
99346
  /**
99045
99347
  * @import {
99046
99348
  * Break,
@@ -99120,6 +99422,8 @@ function fromMarkdown(value, encoding, options) {
99120
99422
  function compiler(options) {
99121
99423
  /** @type {Config} */
99122
99424
  const config = {
99425
+ afterExit: [],
99426
+ beforeEnter: [],
99123
99427
  transforms: [],
99124
99428
  canContainEols: [
99125
99429
  "emphasis",
@@ -99239,7 +99543,8 @@ function compiler(options) {
99239
99543
  /** @type {Root} */
99240
99544
  let tree = {
99241
99545
  type: "root",
99242
- children: []
99546
+ children: [],
99547
+ position: void 0
99243
99548
  };
99244
99549
  /** @type {Omit<CompileContext, 'sliceSerialize'>} */
99245
99550
  const context = {
@@ -99254,15 +99559,32 @@ function compiler(options) {
99254
99559
  };
99255
99560
  /** @type {Array<number>} */
99256
99561
  const listStack = [];
99562
+ /** @type {Array<Event>} */
99563
+ const prepared = [];
99257
99564
  let index = -1;
99258
- while (++index < events.length) if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
99259
- if (events[index][0] === "enter") listStack.push(index);
99260
- else index = prepareList(events, listStack.pop(), index);
99565
+ while (++index < events.length) {
99566
+ prepared.push(events[index]);
99567
+ if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
99568
+ if (events[index][0] === "enter") listStack.push(prepared.length - 1);
99569
+ else prepareList(prepared, listStack.pop());
99570
+ }
99261
99571
  }
99572
+ events = prepared;
99262
99573
  index = -1;
99263
99574
  while (++index < events.length) {
99264
99575
  const handler = config[events[index][0]];
99265
- if (own$2.call(handler, events[index][1].type)) handler[events[index][1].type].call(Object.assign({ sliceSerialize: events[index][2].sliceSerialize }, context), events[index][1]);
99576
+ if (events[index][0] === "enter" && config.beforeEnter.length > 0) callListeners(config.beforeEnter, {
99577
+ ...context,
99578
+ sliceSerialize: events[index][2].sliceSerialize
99579
+ }, events[index][1]);
99580
+ if (own$2.call(handler, events[index][1].type)) handler[events[index][1].type].call({
99581
+ ...context,
99582
+ sliceSerialize: events[index][2].sliceSerialize
99583
+ }, events[index][1]);
99584
+ if (events[index][0] === "exit" && config.afterExit.length > 0) callListeners(config.afterExit, {
99585
+ ...context,
99586
+ sliceSerialize: events[index][2].sliceSerialize
99587
+ }, events[index][1]);
99266
99588
  }
99267
99589
  if (context.tokenStack.length > 0) {
99268
99590
  const tail = context.tokenStack[context.tokenStack.length - 1];
@@ -99285,12 +99607,28 @@ function compiler(options) {
99285
99607
  return tree;
99286
99608
  }
99287
99609
  /**
99610
+ * @param {Array<Handle>} listeners
99611
+ * Listeners.
99612
+ * @param {CompileContext} context
99613
+ * Context.
99614
+ * @param {Token} token
99615
+ * Token.
99616
+ * @returns {undefined}
99617
+ * Nothing.
99618
+ */
99619
+ function callListeners(listeners, context, token) {
99620
+ let index = -1;
99621
+ while (++index < listeners.length) listeners[index].call(context, token);
99622
+ }
99623
+ /**
99288
99624
  * @param {Array<Event>} events
99625
+ * Events, ending with the list to prepare.
99289
99626
  * @param {number} start
99290
- * @param {number} length
99291
- * @returns {number}
99627
+ * Index of the list enter.
99628
+ * @returns {undefined}
99292
99629
  */
99293
- function prepareList(events, start, length) {
99630
+ function prepareList(events, start) {
99631
+ const end = events.length - 1;
99294
99632
  let index = start - 1;
99295
99633
  let containerBalance = -1;
99296
99634
  let listSpread = false;
@@ -99302,7 +99640,14 @@ function compiler(options) {
99302
99640
  let firstBlankLineIndex;
99303
99641
  /** @type {boolean | undefined} */
99304
99642
  let atMarker;
99305
- while (++index <= length) {
99643
+ /**
99644
+ * `listItem` events to insert before the event at `at`, in walk order.
99645
+ * Merged in after the walk, as inserting while walking would shift the
99646
+ * rest of the list once per item.
99647
+ * @type {Array<{at: number, event: Event}>}
99648
+ */
99649
+ const insertions = [];
99650
+ while (++index <= end) {
99306
99651
  const event = events[index];
99307
99652
  switch (event[1].type) {
99308
99653
  case "listUnordered":
@@ -99343,13 +99688,14 @@ function compiler(options) {
99343
99688
  }
99344
99689
  if (firstBlankLineIndex && (!lineIndex || firstBlankLineIndex < lineIndex)) listItem._spread = true;
99345
99690
  listItem.end = Object.assign({}, lineIndex ? events[lineIndex][1].start : event[1].end);
99346
- events.splice(lineIndex || index, 0, [
99347
- "exit",
99348
- listItem,
99349
- event[2]
99350
- ]);
99351
- index++;
99352
- length++;
99691
+ insertions.push({
99692
+ at: lineIndex || index,
99693
+ event: [
99694
+ "exit",
99695
+ listItem,
99696
+ event[2]
99697
+ ]
99698
+ });
99353
99699
  }
99354
99700
  if (event[1].type === "listItemPrefix") {
99355
99701
  /** @type {Token} */
@@ -99360,20 +99706,27 @@ function compiler(options) {
99360
99706
  end: void 0
99361
99707
  };
99362
99708
  listItem = item;
99363
- events.splice(index, 0, [
99364
- "enter",
99365
- item,
99366
- event[2]
99367
- ]);
99368
- index++;
99369
- length++;
99709
+ insertions.push({
99710
+ at: index,
99711
+ event: [
99712
+ "enter",
99713
+ item,
99714
+ event[2]
99715
+ ]
99716
+ });
99370
99717
  firstBlankLineIndex = void 0;
99371
99718
  atMarker = true;
99372
99719
  }
99373
99720
  }
99374
99721
  }
99722
+ const listEvents = events.splice(start);
99723
+ let insertion = 0;
99724
+ index = -1;
99725
+ while (++index < listEvents.length) {
99726
+ while (insertion < insertions.length && insertions[insertion].at === start + index) events.push(insertions[insertion++].event);
99727
+ events.push(listEvents[index]);
99728
+ }
99375
99729
  events[start][1]._spread = listSpread;
99376
- return length;
99377
99730
  }
99378
99731
  /**
99379
99732
  * Create an opener handle.
@@ -99448,7 +99801,7 @@ function compiler(options) {
99448
99801
  start: token.start,
99449
99802
  end: token.end
99450
99803
  }) + "): it’s not open");
99451
- else if (open[0].type !== token.type) {
99804
+ if (open[0].type !== token.type) {
99452
99805
  if (onExitError) onExitError.call(this, token, open[0]);
99453
99806
  else (open[1] || defaultOnError).call(this, token, open[0]);
99454
99807
  }
@@ -99816,7 +100169,8 @@ function compiler(options) {
99816
100169
  function blockQuote() {
99817
100170
  return {
99818
100171
  type: "blockquote",
99819
- children: []
100172
+ children: [],
100173
+ position: void 0
99820
100174
  };
99821
100175
  }
99822
100176
  /** @returns {Code} */
@@ -99825,14 +100179,16 @@ function compiler(options) {
99825
100179
  type: "code",
99826
100180
  lang: null,
99827
100181
  meta: null,
99828
- value: ""
100182
+ value: "",
100183
+ position: void 0
99829
100184
  };
99830
100185
  }
99831
100186
  /** @returns {InlineCode} */
99832
100187
  function codeText() {
99833
100188
  return {
99834
100189
  type: "inlineCode",
99835
- value: ""
100190
+ value: "",
100191
+ position: void 0
99836
100192
  };
99837
100193
  }
99838
100194
  /** @returns {Definition} */
@@ -99842,14 +100198,16 @@ function compiler(options) {
99842
100198
  identifier: "",
99843
100199
  label: null,
99844
100200
  title: null,
99845
- url: ""
100201
+ url: "",
100202
+ position: void 0
99846
100203
  };
99847
100204
  }
99848
100205
  /** @returns {Emphasis} */
99849
100206
  function emphasis() {
99850
100207
  return {
99851
100208
  type: "emphasis",
99852
- children: []
100209
+ children: [],
100210
+ position: void 0
99853
100211
  };
99854
100212
  }
99855
100213
  /** @returns {Heading} */
@@ -99857,18 +100215,23 @@ function compiler(options) {
99857
100215
  return {
99858
100216
  type: "heading",
99859
100217
  depth: 0,
99860
- children: []
100218
+ children: [],
100219
+ position: void 0
99861
100220
  };
99862
100221
  }
99863
100222
  /** @returns {Break} */
99864
100223
  function hardBreak() {
99865
- return { type: "break" };
100224
+ return {
100225
+ type: "break",
100226
+ position: void 0
100227
+ };
99866
100228
  }
99867
100229
  /** @returns {Html} */
99868
100230
  function html() {
99869
100231
  return {
99870
100232
  type: "html",
99871
- value: ""
100233
+ value: "",
100234
+ position: void 0
99872
100235
  };
99873
100236
  }
99874
100237
  /** @returns {Image} */
@@ -99877,7 +100240,8 @@ function compiler(options) {
99877
100240
  type: "image",
99878
100241
  title: null,
99879
100242
  url: "",
99880
- alt: null
100243
+ alt: null,
100244
+ position: void 0
99881
100245
  };
99882
100246
  }
99883
100247
  /** @returns {Link} */
@@ -99886,7 +100250,8 @@ function compiler(options) {
99886
100250
  type: "link",
99887
100251
  title: null,
99888
100252
  url: "",
99889
- children: []
100253
+ children: [],
100254
+ position: void 0
99890
100255
  };
99891
100256
  }
99892
100257
  /**
@@ -99899,7 +100264,8 @@ function compiler(options) {
99899
100264
  ordered: token.type === "listOrdered",
99900
100265
  start: null,
99901
100266
  spread: token._spread,
99902
- children: []
100267
+ children: [],
100268
+ position: void 0
99903
100269
  };
99904
100270
  }
99905
100271
  /**
@@ -99911,33 +100277,40 @@ function compiler(options) {
99911
100277
  type: "listItem",
99912
100278
  spread: token._spread,
99913
100279
  checked: null,
99914
- children: []
100280
+ children: [],
100281
+ position: void 0
99915
100282
  };
99916
100283
  }
99917
100284
  /** @returns {Paragraph} */
99918
100285
  function paragraph() {
99919
100286
  return {
99920
100287
  type: "paragraph",
99921
- children: []
100288
+ children: [],
100289
+ position: void 0
99922
100290
  };
99923
100291
  }
99924
100292
  /** @returns {Strong} */
99925
100293
  function strong() {
99926
100294
  return {
99927
100295
  type: "strong",
99928
- children: []
100296
+ children: [],
100297
+ position: void 0
99929
100298
  };
99930
100299
  }
99931
100300
  /** @returns {Text} */
99932
100301
  function text() {
99933
100302
  return {
99934
100303
  type: "text",
99935
- value: ""
100304
+ value: "",
100305
+ position: void 0
99936
100306
  };
99937
100307
  }
99938
100308
  /** @returns {ThematicBreak} */
99939
100309
  function thematicBreak() {
99940
- return { type: "thematicBreak" };
100310
+ return {
100311
+ type: "thematicBreak",
100312
+ position: void 0
100313
+ };
99941
100314
  }
99942
100315
  }
99943
100316
  /**
@@ -99987,6 +100360,12 @@ function extension(combined, extension) {
99987
100360
  if (right) combined[key].push(...right);
99988
100361
  break;
99989
100362
  }
100363
+ case "afterExit":
100364
+ case "beforeEnter": {
100365
+ const right = extension[key];
100366
+ if (right) combined[key].push(right);
100367
+ break;
100368
+ }
99990
100369
  case "enter":
99991
100370
  case "exit": {
99992
100371
  const right = extension[key];
@@ -100004,7 +100383,7 @@ function defaultOnError(left, right) {
100004
100383
  start: right.start,
100005
100384
  end: right.end
100006
100385
  }) + ") is open");
100007
- else throw new Error("Cannot close document, a token (`" + right.type + "`, " + stringifyPosition({
100386
+ throw new Error("Cannot close document, a token (`" + right.type + "`, " + stringifyPosition({
100008
100387
  start: right.start,
100009
100388
  end: right.end
100010
100389
  }) + ") is still open");
@@ -103736,6 +104115,7 @@ const formatNodeTranslationSummary = (translation) => [
103736
104115
  `- **Updated**: ${translation.updated_at}`
103737
104116
  ].join("\n");
103738
104117
  const formatCategory = (category) => `- **${category.name}** (${category.id}) — ${category.description || "No description"}`;
104118
+ const formatBrand = (brand) => `- **${brand.name}** (${brand.id}) — ${brand.brand_url}${brand.default ? " — default" : ""}${brand.active ? "" : " — inactive"}`;
103739
104119
  const formatSection = (section) => `- **${section.name}** (${section.id}) — Category: ${section.category_id} — ${section.description || "No description"}`;
103740
104120
  const formatView = (view, count) => {
103741
104121
  const countText = count ? ` — ${count.pretty} ticket(s)${count.fresh ? "" : " (count updating)"}` : "";
@@ -103786,13 +104166,7 @@ const extractSearchPaginationMeta = (response, perPage, page) => {
103786
104166
  };
103787
104167
  //#endregion
103788
104168
  //#region src/guidance/article-resources.ts
103789
- /**
103790
- * Name of the companion tool that lists promoted articles. Shared between the
103791
- * tool definition (`help-center.ts`) and the `--no-promoted-articles` opt-out
103792
- * filter (`server.ts`) so the two can never drift: renaming the tool here keeps
103793
- * the filter dropping it, preserving the "zero preloading requests when off"
103794
- * invariant.
103795
- */
104169
+ /** Name of the companion tool that lists promoted articles (`help-center.ts`). */
103796
104170
  const LIST_PROMOTED_ARTICLES_TOOL = "list_promoted_articles";
103797
104171
  /**
103798
104172
  * Scan the Help Center for promoted ("featured") articles with the CALLER'S
@@ -103801,9 +104175,11 @@ const LIST_PROMOTED_ARTICLES_TOOL = "list_promoted_articles";
103801
104175
  * `/articles` and filter `promoted` client-side, bounded by `maxPages` to keep
103802
104176
  * the scan tractable on a large Help Center. `truncated` signals the cap was hit.
103803
104177
  *
103804
- * Returns the FULL promoted articles so callers that need rich metadata (the
103805
- * `list_promoted_articles` tool) get everything; the resource provider maps these
103806
- * down to lean refs before caching so the per-session cache doesn't retain bodies.
104178
+ * `subdomain` is the EFFECTIVE one — the account's, or the resolved brand's own
104179
+ * (a brand is only ever <subdomain>.zendesk.com). Returns the FULL promoted
104180
+ * articles so callers that need rich metadata (the `list_promoted_articles`
104181
+ * tool) get everything; the resource provider maps these down to lean refs
104182
+ * before caching so the per-session cache doesn't retain bodies.
103807
104183
  */
103808
104184
  const fetchPromotedArticles = async (subdomain, token, maxPages = ARTICLE_RESOURCES_SCAN_MAX_PAGES) => {
103809
104185
  const promoted = [];
@@ -103833,6 +104209,7 @@ const fetchPromotedArticles = async (subdomain, token, maxPages = ARTICLE_RESOUR
103833
104209
  * caller's token and render it as Markdown: the shared metadata summary plus the
103834
104210
  * body converted from HTML (rather than a raw HTML dump), capped by the response
103835
104211
  * character limit. Reuses the same formatting as the `get_article` tool.
104212
+ * `subdomain` is the EFFECTIVE one — the account's, or the resolved brand's own.
103836
104213
  */
103837
104214
  const fetchArticleMarkdown = async (subdomain, token, id, locale) => {
103838
104215
  const path = locale ? `/${locale}/articles/${id}` : `/articles/${id}`;
@@ -103852,24 +104229,69 @@ const fetchArticleMarkdown = async (subdomain, token, id, locale) => {
103852
104229
  * module scope — in HTTP mode a shared one would leak a caller's data to
103853
104230
  * another. `getToken` resolves lazily, so connecting never triggers the OAuth
103854
104231
  * flow, and a 401 notifies `onUnauthorized` to drop the stale token.
104232
+ *
104233
+ * Branding is resolved through the SHARED brand subdomain resolver, never a
104234
+ * second /brands walk: `brandIds` decides which brands `listPromoted` scans
104235
+ * (all of them in multi mode, so every allowed brand's promoted articles are
104236
+ * listed with their own URIs; 'all' scans every brand of the account), and
104237
+ * `readArticle` resolves the URI's {brand} through the same allow-list +
104238
+ * resolver the tools use.
103855
104239
  */
103856
- const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized) => {
104240
+ const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized, resolveBrandSubdomain = () => Promise.resolve(subdomain), brandIds) => {
103857
104241
  let cached;
103858
104242
  const notifyIfUnauthorized = (err) => {
103859
104243
  if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
103860
104244
  };
104245
+ const isSingleLock = brandIds !== void 0 && brandIds.length === 1 && brandIds[0] !== "all";
104246
+ const listBrands = async () => {
104247
+ if (brandIds === void 0 || isSingleLock) return [void 0];
104248
+ if (brandIds[0] === "all") return (await fetchAllBrands(subdomain, await getToken())).filter((b) => b.active && b.has_help_center).map((b) => b.subdomain);
104249
+ return brandIds;
104250
+ };
104251
+ const effectiveSubdomain = async (brand) => {
104252
+ if (brand !== void 0) return resolveBrandSubdomain(brand);
104253
+ if (isSingleLock) return resolveBrandSubdomain(brandIds[0]);
104254
+ return subdomain;
104255
+ };
104256
+ const scanBrand = async (brand, token) => {
104257
+ try {
104258
+ const hcSubdomain = await effectiveSubdomain(brand);
104259
+ const scan = await fetchPromotedArticles(hcSubdomain, token, ARTICLE_RESOURCES_SCAN_MAX_PAGES);
104260
+ return {
104261
+ refs: scan.articles.map((a) => brand === void 0 ? {
104262
+ id: a.id,
104263
+ title: a.title
104264
+ } : {
104265
+ id: a.id,
104266
+ title: a.title,
104267
+ brand
104268
+ }),
104269
+ truncated: scan.truncated
104270
+ };
104271
+ } catch (err) {
104272
+ if (err instanceof ZendeskApiError && err.status === 401) throw err;
104273
+ return {
104274
+ refs: [],
104275
+ truncated: true
104276
+ };
104277
+ }
104278
+ };
103861
104279
  return {
103862
104280
  listPromoted() {
103863
104281
  const now = Date.now();
103864
104282
  if (cached && now - cached.at < 3e5) return cached.promise;
103865
104283
  const promise = (async () => {
103866
104284
  const token = await getToken();
103867
- const { articles, truncated } = await fetchPromotedArticles(subdomain, token);
104285
+ const brands = await listBrands();
104286
+ const refs = [];
104287
+ let truncated = false;
104288
+ for (const brand of brands) {
104289
+ const scan = await scanBrand(brand, token);
104290
+ truncated = truncated || scan.truncated;
104291
+ refs.push(...scan.refs);
104292
+ }
103868
104293
  return {
103869
- refs: articles.map((a) => ({
103870
- id: a.id,
103871
- title: a.title
103872
- })),
104294
+ refs,
103873
104295
  truncated
103874
104296
  };
103875
104297
  })().catch((err) => {
@@ -103883,10 +104305,10 @@ const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized) =>
103883
104305
  };
103884
104306
  return promise;
103885
104307
  },
103886
- async readArticle(id) {
104308
+ async readArticle(id, brand) {
103887
104309
  try {
103888
- const token = await getToken();
103889
- return await fetchArticleMarkdown(subdomain, token, id);
104310
+ const [token, hcSubdomain] = await Promise.all([getToken(), effectiveSubdomain(brand)]);
104311
+ return await fetchArticleMarkdown(hcSubdomain, token, id, void 0);
103890
104312
  } catch (err) {
103891
104313
  notifyIfUnauthorized(err);
103892
104314
  throw err;
@@ -103907,17 +104329,33 @@ const topologyResourceUri = (config) => `${config.hcResourceScheme}://topology`;
103907
104329
  /**
103908
104330
  * URI template of the pull-only Help Center article resources, built from the
103909
104331
  * configured scheme exactly like `topologyResourceUri` (`--hc-resource-scheme`,
103910
- * default `zendesk-hc` → `zendesk-hc://article/{id}`). The template's `list`
103911
- * callback enumerates the promoted ("featured") articles (so clients can surface
103912
- * them for pinning), while any article id can be read on demand.
104332
+ * default `zendesk-hc`). In multi/all brand mode the URI carries a brand
104333
+ * dimension — `<scheme>://brands/{brand}/articles/{id}` — because an article id
104334
+ * alone does not say which brand's Help Center holds it; the brand is named by
104335
+ * id or subdomain, the same idiom the tools' `brand_id` accepts. Unset and
104336
+ * single-brand modes keep `<scheme>://article/{id}`: one Help Center is
104337
+ * addressed, so a brand segment would be noise. The template's `list` callback
104338
+ * enumerates the promoted ("featured") articles (so clients can surface them
104339
+ * for pinning), while any article id can be read on demand.
103913
104340
  */
103914
- const articleResourceUriTemplate = (config) => `${config.hcResourceScheme}://article/{id}`;
104341
+ const articleResourceUriTemplate = (config) => brandScopedArticles(config) ? `${config.hcResourceScheme}://brands/{brand}/articles/{id}` : `${config.hcResourceScheme}://article/{id}`;
103915
104342
  /**
103916
104343
  * Build the concrete resource URI for a single article id, under the configured
103917
104344
  * scheme. Shares the scheme with the template above so the listed URIs always
103918
- * match the template the read callback is registered under.
104345
+ * match the template the read callback is registered under. In multi/all mode
104346
+ * the brand argument (id or subdomain) is required and fills the {brand} slot.
104347
+ */
104348
+ const articleResourceUri = (config, id, brand) => brandScopedArticles(config) ? `${config.hcResourceScheme}://brands/${brand}/articles/${id}` : `${config.hcResourceScheme}://article/${id}`;
104349
+ /**
104350
+ * Whether the article resources address one Help Center among several: the
104351
+ * server was started with several brands, or with `all`. Only then does the URI
104352
+ * carry the brand dimension; unset and single-lock modes name exactly one Help
104353
+ * Center implicitly.
103919
104354
  */
103920
- const articleResourceUri = (config, id) => `${config.hcResourceScheme}://article/${id}`;
104355
+ const brandScopedArticles = (config) => {
104356
+ const brandIds = config.brandIds;
104357
+ return brandIds !== void 0 && (brandIds.length > 1 || brandIds[0] === "all");
104358
+ };
103921
104359
  /**
103922
104360
  * Whether the `help_center` namespace is active: no `--namespace` filter, or one
103923
104361
  * that includes it. The shared second half of the two Help Center feature gates
@@ -103943,13 +104381,11 @@ const helpCenterContextEnabled = (config) => config.topology && helpCenterNamesp
103943
104381
  const articleResourceEnabled = (config) => helpCenterNamespaceActive(config);
103944
104382
  /**
103945
104383
  * Whether the promoted-article PRE-LISTING is exposed: the resource `list`
103946
- * callback's scan (which enumerates the promoted articles for `resources/list`)
103947
- * AND the `list_promoted_articles` tool. This is the costly, fan-out part (a capped
103948
- * scan of `/articles`, no server-side promoted filter), so it gets its own flag —
103949
- * `--no-promoted-articles` turns it off so the server issues zero preloading
103950
- * requests, while read-by-id (above) stays available. `!== false` (not truthiness)
103951
- * so an omitted flag on a hand-built Config stays default-on, matching the tool
103952
- * filter in `server.ts`.
104384
+ * callback's scan, which enumerates the promoted articles for `resources/list`.
104385
+ * A client may trigger it unprompted, and it fans out (a capped scan of
104386
+ * `/articles`, per brand in scope), so `--no-promoted-articles` turns it off.
104387
+ * Read-by-id and the explicit `list_promoted_articles` tool stay available.
104388
+ * `!== false` so an omitted flag on a hand-built Config stays default-on.
103953
104389
  */
103954
104390
  const promotedArticlesEnabled = (config) => config.promotedArticles !== false && helpCenterNamespaceActive(config);
103955
104391
  /**
@@ -103960,13 +104396,20 @@ const promotedArticlesEnabled = (config) => config.promotedArticles !== false &&
103960
104396
  */
103961
104397
  const buildInstructions = (config) => {
103962
104398
  if (!helpCenterContextEnabled(config)) return void 0;
104399
+ let scope;
104400
+ if (config.brandIds === void 0) scope = `the Zendesk Help Center of "${config.subdomain}"`;
104401
+ else if (config.brandIds.length === 1 && config.brandIds[0] === "all") scope = `all brands of the Zendesk Help Center of "${config.subdomain}"`;
104402
+ else if (config.brandIds.length === 1) scope = `brand ${config.brandIds[0]} of the Zendesk Help Center of "${config.subdomain}"`;
104403
+ else scope = `brands ${config.brandIds.join(", ")} of the Zendesk Help Center of "${config.subdomain}"`;
104404
+ const topologyBrandNote = config.brandIds !== void 0 && (config.brandIds.length > 1 || config.brandIds[0] === "all") ? [`The topology describes ${config.brandIds[0] === "all" ? "the account default brand" : `brand ${config.brandIds[0]}`} only; for another brand, use list_categories / list_sections with its brand_id.`] : [];
103963
104405
  return [
103964
- `This MCP server is connected to the Zendesk Help Center of "${config.subdomain}".`,
104406
+ `This MCP server is connected to ${scope}.`,
103965
104407
  "",
103966
104408
  `When creating or editing Help Center content, the resource ${topologyResourceUri(config)} is useful context:`,
103967
104409
  "it lists the active locales (and the default one), the category → section tree with IDs,",
103968
104410
  "the visibility user segments, the permission groups, and your current role.",
103969
104411
  "Prefer its IDs (section_id, permission_group_id, user_segment_id, locale) over guessing from names.",
104412
+ ...topologyBrandNote,
103970
104413
  "",
103971
104414
  "It degrades gracefully: without Guide-admin / Help Center manager rights the permission-groups and",
103972
104415
  "user-segments sections are marked unavailable (not empty). In that case reuse a permission_group_id",
@@ -104003,12 +104446,13 @@ const tolerate403 = async (promise, fallback) => {
104003
104446
  * and sections are each capped at one max-size page; `sectionsHasMore` /
104004
104447
  * `categoriesHasMore` signal a Help Center too large to enumerate inline.
104005
104448
  */
104006
- const fetchTopology = async (subdomain, token) => {
104449
+ const fetchTopology = async (subdomain, token, brandIds, brandSubdomain) => {
104007
104450
  const pageParams = { "page[size]": String(100) };
104451
+ const hcSubdomain = brandSubdomain ?? subdomain;
104008
104452
  const [locales, categoriesRes, sectionsRes, segments, perms, meRes] = await Promise.all([
104009
- helpCenterGet(subdomain, token, "/locales"),
104010
- helpCenterGet(subdomain, token, "/categories", pageParams),
104011
- helpCenterGet(subdomain, token, "/sections", pageParams),
104453
+ helpCenterGet(hcSubdomain, token, "/locales"),
104454
+ helpCenterGet(hcSubdomain, token, "/categories", pageParams),
104455
+ helpCenterGet(hcSubdomain, token, "/sections", pageParams),
104012
104456
  tolerate403(helpCenterGet(subdomain, token, "/user_segments"), { user_segments: [] }),
104013
104457
  tolerate403(zendeskGet(subdomain, token, "/guide/permission_groups"), { permission_groups: [] }),
104014
104458
  zendeskGet(subdomain, token, "/users/me")
@@ -104017,6 +104461,7 @@ const fetchTopology = async (subdomain, token) => {
104017
104461
  const sections = sectionsRes.sections ?? [];
104018
104462
  return {
104019
104463
  subdomain,
104464
+ ...brandIds === void 0 ? {} : { brandIds },
104020
104465
  locales,
104021
104466
  categories,
104022
104467
  sections,
@@ -104068,10 +104513,20 @@ const renderAdminSection = (items, denied, deniedNote) => {
104068
104513
  };
104069
104514
  /** Render the topology as a compact Markdown document for the LLM context. */
104070
104515
  const formatTopology = (data) => {
104516
+ let brandNote;
104517
+ let allowedBrandsLine = [];
104518
+ if (data.brandIds === void 0) brandNote = "";
104519
+ else if (data.brandIds.length === 1 && data.brandIds[0] === "all") brandNote = " (all brands; tree below: account default)";
104520
+ else if (data.brandIds.length === 1) brandNote = ` (brand ${data.brandIds[0]})`;
104521
+ else {
104522
+ brandNote = ` (brand ${data.brandIds[0]})`;
104523
+ allowedBrandsLine = ["", `**Allowed brands**: ${data.brandIds.join(", ")}`];
104524
+ }
104071
104525
  const text = [
104072
- `# Zendesk Help Center topology — ${data.subdomain}`,
104526
+ `# Zendesk Help Center topology — ${data.subdomain}${brandNote}`,
104073
104527
  "",
104074
104528
  `**Your access**: ${data.currentUser.name} (id ${data.currentUser.id}), role "${data.currentUser.role}".`,
104529
+ ...allowedBrandsLine,
104075
104530
  "",
104076
104531
  "## Locales",
104077
104532
  `- Default: ${data.locales.default_locale}`,
@@ -104098,14 +104553,14 @@ const formatTopology = (data) => {
104098
104553
  * token. A 401 notifies `onUnauthorized` (stdio OAuth only) to invalidate the
104099
104554
  * stale token, mirroring the tool dispatch path in `server.ts`.
104100
104555
  */
104101
- const createTopologyProvider = (getToken, subdomain, onUnauthorized) => {
104556
+ const createTopologyProvider = (getToken, subdomain, onUnauthorized, brandIds, resolveBrandSubdomain = () => Promise.resolve(void 0)) => {
104102
104557
  let cached;
104103
104558
  return { read() {
104104
104559
  const now = Date.now();
104105
104560
  if (cached && now - cached.at < 3e5) return cached.promise;
104106
104561
  const promise = (async () => {
104107
- const token = await getToken();
104108
- return formatTopology(await fetchTopology(subdomain, token));
104562
+ const [token, brandSubdomain] = await Promise.all([getToken(), resolveBrandSubdomain()]);
104563
+ return formatTopology(await fetchTopology(subdomain, token, brandIds, brandSubdomain));
104109
104564
  })().catch((err) => {
104110
104565
  cached = void 0;
104111
104566
  if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
@@ -104149,7 +104604,6 @@ const filterTools = (allTools, options) => allTools.filter((tool) => {
104149
104604
  if (options.readOnly && !tool.readOnly) return false;
104150
104605
  if (options.namespaces?.length && !options.namespaces.includes(tool.namespace)) return false;
104151
104606
  if (options.tools?.length && !options.tools.includes(tool.name)) return false;
104152
- if (options.promotedArticles === false && tool.name === "list_promoted_articles") return false;
104153
104607
  return true;
104154
104608
  });
104155
104609
  const groupByNamespace = (tools) => {
@@ -104267,6 +104721,50 @@ const isPlacedAsRequested = (effectiveAfter, movedId, target, referenceId) => {
104267
104721
  //#endregion
104268
104722
  //#region src/tools/help-center.ts
104269
104723
  const ARTICLE_ID_DESC = "Article ID — the numeric id of the Help Center article. Obtain it from list_articles or search_articles.";
104724
+ const brandIdField = (brandIds) => {
104725
+ if (!brandIds || brandIds.length === 0) return void 0;
104726
+ if (brandIds.length === 1 && brandIds[0] !== "all") return void 0;
104727
+ const description = brandIds[0] === "all" ? "Brand of the Help Center to operate on, by id or subdomain. Required: any brand from list_brands." : `Brand of the Help Center to operate on, by id or subdomain. Required: one of ${brandIds.join(", ")}.`;
104728
+ return union([number$2().int(), string$2().min(1)]).describe(description);
104729
+ };
104730
+ /**
104731
+ * Resolve which brand a call targets, enforcing the deploy allow-list.
104732
+ *
104733
+ * - unset: no brand override; a per-call brand_id is rejected (schema has no
104734
+ * field for it, so this is a backstop for hand-crafted calls).
104735
+ * - single: the configured entry is the only brand; ANY per-call brand_id is
104736
+ * rejected (hard lock, no escape — the schema has no field for it).
104737
+ * - multi: brand_id is REQUIRED; the per-call id is resolved and must match an
104738
+ * allowed entry by id or resolved subdomain.
104739
+ * - all: brand_id is REQUIRED; any existing brand passes (validated by the
104740
+ * resolver).
104741
+ *
104742
+ * Returns the brand's subdomain, or undefined for the account default.
104743
+ */
104744
+ const resolveBrand = async (perCall, brandIds, resolveSubdomain) => {
104745
+ if (!brandIds || brandIds.length === 0) {
104746
+ if (perCall !== void 0) throw new Error("brand_id is not accepted: this server was started without --brand-ids, so it targets the account default brand.");
104747
+ return;
104748
+ }
104749
+ const isAll = brandIds.length === 1 && brandIds[0] === "all";
104750
+ if (brandIds.length === 1 && !isAll) {
104751
+ const locked = brandIds[0];
104752
+ if (perCall !== void 0) throw new Error(`brand_id is not accepted: this server was started with --brand-ids ${locked}, which locks every Help Center operation to that brand. Omit brand_id.`);
104753
+ return resolveSubdomain(locked);
104754
+ }
104755
+ if (perCall === void 0) throw new Error(`brand_id is required: this server was started with --brand-ids ${brandIds.join(", ")}, so every brand-scoped Help Center call must name one.`);
104756
+ const resolved = await resolveSubdomain(String(perCall));
104757
+ if (isAll) return resolved;
104758
+ await assertAllowed(perCall, resolved, brandIds, resolveSubdomain);
104759
+ return resolved;
104760
+ };
104761
+ const assertAllowed = async (perCall, resolved, brandIds, resolveSubdomain) => {
104762
+ if (brandIds.includes(String(perCall))) return;
104763
+ const settled = await Promise.allSettled(brandIds.map((entry) => resolveSubdomain(entry)));
104764
+ const allowed = new Set(brandIds);
104765
+ for (const r of settled) if (r.status === "fulfilled") allowed.add(r.value);
104766
+ if (!allowed.has(resolved)) throw new Error(`brand_id ${perCall} is not allowed: this server was started with --brand-ids ${brandIds.join(", ")}. Allowed: ${[...allowed].join(", ")}.`);
104767
+ };
104270
104768
  const listTranslations = (subdomain, token, articleId) => helpCenterGet(subdomain, token, `/articles/${articleId}/translations`).then((res) => res.translations);
104271
104769
  const NODE_LABEL = {
104272
104770
  sections: "section",
@@ -104467,13 +104965,54 @@ const structureWarning = (r) => {
104467
104965
  return [];
104468
104966
  };
104469
104967
  const sectionUpdateNotes = (r) => [...r.stripped ? [`Note: removed a leading <${r.stripped.tag}> "${r.stripped.text}" from content; the section heading is kept automatically and is not part of content.`] : [], ...structureWarning(r)];
104968
+ /**
104969
+ * Build the `list_brands` tool when the server runs in multi/all mode; an empty
104970
+ * array otherwise. It is the discovery source for the required per-call
104971
+ * `brand_id`: in single-lock or unset mode the brand is implicit, the schemas
104972
+ * carry no `brand_id` field, and the tool stays hidden. In multi mode (not
104973
+ * 'all') the listing is filtered to the allowed brands.
104974
+ */
104975
+ const createListBrandsTool = (ctx) => {
104976
+ const { subdomain, brandIds, getToken } = ctx;
104977
+ if (brandIds === void 0) return [];
104978
+ const isAll = brandIds.length === 1 && brandIds[0] === "all";
104979
+ if (brandIds.length === 1 && !isAll) return [];
104980
+ return [{
104981
+ name: "list_brands",
104982
+ namespace: "help_center",
104983
+ readOnly: true,
104984
+ title: "List Zendesk Brands",
104985
+ description: "List the Zendesk account's brands (id, name, URL, default/active flags) from the Support API. Multi-brand Guide accounts have one Help Center per brand: find the brand id here, then pass it as brand_id (id or subdomain) — REQUIRED on every brand-scoped Help Center tool in multi/all mode.",
104986
+ inputSchema: object({}),
104987
+ annotations: {
104988
+ readOnlyHint: true,
104989
+ destructiveHint: false,
104990
+ idempotentHint: true,
104991
+ openWorldHint: true
104992
+ },
104993
+ handler: async () => {
104994
+ const token = await getToken();
104995
+ const brands = await fetchAllBrands(subdomain, token);
104996
+ const visible = isAll ? brands : brands.filter((b) => brandIds.includes(String(b.id)) || brandIds.includes(b.subdomain));
104997
+ return { content: [{
104998
+ type: "text",
104999
+ text: formatList(visible, formatBrand, void 0, "Pass a brand id as brand_id to any Help Center tool to operate on that brand.")
105000
+ }] };
105001
+ }
105002
+ }];
105003
+ };
104470
105004
  const createHelpCenterTools = (ctx) => {
104471
- const { subdomain, getToken } = ctx;
104472
- const fetchSectionOrder = async (sectionId, locale, token) => {
105005
+ const { subdomain, brandIds, resolveBrandSubdomain, getToken } = ctx;
105006
+ const brandIdSchema = brandIdField(brandIds);
105007
+ const brandIdPart = brandIdSchema ? { brand_id: brandIdSchema } : {};
105008
+ const resolveHcSubdomain = async (brand_id) => {
105009
+ return await resolveBrand(brand_id, brandIds, resolveBrandSubdomain) ?? subdomain;
105010
+ };
105011
+ const fetchSectionOrder = async (sectionId, locale, token, hcSubdomain) => {
104473
105012
  const order = [];
104474
105013
  let cursor;
104475
105014
  do {
104476
- const response = await helpCenterGet(subdomain, token, `/${locale}/sections/${sectionId}/articles`, buildCursorParams(100, cursor));
105015
+ const response = await helpCenterGet(hcSubdomain, token, `/${locale}/sections/${sectionId}/articles`, buildCursorParams(100, cursor));
104477
105016
  const articles = response.articles ?? [];
104478
105017
  for (const article of articles) order.push({
104479
105018
  id: article.id,
@@ -104484,19 +105023,19 @@ const createHelpCenterTools = (ctx) => {
104484
105023
  } while (cursor);
104485
105024
  return order;
104486
105025
  };
104487
- const assertReferenceInSection = async (effective, referenceArticleId, articleId, sectionId, token) => {
105026
+ const assertReferenceInSection = async (effective, referenceArticleId, articleId, sectionId, token, hcSubdomain) => {
104488
105027
  if (effective.some((a) => a.id === referenceArticleId)) return;
104489
105028
  let detail = "was not found";
104490
105029
  try {
104491
- const { article: ref } = await helpCenterGet(subdomain, token, `/articles/${referenceArticleId}`);
105030
+ const { article: ref } = await helpCenterGet(hcSubdomain, token, `/articles/${referenceArticleId}`);
104492
105031
  detail = `is in section #${ref.section_id}, not section #${sectionId}`;
104493
105032
  } catch {}
104494
105033
  throw new Error(`Reference article #${referenceArticleId} ${detail}. It must be in the same section (#${sectionId}) as article #${articleId}.`);
104495
105034
  };
104496
- const applyPositionWrites = async (writes, articleId, token) => {
105035
+ const applyPositionWrites = async (writes, articleId, token, hcSubdomain) => {
104497
105036
  let applied = 0;
104498
105037
  for (const write of writes) try {
104499
- await helpCenterPut(subdomain, token, `/articles/${write.id}`, { article: { position: write.position } });
105038
+ await helpCenterPut(hcSubdomain, token, `/articles/${write.id}`, { article: { position: write.position } });
104500
105039
  applied += 1;
104501
105040
  } catch (error) {
104502
105041
  const reason = error instanceof Error ? error.message : String(error);
@@ -104505,6 +105044,7 @@ const createHelpCenterTools = (ctx) => {
104505
105044
  return applied;
104506
105045
  };
104507
105046
  return [
105047
+ ...createListBrandsTool(ctx),
104508
105048
  {
104509
105049
  name: "search_articles",
104510
105050
  namespace: "help_center",
@@ -104515,7 +105055,8 @@ const createHelpCenterTools = (ctx) => {
104515
105055
  query: string$2().min(1).describe("Full-text query matched against article titles and body. Plain keywords; combine with the locale filter to scope to one language."),
104516
105056
  locale: string$2().optional().describe("Filter by locale (e.g., \"en-us\", \"fr\")"),
104517
105057
  per_page: number$2().int().min(1).max(100).default(100).describe(PER_PAGE_DESC),
104518
- page: number$2().int().min(1).default(1).describe(PAGE_DESC)
105058
+ page: number$2().int().min(1).default(1).describe(PAGE_DESC),
105059
+ ...brandIdPart
104519
105060
  }),
104520
105061
  annotations: {
104521
105062
  readOnlyHint: true,
@@ -104524,14 +105065,15 @@ const createHelpCenterTools = (ctx) => {
104524
105065
  openWorldHint: true
104525
105066
  },
104526
105067
  handler: async (params) => {
104527
- const { query, locale, per_page, page } = params;
105068
+ const { query, locale, per_page, page, brand_id } = params;
104528
105069
  const token = await getToken();
105070
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104529
105071
  const p = {
104530
105072
  query,
104531
105073
  ...buildOffsetParams(per_page, page)
104532
105074
  };
104533
105075
  if (locale) p["locale"] = locale;
104534
- const response = await helpCenterGet(subdomain, token, "/articles/search", p);
105076
+ const response = await helpCenterGet(hcSubdomain, token, "/articles/search", p);
104535
105077
  return { content: [{
104536
105078
  type: "text",
104537
105079
  text: formatList(response.results ?? [], formatArticleSummary, extractSearchPaginationMeta(response, per_page, page))
@@ -104546,7 +105088,8 @@ const createHelpCenterTools = (ctx) => {
104546
105088
  description: "Retrieve an article by ID with full body content. For large articles, prefer get_article_outline + get_article_section to save tokens. Optionally specify locale for a translated version. Returns body (HTML), metadata, source_locale, and list of available translations.",
104547
105089
  inputSchema: object({
104548
105090
  article_id: number$2().int().describe(ARTICLE_ID_DESC),
104549
- locale: string$2().optional().describe("Locale for translated version")
105091
+ locale: string$2().optional().describe("Locale for translated version"),
105092
+ ...brandIdPart
104550
105093
  }),
104551
105094
  annotations: {
104552
105095
  readOnlyHint: true,
@@ -104555,11 +105098,12 @@ const createHelpCenterTools = (ctx) => {
104555
105098
  openWorldHint: true
104556
105099
  },
104557
105100
  handler: async (params) => {
104558
- const { article_id, locale } = params;
105101
+ const { article_id, locale, brand_id } = params;
104559
105102
  const token = await getToken();
105103
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104560
105104
  const path = locale ? `/${locale}/articles/${article_id}` : `/articles/${article_id}`;
104561
- const { article } = await helpCenterGet(subdomain, token, path);
104562
- const translations = await listTranslations(subdomain, token, article_id);
105105
+ const { article } = await helpCenterGet(hcSubdomain, token, path, void 0);
105106
+ const translations = await listTranslations(hcSubdomain, token, article_id);
104563
105107
  const text = (largeArticleHint(article.body, parseSections(article.body).length) ?? "") + formatArticle(article) + `\n\n**Available translations**: ${translations.map((t) => t.locale).join(", ")}`;
104564
105108
  return { content: [{
104565
105109
  type: "text",
@@ -104576,7 +105120,8 @@ const createHelpCenterTools = (ctx) => {
104576
105120
  inputSchema: object({
104577
105121
  locale: string$2().optional().describe("Locale for category names (e.g., \"en-us\", \"fr\"). Defaults to the Help Center default locale."),
104578
105122
  page_size: number$2().int().min(1).max(100).default(100).describe("Categories per page (1-100, default 100)."),
104579
- cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page.")
105123
+ cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page."),
105124
+ ...brandIdPart
104580
105125
  }),
104581
105126
  annotations: {
104582
105127
  readOnlyHint: true,
@@ -104585,10 +105130,11 @@ const createHelpCenterTools = (ctx) => {
104585
105130
  openWorldHint: true
104586
105131
  },
104587
105132
  handler: async (params) => {
104588
- const { locale, page_size, cursor } = params;
105133
+ const { locale, page_size, cursor, brand_id } = params;
104589
105134
  const token = await getToken();
105135
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104590
105136
  const path = `${localePrefix(locale)}/categories`;
104591
- const response = await helpCenterGet(subdomain, token, path, buildCursorParams(page_size, cursor));
105137
+ const response = await helpCenterGet(hcSubdomain, token, path, buildCursorParams(page_size, cursor));
104592
105138
  const categories = response.categories ?? [];
104593
105139
  return { content: [{
104594
105140
  type: "text",
@@ -104606,7 +105152,8 @@ const createHelpCenterTools = (ctx) => {
104606
105152
  category_id: number$2().int().optional().describe("Restrict to sections of this category (id from list_categories). Omit to list every section."),
104607
105153
  locale: string$2().optional().describe("Locale for section names (e.g., \"en-us\", \"fr\"). Defaults to the Help Center default locale."),
104608
105154
  page_size: number$2().int().min(1).max(100).default(100).describe("Sections per page (1-100, default 100)."),
104609
- cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page.")
105155
+ cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page."),
105156
+ ...brandIdPart
104610
105157
  }),
104611
105158
  annotations: {
104612
105159
  readOnlyHint: true,
@@ -104615,9 +105162,10 @@ const createHelpCenterTools = (ctx) => {
104615
105162
  openWorldHint: true
104616
105163
  },
104617
105164
  handler: async (params) => {
104618
- const { category_id, locale, page_size, cursor } = params;
105165
+ const { category_id, locale, page_size, cursor, brand_id } = params;
104619
105166
  const token = await getToken();
104620
- const response = await helpCenterGet(subdomain, token, sectionListPath(category_id, locale), buildCursorParams(page_size, cursor));
105167
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105168
+ const response = await helpCenterGet(hcSubdomain, token, sectionListPath(category_id, locale), buildCursorParams(page_size, cursor));
104621
105169
  const sections = response.sections ?? [];
104622
105170
  return { content: [{
104623
105171
  type: "text",
@@ -104643,7 +105191,8 @@ const createHelpCenterTools = (ctx) => {
104643
105191
  "title"
104644
105192
  ]).default("position").describe("Field to sort by; \"position\" (the default) is the manual order set in Guide."),
104645
105193
  sort_order: _enum(["asc", "desc"]).default("asc").describe("Sort direction: ascending or descending."),
104646
- include_translations: boolean$1().default(false).describe("Include available translation locales per article (causes 1 extra API call per article)")
105194
+ include_translations: boolean$1().default(false).describe("Include available translation locales per article (causes 1 extra API call per article)"),
105195
+ ...brandIdPart
104647
105196
  }),
104648
105197
  annotations: {
104649
105198
  readOnlyHint: true,
@@ -104652,9 +105201,10 @@ const createHelpCenterTools = (ctx) => {
104652
105201
  openWorldHint: true
104653
105202
  },
104654
105203
  handler: async (params) => {
104655
- const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations } = params;
105204
+ const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations, brand_id } = params;
104656
105205
  const token = await getToken();
104657
- const response = await helpCenterGet(subdomain, token, articleListPath(section_id, locale), {
105206
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105207
+ const response = await helpCenterGet(hcSubdomain, token, articleListPath(section_id, locale), {
104658
105208
  ...buildCursorParams(page_size, cursor),
104659
105209
  sort_by,
104660
105210
  sort_order
@@ -104665,7 +105215,7 @@ const createHelpCenterTools = (ctx) => {
104665
105215
  text: formatList(articles, formatArticleSummary, extractPaginationMeta(response, articles.length))
104666
105216
  }] };
104667
105217
  const formatted = await Promise.all(articles.map(async (article) => {
104668
- const locales = (await listTranslations(subdomain, token, article.id)).map((t) => t.locale).join(", ");
105218
+ const locales = (await listTranslations(hcSubdomain, token, article.id)).map((t) => t.locale).join(", ");
104669
105219
  return `${formatArticleSummary(article)}\n- **Translations**: ${locales}`;
104670
105220
  }));
104671
105221
  const meta = extractPaginationMeta(response, articles.length);
@@ -104682,23 +105232,25 @@ const createHelpCenterTools = (ctx) => {
104682
105232
  readOnly: true,
104683
105233
  title: "List Promoted Help Center Articles",
104684
105234
  description: "List the promoted (\"featured\") Help Center articles — the small, editorially-curated set surfaced at the top of their sections. Returns metadata only (no body); use get_article for full content. COST: the Help Center API has no server-side promoted filter, so this scans article pages (one Zendesk API request per page, up to ARTICLE_RESOURCES_SCAN_MAX_PAGES, default 20) and filters client-side — potentially costly on a large Help Center. Each call performs a fresh, uncached scan, so avoid calling it repeatedly. On a very large Help Center some promoted articles may be omitted, and both the omission and the number of pages scanned are flagged in the output. Lists the default locale. To promote or unpromote an article, use update_article with `promoted` (requires Help Center admin / Guide admin rights).",
104685
- inputSchema: object({}),
105235
+ inputSchema: object({ ...brandIdPart }),
104686
105236
  annotations: {
104687
105237
  readOnlyHint: true,
104688
105238
  destructiveHint: false,
104689
105239
  idempotentHint: true,
104690
105240
  openWorldHint: true
104691
105241
  },
104692
- handler: async () => {
105242
+ handler: async (params) => {
105243
+ const { brand_id } = params;
104693
105244
  const token = await getToken();
104694
- const { articles, truncated, pagesScanned } = await fetchPromotedArticles(subdomain, token);
105245
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105246
+ const { articles, truncated, pagesScanned } = await fetchPromotedArticles(hcSubdomain, token, ARTICLE_RESOURCES_SCAN_MAX_PAGES);
104695
105247
  const header = `Promoted (featured) articles: ${articles.length}`;
104696
105248
  const body = articles.length ? articles.map(formatArticleSummary).join("\n\n") : "_No promoted articles found._";
104697
105249
  const cost = `${pagesScanned} Zendesk API request${pagesScanned === 1 ? "" : "s"}`;
104698
105250
  const note = scanCostNote(truncated, pagesScanned, cost);
104699
105251
  return { content: [{
104700
105252
  type: "text",
104701
- text: truncateIfNeeded(`${header}\n\n${body}${note}`, "list_promoted_articles takes no parameters, so this listing cannot be narrowed from the call; read a single article with get_article.")
105253
+ text: truncateIfNeeded(`${header}\n\n${body}${note}`, "list_promoted_articles takes no filter parameters, so this listing cannot be narrowed from the call; read a single article with get_article.")
104702
105254
  }] };
104703
105255
  }
104704
105256
  },
@@ -104708,7 +105260,10 @@ const createHelpCenterTools = (ctx) => {
104708
105260
  readOnly: true,
104709
105261
  title: "List Article Translations",
104710
105262
  description: "List all available translations for an article (metadata only, no body: locale, title, draft, updated_at). Use get_article with locale for full translated content.",
104711
- inputSchema: object({ article_id: number$2().int().describe(ARTICLE_ID_DESC) }),
105263
+ inputSchema: object({
105264
+ article_id: number$2().int().describe(ARTICLE_ID_DESC),
105265
+ ...brandIdPart
105266
+ }),
104712
105267
  annotations: {
104713
105268
  readOnlyHint: true,
104714
105269
  destructiveHint: false,
@@ -104716,9 +105271,10 @@ const createHelpCenterTools = (ctx) => {
104716
105271
  openWorldHint: true
104717
105272
  },
104718
105273
  handler: async (params) => {
104719
- const { article_id } = params;
105274
+ const { article_id, brand_id } = params;
104720
105275
  const token = await getToken();
104721
- const translations = await listTranslations(subdomain, token, article_id);
105276
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105277
+ const translations = await listTranslations(hcSubdomain, token, article_id);
104722
105278
  return { content: [{
104723
105279
  type: "text",
104724
105280
  text: formatList(translations, formatTranslationSummary, void 0, "list_article_translations takes only article_id, so this listing cannot be narrowed from the call; read one locale in full with get_article.")
@@ -104736,7 +105292,8 @@ const createHelpCenterTools = (ctx) => {
104736
105292
  locale: string$2().describe("Target locale (e.g., \"fr\", \"de\")"),
104737
105293
  title: string$2().min(1).describe("Translated article title."),
104738
105294
  body: string$2().min(1).describe("Translated body (HTML)"),
104739
- draft: boolean$1().default(false).describe("Create the translation as a draft (not visible to end users). Defaults to false (published).")
105295
+ draft: boolean$1().default(false).describe("Create the translation as a draft (not visible to end users). Defaults to false (published)."),
105296
+ ...brandIdPart
104740
105297
  }),
104741
105298
  annotations: {
104742
105299
  readOnlyHint: false,
@@ -104745,9 +105302,10 @@ const createHelpCenterTools = (ctx) => {
104745
105302
  openWorldHint: true
104746
105303
  },
104747
105304
  handler: async (params) => {
104748
- const { article_id, locale, title, body, draft } = params;
105305
+ const { article_id, locale, title, body, draft, brand_id } = params;
104749
105306
  const token = await getToken();
104750
- const { translation } = await helpCenterPost(subdomain, token, `/articles/${article_id}/translations`, { translation: {
105307
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105308
+ const { translation } = await helpCenterPost(hcSubdomain, token, `/articles/${article_id}/translations`, { translation: {
104751
105309
  locale,
104752
105310
  title,
104753
105311
  body,
@@ -104770,7 +105328,8 @@ const createHelpCenterTools = (ctx) => {
104770
105328
  locale: string$2().describe("Locale of the translation to update, e.g. \"en-us\" or \"fr\". Use the source_locale (from get_article) to edit the default language."),
104771
105329
  title: string$2().optional().describe("New title for this locale. Omit to leave the current title unchanged."),
104772
105330
  body: string$2().optional().describe("New full body (HTML) for this locale. Replaces the entire body — for a single-section edit prefer update_article_section. Omit to leave the body unchanged."),
104773
- draft: boolean$1().optional().describe("When true, keeps this translation as a draft; when false, publishes it.")
105331
+ draft: boolean$1().optional().describe("When true, keeps this translation as a draft; when false, publishes it."),
105332
+ ...brandIdPart
104774
105333
  }),
104775
105334
  annotations: {
104776
105335
  readOnlyHint: false,
@@ -104779,9 +105338,10 @@ const createHelpCenterTools = (ctx) => {
104779
105338
  openWorldHint: true
104780
105339
  },
104781
105340
  handler: async (params) => {
104782
- const { article_id, locale, ...updates } = params;
105341
+ const { article_id, locale, brand_id, ...updates } = params;
104783
105342
  const token = await getToken();
104784
- const { translation } = await helpCenterPut(subdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: updates });
105343
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105344
+ const { translation } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: updates });
104785
105345
  return { content: [{
104786
105346
  type: "text",
104787
105347
  text: `Translation updated for article #${article_id} in "${locale}".\n\n${formatTranslation(translation)}`
@@ -104794,7 +105354,10 @@ const createHelpCenterTools = (ctx) => {
104794
105354
  readOnly: true,
104795
105355
  title: "List Section Translations",
104796
105356
  description: "List the translations of a Help Center section: for each locale, the localized name, whether a description is set, and whether the translation is published or still a draft. Reach for this when a section looks wrong in a locale, because list_sections with that locale cannot settle it: a section with no translation is omitted from it, while a section whose translation is an unpublished draft may still be listed there under the draft name — so appearing in that listing does not mean published, and the draft flag here is what decides. Fix either case with set_section_translation; to sweep every category and section at once, use find_translation_gaps.",
104797
- inputSchema: object({ section_id: number$2().int().describe("Section ID — the numeric id of the Help Center section. Obtain it from list_sections or the zendesk-hc://topology resource.") }),
105357
+ inputSchema: object({
105358
+ section_id: number$2().int().describe("Section ID — the numeric id of the Help Center section. Obtain it from list_sections or the zendesk-hc://topology resource."),
105359
+ ...brandIdPart
105360
+ }),
104798
105361
  annotations: {
104799
105362
  readOnlyHint: true,
104800
105363
  destructiveHint: false,
@@ -104802,9 +105365,10 @@ const createHelpCenterTools = (ctx) => {
104802
105365
  openWorldHint: true
104803
105366
  },
104804
105367
  handler: async (params) => {
104805
- const { section_id } = params;
105368
+ const { section_id, brand_id } = params;
104806
105369
  const token = await getToken();
104807
- const translations = await listNodeTranslations(subdomain, token, "sections", section_id);
105370
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105371
+ const translations = await listNodeTranslations(hcSubdomain, token, "sections", section_id, void 0);
104808
105372
  return { content: [{
104809
105373
  type: "text",
104810
105374
  text: formatList(translations, formatNodeTranslationSummary, void 0, "list_section_translations takes only section_id, so this listing cannot be narrowed from the call; write one locale with set_section_translation.")
@@ -104817,7 +105381,10 @@ const createHelpCenterTools = (ctx) => {
104817
105381
  readOnly: true,
104818
105382
  title: "List Category Translations",
104819
105383
  description: "List the translations of a Help Center category: for each locale, the localized name, whether a description is set, and whether the translation is published or still a draft. Reach for this when a category looks wrong in a locale, because list_categories with that locale cannot settle it: a category with no translation is omitted from it, while a category whose translation is an unpublished draft may still be listed there under the draft name — so appearing in that listing does not mean published, and the draft flag here is what decides. Fix either case with set_category_translation; to sweep every category and section at once, use find_translation_gaps.",
104820
- inputSchema: object({ category_id: number$2().int().describe("Category ID — the numeric id of the Help Center category. Obtain it from list_categories or the zendesk-hc://topology resource.") }),
105384
+ inputSchema: object({
105385
+ category_id: number$2().int().describe("Category ID — the numeric id of the Help Center category. Obtain it from list_categories or the zendesk-hc://topology resource."),
105386
+ ...brandIdPart
105387
+ }),
104821
105388
  annotations: {
104822
105389
  readOnlyHint: true,
104823
105390
  destructiveHint: false,
@@ -104825,9 +105392,10 @@ const createHelpCenterTools = (ctx) => {
104825
105392
  openWorldHint: true
104826
105393
  },
104827
105394
  handler: async (params) => {
104828
- const { category_id } = params;
105395
+ const { category_id, brand_id } = params;
104829
105396
  const token = await getToken();
104830
- const translations = await listNodeTranslations(subdomain, token, "categories", category_id);
105397
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105398
+ const translations = await listNodeTranslations(hcSubdomain, token, "categories", category_id, void 0);
104831
105399
  return { content: [{
104832
105400
  type: "text",
104833
105401
  text: formatList(translations, formatNodeTranslationSummary, void 0, "list_category_translations takes only category_id, so this listing cannot be narrowed from the call; write one locale with set_category_translation.")
@@ -104842,7 +105410,8 @@ const createHelpCenterTools = (ctx) => {
104842
105410
  description: "Audit the Help Center tree for a target locale and report every category and section that has no translation, or one that is still an unpublished draft. Use it before or after translating articles: an article published in a second locale is unreachable while its parent section only exists in the source locale. Listing sections in that locale cannot answer this — a node with no translation is simply absent, without saying why, and a node whose translation is an unpublished draft may still be listed under its draft name — so this audit reads the draft flag on each node instead of trusting that listing. Costs two listings and covers up to 100 categories and 100 sections; past that only the first page of each level is audited, and the report says so — pass category_id to narrow it. Fix what it reports with set_section_translation / set_category_translation.",
104843
105411
  inputSchema: object({
104844
105412
  locale: string$2().describe("Locale to audit, e.g. \"fr\" or \"de\" — usually a non-default active locale of the Help Center (zendesk-hc://topology lists them). A locale that is not active is reported as a warning, since every node would then look untranslated."),
104845
- category_id: number$2().int().optional().describe("Restrict the audit to this category and the sections it contains (id from list_categories). Omit to sweep the whole tree, which costs two listings and covers up to 100 categories and 100 sections.")
105413
+ category_id: number$2().int().optional().describe("Restrict the audit to this category and the sections it contains (id from list_categories). Omit to sweep the whole tree, which costs two listings and covers up to 100 categories and 100 sections."),
105414
+ ...brandIdPart
104846
105415
  }),
104847
105416
  annotations: {
104848
105417
  readOnlyHint: true,
@@ -104851,12 +105420,13 @@ const createHelpCenterTools = (ctx) => {
104851
105420
  openWorldHint: true
104852
105421
  },
104853
105422
  handler: async (params) => {
104854
- const { locale, category_id } = params;
105423
+ const { locale, category_id, brand_id } = params;
104855
105424
  const token = await getToken();
105425
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104856
105426
  const [locales, categoryScope, sectionsRes] = await Promise.all([
104857
- helpCenterGet(subdomain, token, "/locales"),
104858
- fetchGapCategories(subdomain, token, category_id),
104859
- helpCenterGet(subdomain, token, sectionListPath(category_id, void 0), {
105427
+ helpCenterGet(hcSubdomain, token, "/locales", void 0),
105428
+ fetchGapCategories(hcSubdomain, token, category_id),
105429
+ helpCenterGet(hcSubdomain, token, sectionListPath(category_id, void 0), {
104860
105430
  ...buildCursorParams(100, void 0),
104861
105431
  include: TRANSLATIONS_SIDELOAD
104862
105432
  })
@@ -104899,7 +105469,8 @@ const createHelpCenterTools = (ctx) => {
104899
105469
  locale: string$2().describe("Locale to write, e.g. \"fr\" or \"de\". Must be an active locale of the Help Center (zendesk-hc://topology lists them); list_section_translations shows which ones the section already has."),
104900
105470
  name: string$2().min(1).optional().describe("Localized section name for this locale (sent as the API's translation `title`). Required when the locale has no translation yet; omit on an existing one to leave its name untouched, for instance when only publishing a draft."),
104901
105471
  description: string$2().optional().describe("Localized section description for this locale (sent as the API's translation `body`). Omit to leave an existing description untouched; pass an empty string to clear it."),
104902
- draft: boolean$1().optional().describe("Publication state: false publishes the translation, making the section visible to end users in this locale; true keeps (or puts) it back as a draft. Defaults to false when creating; omit on an existing translation to leave its state unchanged.")
105472
+ draft: boolean$1().optional().describe("Publication state: false publishes the translation, making the section visible to end users in this locale; true keeps (or puts) it back as a draft. Defaults to false when creating; omit on an existing translation to leave its state unchanged."),
105473
+ ...brandIdPart
104903
105474
  }),
104904
105475
  annotations: {
104905
105476
  readOnlyHint: false,
@@ -104908,9 +105479,10 @@ const createHelpCenterTools = (ctx) => {
104908
105479
  openWorldHint: true
104909
105480
  },
104910
105481
  handler: async (params) => {
104911
- const { section_id, ...input } = params;
105482
+ const { section_id, brand_id, ...input } = params;
104912
105483
  const token = await getToken();
104913
- const { translation, created } = await upsertNodeTranslation(subdomain, token, "sections", section_id, input);
105484
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105485
+ const { translation, created } = await upsertNodeTranslation(hcSubdomain, token, "sections", section_id, input);
104914
105486
  return { content: [{
104915
105487
  type: "text",
104916
105488
  text: nodeTranslationWriteText("sections", section_id, translation, created)
@@ -104928,7 +105500,8 @@ const createHelpCenterTools = (ctx) => {
104928
105500
  locale: string$2().describe("Locale to write, e.g. \"fr\" or \"de\". Must be an active locale of the Help Center (zendesk-hc://topology lists them); list_category_translations shows which ones the category already has."),
104929
105501
  name: string$2().min(1).optional().describe("Localized category name for this locale (sent as the API's translation `title`). Required when the locale has no translation yet; omit on an existing one to leave its name untouched, for instance when only publishing a draft."),
104930
105502
  description: string$2().optional().describe("Localized category description for this locale (sent as the API's translation `body`). Omit to leave an existing description untouched; pass an empty string to clear it."),
104931
- draft: boolean$1().optional().describe("Publication state: false publishes the translation, making the category visible to end users in this locale; true keeps (or puts) it back as a draft. Defaults to false when creating; omit on an existing translation to leave its state unchanged.")
105503
+ draft: boolean$1().optional().describe("Publication state: false publishes the translation, making the category visible to end users in this locale; true keeps (or puts) it back as a draft. Defaults to false when creating; omit on an existing translation to leave its state unchanged."),
105504
+ ...brandIdPart
104932
105505
  }),
104933
105506
  annotations: {
104934
105507
  readOnlyHint: false,
@@ -104937,9 +105510,10 @@ const createHelpCenterTools = (ctx) => {
104937
105510
  openWorldHint: true
104938
105511
  },
104939
105512
  handler: async (params) => {
104940
- const { category_id, ...input } = params;
105513
+ const { category_id, brand_id, ...input } = params;
104941
105514
  const token = await getToken();
104942
- const { translation, created } = await upsertNodeTranslation(subdomain, token, "categories", category_id, input);
105515
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105516
+ const { translation, created } = await upsertNodeTranslation(hcSubdomain, token, "categories", category_id, input);
104943
105517
  return { content: [{
104944
105518
  type: "text",
104945
105519
  text: nodeTranslationWriteText("categories", category_id, translation, created)
@@ -104991,7 +105565,8 @@ const createHelpCenterTools = (ctx) => {
104991
105565
  locale: string$2().optional().describe("Source locale for the article, e.g. \"en-us\" or \"fr\". Defaults to the Help Center's default locale; becomes the article's source_locale."),
104992
105566
  draft: boolean$1().default(true).describe("When true (default), the article is created unpublished; set false to publish immediately."),
104993
105567
  promoted: boolean$1().default(false).describe("When true, marks the article as promoted (featured) in its section. Defaults to false."),
104994
- label_names: array(string$2()).optional().describe("Label names for search ranking (use list_labels to see existing labels)")
105568
+ label_names: array(string$2()).optional().describe("Label names for search ranking (use list_labels to see existing labels)"),
105569
+ ...brandIdPart
104995
105570
  }),
104996
105571
  annotations: {
104997
105572
  readOnlyHint: false,
@@ -105000,9 +105575,10 @@ const createHelpCenterTools = (ctx) => {
105000
105575
  openWorldHint: true
105001
105576
  },
105002
105577
  handler: async (params) => {
105003
- const { section_id, ...articleData } = params;
105578
+ const { section_id, brand_id, ...articleData } = params;
105004
105579
  const token = await getToken();
105005
- const { article } = await helpCenterPost(subdomain, token, `/sections/${section_id}/articles`, { article: articleData });
105580
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105581
+ const { article } = await helpCenterPost(hcSubdomain, token, `/sections/${section_id}/articles`, { article: articleData });
105006
105582
  return { content: [{
105007
105583
  type: "text",
105008
105584
  text: `Article #${article.id} created.\n\n${formatArticle(article)}`
@@ -105025,7 +105601,8 @@ const createHelpCenterTools = (ctx) => {
105025
105601
  author_id: number$2().int().optional().describe("User id of the article author (from search_users)."),
105026
105602
  permission_group_id: number$2().int().optional().describe("Guide permission group controlling who can edit (id from list_permission_groups; if that is forbidden because the token is not a Guide admin, reuse the permission_group_id of an existing article from get_article)."),
105027
105603
  section_id: number$2().int().optional().describe("Move the article to this section (numeric id from list_sections)."),
105028
- position: number$2().int().min(0).optional().describe("Sort position within the section (manual ordering only; 0 = first/top). New articles default to position 0. To move an article to the END of its section, set this to one more than the highest current position: read the highest position P from list_articles with sort_by=\"position\", sort_order=\"desc\", then set position = P + 1.")
105604
+ position: number$2().int().min(0).optional().describe("Sort position within the section (manual ordering only; 0 = first/top). New articles default to position 0. To move an article to the END of its section, set this to one more than the highest current position: read the highest position P from list_articles with sort_by=\"position\", sort_order=\"desc\", then set position = P + 1."),
105605
+ ...brandIdPart
105029
105606
  }),
105030
105607
  annotations: {
105031
105608
  readOnlyHint: false,
@@ -105034,9 +105611,10 @@ const createHelpCenterTools = (ctx) => {
105034
105611
  openWorldHint: true
105035
105612
  },
105036
105613
  handler: async (params) => {
105037
- const { article_id, ...updates } = params;
105614
+ const { article_id, brand_id, ...updates } = params;
105038
105615
  const token = await getToken();
105039
- const { article } = await helpCenterPut(subdomain, token, `/articles/${article_id}`, { article: updates });
105616
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105617
+ const { article } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}`, { article: updates });
105040
105618
  return { content: [{
105041
105619
  type: "text",
105042
105620
  text: `Article #${article.id} updated.\n\n${formatArticle(article)}`
@@ -105059,7 +105637,8 @@ const createHelpCenterTools = (ctx) => {
105059
105637
  ]).describe("Where to move the article relative to its section siblings: \"top\" (becomes first), \"bottom\" (becomes last), or \"before\"/\"after\" a specific reference article. \"before\" and \"after\" require reference_article_id."),
105060
105638
  reference_article_id: number$2().int().optional().describe("The sibling article to position next to when target is \"before\" or \"after\" (numeric id from list_articles). Must belong to the same section and differ from article_id; leave it unset for \"top\" or \"bottom\"."),
105061
105639
  normalize: boolean$1().default(false).describe("When true, also renumber every article in the section to contiguous positions (0, 1, 2, …) so the stored positions stay tidy. Defaults to false, which writes the fewest positions possible and lets gaps remain. Either way the confirmation threshold still applies."),
105062
- confirm: boolean$1().default(false).describe("Safety guard for large reorders. When the move would rewrite more article positions than the configured threshold (REORDER_CONFIRM_THRESHOLD, default 20), the tool refuses and reports the count until you pass true here. Has no effect on small reorders.")
105640
+ confirm: boolean$1().default(false).describe("Safety guard for large reorders. When the move would rewrite more article positions than the configured threshold (REORDER_CONFIRM_THRESHOLD, default 20), the tool refuses and reports the count until you pass true here. Has no effect on small reorders."),
105641
+ ...brandIdPart
105063
105642
  }),
105064
105643
  annotations: {
105065
105644
  readOnlyHint: false,
@@ -105068,15 +105647,16 @@ const createHelpCenterTools = (ctx) => {
105068
105647
  openWorldHint: true
105069
105648
  },
105070
105649
  handler: async (params) => {
105071
- const { article_id, target, reference_article_id, normalize = false, confirm = false } = params;
105650
+ const { article_id, target, reference_article_id, normalize = false, confirm = false, brand_id } = params;
105072
105651
  assertReorderParamsCoherent(article_id, target, reference_article_id);
105073
105652
  const needsReference = needsReferenceArticle(target);
105074
105653
  const token = await getToken();
105075
- const { article } = await helpCenterGet(subdomain, token, `/articles/${article_id}`);
105654
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105655
+ const { article } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}`, void 0);
105076
105656
  const sectionId = article.section_id;
105077
105657
  const locale = article.source_locale;
105078
- const effective = await fetchSectionOrder(sectionId, locale, token);
105079
- if (reference_article_id !== void 0) await assertReferenceInSection(effective, reference_article_id, article_id, sectionId, token);
105658
+ const effective = await fetchSectionOrder(sectionId, locale, token, hcSubdomain);
105659
+ if (reference_article_id !== void 0) await assertReferenceInSection(effective, reference_article_id, article_id, sectionId, token, hcSubdomain);
105080
105660
  const targetLabel = needsReference ? `${target} article #${reference_article_id}` : target;
105081
105661
  const desired = arrangeDesiredOrder(effective, article_id, target, reference_article_id);
105082
105662
  const writes = computePositionWrites(desired, article_id, normalize);
@@ -105092,8 +105672,8 @@ const createHelpCenterTools = (ctx) => {
105092
105672
  type: "text",
105093
105673
  text: `Reordering article #${article_id} to ${targetLabel} would reposition ${writes.length} articles in section #${sectionId}, above the safety threshold of ${REORDER_CONFIRM_THRESHOLD}. Re-run with confirm: true to proceed.`
105094
105674
  }] };
105095
- const applied = await applyPositionWrites(writes, article_id, token);
105096
- const after = await fetchSectionOrder(sectionId, locale, token);
105675
+ const applied = await applyPositionWrites(writes, article_id, token, hcSubdomain);
105676
+ const after = await fetchSectionOrder(sectionId, locale, token, hcSubdomain);
105097
105677
  if (!isPlacedAsRequested(after, article_id, target, reference_article_id)) return { content: [{
105098
105678
  type: "text",
105099
105679
  text: autoSortNotice(sectionId, applied)
@@ -105112,7 +105692,8 @@ const createHelpCenterTools = (ctx) => {
105112
105692
  description: "Archive (soft-delete) a Help Center article: it is removed from the Help Center but can be restored from the Guide admin UI. Returns a confirmation message; the article and all its translations become invisible to end users. This is the only removal the Zendesk API offers — permanent deletion is not available via the API (do it from the Guide admin UI). To only hide an article temporarily while keeping it in the knowledge base, use update_article with draft: true (unpublish) instead. Guarded by a required confirm flag.",
105113
105693
  inputSchema: object({
105114
105694
  article_id: number$2().int().describe(ARTICLE_ID_DESC),
105115
- confirm: boolean$1().describe("Explicit safety guard: must be set to true to archive the article. Any other value refuses the operation without calling Zendesk.")
105695
+ confirm: boolean$1().describe("Explicit safety guard: must be set to true to archive the article. Any other value refuses the operation without calling Zendesk."),
105696
+ ...brandIdPart
105116
105697
  }),
105117
105698
  annotations: {
105118
105699
  readOnlyHint: false,
@@ -105121,10 +105702,11 @@ const createHelpCenterTools = (ctx) => {
105121
105702
  openWorldHint: true
105122
105703
  },
105123
105704
  handler: async (params) => {
105124
- const { article_id, confirm } = params;
105705
+ const { article_id, confirm, brand_id } = params;
105125
105706
  if (confirm !== true) throw new Error("Archiving is guarded: pass confirm: true to archive (soft-delete) this article. No changes were made.");
105126
105707
  const token = await getToken();
105127
- await helpCenterDelete(subdomain, token, `/articles/${article_id}`);
105708
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105709
+ await helpCenterDelete(hcSubdomain, token, `/articles/${article_id}`);
105128
105710
  return { content: [{
105129
105711
  type: "text",
105130
105712
  text: `Article #${article_id} archived (soft-deleted). It is removed from the Help Center; restore it from the Guide admin UI if needed.`
@@ -105196,19 +105778,21 @@ const createHelpCenterTools = (ctx) => {
105196
105778
  readOnly: true,
105197
105779
  title: "List Article Labels",
105198
105780
  description: "List all article labels. Labels improve Help Center search ranking and are not visible to end users. Attach them to an article through the label_names parameter of create_article or update_article.",
105199
- inputSchema: object({}),
105781
+ inputSchema: object({ ...brandIdPart }),
105200
105782
  annotations: {
105201
105783
  readOnlyHint: true,
105202
105784
  destructiveHint: false,
105203
105785
  idempotentHint: true,
105204
105786
  openWorldHint: true
105205
105787
  },
105206
- handler: async () => {
105788
+ handler: async (params) => {
105789
+ const { brand_id } = params;
105207
105790
  const token = await getToken();
105208
- const response = await helpCenterGet(subdomain, token, "/articles/labels");
105791
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105792
+ const response = await helpCenterGet(hcSubdomain, token, "/articles/labels", void 0);
105209
105793
  return { content: [{
105210
105794
  type: "text",
105211
- text: formatList(response.labels ?? [], formatLabel, void 0, "list_labels takes no parameters, so this listing cannot be narrowed from the call.")
105795
+ text: formatList(response.labels ?? [], formatLabel, void 0, "list_labels takes no filter parameters, so this listing cannot be narrowed from the call.")
105212
105796
  }] };
105213
105797
  }
105214
105798
  },
@@ -105229,7 +105813,7 @@ const createHelpCenterTools = (ctx) => {
105229
105813
  const token = await getToken();
105230
105814
  let response;
105231
105815
  try {
105232
- response = await helpCenterGet(subdomain, token, "/user_segments");
105816
+ response = await helpCenterGet(subdomain, token, "/user_segments", void 0);
105233
105817
  } catch (error) {
105234
105818
  if (error instanceof ZendeskApiError && error.status === 403) throw new Error("list_user_segments reads Help Center user segments (GET /help_center/user_segments), which Zendesk restricts to Guide admins / Help Center managers. The current token lacks that role (HTTP 403). To set an article's visibility without it, reuse the user_segment_id of an existing article (get_article), or omit user_segment_id when creating/updating to default to everyone.", { cause: error });
105235
105819
  throw error;
@@ -105246,7 +105830,10 @@ const createHelpCenterTools = (ctx) => {
105246
105830
  readOnly: true,
105247
105831
  title: "List Article Attachments",
105248
105832
  description: "List all attachments for an article. Returns attachment metadata only (id, file name, content type, size, URL), not the file bytes; both inline and block attachments are included. This is for Help Center articles — for attachments on support tickets use get_ticket_attachments instead. Upload new files with create_article_attachment.",
105249
- inputSchema: object({ article_id: number$2().int().describe("ID of the Help Center article whose attachments to list.") }),
105833
+ inputSchema: object({
105834
+ article_id: number$2().int().describe("ID of the Help Center article whose attachments to list."),
105835
+ ...brandIdPart
105836
+ }),
105250
105837
  annotations: {
105251
105838
  readOnlyHint: true,
105252
105839
  destructiveHint: false,
@@ -105254,9 +105841,10 @@ const createHelpCenterTools = (ctx) => {
105254
105841
  openWorldHint: true
105255
105842
  },
105256
105843
  handler: async (params) => {
105257
- const { article_id } = params;
105844
+ const { article_id, brand_id } = params;
105258
105845
  const token = await getToken();
105259
- const attachments = (await helpCenterGet(subdomain, token, `/articles/${article_id}/attachments`)).article_attachments ?? [];
105846
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105847
+ const attachments = (await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/attachments`)).article_attachments ?? [];
105260
105848
  if (attachments.length === 0) return { content: [{
105261
105849
  type: "text",
105262
105850
  text: `No attachments found on article #${article_id}.`
@@ -105275,7 +105863,8 @@ const createHelpCenterTools = (ctx) => {
105275
105863
  description: "Return a compact outline of an article (list of sections delimited by h1/h2/h3, with word counts) for the given locale (defaults to source_locale). Includes available translations with their outdated status. Use get_article_section to fetch a specific section.",
105276
105864
  inputSchema: object({
105277
105865
  article_id: number$2().int().describe(ARTICLE_ID_DESC),
105278
- locale: string$2().optional().describe("Locale of the body to outline (defaults to article source_locale)")
105866
+ locale: string$2().optional().describe("Locale of the body to outline (defaults to article source_locale)"),
105867
+ ...brandIdPart
105279
105868
  }),
105280
105869
  annotations: {
105281
105870
  readOnlyHint: true,
@@ -105284,12 +105873,13 @@ const createHelpCenterTools = (ctx) => {
105284
105873
  openWorldHint: true
105285
105874
  },
105286
105875
  handler: async (params) => {
105287
- const { article_id, locale } = params;
105876
+ const { article_id, locale, brand_id } = params;
105288
105877
  const token = await getToken();
105289
- const { article } = await helpCenterGet(subdomain, token, `/articles/${article_id}`);
105878
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105879
+ const { article } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}`, void 0);
105290
105880
  const effectiveLocale = locale ?? article.source_locale;
105291
- const { translation } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${effectiveLocale}`);
105292
- const translations = await listTranslations(subdomain, token, article_id);
105881
+ const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${effectiveLocale}`, void 0);
105882
+ const translations = await listTranslations(hcSubdomain, token, article_id);
105293
105883
  const sections = parseSections(translation.body);
105294
105884
  const outlineLines = sections.length ? sections.map((s) => `- [${s.index}] ${s.headingTag ? `${s.headingTag}: ` : ""}${s.heading} (${s.wordCount} words)`).join("\n") : "_(no sections detected)_";
105295
105885
  const translationsList = translations.map((t) => `- ${t.locale}${t.outdated ? " (outdated)" : ""}`).join("\n");
@@ -105318,7 +105908,8 @@ const createHelpCenterTools = (ctx) => {
105318
105908
  article_id: number$2().int().describe(ARTICLE_ID_DESC),
105319
105909
  locale: string$2().describe("Locale of the body (e.g., \"en-us\", \"fr\")"),
105320
105910
  section_index: number$2().int().min(0).describe("0-based index of the section (see get_article_outline)"),
105321
- format: _enum(["html", "markdown"]).default("html").describe("Output format. \"html\" (default) is round-trip safe. \"markdown\" is lossy on some HTML structures — use only for human review, not before update_article_section.")
105911
+ format: _enum(["html", "markdown"]).default("html").describe("Output format. \"html\" (default) is round-trip safe. \"markdown\" is lossy on some HTML structures — use only for human review, not before update_article_section."),
105912
+ ...brandIdPart
105322
105913
  }),
105323
105914
  annotations: {
105324
105915
  readOnlyHint: true,
@@ -105327,9 +105918,10 @@ const createHelpCenterTools = (ctx) => {
105327
105918
  openWorldHint: true
105328
105919
  },
105329
105920
  handler: async (params) => {
105330
- const { article_id, locale, section_index, format } = params;
105921
+ const { article_id, locale, section_index, format, brand_id } = params;
105331
105922
  const token = await getToken();
105332
- const { translation } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${locale}`);
105923
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105924
+ const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, void 0);
105333
105925
  const sections = parseSections(translation.body);
105334
105926
  const section = sections[section_index];
105335
105927
  if (!section) throw new Error(`Section index ${section_index} not found. Article has ${sections.length} section(s) (0-${Math.max(0, sections.length - 1)}).`);
@@ -105357,7 +105949,8 @@ const createHelpCenterTools = (ctx) => {
105357
105949
  locale: string$2().describe("Locale of the translation to update"),
105358
105950
  section_index: number$2().int().min(0).describe("0-based index of the section to replace (see get_article_outline). 0 is the heading-less intro when the body has text before its first heading, otherwise the first heading."),
105359
105951
  content: string$2().describe("New content for the section (heading excluded: send neither the heading nor the \"## [n] hN: ...\" label line that get_article_section prints). HTML by default, Markdown if format=\"markdown\"."),
105360
- format: _enum(["html", "markdown"]).default("html").describe("Input format. \"html\" (default) is the safe path. \"markdown\" is converted to HTML server-side but may introduce artifacts on complex content.")
105952
+ format: _enum(["html", "markdown"]).default("html").describe("Input format. \"html\" (default) is the safe path. \"markdown\" is converted to HTML server-side but may introduce artifacts on complex content."),
105953
+ ...brandIdPart
105361
105954
  }),
105362
105955
  annotations: {
105363
105956
  readOnlyHint: false,
@@ -105366,12 +105959,13 @@ const createHelpCenterTools = (ctx) => {
105366
105959
  openWorldHint: true
105367
105960
  },
105368
105961
  handler: async (params) => {
105369
- const { article_id, locale, section_index, content, format } = params;
105962
+ const { article_id, locale, section_index, content, format, brand_id } = params;
105370
105963
  const token = await getToken();
105371
- const { translation } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${locale}`);
105964
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105965
+ const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, void 0);
105372
105966
  const newSectionHtml = format === "markdown" ? markdownToHtml(content) : content;
105373
105967
  const { body: newBody, stripped, contentHeadings, introLost, sectionsBefore } = applySectionUpdate(translation.body, section_index, newSectionHtml);
105374
- const { translation: updated } = await helpCenterPut(subdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: { body: newBody } });
105968
+ const { translation: updated } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: { body: newBody } });
105375
105969
  const updatedSections = parseSections(updated.body);
105376
105970
  const updatedSection = updatedSections[section_index];
105377
105971
  const emptied = introLost === "empty";
@@ -105402,7 +105996,8 @@ const createHelpCenterTools = (ctx) => {
105402
105996
  inputSchema: object({
105403
105997
  article_id: number$2().int().describe(ARTICLE_ID_DESC),
105404
105998
  source_locale: string$2().describe("Reference locale to diff against, e.g. \"en-us\". Usually the article source_locale (from get_article)."),
105405
- target_locale: string$2().describe("Target locale to compare against source")
105999
+ target_locale: string$2().describe("Target locale to compare against source"),
106000
+ ...brandIdPart
105406
106001
  }),
105407
106002
  annotations: {
105408
106003
  readOnlyHint: true,
@@ -105411,12 +106006,13 @@ const createHelpCenterTools = (ctx) => {
105411
106006
  openWorldHint: true
105412
106007
  },
105413
106008
  handler: async (params) => {
105414
- const { article_id, source_locale, target_locale } = params;
106009
+ const { article_id, source_locale, target_locale, brand_id } = params;
105415
106010
  const token = await getToken();
106011
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105416
106012
  const [sourceRes, targetRes, translations] = await Promise.all([
105417
- helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${source_locale}`),
105418
- helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${target_locale}`),
105419
- listTranslations(subdomain, token, article_id)
106013
+ helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${source_locale}`, void 0),
106014
+ helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${target_locale}`, void 0),
106015
+ listTranslations(hcSubdomain, token, article_id)
105420
106016
  ]);
105421
106017
  const sourceSections = parseSections(sourceRes.translation.body);
105422
106018
  const targetSections = parseSections(targetRes.translation.body);
@@ -105455,7 +106051,8 @@ const createHelpCenterTools = (ctx) => {
105455
106051
  abort: true,
105456
106052
  error: (issue) => `File too large: ${issue.input.length} base64 characters, limit ${MAX_BASE64_INPUT_CHARS}. Downscale the file, split it, or host it elsewhere and link to it.`
105457
106053
  }).describe(`The file's raw bytes as a base64-encoded string; the server decodes them before upload. At most ${MAX_BASE64_INPUT_CHARS} characters (about ${MAX_BASE64_INPUT_MB} MB of file); the HTTP transport additionally caps request bodies at 4 MB.`),
105458
- content_type: string$2().default("application/octet-stream").describe("MIME type of the file, e.g. \"image/png\" or \"application/pdf\". Defaults to application/octet-stream when omitted.")
106054
+ content_type: string$2().default("application/octet-stream").describe("MIME type of the file, e.g. \"image/png\" or \"application/pdf\". Defaults to application/octet-stream when omitted."),
106055
+ ...brandIdPart
105459
106056
  }),
105460
106057
  annotations: {
105461
106058
  readOnlyHint: false,
@@ -105464,13 +106061,14 @@ const createHelpCenterTools = (ctx) => {
105464
106061
  openWorldHint: true
105465
106062
  },
105466
106063
  handler: async (params) => {
105467
- const { article_id, file_name, file_base64, content_type } = params;
106064
+ const { article_id, file_name, file_base64, content_type, brand_id } = params;
105468
106065
  const token = await getToken();
106066
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105469
106067
  const buffer = Buffer.from(file_base64, "base64");
105470
106068
  const blob = new Blob([buffer], { type: content_type });
105471
106069
  const formData = new FormData();
105472
106070
  formData.append("file", blob, file_name);
105473
- const { article_attachment } = await helpCenterUpload(subdomain, token, `/articles/${article_id}/attachments`, formData);
106071
+ const { article_attachment } = await helpCenterUpload(hcSubdomain, token, `/articles/${article_id}/attachments`, formData);
105474
106072
  return { content: [{
105475
106073
  type: "text",
105476
106074
  text: `Attachment created for article #${article_id}.\n\n${formatAttachment(article_attachment)}`
@@ -105496,7 +106094,7 @@ const attachmentSchema = object({
105496
106094
  error: (issue) => `Attachment too large: ${issue.input.length} base64 characters, limit ${MAX_BASE64_INPUT_CHARS}. Downscale the file, split the upload, or link to it instead of uploading.`
105497
106095
  }).base64().describe(`File content encoded as base64. At most ${MAX_BASE64_INPUT_CHARS} characters (about ${MAX_BASE64_INPUT_MB} MB of file), and the attachments of one call must stay under that total; the HTTP transport additionally caps request bodies at 4 MB.`),
105498
106096
  content_type: string$2().min(1).default("application/octet-stream").describe("MIME type, e.g. \"text/plain\", \"image/png\", \"application/pdf\".")
105499
- });
106097
+ }).strict();
105500
106098
  /**
105501
106099
  * The optional `attachments` array parameter, capped as a whole.
105502
106100
  *
@@ -105826,7 +106424,7 @@ const createRequestTools = (ctx) => {
105826
106424
  custom_fields: array(object({
105827
106425
  id: number$2().int(),
105828
106426
  value: unknown$3()
105829
- })).optional().describe("Answers to the form's own questions, as { id, value } pairs. Take both the ids and the accepted values from get_request_form; an id this form does not carry, or a value a dropdown does not offer, is refused here because Zendesk would drop it without an error. Option values are the tag strings, never numbers."),
106427
+ }).strict()).optional().describe("Answers to the form's own questions, as { id, value } pairs. Take both the ids and the accepted values from get_request_form; an id this form does not carry, or a value a dropdown does not offer, is refused here because Zendesk would drop it without an error. Option values are the tag strings, never numbers."),
105830
106428
  attachments: attachmentsParam("Files to attach to the request, such as a screenshot or a log, with their content base64-encoded.")
105831
106429
  }),
105832
106430
  annotations: {
@@ -106616,7 +107214,7 @@ const createTicketTools = (ctx) => {
106616
107214
  custom_fields: array(object({
106617
107215
  id: number$2().int(),
106618
107216
  value: unknown$3()
106619
- })).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts.")
107217
+ }).strict()).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts.")
106620
107218
  }),
106621
107219
  annotations: {
106622
107220
  readOnlyHint: false,
@@ -106673,7 +107271,7 @@ const createTicketTools = (ctx) => {
106673
107271
  custom_fields: array(object({
106674
107272
  id: number$2().int(),
106675
107273
  value: unknown$3()
106676
- })).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts."),
107274
+ }).strict()).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts."),
106677
107275
  followers: object({
106678
107276
  add: subscriberIdList("User ids to start following the ticket. Omit to only remove."),
106679
107277
  remove: subscriberIdList("User ids to stop following the ticket. Omit to only add.")
@@ -107337,32 +107935,6 @@ const readPackageInfo = () => {
107337
107935
  return cached;
107338
107936
  };
107339
107937
  //#endregion
107340
- //#region src/utils/validation.ts
107341
- /**
107342
- * Build a strict params parser for a tool's input schema, computing the strict
107343
- * schema and the valid-key list once at construction rather than per call.
107344
- *
107345
- * Zod objects default to `strip`, which silently drops unknown keys. That hid
107346
- * #100: a caller passing `per_page` to list_tickets (whose parameter is
107347
- * `page_size`) had the key dropped, so a large unpaginated page came back. The
107348
- * parser rejects unknown keys and names both the offending ones and the valid
107349
- * parameters, so a misremembered name fails loudly.
107350
- *
107351
- * Used on the proxy dispatch path (namespace/single modes); in `all` mode the
107352
- * SDK validates against the strict schema we register.
107353
- */
107354
- const createStrictParamsParser = (schema) => {
107355
- const strict = schema.strict();
107356
- const validKeys = Object.keys(schema.shape).sort().join(", ");
107357
- return (params) => {
107358
- const result = strict.safeParse(params);
107359
- if (result.success) return result.data;
107360
- const unknownKeys = result.error.issues.flatMap((issue) => issue.code === "unrecognized_keys" ? issue.keys : []);
107361
- if (unknownKeys.length > 0) throw new Error(`Unknown parameter(s): ${unknownKeys.join(", ")}. Valid parameters: ${validKeys || "(none)"}.`);
107362
- throw result.error;
107363
- };
107364
- };
107365
- //#endregion
107366
107938
  //#region src/server.ts
107367
107939
  /**
107368
107940
  * Invoke a tool handler, notifying `onUnauthorized` when Zendesk rejects the
@@ -107445,6 +108017,31 @@ const createServerShell = (config, logger = silentLogger) => {
107445
108017
  return server;
107446
108018
  };
107447
108019
  /**
108020
+ * Lazily resolve the Help Center subdomain the topology resource pins to: the
108021
+ * FIRST allowed brand when a list is set (named in the header), else undefined
108022
+ * (the account default). Uses the SHARED resolver so the topology and article
108023
+ * resources hit the same cached brand list the tools use; it only fires when a
108024
+ * brand list is set. Providers await it per read (cheap after the first), since
108025
+ * registerToolset is synchronous and cannot resolve up front.
108026
+ */
108027
+ const firstBrandSubdomainResolver = (config, resolveBrandSubdomain) => {
108028
+ const first = config.brandIds?.[0];
108029
+ if (first === void 0 || first === "all") return () => Promise.resolve(void 0);
108030
+ return () => resolveBrandSubdomain(first);
108031
+ };
108032
+ /**
108033
+ * Per-call brand guard for the article resource URIs in multi/all mode: the
108034
+ * {brand} slot of `<scheme>://brands/{brand}/articles/{id}` is resolved and
108035
+ * allow-list-checked EXACTLY like a tool call's `brand_id` (shared resolver,
108036
+ * same resolveBrand rules), returning the brand's subdomain for the Help
108037
+ * Center fetch. Unset/single modes never reach here — their URIs carry no
108038
+ * brand segment.
108039
+ */
108040
+ const resourceBrandResolver = (config, resolveBrandSubdomain) => {
108041
+ const singleLock = config.brandIds?.length === 1 && config.brandIds[0] !== "all";
108042
+ return (idOrSubdomain) => singleLock ? resolveBrandSubdomain(idOrSubdomain) : resolveBrand(idOrSubdomain, config.brandIds, resolveBrandSubdomain).then((sub) => sub ?? config.subdomain);
108043
+ };
108044
+ /**
107448
108045
  * Registers one generation of the toolset (mode/filters applied) plus the
107449
108046
  * optional topology resource onto an existing server, and returns a handle
107450
108047
  * whose `dispose()` removes exactly what this call added. When the SDK server
@@ -107454,7 +108051,8 @@ const createServerShell = (config, logger = silentLogger) => {
107454
108051
  * without dropping the transport. `tools` is passed in (not built here) so the
107455
108052
  * reload path can hand over freshly re-imported definitions.
107456
108053
  */
107457
- const registerToolset = (server, { config, getToken, onUnauthorized, logger = silentLogger }, tools) => {
108054
+ const registerToolset = (server, { config, getToken, resolveBrandSubdomain, onUnauthorized, logger = silentLogger }, tools) => {
108055
+ const sharedResolver = resolveBrandSubdomain ?? createBrandSubdomainResolver(config.subdomain, getToken);
107458
108056
  const registered = [];
107459
108057
  const dispose = () => {
107460
108058
  for (const handle of registered) handle.remove();
@@ -107462,8 +108060,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107462
108060
  const filteredTools = filterTools(tools, {
107463
108061
  readOnly: config.readOnly,
107464
108062
  namespaces: config.namespaces,
107465
- tools: config.tools,
107466
- promotedArticles: config.promotedArticles
108063
+ tools: config.tools
107467
108064
  });
107468
108065
  try {
107469
108066
  switch (config.mode) {
@@ -107471,7 +108068,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107471
108068
  for (const tool of filteredTools) registered.push(server.registerTool(tool.name, {
107472
108069
  title: tool.title,
107473
108070
  description: tool.description,
107474
- inputSchema: tool.inputSchema.strict(),
108071
+ inputSchema: toStrictStandardSchema(tool.inputSchema),
107475
108072
  annotations: tool.annotations
107476
108073
  }, async (params) => runHandler(tool, params, onUnauthorized)));
107477
108074
  break;
@@ -107492,7 +108089,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107492
108089
  }
107493
108090
  }
107494
108091
  if (helpCenterContextEnabled(config)) {
107495
- const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized);
108092
+ const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized, config.brandIds, firstBrandSubdomainResolver(config, sharedResolver));
107496
108093
  registered.push(server.registerResource("help-center-topology", topologyResourceUri(config), {
107497
108094
  title: "Zendesk Help Center topology",
107498
108095
  description: "Active locales, category → section tree, visibility segments, permission groups, and your role. Useful context when creating or editing content; admin-only sections (permission groups, user segments) are marked unavailable rather than empty when your role lacks Guide-admin rights.",
@@ -107504,7 +108101,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107504
108101
  }] })));
107505
108102
  }
107506
108103
  if (articleResourceEnabled(config)) {
107507
- const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized);
108104
+ const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized, resourceBrandResolver(config, sharedResolver), config.brandIds);
107508
108105
  const listPromotedEnabled = promotedArticlesEnabled(config);
107509
108106
  const template = new ResourceTemplate(articleResourceUriTemplate(config), { list: async () => {
107510
108107
  if (!listPromotedEnabled) return { resources: [] };
@@ -107515,10 +108112,10 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107515
108112
  listed: refs.length
107516
108113
  });
107517
108114
  return { resources: refs.map((ref) => ({
107518
- uri: articleResourceUri(config, ref.id),
107519
- name: ref.title,
108115
+ uri: articleResourceUri(config, ref.id, ref.brand),
108116
+ name: ref.brand === void 0 ? ref.title : `${ref.title} (${ref.brand})`,
107520
108117
  title: ref.title,
107521
- description: `"${ref.title}" (article ${ref.id}) — promoted Help Center article, as Markdown.`,
108118
+ description: ref.brand === void 0 ? `"${ref.title}" (article ${ref.id}) — promoted Help Center article, as Markdown.` : `"${ref.title}" (article ${ref.id}, brand ${ref.brand}) — promoted Help Center article, as Markdown.`,
107522
108119
  mimeType: "text/markdown"
107523
108120
  })) };
107524
108121
  } catch (err) {
@@ -107528,16 +108125,18 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107528
108125
  } });
107529
108126
  registered.push(server.registerResource("help-center-article", template, {
107530
108127
  title: "Zendesk Help Center article",
107531
- description: "A Help Center article rendered as Markdown, addressed by id. The list surfaces the promoted (featured) articles so one can be pinned as context; any article id can be read, subject to your Zendesk read permissions.",
108128
+ description: "A Help Center article rendered as Markdown, addressed by id (by brand and id when the server runs multi-brand). The list surfaces the promoted (featured) articles so one can be pinned as context; any article id can be read, subject to your Zendesk read permissions.",
107532
108129
  mimeType: "text/markdown"
107533
108130
  }, async (uri, variables) => {
107534
- const raw = Array.isArray(variables["id"]) ? variables["id"][0] : variables["id"];
107535
- const id = Number(raw);
108131
+ const rawId = Array.isArray(variables["id"]) ? variables["id"][0] : variables["id"];
108132
+ const id = Number(rawId);
107536
108133
  if (!Number.isSafeInteger(id) || id <= 0) throw new Error(`Invalid article id in resource URI: ${uri.toString()}`);
108134
+ const rawBrand = variables["brand"];
108135
+ const brand = Array.isArray(rawBrand) ? rawBrand[0] : rawBrand;
107537
108136
  return { contents: [{
107538
108137
  uri: uri.toString(),
107539
108138
  mimeType: "text/markdown",
107540
- text: await articles.readArticle(id)
108139
+ text: await articles.readArticle(id, brand)
107541
108140
  }] };
107542
108141
  }));
107543
108142
  }
@@ -107556,20 +108155,24 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107556
108155
  };
107557
108156
  const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized) => {
107558
108157
  const server = createServerShell(config, logger);
108158
+ const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
107559
108159
  const tools = createAllTools({
107560
108160
  subdomain: config.subdomain,
108161
+ brandIds: config.brandIds,
108162
+ resolveBrandSubdomain,
107561
108163
  getToken
107562
108164
  });
107563
108165
  registerToolset(server, {
107564
108166
  config,
107565
108167
  getToken,
108168
+ resolveBrandSubdomain,
107566
108169
  onUnauthorized,
107567
108170
  logger
107568
108171
  }, tools);
107569
108172
  return server;
107570
108173
  };
107571
108174
  //#endregion
107572
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
108175
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
107573
108176
  /**
107574
108177
  * Marks a stdout `'error'` listener that a closed transport left attached purely to
107575
108178
  * swallow late write failures (see {@link StdioServerTransport.close}). A fresh
@@ -107752,13 +108355,17 @@ const loadFreshTools = async (ctx) => {
107752
108355
  */
107753
108356
  const createReloadableServer = (config, getToken, logger = silentLogger, onUnauthorized, loadTools = loadFreshTools) => {
107754
108357
  const server = createServerShell(config, logger);
108358
+ const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
107755
108359
  const ctx = {
107756
108360
  subdomain: config.subdomain,
108361
+ brandIds: config.brandIds,
108362
+ resolveBrandSubdomain,
107757
108363
  getToken
107758
108364
  };
107759
108365
  const params = {
107760
108366
  config,
107761
108367
  getToken,
108368
+ resolveBrandSubdomain,
107762
108369
  onUnauthorized,
107763
108370
  logger
107764
108371
  };
@@ -107897,8 +108504,7 @@ const renderToolSurface = (config, tools) => {
107897
108504
  const filtered = filterTools(tools, {
107898
108505
  readOnly: config.readOnly,
107899
108506
  namespaces: config.namespaces,
107900
- tools: config.tools,
107901
- promotedArticles: config.promotedArticles
108507
+ tools: config.tools
107902
108508
  });
107903
108509
  const header = renderHeader(config);
107904
108510
  if (filtered.length === 0) return `${header}\n\nNo tools exposed. Check --namespace / --tool / --read-only.`;
@@ -107994,7 +108600,7 @@ const installShutdown = (options) => {
107994
108600
  //#endregion
107995
108601
  //#region src/index.ts
107996
108602
  const buildStdioTokenStore = async (config, logger) => {
107997
- const { createTokenStore } = await import("./token-store-SYK0DN9M.js");
108603
+ const { createTokenStore } = await import("./token-store-BLAkLLQO.js");
107998
108604
  return createTokenStore({
107999
108605
  subdomain: config.subdomain,
108000
108606
  oauthClientId: config.oauthClientId,
@@ -108013,6 +108619,8 @@ const main = async () => {
108013
108619
  if (config.printTools) {
108014
108620
  const tools = createAllTools({
108015
108621
  subdomain: config.subdomain,
108622
+ brandIds: config.brandIds,
108623
+ resolveBrandSubdomain: () => Promise.resolve(""),
108016
108624
  getToken: () => ""
108017
108625
  });
108018
108626
  console.log(renderToolSurface(config, tools));
@@ -108033,7 +108641,7 @@ const main = async () => {
108033
108641
  return;
108034
108642
  }
108035
108643
  if (config.dev) logger.warn("dev_mode_ignored_http");
108036
- const { startHttpTransport } = await import("./http-7NVoxfEA.js");
108644
+ const { startHttpTransport } = await import("./http-BYYYoQaK.js");
108037
108645
  const http = await startHttpTransport(config, logger);
108038
108646
  installShutdown({
108039
108647
  watchStdin: false,
@@ -108047,4 +108655,4 @@ main().catch((error) => {
108047
108655
  process.exit(1);
108048
108656
  });
108049
108657
  //#endregion
108050
- export { __toESM$1 as _, REQUEST_TIMEOUT_MS as a, getOAuthUrls as c, createStartupError as d, errnoCode as f, __require as g, __exportAll$1 as h, zendeskGet as i, WebStandardStreamableHTTPServerTransport as l, __commonJSMin$1 as m, readPackageInfo as n, deadlineSignal as o, readEnv as p, silentLogger as r, DEFAULT_CALLBACK_PORT as s, createMcpServer as t, STARTUP_DOCS as u };
108658
+ export { __commonJSMin$1 as _, verifyBearerToken as a, __toESM$1 as b, zendeskGet as c, DEFAULT_CALLBACK_PORT as d, getOAuthUrls as f, readEnv as g, errnoCode as h, WebStandardStreamableHTTPServerTransport as i, REQUEST_TIMEOUT_MS as l, createStartupError as m, readPackageInfo as n, OAuthError as o, STARTUP_DOCS as p, silentLogger as r, OAuthErrorCode as s, createMcpServer as t, deadlineSignal as u, __exportAll$1 as v, __require as y };