feat(mcp): sprints, epics, backlog, trash, checklists and release notes

Thirty new tools (54 in all) for what the API gained since the MCP:
create a project and read its public showcase; sprints (list, progress
and burndown, plan, change, start, close with carry-over, delete, move a
ticket in or out); epics and the backlog (list epics with progress, set
a ticket's parent, rank); bulk edit of up to 100 tickets by key, saying
which refused; the trash; checklists; following a ticket, removing a
relation, editing and deleting comments; editing and archiving releases,
drafting and saving their notes; notifications.

Searching filters by epic, sprint and dates and sorts by backlog order;
get_issue shows parent, checklist, commits and followers; create and
update take parent and start date.

API: `unreadOnly` and `dryRun` were documented as objects in the
OpenAPI; they are booleans now. Chapter 9 lists every tool; Word and PDF
regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 10:17:10 +02:00
parent 58fe5e8301
commit e6ac02d328
16 changed files with 1129 additions and 54 deletions

View File

@@ -54,7 +54,7 @@ export class ReleasesResolver {
@Tool({
name: 'get_release',
description:
'A release with its progress (finished out of total), its tickets and, optionally, the day-by-day burnup.',
'A release with its progress (finished out of total), its notes, its tickets and, optionally, the day-by-day burnup.',
paramsSchema: z.object({
releaseId,
withBurnup: z.boolean().default(false),
@@ -90,6 +90,7 @@ export class ReleasesResolver {
return {
...releaseLine(release),
description: release.description,
notes: release.notes,
progress,
issues: issues.items.map(issueLine),
burnup,
@@ -148,4 +149,104 @@ export class ReleasesResolver {
),
);
}
@Tool({
name: 'update_release',
description: 'Rename a release, describe it or move its target date.',
paramsSchema: z.object({
releaseId,
name: z.string().min(1).max(60).optional(),
description: z.string().max(5000).nullable().optional(),
targetDate: z.string().nullable().optional().describe('ISO date'),
}),
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
},
})
update(
params: {
releaseId: string;
name?: string;
description?: string | null;
targetDate?: string | null;
},
ctx: McpContext,
) {
const { releaseId, ...body } = params;
return toolResult(() =>
this.api.data(
this.api.as(ctx).PATCH('/api/releases/{releaseId}', {
params: { path: { releaseId } },
body,
}),
),
);
}
@Tool({
name: 'archive_release',
description:
'Archive a planned release that will not ship; its tickets leave it.',
paramsSchema: z.object({ releaseId }),
annotations: { readOnlyHint: false, destructiveHint: true },
})
archive({ releaseId }: { releaseId: string }, ctx: McpContext) {
return toolResult(() =>
this.api.data(
this.api.as(ctx).POST('/api/releases/{releaseId}/archive', {
params: { path: { releaseId } },
}),
),
);
}
@Tool({
name: 'draft_release_notes',
description:
"A Markdown draft of a release's notes from its finished tickets, grouped by type. Nothing is saved: polish it and save it with set_release_notes.",
paramsSchema: z.object({ releaseId }),
annotations: READ,
})
draftNotes({ releaseId }: { releaseId: string }, ctx: McpContext) {
return toolResult(
async () =>
(
await this.api.data(
this.api.as(ctx).GET('/api/releases/{releaseId}/notes/draft', {
params: { path: { releaseId } },
}),
)
).markdown,
);
}
@Tool({
name: 'set_release_notes',
description:
'Write the notes of a release (Markdown, for people: what shipped). null clears them.',
paramsSchema: z.object({
releaseId,
notes: z.string().max(20000).nullable(),
}),
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
},
})
setNotes(
{ releaseId, notes }: { releaseId: string; notes: string | null },
ctx: McpContext,
) {
return toolResult(() =>
this.api.data(
this.api.as(ctx).PUT('/api/releases/{releaseId}/notes', {
params: { path: { releaseId } },
body: { notes },
}),
),
);
}
}