@fruggr/zendesk-mcp-server 3.6.0 → 3.7.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/THIRD-PARTY-NOTICES.md +2 -2
- package/dist/{http-DkjvhtQj.js → http-D-Xy9BWD.js} +450 -390
- package/dist/index.js +41 -22
- package/dist/sbom.cdx.json +10 -10
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -30084,7 +30084,7 @@ const createHelpCenterTools = (env) => {
|
|
|
30084
30084
|
namespace: "help_center",
|
|
30085
30085
|
readOnly: true,
|
|
30086
30086
|
title: "List Permission Groups",
|
|
30087
|
-
description: "List all Guide permission groups. Use this to find the permission_group_id required when creating articles.",
|
|
30087
|
+
description: "List all Guide permission groups, which control who can edit and publish an article, with each group's id, name and whether it is built-in. Use this to find the permission_group_id required when creating articles with create_article, or to change it with update_article; to control who can view an article instead, use list_user_segments. Zendesk restricts this list to Guide admins and Help Center managers: without that role the call fails, and you can reuse the permission_group_id of an existing article read with get_article.",
|
|
30088
30088
|
inputSchema: object({}),
|
|
30089
30089
|
annotations: {
|
|
30090
30090
|
readOnlyHint: true,
|
|
@@ -30149,7 +30149,7 @@ const createHelpCenterTools = (env) => {
|
|
|
30149
30149
|
namespace: "help_center",
|
|
30150
30150
|
readOnly: false,
|
|
30151
30151
|
title: "Update Help Center Article",
|
|
30152
|
-
description: "Update article metadata only (draft, promoted, labels as label_names, content tags as content_tag_ids, visibility as permission_group_id / user_segment_id, section_id, sort position, etc.) and return the updated article. Does NOT update content (title, body) — use update_article_translation for that.",
|
|
30152
|
+
description: "Update article metadata only (draft, promoted, labels as label_names, content tags as content_tag_ids, visibility as permission_group_id / user_segment_id, section_id, sort position, author, etc.) and return the updated article; fields you omit keep their value. Does NOT update content (title, body) — use update_article_translation for that, or update_article_section to edit one section. To put an article first or last in its section, prefer reorder_article: a plain position write is ambiguous while articles share position 0. To remove an article from the Help Center, use archive_article (a Guide admin can restore it); draft: true only unpublishes it.",
|
|
30153
30153
|
inputSchema: object({
|
|
30154
30154
|
article_id: number$1().int().describe("Article ID — the numeric id of the article to update. Obtain it from list_articles or search_articles."),
|
|
30155
30155
|
draft: boolean().optional().describe("Set true to unpublish the article (revert to draft) or false to publish it."),
|
|
@@ -30336,7 +30336,7 @@ const createHelpCenterTools = (env) => {
|
|
|
30336
30336
|
namespace: "help_center",
|
|
30337
30337
|
readOnly: true,
|
|
30338
30338
|
title: "List Article Labels",
|
|
30339
|
-
description: "List all article labels. Labels improve Help Center search ranking and are not visible to end users. Attach them to an article through the label_names parameter of create_article or update_article.",
|
|
30339
|
+
description: "List all article labels, with each label's id and name. Labels improve Help Center search ranking and are not visible to end users. Attach them to an article through the label_names parameter of create_article or update_article, reusing an existing label rather than coining a near-duplicate. Labels are not content tags: for the end-user-visible tags readers browse by, use list_content_tags.",
|
|
30340
30340
|
inputSchema: object({ ...brandIdPart }),
|
|
30341
30341
|
annotations: {
|
|
30342
30342
|
readOnlyHint: true,
|
|
@@ -30360,7 +30360,7 @@ const createHelpCenterTools = (env) => {
|
|
|
30360
30360
|
namespace: "help_center",
|
|
30361
30361
|
readOnly: true,
|
|
30362
30362
|
title: "List User Segments",
|
|
30363
|
-
description: "List all user segments
|
|
30363
|
+
description: "List all Help Center user segments, which control article visibility (who can view an article), with each segment's id, name, user type and whether it is built-in. Use the id as user_segment_id when creating or updating articles with create_article or update_article; to control who can edit an article instead, use list_permission_groups. Zendesk restricts this list to Guide admins and Help Center managers: without that role the call fails, and you can reuse the user_segment_id of an existing article from get_article, or omit it on create_article to make the article visible to everyone.",
|
|
30364
30364
|
inputSchema: object({}),
|
|
30365
30365
|
annotations: {
|
|
30366
30366
|
readOnlyHint: true,
|
|
@@ -31106,8 +31106,8 @@ const createRequestTools = (env) => {
|
|
|
31106
31106
|
body: string().min(1).describe("The request itself: what happened, what was expected, and any steps to reproduce. Becomes the first public comment on the ticket."),
|
|
31107
31107
|
form_id: number$1().int().optional().describe("Form id chosen from list_request_forms, deciding which questions apply. Omit to use the account default form."),
|
|
31108
31108
|
custom_fields: array(object({
|
|
31109
|
-
id: number$1().int(),
|
|
31110
|
-
value: unknown()
|
|
31109
|
+
id: number$1().int().describe("Numeric id of a question on the chosen form, from get_request_form."),
|
|
31110
|
+
value: unknown().describe("The answer, typed after the question: the option tag (a string, never a number) for a dropdown, an array of option tags for a multiselect, true or false for a checkbox, text otherwise.")
|
|
31111
31111
|
}).strict()).optional().describe("Answers to the form's own questions, as { id, value } pairs. Take both the ids and the accepted values from get_request_form; an id this form does not carry, or a value a dropdown does not offer, is refused here because Zendesk would drop it without an error. Option values are the tag strings, never numbers."),
|
|
31112
31112
|
attachments: attachmentsParam("Files to attach to the request, such as a screenshot or a log, with their content base64-encoded.")
|
|
31113
31113
|
}),
|
|
@@ -31795,6 +31795,7 @@ const fetchAllTicketComments = async (subdomain, token, ticketId) => {
|
|
|
31795
31795
|
const all = [];
|
|
31796
31796
|
let cursor;
|
|
31797
31797
|
let pages = 0;
|
|
31798
|
+
let truncated = false;
|
|
31798
31799
|
while (pages < MAX_COMMENT_PAGES) {
|
|
31799
31800
|
const response = await zendeskGet(subdomain, token, `/tickets/${ticketId}/comments`, {
|
|
31800
31801
|
...buildCursorParams(100, cursor),
|
|
@@ -31804,9 +31805,14 @@ const fetchAllTicketComments = async (subdomain, token, ticketId) => {
|
|
|
31804
31805
|
pages += 1;
|
|
31805
31806
|
if (!response.meta?.has_more || !response.meta?.after_cursor) break;
|
|
31806
31807
|
cursor = response.meta.after_cursor;
|
|
31808
|
+
truncated = pages === MAX_COMMENT_PAGES;
|
|
31807
31809
|
}
|
|
31808
|
-
return
|
|
31810
|
+
return {
|
|
31811
|
+
comments: all,
|
|
31812
|
+
truncated
|
|
31813
|
+
};
|
|
31809
31814
|
};
|
|
31815
|
+
const COMMENT_CAP_NOTICE = `Comment scan stopped at its page cap (${MAX_COMMENT_PAGES} pages, COMMENT_MAX_PAGES): attachments on any later comments are not listed. Pass attachment_ids taken from list_ticket_comments to fetch them.`;
|
|
31810
31816
|
const commentPageMeta = (response, itemCount) => extractPaginationMeta({
|
|
31811
31817
|
...response.meta && { meta: response.meta },
|
|
31812
31818
|
...response.count !== void 0 && { count: response.count }
|
|
@@ -32108,6 +32114,10 @@ const createTicketTools = (env) => {
|
|
|
32108
32114
|
const ids = array(number$1().int().positive());
|
|
32109
32115
|
return (max === void 0 ? ids : ids.max(max)).optional().describe(description);
|
|
32110
32116
|
};
|
|
32117
|
+
const customFieldEntry = object({
|
|
32118
|
+
id: number$1().int().describe("Numeric id of the ticket field to set, as listed by list_ticket_fields."),
|
|
32119
|
+
value: unknown().describe("Value typed after the field: the option tag (a string) for a dropdown, an array of option tags for a multiselect, true or false for a checkbox, text or a number for a text or numeric field. For any other type (a date, a lookup), follow the type list_ticket_fields reports; it also lists each dropdown's accepted tags.")
|
|
32120
|
+
}).strict();
|
|
32111
32121
|
return [
|
|
32112
32122
|
{
|
|
32113
32123
|
name: "get_ticket",
|
|
@@ -32245,10 +32255,10 @@ const createTicketTools = (env) => {
|
|
|
32245
32255
|
namespace: "tickets",
|
|
32246
32256
|
readOnly: true,
|
|
32247
32257
|
title: "Get Zendesk Ticket Attachments",
|
|
32248
|
-
description: "
|
|
32258
|
+
description: "Download the files attached to a ticket's comments so you can look at them: images come back embedded inline, other files as text references (file name, id, content type, size, download URL). Use it to see an attached image, or a file's name, size and download URL (only images come back with their content); to learn which files a comment carries, list_ticket_comments already shows their ids. Pass those ids as attachment_ids to fetch only them, or omit it to collect the attachments of the ticket's comments, read up to a page cap (10 pages of 100 comments by default); past it the response says later comments were not scanned, and attachment_ids fetches their files. An image over the per-image size limit or past the embedded-image cap comes back as a reference saying why, and once the response size budget is spent the remaining files are counted as omitted.",
|
|
32249
32259
|
inputSchema: object({
|
|
32250
32260
|
ticket_id: number$1().int().describe("Ticket ID — the numeric id of the ticket whose attachments to fetch. Obtain it from search_tickets or list_tickets."),
|
|
32251
|
-
attachment_ids: array(number$1().int()).optional().describe("Attachment IDs to fetch directly (e.g. extracted from a previous list_ticket_comments or get_ticket(include_comments=true) call). When provided, skips the comments fetch entirely. When omitted,
|
|
32261
|
+
attachment_ids: array(number$1().int()).optional().describe("Attachment IDs to fetch directly (e.g. extracted from a previous list_ticket_comments or get_ticket(include_comments=true) call). When provided, skips the comments fetch entirely. When omitted, the attachments of the ticket's comments are returned, read up to a page cap: on a very long thread the scan stops there, and the response says so.")
|
|
32252
32262
|
}),
|
|
32253
32263
|
annotations: {
|
|
32254
32264
|
readOnlyHint: true,
|
|
@@ -32259,16 +32269,31 @@ const createTicketTools = (env) => {
|
|
|
32259
32269
|
handler: async (params, { caller }) => {
|
|
32260
32270
|
const { ticket_id, attachment_ids } = params;
|
|
32261
32271
|
const token = await caller.getToken();
|
|
32262
|
-
const attachments = attachment_ids && attachment_ids.length > 0 ?
|
|
32272
|
+
const { attachments, truncated } = attachment_ids && attachment_ids.length > 0 ? {
|
|
32273
|
+
attachments: await fetchAttachmentsByIds(subdomain, token, attachment_ids),
|
|
32274
|
+
truncated: false
|
|
32275
|
+
} : await fetchAllTicketComments(subdomain, token, ticket_id).then((walk) => ({
|
|
32276
|
+
attachments: walk.comments.flatMap((c) => c.attachments ?? []),
|
|
32277
|
+
truncated: walk.truncated
|
|
32278
|
+
}));
|
|
32263
32279
|
if (attachments.length === 0) return { content: [{
|
|
32264
32280
|
type: "text",
|
|
32265
|
-
text: `No attachments found on ticket #${ticket_id}.`
|
|
32281
|
+
text: truncated ? `No attachments found on the first ${MAX_COMMENT_PAGES} pages of comments of ticket #${ticket_id}. ${COMMENT_CAP_NOTICE}` : `No attachments found on ticket #${ticket_id}.`
|
|
32266
32282
|
}] };
|
|
32267
32283
|
const header = {
|
|
32268
32284
|
type: "text",
|
|
32269
32285
|
text: `# Attachments for ticket #${ticket_id} (${attachments.length} total)`
|
|
32270
32286
|
};
|
|
32271
|
-
|
|
32287
|
+
const notice = truncated ? [{
|
|
32288
|
+
type: "text",
|
|
32289
|
+
text: COMMENT_CAP_NOTICE
|
|
32290
|
+
}] : [];
|
|
32291
|
+
const blocks = await collectAttachmentBlocks(subdomain, token, attachments, [header, ...notice].reduce((total, block) => total + blockCost(block), 1));
|
|
32292
|
+
return { content: [
|
|
32293
|
+
header,
|
|
32294
|
+
...notice,
|
|
32295
|
+
...blocks
|
|
32296
|
+
] };
|
|
32272
32297
|
}
|
|
32273
32298
|
},
|
|
32274
32299
|
{
|
|
@@ -32334,10 +32359,7 @@ const createTicketTools = (env) => {
|
|
|
32334
32359
|
assignee_id: number$1().int().optional().describe("User id of the agent to assign the ticket to."),
|
|
32335
32360
|
group_id: number$1().int().optional().describe("Id of the group to assign the ticket to."),
|
|
32336
32361
|
tags: array(string()).optional().describe("Tags to set on the new ticket. Each tag is a single lowercase token (join multi-word tags with an underscore). Use manage_tags later to add or remove individual tags."),
|
|
32337
|
-
custom_fields: array(
|
|
32338
|
-
id: number$1().int(),
|
|
32339
|
-
value: unknown()
|
|
32340
|
-
}).strict()).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.")
|
|
32362
|
+
custom_fields: array(customFieldEntry).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.")
|
|
32341
32363
|
}),
|
|
32342
32364
|
annotations: {
|
|
32343
32365
|
readOnlyHint: false,
|
|
@@ -32391,10 +32413,7 @@ const createTicketTools = (env) => {
|
|
|
32391
32413
|
group_id: number$1().int().optional().describe("Id of the group to assign the ticket to."),
|
|
32392
32414
|
subject: string().optional().describe("New subject line for the ticket; replaces the current subject when provided."),
|
|
32393
32415
|
tags: array(string()).optional().describe("Replaces the full tag set on the ticket. Use manage_tags for incremental add/remove."),
|
|
32394
|
-
custom_fields: array(
|
|
32395
|
-
id: number$1().int(),
|
|
32396
|
-
value: unknown()
|
|
32397
|
-
}).strict()).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."),
|
|
32416
|
+
custom_fields: array(customFieldEntry).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."),
|
|
32398
32417
|
followers: object({
|
|
32399
32418
|
add: subscriberIdList("User ids to start following the ticket. Omit to only remove."),
|
|
32400
32419
|
remove: subscriberIdList("User ids to stop following the ticket. Omit to only add.")
|
|
@@ -33048,7 +33067,7 @@ const createUserTools = (env) => {
|
|
|
33048
33067
|
namespace: "users",
|
|
33049
33068
|
readOnly: true,
|
|
33050
33069
|
title: "Search Zendesk Users",
|
|
33051
|
-
description: "Search for users by name, email, or other criteria using Zendesk search query syntax
|
|
33070
|
+
description: "Search for users by name, email, or other criteria using Zendesk search query syntax, returning each match's id, name, email, role, active flag and organization id, with the total count. Use it to turn a person into the numeric user id other tools take, such as the followers of update_ticket; a name can match several users, so confirm the right one. With an id already in hand, call get_user to read that one user, and for your own account get_current_user. Prefer it over `search` when you want users only: `search` mixes in tickets and organizations unless the query says type:user.",
|
|
33052
33071
|
inputSchema: object({
|
|
33053
33072
|
query: string().min(1).describe("Zendesk user search query — free text matched against name and email, and/or field filters like \"email:jane@acme.com\", \"role:agent\", \"organization_id:123\". A \"type:user\" scope is added automatically."),
|
|
33054
33073
|
per_page: number$1().int().min(1).max(100).default(100).describe(PER_PAGE_DESC),
|
|
@@ -35177,7 +35196,7 @@ const main = async () => {
|
|
|
35177
35196
|
logger,
|
|
35178
35197
|
cleanup: async () => (await starting?.catch(() => void 0))?.close()
|
|
35179
35198
|
});
|
|
35180
|
-
const { startHttpTransport } = await import("./http-
|
|
35199
|
+
const { startHttpTransport } = await import("./http-D-Xy9BWD.js");
|
|
35181
35200
|
starting = startHttpTransport(config, logger);
|
|
35182
35201
|
await starting;
|
|
35183
35202
|
};
|
package/dist/sbom.cdx.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:081eda00-f41f-539e-8e22-325ebea4a10a",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
7
|
"component": {
|
|
8
8
|
"type": "application",
|
|
9
|
-
"bom-ref": "pkg:npm/%40fruggr/zendesk-mcp-server@3.
|
|
9
|
+
"bom-ref": "pkg:npm/%40fruggr/zendesk-mcp-server@3.7.0",
|
|
10
10
|
"name": "@fruggr/zendesk-mcp-server",
|
|
11
|
-
"version": "3.
|
|
12
|
-
"purl": "pkg:npm/%40fruggr/zendesk-mcp-server@3.
|
|
11
|
+
"version": "3.7.0",
|
|
12
|
+
"purl": "pkg:npm/%40fruggr/zendesk-mcp-server@3.7.0"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"components": [
|
|
@@ -927,10 +927,10 @@
|
|
|
927
927
|
},
|
|
928
928
|
{
|
|
929
929
|
"type": "library",
|
|
930
|
-
"bom-ref": "pkg:npm/jose@6.2.
|
|
930
|
+
"bom-ref": "pkg:npm/jose@6.2.13",
|
|
931
931
|
"name": "jose",
|
|
932
|
-
"version": "6.2.
|
|
933
|
-
"purl": "pkg:npm/jose@6.2.
|
|
932
|
+
"version": "6.2.13",
|
|
933
|
+
"purl": "pkg:npm/jose@6.2.13",
|
|
934
934
|
"licenses": [
|
|
935
935
|
{
|
|
936
936
|
"expression": "MIT"
|
|
@@ -1539,10 +1539,10 @@
|
|
|
1539
1539
|
},
|
|
1540
1540
|
{
|
|
1541
1541
|
"type": "library",
|
|
1542
|
-
"bom-ref": "pkg:npm/oidc-provider@9.
|
|
1542
|
+
"bom-ref": "pkg:npm/oidc-provider@9.13.0",
|
|
1543
1543
|
"name": "oidc-provider",
|
|
1544
|
-
"version": "9.
|
|
1545
|
-
"purl": "pkg:npm/oidc-provider@9.
|
|
1544
|
+
"version": "9.13.0",
|
|
1545
|
+
"purl": "pkg:npm/oidc-provider@9.13.0",
|
|
1546
1546
|
"licenses": [
|
|
1547
1547
|
{
|
|
1548
1548
|
"expression": "MIT"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fruggr/zendesk-mcp-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.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",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"test:smoke": "pnpm build && node scripts/smoke-test.mjs",
|
|
34
34
|
"test:bundle": "pnpm build && vitest run --config vitest.bundle.config.ts",
|
|
35
35
|
"bundle:size": "pnpm build && node scripts/bundle-size.mjs",
|
|
36
|
-
"mcp:live": "tsx scripts/mcp-live.ts"
|
|
36
|
+
"mcp:live": "tsx scripts/mcp-live.ts",
|
|
37
|
+
"tool-definitions:report": "tsx scripts/tool-definitions-report.ts"
|
|
37
38
|
},
|
|
38
39
|
"keywords": [
|
|
39
40
|
"mcp",
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
"engines": {
|
|
74
75
|
"node": ">=22"
|
|
75
76
|
},
|
|
76
|
-
"packageManager": "pnpm@12.
|
|
77
|
+
"packageManager": "pnpm@12.16.1+sha512.091d1dee7927575661b25dab3c2ae31cafc1c54d6b71ed6a6600cf0c1cb10434a447eae14015f1b23e2e6bedeed79df94a932e2164d2832280dce3fb32f656d1",
|
|
77
78
|
"devDependencies": {
|
|
78
79
|
"@biomejs/biome": "2.6.2",
|
|
79
80
|
"@modelcontextprotocol/client": "2.3.1",
|
|
@@ -97,10 +98,11 @@
|
|
|
97
98
|
"conventional-changelog-conventionalcommits": "^10.2.1",
|
|
98
99
|
"hast-util-to-html": "9.0.5",
|
|
99
100
|
"hast-util-to-mdast": "10.1.2",
|
|
100
|
-
"jose": "6.2.
|
|
101
|
+
"jose": "6.2.13",
|
|
101
102
|
"lefthook": "^2.2.2",
|
|
103
|
+
"mcp-tdqs": "0.2.0",
|
|
102
104
|
"msw": "^3.0.0",
|
|
103
|
-
"oidc-provider": "~9.
|
|
105
|
+
"oidc-provider": "~9.13.0",
|
|
104
106
|
"open": "11.0.4",
|
|
105
107
|
"rehype-parse": "9.0.1",
|
|
106
108
|
"rehype-raw": "7.0.0",
|