@fruggr/zendesk-mcp-server 2.23.0 → 2.24.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.
Files changed (3) hide show
  1. package/README.md +4 -4
  2. package/dist/index.js +86 -32
  3. package/package.json +5 -3
package/README.md CHANGED
@@ -83,7 +83,7 @@ differently.
83
83
  - Two deployment shapes, same auth story. Run it on your laptop as a stdio MCP
84
84
  server, or deploy it as a private remote MCP server reached over HTTP, with one
85
85
  Zendesk session per request and each client carrying its own user's token.
86
- - A lean stack: the official `@modelcontextprotocol/sdk` plus `zod`, speaking to
86
+ - A lean stack: the official MCP TypeScript SDK (`@modelcontextprotocol/server`) plus `zod`, speaking to
87
87
  the Zendesk Support and Help Center (Guide) APIs.
88
88
 
89
89
  Look elsewhere when:
@@ -139,7 +139,7 @@ Signing in needs a Zendesk OAuth client, so register one first (next section).
139
139
  2. Create a **public** client:
140
140
  - **Identifier**: `<your-subdomain>_zendesk` (or set `ZENDESK_OAUTH_CLIENT_ID`)
141
141
  - **Redirect URL**: `http://localhost:27439/callback` (change the port to match
142
- `ZENDESK_OAUTH_CALLBACK_PORT` / `--callback-port` if you override it; Zendesk
142
+ `OAUTH_CALLBACK_PORT` / `--callback-port` if you override it; Zendesk
143
143
  accepts several redirect URLs, one per line)
144
144
 
145
145
  If the client restricts its **allowed scopes**, it needs `read` — plus `write`
@@ -151,7 +151,7 @@ window and returns the authorize URL in a tool message. The call does not block
151
151
  waiting for sign-in, so authenticate in the browser and then retry the request.
152
152
  The token is persisted to an owner-only file and reused across restarts, so you
153
153
  don't authenticate again every time your MCP client respawns the server (path
