@taiga-ai/mcp-server 0.26.0-rc.504 → 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.
Files changed (2) hide show
  1. package/dist/index.js +140 -19
  2. 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
  }
@@ -1457,6 +1468,24 @@ function hardeningCoverage(result) {
1457
1468
  gaps
1458
1469
  };
1459
1470
  }
1471
+ //#endregion
1472
+ //#region ../../packages/types/dist/inbox.js
1473
+ /** What action is required. Drives icon, copy, and completion logic. */
1474
+ const INBOX_ITEM_KINDS = [
1475
+ "review_plan",
1476
+ "review_backlog",
1477
+ "review_doc",
1478
+ "review_pr",
1479
+ "review_maintenance",
1480
+ "review_policy",
1481
+ "setup_env_var",
1482
+ "setup_secret",
1483
+ "setup_third_party",
1484
+ "setup_dns",
1485
+ "setup_billing",
1486
+ "setup_cloud_permission",
1487
+ "setup_manual"
1488
+ ];
1460
1489
  z.discriminatedUnion("kind", [
1461
1490
  z.object({
1462
1491
  type: z.literal("narrative"),
@@ -2443,7 +2472,7 @@ const DOCUMENT_INPUTS = Object.freeze({
2443
2472
  * reads as deleted) and fields they explain would simply be missing. So it
2444
2473
  * refuses to start, and says what to do.
2445
2474
  */
2446
- function apiLevelProblem(introspection, apiUrl, required = 2, version = SERVER_VERSION) {
2475
+ function apiLevelProblem(introspection, apiUrl, required = 3, version = SERVER_VERSION) {
2447
2476
  const offered = introspection.mcpApiLevel ?? 0;
2448
2477
  if (offered >= required) return null;
2449
2478
  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 +2963,11 @@ async function runTool(handler) {
2934
2963
  if (error instanceof TaigaApiError) {
2935
2964
  const code = error.code ?? CODE_BY_STATUS[error.status] ?? "API_ERROR";
2936
2965
  const worded = inProductWording(error.apiMessage ?? `Taiga API answered ${error.status}.`);
2937
- return toolError(code, error.status === 404 ? withNotFoundHint(worded) : worded, error.details);
2966
+ return toolError(code, error.status === 404 ? withNotFoundHint(worded) : [
2967
+ 502,
2968
+ 503,
2969
+ 504
2970
+ ].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
2971
  }
2939
2972
  if (isNetworkFailure(error)) return toolError("NETWORK_ERROR", `Could not reach the Taiga API: ${networkFailureDetail(error)}. Check TAIGA_API_URL.`);
2940
2973
  throw error;
@@ -3902,10 +3935,21 @@ async function publishDraft(scope) {
3902
3935
  ...published.warnings ? { warnings: published.warnings } : {}
3903
3936
  });
3904
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
+ }
3942
+ /** The policies the section ids name when every one is a control id (SEC-5.1), else none. */
3943
+ function policiesNamed(sections) {
3944
+ if (sections.length === 0) return [];
3945
+ const types = sections.map((id) => isControlId(id.toUpperCase()) ? policyNamedByControl(id.toUpperCase()) : void 0);
3946
+ if (types.some((type) => type === void 0)) return [];
3947
+ return [...new Set(types)];
3948
+ }
3905
3949
  function registerDocumentTools(server, client) {
3906
3950
  if (client.hasCapability("tenant:documents:read")) server.registerTool("taiga_get_organization_document", {
3907
3951
  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.`,
3952
+ 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
3953
  inputSchema: z.object({
3910
3954
  type: tenantDocumentType.optional(),
3911
3955
  sections: policySectionsInput,
@@ -3913,19 +3957,25 @@ function registerDocumentTools(server, client) {
3913
3957
  }),
3914
3958
  annotations: READ_ONLY
3915
3959
  }, (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.");
3960
+ const named = params.type === void 0 && params.sections ? policiesNamed(params.sections) : [];
3961
+ const args = params.type === void 0 && named.length === 1 ? {
3962
+ ...params,
3963
+ type: named[0],
3964
+ sections: params.sections?.map((id) => id.toUpperCase())
3965
+ } : params;
3966
+ if (args.type === void 0) {
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.");
3918
3968
  const result = await client.listTenantDocuments({
3919
- limit: params.limit,
3920
- cursor: params.cursor
3969
+ limit: args.limit,
3970
+ cursor: args.cursor
3921
3971
  });
3922
3972
  return toolResult(page(summaries(result.data), result.nextCursor));
3923
3973
  }
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.");
3974
+ const type = args.type;
3975
+ 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
3976
  try {
3927
3977
  const doc = stripProvenance((await client.getTenantDocument(type)).data);
3928
- return params.sections ? sectionReadResult(doc, params.sections, type, { controlIds: POLICY_TYPES.includes(type) }) : toolResult(doc);
3978
+ return args.sections ? sectionReadResult(doc, args.sections, type, { controlIds: POLICY_TYPES.includes(type) }) : toolResult(doc);
3929
3979
  } catch (error) {
3930
3980
  if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
3931
3981
  const [draft, all] = await Promise.allSettled([client.getTenantDocumentDraft(type), client.listTenantDocuments({ limit: 100 })]);
@@ -4672,7 +4722,7 @@ function waitingOn(rows, station) {
4672
4722
  }
4673
4723
  const STATION_NAMED = 3;
4674
4724
  /** Who holds the station, or null when the key cannot read the backlog or the read failed. */
4675
- async function stationHolders(client, product, head) {
4725
+ async function stationHolders(client, product, except) {
4676
4726
  if (!client.hasCapability("backlog:read")) return null;
4677
4727
  try {
4678
4728
  const columns = await Promise.all(["plan", "build"].map((state) => client.listBacklogItems(product, {
@@ -4680,7 +4730,7 @@ async function stationHolders(client, product, head) {
4680
4730
  order: "newest",
4681
4731
  limit: 100
4682
4732
  })));
4683
- const rows = columns.flatMap((page) => page.data).filter((row) => row.id !== head);
4733
+ const rows = columns.flatMap((page) => page.data).filter((row) => !except.has(row.id));
4684
4734
  const capped = columns.some((page) => page.nextCursor !== null);
4685
4735
  return {
4686
4736
  count: rows.length,
@@ -4756,10 +4806,14 @@ async function queueView(client, product, options = { name: "all" }) {
4756
4806
  });
4757
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.";
4758
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.` } : {};
4759
4812
  return {
4760
4813
  linePaused: paused === null ? null : typeof paused === "string",
4761
4814
  entries: rows.map(({ lifecycle: _lifecycle, runId: _runId, ...row }) => row),
4762
- waitingOn: line ?? waitingOn(rows, rows.length > 0 && blockingIndex(rows, (row) => row.entryState) < 0 ? await stationHolders(client, product, rows[0].backlogItemId) : null),
4815
+ waitingOn: line ?? waitingOn(rows, idle ? station : null),
4816
+ ...offLine,
4763
4817
  ...note ? { note } : {}
4764
4818
  };
4765
4819
  }
@@ -5363,6 +5417,19 @@ function registerPlanVerdicts(server, client) {
5363
5417
  * next sweep no longer detects it, so there is no tool for either.
5364
5418
  */
5365
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
+ ];
5366
5433
  const FINDING_ROW_FIELDS = [
5367
5434
  "id",
5368
5435
  "kind",
@@ -5436,7 +5503,7 @@ function findingRows(findings, extra = []) {
5436
5503
  "dependencyGroups",
5437
5504
  "dependencyRelation",
5438
5505
  ...extra
5439
- ]).map((row) => withDependencyScope(row)).map(withFixability);
5506
+ ]).map((row) => withDependencyScope(row)).map(withFixability).map((row) => Object.fromEntries(Object.entries(row).filter(([key, value]) => value !== null || key === "ignoreExpiresAt" && row["status"] === "ignored")));
5440
5507
  }
5441
5508
  /** The parts of an advisory record an agent reads; the rest (every affected range, every reference) is full: true. */
5442
5509
  const ADVISORY_KEPT = [
@@ -5544,9 +5611,10 @@ function registerMaintenanceTools(server, client) {
5544
5611
  if (!client.hasCapability("products:read")) return;
5545
5612
  server.registerTool("taiga_list_findings", {
5546
5613
  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.",
5614
+ 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
5615
  inputSchema: z.object({
5549
5616
  productId,
5617
+ 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
5618
  status: z.enum(MAINTENANCE_FINDING_STATUSES).optional().describe("Only findings in this status; omit for the ones needing action"),
5551
5619
  severity: z.enum(MAINTENANCE_FINDING_SEVERITIES).optional().describe("Only this severity"),
5552
5620
  kind: z.enum(MAINTENANCE_FINDING_KINDS).optional().describe("Only this kind of finding"),
@@ -5554,7 +5622,21 @@ function registerMaintenanceTools(server, client) {
5554
5622
  }),
5555
5623
  annotations: READ_ONLY
5556
5624
  }, (params) => runTool(async () => {
5557
- const { productId: id, ...filters } = params;
5625
+ const { productId: id, controlId, ...filters } = params;
5626
+ if (controlId) {
5627
+ if (params.cursor !== void 0) return toolError("VALIDATION_ERROR", "controlId reads every page itself; drop the cursor.");
5628
+ const wanted = controlId.toUpperCase();
5629
+ const found = await searchPages((cursor) => client.listFindings(id, {
5630
+ ...filters,
5631
+ limit: 100,
5632
+ cursor
5633
+ }), "", () => [""], params.limit ?? 25, (row) => Array.isArray(row["controls"]) && row["controls"].some((c) => String(c).toUpperCase() === wanted));
5634
+ return toolResult({
5635
+ ...page(findingRows(found.rows), null),
5636
+ ...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." } : {},
5637
+ ...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.` } : {}
5638
+ });
5639
+ }
5558
5640
  const [result, sweep] = await Promise.all([client.listFindings(id, filters), params.cursor === void 0 ? sweepOverview(client, id).catch((error) => {
5559
5641
  if (error instanceof TaigaApiError || isNetworkFailure(error)) return "unread";
5560
5642
  throw error;
@@ -5569,6 +5651,35 @@ function registerMaintenanceTools(server, client) {
5569
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." } : {}
5570
5652
  });
5571
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))));
5572
5683
  server.registerTool("taiga_get_finding", {
5573
5684
  title: "Get a maintenance finding",
5574
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.",
@@ -5788,6 +5899,9 @@ function readWith(row, backlogRead) {
5788
5899
  * were the same things.
5789
5900
  */
5790
5901
  const KIND_SAID = { review_backlog: "review_initiatives" };
5902
+ /** The kinds as the rows say them, for the kind filter, and the way back to the API's word. */
5903
+ const KINDS_SAID = INBOX_ITEM_KINDS.map((kind) => KIND_SAID[kind] ?? kind);
5904
+ const KIND_FROM_SAID = Object.fromEntries(Object.entries(KIND_SAID).map(([api, said]) => [said, api]));
5791
5905
  const SUBJECT_SAID = {
5792
5906
  backlog_item: "initiative",
5793
5907
  build_run: "run",
@@ -6060,15 +6174,17 @@ function registerInboxTools(server, client) {
6060
6174
  inputSchema: z.object({
6061
6175
  productId: productId.optional(),
6062
6176
  category: z.enum(["gate", "setup"]).optional().describe("gate: things to review; setup: things to set up"),
6177
+ kind: z.enum(KINDS_SAID).optional().describe("Only this kind, such as review_plan or review_pr, as the rows name it"),
6063
6178
  resolved: z.boolean().optional().describe("Read the resolved archive (done and dismissed) instead"),
6064
6179
  full: z.boolean().optional().describe("Carry each item's metadata, such as a document decision's whole text (default false)"),
6065
6180
  ...pagination
6066
6181
  }),
6067
6182
  annotations: READ_ONLY
6068
6183
  }, (params) => runTool(async () => {
6069
- const { resolved, full, ...rest } = params;
6184
+ const { resolved, full, kind, ...rest } = params;
6070
6185
  const query = {
6071
6186
  ...rest,
6187
+ ...kind ? { kind: KIND_FROM_SAID[kind] ?? kind } : {},
6072
6188
  ...resolved ? { resolved: true } : {}
6073
6189
  };
6074
6190
  const first = params.cursor === void 0;
@@ -6503,10 +6619,15 @@ async function standingPart(client, productId) {
6503
6619
  const standing = (await client.getPolicyStanding(productId)).data;
6504
6620
  const summary = standingSummary(standing);
6505
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;
6506
6624
  return {
6507
6625
  openViolations: summary.violations,
6508
6626
  bySeverity: summary.bySeverity,
6509
- controlsViolated: summary.controls,
6627
+ controlsBreached: withAny("findingCount"),
6628
+ controlsDiverged: withAny("divergenceCount"),
6629
+ controlsMet: summary.controlsMet,
6630
+ controlsNotApplicable: summary.controlsNotApplicable,
6510
6631
  ...state === "never" || state === "failed" ? { note: `The assessment ${state === "never" ? "has never run" : "failed"}: no violations here is not an all-clear.` } : {}
6511
6632
  };
6512
6633
  }
@@ -6864,7 +6985,7 @@ const ENVIRONMENT_FIELDS = [
6864
6985
  */
6865
6986
  function deploymentLines(rows) {
6866
6987
  return rows.map((row) => {
6867
- const repository = row.metadata?.["repositoryFullName"];
6988
+ const repository = row.metadata?.repositoryFullName;
6868
6989
  return {
6869
6990
  ...pickFields([row], DEPLOYMENT_FIELDS)[0],
6870
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.504",
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",