@taiga-ai/mcp-server 0.18.0-dev.450 → 0.19.0-dev.451

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.
@@ -70,7 +70,7 @@ export function registerAutonomyTools(server: McpServer, client: TaigaClient) {
70
70
  'organization and factories allow: where either has turned autonomous merge off, true changes nothing. ' +
71
71
  'Taiga merges through your rules, never around them.',
72
72
  inputSchema: z.object({
73
- itemId: idInput('backlog item', 'taiga_list_backlog_items (query finds one by title) gives it'),
73
+ initiativeId: idInput('initiative', 'taiga_list_initiatives (query finds one by title) gives it'),
74
74
  autoMerge: z
75
75
  .boolean()
76
76
  .nullable()
@@ -80,7 +80,7 @@ export function registerAutonomyTools(server: McpServer, client: TaigaClient) {
80
80
  },
81
81
  (params) =>
82
82
  runTool(async () => {
83
- const item = (await client.updateBacklogItem(params.itemId, { autoMerge: params.autoMerge })).data;
83
+ const item = (await client.updateBacklogItem(params.initiativeId, { autoMerge: params.autoMerge })).data;
84
84
  return toolResult(pickFields([item], ['id', 'title', 'state', 'autoMerge', 'autoBuild'])[0]);
85
85
  })
86
86
  );
@@ -1,14 +1,26 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
- import type { ActivityEvent, ActivityActor, BacklogItem } from '@taiga/types';
3
+ import {
4
+ type ActivityEvent,
5
+ type ActivityActor,
6
+ type BacklogItem,
7
+ BUILD_RUN_STATUSES,
8
+ PLAN_REJECTION_COMMENT_MAX_CHARS,
9
+ PR_CLOSING_STATES,
10
+ USER_DROP_STATES,
11
+ renderHalt,
12
+ resolveAutoBuild,
13
+ type BuildRunStatus,
14
+ type Halt,
15
+ } from '@taiga/types';
4
16
  import { TaigaApiError, type TaigaClient, type WithoutTenant } from '../client.js';
5
- import { PLAN_REJECTION_COMMENT_MAX_CHARS, PR_CLOSING_STATES, USER_DROP_STATES, resolveAutoBuild } from '@taiga/types';
6
17
  import { OPERATE, OPERATE_SETTING, READ_ONLY } from './annotations.js';
7
18
  import { idInput, pagination, productId } from './inputs.js';
8
19
  import { isNetworkFailure, runTool, toolError, toolResult } from './errors.js';
9
20
  import { jobReport } from './jobs.js';
10
21
  import { requestPrompt } from './request-prompt.js';
11
22
  import { queueView } from './queue.js';
23
+ import { landedDisposition } from './builds.js';
12
24
  import {
13
25
  SEARCH_NOTE,
14
26
  SEARCH_PAGE_SIZE,
@@ -62,11 +74,106 @@ const ITEM_OUTCOME_FIELDS = [
62
74
  * leaves it null, and the lifecycle is what says why and what moves it on.
63
75
  */
64
76
  const LIFECYCLE_MEANING =
65
- 'lifecycle says whose turn it is and what moves it on: cta start_plan is taiga_queue_backlog_item, ' +
77
+ 'situation says in one sentence where it stands, whose turn it is and the call that moves it on. The same ' +
78
+ 'as fields: lifecycle says whose turn it is and what moves it on: cta start_plan is taiga_queue_initiative, ' +
66
79
  'retry_plan taiga_plan_again (planFailureCause says why the plan stopped), start_build taiga_approve_plan, ' +
67
- 'resume_build taiga_resume_backlog_item, retry_build a fresh run (start over, in the web), review_pr and ' +
80
+ 'resume_build taiga_resume_initiative, retry_build a fresh run (start over, in the web), review_pr and ' +
68
81
  'view_pr the pull request. needsHumanCause is set only when the line stopped on it.';
69
82
 
83
+ /** The move each call to action is, in the tools' words. */
84
+ const CTA_MOVE: Record<string, string> = {
85
+ start_plan: 'taiga_queue_initiative puts it on the line to be planned and built (paid work: ask first)',
86
+ retry_plan: 'taiga_plan_again asks for a new plan (paid work: ask first)',
87
+ start_build:
88
+ 'read the plan with taiga_get_plan, then taiga_approve_plan builds it or taiga_reject_plan sends it back',
89
+ resume_build: 'taiga_resume_initiative carries the run on (paid work: ask first)',
90
+ retry_build: 'a new run starts over from the web; taiga_move_initiative takes it off the line',
91
+ review_pr: 'a person reviews and merges the pull request',
92
+ view_pr: 'the line merges the pull request once its checks pass',
93
+ };
94
+
95
+ /** How a run stands, as the end of "its latest run ...", by disposition. */
96
+ const RUN_STANDS: Record<string, string> = {
97
+ in_flight: 'is running',
98
+ gated: 'is in review',
99
+ succeeded: 'merged',
100
+ unmerged: 'settled unmerged',
101
+ failed: 'failed',
102
+ stopped: 'was stopped',
103
+ rejected: 'had its pull request closed without merging',
104
+ };
105
+
106
+ /** Where an initiative is, in a phrase, by its lifecycle stage. */
107
+ const STAGE: Record<string, string> = {
108
+ backlog: 'In the backlog, not on the line yet',
109
+ planning: 'Being planned',
110
+ planned: 'Planned',
111
+ building: 'Being built',
112
+ in_review: 'Its pull request is in review',
113
+ done: 'Done',
114
+ canceled: 'Canceled',
115
+ };
116
+
117
+ /**
118
+ * Where an initiative stands and what moves it on, in one sentence (PRO-2800).
119
+ * Five fields carried it (status, state, planningStatus, needsHumanCause,
120
+ * lifecycle) and a reviewing agent had to read the queue, the initiative and
121
+ * the run together to say what had happened; the lifecycle the API derives
122
+ * already decides it. Null for a row without a lifecycle.
123
+ */
124
+ export function situationOf(item: Record<string, unknown>): string | null {
125
+ const life = item['lifecycle'] as
126
+ | { turn?: unknown; stage?: unknown; cta?: unknown; planFailureCause?: unknown; latestBuild?: unknown }
127
+ | null
128
+ | undefined;
129
+ if (!life || typeof life.stage !== 'string') return null;
130
+ const parts: string[] = [];
131
+ let stage = STAGE[life.stage] ?? life.stage;
132
+ if (life.stage === 'planning' && life.turn === 'you') {
133
+ stage = `Planning stopped${typeof life.planFailureCause === 'string' ? ` (${life.planFailureCause})` : ''}`;
134
+ }
135
+ const build = life.latestBuild as { status?: unknown; prNumber?: unknown; prMerged?: unknown } | null | undefined;
136
+ if (build && typeof build.status === 'string' && life.stage !== 'done' && life.stage !== 'canceled') {
137
+ const pr = typeof build.prNumber === 'number' ? ` (PR #${build.prNumber})` : '';
138
+ const said = (BUILD_RUN_STATUSES as readonly string[]).includes(build.status)
139
+ ? RUN_STANDS[landedDisposition(build.status as BuildRunStatus, build.prMerged === true)]
140
+ : build.status;
141
+ stage += `; its latest run ${said}${pr}`;
142
+ }
143
+ parts.push(`${stage}.`);
144
+ if (typeof item['needsHumanCause'] === 'string') {
145
+ const halt = renderHalt({ cause: item['needsHumanCause'], params: {} } as unknown as Halt);
146
+ const detail = typeof item['needsHumanDetail'] === 'string' ? item['needsHumanDetail'] : '';
147
+ // The halt says what stopped the line; the detail is the run's own reason, kept beside it.
148
+ const why = [halt, detail]
149
+ .filter(Boolean)
150
+ .map((text) => text.replace(/\.$/, ''))
151
+ .join('. ');
152
+ if (why) parts.push(`The line stopped on it: ${why}.`);
153
+ }
154
+ const turn = life.turn === 'you' ? "A person's turn" : life.turn === 'agent' ? "The agent's turn" : null;
155
+ const move = typeof life.cta === 'string' ? CTA_MOVE[life.cta] : undefined;
156
+ if (life.cta === 'view_pr') parts.push(`Nothing to do: ${move}.`);
157
+ else if (turn && move) parts.push(`${turn}: ${move}.`);
158
+ else if (turn) parts.push(`${turn}.`);
159
+ else if (move) parts.push(`Next: ${move}.`);
160
+ return parts.join(' ');
161
+ }
162
+
163
+ /** A row or item with its situation added when it has a lifecycle. */
164
+ export function withSituation(
165
+ item: Record<string, unknown>,
166
+ row: Record<string, unknown> = item
167
+ ): Record<string, unknown> {
168
+ const situation = situationOf(item);
169
+ return situation ? { ...row, situation } : row;
170
+ }
171
+
172
+ /** List rows, each with its situation. */
173
+ export function situatedRows(items: Record<string, unknown>[]): Record<string, unknown>[] {
174
+ return itemRows(items, BACKLOG_SUMMARY_FIELDS).map((row, index) => withSituation(items[index]!, row));
175
+ }
176
+
70
177
  /** What each plan failure cause means for whoever reads it next (PLAN_FAILURE_CAUSES in @taiga/types). */
71
178
  const PLAN_FAILURE_MEANING: Record<string, string> = {
72
179
  agent_error: 'The planning agent failed. Planning again is the usual next step.',
@@ -81,12 +188,12 @@ const PLAN_FAILURE_MEANING: Record<string, string> = {
81
188
  */
82
189
  async function planFailureOf(
83
190
  client: TaigaClient,
84
- itemId: string,
191
+ initiativeId: string,
85
192
  itemCause: unknown
86
193
  ): Promise<Record<string, unknown> | null> {
87
194
  let events: WithoutTenant<ActivityEvent>[];
88
195
  try {
89
- events = (await client.listBacklogItemActivity(itemId)).data;
196
+ events = (await client.listBacklogItemActivity(initiativeId)).data;
90
197
  } catch (error) {
91
198
  if (error instanceof TaigaApiError || isNetworkFailure(error)) return null;
92
199
  throw error;
@@ -126,7 +233,7 @@ async function planFailureOf(
126
233
 
127
234
  const planId = idInput('plan', "taiga_get_plan gives the initiative's active plan and its ID");
128
235
 
129
- const itemId = idInput('backlog item', 'taiga_list_backlog_items (query finds one by title) gives it');
236
+ const initiativeId = idInput('initiative', 'taiga_list_initiatives (query finds one by title) gives it');
130
237
 
131
238
  /** The plan without its steps' prose: enough to approve it or say what a run will do. */
132
239
  const PLAN_SUMMARY_FIELDS = ['id', 'backlogItemId', 'version', 'status', 'createdAt'] as const;
@@ -143,8 +250,12 @@ const PLAN_STEP_SUMMARY_FIELDS = [
143
250
 
144
251
  type Item = WithoutTenant<BacklogItem>;
145
252
 
146
- /** The outcome fields of an initiative, with how many of its criteria are met. */
147
- const outcomeOf = (item: Item) => ({ ...pickFields([item], ITEM_OUTCOME_FIELDS)[0], criteria: criteriaCounts(item) });
253
+ /** The outcome fields of an initiative, with how many of its criteria are met, and where it stands. */
254
+ const outcomeOf = (item: Item) =>
255
+ withSituation(item as unknown as Record<string, unknown>, {
256
+ ...pickFields([item], ITEM_OUTCOME_FIELDS)[0],
257
+ criteria: criteriaCounts(item),
258
+ });
148
259
 
149
260
  const outcome = (item: Item) => toolResult(outcomeOf(item));
150
261
 
@@ -180,15 +291,16 @@ export function registerBacklogTools(server: McpServer, client: TaigaClient) {
180
291
 
181
292
  function registerBacklogReads(server: McpServer, client: TaigaClient) {
182
293
  server.registerTool(
183
- 'taiga_list_backlog_items',
294
+ 'taiga_list_initiatives',
184
295
  {
185
- title: 'List backlog items',
296
+ title: 'List initiatives',
186
297
  description:
187
- "List a product's backlog items (initiatives): titles, category, status, where each one is on the " +
188
- 'line (backlog, todo, queue, plan, build, done) and criteria, how many of its acceptance criteria are met ' +
298
+ "List a product's initiatives, its backlog: titles, category, status, where each one is on the " +
299
+ 'line (backlog, todo, queue, plan, build, done), situation (where it stands and what moves it on, in a ' +
300
+ 'sentence) and criteria, how many of its acceptance criteria are met ' +
189
301
  '(null before it is planned; met counts the criteria the agent confirmed with evidence or a person checked ' +
190
302
  'off, so a done item can show fewer met than it has). Use this to find the item you are working on (query ' +
191
- 'finds one by title), then read its acceptance criteria and technical context with taiga_get_backlog_item. ' +
303
+ 'finds one by title), then read its acceptance criteria and technical context with taiga_get_initiative. ' +
192
304
  'Archived items are left out unless asked for.',
193
305
  inputSchema: z.object({
194
306
  productId,
@@ -218,7 +330,7 @@ function registerBacklogReads(server: McpServer, client: TaigaClient) {
218
330
  const { query, ...filters } = params;
219
331
  if (query === undefined) {
220
332
  const result = await client.listBacklogItems(params.productId, filters);
221
- return toolResult(page(itemRows(result.data, BACKLOG_SUMMARY_FIELDS), result.nextCursor));
333
+ return toolResult(page(situatedRows(result.data as Record<string, unknown>[]), result.nextCursor));
222
334
  }
223
335
  if (params.cursor !== undefined) {
224
336
  return toolError('VALIDATION_ERROR', SEARCH_WITH_CURSOR);
@@ -230,36 +342,37 @@ function registerBacklogReads(server: McpServer, client: TaigaClient) {
230
342
  params.limit ?? 25
231
343
  );
232
344
  return toolResult({
233
- ...page(itemRows(found.rows, BACKLOG_SUMMARY_FIELDS), null),
345
+ ...page(situatedRows(found.rows), null),
234
346
  ...(found.more ? { more: SEARCH_NOTE } : {}),
235
347
  });
236
348
  })
237
349
  );
238
350
 
239
351
  server.registerTool(
240
- 'taiga_get_backlog_item',
352
+ 'taiga_get_initiative',
241
353
  {
242
- title: 'Get backlog item',
354
+ title: 'Get initiative',
243
355
  description:
244
- 'Get one backlog item in full: description, acceptance criteria (and which are met), technical ' +
356
+ 'Get one initiative in full: description, acceptance criteria (and which are met), technical ' +
245
357
  'context (patterns, constraints, and the assumptions to verify before starting), dependencies and source documents. Read this before ' +
246
- 'implementing an item, and read its runs with taiga_list_builds to see what the coding agent already did. ' +
358
+ 'implementing an item, and read its runs with taiga_list_runs to see what the coding agent already did. ' +
247
359
  'A failed plan carries planFailure: the cause, what it means, when, and by whom. ' +
248
360
  LIFECYCLE_MEANING,
249
361
  inputSchema: z.object({
250
- itemId,
362
+ initiativeId,
251
363
  }),
252
364
  annotations: READ_ONLY,
253
365
  },
254
366
  (params) =>
255
367
  runTool(async () => {
256
- const item = (await client.getBacklogItem(params.itemId)).data;
257
- if (item.planningStatus !== 'failed') return toolResult(item);
368
+ const item = (await client.getBacklogItem(params.initiativeId)).data;
369
+ const row = item as unknown as Record<string, unknown>;
370
+ if (item.planningStatus !== 'failed') return toolResult(withSituation(row));
258
371
  // Always undefined: the API sends the cause only under `lifecycle`, so
259
372
  // planFailureOf's cross-check never fires (PRO-2799). Kept as it was.
260
373
  const ownCause = (item as { planFailureCause?: unknown }).planFailureCause;
261
- const planFailure = await planFailureOf(client, params.itemId, ownCause);
262
- return toolResult(planFailure ? { ...item, planFailure } : item);
374
+ const planFailure = await planFailureOf(client, params.initiativeId, ownCause);
375
+ return toolResult(withSituation(row, planFailure ? { ...item, planFailure } : item));
263
376
  })
264
377
  );
265
378
 
@@ -275,14 +388,14 @@ function registerBacklogReads(server: McpServer, client: TaigaClient) {
275
388
  "full: true for each step's complete instructions, several thousand tokens on a large plan. NOT_FOUND " +
276
389
  'when the initiative has no active plan yet (planningStatus says whether one is being generated).',
277
390
  inputSchema: z.object({
278
- itemId,
391
+ initiativeId,
279
392
  full: z.boolean().optional().describe("Carry each step's complete instructions (default false: a summary)"),
280
393
  }),
281
394
  annotations: READ_ONLY,
282
395
  },
283
396
  (params) =>
284
397
  runTool(async () => {
285
- const plan = (await client.getActivePlan(params.itemId)).data;
398
+ const plan = (await client.getActivePlan(params.initiativeId)).data;
286
399
  if (params.full) return toolResult(plan);
287
400
  const steps = Array.isArray(plan.steps) ? plan.steps : [];
288
401
  return toolResult({
@@ -340,12 +453,12 @@ function registerRequest(server: McpServer, client: TaigaClient) {
340
453
 
341
454
  function registerMove(server: McpServer, client: TaigaClient) {
342
455
  server.registerTool(
343
- 'taiga_move_backlog_item',
456
+ 'taiga_move_initiative',
344
457
  {
345
- title: 'Move backlog item on the board',
458
+ title: 'Move initiative on the board',
346
459
  description:
347
460
  'Move an initiative on the board, or order it within a column: backlog and todo are yours and the line ' +
348
- 'never moves an initiative out of them; queue puts it in the line (taiga_queue_backlog_item says more); ' +
461
+ 'never moves an initiative out of them; queue puts it in the line (taiga_queue_initiative says more); ' +
349
462
  'done and canceled finish it by hand. The queue is the only way to start work: there is no "plan this ' +
350
463
  'now", and nothing is moved into Build by a person. Moving an initiative out of Build stops the plan ' +
351
464
  'being written or the run in flight: that is how work is stopped, and it throws that work away. A move to ' +
@@ -356,9 +469,9 @@ function registerMove(server: McpServer, client: TaigaClient) {
356
469
  'archived: true files the initiative where it stands (pass its current column as state; never from the Build station, plan or build) ' +
357
470
  'and archived: false brings it back; archived work is left out of lists unless includeArchived asks for it.',
358
471
  inputSchema: z.object({
359
- itemId,
472
+ initiativeId,
360
473
  state: z.enum(USER_DROP_STATES).describe('The column to move into'),
361
- afterId: idInput('backlog item', 'taiga_list_backlog_items gives it')
474
+ afterId: idInput('initiative', 'taiga_list_initiatives gives it')
362
475
  .nullable()
363
476
  .optional()
364
477
  .describe('The initiative to follow in that column; null for the head; omit to keep rank order'),
@@ -371,7 +484,7 @@ function registerMove(server: McpServer, client: TaigaClient) {
371
484
  },
372
485
  (params) =>
373
486
  runTool(async () => {
374
- const { itemId: id, ...body } = params;
487
+ const { initiativeId: id, ...body } = params;
375
488
  // What the route checks on top of backlog:write for a drop into the
376
489
  // queue; refused here so the model learns which capability, not just
377
490
  // that it was forbidden.
@@ -401,10 +514,10 @@ function registerPlanAgain(server: McpServer, client: TaigaClient) {
401
514
  'stays open. Offered only when the turn is yours: refused (CONFLICT) while an agent holds it (planning, ' +
402
515
  'building, reviewing). Also refused while a dependency is still in flight or a UI direction has no ' +
403
516
  'chosen concept, unless force says to plan anyway, which only the person decides. Anywhere off the ' +
404
- 'Build station, put the initiative in the queue instead (taiga_queue_backlog_item): the queue is the ' +
517
+ 'Build station, put the initiative in the queue instead (taiga_queue_initiative): the queue is the ' +
405
518
  'only way to start work. To send a plan back with direction, taiga_reject_plan. Starts a planner run.',
406
519
  inputSchema: z.object({
407
- itemId,
520
+ initiativeId,
408
521
  force: z
409
522
  .boolean()
410
523
  .optional()
@@ -416,16 +529,16 @@ function registerPlanAgain(server: McpServer, client: TaigaClient) {
416
529
  },
417
530
  (params) =>
418
531
  runTool(async () => {
419
- const item = (await client.getBacklogItem(params.itemId)).data;
532
+ const item = (await client.getBacklogItem(params.initiativeId)).data;
420
533
  if (!BUILD_STATION.has(String(item.state))) {
421
534
  return toolError(
422
535
  'CONFLICT',
423
536
  `This initiative is in ${String(item.state)}, not at the Build station. The queue is the only way to ` +
424
- 'start work: put it in the queue with taiga_queue_backlog_item and it is planned when its turn comes.'
537
+ 'start work: put it in the queue with taiga_queue_initiative and it is planned when its turn comes.'
425
538
  );
426
539
  }
427
540
  const body = { state: 'plan', ...(params.force ? { force: true } : {}) };
428
- return outcome((await client.transitionInitiative(params.itemId, body)).data);
541
+ return outcome((await client.transitionInitiative(params.initiativeId, body)).data);
429
542
  })
430
543
  );
431
544
  }
@@ -481,9 +594,9 @@ const autoBuildInput = z
481
594
 
482
595
  function registerQueue(server: McpServer, client: TaigaClient) {
483
596
  server.registerTool(
484
- 'taiga_queue_backlog_item',
597
+ 'taiga_queue_initiative',
485
598
  {
486
- title: 'Queue backlog item',
599
+ title: 'Queue initiative',
487
600
  description:
488
601
  'Add an initiative to the product queue, the line the coding agent works: queued initiatives are ' +
489
602
  'planned and built one at a time, each advancing when the previous pull request merges. This starts ' +
@@ -491,12 +604,12 @@ function registerQueue(server: McpServer, client: TaigaClient) {
491
604
  "repository. Whether the plan is built at once or waits for a person is the initiative's review gate: " +
492
605
  "pass autoBuild: false to read the plan before anything is built (the product's default applies " +
493
606
  'otherwise, and the outcome says which it is); the gate is set first and stays set if the move is then ' +
494
- 'refused. Ask the person before calling this. An initiative already ' +
607
+ 'refused. An initiative already ' +
495
608
  'in the queue stays where it is unless afterId says where to go; one being planned or built cannot move ' +
496
609
  '(CONFLICT). Returns the initiative with its new state, its review gate and its place in the queue.',
497
610
  inputSchema: z.object({
498
- itemId,
499
- afterId: idInput('backlog item', 'taiga_list_backlog_items gives it')
611
+ initiativeId,
612
+ afterId: idInput('initiative', 'taiga_list_initiatives gives it')
500
613
  .nullable()
501
614
  .optional()
502
615
  .describe(
@@ -513,7 +626,7 @@ function registerQueue(server: McpServer, client: TaigaClient) {
513
626
  },
514
627
  (params) =>
515
628
  runTool(async () => {
516
- const { itemId: id, autoBuild, ...placement } = params;
629
+ const { initiativeId: id, autoBuild, ...placement } = params;
517
630
  // The gate first: set after the move, a plan landing in between
518
631
  // would be built under the old answer.
519
632
  if (autoBuild !== undefined) await client.setAutoBuild(id, autoBuild);
@@ -527,7 +640,7 @@ function registerQueue(server: McpServer, client: TaigaClient) {
527
640
  queue,
528
641
  ...(gate && queue
529
642
  ? {}
530
- : { note: 'Part of the outcome could not be read; taiga_get_queue and taiga_get_product say the rest.' }),
643
+ : { note: 'Part of the outcome could not be read; taiga_get_line and taiga_get_product say the rest.' }),
531
644
  });
532
645
  })
533
646
  );
@@ -543,12 +656,12 @@ function registerReviewGate(server: McpServer, client: TaigaClient) {
543
656
  "approve or reject it (false); null follows the product's default. Standing: it applies to every plan the " +
544
657
  'initiative gets, including one being written now. Opening the gate on a plan that is already waiting ' +
545
658
  'starts its build at once. Returns the initiative with the gate that now applies.',
546
- inputSchema: z.object({ itemId, autoBuild: autoBuildInput }),
659
+ inputSchema: z.object({ initiativeId, autoBuild: autoBuildInput }),
547
660
  annotations: OPERATE_SETTING,
548
661
  },
549
662
  (params) =>
550
663
  runTool(async () => {
551
- const item = (await client.setAutoBuild(params.itemId, params.autoBuild)).data;
664
+ const item = (await client.setAutoBuild(params.initiativeId, params.autoBuild)).data;
552
665
  const gate = await reviewGate(client, item);
553
666
  return toolResult({
554
667
  ...outcomeOf(item),
@@ -561,9 +674,9 @@ function registerReviewGate(server: McpServer, client: TaigaClient) {
561
674
 
562
675
  function registerResume(server: McpServer, client: TaigaClient) {
563
676
  server.registerTool(
564
- 'taiga_resume_backlog_item',
677
+ 'taiga_resume_initiative',
565
678
  {
566
- title: 'Resume backlog item',
679
+ title: 'Resume initiative',
567
680
  description:
568
681
  'Resume an initiative the line stopped on, which is when needsHumanCause is set (the line sets it): a ' +
569
682
  'failed or cancelled plan is planned again, a stopped or failed run resumes from its first unfinished ' +
@@ -572,10 +685,10 @@ function registerResume(server: McpServer, client: TaigaClient) {
572
685
  'initiative. Refused (CONFLICT) when nothing is flagged or the cause has no resume: a plan that failed ' +
573
686
  'with needsHumanCause null (lifecycle.cta retry_plan, planFailureCause says why) is planned again with ' +
574
687
  'taiga_plan_again. Starts paid work.',
575
- inputSchema: z.object({ itemId }),
688
+ inputSchema: z.object({ initiativeId }),
576
689
  annotations: OPERATE,
577
690
  },
578
- (params) => runTool(async () => outcome((await client.resumeInitiative(params.itemId)).data))
691
+ (params) => runTool(async () => outcome((await client.resumeInitiative(params.initiativeId)).data))
579
692
  );
580
693
  }
581
694