@taiga-ai/mcp-server 0.26.0-dev.506 → 0.26.0-dev.507

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.
Files changed (2) hide show
  1. package/dist/index.js +69 -14
  2. 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 = 2, version = SERVER_VERSION) {
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) : worded, error.details);
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
- if (params.type === void 0) {
3917
- if (params.sections) return toolError("VALIDATION_ERROR", "Name the document (type) to read its sections.");
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: params.limit,
3920
- cursor: params.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 = params.type;
3925
- if (params.limit !== void 0 || params.cursor !== void 0) return toolError("VALIDATION_ERROR", "limit and cursor page the list; omit type to list, or drop them to read.");
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 params.sections ? sectionReadResult(doc, params.sections, type, { controlIds: POLICY_TYPES.includes(type) }) : toolResult(doc);
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.506",
3
+ "version": "0.26.0-dev.507",
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",