154
- and overrides: [`ZENDESK_TOKEN_FILE`](docs/configuration.md#zendesk_token_file)).
154
+ and overrides: [`OAUTH_TOKEN_FILE`](docs/configuration.md#oauth_token_file)).
155
155
  Several instances can run side by side — a read-write one and a `--read-only`
156
156
  one, say — without trading credentials.
157
157
 
@@ -326,7 +326,7 @@ to turn each piece off: **[docs/help-center-context.md](docs/help-center-context
326
326
 
327
327
  The complete reference for the CLI flags (`--mode`, `--namespace`,
328
328
  `--read-only`, `--transport`, `--public-url`, and so on) and the environment
329
- variables (`ZENDESK_SUBDOMAIN`, `ZENDESK_TOKEN_FILE`, `PUBLIC_URL`, the
329
+ variables (`ZENDESK_SUBDOMAIN`, `OAUTH_TOKEN_FILE`, `PUBLIC_URL`, the
330
330
  attachment-vision caps) lives in
331
331
  **[docs/configuration.md](docs/configuration.md)**. Every variable has its own
332
332
  anchor, so you can deep-link a specific setting.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { dirname, join } from "node:path";
9
9
  import { fileURLToPath, pathToFileURL } from "node:url";
10
10
  import { parseArgs } from "node:util";
11
11
  import * as z from "zod/v4";
12
- import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/server";
13
13
  import * as cheerio from "cheerio";
14
14
  import { toHtml } from "hast-util-to-html";
15
15
  import rehypeParse from "rehype-parse";
@@ -21,8 +21,8 @@ import remarkParse from "remark-parse";
21
21
  import remarkRehype from "remark-rehype";
22
22
  import remarkStringify from "remark-stringify";
23
23
  import { unified } from "unified";
24
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
25
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
24
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
25
+ import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
26
26
  //#region src/utils/logger.ts
27
27
  const SEVERITY = {
28
28
  debug: 0,
@@ -118,25 +118,74 @@ const createLogger = (level) => {
118
118
  };
119
119
  };
120
120
  //#endregion
121
+ //#region src/utils/env.ts
122
+ const LEGACY_NAMES = /* @__PURE__ */ new Map([
123
+ ["OAUTH_TOKEN_FILE", "ZENDESK_TOKEN_FILE"],
124
+ ["OAUTH_CALLBACK_PORT", "ZENDESK_OAUTH_CALLBACK_PORT"],
125
+ ["LISTEN_HOST", "HOST"],
126
+ ["RESPONSE_CHARACTER_LIMIT", "ZENDESK_CHARACTER_LIMIT"],
127
+ ["RESPONSE_MAX_BYTES", "ZENDESK_MAX_RESPONSE_BYTES"],
128
+ ["ATTACHMENT_MAX_BYTES", "ZENDESK_MAX_ATTACHMENT_BYTES"],
129
+ ["EMBEDDED_IMAGES_MAX", "ZENDESK_MAX_EMBEDDED_IMAGES"],
130
+ ["COMMENT_MAX_PAGES", "ZENDESK_MAX_COMMENT_PAGES"],
131
+ ["TICKET_FIELD_SCAN_MAX_PAGES", "ZENDESK_TICKET_FIELD_SCAN_MAX_PAGES"],
132
+ ["ARTICLE_RESOURCES_SCAN_MAX_PAGES", "ZENDESK_ARTICLE_RESOURCES_SCAN_MAX_PAGES"],
133
+ ["REORDER_CONFIRM_THRESHOLD", "ZENDESK_REORDER_CONFIRM_THRESHOLD"]
134
+ ]);
135
+ const warned = /* @__PURE__ */ new Set();
136
+ const warnDeprecated = (legacy, current) => {
137
+ if (warned.has(legacy)) return;
138
+ warned.add(legacy);
139
+ try {
140
+ console.error(`[zendesk-mcp] [warn] deprecated_env_var name=${legacy} replacement=${current} removal=3.0.0`);
141
+ } catch {}
142
+ };
143
+ /**
144
+ * Reads `name`, falling back to the legacy name it replaced. `name` in the result
145
+ * is the variable the value came from, so an error about it names what the
146
+ * operator actually set.
147
+ */
148
+ const readEnv = (name) => {
149
+ const value = process.env[name];
150
+ const legacy = LEGACY_NAMES.get(name);
151
+ if (legacy === void 0) return {
152
+ name,
153
+ value
154
+ };
155
+ const legacyValue = process.env[legacy];
156
+ if (legacyValue === void 0) return {
157
+ name,
158
+ value
159
+ };
160
+ warnDeprecated(legacy, name);
161
+ return value === void 0 ? {
162
+ name: legacy,
163
+ value: legacyValue
164
+ } : {
165
+ name,
166
+ value
167
+ };
168
+ };
169
+ //#endregion
121
170
  //#region src/constants.ts
122
171
  const positiveIntEnv = (name, fallback) => {
123
- const raw = process.env[name];
172
+ const raw = readEnv(name).value;
124
173
  if (raw === void 0 || raw.trim() === "") return fallback;
125
174
  const parsed = Number(raw);
126
175
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
127
176
  };
128
- const CHARACTER_LIMIT = positiveIntEnv("ZENDESK_CHARACTER_LIMIT", 25e3);
129
- const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ZENDESK_ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
130
- const MAX_ATTACHMENT_BYTES = positiveIntEnv("ZENDESK_MAX_ATTACHMENT_BYTES", 5242880);
131
- const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("ZENDESK_MAX_EMBEDDED_IMAGES", 10);
177
+ const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
178
+ const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
179
+ const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
180
+ const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
132
181
  const STDIO_MAX_MESSAGE_BYTES = 10485760;
133
182
  const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
134
- const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("ZENDESK_MAX_RESPONSE_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
183
+ const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
135
184
  const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
136
185
  const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
137
- const MAX_COMMENT_PAGES = positiveIntEnv("ZENDESK_MAX_COMMENT_PAGES", 10);
138
- const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("ZENDESK_TICKET_FIELD_SCAN_MAX_PAGES", 10);
139
- const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("ZENDESK_REORDER_CONFIRM_THRESHOLD", 20);
186
+ const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
187
+ const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
188
+ const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
140
189
  const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
141
190
  const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
142
191
  const getOAuthUrls = (subdomain) => ({
@@ -178,7 +227,7 @@ const supportedScopes = (readOnly) => scopeTokens(requestedScope(readOnly));
178
227
  * Whether a grant still covers what this process needs: a flat subset test.
179
228
  * Coverage, not equality, so a token Zendesk granted wider than requested stays
180
229
  * usable. A non-string `granted` is a pre-#283 record, i.e. `read write` — only
181
- * reachable now through a `ZENDESK_TOKEN_FILE` aimed at one by hand, since the
230
+ * reachable now through a `OAUTH_TOKEN_FILE` aimed at one by hand, since the
182
231
  * default layout no longer names those files. No scope hierarchy: granular
183
232
  * scopes (#284) replace this.
184
233
  */
@@ -207,7 +256,7 @@ const detectWsl = () => {
207
256
  * (`docs/decisions/token-file-keying.md`). The `(EADDRINUSE)` marker and `code`
208
257
  * are kept for diagnostics/tests.
209
258
  */
210
- const callbackPortInUseError = (port, cause) => Object.assign(/* @__PURE__ */ new Error(`Cannot start the Zendesk OAuth sign-in: local callback port ${port} is already in use. Another instance of this server is most likely signing in right now: finish that browser window, then retry. If an unrelated program holds the port, set ZENDESK_OAUTH_CALLBACK_PORT (or --callback-port) to a free port, then register http://localhost:<port>/callback as a redirect URL in your Zendesk OAuth client. (EADDRINUSE)`), {
259
+ const callbackPortInUseError = (port, cause) => Object.assign(/* @__PURE__ */ new Error(`Cannot start the Zendesk OAuth sign-in: local callback port ${port} is already in use. Another instance of this server is most likely signing in right now: finish that browser window, then retry. If an unrelated program holds the port, set OAUTH_CALLBACK_PORT (or --callback-port) to a free port, then register http://localhost:<port>/callback as a redirect URL in your Zendesk OAuth client. (EADDRINUSE)`), {
211
260
  code: "EADDRINUSE",
212
261
  cause
213
262
  });
@@ -500,11 +549,12 @@ const keyDigest = (key) => createHash("sha256").update(JSON.stringify([
500
549
  * their own record instead of clobbering a shared file. Why that triple, and
501
550
  * why no migration from the old subdomain-only layout:
502
551
  * `docs/decisions/token-file-keying.md`.
503
- * `ZENDESK_TOKEN_FILE` overrides with an explicit path — the way to separate
552
+ * `OAUTH_TOKEN_FILE` overrides with an explicit path — the way to separate
504
553
  * two Zendesk accounts that share a subdomain, client and scope.
505
554
  */
506
555
  const resolveTokenPath = (key) => {
507
- const override = process.env["ZENDESK_TOKEN_FILE"];
556
+ const { name, value: override } = readEnv("OAUTH_TOKEN_FILE");
557
+ if (override === "") throw new Error(`Empty ${name}. Set it to a value, or unset it entirely.`);
508
558
  if (override) return override;
509
559
  const readable = [
510
560
  key.subdomain,
@@ -828,11 +878,15 @@ const parsePort = (raw, label) => {
828
878
  if (!DIGITS_ONLY.test(raw)) throw new Error(`Invalid ${label} value. Expected an integer 0-65535.`);
829
879
  return Number(raw);
830
880
  };
831
- const parsePortEnv = (raw, label) => raw === void 0 ? void 0 : parsePort(raw, label);
832
- const requireNonEmptyEnv = (name) => {
833
- const raw = process.env[name];
834
- if (raw === "") throw new Error(`Empty ${name}. Set it to a value, or unset it entirely.`);
835
- return raw;
881
+ const readNonEmptyEnv = (name) => {
882
+ const env = readEnv(name);
883
+ if (env.value === "") throw new Error(`Empty ${env.name}. Set it to a value, or unset it entirely.`);
884
+ return env;
885
+ };
886
+ const requireNonEmptyEnv = (name) => readNonEmptyEnv(name).value;
887
+ const portEnv = (name) => {
888
+ const env = readNonEmptyEnv(name);
889
+ return env.value === void 0 ? void 0 : parsePort(env.value, env.name);
836
890
  };
837
891
  const CLI_OPTIONS = {
838
892
  mode: { type: "string" },
@@ -906,8 +960,8 @@ const resolveTransportSettings = (cli) => {
906
960
  const corsFromEnv = (process.env["CORS_ORIGIN"] ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
907
961
  return {
908
962
  transport: cli.transport ?? requireNonEmptyEnv("TRANSPORT") ?? "stdio",
909
- host: cli.host ?? requireNonEmptyEnv("HOST") ?? "0.0.0.0",
910
- port: cli.port ?? parsePortEnv(requireNonEmptyEnv("PORT"), "PORT") ?? 3e3,
963
+ host: cli.host ?? requireNonEmptyEnv("LISTEN_HOST") ?? "0.0.0.0",
964
+ port: cli.port ?? portEnv("PORT") ?? 3e3,
911
965
  publicUrl: cli.publicUrl ?? requireNonEmptyEnv("PUBLIC_URL"),
912
966
  corsOrigins: [...cli.corsOrigins ?? [], ...corsFromEnv]
913
967
  };
@@ -918,7 +972,7 @@ const loadConfig = (argv = process.argv.slice(2)) => {
918
972
  const oauthClientId = requireNonEmptyEnv("ZENDESK_OAUTH_CLIENT_ID") ?? `${subdomain}_zendesk`;
919
973
  const mode = cli.tools?.length ? "all" : cli.mode ?? "namespace";
920
974
  const namespaces = cli.namespaces ?? (cli.tools?.length ? [...Namespace.options] : void 0);
921
- const callbackPort = cli.callbackPort ?? parsePortEnv(requireNonEmptyEnv("ZENDESK_OAUTH_CALLBACK_PORT"), "ZENDESK_OAUTH_CALLBACK_PORT");
975
+ const callbackPort = cli.callbackPort ?? portEnv("OAUTH_CALLBACK_PORT");
922
976
  const hcResourceScheme = cli.hcResourceScheme ?? requireNonEmptyEnv("HC_RESOURCE_SCHEME");
923
977
  return ConfigSchema.parse({
924
978
  subdomain,
@@ -2297,7 +2351,7 @@ const localePrefix = (locale) => locale ? `/${locale}` : "";
2297
2351
  const articleListPath = (sectionId, locale) => `${localePrefix(locale)}${sectionId ? `/sections/${sectionId}` : ""}/articles`;
2298
2352
  const sectionListPath = (categoryId, locale) => `${localePrefix(locale)}${categoryId ? `/categories/${categoryId}` : ""}/sections`;
2299
2353
  const scanCostNote = (truncated, pagesScanned, cost) => {
2300
- if (truncated) return `\n\n_Note: the scan hit its ${ARTICLE_RESOURCES_SCAN_MAX_PAGES}-page cap (${cost}), so promoted articles deeper in the catalog may be missing. This call is costly on this Help Center — avoid repeating it; raise ZENDESK_ARTICLE_RESOURCES_SCAN_MAX_PAGES to widen coverage._`;
2354
+ if (truncated) return `\n\n_Note: the scan hit its ${ARTICLE_RESOURCES_SCAN_MAX_PAGES}-page cap (${cost}), so promoted articles deeper in the catalog may be missing. This call is costly on this Help Center — avoid repeating it; raise ARTICLE_RESOURCES_SCAN_MAX_PAGES to widen coverage._`;
2301
2355
  if (pagesScanned > 1) return `\n\n_Note: this scan cost ${cost}; this tool performs a fresh scan every call (no caching), so avoid calling it again right away._`;
2302
2356
  return "";
2303
2357
  };
@@ -2522,7 +2576,7 @@ const createHelpCenterTools = (ctx) => {
2522
2576
  namespace: "help_center",
2523
2577
  readOnly: true,
2524
2578
  title: "List Promoted Help Center Articles",
2525
- 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 ZENDESK_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).",
2579
+ 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).",
2526
2580
  inputSchema: z.object({}),
2527
2581
  annotations: {
2528
2582
  readOnlyHint: true,
@@ -2900,7 +2954,7 @@ const createHelpCenterTools = (ctx) => {
2900
2954
  ]).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."),
2901
2955
  reference_article_id: z.number().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\"."),
2902
2956
  normalize: z.boolean().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."),
2903
- confirm: z.boolean().default(false).describe("Safety guard for large reorders. When the move would rewrite more article positions than the configured threshold (ZENDESK_REORDER_CONFIRM_THRESHOLD, default 20), the tool refuses and reports the count until you pass true here. Has no effect on small reorders.")
2957
+ confirm: z.boolean().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.")
2904
2958
  }),
2905
2959
  annotations: {
2906
2960
  readOnlyHint: false,
@@ -3383,7 +3437,7 @@ const END_USER_FORM_PARAMS = {
3383
3437
  * form invisible or a required field unenforced, and the caller then gets a
3384
3438
  * confidently wrong "no such form" or an opaque Zendesk 422.
3385
3439
  */
3386
- const fetchAllPages = async ({ subdomain, token, tool, path, extract, params = {}, maxPages = TICKET_FIELD_SCAN_MAX_PAGES, capEnvVar = "ZENDESK_TICKET_FIELD_SCAN_MAX_PAGES", onPage, forbiddenHint }) => {
3440
+ const fetchAllPages = async ({ subdomain, token, tool, path, extract, params = {}, maxPages = TICKET_FIELD_SCAN_MAX_PAGES, capEnvVar = "TICKET_FIELD_SCAN_MAX_PAGES", onPage, forbiddenHint }) => {
3387
3441
  const items = [];
3388
3442
  let page = 1;
3389
3443
  while (true) {
@@ -3440,7 +3494,7 @@ const fetchAllRequestComments = async (subdomain, token, requestId) => {
3440
3494
  extract: (response) => response.comments,
3441
3495
  params: { include: "users" },
3442
3496
  maxPages: MAX_COMMENT_PAGES,
3443
- capEnvVar: "ZENDESK_MAX_COMMENT_PAGES",
3497
+ capEnvVar: "COMMENT_MAX_PAGES",
3444
3498
  forbiddenHint: OTHER_USERS_REQUEST_HINT,
3445
3499
  onPage: (response) => {
3446
3500
  for (const user of response.users ?? []) authors.set(user.id, user);
@@ -5116,10 +5170,10 @@ const registerProxyTool = (server, toolName, title, tools, readOnlyMode, onUnaut
5116
5170
  return server.registerTool(toolName, {
5117
5171
  title,
5118
5172
  description: `${prefix}${title}. Specify the operation and its parameters.\n\nAvailable operations:\n${operationList}`,
5119
- inputSchema: {
5173
+ inputSchema: z.object({
5120
5174
  operation: z.string().describe(`One of: ${operationNames.join(", ")}`),
5121
5175
  params: z.record(z.string(), z.unknown()).default({}).describe("Operation parameters")
5122
- },
5176
+ }),
5123
5177
  annotations
5124
5178
  }, async (args) => dispatch(args));
5125
5179
  };
@@ -5738,7 +5792,7 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
5738
5792
  }
5739
5793
  const auth = { bearer };
5740
5794
  const server = createMcpServer(config, () => auth.bearer, logger);
5741
- const transport = new StreamableHTTPServerTransport({
5795
+ const transport = new NodeStreamableHTTPServerTransport({
5742
5796
  sessionIdGenerator: () => randomUUID(),
5743
5797
  onsessioninitialized: (newId) => {
5744
5798
  sessions.set(newId, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fruggr/zendesk-mcp-server",
3
- "version": "2.23.0",
3
+ "version": "2.24.0",
4
4
  "mcpName": "io.github.fruggr/zendesk-mcp-server",
5
5
  "description": "Deep Zendesk MCP server for your AI assistant: search, draft, update and translate Help Center articles and manage Support tickets end to end — comments, triage and image attachments.",
6
6
  "type": "module",
@@ -70,9 +70,10 @@
70
70
  "engines": {
71
71
  "node": ">=20"
72
72
  },
73
- "packageManager": "pnpm@12.4.2+sha512.08adc6613180275c7c9edada39dcf08c9c61ad4e7eaf330a4f3461f102b0f907423454d117f98e72d47fef0616070644d7bffc973a6a57f5090a6d7c368b07c9",
73
+ "packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
74
74
  "dependencies": {
75
- "@modelcontextprotocol/sdk": "1.30.0",
75
+ "@modelcontextprotocol/node": "2.0.0",
76
+ "@modelcontextprotocol/server": "2.0.0",
76
77
  "cheerio": "1.2.0",
77
78
  "hast-util-to-html": "9.0.5",
78
79
  "hast-util-to-mdast": "10.1.2",
@@ -90,6 +91,7 @@
90
91
  },
91
92
  "devDependencies": {
92
93
  "@biomejs/biome": "2.5.13",
94
+ "@modelcontextprotocol/client": "2.0.0",
93
95
  "@semantic-release/changelog": "^7.0.0",
94
96
  "@semantic-release/exec": "^7.1.0",
95
97
  "@semantic-release/git": "^11.0.0",