@fruggr/zendesk-mcp-server 2.13.0 → 2.14.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 +39 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1478,6 +1478,7 @@ const markdownToHtml = (markdown) => {
|
|
|
1478
1478
|
//#endregion
|
|
1479
1479
|
//#region src/tools/help-center.ts
|
|
1480
1480
|
const ARTICLE_ID_DESC = "Article ID — the numeric id of the Help Center article. Obtain it from list_articles or search_articles.";
|
|
1481
|
+
const listTranslations = (subdomain, token, articleId) => helpCenterGet(subdomain, token, `/articles/${articleId}/translations`).then((res) => res.translations);
|
|
1481
1482
|
const largeArticleHint = (body, sectionCount) => {
|
|
1482
1483
|
if (body.length < 3e3 && sectionCount < 4) return null;
|
|
1483
1484
|
return [
|
|
@@ -1560,7 +1561,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1560
1561
|
const token = await getToken();
|
|
1561
1562
|
const path = locale ? `/${locale}/articles/${article_id}` : `/articles/${article_id}`;
|
|
1562
1563
|
const { article } = await helpCenterGet(subdomain, token, path);
|
|
1563
|
-
const
|
|
1564
|
+
const translations = await listTranslations(subdomain, token, article_id);
|
|
1564
1565
|
return { content: [{
|
|
1565
1566
|
type: "text",
|
|
1566
1567
|
text: truncateIfNeeded((largeArticleHint(article.body, parseSections(article.body).length) ?? "") + formatArticle(article) + `\n\n**Available translations**: ${translations.map((t) => t.locale).join(", ")}`)
|
|
@@ -1667,8 +1668,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1667
1668
|
text: formatList(articles, formatArticleSummary, extractPaginationMeta(response, articles.length))
|
|
1668
1669
|
}] };
|
|
1669
1670
|
const formatted = await Promise.all(articles.map(async (article) => {
|
|
1670
|
-
const
|
|
1671
|
-
const locales = translations.map((t) => t.locale).join(", ");
|
|
1671
|
+
const locales = (await listTranslations(subdomain, token, article.id)).map((t) => t.locale).join(", ");
|
|
1672
1672
|
return `${formatArticleSummary(article)}\n- **Translations**: ${locales}`;
|
|
1673
1673
|
}));
|
|
1674
1674
|
const meta = extractPaginationMeta(response, articles.length);
|
|
@@ -1694,10 +1694,9 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1694
1694
|
handler: async (params) => {
|
|
1695
1695
|
const { article_id } = params;
|
|
1696
1696
|
const token = await getToken();
|
|
1697
|
-
const { translations } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations`);
|
|
1698
1697
|
return { content: [{
|
|
1699
1698
|
type: "text",
|
|
1700
|
-
text: formatList(
|
|
1699
|
+
text: formatList(await listTranslations(subdomain, token, article_id), formatTranslationSummary)
|
|
1701
1700
|
}] };
|
|
1702
1701
|
}
|
|
1703
1702
|
},
|
|
@@ -2120,7 +2119,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
2120
2119
|
const { article } = await helpCenterGet(subdomain, token, `/articles/${article_id}`);
|
|
2121
2120
|
const effectiveLocale = locale ?? article.source_locale;
|
|
2122
2121
|
const { translation } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${effectiveLocale}`);
|
|
2123
|
-
const
|
|
2122
|
+
const translations = await listTranslations(subdomain, token, article_id);
|
|
2124
2123
|
const sections = parseSections(translation.body);
|
|
2125
2124
|
const outlineLines = sections.length ? sections.map((s) => `- [${s.index}] ${s.headingTag ? `${s.headingTag}: ` : ""}${s.heading} (${s.wordCount} words)`).join("\n") : "_(no sections detected)_";
|
|
2126
2125
|
const translationsList = translations.map((t) => `- ${t.locale}${t.outdated ? " (outdated)" : ""}`).join("\n");
|
|
@@ -2215,7 +2214,7 @@ const createHelpCenterTools = (ctx) => {
|
|
|
2215
2214
|
namespace: "help_center",
|
|
2216
2215
|
readOnly: true,
|
|
2217
2216
|
title: "Compare Article Translations",
|
|
2218
|
-
description: "Compare
|
|
2217
|
+
description: "Compare two locales of the same article to decide whether the target translation needs work, reporting independent signals instead of one ambiguous verdict. (1) Header — a \"Freshness\" verdict for the target, derived from the two translations' updated_at timestamps: if the source was edited after the target it is \"likely behind, review recommended\" (with the day gap), otherwise \"up to date\". This is the primary staleness signal and is always available. (2) Zendesk's own per-translation \"outdated\" flag for the target (\"yes\"/\"no\"/\"unknown\"), shown as a secondary overlay: it is only set through Guide's native \"mark out of date\" workflow and NOT by API edits, so a \"no\" does not by itself mean current — prefer Freshness. (3) A global structure check (section count and heading-tag sequence); on mismatch the header warns the per-index rows may be misaligned. (4) A per-section table matched by index, status \"ok\" (present in both), \"missing\" (present in source, absent in target) or \"extra\" (present in target, absent in source). (5) Per-section source/target word counts, INFORMATIONAL ONLY: a length difference between languages is normal and is deliberately NOT flagged as a divergence — do not read a word-count gap as an edit regression or staleness. Read-only; performs three Help Center GET calls (both translations plus the translations list for the outdated flag).",
|
|
2219
2218
|
inputSchema: z.object({
|
|
2220
2219
|
article_id: z.number().int().describe(ARTICLE_ID_DESC),
|
|
2221
2220
|
source_locale: z.string().describe("Reference locale to diff against, e.g. \"en-us\". Usually the article source_locale (from get_article)."),
|
|
@@ -2230,10 +2229,32 @@ const createHelpCenterTools = (ctx) => {
|
|
|
2230
2229
|
handler: async (params) => {
|
|
2231
2230
|
const { article_id, source_locale, target_locale } = params;
|
|
2232
2231
|
const token = await getToken();
|
|
2233
|
-
const [sourceRes, targetRes] = await Promise.all([
|
|
2232
|
+
const [sourceRes, targetRes, translations] = await Promise.all([
|
|
2233
|
+
helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${source_locale}`),
|
|
2234
|
+
helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${target_locale}`),
|
|
2235
|
+
listTranslations(subdomain, token, article_id)
|
|
2236
|
+
]);
|
|
2234
2237
|
const sourceSections = parseSections(sourceRes.translation.body);
|
|
2235
2238
|
const targetSections = parseSections(targetRes.translation.body);
|
|
2236
2239
|
const maxLen = Math.max(sourceSections.length, targetSections.length);
|
|
2240
|
+
const sourceUpdated = sourceRes.translation.updated_at;
|
|
2241
|
+
const targetUpdated = targetRes.translation.updated_at;
|
|
2242
|
+
const srcMs = Date.parse(sourceUpdated);
|
|
2243
|
+
const tgtMs = Date.parse(targetUpdated);
|
|
2244
|
+
const comparable = Number.isFinite(srcMs) && Number.isFinite(tgtMs);
|
|
2245
|
+
let freshnessLine;
|
|
2246
|
+
if (comparable && srcMs > tgtMs) {
|
|
2247
|
+
const days = Math.floor((srcMs - tgtMs) / 864e5);
|
|
2248
|
+
freshnessLine = `- **Freshness (target ${target_locale})**: source was edited ${days >= 1 ? `${days} day(s)` : "less than a day"} after this translation → likely behind, review recommended.`;
|
|
2249
|
+
} else if (comparable) freshnessLine = `- **Freshness (target ${target_locale})**: up to date (source has not been edited since this translation).`;
|
|
2250
|
+
else freshnessLine = `- **Freshness (target ${target_locale})**: unknown (could not compare edit timestamps).`;
|
|
2251
|
+
const targetLocaleKey = target_locale.toLowerCase();
|
|
2252
|
+
const targetListEntry = translations.find((t) => t.locale.toLowerCase() === targetLocaleKey);
|
|
2253
|
+
const outdated = targetListEntry?.outdated === void 0 ? "unknown" : targetListEntry.outdated ? "yes" : "no";
|
|
2254
|
+
const outdatedLine = outdated === "yes" ? `- **Zendesk outdated flag (target ${target_locale})**: yes — explicitly marked out of date in Guide.` : outdated === "no" ? `- **Zendesk outdated flag (target ${target_locale})**: no (only set via Guide's own edit workflow; "no" does not by itself mean current — rely on Freshness above).` : `- **Zendesk outdated flag (target ${target_locale})**: unknown.`;
|
|
2255
|
+
const sourceTags = sourceSections.map((s) => s.headingTag).join(",");
|
|
2256
|
+
const targetTags = targetSections.map((s) => s.headingTag).join(",");
|
|
2257
|
+
const structureLine = sourceSections.length === targetSections.length && sourceTags === targetTags ? `- **Structure**: ${sourceSections.length} sections in both locales — aligned.` : `- **Structure**: ${sourceSections.length} source vs ${targetSections.length} target sections — MISMATCH; the per-index rows below may be misaligned.`;
|
|
2237
2258
|
const rows = [];
|
|
2238
2259
|
rows.push(`| Idx | Heading | Status | Source words | Target words |`);
|
|
2239
2260
|
rows.push(`| --- | --- | --- | --- | --- |`);
|
|
@@ -2245,11 +2266,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
2245
2266
|
const targetWords = tgt?.wordCount ?? 0;
|
|
2246
2267
|
let status;
|
|
2247
2268
|
if (!tgt) status = "missing";
|
|
2248
|
-
else if (!src) status = "
|
|
2249
|
-
else
|
|
2250
|
-
const denom = Math.max(sourceWords, 1);
|
|
2251
|
-
status = Math.abs(sourceWords - targetWords) / denom > .25 ? "different" : "ok";
|
|
2252
|
-
}
|
|
2269
|
+
else if (!src) status = "extra";
|
|
2270
|
+
else status = "ok";
|
|
2253
2271
|
rows.push(`| ${i} | ${heading} | ${status} | ${sourceWords} | ${targetWords} |`);
|
|
2254
2272
|
}
|
|
2255
2273
|
return { content: [{
|
|
@@ -2257,6 +2275,14 @@ const createHelpCenterTools = (ctx) => {
|
|
|
2257
2275
|
text: [
|
|
2258
2276
|
`# Translation diff — Article #${article_id} (${source_locale} → ${target_locale})`,
|
|
2259
2277
|
"",
|
|
2278
|
+
freshnessLine,
|
|
2279
|
+
outdatedLine,
|
|
2280
|
+
structureLine,
|
|
2281
|
+
`- **Updated**: source ${sourceUpdated} | target ${targetUpdated}`,
|
|
2282
|
+
`- **Target draft**: ${targetRes.translation.draft ? "yes" : "no"}`,
|
|
2283
|
+
"",
|
|
2284
|
+
"_Word counts are informational: a length difference between languages is normal, not a divergence._",
|
|
2285
|
+
"",
|
|
2260
2286
|
...rows
|
|
2261
2287
|
].join("\n")
|
|
2262
2288
|
}] };
|