@taiga-ai/mcp-server 0.20.0-dev.454 → 0.21.0-dev.455
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 +435 -183
- package/package.json +1 -1
- package/src/server.integration.test.ts +355 -68
- package/src/tools/autonomy.ts +9 -11
- package/src/tools/backlogs.ts +84 -81
- package/src/tools/builds.ts +96 -28
- package/src/tools/catalogue.test.ts +5 -8
- package/src/tools/catalogue.ts +5 -8
- package/src/tools/context.ts +21 -20
- package/src/tools/documents.ts +34 -43
- package/src/tools/errors.ts +2 -1
- package/src/tools/inbox.ts +19 -33
- package/src/tools/index.ts +42 -7
- package/src/tools/inputs.ts +1 -1
- package/src/tools/instructions.ts +17 -19
- package/src/tools/jobs.ts +5 -6
- package/src/tools/knowledge.ts +17 -23
- package/src/tools/maintenance.ts +129 -82
- package/src/tools/observe.ts +30 -39
- package/src/tools/product-documents.ts +20 -26
- package/src/tools/products.ts +23 -73
- package/src/tools/prompts.ts +1 -1
- package/src/tools/queue.ts +10 -12
- package/src/tools/repositories.ts +20 -25
- package/src/tools/sections.ts +41 -6
- package/src/tools/setup.ts +100 -54
- package/src/tools/summarize.ts +4 -1
- package/src/tools/wait.ts +13 -22
- package/taiga-ai-mcp-server-0.21.0-dev.455.tgz +0 -0
- package/taiga-ai-mcp-server-0.20.0-dev.454.tgz +0 -0
package/src/tools/maintenance.ts
CHANGED
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
MAINTENANCE_IGNORE_REASONS,
|
|
7
7
|
} from '@taiga/types';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
-
import type
|
|
9
|
+
import { TaigaApiError, type TaigaClient } from '../client.js';
|
|
10
10
|
import { OPERATE, READ_ONLY } from './annotations.js';
|
|
11
|
-
import { runTool, toolError, toolResult } from './errors.js';
|
|
11
|
+
import { isNetworkFailure, runTool, toolError, toolResult } from './errors.js';
|
|
12
12
|
import { idInput, pagination, productId } from './inputs.js';
|
|
13
13
|
import { page, pickFields } from './summarize.js';
|
|
14
14
|
|
|
@@ -75,6 +75,24 @@ const VIOLATION_FIELDS = [
|
|
|
75
75
|
|
|
76
76
|
type Row = Record<string, unknown>;
|
|
77
77
|
|
|
78
|
+
/** Characters of one text field the full standing carries; violationId reads one violation uncut. */
|
|
79
|
+
const FULL_TEXT_LIMIT = 1_500;
|
|
80
|
+
|
|
81
|
+
/** Resolved violations the full standing carries. */
|
|
82
|
+
const FULL_RESOLVED = 20;
|
|
83
|
+
|
|
84
|
+
/** A copy with every string longer than the limit cut and marked, at any depth. */
|
|
85
|
+
function cutLongText(value: unknown): unknown {
|
|
86
|
+
if (typeof value === 'string') {
|
|
87
|
+
return value.length > FULL_TEXT_LIMIT
|
|
88
|
+
? `${value.slice(0, FULL_TEXT_LIMIT)}... [cut at ${FULL_TEXT_LIMIT} of ${value.length} characters; violationId reads it whole]`
|
|
89
|
+
: value;
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(value)) return value.map(cutLongText);
|
|
92
|
+
if (value === null || typeof value !== 'object') return value;
|
|
93
|
+
return Object.fromEntries(Object.entries(value).map(([key, inner]) => [key, cutLongText(inner)]));
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
/**
|
|
79
97
|
* How bad a product's policy standing is, as counts: the controls something is
|
|
80
98
|
* recorded against, the open violations by severity, the resolved ones. The
|
|
@@ -116,55 +134,41 @@ export function standingSummary(standing: {
|
|
|
116
134
|
|
|
117
135
|
const findingId = idInput('finding', 'taiga_list_findings gives it');
|
|
118
136
|
|
|
137
|
+
/** Open findings by severity and kind, and the latest sweeps, as the first page of findings carries them. */
|
|
138
|
+
async function sweepOverview(client: TaigaClient, productId: string) {
|
|
139
|
+
const [summary, sweeps] = await Promise.all([
|
|
140
|
+
client.getMaintenanceSummary(productId),
|
|
141
|
+
client.listMaintenanceSweeps(productId, { limit: 3 }),
|
|
142
|
+
]);
|
|
143
|
+
const latest = pickFields(sweeps.data, SWEEP_FIELDS);
|
|
144
|
+
// The summary's own run rows (one per repository) would repeat the sweeps with every column.
|
|
145
|
+
const {
|
|
146
|
+
lastRun: _lastRun,
|
|
147
|
+
lastRuns: _lastRuns,
|
|
148
|
+
topFindings: _top,
|
|
149
|
+
violatedControls: _controls,
|
|
150
|
+
...counts
|
|
151
|
+
} = summary.data as Record<string, unknown>;
|
|
152
|
+
return {
|
|
153
|
+
...counts,
|
|
154
|
+
latestSweeps: latest,
|
|
155
|
+
...(latest.length === 0 ? { note: 'This product has never been swept: no findings is not an all-clear.' } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
119
159
|
export function registerMaintenanceTools(server: McpServer, client: TaigaClient) {
|
|
120
160
|
if (!client.hasCapability('products:read')) return;
|
|
121
161
|
|
|
122
|
-
server.registerTool(
|
|
123
|
-
'taiga_get_maintenance',
|
|
124
|
-
{
|
|
125
|
-
title: "Get a product's maintenance standing",
|
|
126
|
-
description:
|
|
127
|
-
"Where a product's repository stands: open findings by severity and kind, the most pressing ones, and " +
|
|
128
|
-
'the latest sweeps, so you can tell when the product was last checked and whether that check succeeded. A ' +
|
|
129
|
-
'quiet list from a sweep that last ran long ago, or that failed, is not an all-clear; a product never swept ' +
|
|
130
|
-
'says so. Sweeps run on their own after a push and periodically; nothing here starts one. This is the ' +
|
|
131
|
-
'overview of the scanners alone; taiga_list_findings lists each finding to act on, and ' +
|
|
132
|
-
'taiga_get_policy_standing judges the product against the policies (scanner findings and the documents).',
|
|
133
|
-
inputSchema: z.object({ productId }),
|
|
134
|
-
annotations: READ_ONLY,
|
|
135
|
-
},
|
|
136
|
-
(params) =>
|
|
137
|
-
runTool(async () => {
|
|
138
|
-
const [summary, sweeps] = await Promise.all([
|
|
139
|
-
client.getMaintenanceSummary(params.productId),
|
|
140
|
-
client.listMaintenanceSweeps(params.productId, { limit: 3 }),
|
|
141
|
-
]);
|
|
142
|
-
const latest = pickFields(sweeps.data, SWEEP_FIELDS);
|
|
143
|
-
// The sweeps are reported as `latestSweeps`, narrowed to SWEEP_FIELDS;
|
|
144
|
-
// the summary's own run rows (PRO-2527: one per repository) would
|
|
145
|
-
// repeat them with every column.
|
|
146
|
-
const { lastRun: _lastRun, lastRuns: _lastRuns, ...standing } = summary.data;
|
|
147
|
-
return toolResult({
|
|
148
|
-
...standing,
|
|
149
|
-
latestSweeps: latest,
|
|
150
|
-
...(latest.length === 0 ? { note: 'This product has never been swept: no result is not an all-clear.' } : {}),
|
|
151
|
-
});
|
|
152
|
-
})
|
|
153
|
-
);
|
|
154
|
-
|
|
155
162
|
server.registerTool(
|
|
156
163
|
'taiga_list_findings',
|
|
157
164
|
{
|
|
158
165
|
title: 'List maintenance findings',
|
|
159
166
|
description:
|
|
160
|
-
"List what
|
|
161
|
-
'
|
|
162
|
-
'
|
|
163
|
-
'the two; routine),
|
|
164
|
-
'
|
|
165
|
-
'already fixing it (initiativeId). Omit status for the findings that need action; work from the top tier ' +
|
|
166
|
-
'down. Read one in full with taiga_get_finding. taiga_get_maintenance has the counts and when the last ' +
|
|
167
|
-
'sweep ran, which says whether an empty list can be trusted.',
|
|
167
|
+
"List what maintenance sweeps found in a product's repository: dependencies, secrets, insecure code " +
|
|
168
|
+
'(sast) and infrastructure. The first page opens with sweep (open counts, latest sweeps), so an old or ' +
|
|
169
|
+
'failed sweep is not read as an all-clear. Rows: severity, tier (act_now: known exploited; urgent: ' +
|
|
170
|
+
'likely and serious; elevated: one of the two; routine), location, remediableBy (upstream_only: no fixed ' +
|
|
171
|
+
'version), breached controls and initiativeId of a fix under way. Omit status for open findings. taiga_get_finding reads one.',
|
|
168
172
|
inputSchema: z.object({
|
|
169
173
|
productId,
|
|
170
174
|
status: z
|
|
@@ -180,8 +184,28 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
|
|
|
180
184
|
(params) =>
|
|
181
185
|
runTool(async () => {
|
|
182
186
|
const { productId: id, ...filters } = params;
|
|
183
|
-
|
|
187
|
+
// The sweep overview on the first page: when the product was last
|
|
188
|
+
// checked decides whether an empty list can be trusted.
|
|
189
|
+
const [result, sweep] = await Promise.all([
|
|
190
|
+
client.listFindings(id, filters),
|
|
191
|
+
// Best effort: the findings the agent asked for do not fail on the overview.
|
|
192
|
+
params.cursor === undefined
|
|
193
|
+
? sweepOverview(client, id).catch((error: unknown) => {
|
|
194
|
+
if (error instanceof TaigaApiError || isNetworkFailure(error)) return 'unread' as const;
|
|
195
|
+
throw error;
|
|
196
|
+
})
|
|
197
|
+
: Promise.resolve(null),
|
|
198
|
+
]);
|
|
184
199
|
return toolResult({
|
|
200
|
+
// An overview that could not be read says so: without it an empty list would read as an all-clear.
|
|
201
|
+
...(sweep === 'unread'
|
|
202
|
+
? {
|
|
203
|
+
sweep: null,
|
|
204
|
+
sweepNote: 'The sweep overview could not be read: an empty list here is not an all-clear.',
|
|
205
|
+
}
|
|
206
|
+
: sweep
|
|
207
|
+
? { sweep }
|
|
208
|
+
: {}),
|
|
185
209
|
...page(pickFields(result.data, FINDING_ROW_FIELDS), result.nextCursor),
|
|
186
210
|
...(typeof result.totalCount === 'number' ? { totalCount: result.totalCount } : {}),
|
|
187
211
|
});
|
|
@@ -193,10 +217,10 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
|
|
|
193
217
|
{
|
|
194
218
|
title: 'Get a maintenance finding',
|
|
195
219
|
description:
|
|
196
|
-
'Read one maintenance finding in full:
|
|
197
|
-
'
|
|
198
|
-
|
|
199
|
-
|
|
220
|
+
'Read one maintenance finding in full: exploit likelihood (EPSS, known-exploited list), advisory and ' +
|
|
221
|
+
'fixed version, code location, breached policy controls, why it ranks where it does, history ' +
|
|
222
|
+
"(reintroduced, previously resolved) and the remediation under way. A secret's value is never stored, " +
|
|
223
|
+
'only the rule and location.',
|
|
200
224
|
inputSchema: z.object({ findingId }),
|
|
201
225
|
annotations: READ_ONLY,
|
|
202
226
|
},
|
|
@@ -214,24 +238,24 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
|
|
|
214
238
|
{
|
|
215
239
|
title: "Get a product's policy standing",
|
|
216
240
|
description:
|
|
217
|
-
"How a product stands against the organization's
|
|
218
|
-
|
|
219
|
-
'
|
|
220
|
-
'
|
|
221
|
-
|
|
222
|
-
'
|
|
223
|
-
|
|
224
|
-
'done or canceled without the violation going away, so it needs a person or a new fix. A control ' +
|
|
225
|
-
'breached by scanner findings counts them by tier (findingsByTier): a breach of ' +
|
|
226
|
-
'routine findings only, such as secrets in test fixtures, is not an urgent one. A control with no violation ' +
|
|
227
|
-
'is not listed, and it is not "passing" either: there is no such state; summary.controlsWithNothingRecorded counts them. Pass full: true for every ' +
|
|
228
|
-
"violation's evidence and the resolved ones. Fix a control with taiga_fix_policy_violation.",
|
|
241
|
+
"How a product stands against the organization's published policies: summary counts, when the documents " +
|
|
242
|
+
'were assessed, and each violated control with its violations (critical and high only, citing the policy ' +
|
|
243
|
+
'sentence and the contradicting passage) and the work on each. state: breached (open scanner findings ' +
|
|
244
|
+
'map to it, by findingsByTier; routine-only is not urgent) or diverged (the documents contradict it). ' +
|
|
245
|
+
"live: false means the violation's initiative ended without fixing it: it needs a person or a new fix. " +
|
|
246
|
+
'An unlisted control is not "passing" (summary.controlsWithNothingRecorded counts them). full: true adds ' +
|
|
247
|
+
'evidence and resolved ones. Fix with taiga_fix_policy_violation.',
|
|
229
248
|
inputSchema: z.object({
|
|
230
249
|
productId,
|
|
231
250
|
full: z
|
|
232
251
|
.boolean()
|
|
233
252
|
.optional()
|
|
234
|
-
.describe(
|
|
253
|
+
.describe(
|
|
254
|
+
"Carry each violation's evidence and policy excerpt, cut to about 1,500 characters each, and up to 20 resolved violations (default false)"
|
|
255
|
+
),
|
|
256
|
+
violationId: idInput('violation', 'this tool lists them')
|
|
257
|
+
.optional()
|
|
258
|
+
.describe('Only this violation, in full and uncut: its evidence, policy excerpt and work'),
|
|
235
259
|
}),
|
|
236
260
|
annotations: READ_ONLY,
|
|
237
261
|
},
|
|
@@ -252,7 +276,36 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
|
|
|
252
276
|
'all-clear. An assessment runs when the reality documents are derived or refreshed ' +
|
|
253
277
|
'(taiga_refresh_reality_documents, paid work: ask first).'
|
|
254
278
|
: null;
|
|
255
|
-
if (params.
|
|
279
|
+
if (params.violationId) {
|
|
280
|
+
const one = [
|
|
281
|
+
...violations,
|
|
282
|
+
...(Array.isArray(standing.resolvedDivergences) ? standing.resolvedDivergences : []),
|
|
283
|
+
].find((violation) => (violation as Row)['id'] === params.violationId);
|
|
284
|
+
if (!one) {
|
|
285
|
+
return toolError(
|
|
286
|
+
'NOT_FOUND',
|
|
287
|
+
'No violation with that ID on this product; this tool without violationId lists them.'
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
return toolResult(one);
|
|
291
|
+
}
|
|
292
|
+
// The full form ran to 228k characters on a product with eleven violations (PRO-2800): each text is
|
|
293
|
+
// cut and the resolved list bounded; violationId reads one whole.
|
|
294
|
+
if (params.full) {
|
|
295
|
+
const resolvedAll = Array.isArray(standing.resolvedDivergences) ? standing.resolvedDivergences : [];
|
|
296
|
+
return toolResult({
|
|
297
|
+
summary,
|
|
298
|
+
...(note ? { note } : {}),
|
|
299
|
+
...standing,
|
|
300
|
+
// Only the controls something is recorded against, as the summary answer lists them.
|
|
301
|
+
controls: listed,
|
|
302
|
+
divergences: cutLongText(violations),
|
|
303
|
+
resolvedDivergences: cutLongText(resolvedAll.slice(0, FULL_RESOLVED)),
|
|
304
|
+
...(resolvedAll.length > FULL_RESOLVED
|
|
305
|
+
? { resolvedNote: `The first ${FULL_RESOLVED} of ${resolvedAll.length} resolved violations.` }
|
|
306
|
+
: {}),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
256
309
|
return toolResult({
|
|
257
310
|
summary,
|
|
258
311
|
...(note ? { note } : {}),
|
|
@@ -284,13 +337,11 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
|
|
|
284
337
|
{
|
|
285
338
|
title: 'Ignore or reopen a finding',
|
|
286
339
|
description:
|
|
287
|
-
'Ignore a maintenance finding
|
|
288
|
-
'not_reachable, accepted_risk, wont_fix
|
|
289
|
-
'if
|
|
290
|
-
'
|
|
291
|
-
|
|
292
|
-
"shared signal and can record the team's acceptance of a risk, which is the person's decision: ask the " +
|
|
293
|
-
'person first.',
|
|
340
|
+
'Ignore a maintenance finding with a reason, or reopen one (ignored: false). Reasons: false_positive, ' +
|
|
341
|
+
'not_reachable, accepted_risk, wont_fix; plus an optional note and an expiry after which it resurfaces ' +
|
|
342
|
+
'if still present. There is no "mark fixed": a finding resolves when the next sweep stops detecting it. ' +
|
|
343
|
+
'For a secret only in history, rotate it outside Taiga, then ignore it. Ignoring can record the team ' +
|
|
344
|
+
"accepting a risk, which is the person's decision: ask first.",
|
|
294
345
|
inputSchema: z.object({
|
|
295
346
|
findingId,
|
|
296
347
|
ignored: z.boolean().describe('true ignores the finding, false reopens it'),
|
|
@@ -327,14 +378,11 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
|
|
|
327
378
|
{
|
|
328
379
|
title: 'Fix a finding',
|
|
329
380
|
description:
|
|
330
|
-
"
|
|
331
|
-
"
|
|
332
|
-
'
|
|
333
|
-
|
|
334
|
-
'
|
|
335
|
-
'code or infrastructure, every location the same rule flagged. Absent for a secret only in history, ' +
|
|
336
|
-
'anything in a submodule, and a dependency with no fixed version (remediableBy says upstream_only); the ' +
|
|
337
|
-
'API refuses those. Starts paid work.',
|
|
381
|
+
"Raise an initiative that fixes a maintenance finding, at the end of the product's line; it ends in a " +
|
|
382
|
+
"pull request a person merges, and the initiative's review gate applies. wholeGroup: true fixes the " +
|
|
383
|
+
'group in one initiative: every open advisory on that package version in one bump, or every location the ' +
|
|
384
|
+
'same rule flagged. Refused for a secret only in history, anything in a submodule, and remediableBy ' +
|
|
385
|
+
'upstream_only.',
|
|
338
386
|
inputSchema: z.object({
|
|
339
387
|
productId,
|
|
340
388
|
findingId,
|
|
@@ -378,11 +426,10 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
|
|
|
378
426
|
{
|
|
379
427
|
title: 'Fix policy violations',
|
|
380
428
|
description:
|
|
381
|
-
"
|
|
382
|
-
'
|
|
383
|
-
|
|
384
|
-
'
|
|
385
|
-
'can record that the documents no longer show it. Starts paid work.',
|
|
429
|
+
"Raise one initiative that fixes a product's policy violations and add it to the end of the line. Name a " +
|
|
430
|
+
'control to cover all its violations with no initiative yet, or one violationId when the control has ' +
|
|
431
|
+
'several. The violation stays listed until the next assessment no longer finds it, even while the fix is ' +
|
|
432
|
+
'under way.',
|
|
386
433
|
inputSchema: z.object({
|
|
387
434
|
productId,
|
|
388
435
|
controlId: z.string().min(1).max(40).optional().describe("The control's identifier, e.g. SEC-3.4"),
|
package/src/tools/observe.ts
CHANGED
|
@@ -100,13 +100,11 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
100
100
|
{
|
|
101
101
|
title: 'Get what runs where',
|
|
102
102
|
description:
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
"it last synced. Pass environmentId for one environment's history. Taiga observes deployments and " +
|
|
109
|
-
'never performs them.',
|
|
103
|
+
"Read what runs in each of a product's environments and its recent deployments, newest first: commit, " +
|
|
104
|
+
"pull request, status, deployer and, on failure, the provider's error. What runs is the latest " +
|
|
105
|
+
'successful deployment, so a newer failure shows beside it. History is mirrored from the provider, ' +
|
|
106
|
+
'including deployments made outside Taiga; syncStatus says when it last synced. environmentId narrows to ' +
|
|
107
|
+
'one environment. Taiga never deploys.',
|
|
110
108
|
inputSchema: z.object({ productId, environmentId: environmentId.optional(), ...pagination }),
|
|
111
109
|
annotations: READ_ONLY,
|
|
112
110
|
},
|
|
@@ -127,7 +125,7 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
127
125
|
// environment, or none deployed to yet, is not "nothing is wrong".
|
|
128
126
|
const note =
|
|
129
127
|
environments.rows.length === 0
|
|
130
|
-
? 'This product has no environments yet: an environment is defined with
|
|
128
|
+
? 'This product has no environments yet: an environment is defined with taiga_create_environment, ' +
|
|
131
129
|
'and its deployments are mirrored from the provider once it is.'
|
|
132
130
|
: history.data.length === 0 && !params.cursor
|
|
133
131
|
? 'No deployments recorded yet: none was mirrored from the provider (syncStatus says when it last ' +
|
|
@@ -152,9 +150,9 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
152
150
|
{
|
|
153
151
|
title: "List a product's environments",
|
|
154
152
|
description:
|
|
155
|
-
"
|
|
156
|
-
'
|
|
157
|
-
'
|
|
153
|
+
"List a product's environments with ID, type, order and App URL. These are the environment IDs " +
|
|
154
|
+
'taiga_get_delivery_metrics, taiga_get_monitoring and taiga_update_environment take. ' +
|
|
155
|
+
'taiga_get_deployments lists them too, with what runs where.',
|
|
158
156
|
inputSchema: z.object({ productId }),
|
|
159
157
|
annotations: READ_ONLY,
|
|
160
158
|
},
|
|
@@ -164,7 +162,7 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
164
162
|
return toolResult({
|
|
165
163
|
data: pickFields(rows, ENVIRONMENT_FIELDS),
|
|
166
164
|
...(more ? { more: 'More environments than this answer reads; the rest were left out.' } : {}),
|
|
167
|
-
...(rows.length === 0 ? { note: 'No environment is defined yet;
|
|
165
|
+
...(rows.length === 0 ? { note: 'No environment is defined yet; taiga_create_environment adds one.' } : {}),
|
|
168
166
|
});
|
|
169
167
|
})
|
|
170
168
|
);
|
|
@@ -174,13 +172,11 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
174
172
|
{
|
|
175
173
|
title: 'Get delivery metrics',
|
|
176
174
|
description:
|
|
177
|
-
"DORA's five delivery metrics for one environment (the
|
|
178
|
-
'
|
|
179
|
-
'
|
|
180
|
-
'
|
|
181
|
-
'
|
|
182
|
-
'incidents. A trend built on fewer than ten deployments is approximate; a metric no deployment fed says ' +
|
|
183
|
-
'so instead of showing a number. Previews and checks do not count.',
|
|
175
|
+
"Read DORA's five delivery metrics for one environment (the page shows production) over 30, 60 or 90 " +
|
|
176
|
+
'days: deployment frequency, lead time, failed deployment recovery time, change-failure rate and rework ' +
|
|
177
|
+
'rate, each with its trend against the window before and a performance band (none for rework rate). ' +
|
|
178
|
+
'Computed from deployments, not incidents; previews and checks do not count. Under ten deployments a ' +
|
|
179
|
+
'trend is approximate; a metric with no data says so instead of a number.',
|
|
184
180
|
inputSchema: z.object({ productId, environmentId, windowDays }),
|
|
185
181
|
annotations: READ_ONLY,
|
|
186
182
|
},
|
|
@@ -196,13 +192,11 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
196
192
|
{
|
|
197
193
|
title: "Get a product's monitoring",
|
|
198
194
|
description:
|
|
199
|
-
"How a product's environments are doing
|
|
200
|
-
'
|
|
201
|
-
'
|
|
202
|
-
"
|
|
203
|
-
'
|
|
204
|
-
'installed; until then they read as not set up, which means no data, not good data. Setting up the ' +
|
|
205
|
-
"beacon raises an initiative from the environment's settings in the web.",
|
|
195
|
+
"How a product's environments are doing: each one's health from Taiga's checks of its App URL, uptime " +
|
|
196
|
+
'and last check (not checked when unverifiable now, baseline learning when new). With environmentId: ' +
|
|
197
|
+
'uptime over 30, 60 or 90 days and the beacon signals (Core Web Vitals, JavaScript errors, API health); ' +
|
|
198
|
+
"not set up means no data. Setting up the beacon raises an initiative from the environment's settings in " +
|
|
199
|
+
'the web.',
|
|
206
200
|
inputSchema: z.object({
|
|
207
201
|
productId,
|
|
208
202
|
environmentId: environmentId.optional(),
|
|
@@ -220,7 +214,7 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
220
214
|
? {
|
|
221
215
|
note:
|
|
222
216
|
'No environment is monitored: monitoring starts when an environment has its App URL ' +
|
|
223
|
-
'(
|
|
217
|
+
'(taiga_create_environment, taiga_update_environment). No health is not good health.',
|
|
224
218
|
}
|
|
225
219
|
: {}),
|
|
226
220
|
});
|
|
@@ -265,16 +259,14 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
265
259
|
if (!client.hasCapability('products:write')) return;
|
|
266
260
|
|
|
267
261
|
server.registerTool(
|
|
268
|
-
'
|
|
262
|
+
'taiga_create_environment',
|
|
269
263
|
{
|
|
270
|
-
title: '
|
|
264
|
+
title: 'Create an environment',
|
|
271
265
|
description:
|
|
272
|
-
"Add
|
|
273
|
-
'Deployments are
|
|
274
|
-
'
|
|
275
|
-
|
|
276
|
-
'taiga_update_environment, which is all monitoring needs to start checking. Changes the ' +
|
|
277
|
-
"product's setup for everybody.",
|
|
266
|
+
"Add an environment a product's software runs in (development, staging, production or its own). " +
|
|
267
|
+
'Deployments, monitoring and plans are tracked per environment. Taiga gets no access to it: the pipeline ' +
|
|
268
|
+
'deploys and Taiga learns what happened from source control. Members can do this. Then set its App URL ' +
|
|
269
|
+
"with taiga_update_environment so monitoring starts. Changes the product's setup for everybody.",
|
|
278
270
|
inputSchema: z.object({
|
|
279
271
|
productId,
|
|
280
272
|
name: z.string().trim().min(1).max(100).describe('What the team calls it, e.g. staging'),
|
|
@@ -295,10 +287,9 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
|
|
|
295
287
|
{
|
|
296
288
|
title: 'Update an environment',
|
|
297
289
|
description:
|
|
298
|
-
'Rename an environment, change its kind, or set its public App URL (null clears it).
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
"product's setup for everybody.",
|
|
290
|
+
'Rename an environment, change its kind, or set its public App URL (null clears it). Once an App URL is ' +
|
|
291
|
+
'saved, Taiga checks availability every few minutes. Marking one production makes it the one delivery ' +
|
|
292
|
+
"metrics report on. Changes the product's setup for everybody.",
|
|
302
293
|
inputSchema: z.object({
|
|
303
294
|
environmentId,
|
|
304
295
|
name: z.string().trim().min(1).max(100).optional().describe('A new name'),
|
|
@@ -65,11 +65,10 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
65
65
|
{
|
|
66
66
|
title: 'Update a section of a product document draft',
|
|
67
67
|
description:
|
|
68
|
-
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
'the
|
|
72
|
-
'and not edited.',
|
|
68
|
+
"Replace one section's text in a plan-timeline product document's draft; other sections stay. Agents " +
|
|
69
|
+
'read the published version until taiga_publish_product_document. Refused once Discovery is finished and ' +
|
|
70
|
+
'the set is locked (reopen it with taiga_reopen_discovery or in the web). Reality documents come from ' +
|
|
71
|
+
'the code and are not edited.',
|
|
73
72
|
inputSchema: z.object({
|
|
74
73
|
productId,
|
|
75
74
|
type: documentType,
|
|
@@ -91,9 +90,8 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
91
90
|
title: 'Publish a product document draft',
|
|
92
91
|
description:
|
|
93
92
|
"Publish a product document's draft as its new version, the one agents work from. Documents generated " +
|
|
94
|
-
'from it are
|
|
95
|
-
'
|
|
96
|
-
'one).',
|
|
93
|
+
'from it are marked outdated, not rewritten; regenerate them with taiga_regenerate_product_document, or ' +
|
|
94
|
+
'taiga_generate_product_documents for every outdated and missing one.',
|
|
97
95
|
inputSchema: z.object({ productId, type: documentType }),
|
|
98
96
|
annotations: OPERATE,
|
|
99
97
|
},
|
|
@@ -105,10 +103,9 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
105
103
|
{
|
|
106
104
|
title: 'Mark a product document reviewed',
|
|
107
105
|
description:
|
|
108
|
-
|
|
109
|
-
"
|
|
110
|
-
'
|
|
111
|
-
'have: reading it yourself is not a review.',
|
|
106
|
+
"Record that a person reviewed a product document's latest published version. Once every published " +
|
|
107
|
+
"document is reviewed, the product's review item in Action Required resolves. Only when the person says " +
|
|
108
|
+
'they read it; your own reading is not a review.',
|
|
112
109
|
inputSchema: z.object({ productId, type: reviewableType }),
|
|
113
110
|
annotations: OPERATE,
|
|
114
111
|
},
|
|
@@ -122,13 +119,11 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
122
119
|
{
|
|
123
120
|
title: 'Regenerate a product document',
|
|
124
121
|
description:
|
|
125
|
-
'
|
|
126
|
-
'
|
|
127
|
-
'conversation and the Look & Feel from its concepts
|
|
128
|
-
'are
|
|
129
|
-
'
|
|
130
|
-
'the documents generated from it are marked outdated in turn. Runs an agent in the background: this ' +
|
|
131
|
-
'returns the job; taiga_wait_for_job says when it is done.',
|
|
122
|
+
'Regenerate one product document from the documents it derives from, when it is outdated or needs ' +
|
|
123
|
+
'another pass, and return the job. The result is published and documents derived from it are marked ' +
|
|
124
|
+
'outdated. The Specification (from the Discovery conversation) and the Look & Feel (from its concepts) ' +
|
|
125
|
+
'are made in the web. Documents Discovery requires regenerate only while Discovery is open; the optional ' +
|
|
126
|
+
'Service Blueprint any time. taiga_wait_for_job says when it is done.',
|
|
132
127
|
inputSchema: z.object({ productId, type: generatedType }),
|
|
133
128
|
annotations: OPERATE,
|
|
134
129
|
},
|
|
@@ -164,11 +159,10 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
164
159
|
{
|
|
165
160
|
title: 'Generate the remaining product documents',
|
|
166
161
|
description:
|
|
167
|
-
'Generate
|
|
168
|
-
'order they build on each other
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
'(jobIds); taiga_stop_generating_documents stops the run.',
|
|
162
|
+
'Generate every missing or outdated product document (Generate remaining in the web), one after another ' +
|
|
163
|
+
'in the order they build on each other; on an imported product, every unpublished one. Returns the jobs ' +
|
|
164
|
+
'it started (jobIds). Refused (CONFLICT) while a document is already being generated. ' +
|
|
165
|
+
'taiga_stop_generating_documents stops it.',
|
|
172
166
|
inputSchema: z.object({ productId }),
|
|
173
167
|
annotations: OPERATE,
|
|
174
168
|
},
|
|
@@ -180,8 +174,8 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
180
174
|
{
|
|
181
175
|
title: 'Stop generating product documents',
|
|
182
176
|
description:
|
|
183
|
-
'Stop a Generate remaining run:
|
|
184
|
-
'after them starts; published
|
|
177
|
+
'Stop a Generate remaining run (taiga_generate_product_documents): documents still being generated are ' +
|
|
178
|
+
'cancelled and nothing after them starts; published ones stay. An empty list means none was running.',
|
|
185
179
|
inputSchema: z.object({ productId }),
|
|
186
180
|
annotations: OPERATE,
|
|
187
181
|
},
|