@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.
- package/README.md +4 -4
- package/dist/index.js +86 -32
- 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/
|
|
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
|
-
`
|
|
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: [`
|
|
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`, `
|
|
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/
|
|
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/
|
|
25
|
-
import {
|
|
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 =
|
|
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("
|
|
129
|
-
const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("
|
|
130
|
-
const MAX_ATTACHMENT_BYTES = positiveIntEnv("
|
|
131
|
-
const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("
|
|
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("
|
|
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("
|
|
138
|
-
const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("
|
|
139
|
-
const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("
|
|
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 `
|
|
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
|
|
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
|
-
* `
|
|
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 =
|
|
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
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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("
|
|
910
|
-
port: cli.port ??
|
|
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 ??
|
|
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
|
|
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
|
|
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 (
|
|
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 = "
|
|
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: "
|
|
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
|
|
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.
|
|
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.
|
|
73
|
+
"packageManager": "pnpm@12.5.1+sha512.e3f305bc784a2bc89f5ad3b6138889470fae8d2af5f36b61216ec91c2c3d64089775f9de38aac331044ea40f245cb0d5666392dfdf65824e1907ef6a2c62de5f",
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@modelcontextprotocol/
|
|
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",
|