@fruggr/zendesk-mcp-server 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7583,6 +7583,14 @@ const Transport = _enum(["stdio", "http"]);
7583
7583
  const ConfigSchema = object({
7584
7584
  subdomain: string$2().min(1, "ZENDESK_SUBDOMAIN is required"),
7585
7585
  oauthClientId: string$2().min(1),
7586
+ /**
7587
+ * Help Center brand allow-list (`--brand-ids`): brand ids or subdomains, or
7588
+ * 'all'. Unset: account default brand, surface unchanged. One entry: hard
7589
+ * lock, no `brand_id` field. Several or 'all': `list_brands` exposed and
7590
+ * `brand_id` required on brand-scoped tools. Entries resolve lazily, on first
7591
+ * use. Modes and rationale: docs/configuration.md.
7592
+ */
7593
+ brandIds: array(string$2().min(1)).min(1).optional(),
7586
7594
  logLevel: LogLevel,
7587
7595
  mode: ToolMode,
7588
7596
  readOnly: boolean$1(),
@@ -7610,14 +7618,12 @@ const ConfigSchema = object({
7610
7618
  topology: boolean$1().default(true),
7611
7619
  /**
7612
7620
  * 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.
7621
+ * article resource's `list` callback, which scans `/articles` to enumerate the
7622
+ * promoted set for `resources/list`. On by default; `--no-promoted-articles`
7623
+ * turns it off (e.g. on a very large or multi-brand Help Center) so the server
7624
+ * issues zero preloading requests. Reading a known article by id and the
7625
+ * on-demand `list_promoted_articles` tool stay available. Only ever active
7626
+ * when the `help_center` namespace itself is active.
7621
7627
  */
7622
7628
  promotedArticles: boolean$1().default(true),
7623
7629
  /**
@@ -7696,8 +7702,25 @@ const portEnv = (name) => {
7696
7702
  const env = readNonEmptyEnv(name);
7697
7703
  return env.value === void 0 ? void 0 : parsePort(env.value, env.name);
7698
7704
  };
7705
+ /**
7706
+ * Split a raw `--brand-ids` / `ZENDESK_BRAND_IDS` value into its entries.
7707
+ * Comma-separated, entries trimmed; an empty entry (from `a,,b`, a leading/
7708
+ * trailing comma, or a whitespace-only value) is a misconfiguration, not an
7709
+ * ignored blank: it fails at startup naming the knob. Format validation only —
7710
+ * whether an id/subdomain exists is decided lazily, on first use (no token is
7711
+ * available at startup on either transport).
7712
+ */
7713
+ const parseBrandIds = (raw) => {
7714
+ if (raw === void 0) return void 0;
7715
+ const entries = raw.split(",").map((entry) => entry.trim());
7716
+ 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);
7717
+ const normalized = entries.map((entry) => entry.toLowerCase());
7718
+ 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);
7719
+ return [...new Set(normalized)];
7720
+ };
7699
7721
  const CLI_OPTIONS = {
7700
7722
  mode: { type: "string" },
7723
+ "brand-ids": { type: "string" },
7701
7724
  namespace: {
7702
7725
  type: "string",
7703
7726
  multiple: true
@@ -7785,6 +7808,7 @@ const parseCliArgs = (args) => {
7785
7808
  }
7786
7809
  if (values.port !== void 0) result.port = parsePort(values.port, "--port");
7787
7810
  if (values["callback-port"] !== void 0) result.callbackPort = parsePort(values["callback-port"], "--callback-port");
7811
+ if (values["brand-ids"] !== void 0) result.brandIds = values["brand-ids"];
7788
7812
  return result;
7789
7813
  };
7790
7814
  const splitList = (raw) => (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -7812,10 +7836,12 @@ const loadConfig = (argv = process.argv.slice(2)) => {
7812
7836
  const mode = cli.tools?.length ? "all" : cli.mode ?? "namespace";
7813
7837
  const namespaces = cli.namespaces ?? (cli.tools?.length ? [...Namespace.options] : void 0);
7814
7838
  const callbackPort = cli.callbackPort ?? portEnv("OAUTH_CALLBACK_PORT");
7839
+ const brandIds = cli.brandIds ?? requireNonEmptyEnv("ZENDESK_BRAND_IDS");
7815
7840
  const hcResourceScheme = cli.hcResourceScheme ?? requireNonEmptyEnv("HC_RESOURCE_SCHEME");
7816
7841
  return validated({
7817
7842
  subdomain,
7818
7843
  oauthClientId,
7844
+ brandIds: parseBrandIds(brandIds),
7819
7845
  logLevel: cli.logLevel ?? requireNonEmptyEnv("LOG_LEVEL") ?? "info",
7820
7846
  mode,
7821
7847
  readOnly: cli.readOnly ?? false,
@@ -7832,6 +7858,388 @@ const loadConfig = (argv = process.argv.slice(2)) => {
7832
7858
  });
7833
7859
  };
7834
7860
  //#endregion
7861
+ //#region src/constants.ts
7862
+ const positiveIntEnv = (name, fallback) => {
7863
+ const raw = readEnv(name).value;
7864
+ if (raw === void 0 || raw.trim() === "") return fallback;
7865
+ const parsed = Number(raw);
7866
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
7867
+ };
7868
+ const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
7869
+ const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
7870
+ const DEFAULT_CALLBACK_PORT = 27439;
7871
+ const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
7872
+ const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
7873
+ const STDIO_MAX_MESSAGE_BYTES = 10485760;
7874
+ const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
7875
+ const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
7876
+ const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
7877
+ const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
7878
+ const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
7879
+ const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
7880
+ const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
7881
+ const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
7882
+ const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
7883
+ const getOAuthUrls = (subdomain) => ({
7884
+ authorizeUrl: `https://${subdomain}.zendesk.com/oauth/authorizations/new`,
7885
+ tokenUrl: `https://${subdomain}.zendesk.com/oauth/tokens`
7886
+ });
7887
+ //#endregion
7888
+ //#region src/client/retry.ts
7889
+ const MAX_ATTEMPTS = 3;
7890
+ const BASE_DELAY_MS = 250;
7891
+ const MAX_DELAY_MS = 4e3;
7892
+ const REQUEST_TIMEOUT_MS = 3e4;
7893
+ const TRANSFER_TIMEOUT_MS = 12e4;
7894
+ /**
7895
+ * A deadline for one attempt. The abort surfaces as a `TimeoutError` whose `code`
7896
+ * is numeric, not a syscall string, so it classifies as `unknown`: a GET is
7897
+ * retried, a write is not — the request may have arrived before the deadline.
7898
+ */
7899
+ const deadlineSignal = (timeoutMs) => AbortSignal.timeout(timeoutMs);
7900
+ const MAX_RETRY_AFTER_MS = 5e3;
7901
+ const POLICIES = {
7902
+ GET: {
7903
+ network: "any",
7904
+ serverErrors: true
7905
+ },
7906
+ DELETE: {
7907
+ network: "pre-send",
7908
+ serverErrors: false
7909
+ },
7910
+ POST: {
7911
+ network: "pre-send",
7912
+ serverErrors: false
7913
+ },
7914
+ PUT: {
7915
+ network: "pre-send",
7916
+ serverErrors: false
7917
+ }
7918
+ };
7919
+ const DELAY_SECONDS = /^\d+$/;
7920
+ const HTTP_DATE_START = /^[A-Za-z]/;
7921
+ const NON_ASCII = /[^ -~]/g;
7922
+ const TOKEN_SEGMENT = /\/token\/[^/]+/;
7923
+ const URL_IN_TEXT = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
7924
+ const PRE_SEND_CODES = /* @__PURE__ */ new Set([
7925
+ "ENOTFOUND",
7926
+ "EAI_AGAIN",
7927
+ "ECONNREFUSED",
7928
+ "UND_ERR_CONNECT_TIMEOUT"
7929
+ ]);
7930
+ const defaultRetryDeps = {
7931
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
7932
+ random: Math.random
7933
+ };
7934
+ /** Non-ASCII bytes break `node:http` headers, so client error text stays ASCII. */
7935
+ const toAscii = (text) => text.replace(NON_ASCII, "?");
7936
+ /**
7937
+ * Identifies the request without leaking a credential: uploads carry an upload
7938
+ * token in the query string, and an attachment `content_url` carries a download
7939
+ * token as a path segment (`/attachments/token/<token>/`).
7940
+ */
7941
+ const describeTarget = (url) => {
7942
+ const { origin, pathname } = new URL(url);
7943
+ return `${origin}${pathname.replace(TOKEN_SEGMENT, "/token/[redacted]")}`;
7944
+ };
7945
+ /** First `code` in the cause chain, inspecting 5 levels so a cycle cannot hang. */
7946
+ const errorCode = (err) => {
7947
+ let current = err;
7948
+ for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth += 1) {
7949
+ const { code, cause } = current;
7950
+ if (typeof code === "string") return code;
7951
+ current = cause;
7952
+ }
7953
+ };
7954
+ const classifyNetworkError = (err) => PRE_SEND_CODES.has(errorCode(err)) ? "pre-send" : "unknown";
7955
+ const UNINFORMATIVE_NAMES = /* @__PURE__ */ new Set(["Error", "TypeError"]);
7956
+ /**
7957
+ * The client refuses to replay a write that may have landed — but the caller is
7958
+ * an LLM whose reflex on a failed tool call is to try again, which would undo
7959
+ * that. So a failed write says whether it may have taken effect. ASCII only,
7960
+ * same rule as the rest of the message.
7961
+ */
7962
+ const MAY_HAVE_APPLIED_NOTE = "The write may already have been applied. Check the current state before retrying, or you may duplicate it.";
7963
+ const NEVER_SENT_NOTE = "The request never reached Zendesk, so nothing was applied. Retrying is safe.";
7964
+ const REFUSED_NOTE = "Zendesk refused the request, so nothing was applied. Retrying is safe.";
7965
+ /** Only a write can be duplicated by a replay, so only a write carries a note. */
7966
+ const writeNote = (method, note) => method === "GET" ? "" : ` ${note}`;
7967
+ /**
7968
+ * A cause message can quote the URL it failed on — Node's `fetch` refuses a URL
7969
+ * carrying credentials by printing the whole thing, query string included — which
7970
+ * would smuggle back exactly what `describeTarget` drops. Same treatment for both,
7971
+ * so one function owns what a URL may look like in our output.
7972
+ */
7973
+ const redactUrls = (text) => text.replace(URL_IN_TEXT, (found) => {
7974
+ try {
7975
+ return new URL(found).origin === "null" ? "[url]" : describeTarget(found);
7976
+ } catch {
7977
+ return "[url]";
7978
+ }
7979
+ });
7980
+ const failureDetail = (cause) => {
7981
+ if (!(cause instanceof Error)) return redactUrls(String(cause));
7982
+ const message = redactUrls(cause.message);
7983
+ const code = errorCode(cause);
7984
+ if (code !== void 0) return `${code}: ${message}`;
7985
+ return UNINFORMATIVE_NAMES.has(cause.name) ? message : `${cause.name}: ${message}`;
7986
+ };
7987
+ const networkErrorMessage = (method, target, attempts, cause) => {
7988
+ const base = `Network error on ${method} ${target} after ${attempts === 1 ? "1 attempt" : `${attempts} attempts`}: ${failureDetail(cause)}`;
7989
+ const note = classifyNetworkError(cause) === "pre-send" ? NEVER_SENT_NOTE : MAY_HAVE_APPLIED_NOTE;
7990
+ return toAscii(method === "GET" ? base : `${base}.${writeNote(method, note)}`);
7991
+ };
7992
+ const createZendeskNetworkError = (method, target, attempts, cause) => Object.assign(new Error(networkErrorMessage(method, target, attempts, cause), { cause }), {
7993
+ name: "ZendeskNetworkError",
7994
+ method,
7995
+ target,
7996
+ attempts
7997
+ });
7998
+ /** Exponential backoff with equal jitter: half the window fixed, half random. */
7999
+ const computeBackoffMs = (attempt, random) => {
8000
+ const window = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
8001
+ return Math.round(window / 2 + window * random() / 2);
8002
+ };
8003
+ /** `Retry-After` in ms — delay-seconds or HTTP-date. */
8004
+ const parseRetryAfter = (header, now = Date.now()) => {
8005
+ if (header === null) return void 0;
8006
+ const value = header.trim();
8007
+ if (DELAY_SECONDS.test(value)) return Number(value) * 1e3;
8008
+ if (!HTTP_DATE_START.test(value)) return void 0;
8009
+ const date = Date.parse(value);
8010
+ if (Number.isNaN(date)) return void 0;
8011
+ return Math.max(0, date - now);
8012
+ };
8013
+ /**
8014
+ * How long to wait before replaying this response, or undefined to accept it.
8015
+ * `Retry-After` wins over backoff wherever it appears — Zendesk sends it on a 503
8016
+ * during maintenance as well as on a 429 — and a value past the cap means the
8017
+ * response is surfaced rather than parking the call for that long.
8018
+ */
8019
+ const retryDelayFor = (response, policy, attempt, random) => {
8020
+ if (!(response.status === 429 || response.status >= 500 && policy.serverErrors)) return void 0;
8021
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
8022
+ if (retryAfter === void 0) return computeBackoffMs(attempt, random);
8023
+ return retryAfter > MAX_RETRY_AFTER_MS ? void 0 : retryAfter;
8024
+ };
8025
+ /**
8026
+ * Runs `attempt` until it succeeds, hits a terminal outcome, or spends the
8027
+ * attempt budget. Returns the last response for the caller to inspect (a
8028
+ * non-ok status is still the caller's to turn into a `ZendeskApiError`), and
8029
+ * throws `ZendeskNetworkError` when no response was ever obtained.
8030
+ */
8031
+ const fetchWithRetry = async (attempt, method, target, deps = defaultRetryDeps) => {
8032
+ const policy = POLICIES[method];
8033
+ for (let tries = 1;; tries += 1) {
8034
+ const last = tries >= MAX_ATTEMPTS;
8035
+ let response;
8036
+ try {
8037
+ response = await attempt();
8038
+ } catch (err) {
8039
+ const replayable = policy.network === "any" || classifyNetworkError(err) === policy.network;
8040
+ if (last || !replayable) throw createZendeskNetworkError(method, target, tries, err);
8041
+ await deps.sleep(computeBackoffMs(tries, deps.random));
8042
+ continue;
8043
+ }
8044
+ if (last) return response;
8045
+ const delayMs = retryDelayFor(response, policy, tries, deps.random);
8046
+ if (delayMs === void 0) return response;
8047
+ await response.text().catch(() => void 0);
8048
+ await deps.sleep(delayMs);
8049
+ }
8050
+ };
8051
+ //#endregion
8052
+ //#region src/client/zendesk-api.ts
8053
+ var ZendeskApiError = class ZendeskApiError extends Error {
8054
+ status;
8055
+ statusText;
8056
+ body;
8057
+ method;
8058
+ constructor(status, statusText, body, method) {
8059
+ super(ZendeskApiError.buildMessage(status, statusText, body, method));
8060
+ this.status = status;
8061
+ this.statusText = statusText;
8062
+ this.body = body;
8063
+ this.method = method;
8064
+ this.name = "ZendeskApiError";
8065
+ }
8066
+ static buildMessage(status, statusText, body, method) {
8067
+ switch (status) {
8068
+ case 401: return "Authentication failed. Your Zendesk token may be expired or invalid. Re-authenticate to get a new token.";
8069
+ case 403: return "Permission denied. Your Zendesk account does not have access to this resource.";
8070
+ case 404: return `Resource not found. Please verify the ID is correct. (${statusText})`;
8071
+ case 422: return `Validation error: ${body}`;
8072
+ case 429: return `Rate limit exceeded. Please wait before making more requests.${writeNote(method, REFUSED_NOTE)}`;
8073
+ default: {
8074
+ const message = `Zendesk API error ${status}: ${statusText}. ${body}`;
8075
+ return status >= 500 ? `${message}${writeNote(method, MAY_HAVE_APPLIED_NOTE)}` : message;
8076
+ }
8077
+ }
8078
+ }
8079
+ };
8080
+ const buildAuthHeader = (token) => `Bearer ${token}`;
8081
+ const buildUrl = (base, path, params) => {
8082
+ const url = new URL(`${base}${path}`);
8083
+ if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
8084
+ return url.toString();
8085
+ };
8086
+ const performFetch = (method, url, init, timeoutMs = REQUEST_TIMEOUT_MS) => fetchWithRetry(() => fetch(url, {
8087
+ ...init,
8088
+ method,
8089
+ signal: deadlineSignal(timeoutMs)
8090
+ }), method, describeTarget(url));
8091
+ const executeRequest = async (url, token, options = {}) => {
8092
+ const { method = "GET", body } = options;
8093
+ const headers = {
8094
+ Authorization: buildAuthHeader(token),
8095
+ Accept: "application/json"
8096
+ };
8097
+ if (body) headers["Content-Type"] = "application/json";
8098
+ const init = { headers };
8099
+ if (body) init.body = JSON.stringify(body);
8100
+ const response = await performFetch(method, url, init);
8101
+ if (!response.ok) {
8102
+ const responseBody = await response.text();
8103
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, method);
8104
+ }
8105
+ if (response.status === 204) return {};
8106
+ return response.json();
8107
+ };
8108
+ const zendeskGet = (subdomain, token, path, params) => {
8109
+ const url = buildUrl(getBaseUrl(subdomain), path, params);
8110
+ return executeRequest(url, token);
8111
+ };
8112
+ const zendeskPost = (subdomain, token, path, body) => {
8113
+ const url = buildUrl(getBaseUrl(subdomain), path);
8114
+ return executeRequest(url, token, {
8115
+ method: "POST",
8116
+ body
8117
+ });
8118
+ };
8119
+ const zendeskPut = (subdomain, token, path, body) => {
8120
+ const url = buildUrl(getBaseUrl(subdomain), path);
8121
+ return executeRequest(url, token, {
8122
+ method: "PUT",
8123
+ body
8124
+ });
8125
+ };
8126
+ const helpCenterGet = (subdomain, token, path, params) => {
8127
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
8128
+ return executeRequest(url, token);
8129
+ };
8130
+ const helpCenterPost = (subdomain, token, path, body) => {
8131
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8132
+ return executeRequest(url, token, {
8133
+ method: "POST",
8134
+ body
8135
+ });
8136
+ };
8137
+ const helpCenterPut = (subdomain, token, path, body) => {
8138
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8139
+ return executeRequest(url, token, {
8140
+ method: "PUT",
8141
+ body
8142
+ });
8143
+ };
8144
+ const helpCenterDelete = (subdomain, token, path) => {
8145
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8146
+ return executeRequest(url, token, { method: "DELETE" });
8147
+ };
8148
+ const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
8149
+ const expectedHost = `${subdomain}.zendesk.com`;
8150
+ const headers = {};
8151
+ if (new URL(contentUrl).hostname === expectedHost) headers["Authorization"] = buildAuthHeader(token);
8152
+ const response = await performFetch("GET", contentUrl, { headers }, TRANSFER_TIMEOUT_MS);
8153
+ if (!response.ok) {
8154
+ const body = await response.text();
8155
+ throw new ZendeskApiError(response.status, response.statusText, body, "GET");
8156
+ }
8157
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
8158
+ const arrayBuffer = await response.arrayBuffer();
8159
+ return {
8160
+ data: Buffer.from(arrayBuffer),
8161
+ contentType
8162
+ };
8163
+ };
8164
+ const zendeskUpload = async (subdomain, token, filename, data, contentType, uploadToken) => {
8165
+ const params = { filename };
8166
+ if (uploadToken) params["token"] = uploadToken;
8167
+ const url = buildUrl(getBaseUrl(subdomain), "/uploads", params);
8168
+ const response = await performFetch("POST", url, {
8169
+ headers: {
8170
+ Authorization: buildAuthHeader(token),
8171
+ "Content-Type": contentType
8172
+ },
8173
+ body: data
8174
+ }, TRANSFER_TIMEOUT_MS);
8175
+ if (!response.ok) {
8176
+ const responseBody = await response.text();
8177
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
8178
+ }
8179
+ return response.json();
8180
+ };
8181
+ const helpCenterUpload = async (subdomain, token, path, formData) => {
8182
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8183
+ const response = await performFetch("POST", url, {
8184
+ headers: { Authorization: buildAuthHeader(token) },
8185
+ body: formData
8186
+ }, TRANSFER_TIMEOUT_MS);
8187
+ if (!response.ok) {
8188
+ const responseBody = await response.text();
8189
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
8190
+ }
8191
+ return response.json();
8192
+ };
8193
+ //#endregion
8194
+ //#region src/client/brands.ts
8195
+ const BRAND_SUBDOMAIN = /^[a-z0-9][a-z0-9-]*$/;
8196
+ /**
8197
+ * Fetch every brand of the account (cursor-paginated). `page[size]` is sent on
8198
+ * the FIRST request too: without it Zendesk falls back to OFFSET pagination,
8199
+ * which answers without `meta.after_cursor`, and the loop below would stop
8200
+ * after page 1 — a brand past the first 100 would resolve as Unknown.
8201
+ */
8202
+ const fetchAllBrands = async (subdomain, token) => {
8203
+ const brands = [];
8204
+ let cursor;
8205
+ do {
8206
+ const response = await zendeskGet(subdomain, token, "/brands", {
8207
+ "page[size]": String(100),
8208
+ ...cursor ? { "page[after]": cursor } : {}
8209
+ });
8210
+ brands.push(...response.brands ?? []);
8211
+ const meta = response.meta;
8212
+ if (meta?.has_more) {
8213
+ 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.");
8214
+ cursor = meta.after_cursor;
8215
+ } else cursor = void 0;
8216
+ } while (cursor);
8217
+ return brands;
8218
+ };
8219
+ const createBrandSubdomainResolver = (subdomain, getToken) => {
8220
+ let cached;
8221
+ const brands = () => {
8222
+ if (!cached || Date.now() - cached.at >= 3e5) {
8223
+ const promise = (async () => fetchAllBrands(subdomain, await getToken()))();
8224
+ cached = {
8225
+ at: Date.now(),
8226
+ promise
8227
+ };
8228
+ promise.catch(() => {
8229
+ cached = void 0;
8230
+ });
8231
+ }
8232
+ return cached.promise;
8233
+ };
8234
+ return async (idOrSubdomain) => {
8235
+ const all = await brands();
8236
+ const brand = all.find((b) => String(b.id) === idOrSubdomain) ?? all.find((b) => b.subdomain.toLowerCase() === idOrSubdomain.toLowerCase());
8237
+ 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.`);
8238
+ if (!BRAND_SUBDOMAIN.test(brand.subdomain)) throw new Error(`Brand ${brand.id} has an unexpected subdomain; refusing to send a request to it.`);
8239
+ return brand.subdomain;
8240
+ };
8241
+ };
8242
+ //#endregion
7835
8243
  //#region node_modules/.pnpm/@modelcontextprotocol+server@2.2.0/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
7836
8244
  var __create = Object.create;
7837
8245
  var __defProp = Object.defineProperty;
@@ -25342,339 +25750,6 @@ var WebStandardStreamableHTTPServerTransport = class {
25342
25750
  }
25343
25751
  };
25344
25752
  //#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
25753
  //#region node_modules/.pnpm/cheerio@1.2.0/node_modules/cheerio/dist/esm/options.js
25679
25754
  const defaultOpts$2 = { _useHtmlParser2: false };
25680
25755
  /**
@@ -84056,7 +84131,7 @@ function toAlignment(value) {
84056
84131
  return code === 67 || code === 99 ? 99 : code === 76 || code === 108 ? 108 : code === 82 || code === 114 ? 114 : 0;
84057
84132
  }
84058
84133
  //#endregion
84059
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/configure.js
84134
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/configure.js
84060
84135
  /**
84061
84136
  * @import {Options, State} from './types.js'
84062
84137
  */
@@ -84123,7 +84198,7 @@ function map$2(left, right) {
84123
84198
  if (right) Object.assign(left, right);
84124
84199
  }
84125
84200
  //#endregion
84126
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js
84201
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/blockquote.js
84127
84202
  /**
84128
84203
  * @import {Blockquote, Parents} from 'mdast'
84129
84204
  * @import {Info, Map, State} from 'mdast-util-to-markdown'
@@ -84160,7 +84235,7 @@ function map$1(line, _, blank) {
84160
84235
  return ">" + (blank ? "" : " ") + line;
84161
84236
  }
84162
84237
  //#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
84238
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/pattern-in-scope.js
84164
84239
  /**
84165
84240
  * @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
84166
84241
  */
@@ -84197,7 +84272,7 @@ function listInScope(stack, list, none) {
84197
84272
  return false;
84198
84273
  }
84199
84274
  //#endregion
84200
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/break.js
84275
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/break.js
84201
84276
  /**
84202
84277
  * @import {Break, Parents} from 'mdast'
84203
84278
  * @import {Info, State} from 'mdast-util-to-markdown'
@@ -84253,7 +84328,7 @@ function longestStreak(value, substring) {
84253
84328
  return max;
84254
84329
  }
84255
84330
  //#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
84331
+ //#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
84332
  /**
84258
84333
  * @import {State} from 'mdast-util-to-markdown'
84259
84334
  * @import {Code} from 'mdast'
@@ -84272,7 +84347,7 @@ function formatCodeAsIndented(node, state) {
84272
84347
  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
84348
  }
84274
84349
  //#endregion
84275
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-fence.js
84350
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-fence.js
84276
84351
  /**
84277
84352
  * @import {Options, State} from 'mdast-util-to-markdown'
84278
84353
  */
@@ -84290,7 +84365,7 @@ function checkFence(state) {
84290
84365
  return marker;
84291
84366
  }
84292
84367
  //#endregion
84293
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/code.js
84368
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/code.js
84294
84369
  /**
84295
84370
  * @import {Info, Map, State} from 'mdast-util-to-markdown'
84296
84371
  * @import {Code, Parents} from 'mdast'
@@ -84359,7 +84434,7 @@ function map(line, _, blank) {
84359
84434
  return (blank ? "" : " ") + line;
84360
84435
  }
84361
84436
  //#endregion
84362
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-quote.js
84437
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-quote.js
84363
84438
  /**
84364
84439
  * @import {Options, State} from 'mdast-util-to-markdown'
84365
84440
  */
@@ -84377,7 +84452,7 @@ function checkQuote(state) {
84377
84452
  return marker;
84378
84453
  }
84379
84454
  //#endregion
84380
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/definition.js
84455
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/definition.js
84381
84456
  /**
84382
84457
  * @import {Info, State} from 'mdast-util-to-markdown'
84383
84458
  * @import {Definition, Parents} from 'mdast'
@@ -84443,7 +84518,7 @@ function definition$1(node, _, state, info) {
84443
84518
  return value;
84444
84519
  }
84445
84520
  //#endregion
84446
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js
84521
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-emphasis.js
84447
84522
  /**
84448
84523
  * @import {Options, State} from 'mdast-util-to-markdown'
84449
84524
  */
@@ -84461,7 +84536,7 @@ function checkEmphasis(state) {
84461
84536
  return marker;
84462
84537
  }
84463
84538
  //#endregion
84464
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js
84539
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/emphasis.js
84465
84540
  /**
84466
84541
  * @import {AttentionInfo, Info, State} from 'mdast-util-to-markdown'
84467
84542
  * @import {Emphasis, Parents} from 'mdast'
@@ -84526,7 +84601,7 @@ function peek$1(_, _1, state) {
84526
84601
  return state.options.emphasis || "*";
84527
84602
  }
84528
84603
  //#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
84604
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/encode-character-reference.js
84530
84605
  /**
84531
84606
  * Encode a code point as a character reference.
84532
84607
  *
@@ -84539,7 +84614,7 @@ function encodeCharacterReference(code) {
84539
84614
  return "&#x" + code.toString(16).toUpperCase() + ";";
84540
84615
  }
84541
84616
  //#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
84617
+ //#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
84618
  /**
84544
84619
  * @import {State} from 'mdast-util-to-markdown'
84545
84620
  * @import {Heading} from 'mdast'
@@ -84565,7 +84640,7 @@ function formatHeadingAsSetext(node, state) {
84565
84640
  return Boolean((!node.depth || node.depth < 3) && toString(node) && (state.options.setext || literalWithBreak));
84566
84641
  }
84567
84642
  //#endregion
84568
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/heading.js
84643
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/heading.js
84569
84644
  /**
84570
84645
  * @import {Info, State} from 'mdast-util-to-markdown'
84571
84646
  * @import {Heading, Parents} from 'mdast'
@@ -84617,7 +84692,7 @@ function heading$1(node, _, state, info) {
84617
84692
  return value;
84618
84693
  }
84619
84694
  //#endregion
84620
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/html.js
84695
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/html.js
84621
84696
  /**
84622
84697
  * @import {Html} from 'mdast'
84623
84698
  */
@@ -84643,7 +84718,7 @@ function htmlPeek() {
84643
84718
  return "<";
84644
84719
  }
84645
84720
  //#endregion
84646
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/image.js
84721
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/image.js
84647
84722
  /**
84648
84723
  * @import {Info, State} from 'mdast-util-to-markdown'
84649
84724
  * @import {Image, Parents} from 'mdast'
@@ -84720,7 +84795,7 @@ function imagePeek() {
84720
84795
  return "!";
84721
84796
  }
84722
84797
  //#endregion
84723
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js
84798
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/image-reference.js
84724
84799
  /**
84725
84800
  * @import {Info, State} from 'mdast-util-to-markdown'
84726
84801
  * @import {ImageReference, Parents} from 'mdast'
@@ -84781,7 +84856,7 @@ function imageReferencePeek() {
84781
84856
  return "!";
84782
84857
  }
84783
84858
  //#endregion
84784
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js
84859
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/inline-code.js
84785
84860
  /**
84786
84861
  * @import {State} from 'mdast-util-to-markdown'
84787
84862
  * @import {InlineCode, Parents} from 'mdast'
@@ -84829,7 +84904,7 @@ function inlineCodePeek() {
84829
84904
  return "`";
84830
84905
  }
84831
84906
  //#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
84907
+ //#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
84908
  /**
84834
84909
  * @import {State} from 'mdast-util-to-markdown'
84835
84910
  * @import {Link} from 'mdast'
@@ -84849,7 +84924,7 @@ function formatLinkAsAutolink(node, state) {
84849
84924
  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
84925
  }
84851
84926
  //#endregion
84852
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/link.js
84927
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/link.js
84853
84928
  /**
84854
84929
  * @import {Info, State} from 'mdast-util-to-markdown'
84855
84930
  * @import {Link, Parents} from 'mdast'
@@ -84950,7 +85025,7 @@ function linkPeek(node, _, state) {
84950
85025
  return formatLinkAsAutolink(node, state) ? "<" : "[";
84951
85026
  }
84952
85027
  //#endregion
84953
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js
85028
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/link-reference.js
84954
85029
  /**
84955
85030
  * @import {Info, State} from 'mdast-util-to-markdown'
84956
85031
  * @import {LinkReference, Parents} from 'mdast'
@@ -85011,7 +85086,7 @@ function linkReferencePeek() {
85011
85086
  return "[";
85012
85087
  }
85013
85088
  //#endregion
85014
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js
85089
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet.js
85015
85090
  /**
85016
85091
  * @import {Options, State} from 'mdast-util-to-markdown'
85017
85092
  */
@@ -85029,7 +85104,7 @@ function checkBullet(state) {
85029
85104
  return marker;
85030
85105
  }
85031
85106
  //#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
85107
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet-other.js
85033
85108
  /**
85034
85109
  * @import {Options, State} from 'mdast-util-to-markdown'
85035
85110
  */
@@ -85050,7 +85125,7 @@ function checkBulletOther(state) {
85050
85125
  return bulletOther;
85051
85126
  }
85052
85127
  //#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
85128
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-bullet-ordered.js
85054
85129
  /**
85055
85130
  * @import {Options, State} from 'mdast-util-to-markdown'
85056
85131
  */
@@ -85068,7 +85143,7 @@ function checkBulletOrdered(state) {
85068
85143
  return marker;
85069
85144
  }
85070
85145
  //#endregion
85071
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-rule.js
85146
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-rule.js
85072
85147
  /**
85073
85148
  * @import {Options, State} from 'mdast-util-to-markdown'
85074
85149
  */
@@ -85086,7 +85161,7 @@ function checkRule(state) {
85086
85161
  return marker;
85087
85162
  }
85088
85163
  //#endregion
85089
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/list.js
85164
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/list.js
85090
85165
  /**
85091
85166
  * @import {Info, State} from 'mdast-util-to-markdown'
85092
85167
  * @import {List, Parents} from 'mdast'
@@ -85136,7 +85211,7 @@ function list$2(node, parent, state, info) {
85136
85211
  return value;
85137
85212
  }
85138
85213
  //#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
85214
+ //#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
85215
  /**
85141
85216
  * @import {Options, State} from 'mdast-util-to-markdown'
85142
85217
  */
@@ -85154,7 +85229,7 @@ function checkListItemIndent(state) {
85154
85229
  return style;
85155
85230
  }
85156
85231
  //#endregion
85157
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/list-item.js
85232
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/list-item.js
85158
85233
  /**
85159
85234
  * @import {Info, Map, State} from 'mdast-util-to-markdown'
85160
85235
  * @import {ListItem, Parents} from 'mdast'
@@ -85197,7 +85272,7 @@ function listItem$1(node, parent, state, info) {
85197
85272
  }
85198
85273
  }
85199
85274
  //#endregion
85200
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js
85275
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/paragraph.js
85201
85276
  /**
85202
85277
  * @import {Info, State} from 'mdast-util-to-markdown'
85203
85278
  * @import {Paragraph, Parents} from 'mdast'
@@ -85225,7 +85300,7 @@ function paragraph$1(node, _, state, info) {
85225
85300
  return value;
85226
85301
  }
85227
85302
  //#endregion
85228
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/root.js
85303
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/root.js
85229
85304
  /**
85230
85305
  * @import {Info, State} from 'mdast-util-to-markdown'
85231
85306
  * @import {Parents, Root} from 'mdast'
@@ -85254,7 +85329,7 @@ function root$1(node, _, state, info) {
85254
85329
  return value;
85255
85330
  }
85256
85331
  //#endregion
85257
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/check-strong.js
85332
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-strong.js
85258
85333
  /**
85259
85334
  * @import {Options, State} from 'mdast-util-to-markdown'
85260
85335
  */
@@ -85272,7 +85347,7 @@ function checkStrong(state) {
85272
85347
  return marker;
85273
85348
  }
85274
85349
  //#endregion
85275
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/strong.js
85350
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/strong.js
85276
85351
  /**
85277
85352
  * @import {AttentionInfo, Info, State} from 'mdast-util-to-markdown'
85278
85353
  * @import {Parents, Strong} from 'mdast'
@@ -85337,7 +85412,7 @@ function peek(_, _1, state) {
85337
85412
  return state.options.strong || "*";
85338
85413
  }
85339
85414
  //#endregion
85340
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/text.js
85415
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/text.js
85341
85416
  /**
85342
85417
  * @import {Info, State} from 'mdast-util-to-markdown'
85343
85418
  * @import {Parents, Text} from 'mdast'
@@ -85347,7 +85422,7 @@ function peek(_, _1, state) {
85347
85422
  *
85348
85423
  * @param {Text} node
85349
85424
  * Node to serialize.
85350
- * @param {Parents | undefined} _
85425
+ * @param {Parents | undefined} parent
85351
85426
  * Parent node.
85352
85427
  * @param {State} state
85353
85428
  * Info passed around.
@@ -85356,11 +85431,18 @@ function peek(_, _1, state) {
85356
85431
  * @returns {string}
85357
85432
  * Serialized markdown.
85358
85433
  */
85359
- function text$4(node, _, state, info) {
85360
- return state.safe(node.value, info);
85434
+ function text$4(node, parent, state, info) {
85435
+ /* c8 ignore next -- parent always passed */
85436
+ const siblings = parent ? parent.children : [];
85437
+ const index = siblings.indexOf(node);
85438
+ return state.safe(node.value, {
85439
+ ...info,
85440
+ afterNode: siblings[index + 1],
85441
+ beforeNode: siblings[index - 1]
85442
+ });
85361
85443
  }
85362
85444
  //#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
85445
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/check-rule-repetition.js
85364
85446
  /**
85365
85447
  * @import {Options, State} from 'mdast-util-to-markdown'
85366
85448
  */
@@ -85378,7 +85460,7 @@ function checkRuleRepetition(state) {
85378
85460
  return repetition;
85379
85461
  }
85380
85462
  //#endregion
85381
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js
85463
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/thematic-break.js
85382
85464
  /**
85383
85465
  * @import {State} from 'mdast-util-to-markdown'
85384
85466
  * @import {Parents, ThematicBreak} from 'mdast'
@@ -85400,7 +85482,7 @@ function thematicBreak$2(_, _1, state) {
85400
85482
  return state.options.ruleSpaces ? value.slice(0, -1) : value;
85401
85483
  }
85402
85484
  //#endregion
85403
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/handle/index.js
85485
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/handle/index.js
85404
85486
  /**
85405
85487
  * Default (CommonMark) handlers.
85406
85488
  */
@@ -85526,7 +85608,7 @@ const htmlRawNames = [
85526
85608
  "textarea"
85527
85609
  ];
85528
85610
  //#endregion
85529
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/html-kind.js
85611
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/html-kind.js
85530
85612
  /**
85531
85613
  * @typedef {1 | 2 | 3 | 4 | 5 | 6 | 7} Kind
85532
85614
  * Kind of HTML (flow) element.
@@ -85574,7 +85656,7 @@ function htmlKind(value) {
85574
85656
  return htmlBlockNames.includes(name) ? 6 : 7;
85575
85657
  }
85576
85658
  //#endregion
85577
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/join.js
85659
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/join.js
85578
85660
  /**
85579
85661
  * @import {Join} from 'mdast-util-to-markdown'
85580
85662
  */
@@ -85601,7 +85683,7 @@ function joinDefaults(left, right, parent, state) {
85601
85683
  }
85602
85684
  }
85603
85685
  //#endregion
85604
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/unsafe.js
85686
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/unsafe.js
85605
85687
  /**
85606
85688
  * @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
85607
85689
  */
@@ -88069,7 +88151,7 @@ function decode($0, $1, $2) {
88069
88151
  return decodeNamedCharacterReference($2) || $0;
88070
88152
  }
88071
88153
  //#endregion
88072
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/association.js
88154
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/association.js
88073
88155
  /**
88074
88156
  * @import {AssociationId} from '../types.js'
88075
88157
  */
@@ -88098,7 +88180,7 @@ function association(node) {
88098
88180
  return decodeString(node.identifier);
88099
88181
  }
88100
88182
  //#endregion
88101
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js
88183
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/compile-pattern.js
88102
88184
  /**
88103
88185
  * @import {CompilePattern} from '../types.js'
88104
88186
  */
@@ -88110,7 +88192,7 @@ function association(node) {
88110
88192
  function compilePattern(pattern) {
88111
88193
  if (!pattern._compiled) {
88112
88194
  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");
88195
+ pattern._compiled = new RegExp((pattern.beforeNode ? "^" : before ? "(" + before + ")" : "") + (/[$()*+.?[\\\]^{|}]/.test(pattern.character) ? "\\" : "") + pattern.character + (pattern.afterNode ? "$" : pattern.after ? "(?:" + pattern.after + ")" : ""), pattern.unicode ? "gu" : "g");
88114
88196
  }
88115
88197
  return pattern._compiled;
88116
88198
  }
@@ -88138,7 +88220,7 @@ function classifyCharacter(code) {
88138
88220
  if (unicodePunctuation(code)) return 2;
88139
88221
  }
88140
88222
  //#endregion
88141
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/character.js
88223
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/character.js
88142
88224
  /**
88143
88225
  * Get the first character of a value.
88144
88226
  *
@@ -88168,7 +88250,7 @@ function lastCharacter(value) {
88168
88250
  return code !== void 0 && code > 65535 ? value.slice(-2) : value.slice(-1);
88169
88251
  }
88170
88252
  //#endregion
88171
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/encode-info.js
88253
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/encode-info.js
88172
88254
  /**
88173
88255
  * @import {EncodeSides} from '../types.js'
88174
88256
  */
@@ -88240,7 +88322,7 @@ function encodeInfo(outside, inside, marker) {
88240
88322
  };
88241
88323
  }
88242
88324
  //#endregion
88243
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js
88325
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/container-phrasing.js
88244
88326
  /**
88245
88327
  * @import {Attention, Handle, Info, State} from 'mdast-util-to-markdown'
88246
88328
  * @import {PhrasingParents} from '../types.js'
@@ -88733,7 +88815,7 @@ function serialize(items, before, after) {
88733
88815
  return result;
88734
88816
  }
88735
88817
  //#endregion
88736
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/container-flow.js
88818
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/container-flow.js
88737
88819
  /**
88738
88820
  * @import {State} from 'mdast-util-to-markdown'
88739
88821
  * @import {FlowChildren, FlowParents, TrackFields} from '../types.js'
@@ -88797,7 +88879,7 @@ function between(left, right, parent, state) {
88797
88879
  return "\n\n";
88798
88880
  }
88799
88881
  //#endregion
88800
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js
88882
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/indent-lines.js
88801
88883
  /**
88802
88884
  * @import {IndentLines} from '../types.js'
88803
88885
  */
@@ -88833,9 +88915,10 @@ function indentLines(value, map) {
88833
88915
  }
88834
88916
  }
88835
88917
  //#endregion
88836
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/safe.js
88918
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/safe.js
88837
88919
  /**
88838
- * @import {SafeConfig, State} from 'mdast-util-to-markdown'
88920
+ * @import {Nodes} from 'mdast'
88921
+ * @import {SafeConfig, State, Unsafe} from 'mdast-util-to-markdown'
88839
88922
  */
88840
88923
  const own$3 = {}.hasOwnProperty;
88841
88924
  /**
@@ -88872,17 +88955,21 @@ function safe(state, input, config) {
88872
88955
  /** @type {Record<number, {before: boolean, after: boolean}>} */
88873
88956
  const infos = {};
88874
88957
  const percentEncode = state.stack.includes("autolink");
88958
+ const offset = config.before ? config.before.length : 0;
88959
+ const end = value.length - (config.after ? config.after.length : 0);
88875
88960
  let index = -1;
88876
88961
  while (++index < state.unsafe.length) {
88877
88962
  const pattern = state.unsafe[index];
88878
- if (!patternInScope(state.stack, pattern)) continue;
88963
+ if (!patternInScope(state.stack, pattern) || !nodeInScope(pattern.beforeNode, config.beforeNode) || !nodeInScope(pattern.afterNode, config.afterNode)) continue;
88879
88964
  const expression = state.compilePattern(pattern);
88965
+ const from = pattern.beforeNode ? offset : 0;
88966
+ const subject = value.slice(from, pattern.afterNode ? end : void 0);
88880
88967
  /** @type {RegExpExecArray | null} */
88881
88968
  let match;
88882
- while (match = expression.exec(value)) {
88969
+ while (match = expression.exec(subject)) {
88883
88970
  const before = "before" in pattern || Boolean(pattern.atBreak);
88884
88971
  const after = "after" in pattern;
88885
- const position = match.index + (before ? match[1].length : 0);
88972
+ const position = from + match.index + (before ? match[1].length : 0);
88886
88973
  if (own$3.call(infos, position)) {
88887
88974
  if (infos[position].before && !before) infos[position].before = false;
88888
88975
  if (infos[position].after && !after) infos[position].after = false;
@@ -88896,8 +88983,6 @@ function safe(state, input, config) {
88896
88983
  }
88897
88984
  }
88898
88985
  positions.sort(numerical);
88899
- const offset = config.before ? config.before.length : 0;
88900
- const end = value.length - (config.after ? config.after.length : 0);
88901
88986
  let start = offset;
88902
88987
  index = -1;
88903
88988
  while (++index < positions.length) {
@@ -88932,6 +89017,21 @@ function safe(state, input, config) {
88932
89017
  return result.join("");
88933
89018
  }
88934
89019
  /**
89020
+ * Check if a node matches the node types of a pattern.
89021
+ *
89022
+ * @param {Array<Nodes['type']> | Nodes['type'] | null | undefined} types
89023
+ * Node types.
89024
+ * @param {Nodes | undefined} node
89025
+ * Node.
89026
+ * @returns {boolean}
89027
+ * Whether `node` matches `types`.
89028
+ */
89029
+ function nodeInScope(types, node) {
89030
+ if (!types) return true;
89031
+ if (!node) return false;
89032
+ return typeof types === "string" ? types === node.type : types.includes(node.type);
89033
+ }
89034
+ /**
88935
89035
  * Compare two numbers.
88936
89036
  *
88937
89037
  * @param {number} a
@@ -88975,7 +89075,7 @@ function escapeBackslashes(value, after) {
88975
89075
  return results.join("");
88976
89076
  }
88977
89077
  //#endregion
88978
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/util/track.js
89078
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/util/track.js
88979
89079
  /**
88980
89080
  * @import {CreateTracker, TrackCurrent, TrackMove, TrackShift} from '../types.js'
88981
89081
  */
@@ -89033,7 +89133,7 @@ function track(config) {
89033
89133
  }
89034
89134
  }
89035
89135
  //#endregion
89036
- //#region node_modules/.pnpm/mdast-util-to-markdown@2.1.3/node_modules/mdast-util-to-markdown/lib/index.js
89136
+ //#region node_modules/.pnpm/mdast-util-to-markdown@2.2.0/node_modules/mdast-util-to-markdown/lib/index.js
89037
89137
  /**
89038
89138
  * @import {Info, Join, Options, SafeConfig, State} from 'mdast-util-to-markdown'
89039
89139
  * @import {Nodes} from 'mdast'
@@ -99040,7 +99140,7 @@ function preprocess() {
99040
99140
  }
99041
99141
  }
99042
99142
  //#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
99143
+ //#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
99144
  /**
99045
99145
  * @import {
99046
99146
  * Break,
@@ -99120,6 +99220,8 @@ function fromMarkdown(value, encoding, options) {
99120
99220
  function compiler(options) {
99121
99221
  /** @type {Config} */
99122
99222
  const config = {
99223
+ afterExit: [],
99224
+ beforeEnter: [],
99123
99225
  transforms: [],
99124
99226
  canContainEols: [
99125
99227
  "emphasis",
@@ -99239,7 +99341,8 @@ function compiler(options) {
99239
99341
  /** @type {Root} */
99240
99342
  let tree = {
99241
99343
  type: "root",
99242
- children: []
99344
+ children: [],
99345
+ position: void 0
99243
99346
  };
99244
99347
  /** @type {Omit<CompileContext, 'sliceSerialize'>} */
99245
99348
  const context = {
@@ -99254,15 +99357,32 @@ function compiler(options) {
99254
99357
  };
99255
99358
  /** @type {Array<number>} */
99256
99359
  const listStack = [];
99360
+ /** @type {Array<Event>} */
99361
+ const prepared = [];
99257
99362
  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);
99363
+ while (++index < events.length) {
99364
+ prepared.push(events[index]);
99365
+ if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
99366
+ if (events[index][0] === "enter") listStack.push(prepared.length - 1);
99367
+ else prepareList(prepared, listStack.pop());
99368
+ }
99261
99369
  }
99370
+ events = prepared;
99262
99371
  index = -1;
99263
99372
  while (++index < events.length) {
99264
99373
  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]);
99374
+ if (events[index][0] === "enter" && config.beforeEnter.length > 0) callListeners(config.beforeEnter, {
99375
+ ...context,
99376
+ sliceSerialize: events[index][2].sliceSerialize
99377
+ }, events[index][1]);
99378
+ if (own$2.call(handler, events[index][1].type)) handler[events[index][1].type].call({
99379
+ ...context,
99380
+ sliceSerialize: events[index][2].sliceSerialize
99381
+ }, events[index][1]);
99382
+ if (events[index][0] === "exit" && config.afterExit.length > 0) callListeners(config.afterExit, {
99383
+ ...context,
99384
+ sliceSerialize: events[index][2].sliceSerialize
99385
+ }, events[index][1]);
99266
99386
  }
99267
99387
  if (context.tokenStack.length > 0) {
99268
99388
  const tail = context.tokenStack[context.tokenStack.length - 1];
@@ -99285,12 +99405,28 @@ function compiler(options) {
99285
99405
  return tree;
99286
99406
  }
99287
99407
  /**
99408
+ * @param {Array<Handle>} listeners
99409
+ * Listeners.
99410
+ * @param {CompileContext} context
99411
+ * Context.
99412
+ * @param {Token} token
99413
+ * Token.
99414
+ * @returns {undefined}
99415
+ * Nothing.
99416
+ */
99417
+ function callListeners(listeners, context, token) {
99418
+ let index = -1;
99419
+ while (++index < listeners.length) listeners[index].call(context, token);
99420
+ }
99421
+ /**
99288
99422
  * @param {Array<Event>} events
99423
+ * Events, ending with the list to prepare.
99289
99424
  * @param {number} start
99290
- * @param {number} length
99291
- * @returns {number}
99425
+ * Index of the list enter.
99426
+ * @returns {undefined}
99292
99427
  */
99293
- function prepareList(events, start, length) {
99428
+ function prepareList(events, start) {
99429
+ const end = events.length - 1;
99294
99430
  let index = start - 1;
99295
99431
  let containerBalance = -1;
99296
99432
  let listSpread = false;
@@ -99302,7 +99438,14 @@ function compiler(options) {
99302
99438
  let firstBlankLineIndex;
99303
99439
  /** @type {boolean | undefined} */
99304
99440
  let atMarker;
99305
- while (++index <= length) {
99441
+ /**
99442
+ * `listItem` events to insert before the event at `at`, in walk order.
99443
+ * Merged in after the walk, as inserting while walking would shift the
99444
+ * rest of the list once per item.
99445
+ * @type {Array<{at: number, event: Event}>}
99446
+ */
99447
+ const insertions = [];
99448
+ while (++index <= end) {
99306
99449
  const event = events[index];
99307
99450
  switch (event[1].type) {
99308
99451
  case "listUnordered":
@@ -99343,13 +99486,14 @@ function compiler(options) {
99343
99486
  }
99344
99487
  if (firstBlankLineIndex && (!lineIndex || firstBlankLineIndex < lineIndex)) listItem._spread = true;
99345
99488
  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++;
99489
+ insertions.push({
99490
+ at: lineIndex || index,
99491
+ event: [
99492
+ "exit",
99493
+ listItem,
99494
+ event[2]
99495
+ ]
99496
+ });
99353
99497
  }
99354
99498
  if (event[1].type === "listItemPrefix") {
99355
99499
  /** @type {Token} */
@@ -99360,20 +99504,27 @@ function compiler(options) {
99360
99504
  end: void 0
99361
99505
  };
99362
99506
  listItem = item;
99363
- events.splice(index, 0, [
99364
- "enter",
99365
- item,
99366
- event[2]
99367
- ]);
99368
- index++;
99369
- length++;
99507
+ insertions.push({
99508
+ at: index,
99509
+ event: [
99510
+ "enter",
99511
+ item,
99512
+ event[2]
99513
+ ]
99514
+ });
99370
99515
  firstBlankLineIndex = void 0;
99371
99516
  atMarker = true;
99372
99517
  }
99373
99518
  }
99374
99519
  }
99520
+ const listEvents = events.splice(start);
99521
+ let insertion = 0;
99522
+ index = -1;
99523
+ while (++index < listEvents.length) {
99524
+ while (insertion < insertions.length && insertions[insertion].at === start + index) events.push(insertions[insertion++].event);
99525
+ events.push(listEvents[index]);
99526
+ }
99375
99527
  events[start][1]._spread = listSpread;
99376
- return length;
99377
99528
  }
99378
99529
  /**
99379
99530
  * Create an opener handle.
@@ -99448,7 +99599,7 @@ function compiler(options) {
99448
99599
  start: token.start,
99449
99600
  end: token.end
99450
99601
  }) + "): it’s not open");
99451
- else if (open[0].type !== token.type) {
99602
+ if (open[0].type !== token.type) {
99452
99603
  if (onExitError) onExitError.call(this, token, open[0]);
99453
99604
  else (open[1] || defaultOnError).call(this, token, open[0]);
99454
99605
  }
@@ -99816,7 +99967,8 @@ function compiler(options) {
99816
99967
  function blockQuote() {
99817
99968
  return {
99818
99969
  type: "blockquote",
99819
- children: []
99970
+ children: [],
99971
+ position: void 0
99820
99972
  };
99821
99973
  }
99822
99974
  /** @returns {Code} */
@@ -99825,14 +99977,16 @@ function compiler(options) {
99825
99977
  type: "code",
99826
99978
  lang: null,
99827
99979
  meta: null,
99828
- value: ""
99980
+ value: "",
99981
+ position: void 0
99829
99982
  };
99830
99983
  }
99831
99984
  /** @returns {InlineCode} */
99832
99985
  function codeText() {
99833
99986
  return {
99834
99987
  type: "inlineCode",
99835
- value: ""
99988
+ value: "",
99989
+ position: void 0
99836
99990
  };
99837
99991
  }
99838
99992
  /** @returns {Definition} */
@@ -99842,14 +99996,16 @@ function compiler(options) {
99842
99996
  identifier: "",
99843
99997
  label: null,
99844
99998
  title: null,
99845
- url: ""
99999
+ url: "",
100000
+ position: void 0
99846
100001
  };
99847
100002
  }
99848
100003
  /** @returns {Emphasis} */
99849
100004
  function emphasis() {
99850
100005
  return {
99851
100006
  type: "emphasis",
99852
- children: []
100007
+ children: [],
100008
+ position: void 0
99853
100009
  };
99854
100010
  }
99855
100011
  /** @returns {Heading} */
@@ -99857,18 +100013,23 @@ function compiler(options) {
99857
100013
  return {
99858
100014
  type: "heading",
99859
100015
  depth: 0,
99860
- children: []
100016
+ children: [],
100017
+ position: void 0
99861
100018
  };
99862
100019
  }
99863
100020
  /** @returns {Break} */
99864
100021
  function hardBreak() {
99865
- return { type: "break" };
100022
+ return {
100023
+ type: "break",
100024
+ position: void 0
100025
+ };
99866
100026
  }
99867
100027
  /** @returns {Html} */
99868
100028
  function html() {
99869
100029
  return {
99870
100030
  type: "html",
99871
- value: ""
100031
+ value: "",
100032
+ position: void 0
99872
100033
  };
99873
100034
  }
99874
100035
  /** @returns {Image} */
@@ -99877,7 +100038,8 @@ function compiler(options) {
99877
100038
  type: "image",
99878
100039
  title: null,
99879
100040
  url: "",
99880
- alt: null
100041
+ alt: null,
100042
+ position: void 0
99881
100043
  };
99882
100044
  }
99883
100045
  /** @returns {Link} */
@@ -99886,7 +100048,8 @@ function compiler(options) {
99886
100048
  type: "link",
99887
100049
  title: null,
99888
100050
  url: "",
99889
- children: []
100051
+ children: [],
100052
+ position: void 0
99890
100053
  };
99891
100054
  }
99892
100055
  /**
@@ -99899,7 +100062,8 @@ function compiler(options) {
99899
100062
  ordered: token.type === "listOrdered",
99900
100063
  start: null,
99901
100064
  spread: token._spread,
99902
- children: []
100065
+ children: [],
100066
+ position: void 0
99903
100067
  };
99904
100068
  }
99905
100069
  /**
@@ -99911,33 +100075,40 @@ function compiler(options) {
99911
100075
  type: "listItem",
99912
100076
  spread: token._spread,
99913
100077
  checked: null,
99914
- children: []
100078
+ children: [],
100079
+ position: void 0
99915
100080
  };
99916
100081
  }
99917
100082
  /** @returns {Paragraph} */
99918
100083
  function paragraph() {
99919
100084
  return {
99920
100085
  type: "paragraph",
99921
- children: []
100086
+ children: [],
100087
+ position: void 0
99922
100088
  };
99923
100089
  }
99924
100090
  /** @returns {Strong} */
99925
100091
  function strong() {
99926
100092
  return {
99927
100093
  type: "strong",
99928
- children: []
100094
+ children: [],
100095
+ position: void 0
99929
100096
  };
99930
100097
  }
99931
100098
  /** @returns {Text} */
99932
100099
  function text() {
99933
100100
  return {
99934
100101
  type: "text",
99935
- value: ""
100102
+ value: "",
100103
+ position: void 0
99936
100104
  };
99937
100105
  }
99938
100106
  /** @returns {ThematicBreak} */
99939
100107
  function thematicBreak() {
99940
- return { type: "thematicBreak" };
100108
+ return {
100109
+ type: "thematicBreak",
100110
+ position: void 0
100111
+ };
99941
100112
  }
99942
100113
  }
99943
100114
  /**
@@ -99987,6 +100158,12 @@ function extension(combined, extension) {
99987
100158
  if (right) combined[key].push(...right);
99988
100159
  break;
99989
100160
  }
100161
+ case "afterExit":
100162
+ case "beforeEnter": {
100163
+ const right = extension[key];
100164
+ if (right) combined[key].push(right);
100165
+ break;
100166
+ }
99990
100167
  case "enter":
99991
100168
  case "exit": {
99992
100169
  const right = extension[key];
@@ -100004,7 +100181,7 @@ function defaultOnError(left, right) {
100004
100181
  start: right.start,
100005
100182
  end: right.end
100006
100183
  }) + ") is open");
100007
- else throw new Error("Cannot close document, a token (`" + right.type + "`, " + stringifyPosition({
100184
+ throw new Error("Cannot close document, a token (`" + right.type + "`, " + stringifyPosition({
100008
100185
  start: right.start,
100009
100186
  end: right.end
100010
100187
  }) + ") is still open");
@@ -103736,6 +103913,7 @@ const formatNodeTranslationSummary = (translation) => [
103736
103913
  `- **Updated**: ${translation.updated_at}`
103737
103914
  ].join("\n");
103738
103915
  const formatCategory = (category) => `- **${category.name}** (${category.id}) — ${category.description || "No description"}`;
103916
+ const formatBrand = (brand) => `- **${brand.name}** (${brand.id}) — ${brand.brand_url}${brand.default ? " — default" : ""}${brand.active ? "" : " — inactive"}`;
103739
103917
  const formatSection = (section) => `- **${section.name}** (${section.id}) — Category: ${section.category_id} — ${section.description || "No description"}`;
103740
103918
  const formatView = (view, count) => {
103741
103919
  const countText = count ? ` — ${count.pretty} ticket(s)${count.fresh ? "" : " (count updating)"}` : "";
@@ -103786,13 +103964,7 @@ const extractSearchPaginationMeta = (response, perPage, page) => {
103786
103964
  };
103787
103965
  //#endregion
103788
103966
  //#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
- */
103967
+ /** Name of the companion tool that lists promoted articles (`help-center.ts`). */
103796
103968
  const LIST_PROMOTED_ARTICLES_TOOL = "list_promoted_articles";
103797
103969
  /**
103798
103970
  * Scan the Help Center for promoted ("featured") articles with the CALLER'S
@@ -103801,9 +103973,11 @@ const LIST_PROMOTED_ARTICLES_TOOL = "list_promoted_articles";
103801
103973
  * `/articles` and filter `promoted` client-side, bounded by `maxPages` to keep
103802
103974
  * the scan tractable on a large Help Center. `truncated` signals the cap was hit.
103803
103975
  *
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.
103976
+ * `subdomain` is the EFFECTIVE one — the account's, or the resolved brand's own
103977
+ * (a brand is only ever <subdomain>.zendesk.com). Returns the FULL promoted
103978
+ * articles so callers that need rich metadata (the `list_promoted_articles`
103979
+ * tool) get everything; the resource provider maps these down to lean refs
103980
+ * before caching so the per-session cache doesn't retain bodies.
103807
103981
  */
103808
103982
  const fetchPromotedArticles = async (subdomain, token, maxPages = ARTICLE_RESOURCES_SCAN_MAX_PAGES) => {
103809
103983
  const promoted = [];
@@ -103833,6 +104007,7 @@ const fetchPromotedArticles = async (subdomain, token, maxPages = ARTICLE_RESOUR
103833
104007
  * caller's token and render it as Markdown: the shared metadata summary plus the
103834
104008
  * body converted from HTML (rather than a raw HTML dump), capped by the response
103835
104009
  * character limit. Reuses the same formatting as the `get_article` tool.
104010
+ * `subdomain` is the EFFECTIVE one — the account's, or the resolved brand's own.
103836
104011
  */
103837
104012
  const fetchArticleMarkdown = async (subdomain, token, id, locale) => {
103838
104013
  const path = locale ? `/${locale}/articles/${id}` : `/articles/${id}`;
@@ -103852,24 +104027,69 @@ const fetchArticleMarkdown = async (subdomain, token, id, locale) => {
103852
104027
  * module scope — in HTTP mode a shared one would leak a caller's data to
103853
104028
  * another. `getToken` resolves lazily, so connecting never triggers the OAuth
103854
104029
  * flow, and a 401 notifies `onUnauthorized` to drop the stale token.
104030
+ *
104031
+ * Branding is resolved through the SHARED brand subdomain resolver, never a
104032
+ * second /brands walk: `brandIds` decides which brands `listPromoted` scans
104033
+ * (all of them in multi mode, so every allowed brand's promoted articles are
104034
+ * listed with their own URIs; 'all' scans every brand of the account), and
104035
+ * `readArticle` resolves the URI's {brand} through the same allow-list +
104036
+ * resolver the tools use.
103855
104037
  */
103856
- const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized) => {
104038
+ const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized, resolveBrandSubdomain = () => Promise.resolve(subdomain), brandIds) => {
103857
104039
  let cached;
103858
104040
  const notifyIfUnauthorized = (err) => {
103859
104041
  if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
103860
104042
  };
104043
+ const isSingleLock = brandIds !== void 0 && brandIds.length === 1 && brandIds[0] !== "all";
104044
+ const listBrands = async () => {
104045
+ if (brandIds === void 0 || isSingleLock) return [void 0];
104046
+ if (brandIds[0] === "all") return (await fetchAllBrands(subdomain, await getToken())).filter((b) => b.active && b.has_help_center).map((b) => b.subdomain);
104047
+ return brandIds;
104048
+ };
104049
+ const effectiveSubdomain = async (brand) => {
104050
+ if (brand !== void 0) return resolveBrandSubdomain(brand);
104051
+ if (isSingleLock) return resolveBrandSubdomain(brandIds[0]);
104052
+ return subdomain;
104053
+ };
104054
+ const scanBrand = async (brand, token) => {
104055
+ try {
104056
+ const hcSubdomain = await effectiveSubdomain(brand);
104057
+ const scan = await fetchPromotedArticles(hcSubdomain, token, ARTICLE_RESOURCES_SCAN_MAX_PAGES);
104058
+ return {
104059
+ refs: scan.articles.map((a) => brand === void 0 ? {
104060
+ id: a.id,
104061
+ title: a.title
104062
+ } : {
104063
+ id: a.id,
104064
+ title: a.title,
104065
+ brand
104066
+ }),
104067
+ truncated: scan.truncated
104068
+ };
104069
+ } catch (err) {
104070
+ if (err instanceof ZendeskApiError && err.status === 401) throw err;
104071
+ return {
104072
+ refs: [],
104073
+ truncated: true
104074
+ };
104075
+ }
104076
+ };
103861
104077
  return {
103862
104078
  listPromoted() {
103863
104079
  const now = Date.now();
103864
104080
  if (cached && now - cached.at < 3e5) return cached.promise;
103865
104081
  const promise = (async () => {
103866
104082
  const token = await getToken();
103867
- const { articles, truncated } = await fetchPromotedArticles(subdomain, token);
104083
+ const brands = await listBrands();
104084
+ const refs = [];
104085
+ let truncated = false;
104086
+ for (const brand of brands) {
104087
+ const scan = await scanBrand(brand, token);
104088
+ truncated = truncated || scan.truncated;
104089
+ refs.push(...scan.refs);
104090
+ }
103868
104091
  return {
103869
- refs: articles.map((a) => ({
103870
- id: a.id,
103871
- title: a.title
103872
- })),
104092
+ refs,
103873
104093
  truncated
103874
104094
  };
103875
104095
  })().catch((err) => {
@@ -103883,10 +104103,10 @@ const createArticleResourcesProvider = (getToken, subdomain, onUnauthorized) =>
103883
104103
  };
103884
104104
  return promise;
103885
104105
  },
103886
- async readArticle(id) {
104106
+ async readArticle(id, brand) {
103887
104107
  try {
103888
- const token = await getToken();
103889
- return await fetchArticleMarkdown(subdomain, token, id);
104108
+ const [token, hcSubdomain] = await Promise.all([getToken(), effectiveSubdomain(brand)]);
104109
+ return await fetchArticleMarkdown(hcSubdomain, token, id, void 0);
103890
104110
  } catch (err) {
103891
104111
  notifyIfUnauthorized(err);
103892
104112
  throw err;
@@ -103907,17 +104127,33 @@ const topologyResourceUri = (config) => `${config.hcResourceScheme}://topology`;
103907
104127
  /**
103908
104128
  * URI template of the pull-only Help Center article resources, built from the
103909
104129
  * 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.
104130
+ * default `zendesk-hc`). In multi/all brand mode the URI carries a brand
104131
+ * dimension — `<scheme>://brands/{brand}/articles/{id}` — because an article id
104132
+ * alone does not say which brand's Help Center holds it; the brand is named by
104133
+ * id or subdomain, the same idiom the tools' `brand_id` accepts. Unset and
104134
+ * single-brand modes keep `<scheme>://article/{id}`: one Help Center is
104135
+ * addressed, so a brand segment would be noise. The template's `list` callback
104136
+ * enumerates the promoted ("featured") articles (so clients can surface them
104137
+ * for pinning), while any article id can be read on demand.
103913
104138
  */
103914
- const articleResourceUriTemplate = (config) => `${config.hcResourceScheme}://article/{id}`;
104139
+ const articleResourceUriTemplate = (config) => brandScopedArticles(config) ? `${config.hcResourceScheme}://brands/{brand}/articles/{id}` : `${config.hcResourceScheme}://article/{id}`;
103915
104140
  /**
103916
104141
  * Build the concrete resource URI for a single article id, under the configured
103917
104142
  * scheme. Shares the scheme with the template above so the listed URIs always
103918
- * match the template the read callback is registered under.
104143
+ * match the template the read callback is registered under. In multi/all mode
104144
+ * the brand argument (id or subdomain) is required and fills the {brand} slot.
104145
+ */
104146
+ const articleResourceUri = (config, id, brand) => brandScopedArticles(config) ? `${config.hcResourceScheme}://brands/${brand}/articles/${id}` : `${config.hcResourceScheme}://article/${id}`;
104147
+ /**
104148
+ * Whether the article resources address one Help Center among several: the
104149
+ * server was started with several brands, or with `all`. Only then does the URI
104150
+ * carry the brand dimension; unset and single-lock modes name exactly one Help
104151
+ * Center implicitly.
103919
104152
  */
103920
- const articleResourceUri = (config, id) => `${config.hcResourceScheme}://article/${id}`;
104153
+ const brandScopedArticles = (config) => {
104154
+ const brandIds = config.brandIds;
104155
+ return brandIds !== void 0 && (brandIds.length > 1 || brandIds[0] === "all");
104156
+ };
103921
104157
  /**
103922
104158
  * Whether the `help_center` namespace is active: no `--namespace` filter, or one
103923
104159
  * that includes it. The shared second half of the two Help Center feature gates
@@ -103943,13 +104179,11 @@ const helpCenterContextEnabled = (config) => config.topology && helpCenterNamesp
103943
104179
  const articleResourceEnabled = (config) => helpCenterNamespaceActive(config);
103944
104180
  /**
103945
104181
  * 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`.
104182
+ * callback's scan, which enumerates the promoted articles for `resources/list`.
104183
+ * A client may trigger it unprompted, and it fans out (a capped scan of
104184
+ * `/articles`, per brand in scope), so `--no-promoted-articles` turns it off.
104185
+ * Read-by-id and the explicit `list_promoted_articles` tool stay available.
104186
+ * `!== false` so an omitted flag on a hand-built Config stays default-on.
103953
104187
  */
103954
104188
  const promotedArticlesEnabled = (config) => config.promotedArticles !== false && helpCenterNamespaceActive(config);
103955
104189
  /**
@@ -103960,13 +104194,20 @@ const promotedArticlesEnabled = (config) => config.promotedArticles !== false &&
103960
104194
  */
103961
104195
  const buildInstructions = (config) => {
103962
104196
  if (!helpCenterContextEnabled(config)) return void 0;
104197
+ let scope;
104198
+ if (config.brandIds === void 0) scope = `the Zendesk Help Center of "${config.subdomain}"`;
104199
+ else if (config.brandIds.length === 1 && config.brandIds[0] === "all") scope = `all brands of the Zendesk Help Center of "${config.subdomain}"`;
104200
+ else if (config.brandIds.length === 1) scope = `brand ${config.brandIds[0]} of the Zendesk Help Center of "${config.subdomain}"`;
104201
+ else scope = `brands ${config.brandIds.join(", ")} of the Zendesk Help Center of "${config.subdomain}"`;
104202
+ 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
104203
  return [
103964
- `This MCP server is connected to the Zendesk Help Center of "${config.subdomain}".`,
104204
+ `This MCP server is connected to ${scope}.`,
103965
104205
  "",
103966
104206
  `When creating or editing Help Center content, the resource ${topologyResourceUri(config)} is useful context:`,
103967
104207
  "it lists the active locales (and the default one), the category → section tree with IDs,",
103968
104208
  "the visibility user segments, the permission groups, and your current role.",
103969
104209
  "Prefer its IDs (section_id, permission_group_id, user_segment_id, locale) over guessing from names.",
104210
+ ...topologyBrandNote,
103970
104211
  "",
103971
104212
  "It degrades gracefully: without Guide-admin / Help Center manager rights the permission-groups and",
103972
104213
  "user-segments sections are marked unavailable (not empty). In that case reuse a permission_group_id",
@@ -104003,12 +104244,13 @@ const tolerate403 = async (promise, fallback) => {
104003
104244
  * and sections are each capped at one max-size page; `sectionsHasMore` /
104004
104245
  * `categoriesHasMore` signal a Help Center too large to enumerate inline.
104005
104246
  */
104006
- const fetchTopology = async (subdomain, token) => {
104247
+ const fetchTopology = async (subdomain, token, brandIds, brandSubdomain) => {
104007
104248
  const pageParams = { "page[size]": String(100) };
104249
+ const hcSubdomain = brandSubdomain ?? subdomain;
104008
104250
  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),
104251
+ helpCenterGet(hcSubdomain, token, "/locales"),
104252
+ helpCenterGet(hcSubdomain, token, "/categories", pageParams),
104253
+ helpCenterGet(hcSubdomain, token, "/sections", pageParams),
104012
104254
  tolerate403(helpCenterGet(subdomain, token, "/user_segments"), { user_segments: [] }),
104013
104255
  tolerate403(zendeskGet(subdomain, token, "/guide/permission_groups"), { permission_groups: [] }),
104014
104256
  zendeskGet(subdomain, token, "/users/me")
@@ -104017,6 +104259,7 @@ const fetchTopology = async (subdomain, token) => {
104017
104259
  const sections = sectionsRes.sections ?? [];
104018
104260
  return {
104019
104261
  subdomain,
104262
+ ...brandIds === void 0 ? {} : { brandIds },
104020
104263
  locales,
104021
104264
  categories,
104022
104265
  sections,
@@ -104068,10 +104311,20 @@ const renderAdminSection = (items, denied, deniedNote) => {
104068
104311
  };
104069
104312
  /** Render the topology as a compact Markdown document for the LLM context. */
104070
104313
  const formatTopology = (data) => {
104314
+ let brandNote;
104315
+ let allowedBrandsLine = [];
104316
+ if (data.brandIds === void 0) brandNote = "";
104317
+ else if (data.brandIds.length === 1 && data.brandIds[0] === "all") brandNote = " (all brands; tree below: account default)";
104318
+ else if (data.brandIds.length === 1) brandNote = ` (brand ${data.brandIds[0]})`;
104319
+ else {
104320
+ brandNote = ` (brand ${data.brandIds[0]})`;
104321
+ allowedBrandsLine = ["", `**Allowed brands**: ${data.brandIds.join(", ")}`];
104322
+ }
104071
104323
  const text = [
104072
- `# Zendesk Help Center topology — ${data.subdomain}`,
104324
+ `# Zendesk Help Center topology — ${data.subdomain}${brandNote}`,
104073
104325
  "",
104074
104326
  `**Your access**: ${data.currentUser.name} (id ${data.currentUser.id}), role "${data.currentUser.role}".`,
104327
+ ...allowedBrandsLine,
104075
104328
  "",
104076
104329
  "## Locales",
104077
104330
  `- Default: ${data.locales.default_locale}`,
@@ -104098,14 +104351,14 @@ const formatTopology = (data) => {
104098
104351
  * token. A 401 notifies `onUnauthorized` (stdio OAuth only) to invalidate the
104099
104352
  * stale token, mirroring the tool dispatch path in `server.ts`.
104100
104353
  */
104101
- const createTopologyProvider = (getToken, subdomain, onUnauthorized) => {
104354
+ const createTopologyProvider = (getToken, subdomain, onUnauthorized, brandIds, resolveBrandSubdomain = () => Promise.resolve(void 0)) => {
104102
104355
  let cached;
104103
104356
  return { read() {
104104
104357
  const now = Date.now();
104105
104358
  if (cached && now - cached.at < 3e5) return cached.promise;
104106
104359
  const promise = (async () => {
104107
- const token = await getToken();
104108
- return formatTopology(await fetchTopology(subdomain, token));
104360
+ const [token, brandSubdomain] = await Promise.all([getToken(), resolveBrandSubdomain()]);
104361
+ return formatTopology(await fetchTopology(subdomain, token, brandIds, brandSubdomain));
104109
104362
  })().catch((err) => {
104110
104363
  cached = void 0;
104111
104364
  if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
@@ -104149,7 +104402,6 @@ const filterTools = (allTools, options) => allTools.filter((tool) => {
104149
104402
  if (options.readOnly && !tool.readOnly) return false;
104150
104403
  if (options.namespaces?.length && !options.namespaces.includes(tool.namespace)) return false;
104151
104404
  if (options.tools?.length && !options.tools.includes(tool.name)) return false;
104152
- if (options.promotedArticles === false && tool.name === "list_promoted_articles") return false;
104153
104405
  return true;
104154
104406
  });
104155
104407
  const groupByNamespace = (tools) => {
@@ -104267,6 +104519,50 @@ const isPlacedAsRequested = (effectiveAfter, movedId, target, referenceId) => {
104267
104519
  //#endregion
104268
104520
  //#region src/tools/help-center.ts
104269
104521
  const ARTICLE_ID_DESC = "Article ID — the numeric id of the Help Center article. Obtain it from list_articles or search_articles.";
104522
+ const brandIdField = (brandIds) => {
104523
+ if (!brandIds || brandIds.length === 0) return void 0;
104524
+ if (brandIds.length === 1 && brandIds[0] !== "all") return void 0;
104525
+ 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(", ")}.`;
104526
+ return union([number$2().int(), string$2().min(1)]).describe(description);
104527
+ };
104528
+ /**
104529
+ * Resolve which brand a call targets, enforcing the deploy allow-list.
104530
+ *
104531
+ * - unset: no brand override; a per-call brand_id is rejected (schema has no
104532
+ * field for it, so this is a backstop for hand-crafted calls).
104533
+ * - single: the configured entry is the only brand; ANY per-call brand_id is
104534
+ * rejected (hard lock, no escape — the schema has no field for it).
104535
+ * - multi: brand_id is REQUIRED; the per-call id is resolved and must match an
104536
+ * allowed entry by id or resolved subdomain.
104537
+ * - all: brand_id is REQUIRED; any existing brand passes (validated by the
104538
+ * resolver).
104539
+ *
104540
+ * Returns the brand's subdomain, or undefined for the account default.
104541
+ */
104542
+ const resolveBrand = async (perCall, brandIds, resolveSubdomain) => {
104543
+ if (!brandIds || brandIds.length === 0) {
104544
+ 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.");
104545
+ return;
104546
+ }
104547
+ const isAll = brandIds.length === 1 && brandIds[0] === "all";
104548
+ if (brandIds.length === 1 && !isAll) {
104549
+ const locked = brandIds[0];
104550
+ 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.`);
104551
+ return resolveSubdomain(locked);
104552
+ }
104553
+ 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.`);
104554
+ const resolved = await resolveSubdomain(String(perCall));
104555
+ if (isAll) return resolved;
104556
+ await assertAllowed(perCall, resolved, brandIds, resolveSubdomain);
104557
+ return resolved;
104558
+ };
104559
+ const assertAllowed = async (perCall, resolved, brandIds, resolveSubdomain) => {
104560
+ if (brandIds.includes(String(perCall))) return;
104561
+ const settled = await Promise.allSettled(brandIds.map((entry) => resolveSubdomain(entry)));
104562
+ const allowed = new Set(brandIds);
104563
+ for (const r of settled) if (r.status === "fulfilled") allowed.add(r.value);
104564
+ 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(", ")}.`);
104565
+ };
104270
104566
  const listTranslations = (subdomain, token, articleId) => helpCenterGet(subdomain, token, `/articles/${articleId}/translations`).then((res) => res.translations);
104271
104567
  const NODE_LABEL = {
104272
104568
  sections: "section",
@@ -104467,13 +104763,54 @@ const structureWarning = (r) => {
104467
104763
  return [];
104468
104764
  };
104469
104765
  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)];
104766
+ /**
104767
+ * Build the `list_brands` tool when the server runs in multi/all mode; an empty
104768
+ * array otherwise. It is the discovery source for the required per-call
104769
+ * `brand_id`: in single-lock or unset mode the brand is implicit, the schemas
104770
+ * carry no `brand_id` field, and the tool stays hidden. In multi mode (not
104771
+ * 'all') the listing is filtered to the allowed brands.
104772
+ */
104773
+ const createListBrandsTool = (ctx) => {
104774
+ const { subdomain, brandIds, getToken } = ctx;
104775
+ if (brandIds === void 0) return [];
104776
+ const isAll = brandIds.length === 1 && brandIds[0] === "all";
104777
+ if (brandIds.length === 1 && !isAll) return [];
104778
+ return [{
104779
+ name: "list_brands",
104780
+ namespace: "help_center",
104781
+ readOnly: true,
104782
+ title: "List Zendesk Brands",
104783
+ 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.",
104784
+ inputSchema: object({}),
104785
+ annotations: {
104786
+ readOnlyHint: true,
104787
+ destructiveHint: false,
104788
+ idempotentHint: true,
104789
+ openWorldHint: true
104790
+ },
104791
+ handler: async () => {
104792
+ const token = await getToken();
104793
+ const brands = await fetchAllBrands(subdomain, token);
104794
+ const visible = isAll ? brands : brands.filter((b) => brandIds.includes(String(b.id)) || brandIds.includes(b.subdomain));
104795
+ return { content: [{
104796
+ type: "text",
104797
+ text: formatList(visible, formatBrand, void 0, "Pass a brand id as brand_id to any Help Center tool to operate on that brand.")
104798
+ }] };
104799
+ }
104800
+ }];
104801
+ };
104470
104802
  const createHelpCenterTools = (ctx) => {
104471
- const { subdomain, getToken } = ctx;
104472
- const fetchSectionOrder = async (sectionId, locale, token) => {
104803
+ const { subdomain, brandIds, resolveBrandSubdomain, getToken } = ctx;
104804
+ const brandIdSchema = brandIdField(brandIds);
104805
+ const brandIdPart = brandIdSchema ? { brand_id: brandIdSchema } : {};
104806
+ const resolveHcSubdomain = async (brand_id) => {
104807
+ return await resolveBrand(brand_id, brandIds, resolveBrandSubdomain) ?? subdomain;
104808
+ };
104809
+ const fetchSectionOrder = async (sectionId, locale, token, hcSubdomain) => {
104473
104810
  const order = [];
104474
104811
  let cursor;
104475
104812
  do {
104476
- const response = await helpCenterGet(subdomain, token, `/${locale}/sections/${sectionId}/articles`, buildCursorParams(100, cursor));
104813
+ const response = await helpCenterGet(hcSubdomain, token, `/${locale}/sections/${sectionId}/articles`, buildCursorParams(100, cursor));
104477
104814
  const articles = response.articles ?? [];
104478
104815
  for (const article of articles) order.push({
104479
104816
  id: article.id,
@@ -104484,19 +104821,19 @@ const createHelpCenterTools = (ctx) => {
104484
104821
  } while (cursor);
104485
104822
  return order;
104486
104823
  };
104487
- const assertReferenceInSection = async (effective, referenceArticleId, articleId, sectionId, token) => {
104824
+ const assertReferenceInSection = async (effective, referenceArticleId, articleId, sectionId, token, hcSubdomain) => {
104488
104825
  if (effective.some((a) => a.id === referenceArticleId)) return;
104489
104826
  let detail = "was not found";
104490
104827
  try {
104491
- const { article: ref } = await helpCenterGet(subdomain, token, `/articles/${referenceArticleId}`);
104828
+ const { article: ref } = await helpCenterGet(hcSubdomain, token, `/articles/${referenceArticleId}`);
104492
104829
  detail = `is in section #${ref.section_id}, not section #${sectionId}`;
104493
104830
  } catch {}
104494
104831
  throw new Error(`Reference article #${referenceArticleId} ${detail}. It must be in the same section (#${sectionId}) as article #${articleId}.`);
104495
104832
  };
104496
- const applyPositionWrites = async (writes, articleId, token) => {
104833
+ const applyPositionWrites = async (writes, articleId, token, hcSubdomain) => {
104497
104834
  let applied = 0;
104498
104835
  for (const write of writes) try {
104499
- await helpCenterPut(subdomain, token, `/articles/${write.id}`, { article: { position: write.position } });
104836
+ await helpCenterPut(hcSubdomain, token, `/articles/${write.id}`, { article: { position: write.position } });
104500
104837
  applied += 1;
104501
104838
  } catch (error) {
104502
104839
  const reason = error instanceof Error ? error.message : String(error);
@@ -104505,6 +104842,7 @@ const createHelpCenterTools = (ctx) => {
104505
104842
  return applied;
104506
104843
  };
104507
104844
  return [
104845
+ ...createListBrandsTool(ctx),
104508
104846
  {
104509
104847
  name: "search_articles",
104510
104848
  namespace: "help_center",
@@ -104515,7 +104853,8 @@ const createHelpCenterTools = (ctx) => {
104515
104853
  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
104854
  locale: string$2().optional().describe("Filter by locale (e.g., \"en-us\", \"fr\")"),
104517
104855
  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)
104856
+ page: number$2().int().min(1).default(1).describe(PAGE_DESC),
104857
+ ...brandIdPart
104519
104858
  }),
104520
104859
  annotations: {
104521
104860
  readOnlyHint: true,
@@ -104524,14 +104863,15 @@ const createHelpCenterTools = (ctx) => {
104524
104863
  openWorldHint: true
104525
104864
  },
104526
104865
  handler: async (params) => {
104527
- const { query, locale, per_page, page } = params;
104866
+ const { query, locale, per_page, page, brand_id } = params;
104528
104867
  const token = await getToken();
104868
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104529
104869
  const p = {
104530
104870
  query,
104531
104871
  ...buildOffsetParams(per_page, page)
104532
104872
  };
104533
104873
  if (locale) p["locale"] = locale;
104534
- const response = await helpCenterGet(subdomain, token, "/articles/search", p);
104874
+ const response = await helpCenterGet(hcSubdomain, token, "/articles/search", p);
104535
104875
  return { content: [{
104536
104876
  type: "text",
104537
104877
  text: formatList(response.results ?? [], formatArticleSummary, extractSearchPaginationMeta(response, per_page, page))
@@ -104546,7 +104886,8 @@ const createHelpCenterTools = (ctx) => {
104546
104886
  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
104887
  inputSchema: object({
104548
104888
  article_id: number$2().int().describe(ARTICLE_ID_DESC),
104549
- locale: string$2().optional().describe("Locale for translated version")
104889
+ locale: string$2().optional().describe("Locale for translated version"),
104890
+ ...brandIdPart
104550
104891
  }),
104551
104892
  annotations: {
104552
104893
  readOnlyHint: true,
@@ -104555,11 +104896,12 @@ const createHelpCenterTools = (ctx) => {
104555
104896
  openWorldHint: true
104556
104897
  },
104557
104898
  handler: async (params) => {
104558
- const { article_id, locale } = params;
104899
+ const { article_id, locale, brand_id } = params;
104559
104900
  const token = await getToken();
104901
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104560
104902
  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);
104903
+ const { article } = await helpCenterGet(hcSubdomain, token, path, void 0);
104904
+ const translations = await listTranslations(hcSubdomain, token, article_id);
104563
104905
  const text = (largeArticleHint(article.body, parseSections(article.body).length) ?? "") + formatArticle(article) + `\n\n**Available translations**: ${translations.map((t) => t.locale).join(", ")}`;
104564
104906
  return { content: [{
104565
104907
  type: "text",
@@ -104576,7 +104918,8 @@ const createHelpCenterTools = (ctx) => {
104576
104918
  inputSchema: object({
104577
104919
  locale: string$2().optional().describe("Locale for category names (e.g., \"en-us\", \"fr\"). Defaults to the Help Center default locale."),
104578
104920
  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.")
104921
+ cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page."),
104922
+ ...brandIdPart
104580
104923
  }),
104581
104924
  annotations: {
104582
104925
  readOnlyHint: true,
@@ -104585,10 +104928,11 @@ const createHelpCenterTools = (ctx) => {
104585
104928
  openWorldHint: true
104586
104929
  },
104587
104930
  handler: async (params) => {
104588
- const { locale, page_size, cursor } = params;
104931
+ const { locale, page_size, cursor, brand_id } = params;
104589
104932
  const token = await getToken();
104933
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104590
104934
  const path = `${localePrefix(locale)}/categories`;
104591
- const response = await helpCenterGet(subdomain, token, path, buildCursorParams(page_size, cursor));
104935
+ const response = await helpCenterGet(hcSubdomain, token, path, buildCursorParams(page_size, cursor));
104592
104936
  const categories = response.categories ?? [];
104593
104937
  return { content: [{
104594
104938
  type: "text",
@@ -104606,7 +104950,8 @@ const createHelpCenterTools = (ctx) => {
104606
104950
  category_id: number$2().int().optional().describe("Restrict to sections of this category (id from list_categories). Omit to list every section."),
104607
104951
  locale: string$2().optional().describe("Locale for section names (e.g., \"en-us\", \"fr\"). Defaults to the Help Center default locale."),
104608
104952
  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.")
104953
+ cursor: string$2().optional().describe("Pagination cursor from a previous response; omit for the first page."),
104954
+ ...brandIdPart
104610
104955
  }),
104611
104956
  annotations: {
104612
104957
  readOnlyHint: true,
@@ -104615,9 +104960,10 @@ const createHelpCenterTools = (ctx) => {
104615
104960
  openWorldHint: true
104616
104961
  },
104617
104962
  handler: async (params) => {
104618
- const { category_id, locale, page_size, cursor } = params;
104963
+ const { category_id, locale, page_size, cursor, brand_id } = params;
104619
104964
  const token = await getToken();
104620
- const response = await helpCenterGet(subdomain, token, sectionListPath(category_id, locale), buildCursorParams(page_size, cursor));
104965
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104966
+ const response = await helpCenterGet(hcSubdomain, token, sectionListPath(category_id, locale), buildCursorParams(page_size, cursor));
104621
104967
  const sections = response.sections ?? [];
104622
104968
  return { content: [{
104623
104969
  type: "text",
@@ -104643,7 +104989,8 @@ const createHelpCenterTools = (ctx) => {
104643
104989
  "title"
104644
104990
  ]).default("position").describe("Field to sort by; \"position\" (the default) is the manual order set in Guide."),
104645
104991
  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)")
104992
+ include_translations: boolean$1().default(false).describe("Include available translation locales per article (causes 1 extra API call per article)"),
104993
+ ...brandIdPart
104647
104994
  }),
104648
104995
  annotations: {
104649
104996
  readOnlyHint: true,
@@ -104652,9 +104999,10 @@ const createHelpCenterTools = (ctx) => {
104652
104999
  openWorldHint: true
104653
105000
  },
104654
105001
  handler: async (params) => {
104655
- const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations } = params;
105002
+ const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations, brand_id } = params;
104656
105003
  const token = await getToken();
104657
- const response = await helpCenterGet(subdomain, token, articleListPath(section_id, locale), {
105004
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105005
+ const response = await helpCenterGet(hcSubdomain, token, articleListPath(section_id, locale), {
104658
105006
  ...buildCursorParams(page_size, cursor),
104659
105007
  sort_by,
104660
105008
  sort_order
@@ -104665,7 +105013,7 @@ const createHelpCenterTools = (ctx) => {
104665
105013
  text: formatList(articles, formatArticleSummary, extractPaginationMeta(response, articles.length))
104666
105014
  }] };
104667
105015
  const formatted = await Promise.all(articles.map(async (article) => {
104668
- const locales = (await listTranslations(subdomain, token, article.id)).map((t) => t.locale).join(", ");
105016
+ const locales = (await listTranslations(hcSubdomain, token, article.id)).map((t) => t.locale).join(", ");
104669
105017
  return `${formatArticleSummary(article)}\n- **Translations**: ${locales}`;
104670
105018
  }));
104671
105019
  const meta = extractPaginationMeta(response, articles.length);
@@ -104682,23 +105030,25 @@ const createHelpCenterTools = (ctx) => {
104682
105030
  readOnly: true,
104683
105031
  title: "List Promoted Help Center Articles",
104684
105032
  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({}),
105033
+ inputSchema: object({ ...brandIdPart }),
104686
105034
  annotations: {
104687
105035
  readOnlyHint: true,
104688
105036
  destructiveHint: false,
104689
105037
  idempotentHint: true,
104690
105038
  openWorldHint: true
104691
105039
  },
104692
- handler: async () => {
105040
+ handler: async (params) => {
105041
+ const { brand_id } = params;
104693
105042
  const token = await getToken();
104694
- const { articles, truncated, pagesScanned } = await fetchPromotedArticles(subdomain, token);
105043
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105044
+ const { articles, truncated, pagesScanned } = await fetchPromotedArticles(hcSubdomain, token, ARTICLE_RESOURCES_SCAN_MAX_PAGES);
104695
105045
  const header = `Promoted (featured) articles: ${articles.length}`;
104696
105046
  const body = articles.length ? articles.map(formatArticleSummary).join("\n\n") : "_No promoted articles found._";
104697
105047
  const cost = `${pagesScanned} Zendesk API request${pagesScanned === 1 ? "" : "s"}`;
104698
105048
  const note = scanCostNote(truncated, pagesScanned, cost);
104699
105049
  return { content: [{
104700
105050
  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.")
105051
+ 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
105052
  }] };
104703
105053
  }
104704
105054
  },
@@ -104708,7 +105058,10 @@ const createHelpCenterTools = (ctx) => {
104708
105058
  readOnly: true,
104709
105059
  title: "List Article Translations",
104710
105060
  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) }),
105061
+ inputSchema: object({
105062
+ article_id: number$2().int().describe(ARTICLE_ID_DESC),
105063
+ ...brandIdPart
105064
+ }),
104712
105065
  annotations: {
104713
105066
  readOnlyHint: true,
104714
105067
  destructiveHint: false,
@@ -104716,9 +105069,10 @@ const createHelpCenterTools = (ctx) => {
104716
105069
  openWorldHint: true
104717
105070
  },
104718
105071
  handler: async (params) => {
104719
- const { article_id } = params;
105072
+ const { article_id, brand_id } = params;
104720
105073
  const token = await getToken();
104721
- const translations = await listTranslations(subdomain, token, article_id);
105074
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105075
+ const translations = await listTranslations(hcSubdomain, token, article_id);
104722
105076
  return { content: [{
104723
105077
  type: "text",
104724
105078
  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 +105090,8 @@ const createHelpCenterTools = (ctx) => {
104736
105090
  locale: string$2().describe("Target locale (e.g., \"fr\", \"de\")"),
104737
105091
  title: string$2().min(1).describe("Translated article title."),
104738
105092
  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).")
105093
+ draft: boolean$1().default(false).describe("Create the translation as a draft (not visible to end users). Defaults to false (published)."),
105094
+ ...brandIdPart
104740
105095
  }),
104741
105096
  annotations: {
104742
105097
  readOnlyHint: false,
@@ -104745,9 +105100,10 @@ const createHelpCenterTools = (ctx) => {
104745
105100
  openWorldHint: true
104746
105101
  },
104747
105102
  handler: async (params) => {
104748
- const { article_id, locale, title, body, draft } = params;
105103
+ const { article_id, locale, title, body, draft, brand_id } = params;
104749
105104
  const token = await getToken();
104750
- const { translation } = await helpCenterPost(subdomain, token, `/articles/${article_id}/translations`, { translation: {
105105
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105106
+ const { translation } = await helpCenterPost(hcSubdomain, token, `/articles/${article_id}/translations`, { translation: {
104751
105107
  locale,
104752
105108
  title,
104753
105109
  body,
@@ -104770,7 +105126,8 @@ const createHelpCenterTools = (ctx) => {
104770
105126
  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
105127
  title: string$2().optional().describe("New title for this locale. Omit to leave the current title unchanged."),
104772
105128
  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.")
105129
+ draft: boolean$1().optional().describe("When true, keeps this translation as a draft; when false, publishes it."),
105130
+ ...brandIdPart
104774
105131
  }),
104775
105132
  annotations: {
104776
105133
  readOnlyHint: false,
@@ -104779,9 +105136,10 @@ const createHelpCenterTools = (ctx) => {
104779
105136
  openWorldHint: true
104780
105137
  },
104781
105138
  handler: async (params) => {
104782
- const { article_id, locale, ...updates } = params;
105139
+ const { article_id, locale, brand_id, ...updates } = params;
104783
105140
  const token = await getToken();
104784
- const { translation } = await helpCenterPut(subdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: updates });
105141
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105142
+ const { translation } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: updates });
104785
105143
  return { content: [{
104786
105144
  type: "text",
104787
105145
  text: `Translation updated for article #${article_id} in "${locale}".\n\n${formatTranslation(translation)}`
@@ -104794,7 +105152,10 @@ const createHelpCenterTools = (ctx) => {
104794
105152
  readOnly: true,
104795
105153
  title: "List Section Translations",
104796
105154
  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.") }),
105155
+ inputSchema: object({
105156
+ 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."),
105157
+ ...brandIdPart
105158
+ }),
104798
105159
  annotations: {
104799
105160
  readOnlyHint: true,
104800
105161
  destructiveHint: false,
@@ -104802,9 +105163,10 @@ const createHelpCenterTools = (ctx) => {
104802
105163
  openWorldHint: true
104803
105164
  },
104804
105165
  handler: async (params) => {
104805
- const { section_id } = params;
105166
+ const { section_id, brand_id } = params;
104806
105167
  const token = await getToken();
104807
- const translations = await listNodeTranslations(subdomain, token, "sections", section_id);
105168
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105169
+ const translations = await listNodeTranslations(hcSubdomain, token, "sections", section_id, void 0);
104808
105170
  return { content: [{
104809
105171
  type: "text",
104810
105172
  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 +105179,10 @@ const createHelpCenterTools = (ctx) => {
104817
105179
  readOnly: true,
104818
105180
  title: "List Category Translations",
104819
105181
  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.") }),
105182
+ inputSchema: object({
105183
+ 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."),
105184
+ ...brandIdPart
105185
+ }),
104821
105186
  annotations: {
104822
105187
  readOnlyHint: true,
104823
105188
  destructiveHint: false,
@@ -104825,9 +105190,10 @@ const createHelpCenterTools = (ctx) => {
104825
105190
  openWorldHint: true
104826
105191
  },
104827
105192
  handler: async (params) => {
104828
- const { category_id } = params;
105193
+ const { category_id, brand_id } = params;
104829
105194
  const token = await getToken();
104830
- const translations = await listNodeTranslations(subdomain, token, "categories", category_id);
105195
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105196
+ const translations = await listNodeTranslations(hcSubdomain, token, "categories", category_id, void 0);
104831
105197
  return { content: [{
104832
105198
  type: "text",
104833
105199
  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 +105208,8 @@ const createHelpCenterTools = (ctx) => {
104842
105208
  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
105209
  inputSchema: object({
104844
105210
  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.")
105211
+ 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."),
105212
+ ...brandIdPart
104846
105213
  }),
104847
105214
  annotations: {
104848
105215
  readOnlyHint: true,
@@ -104851,12 +105218,13 @@ const createHelpCenterTools = (ctx) => {
104851
105218
  openWorldHint: true
104852
105219
  },
104853
105220
  handler: async (params) => {
104854
- const { locale, category_id } = params;
105221
+ const { locale, category_id, brand_id } = params;
104855
105222
  const token = await getToken();
105223
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
104856
105224
  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), {
105225
+ helpCenterGet(hcSubdomain, token, "/locales", void 0),
105226
+ fetchGapCategories(hcSubdomain, token, category_id),
105227
+ helpCenterGet(hcSubdomain, token, sectionListPath(category_id, void 0), {
104860
105228
  ...buildCursorParams(100, void 0),
104861
105229
  include: TRANSLATIONS_SIDELOAD
104862
105230
  })
@@ -104899,7 +105267,8 @@ const createHelpCenterTools = (ctx) => {
104899
105267
  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
105268
  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
105269
  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.")
105270
+ 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."),
105271
+ ...brandIdPart
104903
105272
  }),
104904
105273
  annotations: {
104905
105274
  readOnlyHint: false,
@@ -104908,9 +105277,10 @@ const createHelpCenterTools = (ctx) => {
104908
105277
  openWorldHint: true
104909
105278
  },
104910
105279
  handler: async (params) => {
104911
- const { section_id, ...input } = params;
105280
+ const { section_id, brand_id, ...input } = params;
104912
105281
  const token = await getToken();
104913
- const { translation, created } = await upsertNodeTranslation(subdomain, token, "sections", section_id, input);
105282
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105283
+ const { translation, created } = await upsertNodeTranslation(hcSubdomain, token, "sections", section_id, input);
104914
105284
  return { content: [{
104915
105285
  type: "text",
104916
105286
  text: nodeTranslationWriteText("sections", section_id, translation, created)
@@ -104928,7 +105298,8 @@ const createHelpCenterTools = (ctx) => {
104928
105298
  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
105299
  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
105300
  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.")
105301
+ 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."),
105302
+ ...brandIdPart
104932
105303
  }),
104933
105304
  annotations: {
104934
105305
  readOnlyHint: false,
@@ -104937,9 +105308,10 @@ const createHelpCenterTools = (ctx) => {
104937
105308
  openWorldHint: true
104938
105309
  },
104939
105310
  handler: async (params) => {
104940
- const { category_id, ...input } = params;
105311
+ const { category_id, brand_id, ...input } = params;
104941
105312
  const token = await getToken();
104942
- const { translation, created } = await upsertNodeTranslation(subdomain, token, "categories", category_id, input);
105313
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105314
+ const { translation, created } = await upsertNodeTranslation(hcSubdomain, token, "categories", category_id, input);
104943
105315
  return { content: [{
104944
105316
  type: "text",
104945
105317
  text: nodeTranslationWriteText("categories", category_id, translation, created)
@@ -104991,7 +105363,8 @@ const createHelpCenterTools = (ctx) => {
104991
105363
  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
105364
  draft: boolean$1().default(true).describe("When true (default), the article is created unpublished; set false to publish immediately."),
104993
105365
  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)")
105366
+ label_names: array(string$2()).optional().describe("Label names for search ranking (use list_labels to see existing labels)"),
105367
+ ...brandIdPart
104995
105368
  }),
104996
105369
  annotations: {
104997
105370
  readOnlyHint: false,
@@ -105000,9 +105373,10 @@ const createHelpCenterTools = (ctx) => {
105000
105373
  openWorldHint: true
105001
105374
  },
105002
105375
  handler: async (params) => {
105003
- const { section_id, ...articleData } = params;
105376
+ const { section_id, brand_id, ...articleData } = params;
105004
105377
  const token = await getToken();
105005
- const { article } = await helpCenterPost(subdomain, token, `/sections/${section_id}/articles`, { article: articleData });
105378
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105379
+ const { article } = await helpCenterPost(hcSubdomain, token, `/sections/${section_id}/articles`, { article: articleData });
105006
105380
  return { content: [{
105007
105381
  type: "text",
105008
105382
  text: `Article #${article.id} created.\n\n${formatArticle(article)}`
@@ -105025,7 +105399,8 @@ const createHelpCenterTools = (ctx) => {
105025
105399
  author_id: number$2().int().optional().describe("User id of the article author (from search_users)."),
105026
105400
  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
105401
  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.")
105402
+ 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."),
105403
+ ...brandIdPart
105029
105404
  }),
105030
105405
  annotations: {
105031
105406
  readOnlyHint: false,
@@ -105034,9 +105409,10 @@ const createHelpCenterTools = (ctx) => {
105034
105409
  openWorldHint: true
105035
105410
  },
105036
105411
  handler: async (params) => {
105037
- const { article_id, ...updates } = params;
105412
+ const { article_id, brand_id, ...updates } = params;
105038
105413
  const token = await getToken();
105039
- const { article } = await helpCenterPut(subdomain, token, `/articles/${article_id}`, { article: updates });
105414
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105415
+ const { article } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}`, { article: updates });
105040
105416
  return { content: [{
105041
105417
  type: "text",
105042
105418
  text: `Article #${article.id} updated.\n\n${formatArticle(article)}`
@@ -105059,7 +105435,8 @@ const createHelpCenterTools = (ctx) => {
105059
105435
  ]).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
105436
  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
105437
  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.")
105438
+ 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."),
105439
+ ...brandIdPart
105063
105440
  }),
105064
105441
  annotations: {
105065
105442
  readOnlyHint: false,
@@ -105068,15 +105445,16 @@ const createHelpCenterTools = (ctx) => {
105068
105445
  openWorldHint: true
105069
105446
  },
105070
105447
  handler: async (params) => {
105071
- const { article_id, target, reference_article_id, normalize = false, confirm = false } = params;
105448
+ const { article_id, target, reference_article_id, normalize = false, confirm = false, brand_id } = params;
105072
105449
  assertReorderParamsCoherent(article_id, target, reference_article_id);
105073
105450
  const needsReference = needsReferenceArticle(target);
105074
105451
  const token = await getToken();
105075
- const { article } = await helpCenterGet(subdomain, token, `/articles/${article_id}`);
105452
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105453
+ const { article } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}`, void 0);
105076
105454
  const sectionId = article.section_id;
105077
105455
  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);
105456
+ const effective = await fetchSectionOrder(sectionId, locale, token, hcSubdomain);
105457
+ if (reference_article_id !== void 0) await assertReferenceInSection(effective, reference_article_id, article_id, sectionId, token, hcSubdomain);
105080
105458
  const targetLabel = needsReference ? `${target} article #${reference_article_id}` : target;
105081
105459
  const desired = arrangeDesiredOrder(effective, article_id, target, reference_article_id);
105082
105460
  const writes = computePositionWrites(desired, article_id, normalize);
@@ -105092,8 +105470,8 @@ const createHelpCenterTools = (ctx) => {
105092
105470
  type: "text",
105093
105471
  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
105472
  }] };
105095
- const applied = await applyPositionWrites(writes, article_id, token);
105096
- const after = await fetchSectionOrder(sectionId, locale, token);
105473
+ const applied = await applyPositionWrites(writes, article_id, token, hcSubdomain);
105474
+ const after = await fetchSectionOrder(sectionId, locale, token, hcSubdomain);
105097
105475
  if (!isPlacedAsRequested(after, article_id, target, reference_article_id)) return { content: [{
105098
105476
  type: "text",
105099
105477
  text: autoSortNotice(sectionId, applied)
@@ -105112,7 +105490,8 @@ const createHelpCenterTools = (ctx) => {
105112
105490
  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
105491
  inputSchema: object({
105114
105492
  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.")
105493
+ 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."),
105494
+ ...brandIdPart
105116
105495
  }),
105117
105496
  annotations: {
105118
105497
  readOnlyHint: false,
@@ -105121,10 +105500,11 @@ const createHelpCenterTools = (ctx) => {
105121
105500
  openWorldHint: true
105122
105501
  },
105123
105502
  handler: async (params) => {
105124
- const { article_id, confirm } = params;
105503
+ const { article_id, confirm, brand_id } = params;
105125
105504
  if (confirm !== true) throw new Error("Archiving is guarded: pass confirm: true to archive (soft-delete) this article. No changes were made.");
105126
105505
  const token = await getToken();
105127
- await helpCenterDelete(subdomain, token, `/articles/${article_id}`);
105506
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105507
+ await helpCenterDelete(hcSubdomain, token, `/articles/${article_id}`);
105128
105508
  return { content: [{
105129
105509
  type: "text",
105130
105510
  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 +105576,21 @@ const createHelpCenterTools = (ctx) => {
105196
105576
  readOnly: true,
105197
105577
  title: "List Article Labels",
105198
105578
  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({}),
105579
+ inputSchema: object({ ...brandIdPart }),
105200
105580
  annotations: {
105201
105581
  readOnlyHint: true,
105202
105582
  destructiveHint: false,
105203
105583
  idempotentHint: true,
105204
105584
  openWorldHint: true
105205
105585
  },
105206
- handler: async () => {
105586
+ handler: async (params) => {
105587
+ const { brand_id } = params;
105207
105588
  const token = await getToken();
105208
- const response = await helpCenterGet(subdomain, token, "/articles/labels");
105589
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105590
+ const response = await helpCenterGet(hcSubdomain, token, "/articles/labels", void 0);
105209
105591
  return { content: [{
105210
105592
  type: "text",
105211
- text: formatList(response.labels ?? [], formatLabel, void 0, "list_labels takes no parameters, so this listing cannot be narrowed from the call.")
105593
+ text: formatList(response.labels ?? [], formatLabel, void 0, "list_labels takes no filter parameters, so this listing cannot be narrowed from the call.")
105212
105594
  }] };
105213
105595
  }
105214
105596
  },
@@ -105229,7 +105611,7 @@ const createHelpCenterTools = (ctx) => {
105229
105611
  const token = await getToken();
105230
105612
  let response;
105231
105613
  try {
105232
- response = await helpCenterGet(subdomain, token, "/user_segments");
105614
+ response = await helpCenterGet(subdomain, token, "/user_segments", void 0);
105233
105615
  } catch (error) {
105234
105616
  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
105617
  throw error;
@@ -105246,7 +105628,10 @@ const createHelpCenterTools = (ctx) => {
105246
105628
  readOnly: true,
105247
105629
  title: "List Article Attachments",
105248
105630
  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.") }),
105631
+ inputSchema: object({
105632
+ article_id: number$2().int().describe("ID of the Help Center article whose attachments to list."),
105633
+ ...brandIdPart
105634
+ }),
105250
105635
  annotations: {
105251
105636
  readOnlyHint: true,
105252
105637
  destructiveHint: false,
@@ -105254,9 +105639,10 @@ const createHelpCenterTools = (ctx) => {
105254
105639
  openWorldHint: true
105255
105640
  },
105256
105641
  handler: async (params) => {
105257
- const { article_id } = params;
105642
+ const { article_id, brand_id } = params;
105258
105643
  const token = await getToken();
105259
- const attachments = (await helpCenterGet(subdomain, token, `/articles/${article_id}/attachments`)).article_attachments ?? [];
105644
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105645
+ const attachments = (await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/attachments`)).article_attachments ?? [];
105260
105646
  if (attachments.length === 0) return { content: [{
105261
105647
  type: "text",
105262
105648
  text: `No attachments found on article #${article_id}.`
@@ -105275,7 +105661,8 @@ const createHelpCenterTools = (ctx) => {
105275
105661
  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
105662
  inputSchema: object({
105277
105663
  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)")
105664
+ locale: string$2().optional().describe("Locale of the body to outline (defaults to article source_locale)"),
105665
+ ...brandIdPart
105279
105666
  }),
105280
105667
  annotations: {
105281
105668
  readOnlyHint: true,
@@ -105284,12 +105671,13 @@ const createHelpCenterTools = (ctx) => {
105284
105671
  openWorldHint: true
105285
105672
  },
105286
105673
  handler: async (params) => {
105287
- const { article_id, locale } = params;
105674
+ const { article_id, locale, brand_id } = params;
105288
105675
  const token = await getToken();
105289
- const { article } = await helpCenterGet(subdomain, token, `/articles/${article_id}`);
105676
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105677
+ const { article } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}`, void 0);
105290
105678
  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);
105679
+ const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${effectiveLocale}`, void 0);
105680
+ const translations = await listTranslations(hcSubdomain, token, article_id);
105293
105681
  const sections = parseSections(translation.body);
105294
105682
  const outlineLines = sections.length ? sections.map((s) => `- [${s.index}] ${s.headingTag ? `${s.headingTag}: ` : ""}${s.heading} (${s.wordCount} words)`).join("\n") : "_(no sections detected)_";
105295
105683
  const translationsList = translations.map((t) => `- ${t.locale}${t.outdated ? " (outdated)" : ""}`).join("\n");
@@ -105318,7 +105706,8 @@ const createHelpCenterTools = (ctx) => {
105318
105706
  article_id: number$2().int().describe(ARTICLE_ID_DESC),
105319
105707
  locale: string$2().describe("Locale of the body (e.g., \"en-us\", \"fr\")"),
105320
105708
  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.")
105709
+ 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."),
105710
+ ...brandIdPart
105322
105711
  }),
105323
105712
  annotations: {
105324
105713
  readOnlyHint: true,
@@ -105327,9 +105716,10 @@ const createHelpCenterTools = (ctx) => {
105327
105716
  openWorldHint: true
105328
105717
  },
105329
105718
  handler: async (params) => {
105330
- const { article_id, locale, section_index, format } = params;
105719
+ const { article_id, locale, section_index, format, brand_id } = params;
105331
105720
  const token = await getToken();
105332
- const { translation } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${locale}`);
105721
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105722
+ const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, void 0);
105333
105723
  const sections = parseSections(translation.body);
105334
105724
  const section = sections[section_index];
105335
105725
  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 +105747,8 @@ const createHelpCenterTools = (ctx) => {
105357
105747
  locale: string$2().describe("Locale of the translation to update"),
105358
105748
  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
105749
  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.")
105750
+ 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."),
105751
+ ...brandIdPart
105361
105752
  }),
105362
105753
  annotations: {
105363
105754
  readOnlyHint: false,
@@ -105366,12 +105757,13 @@ const createHelpCenterTools = (ctx) => {
105366
105757
  openWorldHint: true
105367
105758
  },
105368
105759
  handler: async (params) => {
105369
- const { article_id, locale, section_index, content, format } = params;
105760
+ const { article_id, locale, section_index, content, format, brand_id } = params;
105370
105761
  const token = await getToken();
105371
- const { translation } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${locale}`);
105762
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105763
+ const { translation } = await helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, void 0);
105372
105764
  const newSectionHtml = format === "markdown" ? markdownToHtml(content) : content;
105373
105765
  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 } });
105766
+ const { translation: updated } = await helpCenterPut(hcSubdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: { body: newBody } });
105375
105767
  const updatedSections = parseSections(updated.body);
105376
105768
  const updatedSection = updatedSections[section_index];
105377
105769
  const emptied = introLost === "empty";
@@ -105402,7 +105794,8 @@ const createHelpCenterTools = (ctx) => {
105402
105794
  inputSchema: object({
105403
105795
  article_id: number$2().int().describe(ARTICLE_ID_DESC),
105404
105796
  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")
105797
+ target_locale: string$2().describe("Target locale to compare against source"),
105798
+ ...brandIdPart
105406
105799
  }),
105407
105800
  annotations: {
105408
105801
  readOnlyHint: true,
@@ -105411,12 +105804,13 @@ const createHelpCenterTools = (ctx) => {
105411
105804
  openWorldHint: true
105412
105805
  },
105413
105806
  handler: async (params) => {
105414
- const { article_id, source_locale, target_locale } = params;
105807
+ const { article_id, source_locale, target_locale, brand_id } = params;
105415
105808
  const token = await getToken();
105809
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105416
105810
  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)
105811
+ helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${source_locale}`, void 0),
105812
+ helpCenterGet(hcSubdomain, token, `/articles/${article_id}/translations/${target_locale}`, void 0),
105813
+ listTranslations(hcSubdomain, token, article_id)
105420
105814
  ]);
105421
105815
  const sourceSections = parseSections(sourceRes.translation.body);
105422
105816
  const targetSections = parseSections(targetRes.translation.body);
@@ -105455,7 +105849,8 @@ const createHelpCenterTools = (ctx) => {
105455
105849
  abort: true,
105456
105850
  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
105851
  }).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.")
105852
+ 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."),
105853
+ ...brandIdPart
105459
105854
  }),
105460
105855
  annotations: {
105461
105856
  readOnlyHint: false,
@@ -105464,13 +105859,14 @@ const createHelpCenterTools = (ctx) => {
105464
105859
  openWorldHint: true
105465
105860
  },
105466
105861
  handler: async (params) => {
105467
- const { article_id, file_name, file_base64, content_type } = params;
105862
+ const { article_id, file_name, file_base64, content_type, brand_id } = params;
105468
105863
  const token = await getToken();
105864
+ const hcSubdomain = await resolveHcSubdomain(brand_id);
105469
105865
  const buffer = Buffer.from(file_base64, "base64");
105470
105866
  const blob = new Blob([buffer], { type: content_type });
105471
105867
  const formData = new FormData();
105472
105868
  formData.append("file", blob, file_name);
105473
- const { article_attachment } = await helpCenterUpload(subdomain, token, `/articles/${article_id}/attachments`, formData);
105869
+ const { article_attachment } = await helpCenterUpload(hcSubdomain, token, `/articles/${article_id}/attachments`, formData);
105474
105870
  return { content: [{
105475
105871
  type: "text",
105476
105872
  text: `Attachment created for article #${article_id}.\n\n${formatAttachment(article_attachment)}`
@@ -107445,6 +107841,31 @@ const createServerShell = (config, logger = silentLogger) => {
107445
107841
  return server;
107446
107842
  };
107447
107843
  /**
107844
+ * Lazily resolve the Help Center subdomain the topology resource pins to: the
107845
+ * FIRST allowed brand when a list is set (named in the header), else undefined
107846
+ * (the account default). Uses the SHARED resolver so the topology and article
107847
+ * resources hit the same cached brand list the tools use; it only fires when a
107848
+ * brand list is set. Providers await it per read (cheap after the first), since
107849
+ * registerToolset is synchronous and cannot resolve up front.
107850
+ */
107851
+ const firstBrandSubdomainResolver = (config, resolveBrandSubdomain) => {
107852
+ const first = config.brandIds?.[0];
107853
+ if (first === void 0 || first === "all") return () => Promise.resolve(void 0);
107854
+ return () => resolveBrandSubdomain(first);
107855
+ };
107856
+ /**
107857
+ * Per-call brand guard for the article resource URIs in multi/all mode: the
107858
+ * {brand} slot of `<scheme>://brands/{brand}/articles/{id}` is resolved and
107859
+ * allow-list-checked EXACTLY like a tool call's `brand_id` (shared resolver,
107860
+ * same resolveBrand rules), returning the brand's subdomain for the Help
107861
+ * Center fetch. Unset/single modes never reach here — their URIs carry no
107862
+ * brand segment.
107863
+ */
107864
+ const resourceBrandResolver = (config, resolveBrandSubdomain) => {
107865
+ const singleLock = config.brandIds?.length === 1 && config.brandIds[0] !== "all";
107866
+ return (idOrSubdomain) => singleLock ? resolveBrandSubdomain(idOrSubdomain) : resolveBrand(idOrSubdomain, config.brandIds, resolveBrandSubdomain).then((sub) => sub ?? config.subdomain);
107867
+ };
107868
+ /**
107448
107869
  * Registers one generation of the toolset (mode/filters applied) plus the
107449
107870
  * optional topology resource onto an existing server, and returns a handle
107450
107871
  * whose `dispose()` removes exactly what this call added. When the SDK server
@@ -107454,7 +107875,8 @@ const createServerShell = (config, logger = silentLogger) => {
107454
107875
  * without dropping the transport. `tools` is passed in (not built here) so the
107455
107876
  * reload path can hand over freshly re-imported definitions.
107456
107877
  */
107457
- const registerToolset = (server, { config, getToken, onUnauthorized, logger = silentLogger }, tools) => {
107878
+ const registerToolset = (server, { config, getToken, resolveBrandSubdomain, onUnauthorized, logger = silentLogger }, tools) => {
107879
+ const sharedResolver = resolveBrandSubdomain ?? createBrandSubdomainResolver(config.subdomain, getToken);
107458
107880
  const registered = [];
107459
107881
  const dispose = () => {
107460
107882
  for (const handle of registered) handle.remove();
@@ -107462,8 +107884,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107462
107884
  const filteredTools = filterTools(tools, {
107463
107885
  readOnly: config.readOnly,
107464
107886
  namespaces: config.namespaces,
107465
- tools: config.tools,
107466
- promotedArticles: config.promotedArticles
107887
+ tools: config.tools
107467
107888
  });
107468
107889
  try {
107469
107890
  switch (config.mode) {
@@ -107492,7 +107913,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107492
107913
  }
107493
107914
  }
107494
107915
  if (helpCenterContextEnabled(config)) {
107495
- const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized);
107916
+ const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized, config.brandIds, firstBrandSubdomainResolver(config, sharedResolver));
107496
107917
  registered.push(server.registerResource("help-center-topology", topologyResourceUri(config), {
107497
107918
  title: "Zendesk Help Center topology",
107498
107919
  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 +107925,7 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107504
107925
  }] })));
107505
107926
  }
107506
107927
  if (articleResourceEnabled(config)) {
107507
- const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized);
107928
+ const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized, resourceBrandResolver(config, sharedResolver), config.brandIds);
107508
107929
  const listPromotedEnabled = promotedArticlesEnabled(config);
107509
107930
  const template = new ResourceTemplate(articleResourceUriTemplate(config), { list: async () => {
107510
107931
  if (!listPromotedEnabled) return { resources: [] };
@@ -107515,10 +107936,10 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107515
107936
  listed: refs.length
107516
107937
  });
107517
107938
  return { resources: refs.map((ref) => ({
107518
- uri: articleResourceUri(config, ref.id),
107519
- name: ref.title,
107939
+ uri: articleResourceUri(config, ref.id, ref.brand),
107940
+ name: ref.brand === void 0 ? ref.title : `${ref.title} (${ref.brand})`,
107520
107941
  title: ref.title,
107521
- description: `"${ref.title}" (article ${ref.id}) — promoted Help Center article, as Markdown.`,
107942
+ 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
107943
  mimeType: "text/markdown"
107523
107944
  })) };
107524
107945
  } catch (err) {
@@ -107528,16 +107949,18 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107528
107949
  } });
107529
107950
  registered.push(server.registerResource("help-center-article", template, {
107530
107951
  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.",
107952
+ 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
107953
  mimeType: "text/markdown"
107533
107954
  }, async (uri, variables) => {
107534
- const raw = Array.isArray(variables["id"]) ? variables["id"][0] : variables["id"];
107535
- const id = Number(raw);
107955
+ const rawId = Array.isArray(variables["id"]) ? variables["id"][0] : variables["id"];
107956
+ const id = Number(rawId);
107536
107957
  if (!Number.isSafeInteger(id) || id <= 0) throw new Error(`Invalid article id in resource URI: ${uri.toString()}`);
107958
+ const rawBrand = variables["brand"];
107959
+ const brand = Array.isArray(rawBrand) ? rawBrand[0] : rawBrand;
107537
107960
  return { contents: [{
107538
107961
  uri: uri.toString(),
107539
107962
  mimeType: "text/markdown",
107540
- text: await articles.readArticle(id)
107963
+ text: await articles.readArticle(id, brand)
107541
107964
  }] };
107542
107965
  }));
107543
107966
  }
@@ -107556,13 +107979,17 @@ const registerToolset = (server, { config, getToken, onUnauthorized, logger = si
107556
107979
  };
107557
107980
  const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized) => {
107558
107981
  const server = createServerShell(config, logger);
107982
+ const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
107559
107983
  const tools = createAllTools({
107560
107984
  subdomain: config.subdomain,
107985
+ brandIds: config.brandIds,
107986
+ resolveBrandSubdomain,
107561
107987
  getToken
107562
107988
  });
107563
107989
  registerToolset(server, {
107564
107990
  config,
107565
107991
  getToken,
107992
+ resolveBrandSubdomain,
107566
107993
  onUnauthorized,
107567
107994
  logger
107568
107995
  }, tools);
@@ -107752,13 +108179,17 @@ const loadFreshTools = async (ctx) => {
107752
108179
  */
107753
108180
  const createReloadableServer = (config, getToken, logger = silentLogger, onUnauthorized, loadTools = loadFreshTools) => {
107754
108181
  const server = createServerShell(config, logger);
108182
+ const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
107755
108183
  const ctx = {
107756
108184
  subdomain: config.subdomain,
108185
+ brandIds: config.brandIds,
108186
+ resolveBrandSubdomain,
107757
108187
  getToken
107758
108188
  };
107759
108189
  const params = {
107760
108190
  config,
107761
108191
  getToken,
108192
+ resolveBrandSubdomain,
107762
108193
  onUnauthorized,
107763
108194
  logger
107764
108195
  };
@@ -107897,8 +108328,7 @@ const renderToolSurface = (config, tools) => {
107897
108328
  const filtered = filterTools(tools, {
107898
108329
  readOnly: config.readOnly,
107899
108330
  namespaces: config.namespaces,
107900
- tools: config.tools,
107901
- promotedArticles: config.promotedArticles
108331
+ tools: config.tools
107902
108332
  });
107903
108333
  const header = renderHeader(config);
107904
108334
  if (filtered.length === 0) return `${header}\n\nNo tools exposed. Check --namespace / --tool / --read-only.`;
@@ -107994,7 +108424,7 @@ const installShutdown = (options) => {
107994
108424
  //#endregion
107995
108425
  //#region src/index.ts
107996
108426
  const buildStdioTokenStore = async (config, logger) => {
107997
- const { createTokenStore } = await import("./token-store-SYK0DN9M.js");
108427
+ const { createTokenStore } = await import("./token-store-Aq7gP39o.js");
107998
108428
  return createTokenStore({
107999
108429
  subdomain: config.subdomain,
108000
108430
  oauthClientId: config.oauthClientId,
@@ -108013,6 +108443,8 @@ const main = async () => {
108013
108443
  if (config.printTools) {
108014
108444
  const tools = createAllTools({
108015
108445
  subdomain: config.subdomain,
108446
+ brandIds: config.brandIds,
108447
+ resolveBrandSubdomain: () => Promise.resolve(""),
108016
108448
  getToken: () => ""
108017
108449
  });
108018
108450
  console.log(renderToolSurface(config, tools));
@@ -108033,7 +108465,7 @@ const main = async () => {
108033
108465
  return;
108034
108466
  }
108035
108467
  if (config.dev) logger.warn("dev_mode_ignored_http");
108036
- const { startHttpTransport } = await import("./http-7NVoxfEA.js");
108468
+ const { startHttpTransport } = await import("./http-GIKJdf05.js");
108037
108469
  const http = await startHttpTransport(config, logger);
108038
108470
  installShutdown({
108039
108471
  watchStdin: false,
@@ -108047,4 +108479,4 @@ main().catch((error) => {
108047
108479
  process.exit(1);
108048
108480
  });
108049
108481
  //#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 };
108482
+ export { __toESM$1 as _, zendeskGet as a, DEFAULT_CALLBACK_PORT as c, createStartupError as d, errnoCode as f, __require as g, __exportAll$1 as h, WebStandardStreamableHTTPServerTransport as i, getOAuthUrls as l, __commonJSMin$1 as m, readPackageInfo as n, REQUEST_TIMEOUT_MS as o, readEnv as p, silentLogger as r, deadlineSignal as s, createMcpServer as t, STARTUP_DOCS as u };