@fruggr/zendesk-mcp-server 2.18.0 → 2.19.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 +110 -8
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -281,7 +281,6 @@ const startBrowserAuth = (config, logger = silentLogger) => {
|
|
|
281
281
|
});
|
|
282
282
|
const requestedPort = config.callbackPort ?? 27439;
|
|
283
283
|
const onStartError = (err) => {
|
|
284
|
-
clearTimeout(authTimeout);
|
|
285
284
|
const code = err.code;
|
|
286
285
|
logger.error("oauth_callback_listen_failed", {
|
|
287
286
|
port: requestedPort,
|
|
@@ -4544,6 +4543,10 @@ const registerReloadTool = (server, reload, logger = silentLogger) => {
|
|
|
4544
4543
|
* `reload_tools` tool that hot-reloads edited tool code on demand. stdio only —
|
|
4545
4544
|
* HTTP builds a per-session server per request, so there is no long-lived
|
|
4546
4545
|
* server to hot-swap.
|
|
4546
|
+
*
|
|
4547
|
+
* Returns the running server so the caller can close it on shutdown: dev mode
|
|
4548
|
+
* runs over the same stdio transport as normal mode and must exit the same way
|
|
4549
|
+
* when the client disconnects.
|
|
4547
4550
|
*/
|
|
4548
4551
|
/* v8 ignore start -- runtime bootstrap: binds the reload tool to a real stdio
|
|
4549
4552
|
transport; the reload machinery it wires up is covered by dev-reload.test.ts */
|
|
@@ -4552,6 +4555,7 @@ const startDevServer = async (config, getToken, logger = silentLogger, onUnautho
|
|
|
4552
4555
|
registerReloadTool(server, reload, logger);
|
|
4553
4556
|
await startStdioTransport(server, logger);
|
|
4554
4557
|
logger.info("dev_mode_enabled");
|
|
4558
|
+
return server;
|
|
4555
4559
|
};
|
|
4556
4560
|
/* v8 ignore stop */
|
|
4557
4561
|
//#endregion
|
|
@@ -4907,27 +4911,125 @@ const startHttpTransport = async (config, logger = silentLogger, options = {}) =
|
|
|
4907
4911
|
};
|
|
4908
4912
|
};
|
|
4909
4913
|
//#endregion
|
|
4914
|
+
//#region src/utils/shutdown.ts
|
|
4915
|
+
/**
|
|
4916
|
+
* How long a shutdown may take before the watchdog forces the exit.
|
|
4917
|
+
*
|
|
4918
|
+
* Generous enough for `server.close()` and an HTTP session drain, short enough
|
|
4919
|
+
* to stay inside the tightest common supervisor grace — `docker stop` allows 10s
|
|
4920
|
+
* and Kubernetes 30s before their own SIGKILL (systemd is far laxer at 90s). A
|
|
4921
|
+
* process killed by its supervisor is exactly the unclean exit this removes.
|
|
4922
|
+
*/
|
|
4923
|
+
const SHUTDOWN_GRACE_MS = 3e3;
|
|
4924
|
+
/**
|
|
4925
|
+
* Adapt a `process` to a {@link ShutdownRuntime}. Takes the process as an
|
|
4926
|
+
* argument rather than closing over the global so the adapter itself is
|
|
4927
|
+
* testable — otherwise the one part of this module that touches the real
|
|
4928
|
+
* process would be the one part no test can reach.
|
|
4929
|
+
*/
|
|
4930
|
+
const createRuntime = (proc) => ({
|
|
4931
|
+
on: (event, listener) => {
|
|
4932
|
+
proc.on(event, listener);
|
|
4933
|
+
},
|
|
4934
|
+
stdin: { on: (event, listener) => {
|
|
4935
|
+
proc.stdin.on(event, listener);
|
|
4936
|
+
} },
|
|
4937
|
+
exit: (code) => {
|
|
4938
|
+
proc.exit(code);
|
|
4939
|
+
},
|
|
4940
|
+
setTimer: (fn, ms) => {
|
|
4941
|
+
const timer = setTimeout(fn, ms);
|
|
4942
|
+
return {
|
|
4943
|
+
unref: () => void timer.unref(),
|
|
4944
|
+
clear: () => clearTimeout(timer)
|
|
4945
|
+
};
|
|
4946
|
+
}
|
|
4947
|
+
});
|
|
4948
|
+
const defaultRuntime = createRuntime(process);
|
|
4949
|
+
/**
|
|
4950
|
+
* Install the process's one shutdown path and return its trigger.
|
|
4951
|
+
*
|
|
4952
|
+
* Registering a `SIGTERM` handler *removes* Node's default terminate, which
|
|
4953
|
+
* makes the exit our responsibility: a cleanup that stalls on an in-flight
|
|
4954
|
+
* request would otherwise leave a process SIGTERM cannot kill — the very
|
|
4955
|
+
* symptom this exists to remove. Hence the watchdog, which is load-bearing
|
|
4956
|
+
* rather than defensive, and the unconditional `exit` on every path.
|
|
4957
|
+
*
|
|
4958
|
+
* The exit is explicit rather than a drained event loop because the OAuth
|
|
4959
|
+
* callback server (`auth/browser-oauth.ts`) is a listening socket that is not
|
|
4960
|
+
* `unref()`'d: letting the loop drain would keep a disconnected session alive
|
|
4961
|
+
* for up to the 5-minute auth timeout.
|
|
4962
|
+
*/
|
|
4963
|
+
const installShutdown = (options) => {
|
|
4964
|
+
const { cleanup, logger, watchStdin, graceMs = SHUTDOWN_GRACE_MS } = options;
|
|
4965
|
+
const runtime = options.runtime ?? defaultRuntime;
|
|
4966
|
+
let started = false;
|
|
4967
|
+
let exited = false;
|
|
4968
|
+
const exitOnce = (code) => {
|
|
4969
|
+
if (exited) return;
|
|
4970
|
+
exited = true;
|
|
4971
|
+
runtime.exit(code);
|
|
4972
|
+
};
|
|
4973
|
+
const shutdown = async (reason) => {
|
|
4974
|
+
if (started) return;
|
|
4975
|
+
started = true;
|
|
4976
|
+
logger.info("shutdown_started", { reason });
|
|
4977
|
+
const watchdog = runtime.setTimer(() => {
|
|
4978
|
+
logger.warn("shutdown_forced", { graceMs });
|
|
4979
|
+
exitOnce(0);
|
|
4980
|
+
}, graceMs);
|
|
4981
|
+
watchdog.unref();
|
|
4982
|
+
try {
|
|
4983
|
+
await cleanup();
|
|
4984
|
+
logger.info("shutdown_complete", { reason });
|
|
4985
|
+
} catch (err) {
|
|
4986
|
+
logger.warn("shutdown_cleanup_failed", { error: err instanceof Error ? err.message : String(err) });
|
|
4987
|
+
} finally {
|
|
4988
|
+
watchdog.clear();
|
|
4989
|
+
exitOnce(0);
|
|
4990
|
+
}
|
|
4991
|
+
};
|
|
4992
|
+
runtime.on("SIGINT", () => void shutdown("SIGINT"));
|
|
4993
|
+
runtime.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
4994
|
+
if (watchStdin) runtime.stdin.on("end", () => void shutdown("stdin_eof"));
|
|
4995
|
+
return shutdown;
|
|
4996
|
+
};
|
|
4997
|
+
//#endregion
|
|
4910
4998
|
//#region src/index.ts
|
|
4911
4999
|
const buildStdioTokenStore = (config, logger) => createTokenStore({
|
|
4912
5000
|
subdomain: config.subdomain,
|
|
4913
5001
|
oauthClientId: config.oauthClientId,
|
|
4914
5002
|
callbackPort: config.callbackPort
|
|
4915
5003
|
}, logger);
|
|
5004
|
+
const connectStdio = async (config, tokenStore, logger) => {
|
|
5005
|
+
if (config.dev) return startDevServer(config, tokenStore.getToken, logger, tokenStore.invalidate);
|
|
5006
|
+
const server = createMcpServer(config, tokenStore.getToken, logger, tokenStore.invalidate);
|
|
5007
|
+
await startStdioTransport(server, logger);
|
|
5008
|
+
return server;
|
|
5009
|
+
};
|
|
4916
5010
|
const main = async () => {
|
|
4917
5011
|
const config = loadConfig();
|
|
4918
5012
|
const logger = createLogger(config.logLevel);
|
|
4919
5013
|
if (config.transport === "stdio") {
|
|
4920
5014
|
const tokenStore = buildStdioTokenStore(config, logger);
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
5015
|
+
const server = await connectStdio(config, tokenStore, logger);
|
|
5016
|
+
installShutdown({
|
|
5017
|
+
watchStdin: true,
|
|
5018
|
+
logger,
|
|
5019
|
+
cleanup: async () => {
|
|
5020
|
+
await server.close();
|
|
5021
|
+
tokenStore.dispose();
|
|
5022
|
+
}
|
|
5023
|
+
});
|
|
4927
5024
|
return;
|
|
4928
5025
|
}
|
|
4929
5026
|
if (config.dev) logger.warn("dev_mode_ignored_http");
|
|
4930
|
-
await startHttpTransport(config, logger);
|
|
5027
|
+
const http = await startHttpTransport(config, logger);
|
|
5028
|
+
installShutdown({
|
|
5029
|
+
watchStdin: false,
|
|
5030
|
+
logger,
|
|
5031
|
+
cleanup: http.close
|
|
5032
|
+
});
|
|
4931
5033
|
};
|
|
4932
5034
|
main().catch((error) => {
|
|
4933
5035
|
console.error("Fatal error:", error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fruggr/zendesk-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.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",
|
|
@@ -69,13 +69,13 @@
|
|
|
69
69
|
"engines": {
|
|
70
70
|
"node": ">=20"
|
|
71
71
|
},
|
|
72
|
-
"packageManager": "pnpm@11.
|
|
72
|
+
"packageManager": "pnpm@11.22.0+sha512.1ff870c4c6133dfd88fb2afc46dd13d47f09c9794b438c6fdb47ca98caf3bc16381ee0be93a091b8e3824cf01f889f46d7d9e20910fb0be1ab0fb5baa80dd621",
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
75
75
|
"cheerio": "1.2.0",
|
|
76
76
|
"hast-util-to-html": "9.0.5",
|
|
77
77
|
"hast-util-to-mdast": "10.1.2",
|
|
78
|
-
"open": "11.0.
|
|
78
|
+
"open": "11.0.1",
|
|
79
79
|
"rehype-parse": "9.0.1",
|
|
80
80
|
"rehype-raw": "7.0.0",
|
|
81
81
|
"rehype-remark": "10.0.1",
|
|
@@ -88,15 +88,15 @@
|
|
|
88
88
|
"zod": "4.4.3"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
|
-
"@biomejs/biome": "2.5.
|
|
91
|
+
"@biomejs/biome": "2.5.8",
|
|
92
92
|
"@semantic-release/changelog": "^7.0.0",
|
|
93
93
|
"@semantic-release/exec": "^7.1.0",
|
|
94
94
|
"@semantic-release/git": "^11.0.0",
|
|
95
95
|
"@semantic-release/github": "^12.0.6",
|
|
96
96
|
"@semantic-release/npm": "^13.1.5",
|
|
97
97
|
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
98
|
-
"@stryker-mutator/core": "^
|
|
99
|
-
"@stryker-mutator/vitest-runner": "^
|
|
98
|
+
"@stryker-mutator/core": "^10.0.0",
|
|
99
|
+
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
100
100
|
"@tsconfig/node20": "^20.1.9",
|
|
101
101
|
"@tsconfig/strictest": "^2.0.8",
|
|
102
102
|
"@types/hast": "^3.0.4",
|