@taiga-ai/mcp-server 0.26.0-rc.510 → 0.26.0-rc.514
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 +72 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -356,6 +356,17 @@ var TaigaClient = class {
|
|
|
356
356
|
async getFinding(id) {
|
|
357
357
|
return this.request("GET", `/maintenance/findings/${seg(id)}`);
|
|
358
358
|
}
|
|
359
|
+
/** The package inventory (PRO-2862); 404 where the `licenses` feature is not enabled. */
|
|
360
|
+
async listPackages(productId, opts) {
|
|
361
|
+
return this.request("GET", `/products/${seg(productId)}/maintenance/packages${query$1({ ...opts })}`);
|
|
362
|
+
}
|
|
363
|
+
async getLicenseSummary(productId) {
|
|
364
|
+
return this.request("GET", `/products/${seg(productId)}/maintenance/licenses`);
|
|
365
|
+
}
|
|
366
|
+
/** A repository's CycloneDX 1.5 SBOM (PRO-2894), the document itself. */
|
|
367
|
+
async getSbom(productId, repositoryId) {
|
|
368
|
+
return this.request("GET", `/products/${seg(productId)}/maintenance/repositories/${seg(repositoryId)}/sbom`);
|
|
369
|
+
}
|
|
359
370
|
async updateFinding(id, body) {
|
|
360
371
|
return this.request("PATCH", `/maintenance/findings/${seg(id)}`, body);
|
|
361
372
|
}
|
|
@@ -3924,6 +3935,10 @@ async function publishDraft(scope) {
|
|
|
3924
3935
|
...published.warnings ? { warnings: published.warnings } : {}
|
|
3925
3936
|
});
|
|
3926
3937
|
}
|
|
3938
|
+
/** The section ids that are not control ids at all. GDPR-1.1 is a control id, so it is not among them, though it names no single policy. */
|
|
3939
|
+
function notControls(sections) {
|
|
3940
|
+
return sections.filter((id) => !isControlId(id.toUpperCase()));
|
|
3941
|
+
}
|
|
3927
3942
|
/** The policies the section ids name when every one is a control id (SEC-5.1), else none. */
|
|
3928
3943
|
function policiesNamed(sections) {
|
|
3929
3944
|
if (sections.length === 0) return [];
|
|
@@ -3949,7 +3964,7 @@ function registerDocumentTools(server, client) {
|
|
|
3949
3964
|
sections: params.sections?.map((id) => id.toUpperCase())
|
|
3950
3965
|
} : params;
|
|
3951
3966
|
if (args.type === void 0) {
|
|
3952
|
-
if (args.sections) return toolError("VALIDATION_ERROR", named.length > 1 ? `Those control ids are in different policies (${named.join(", ")}): read one type at a time.` : "Name the document (type) to read its sections; a control id such as SEC-5.1 names its policy itself.");
|
|
3967
|
+
if (args.sections) return toolError("VALIDATION_ERROR", named.length > 1 ? `Those control ids are in different policies (${named.join(", ")}): read one type at a time.` : notControls(args.sections).length > 0 && notControls(args.sections).length < args.sections.length ? `Not a control id: ${notControls(args.sections).join(", ")}. A control id such as SEC-5.1 names its policy; any other section id needs type.` : "Name the document (type) to read its sections; a control id such as SEC-5.1 names its policy itself, but one of a family that spans policies (GDPR, NIS2) does not.");
|
|
3953
3968
|
const result = await client.listTenantDocuments({
|
|
3954
3969
|
limit: args.limit,
|
|
3955
3970
|
cursor: args.cursor
|
|
@@ -4707,7 +4722,7 @@ function waitingOn(rows, station) {
|
|
|
4707
4722
|
}
|
|
4708
4723
|
const STATION_NAMED = 3;
|
|
4709
4724
|
/** Who holds the station, or null when the key cannot read the backlog or the read failed. */
|
|
4710
|
-
async function stationHolders(client, product,
|
|
4725
|
+
async function stationHolders(client, product, except) {
|
|
4711
4726
|
if (!client.hasCapability("backlog:read")) return null;
|
|
4712
4727
|
try {
|
|
4713
4728
|
const columns = await Promise.all(["plan", "build"].map((state) => client.listBacklogItems(product, {
|
|
@@ -4715,7 +4730,7 @@ async function stationHolders(client, product, head) {
|
|
|
4715
4730
|
order: "newest",
|
|
4716
4731
|
limit: 100
|
|
4717
4732
|
})));
|
|
4718
|
-
const rows = columns.flatMap((page) => page.data).filter((row) => row.id
|
|
4733
|
+
const rows = columns.flatMap((page) => page.data).filter((row) => !except.has(row.id));
|
|
4719
4734
|
const capped = columns.some((page) => page.nextCursor !== null);
|
|
4720
4735
|
return {
|
|
4721
4736
|
count: rows.length,
|
|
@@ -4791,10 +4806,14 @@ async function queueView(client, product, options = { name: "all" }) {
|
|
|
4791
4806
|
});
|
|
4792
4807
|
const note = unnamed === 0 ? null : !canName ? "Entries carry initiativeId only: naming them needs backlog:read, which this key lacks." : entries.length > NAMED_ENTRIES ? `Only the first ${NAMED_ENTRIES} entries are named; the rest carry initiativeId only.` : "An entry whose initiative could not be read carries initiativeId only; taiga_get_initiative reads it.";
|
|
4793
4808
|
const line = typeof paused === "string" ? `The line is paused (since ${paused}): nothing is planned or built until a person resumes it.` : null;
|
|
4809
|
+
const idle = rows.length > 0 && blockingIndex(rows, (row) => row.entryState) < 0;
|
|
4810
|
+
const station = rows.length > 0 || options.name === "all" ? await stationHolders(client, product, new Set(idle ? [rows[0].backlogItemId] : rows.map((row) => row.backlogItemId))) : null;
|
|
4811
|
+
const offLine = !idle && station && station.count > 0 ? { offTheLine: `${station.atLeast ? "At least " : ""}${station.count} initiative${station.count === 1 ? " is" : "s are"} in Plan or Build without a queue entry (${station.titles.map((title) => `"${title}"`).join(", ")}${station.count > station.titles.length ? ", and more" : ""}): moved there by hand or waiting on a pull request review. Each holds the station like the active entry; taiga_list_initiatives with column: 'plan' or 'build' lists them.` } : {};
|
|
4794
4812
|
return {
|
|
4795
4813
|
linePaused: paused === null ? null : typeof paused === "string",
|
|
4796
4814
|
entries: rows.map(({ lifecycle: _lifecycle, runId: _runId, ...row }) => row),
|
|
4797
|
-
waitingOn: line ?? waitingOn(rows,
|
|
4815
|
+
waitingOn: line ?? waitingOn(rows, idle ? station : null),
|
|
4816
|
+
...offLine,
|
|
4798
4817
|
...note ? { note } : {}
|
|
4799
4818
|
};
|
|
4800
4819
|
}
|
|
@@ -5398,6 +5417,19 @@ function registerPlanVerdicts(server, client) {
|
|
|
5398
5417
|
* next sweep no longer detects it, so there is no tool for either.
|
|
5399
5418
|
*/
|
|
5400
5419
|
/** A finding as a list row: enough to decide whether to open, fix or ignore it. */
|
|
5420
|
+
const PACKAGE_ROW_FIELDS = [
|
|
5421
|
+
"id",
|
|
5422
|
+
"ecosystem",
|
|
5423
|
+
"packageName",
|
|
5424
|
+
"version",
|
|
5425
|
+
"purl",
|
|
5426
|
+
"licenses",
|
|
5427
|
+
"scope",
|
|
5428
|
+
"relation",
|
|
5429
|
+
"sourcePath",
|
|
5430
|
+
"productRepositoryId",
|
|
5431
|
+
"detectedAt"
|
|
5432
|
+
];
|
|
5401
5433
|
const FINDING_ROW_FIELDS = [
|
|
5402
5434
|
"id",
|
|
5403
5435
|
"kind",
|
|
@@ -5619,6 +5651,35 @@ function registerMaintenanceTools(server, client) {
|
|
|
5619
5651
|
...params.status === void 0 && params.severity === void 0 && params.kind === void 0 && typeof result.totalCount === "number" && sweep && sweep !== "unread" && typeof sweep["openFindings"] === "number" && sweep["openFindings"] !== result.totalCount ? { totalNote: "totalCount includes findings with a fix under way (remediating) and ignored findings whose ignore has expired; sweep.openFindings counts open ones only." } : {}
|
|
5620
5652
|
});
|
|
5621
5653
|
}));
|
|
5654
|
+
server.registerTool("taiga_list_packages", {
|
|
5655
|
+
title: "List the package inventory",
|
|
5656
|
+
description: "Every package a product's repositories resolve, vulnerable or not, as the latest sweep read it: name, version, ecosystem, SPDX licenses, scope (dev / runtime / unknown), relation (direct / transitive / unknown) and the lockfile. The first page opens with licenses: each license string across the inventory, how many packages carry it and its standing against the allowlist (allowed, not_allowed, unknown), plus the allowlist and when each repository was last inventoried. Filter by one license string (UNKNOWN for the ones the data source could not name), a name substring or a repository. A license outside the allowlist is also a license_violation finding in taiga_list_findings. Where the licenses feature is not enabled the product answers NOT_FOUND. taiga_get_sbom downloads one repository as CycloneDX.",
|
|
5657
|
+
inputSchema: z.object({
|
|
5658
|
+
productId,
|
|
5659
|
+
license: z.string().min(1).max(4096).optional().describe("Only packages carrying exactly this SPDX string"),
|
|
5660
|
+
search: z.string().min(1).max(255).optional().describe("Case-insensitive substring of the package name"),
|
|
5661
|
+
productRepositoryId: idInput("repository", "taiga_list_product_repositories gives it", "only this repository").optional(),
|
|
5662
|
+
...pagination
|
|
5663
|
+
}),
|
|
5664
|
+
annotations: READ_ONLY
|
|
5665
|
+
}, (params) => runTool(async () => {
|
|
5666
|
+
const { productId: id, ...filters } = params;
|
|
5667
|
+
const [result, summary] = await Promise.all([client.listPackages(id, filters), params.cursor === void 0 ? client.getLicenseSummary(id) : Promise.resolve(null)]);
|
|
5668
|
+
return toolResult({
|
|
5669
|
+
...summary ? { licenses: summary.data } : {},
|
|
5670
|
+
...page(pickFields(result.data, PACKAGE_ROW_FIELDS), result.nextCursor),
|
|
5671
|
+
...typeof result.totalCount === "number" ? { totalCount: result.totalCount } : {}
|
|
5672
|
+
});
|
|
5673
|
+
}));
|
|
5674
|
+
server.registerTool("taiga_get_sbom", {
|
|
5675
|
+
title: "Download a repository SBOM",
|
|
5676
|
+
description: "One repository's software bill of materials as a CycloneDX 1.5 JSON document, rendered from the stored package inventory: metadata (timestamp, tools, the repository), every package with its purl, license and scope, and the packages the repository declares directly. The whole document is returned, so for a large repository the answer is large; read taiga_list_packages when a summary is enough. NOT_FOUND where the licenses feature is not enabled, and for a repository no sweep has inventoried yet.",
|
|
5677
|
+
inputSchema: z.object({
|
|
5678
|
+
productId,
|
|
5679
|
+
repositoryId: idInput("repository", "taiga_list_product_repositories gives it")
|
|
5680
|
+
}),
|
|
5681
|
+
annotations: READ_ONLY
|
|
5682
|
+
}, (params) => runTool(async () => toolResult(await client.getSbom(params.productId, params.repositoryId))));
|
|
5622
5683
|
server.registerTool("taiga_get_finding", {
|
|
5623
5684
|
title: "Get a maintenance finding",
|
|
5624
5685
|
description: "Read one maintenance finding: exploit likelihood (EPSS, known-exploited list), the advisory summarized (full: true for its whole record: every affected range and reference) and fixed version, code location, breached policy controls, why it ranks where it does, history (reintroduced, previously resolved) and the remediation under way. fixable: false with fixNote says taiga_fix_finding would refuse it. A secret's value is never stored, only the rule and location.",
|
|
@@ -6558,10 +6619,15 @@ async function standingPart(client, productId) {
|
|
|
6558
6619
|
const standing = (await client.getPolicyStanding(productId)).data;
|
|
6559
6620
|
const summary = standingSummary(standing);
|
|
6560
6621
|
const state = standing.assessment?.state;
|
|
6622
|
+
const controls = Array.isArray(standing.controls) ? standing.controls : [];
|
|
6623
|
+
const withAny = (key) => controls.filter((control) => Number(control[key] ?? 0) > 0).length;
|
|
6561
6624
|
return {
|
|
6562
6625
|
openViolations: summary.violations,
|
|
6563
6626
|
bySeverity: summary.bySeverity,
|
|
6564
|
-
|
|
6627
|
+
controlsBreached: withAny("findingCount"),
|
|
6628
|
+
controlsDiverged: withAny("divergenceCount"),
|
|
6629
|
+
controlsMet: summary.controlsMet,
|
|
6630
|
+
controlsNotApplicable: summary.controlsNotApplicable,
|
|
6565
6631
|
...state === "never" || state === "failed" ? { note: `The assessment ${state === "never" ? "has never run" : "failed"}: no violations here is not an all-clear.` } : {}
|
|
6566
6632
|
};
|
|
6567
6633
|
}
|
|
@@ -6919,7 +6985,7 @@ const ENVIRONMENT_FIELDS = [
|
|
|
6919
6985
|
*/
|
|
6920
6986
|
function deploymentLines(rows) {
|
|
6921
6987
|
return rows.map((row) => {
|
|
6922
|
-
const repository = row.metadata?.
|
|
6988
|
+
const repository = row.metadata?.repositoryFullName;
|
|
6923
6989
|
return {
|
|
6924
6990
|
...pickFields([row], DEPLOYMENT_FIELDS)[0],
|
|
6925
6991
|
repository: typeof repository === "string" ? repository : null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ai/mcp-server",
|
|
3
|
-
"version": "0.26.0-rc.
|
|
3
|
+
"version": "0.26.0-rc.514",
|
|
4
4
|
"description": "Taiga for AI coding assistants: read products, policies, initiatives and runs, and operate the line, over MCP",
|
|
5
5
|
"mcpName": "ga.tai/mcp-server",
|
|
6
6
|
"type": "module",
|