@fruggr/zendesk-mcp-server 2.22.2 → 2.22.3
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 +2 -0
- package/dist/index.js +37 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,6 +152,8 @@ 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
154
|
and overrides: [`ZENDESK_TOKEN_FILE`](docs/configuration.md#zendesk_token_file)).
|
|
155
|
+
Several instances can run side by side — a read-write one and a `--read-only`
|
|
156
|
+
one, say — without trading credentials.
|
|
155
157
|
|
|
156
158
|
### MCP client wiring
|
|
157
159
|
|
package/dist/index.js
CHANGED
|
@@ -176,9 +176,11 @@ const requestedScope = (readOnly) => readOnly ? READ_SCOPE : READ_WRITE_SCOPE;
|
|
|
176
176
|
const supportedScopes = (readOnly) => scopeTokens(requestedScope(readOnly));
|
|
177
177
|
/**
|
|
178
178
|
* Whether a grant still covers what this process needs: a flat subset test.
|
|
179
|
-
* Coverage, not equality, so a
|
|
180
|
-
*
|
|
181
|
-
*
|
|
179
|
+
* Coverage, not equality, so a token Zendesk granted wider than requested stays
|
|
180
|
+
* 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
|
|
182
|
+
* default layout no longer names those files. No scope hierarchy: granular
|
|
183
|
+
* scopes (#284) replace this.
|
|
182
184
|
*/
|
|
183
185
|
const grantCovers = (granted, requested) => {
|
|
184
186
|
if (typeof granted !== "string") return true;
|
|
@@ -199,11 +201,13 @@ const detectWsl = () => {
|
|
|
199
201
|
};
|
|
200
202
|
/**
|
|
201
203
|
* Build an actionable error for a callback port that's already taken. The raw
|
|
202
|
-
* Node `EADDRINUSE` is opaque to both the user and the LLM
|
|
203
|
-
*
|
|
204
|
-
*
|
|
204
|
+
* Node `EADDRINUSE` is opaque to both the user and the LLM. The port is also
|
|
205
|
+
* the mutex between instances, so retrying once the other sign-in ends comes
|
|
206
|
+
* first and the free-port escape hatch second
|
|
207
|
+
* (`docs/decisions/token-file-keying.md`). The `(EADDRINUSE)` marker and `code`
|
|
208
|
+
* are kept for diagnostics/tests.
|
|
205
209
|
*/
|
|
206
|
-
const callbackPortInUseError = (port, cause) => Object.assign(/* @__PURE__ */ new Error(`Cannot start the Zendesk OAuth sign-in: local callback port ${port} is already in use
|
|
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)`), {
|
|
207
211
|
code: "EADDRINUSE",
|
|
208
212
|
cause
|
|
209
213
|
});
|
|
@@ -483,18 +487,31 @@ const configDir = () => {
|
|
|
483
487
|
const base = process.env["XDG_CONFIG_HOME"] ?? join(homedir(), ".config");
|
|
484
488
|
return join(base, ...segments);
|
|
485
489
|
};
|
|
486
|
-
const safeName = (
|
|
490
|
+
const safeName = (part) => part.replace(/[^a-z0-9-]/gi, "_");
|
|
491
|
+
const READABLE_BUDGET = 120;
|
|
492
|
+
const keyDigest = (key) => createHash("sha256").update(JSON.stringify([
|
|
493
|
+
key.subdomain,
|
|
494
|
+
key.oauthClientId,
|
|
495
|
+
key.scope
|
|
496
|
+
])).digest("hex").slice(0, 8);
|
|
487
497
|
/**
|
|
488
|
-
* Path to the token file for
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
498
|
+
* Path to the token file for one credential. Keyed on the whole `TokenKey`, so
|
|
499
|
+
* a read-only server, a read-write one and one on another OAuth client each get
|
|
500
|
+
* their own record instead of clobbering a shared file. Why that triple, and
|
|
501
|
+
* why no migration from the old subdomain-only layout:
|
|
502
|
+
* `docs/decisions/token-file-keying.md`.
|
|
503
|
+
* `ZENDESK_TOKEN_FILE` overrides with an explicit path — the way to separate
|
|
504
|
+
* two Zendesk accounts that share a subdomain, client and scope.
|
|
493
505
|
*/
|
|
494
|
-
const resolveTokenPath = (
|
|
506
|
+
const resolveTokenPath = (key) => {
|
|
495
507
|
const override = process.env["ZENDESK_TOKEN_FILE"];
|
|
496
508
|
if (override) return override;
|
|
497
|
-
|
|
509
|
+
const readable = [
|
|
510
|
+
key.subdomain,
|
|
511
|
+
key.oauthClientId,
|
|
512
|
+
key.scope
|
|
513
|
+
].map(safeName).join("--").slice(0, READABLE_BUDGET);
|
|
514
|
+
return join(configDir(), `${readable}--${keyDigest(key)}.json`);
|
|
498
515
|
};
|
|
499
516
|
const writeFileAtomic = (path, record) => {
|
|
500
517
|
const dir = dirname(path);
|
|
@@ -539,8 +556,12 @@ const createAuthRequiredError = (authorizeUrl) => Object.assign(/* @__PURE__ */
|
|
|
539
556
|
});
|
|
540
557
|
const expiryFrom = (expiresIn) => typeof expiresIn === "number" ? Date.now() + expiresIn * 1e3 : void 0;
|
|
541
558
|
const createTokenStore = (config, logger = silentLogger) => {
|
|
542
|
-
const tokenPath = resolveTokenPath(config.subdomain);
|
|
543
559
|
const requested = requestedScope(config.readOnly);
|
|
560
|
+
const tokenPath = resolveTokenPath({
|
|
561
|
+
subdomain: config.subdomain,
|
|
562
|
+
oauthClientId: config.oauthClientId,
|
|
563
|
+
scope: requested
|
|
564
|
+
});
|
|
544
565
|
let token = loadToken(tokenPath);
|
|
545
566
|
if (token) {
|
|
546
567
|
if (grantCovers(token.scope, requested)) logger.debug("oauth_token_loaded_from_disk");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fruggr/zendesk-mcp-server",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.3",
|
|
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",
|