@fruggr/zendesk-mcp-server 2.8.0 → 2.9.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 +123 -43
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
[](https://github.com/semantic-release/semantic-release)
|
|
10
10
|
|
|
11
11
|
**Bring Zendesk deep into your AI assistant.** A
|
|
12
|
-
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) server
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) server: find
|
|
13
|
+
answers in the Help Center, **draft, update and translate** articles (keeping
|
|
14
|
+
languages in sync), and **manage Support tickets** end to end — comments, triage
|
|
15
|
+
and image attachments — all in plain language,
|
|
16
16
|
**without switching apps**.
|
|
17
17
|
|
|
18
18
|
Think of it as the [Zendesk agent for Microsoft 365 Copilot](https://support.zendesk.com/hc/en-us/articles/9958331458458-Using-the-Zendesk-agent-in-Microsoft-365-Copilot),
|
package/dist/index.js
CHANGED
|
@@ -261,14 +261,15 @@ const startBrowserAuth = (config, logger = silentLogger) => {
|
|
|
261
261
|
});
|
|
262
262
|
const port = callbackServer.address().port;
|
|
263
263
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
264
|
-
const
|
|
264
|
+
const params = new URLSearchParams({
|
|
265
265
|
response_type: "code",
|
|
266
266
|
client_id: oauthClientId,
|
|
267
267
|
redirect_uri: redirectUri,
|
|
268
268
|
scope: "read write",
|
|
269
269
|
code_challenge: codeChallenge,
|
|
270
270
|
code_challenge_method: "S256"
|
|
271
|
-
})
|
|
271
|
+
});
|
|
272
|
+
const authUrl = `${authorizeBase}?${params.toString()}`;
|
|
272
273
|
logger.debug("oauth_callback_listening", {
|
|
273
274
|
port,
|
|
274
275
|
redirectUri
|
|
@@ -533,7 +534,8 @@ const createTokenStore = (config, logger = silentLogger) => {
|
|
|
533
534
|
if (refreshed) return refreshed;
|
|
534
535
|
}
|
|
535
536
|
if (!starting) starting = beginAuth();
|
|
536
|
-
|
|
537
|
+
const url = authorizeUrl ?? await starting;
|
|
538
|
+
throw createAuthRequiredError(url);
|
|
537
539
|
};
|
|
538
540
|
const invalidate = () => {
|
|
539
541
|
if (token?.refreshToken) {
|
|
@@ -746,37 +748,44 @@ const executeRequest = async (url, token, options = {}) => {
|
|
|
746
748
|
return response.json();
|
|
747
749
|
};
|
|
748
750
|
const zendeskGet = (subdomain, token, path, params) => {
|
|
749
|
-
|
|
751
|
+
const url = buildUrl(getBaseUrl(subdomain), path, params);
|
|
752
|
+
return executeRequest(url, token);
|
|
750
753
|
};
|
|
751
754
|
const zendeskPost = (subdomain, token, path, body) => {
|
|
752
|
-
|
|
755
|
+
const url = buildUrl(getBaseUrl(subdomain), path);
|
|
756
|
+
return executeRequest(url, token, {
|
|
753
757
|
method: "POST",
|
|
754
758
|
body
|
|
755
759
|
});
|
|
756
760
|
};
|
|
757
761
|
const zendeskPut = (subdomain, token, path, body) => {
|
|
758
|
-
|
|
762
|
+
const url = buildUrl(getBaseUrl(subdomain), path);
|
|
763
|
+
return executeRequest(url, token, {
|
|
759
764
|
method: "PUT",
|
|
760
765
|
body
|
|
761
766
|
});
|
|
762
767
|
};
|
|
763
768
|
const helpCenterGet = (subdomain, token, path, params) => {
|
|
764
|
-
|
|
769
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
|
|
770
|
+
return executeRequest(url, token);
|
|
765
771
|
};
|
|
766
772
|
const helpCenterPost = (subdomain, token, path, body) => {
|
|
767
|
-
|
|
773
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
774
|
+
return executeRequest(url, token, {
|
|
768
775
|
method: "POST",
|
|
769
776
|
body
|
|
770
777
|
});
|
|
771
778
|
};
|
|
772
779
|
const helpCenterPut = (subdomain, token, path, body) => {
|
|
773
|
-
|
|
780
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
781
|
+
return executeRequest(url, token, {
|
|
774
782
|
method: "PUT",
|
|
775
783
|
body
|
|
776
784
|
});
|
|
777
785
|
};
|
|
778
786
|
const helpCenterDelete = (subdomain, token, path) => {
|
|
779
|
-
|
|
787
|
+
const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
|
|
788
|
+
return executeRequest(url, token, { method: "DELETE" });
|
|
780
789
|
};
|
|
781
790
|
const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
|
|
782
791
|
const expectedHost = `${subdomain}.zendesk.com`;
|
|
@@ -888,6 +897,18 @@ const formatSlaPolicy = (policy) => {
|
|
|
888
897
|
...targets
|
|
889
898
|
].filter(Boolean).join("\n");
|
|
890
899
|
};
|
|
900
|
+
const formatTicketField = (field) => {
|
|
901
|
+
const flags = [field.active ? "active" : "inactive", field.required ? "required" : "optional"].join(", ");
|
|
902
|
+
const options = field.custom_field_options ?? field.system_field_options ?? [];
|
|
903
|
+
return [
|
|
904
|
+
`## ${field.title} (id ${field.id})`,
|
|
905
|
+
`- **Type**: ${field.type} | **${flags}**`,
|
|
906
|
+
field.description ? `- **Description**: ${field.description}` : "",
|
|
907
|
+
field.tag ? `- **Tag**: ${field.tag}` : "",
|
|
908
|
+
options.length > 0 ? "- **Options** (name → value):" : "",
|
|
909
|
+
...options.map((o) => ` - ${o.name} → ${o.value}`)
|
|
910
|
+
].filter(Boolean).join("\n");
|
|
911
|
+
};
|
|
891
912
|
const minutesUntil = (iso) => {
|
|
892
913
|
const t = Date.parse(iso);
|
|
893
914
|
return Number.isNaN(t) ? null : Math.round((t - Date.now()) / 6e4);
|
|
@@ -967,7 +988,8 @@ const formatLabel = (label) => `- **${label.name}** (${label.id})`;
|
|
|
967
988
|
const formatUserSegment = (segment) => `- **${segment.name}** (${segment.id}) — ${segment.user_type}${segment.built_in ? " — Built-in" : ""}`;
|
|
968
989
|
const formatAttachment = (attachment) => `- **${attachment.file_name}** (${attachment.id}) — ${attachment.content_type} — ${attachment.size} bytes`;
|
|
969
990
|
const formatList = (items, formatter, meta) => {
|
|
970
|
-
|
|
991
|
+
const text = [meta ? formatPagination(meta) : "", items.map(formatter).join("\n\n")].filter(Boolean).join("\n\n");
|
|
992
|
+
return truncateIfNeeded(text);
|
|
971
993
|
};
|
|
972
994
|
//#endregion
|
|
973
995
|
//#region src/utils/pagination.ts
|
|
@@ -1093,7 +1115,8 @@ const createTopologyProvider = (getToken, subdomain, onUnauthorized) => {
|
|
|
1093
1115
|
const now = Date.now();
|
|
1094
1116
|
if (cached && now - cached.at < 3e5) return cached.promise;
|
|
1095
1117
|
const promise = (async () => {
|
|
1096
|
-
|
|
1118
|
+
const token = await getToken();
|
|
1119
|
+
return formatTopology(await fetchTopology(subdomain, token));
|
|
1097
1120
|
})().catch((err) => {
|
|
1098
1121
|
cached = void 0;
|
|
1099
1122
|
if (onUnauthorized && err instanceof ZendeskApiError && err.status === 401) onUnauthorized();
|
|
@@ -1285,7 +1308,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1285
1308
|
handler: async (params) => {
|
|
1286
1309
|
const { article_id, locale } = params;
|
|
1287
1310
|
const token = await getToken();
|
|
1288
|
-
const
|
|
1311
|
+
const path = locale ? `/${locale}/articles/${article_id}` : `/articles/${article_id}`;
|
|
1312
|
+
const { article } = await helpCenterGet(subdomain, token, path);
|
|
1289
1313
|
const { translations } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations`);
|
|
1290
1314
|
return { content: [{
|
|
1291
1315
|
type: "text",
|
|
@@ -1312,7 +1336,9 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1312
1336
|
},
|
|
1313
1337
|
handler: async (params) => {
|
|
1314
1338
|
const { locale, page_size, cursor } = params;
|
|
1315
|
-
const
|
|
1339
|
+
const token = await getToken();
|
|
1340
|
+
const path = locale ? `/${locale}/categories` : "/categories";
|
|
1341
|
+
const response = await helpCenterGet(subdomain, token, path, buildCursorParams(page_size, cursor));
|
|
1316
1342
|
const categories = response.categories ?? [];
|
|
1317
1343
|
return { content: [{
|
|
1318
1344
|
type: "text",
|
|
@@ -1340,7 +1366,9 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1340
1366
|
},
|
|
1341
1367
|
handler: async (params) => {
|
|
1342
1368
|
const { category_id, locale, page_size, cursor } = params;
|
|
1343
|
-
const
|
|
1369
|
+
const token = await getToken();
|
|
1370
|
+
const path = category_id && locale ? `/${locale}/categories/${category_id}/sections` : category_id ? `/categories/${category_id}/sections` : locale ? `/${locale}/sections` : "/sections";
|
|
1371
|
+
const response = await helpCenterGet(subdomain, token, path, buildCursorParams(page_size, cursor));
|
|
1344
1372
|
const sections = response.sections ?? [];
|
|
1345
1373
|
return { content: [{
|
|
1346
1374
|
type: "text",
|
|
@@ -1377,7 +1405,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1377
1405
|
handler: async (params) => {
|
|
1378
1406
|
const { section_id, locale, page_size, cursor, sort_by, sort_order, include_translations } = params;
|
|
1379
1407
|
const token = await getToken();
|
|
1380
|
-
const
|
|
1408
|
+
const path = section_id && locale ? `/${locale}/sections/${section_id}/articles` : section_id ? `/sections/${section_id}/articles` : locale ? `/${locale}/articles` : "/articles";
|
|
1409
|
+
const response = await helpCenterGet(subdomain, token, path, {
|
|
1381
1410
|
...buildCursorParams(page_size, cursor),
|
|
1382
1411
|
sort_by,
|
|
1383
1412
|
sort_order
|
|
@@ -1414,7 +1443,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1414
1443
|
},
|
|
1415
1444
|
handler: async (params) => {
|
|
1416
1445
|
const { article_id } = params;
|
|
1417
|
-
const
|
|
1446
|
+
const token = await getToken();
|
|
1447
|
+
const { translations } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations`);
|
|
1418
1448
|
return { content: [{
|
|
1419
1449
|
type: "text",
|
|
1420
1450
|
text: formatList(translations, formatTranslationSummary)
|
|
@@ -1442,7 +1472,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1442
1472
|
},
|
|
1443
1473
|
handler: async (params) => {
|
|
1444
1474
|
const { article_id, locale, title, body, draft } = params;
|
|
1445
|
-
const
|
|
1475
|
+
const token = await getToken();
|
|
1476
|
+
const { translation } = await helpCenterPost(subdomain, token, `/articles/${article_id}/translations`, { translation: {
|
|
1446
1477
|
locale,
|
|
1447
1478
|
title,
|
|
1448
1479
|
body,
|
|
@@ -1475,7 +1506,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1475
1506
|
},
|
|
1476
1507
|
handler: async (params) => {
|
|
1477
1508
|
const { article_id, locale, ...updates } = params;
|
|
1478
|
-
const
|
|
1509
|
+
const token = await getToken();
|
|
1510
|
+
const { translation } = await helpCenterPut(subdomain, token, `/articles/${article_id}/translations/${locale}`, { translation: updates });
|
|
1479
1511
|
return { content: [{
|
|
1480
1512
|
type: "text",
|
|
1481
1513
|
text: `Translation updated for article #${article_id} in "${locale}".\n\n${formatTranslation(translation)}`
|
|
@@ -1496,9 +1528,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1496
1528
|
openWorldHint: true
|
|
1497
1529
|
},
|
|
1498
1530
|
handler: async () => {
|
|
1531
|
+
const token = await getToken();
|
|
1499
1532
|
return { content: [{
|
|
1500
1533
|
type: "text",
|
|
1501
|
-
text: formatList((await zendeskGet(subdomain,
|
|
1534
|
+
text: formatList((await zendeskGet(subdomain, token, "/guide/permission_groups")).permission_groups ?? [], formatPermissionGroup)
|
|
1502
1535
|
}] };
|
|
1503
1536
|
}
|
|
1504
1537
|
},
|
|
@@ -1529,7 +1562,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1529
1562
|
},
|
|
1530
1563
|
handler: async (params) => {
|
|
1531
1564
|
const { section_id, ...articleData } = params;
|
|
1532
|
-
const
|
|
1565
|
+
const token = await getToken();
|
|
1566
|
+
const { article } = await helpCenterPost(subdomain, token, `/sections/${section_id}/articles`, { article: articleData });
|
|
1533
1567
|
return { content: [{
|
|
1534
1568
|
type: "text",
|
|
1535
1569
|
text: `Article #${article.id} created.\n\n${formatArticle(article)}`
|
|
@@ -1562,7 +1596,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1562
1596
|
},
|
|
1563
1597
|
handler: async (params) => {
|
|
1564
1598
|
const { article_id, ...updates } = params;
|
|
1565
|
-
const
|
|
1599
|
+
const token = await getToken();
|
|
1600
|
+
const { article } = await helpCenterPut(subdomain, token, `/articles/${article_id}`, { article: updates });
|
|
1566
1601
|
return { content: [{
|
|
1567
1602
|
type: "text",
|
|
1568
1603
|
text: `Article #${article.id} updated.\n\n${formatArticle(article)}`
|
|
@@ -1588,7 +1623,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1588
1623
|
handler: async (params) => {
|
|
1589
1624
|
const { article_id, confirm } = params;
|
|
1590
1625
|
if (confirm !== true) throw new Error("Archiving is guarded: pass confirm: true to archive (soft-delete) this article. No changes were made.");
|
|
1591
|
-
|
|
1626
|
+
const token = await getToken();
|
|
1627
|
+
await helpCenterDelete(subdomain, token, `/articles/${article_id}`);
|
|
1592
1628
|
return { content: [{
|
|
1593
1629
|
type: "text",
|
|
1594
1630
|
text: `Article #${article_id} archived (soft-deleted). It is removed from the Help Center; restore it from the Guide admin UI if needed.`
|
|
@@ -1646,7 +1682,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1646
1682
|
},
|
|
1647
1683
|
handler: async (params) => {
|
|
1648
1684
|
const { name } = params;
|
|
1649
|
-
const
|
|
1685
|
+
const token = await getToken();
|
|
1686
|
+
const { content_tag } = await zendeskPost(subdomain, token, "/guide/content_tags", { content_tag: { name } });
|
|
1650
1687
|
return { content: [{
|
|
1651
1688
|
type: "text",
|
|
1652
1689
|
text: `Content tag created.\n\n${formatContentTag(content_tag)}`
|
|
@@ -1667,9 +1704,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1667
1704
|
openWorldHint: true
|
|
1668
1705
|
},
|
|
1669
1706
|
handler: async () => {
|
|
1707
|
+
const token = await getToken();
|
|
1670
1708
|
return { content: [{
|
|
1671
1709
|
type: "text",
|
|
1672
|
-
text: formatList((await helpCenterGet(subdomain,
|
|
1710
|
+
text: formatList((await helpCenterGet(subdomain, token, "/articles/labels")).labels ?? [], formatLabel)
|
|
1673
1711
|
}] };
|
|
1674
1712
|
}
|
|
1675
1713
|
},
|
|
@@ -1687,9 +1725,10 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1687
1725
|
openWorldHint: true
|
|
1688
1726
|
},
|
|
1689
1727
|
handler: async () => {
|
|
1728
|
+
const token = await getToken();
|
|
1690
1729
|
return { content: [{
|
|
1691
1730
|
type: "text",
|
|
1692
|
-
text: formatList((await helpCenterGet(subdomain,
|
|
1731
|
+
text: formatList((await helpCenterGet(subdomain, token, "/user_segments")).user_segments ?? [], formatUserSegment)
|
|
1693
1732
|
}] };
|
|
1694
1733
|
}
|
|
1695
1734
|
},
|
|
@@ -1708,7 +1747,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1708
1747
|
},
|
|
1709
1748
|
handler: async (params) => {
|
|
1710
1749
|
const { article_id } = params;
|
|
1711
|
-
const
|
|
1750
|
+
const token = await getToken();
|
|
1751
|
+
const attachments = (await helpCenterGet(subdomain, token, `/articles/${article_id}/attachments`)).article_attachments ?? [];
|
|
1712
1752
|
if (attachments.length === 0) return { content: [{
|
|
1713
1753
|
type: "text",
|
|
1714
1754
|
text: `No attachments found on article #${article_id}.`
|
|
@@ -1780,7 +1820,8 @@ const createHelpCenterTools = (ctx) => {
|
|
|
1780
1820
|
},
|
|
1781
1821
|
handler: async (params) => {
|
|
1782
1822
|
const { article_id, locale, section_index, format } = params;
|
|
1783
|
-
const
|
|
1823
|
+
const token = await getToken();
|
|
1824
|
+
const { translation } = await helpCenterGet(subdomain, token, `/articles/${article_id}/translations/${locale}`);
|
|
1784
1825
|
const sections = parseSections(translation.body);
|
|
1785
1826
|
const section = sections[section_index];
|
|
1786
1827
|
if (!section) throw new Error(`Section index ${section_index} not found. Article has ${sections.length} section(s) (0-${Math.max(0, sections.length - 1)}).`);
|
|
@@ -1952,7 +1993,8 @@ const createSearchTools = (ctx) => {
|
|
|
1952
1993
|
},
|
|
1953
1994
|
handler: async (params) => {
|
|
1954
1995
|
const { query, per_page, page } = params;
|
|
1955
|
-
const
|
|
1996
|
+
const token = await getToken();
|
|
1997
|
+
const response = await zendeskGet(subdomain, token, "/search", {
|
|
1956
1998
|
query,
|
|
1957
1999
|
...buildOffsetParams(per_page, page)
|
|
1958
2000
|
});
|
|
@@ -2156,7 +2198,8 @@ const createTicketTools = (ctx) => {
|
|
|
2156
2198
|
},
|
|
2157
2199
|
handler: async (params) => {
|
|
2158
2200
|
const { query, per_page, page } = params;
|
|
2159
|
-
const
|
|
2201
|
+
const token = await getToken();
|
|
2202
|
+
const response = await zendeskGet(subdomain, token, "/search", {
|
|
2160
2203
|
query: `type:ticket ${query}`,
|
|
2161
2204
|
include: "tickets(slas)",
|
|
2162
2205
|
...buildOffsetParams(per_page, page)
|
|
@@ -2173,7 +2216,7 @@ const createTicketTools = (ctx) => {
|
|
|
2173
2216
|
namespace: "tickets",
|
|
2174
2217
|
readOnly: false,
|
|
2175
2218
|
title: "Create Zendesk Ticket",
|
|
2176
|
-
description: "Create a new Zendesk support ticket with subject, description, and optional priority/type/assignee/tags. The description becomes the first public comment of the ticket, and the new ticket id is returned. After creation, use update_ticket to change status or assignee, add_public_comment or add_private_note to reply, and manage_tags to adjust tags. Look up valid assignee_id / group_id and custom field ids via search_users or your Zendesk admin settings.",
|
|
2219
|
+
description: "Create a new Zendesk support ticket with subject, description, and optional priority/type/assignee/tags. The description becomes the first public comment of the ticket, and the new ticket id is returned. After creation, use update_ticket to change status or assignee, add_public_comment or add_private_note to reply, and manage_tags to adjust tags. Look up valid assignee_id / group_id and custom field ids via search_users or your Zendesk admin settings. Discover custom field ids and their accepted option values with list_ticket_fields.",
|
|
2177
2220
|
inputSchema: z.object({
|
|
2178
2221
|
subject: z.string().min(1).describe("Ticket subject — the short summary line shown in ticket lists and search results."),
|
|
2179
2222
|
description: z.string().min(1).describe("Ticket description — the body of the request. It becomes the ticket's first public comment (visible to the requester)."),
|
|
@@ -2195,7 +2238,7 @@ const createTicketTools = (ctx) => {
|
|
|
2195
2238
|
custom_fields: z.array(z.object({
|
|
2196
2239
|
id: z.number().int(),
|
|
2197
2240
|
value: z.unknown()
|
|
2198
|
-
})).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings).")
|
|
2241
|
+
})).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts.")
|
|
2199
2242
|
}),
|
|
2200
2243
|
annotations: {
|
|
2201
2244
|
readOnlyHint: false,
|
|
@@ -2205,7 +2248,8 @@ const createTicketTools = (ctx) => {
|
|
|
2205
2248
|
},
|
|
2206
2249
|
handler: async (params) => {
|
|
2207
2250
|
const { subject, description, ...rest } = params;
|
|
2208
|
-
const
|
|
2251
|
+
const token = await getToken();
|
|
2252
|
+
const { ticket } = await zendeskPost(subdomain, token, "/tickets", { ticket: {
|
|
2209
2253
|
subject,
|
|
2210
2254
|
comment: { body: description },
|
|
2211
2255
|
...rest
|
|
@@ -2251,7 +2295,7 @@ const createTicketTools = (ctx) => {
|
|
|
2251
2295
|
custom_fields: z.array(z.object({
|
|
2252
2296
|
id: z.number().int(),
|
|
2253
2297
|
value: z.unknown()
|
|
2254
|
-
})).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings).")
|
|
2298
|
+
})).optional().describe("Custom field values as { id, value } pairs (field ids come from your Zendesk admin settings). Call list_ticket_fields first to discover the numeric field ids and, for dropdown/multiselect fields, the exact option values Zendesk accepts.")
|
|
2255
2299
|
}),
|
|
2256
2300
|
annotations: {
|
|
2257
2301
|
readOnlyHint: false,
|
|
@@ -2261,7 +2305,8 @@ const createTicketTools = (ctx) => {
|
|
|
2261
2305
|
},
|
|
2262
2306
|
handler: async (params) => {
|
|
2263
2307
|
const { ticket_id, ...updates } = params;
|
|
2264
|
-
const
|
|
2308
|
+
const token = await getToken();
|
|
2309
|
+
const { ticket } = await zendeskPut(subdomain, token, `/tickets/${ticket_id}`, { ticket: updates });
|
|
2265
2310
|
return { content: [{
|
|
2266
2311
|
type: "text",
|
|
2267
2312
|
text: `Ticket #${ticket.id} updated.\n\n${formatTicket(ticket)}`
|
|
@@ -2350,7 +2395,8 @@ const createTicketTools = (ctx) => {
|
|
|
2350
2395
|
},
|
|
2351
2396
|
handler: async (params) => {
|
|
2352
2397
|
const { page_size, cursor } = params;
|
|
2353
|
-
const
|
|
2398
|
+
const token = await getToken();
|
|
2399
|
+
const response = await zendeskGet(subdomain, token, "/tickets", buildCursorParams(page_size, cursor));
|
|
2354
2400
|
const tickets = response.tickets ?? [];
|
|
2355
2401
|
return { content: [{
|
|
2356
2402
|
type: "text",
|
|
@@ -2373,7 +2419,8 @@ const createTicketTools = (ctx) => {
|
|
|
2373
2419
|
},
|
|
2374
2420
|
handler: async (params) => {
|
|
2375
2421
|
const { problem_id } = params;
|
|
2376
|
-
const
|
|
2422
|
+
const token = await getToken();
|
|
2423
|
+
const incidents = (await zendeskGet(subdomain, token, `/tickets/${problem_id}/incidents`)).tickets ?? [];
|
|
2377
2424
|
return { content: [{
|
|
2378
2425
|
type: "text",
|
|
2379
2426
|
text: truncateIfNeeded(incidents.length > 0 ? `# Incidents linked to problem #${problem_id}\n\n${incidents.map(formatTicket).join("\n\n")}` : `No incidents linked to problem #${problem_id}.`)
|
|
@@ -2451,6 +2498,33 @@ const createTicketTools = (ctx) => {
|
|
|
2451
2498
|
})
|
|
2452
2499
|
}] };
|
|
2453
2500
|
}
|
|
2501
|
+
},
|
|
2502
|
+
{
|
|
2503
|
+
name: "list_ticket_fields",
|
|
2504
|
+
namespace: "tickets",
|
|
2505
|
+
readOnly: true,
|
|
2506
|
+
title: "List Ticket Fields",
|
|
2507
|
+
description: "List the ticket field definitions configured on this Zendesk (both system fields and custom fields), returning each field's id, type, whether it is active/required, and — for dropdown and multiselect fields — the valid option values. Use this to discover the numeric field ids and accepted option tags that create_ticket and update_ticket expect in their custom_fields argument, so a natural-language intent (\"set severity to High\") maps to the right id and a value Zendesk will accept instead of a blind guess. Read-only reference lookup; cursor-paginated in Zendesk's default field order.",
|
|
2508
|
+
inputSchema: z.object({
|
|
2509
|
+
page_size: z.number().int().min(1).max(100).default(100).describe("Ticket field definitions per page (1-100, default 100)."),
|
|
2510
|
+
cursor: z.string().optional().describe("Pagination cursor from a previous response; omit for the first page.")
|
|
2511
|
+
}),
|
|
2512
|
+
annotations: {
|
|
2513
|
+
readOnlyHint: true,
|
|
2514
|
+
destructiveHint: false,
|
|
2515
|
+
idempotentHint: true,
|
|
2516
|
+
openWorldHint: true
|
|
2517
|
+
},
|
|
2518
|
+
handler: async (params) => {
|
|
2519
|
+
const { page_size, cursor } = params;
|
|
2520
|
+
const token = await getToken();
|
|
2521
|
+
const response = await zendeskGet(subdomain, token, "/ticket_fields", buildCursorParams(page_size, cursor));
|
|
2522
|
+
const fields = response.ticket_fields ?? [];
|
|
2523
|
+
return { content: [{
|
|
2524
|
+
type: "text",
|
|
2525
|
+
text: formatList(fields, formatTicketField, extractPaginationMeta(response, fields.length))
|
|
2526
|
+
}] };
|
|
2527
|
+
}
|
|
2454
2528
|
}
|
|
2455
2529
|
];
|
|
2456
2530
|
};
|
|
@@ -2473,7 +2547,8 @@ const createUserTools = (ctx) => {
|
|
|
2473
2547
|
openWorldHint: true
|
|
2474
2548
|
},
|
|
2475
2549
|
handler: async () => {
|
|
2476
|
-
const
|
|
2550
|
+
const token = await getToken();
|
|
2551
|
+
const { user } = await zendeskGet(subdomain, token, "/users/me");
|
|
2477
2552
|
return { content: [{
|
|
2478
2553
|
type: "text",
|
|
2479
2554
|
text: formatUser(user)
|
|
@@ -2499,7 +2574,8 @@ const createUserTools = (ctx) => {
|
|
|
2499
2574
|
},
|
|
2500
2575
|
handler: async (params) => {
|
|
2501
2576
|
const { query, per_page, page } = params;
|
|
2502
|
-
const
|
|
2577
|
+
const token = await getToken();
|
|
2578
|
+
const response = await zendeskGet(subdomain, token, "/search", {
|
|
2503
2579
|
query: `type:user ${query}`,
|
|
2504
2580
|
...buildOffsetParams(per_page, page)
|
|
2505
2581
|
});
|
|
@@ -2524,7 +2600,8 @@ const createUserTools = (ctx) => {
|
|
|
2524
2600
|
},
|
|
2525
2601
|
handler: async (params) => {
|
|
2526
2602
|
const { user_id } = params;
|
|
2527
|
-
const
|
|
2603
|
+
const token = await getToken();
|
|
2604
|
+
const { user } = await zendeskGet(subdomain, token, `/users/${user_id}`);
|
|
2528
2605
|
return { content: [{
|
|
2529
2606
|
type: "text",
|
|
2530
2607
|
text: formatUser(user)
|
|
@@ -2546,7 +2623,8 @@ const createUserTools = (ctx) => {
|
|
|
2546
2623
|
},
|
|
2547
2624
|
handler: async (params) => {
|
|
2548
2625
|
const { organization_id } = params;
|
|
2549
|
-
const
|
|
2626
|
+
const token = await getToken();
|
|
2627
|
+
const { organization } = await zendeskGet(subdomain, token, `/organizations/${organization_id}`);
|
|
2550
2628
|
return { content: [{
|
|
2551
2629
|
type: "text",
|
|
2552
2630
|
text: formatOrganization(organization)
|
|
@@ -2571,7 +2649,8 @@ const createUserTools = (ctx) => {
|
|
|
2571
2649
|
},
|
|
2572
2650
|
handler: async (params) => {
|
|
2573
2651
|
const { page_size, cursor } = params;
|
|
2574
|
-
const
|
|
2652
|
+
const token = await getToken();
|
|
2653
|
+
const response = await zendeskGet(subdomain, token, "/organizations", buildCursorParams(page_size, cursor));
|
|
2575
2654
|
const organizations = response.organizations ?? [];
|
|
2576
2655
|
return { content: [{
|
|
2577
2656
|
type: "text",
|
|
@@ -2899,7 +2978,8 @@ const sendJsonRpcError = (res, status, code, message, headers = {}) => {
|
|
|
2899
2978
|
}));
|
|
2900
2979
|
};
|
|
2901
2980
|
const sendUnauthorized = (res, resource) => {
|
|
2902
|
-
|
|
2981
|
+
const wwwAuthenticate = `Bearer resource_metadata="${resource}/.well-known/oauth-protected-resource", error="invalid_token", error_description="${MISSING_BEARER_MESSAGE}"`;
|
|
2982
|
+
sendJsonRpcError(res, 401, -32e3, MISSING_BEARER_MESSAGE, { "WWW-Authenticate": wwwAuthenticate });
|
|
2903
2983
|
};
|
|
2904
2984
|
const readJsonBody = (req, maxBodyBytes) => new Promise((resolve) => {
|
|
2905
2985
|
const chunks = [];
|