@taiga-ai/mcp-server 0.26.0-dev.506 → 0.26.0-dev.508
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 +69 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1457,6 +1457,24 @@ function hardeningCoverage(result) {
|
|
|
1457
1457
|
gaps
|
|
1458
1458
|
};
|
|
1459
1459
|
}
|
|
1460
|
+
//#endregion
|
|
1461
|
+
//#region ../../packages/types/dist/inbox.js
|
|
1462
|
+
/** What action is required. Drives icon, copy, and completion logic. */
|
|
1463
|
+
const INBOX_ITEM_KINDS = [
|
|
1464
|
+
"review_plan",
|
|
1465
|
+
"review_backlog",
|
|
1466
|
+
"review_doc",
|
|
1467
|
+
"review_pr",
|
|
1468
|
+
"review_maintenance",
|
|
1469
|
+
"review_policy",
|
|
1470
|
+
"setup_env_var",
|
|
1471
|
+
"setup_secret",
|
|
1472
|
+
"setup_third_party",
|
|
1473
|
+
"setup_dns",
|
|
1474
|
+
"setup_billing",
|
|
1475
|
+
"setup_cloud_permission",
|
|
1476
|
+
"setup_manual"
|
|
1477
|
+
];
|
|
1460
1478
|
z.discriminatedUnion("kind", [
|
|
1461
1479
|
z.object({
|
|
1462
1480
|
type: z.literal("narrative"),
|
|
@@ -2443,7 +2461,7 @@ const DOCUMENT_INPUTS = Object.freeze({
|
|
|
2443
2461
|
* reads as deleted) and fields they explain would simply be missing. So it
|
|
2444
2462
|
* refuses to start, and says what to do.
|
|
2445
2463
|
*/
|
|
2446
|
-
function apiLevelProblem(introspection, apiUrl, required =
|
|
2464
|
+
function apiLevelProblem(introspection, apiUrl, required = 3, version = SERVER_VERSION) {
|
|
2447
2465
|
const offered = introspection.mcpApiLevel ?? 0;
|
|
2448
2466
|
if (offered >= required) return null;
|
|
2449
2467
|
return `The Taiga API at ${apiUrl} is older than @taiga-ai/mcp-server ${version} needs (API level ${offered}, needs ${required}). Run the version published for it, or wait for that API to be updated; an earlier release of this package works against it.`;
|
|
@@ -2934,7 +2952,11 @@ async function runTool(handler) {
|
|
|
2934
2952
|
if (error instanceof TaigaApiError) {
|
|
2935
2953
|
const code = error.code ?? CODE_BY_STATUS[error.status] ?? "API_ERROR";
|
|
2936
2954
|
const worded = inProductWording(error.apiMessage ?? `Taiga API answered ${error.status}.`);
|
|
2937
|
-
return toolError(code, error.status === 404 ? withNotFoundHint(worded) :
|
|
2955
|
+
return toolError(code, error.status === 404 ? withNotFoundHint(worded) : [
|
|
2956
|
+
502,
|
|
2957
|
+
503,
|
|
2958
|
+
504
|
|
2959
|
+
].includes(error.status) ? `${withStop(worded)} This is usually temporary: a read may answer on a retry; after a write, check whether it took effect before repeating it.` : worded, error.details);
|
|
2938
2960
|
}
|
|
2939
2961
|
if (isNetworkFailure(error)) return toolError("NETWORK_ERROR", `Could not reach the Taiga API: ${networkFailureDetail(error)}. Check TAIGA_API_URL.`);
|
|
2940
2962
|
throw error;
|
|
@@ -3902,10 +3924,17 @@ async function publishDraft(scope) {
|
|
|
3902
3924
|
...published.warnings ? { warnings: published.warnings } : {}
|
|
3903
3925
|
});
|
|
3904
3926
|
}
|
|
3927
|
+
/** The policies the section ids name when every one is a control id (SEC-5.1), else none. */
|
|
3928
|
+
function policiesNamed(sections) {
|
|
3929
|
+
if (sections.length === 0) return [];
|
|
3930
|
+
const types = sections.map((id) => isControlId(id.toUpperCase()) ? policyNamedByControl(id.toUpperCase()) : void 0);
|
|
3931
|
+
if (types.some((type) => type === void 0)) return [];
|
|
3932
|
+
return [...new Set(types)];
|
|
3933
|
+
}
|
|
3905
3934
|
function registerDocumentTools(server, client) {
|
|
3906
3935
|
if (client.hasCapability("tenant:documents:read")) server.registerTool("taiga_get_organization_document", {
|
|
3907
3936
|
title: "Get an organization document",
|
|
3908
|
-
description: `Read the organization's own documents, which apply to every product. Two answers: without type, the list (type, name, version, draft state); with type, that document. The ${POLICY_TYPES.length} policies (${named$1(POLICY_TYPES)}) set the rules for every product; the others (${named$1(OTHER_TENANT_DOCUMENT_TYPES)}) carry goals, standards, voice and instructions. Policies are long: pass sections: [] for the outline, then the section ids you need, or a control id such as SEC-5.1; unknownSections names ids it lacks. A miss says which documents are published and whether a draft exists. A section's source (template, overlay-<id>, cloud-preset:<providers>) and priority (core: every organization keeps it) do not change what it requires.`,
|
|
3937
|
+
description: `Read the organization's own documents, which apply to every product. Two answers: without type, the list (type, name, version, draft state); with type, that document. The ${POLICY_TYPES.length} policies (${named$1(POLICY_TYPES)}) set the rules for every product; the others (${named$1(OTHER_TENANT_DOCUMENT_TYPES)}) carry goals, standards, voice and instructions. Policies are long: pass sections: [] for the outline, then the section ids you need, or a control id such as SEC-5.1 (which names its policy, so type may be left out); unknownSections names ids it lacks. A miss says which documents are published and whether a draft exists. A section's source (template, overlay-<id>, cloud-preset:<providers>) and priority (core: every organization keeps it) do not change what it requires.`,
|
|
3909
3938
|
inputSchema: z.object({
|
|
3910
3939
|
type: tenantDocumentType.optional(),
|
|
3911
3940
|
sections: policySectionsInput,
|
|
@@ -3913,19 +3942,25 @@ function registerDocumentTools(server, client) {
|
|
|
3913
3942
|
}),
|
|
3914
3943
|
annotations: READ_ONLY
|
|
3915
3944
|
}, (params) => runTool(async () => {
|
|
3916
|
-
|
|
3917
|
-
|
|
3945
|
+
const named = params.type === void 0 && params.sections ? policiesNamed(params.sections) : [];
|
|
3946
|
+
const args = params.type === void 0 && named.length === 1 ? {
|
|
3947
|
+
...params,
|
|
3948
|
+
type: named[0],
|
|
3949
|
+
sections: params.sections?.map((id) => id.toUpperCase())
|
|
3950
|
+
} : params;
|
|
3951
|
+
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.");
|
|
3918
3953
|
const result = await client.listTenantDocuments({
|
|
3919
|
-
limit:
|
|
3920
|
-
cursor:
|
|
3954
|
+
limit: args.limit,
|
|
3955
|
+
cursor: args.cursor
|
|
3921
3956
|
});
|
|
3922
3957
|
return toolResult(page(summaries(result.data), result.nextCursor));
|
|
3923
3958
|
}
|
|
3924
|
-
const type =
|
|
3925
|
-
if (
|
|
3959
|
+
const type = args.type;
|
|
3960
|
+
if (args.limit !== void 0 || args.cursor !== void 0) return toolError("VALIDATION_ERROR", params.type === void 0 ? "limit and cursor page the list, which takes no sections; drop them to read the control." : "limit and cursor page the list; omit type to list, or drop them to read.");
|
|
3926
3961
|
try {
|
|
3927
3962
|
const doc = stripProvenance((await client.getTenantDocument(type)).data);
|
|
3928
|
-
return
|
|
3963
|
+
return args.sections ? sectionReadResult(doc, args.sections, type, { controlIds: POLICY_TYPES.includes(type) }) : toolResult(doc);
|
|
3929
3964
|
} catch (error) {
|
|
3930
3965
|
if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
|
|
3931
3966
|
const [draft, all] = await Promise.allSettled([client.getTenantDocumentDraft(type), client.listTenantDocuments({ limit: 100 })]);
|
|
@@ -5436,7 +5471,7 @@ function findingRows(findings, extra = []) {
|
|
|
5436
5471
|
"dependencyGroups",
|
|
5437
5472
|
"dependencyRelation",
|
|
5438
5473
|
...extra
|
|
5439
|
-
]).map((row) => withDependencyScope(row)).map(withFixability);
|
|
5474
|
+
]).map((row) => withDependencyScope(row)).map(withFixability).map((row) => Object.fromEntries(Object.entries(row).filter(([key, value]) => value !== null || key === "ignoreExpiresAt" && row["status"] === "ignored")));
|
|
5440
5475
|
}
|
|
5441
5476
|
/** The parts of an advisory record an agent reads; the rest (every affected range, every reference) is full: true. */
|
|
5442
5477
|
const ADVISORY_KEPT = [
|
|
@@ -5544,9 +5579,10 @@ function registerMaintenanceTools(server, client) {
|
|
|
5544
5579
|
if (!client.hasCapability("products:read")) return;
|
|
5545
5580
|
server.registerTool("taiga_list_findings", {
|
|
5546
5581
|
title: "List maintenance findings",
|
|
5547
|
-
description: "List what maintenance sweeps found in a product's repository: dependencies, secrets, insecure code (sast) and infrastructure. The first page opens with sweep (open counts, latest sweeps), so an old or failed sweep is not read as an all-clear. Rows: severity, tier (act_now: known exploited; urgent: likely and serious; elevated: one of the two; routine), location, remediableBy (coding_agent: a run here can fix it; upstream_only: the code is in a submodule, so the fix belongs in its own repository), fixable: false when no fixed version is known yet, breached controls and initiativeId of a fix under way. Omit status for open findings. taiga_get_finding reads one.",
|
|
5582
|
+
description: "List what maintenance sweeps found in a product's repository: dependencies, secrets, insecure code (sast) and infrastructure. The first page opens with sweep (open counts, latest sweeps), so an old or failed sweep is not read as an all-clear. Rows: severity, tier (act_now: known exploited; urgent: likely and serious; elevated: one of the two; routine), location, remediableBy (coding_agent: a run here can fix it; upstream_only: the code is in a submodule, so the fix belongs in its own repository), fixable: false when no fixed version is known yet, breached controls and initiativeId of a fix under way. Omit status for open findings. controlId lists the findings that breach one control. taiga_get_finding reads one.",
|
|
5548
5583
|
inputSchema: z.object({
|
|
5549
5584
|
productId,
|
|
5585
|
+
controlId: z.string().trim().min(1).max(40).optional().describe("Only the findings that breach this control, such as SEC-5.1 (one page, searched across all)"),
|
|
5550
5586
|
status: z.enum(MAINTENANCE_FINDING_STATUSES).optional().describe("Only findings in this status; omit for the ones needing action"),
|
|
5551
5587
|
severity: z.enum(MAINTENANCE_FINDING_SEVERITIES).optional().describe("Only this severity"),
|
|
5552
5588
|
kind: z.enum(MAINTENANCE_FINDING_KINDS).optional().describe("Only this kind of finding"),
|
|
@@ -5554,7 +5590,21 @@ function registerMaintenanceTools(server, client) {
|
|
|
5554
5590
|
}),
|
|
5555
5591
|
annotations: READ_ONLY
|
|
5556
5592
|
}, (params) => runTool(async () => {
|
|
5557
|
-
const { productId: id, ...filters } = params;
|
|
5593
|
+
const { productId: id, controlId, ...filters } = params;
|
|
5594
|
+
if (controlId) {
|
|
5595
|
+
if (params.cursor !== void 0) return toolError("VALIDATION_ERROR", "controlId reads every page itself; drop the cursor.");
|
|
5596
|
+
const wanted = controlId.toUpperCase();
|
|
5597
|
+
const found = await searchPages((cursor) => client.listFindings(id, {
|
|
5598
|
+
...filters,
|
|
5599
|
+
limit: 100,
|
|
5600
|
+
cursor
|
|
5601
|
+
}), "", () => [""], params.limit ?? 25, (row) => Array.isArray(row["controls"]) && row["controls"].some((c) => String(c).toUpperCase() === wanted));
|
|
5602
|
+
return toolResult({
|
|
5603
|
+
...page(findingRows(found.rows), null),
|
|
5604
|
+
...found.more ? { more: found.rows.length < (params.limit ?? 25) ? `Only the first 1000 findings were searched; narrow by status, severity or kind to reach the rest.` : "There may be more than the limit carries: raise the limit, or narrow by status, severity or kind." } : {},
|
|
5605
|
+
...found.rows.length === 0 && !found.more ? { note: `No ${params.status ? `${params.status} ` : "open "}finding breaches ${wanted}. That is no all-clear on its own: taiga_list_findings without controlId says when the product was last swept.` } : {}
|
|
5606
|
+
});
|
|
5607
|
+
}
|
|
5558
5608
|
const [result, sweep] = await Promise.all([client.listFindings(id, filters), params.cursor === void 0 ? sweepOverview(client, id).catch((error) => {
|
|
5559
5609
|
if (error instanceof TaigaApiError || isNetworkFailure(error)) return "unread";
|
|
5560
5610
|
throw error;
|
|
@@ -5788,6 +5838,9 @@ function readWith(row, backlogRead) {
|
|
|
5788
5838
|
* were the same things.
|
|
5789
5839
|
*/
|
|
5790
5840
|
const KIND_SAID = { review_backlog: "review_initiatives" };
|
|
5841
|
+
/** The kinds as the rows say them, for the kind filter, and the way back to the API's word. */
|
|
5842
|
+
const KINDS_SAID = INBOX_ITEM_KINDS.map((kind) => KIND_SAID[kind] ?? kind);
|
|
5843
|
+
const KIND_FROM_SAID = Object.fromEntries(Object.entries(KIND_SAID).map(([api, said]) => [said, api]));
|
|
5791
5844
|
const SUBJECT_SAID = {
|
|
5792
5845
|
backlog_item: "initiative",
|
|
5793
5846
|
build_run: "run",
|
|
@@ -6060,15 +6113,17 @@ function registerInboxTools(server, client) {
|
|
|
6060
6113
|
inputSchema: z.object({
|
|
6061
6114
|
productId: productId.optional(),
|
|
6062
6115
|
category: z.enum(["gate", "setup"]).optional().describe("gate: things to review; setup: things to set up"),
|
|
6116
|
+
kind: z.enum(KINDS_SAID).optional().describe("Only this kind, such as review_plan or review_pr, as the rows name it"),
|
|
6063
6117
|
resolved: z.boolean().optional().describe("Read the resolved archive (done and dismissed) instead"),
|
|
6064
6118
|
full: z.boolean().optional().describe("Carry each item's metadata, such as a document decision's whole text (default false)"),
|
|
6065
6119
|
...pagination
|
|
6066
6120
|
}),
|
|
6067
6121
|
annotations: READ_ONLY
|
|
6068
6122
|
}, (params) => runTool(async () => {
|
|
6069
|
-
const { resolved, full, ...rest } = params;
|
|
6123
|
+
const { resolved, full, kind, ...rest } = params;
|
|
6070
6124
|
const query = {
|
|
6071
6125
|
...rest,
|
|
6126
|
+
...kind ? { kind: KIND_FROM_SAID[kind] ?? kind } : {},
|
|
6072
6127
|
...resolved ? { resolved: true } : {}
|
|
6073
6128
|
};
|
|
6074
6129
|
const first = params.cursor === void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ai/mcp-server",
|
|
3
|
-
"version": "0.26.0-dev.
|
|
3
|
+
"version": "0.26.0-dev.508",
|
|
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",
|