@taiga-ai/mcp-server 0.24.0-rc.462 → 0.25.0-dev.465

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 CHANGED
@@ -202,13 +202,6 @@ var TaigaClient = class {
202
202
  async publishTenantDocument(type) {
203
203
  return this.request("POST", `/tenants/current/documents/${seg(type)}/publish`);
204
204
  }
205
- async listFactoryDocuments(factoryId, opts) {
206
- const params = new URLSearchParams();
207
- if (opts?.limit) params.set("limit", String(opts.limit));
208
- if (opts?.cursor) params.set("cursor", opts.cursor);
209
- const qs = params.toString();
210
- return this.request("GET", `/factories/${seg(factoryId)}/documents${qs ? `?${qs}` : ""}`);
211
- }
212
205
  async getFactoryDocument(factoryId, type) {
213
206
  return this.request("GET", `/factories/${seg(factoryId)}/documents/${seg(type)}`);
214
207
  }
@@ -2251,7 +2244,7 @@ function setupNextStep(product) {
2251
2244
  if (isImportedProduct(product)) switch (importStatusOf(product)) {
2252
2245
  case "analyzing":
2253
2246
  case "generating_docs": return "The import is running and derives the documents from the code (taiga_wait_for_job follows its job). Then review them, and taiga_finish_discovery opens the product.";
2254
- case "complete": return "The import is done: review the reality documents (taiga_list_product_documents), then taiga_finish_discovery opens the product.";
2247
+ case "complete": return "The import is done: review the reality documents (taiga_get_product_document without type), then taiga_finish_discovery opens the product.";
2255
2248
  case "failed":
2256
2249
  case "cancelled": return "The last import did not finish: taiga_start_import runs it again.";
2257
2250
  default: return "Link its repository if it has none (taiga_list_linkable_repositories, taiga_link_repository), then taiga_start_import: the import derives the documents from the code and runs by itself.";
@@ -2735,7 +2728,7 @@ function sectionReadResult(doc, asked, label, options = {}) {
2735
2728
  const found = Object.keys(controls).length > 0 ? { controls } : {};
2736
2729
  const asControl = options.controlIds ? void 0 : unknownSections.find((id) => isControlId(id));
2737
2730
  const policy = asControl ? policyOfControl(asControl) : void 0;
2738
- const controlNote = asControl ? { note: policy ? `${asControl} is a control of ${policy}: read it with taiga_get_document (type: ${policy}, sections: ["${asControl}"]).` : `${asControl} looks like a policy control id, which only a policy resolves: taiga_list_documents lists them.` } : {};
2731
+ const controlNote = asControl ? { note: policy ? `${asControl} is a control of ${policy}: read it with taiga_get_document (type: ${policy}, sections: ["${asControl}"]).` : `${asControl} looks like a policy control id, which only a policy resolves: taiga_get_document without type lists them.` } : {};
2739
2732
  if (known.length === 0) {
2740
2733
  const { data: _data, ...meta } = cut.doc;
2741
2734
  return toolResult({
@@ -2826,8 +2819,8 @@ function nextStepFor(state) {
2826
2819
  case "running": return "The import is running and derives the documents from the code; taiga_wait_for_job follows it.";
2827
2820
  case "failed": return "The last import did not finish: taiga_start_import runs it again.";
2828
2821
  case "complete":
2829
- if (state.missing && state.missing.length > 0) return `The import finished but ${named(state.missing)} ${state.missing.length === 1 ? "is" : "are"} not published: taiga_list_product_documents shows them, taiga_publish_product_document publishes a draft.`;
2830
- return `The import is done: review the reality documents (taiga_list_product_documents), then ${finish}`;
2822
+ if (state.missing && state.missing.length > 0) return `The import finished but ${named(state.missing)} ${state.missing.length === 1 ? "is" : "are"} not published: taiga_get_product_document without type shows them, taiga_publish_product_document publishes a draft.`;
2823
+ return `The import is done: review the reality documents (taiga_get_product_document without type), then ${finish}`;
2831
2824
  default: return "taiga_start_import derives the documents from the code and runs by itself (paid agent work: ask first).";
2832
2825
  }
2833
2826
  }
@@ -3120,7 +3113,7 @@ async function resolveProductRef(client, ref, accepted, usage) {
3120
3113
  return { product: (await client.getProduct(ref.productId)).data };
3121
3114
  } catch (error) {
3122
3115
  if (!isMissOrRefusal(error)) throw error;
3123
- return refusal(apiFailureCode(error), error instanceof TaigaApiError && error.status === 404 ? "No product with that ID the key can see; taiga_list_products shows the ones it can." : `No product with that ID the key can see (${apiFailureText(error)}); taiga_list_products shows the ones it can.`);
3116
+ return refusal(apiFailureCode(error), error instanceof TaigaApiError && error.status === 404 ? "No product with that ID the key can see. taiga_list_products (query finds one by name) shows the ones it can; a slug or git remote URL names one too." : `No product with that ID the key can see (${apiFailureText(error)}); taiga_list_products shows the ones it can.`);
3124
3117
  }
3125
3118
  if (ref.slug) {
3126
3119
  const [product] = (await client.listProducts({
@@ -3242,38 +3235,34 @@ function registerProductTools(server, client) {
3242
3235
  readiness
3243
3236
  } : resolved.product);
3244
3237
  }));
3245
- server.registerTool("taiga_list_product_documents", {
3246
- title: "List product documents",
3247
- description: `List a product's documents on both timelines (type, name, version, draft state, review and update times); defaultOrigin is the product's own. ${ORIGIN_MEANING.plan} ${ORIGIN_MEANING.reality} An empty side says why; an unlisted side is null with a note. taiga_get_product_document reads one.`,
3248
- inputSchema: z.object({ productId }),
3249
- annotations: READ_ONLY
3250
- }, (params) => runTool(async () => {
3251
- const { unlistable: _unlistable, ...timelines } = await documentTimelines(client, params.productId);
3252
- return toolResult(timelines);
3253
- }));
3254
3238
  server.registerTool("taiga_get_product_document", {
3255
3239
  title: "Get product document",
3256
- description: "Read one product document from one timeline: plan (specification, architecture, threat_model, technology_decisions) says what to build; reality says what the code does, so read it before changing code. Omit origin for the product's own timeline. sections: [] returns the outline (ids, sizes); then name the sections you need (unknownSections names missing ids). A miss says which documents exist.",
3240
+ description: "Read one product document from one timeline, or without type list both timelines (type, version, draft and review state; defaultOrigin is the product's own; an empty side says why). plan (specification, architecture, threat_model, technology_decisions) says what to build; reality says what the code does, so read it before changing code. Omit origin for the product's own timeline. sections: [] returns the outline (ids, sizes); then name the sections you need (unknownSections names missing ids). A miss says which documents exist.",
3257
3241
  inputSchema: z.object({
3258
3242
  productId,
3259
- type: z.enum(PRODUCT_DOCUMENT_TYPES).describe("Document type -- taiga_list_product_documents shows which ones the product has"),
3243
+ type: z.enum(PRODUCT_DOCUMENT_TYPES).optional().describe("Document type; omit to list what the product has on both timelines"),
3260
3244
  origin: originInput,
3261
3245
  sections: sectionsInput
3262
3246
  }),
3263
3247
  annotations: READ_ONLY
3264
3248
  }, (params) => runTool(async () => {
3249
+ if (params.type === void 0) {
3250
+ if (params.sections || params.origin) return toolError("VALIDATION_ERROR", "Name the document (type) to read one timeline or its sections.");
3251
+ const { unlistable: _unlistable, ...timelines } = await documentTimelines(client, params.productId);
3252
+ return toolResult(timelines);
3253
+ }
3254
+ const type = params.type;
3265
3255
  let product;
3266
3256
  if (!params.origin) product = (await client.getProduct(params.productId)).data;
3267
- const origin = params.origin ?? defaultOriginFor(product, params.type);
3257
+ const origin = params.origin ?? defaultOriginFor(product, type);
3268
3258
  try {
3269
- const doc = stripProvenance((await client.getProductDocument(params.productId, params.type, origin)).data);
3259
+ const doc = stripProvenance((await client.getProductDocument(params.productId, type, origin)).data);
3270
3260
  if (!params.sections) return toolResult(doc);
3271
- return sectionReadResult(doc, params.sections, `${origin} ${params.type}`);
3261
+ return sectionReadResult(doc, params.sections, `${origin} ${type}`);
3272
3262
  } catch (error) {
3273
3263
  if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
3274
3264
  product ??= (await client.getProduct(params.productId)).data;
3275
- const timelines = await documentTimelines(client, params.productId, product);
3276
- return toolError("NOT_FOUND", describeMiss(params.type, origin, timelines, product));
3265
+ return toolError("NOT_FOUND", describeMiss(type, origin, await documentTimelines(client, params.productId, product), product));
3277
3266
  }
3278
3267
  }));
3279
3268
  }
@@ -3319,7 +3308,7 @@ function sectionIdFrom(readTool) {
3319
3308
  return z.string().min(1).describe(`Section id -- the keys of \`data.sections\` in ${readTool}, e.g. summary, scope, apiDesign. An id that does not exist yet adds a section.`);
3320
3309
  }
3321
3310
  const sectionContentInput = z.string().describe("The full new Markdown text of the section; it replaces the old text");
3322
- const tenantDocumentType = z.enum(TENANT_DOCUMENT_TYPES).describe(`Document type: a policy (${named$1(POLICY_TYPES)}) or ${named$1(OTHER_TENANT_DOCUMENT_TYPES)}`);
3311
+ const tenantDocumentType = z.enum(TENANT_DOCUMENT_TYPES).describe("Document type: the policy_* ones are policies");
3323
3312
  /** Everything the model needs back from a write, without the content it already holds. */
3324
3313
  function documentSummary(doc) {
3325
3314
  const { data, ...rest } = doc;
@@ -3378,42 +3367,42 @@ async function publishDraft(scope) {
3378
3367
  });
3379
3368
  }
3380
3369
  function registerDocumentTools(server, client) {
3381
- if (client.hasCapability("tenant:documents:read")) {
3382
- server.registerTool("taiga_list_documents", {
3383
- title: "List company documents",
3384
- description: `List the organization's documents as summaries (type, name, description, version, draft state). The ${POLICY_TYPES.length} policies (${named$1(POLICY_TYPES)}) set the security, data privacy, SDLC, operations, architecture and compliance rules for every product; the others (${named$1(OTHER_TENANT_DOCUMENT_TYPES)}) carry goals, standards, voice and standing instructions. taiga_get_document reads one.`,
3385
- inputSchema: z.object({ ...pagination }),
3386
- annotations: READ_ONLY
3387
- }, (params) => runTool(async () => {
3388
- const result = await client.listTenantDocuments(params);
3370
+ if (client.hasCapability("tenant:documents:read")) server.registerTool("taiga_get_document", {
3371
+ title: "Get company document",
3372
+ description: `Read one of the organization's documents by type, or without type list them all (type, name, version, draft state). 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.`,
3373
+ inputSchema: z.object({
3374
+ type: tenantDocumentType.optional(),
3375
+ sections: policySectionsInput,
3376
+ ...pagination
3377
+ }),
3378
+ annotations: READ_ONLY
3379
+ }, (params) => runTool(async () => {
3380
+ if (params.type === void 0) {
3381
+ if (params.sections) return toolError("VALIDATION_ERROR", "Name the document (type) to read its sections.");
3382
+ const result = await client.listTenantDocuments({
3383
+ limit: params.limit,
3384
+ cursor: params.cursor
3385
+ });
3389
3386
  return toolResult(page(summaries(result.data), result.nextCursor));
3390
- }));
3391
- server.registerTool("taiga_get_document", {
3392
- title: "Get company document",
3393
- description: "Read one of the organization's documents (a policy, goals, standards). 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.",
3394
- inputSchema: z.object({
3395
- type: tenantDocumentType,
3396
- sections: policySectionsInput
3397
- }),
3398
- annotations: READ_ONLY
3399
- }, (params) => runTool(async () => {
3400
- try {
3401
- const doc = stripProvenance((await client.getTenantDocument(params.type)).data);
3402
- return params.sections ? sectionReadResult(doc, params.sections, params.type, { controlIds: POLICY_TYPES.includes(params.type) }) : toolResult(doc);
3403
- } catch (error) {
3404
- if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
3405
- const [draft, all] = await Promise.allSettled([client.getTenantDocumentDraft(params.type), client.listTenantDocuments({ limit: 100 })]);
3406
- const parts = [`No published ${params.type}.`];
3407
- if (draft.status === "fulfilled") parts.push(draft.value.data ? "A draft exists; taiga_publish_document publishes it once the person asks." : "There is no draft either.");
3408
- else parts.push(failedLookup("The draft", draft.reason));
3409
- if (all.status === "fulfilled") {
3410
- const published = publishedList(all.value.data);
3411
- parts.push(published ? `Published documents: ${published}.` : "Nothing is published yet.");
3412
- } else parts.push(failedLookup("The published list", all.reason));
3413
- return toolError("NOT_FOUND", parts.join(" "));
3414
- }
3415
- }));
3416
- }
3387
+ }
3388
+ const type = params.type;
3389
+ 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.");
3390
+ try {
3391
+ const doc = stripProvenance((await client.getTenantDocument(type)).data);
3392
+ return params.sections ? sectionReadResult(doc, params.sections, type, { controlIds: POLICY_TYPES.includes(type) }) : toolResult(doc);
3393
+ } catch (error) {
3394
+ if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
3395
+ const [draft, all] = await Promise.allSettled([client.getTenantDocumentDraft(type), client.listTenantDocuments({ limit: 100 })]);
3396
+ const parts = [`No published ${type}.`];
3397
+ if (draft.status === "fulfilled") parts.push(draft.value.data ? "A draft exists; taiga_publish_document publishes it once the person asks." : "There is no draft either.");
3398
+ else parts.push(failedLookup("The draft", draft.reason));
3399
+ if (all.status === "fulfilled") {
3400
+ const published = publishedList(all.value.data);
3401
+ parts.push(published ? `Published documents: ${published}.` : "Nothing is published yet.");
3402
+ } else parts.push(failedLookup("The published list", all.reason));
3403
+ return toolError("NOT_FOUND", parts.join(" "));
3404
+ }
3405
+ }));
3417
3406
  if (client.hasCapability("tenant:documents:read") && client.hasCapability("tenant:documents:write")) {
3418
3407
  const tenantScope = (type) => ({
3419
3408
  getDraft: () => client.getTenantDocumentDraft(type),
@@ -3438,41 +3427,27 @@ function registerDocumentTools(server, client) {
3438
3427
  annotations: PUBLISH
3439
3428
  }, (params) => runTool(() => publishDraft(tenantScope(params.type))));
3440
3429
  }
3441
- if (client.hasCapability("factories:read")) {
3442
- server.registerTool("taiga_list_factory_documents", {
3443
- title: "List factory documents",
3444
- description: `List a factory's documents, published and draft (isDraft), as summaries (type, name, version). The only type is ${named$1(FACTORY_DOCUMENT_TYPES)}: standing guidance (domain context, tech preferences, conventions) for every product in the factory. An empty list means no publication or draft exists. taiga_get_factory_document reads it.`,
3445
- inputSchema: z.object({
3446
- factoryId: idInput("factory", "taiga_list_factories (query finds one by name) gives it"),
3447
- ...pagination
3448
- }),
3449
- annotations: READ_ONLY
3450
- }, (params) => runTool(async () => {
3451
- const result = await client.listFactoryDocuments(params.factoryId, params);
3452
- return toolResult(page(summaries(result.data), result.nextCursor));
3453
- }));
3454
- server.registerTool("taiga_get_factory_document", {
3455
- title: "Get factory instructions",
3456
- description: "Read a factory's instructions: the domain context, tech preferences and conventions its products follow. If nothing is published, it says whether a draft exists and how to write one.",
3457
- inputSchema: z.object({
3458
- factoryId: idInput("factory", "taiga_list_factories (query finds one by name) gives it"),
3459
- type: z.enum(FACTORY_DOCUMENT_TYPES).default("factory_instructions").describe("Document type")
3460
- }),
3461
- annotations: READ_ONLY
3462
- }, (params) => runTool(async () => {
3463
- try {
3464
- return toolResult(stripProvenance((await client.getFactoryDocument(params.factoryId, params.type)).data));
3465
- } catch (error) {
3466
- if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
3467
- await client.getFactory(params.factoryId);
3468
- const detail = await client.getFactoryDocumentDraft(params.factoryId, params.type).then((draft) => draft.data ? "A draft exists; taiga_publish_factory_document publishes it once the person asks." : "There is no draft either: write one with taiga_update_factory_document, then publish it when the person asks.").catch((reason) => {
3469
- if (reason instanceof TaigaApiError && reason.status === 404) throw reason;
3470
- return failedLookup("The draft", reason);
3471
- });
3472
- return toolError("NOT_FOUND", `This factory has no published ${params.type} yet. ${detail}`);
3473
- }
3474
- }));
3475
- }
3430
+ if (client.hasCapability("factories:read")) server.registerTool("taiga_get_factory_document", {
3431
+ title: "Get factory instructions",
3432
+ description: "Read a factory's instructions: the domain context, tech preferences and conventions its products follow. If nothing is published, it says whether a draft exists and how to write one.",
3433
+ inputSchema: z.object({
3434
+ factoryId: idInput("factory", "taiga_list_factories (query finds one by name) gives it"),
3435
+ type: z.enum(FACTORY_DOCUMENT_TYPES).default("factory_instructions").describe("Document type")
3436
+ }),
3437
+ annotations: READ_ONLY
3438
+ }, (params) => runTool(async () => {
3439
+ try {
3440
+ return toolResult(stripProvenance((await client.getFactoryDocument(params.factoryId, params.type)).data));
3441
+ } catch (error) {
3442
+ if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
3443
+ await client.getFactory(params.factoryId);
3444
+ const detail = await client.getFactoryDocumentDraft(params.factoryId, params.type).then((draft) => draft.data ? "A draft exists; taiga_publish_factory_document publishes it once the person asks." : "There is no draft either: write one with taiga_update_factory_document, then publish it when the person asks.").catch((reason) => {
3445
+ if (reason instanceof TaigaApiError && reason.status === 404) throw reason;
3446
+ return failedLookup("The draft", reason);
3447
+ });
3448
+ return toolError("NOT_FOUND", `This factory has no published ${params.type} yet. ${detail}`);
3449
+ }
3450
+ }));
3476
3451
  if (client.hasCapability("factories:read") && client.hasCapability("factories:documents:write")) {
3477
3452
  const factoryScope = (factoryId, type) => ({
3478
3453
  getDraft: () => client.getFactoryDocumentDraft(factoryId, type),
@@ -4840,7 +4815,7 @@ function registerMaintenanceTools(server, client) {
4840
4815
  if (!client.hasCapability("products:read")) return;
4841
4816
  server.registerTool("taiga_list_findings", {
4842
4817
  title: "List maintenance findings",
4843
- 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 (upstream_only: no fixed version), breached controls and initiativeId of a fix under way. Omit status for open findings. taiga_get_finding reads one.",
4818
+ 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.",
4844
4819
  inputSchema: z.object({
4845
4820
  productId,
4846
4821
  status: z.enum(MAINTENANCE_FINDING_STATUSES).optional().describe("Only findings in this status; omit for the ones needing action"),
@@ -4953,7 +4928,7 @@ function registerMaintenanceTools(server, client) {
4953
4928
  ignored: z.boolean().describe("true ignores the finding, false reopens it"),
4954
4929
  reason: z.enum(MAINTENANCE_IGNORE_REASONS).optional().describe("Why it is ignored; required when ignoring"),
4955
4930
  note: z.string().trim().max(280).optional().describe("A short justification, kept for audit"),
4956
- expiresAt: z.string().datetime({ offset: true }).optional().describe("When the ignore lapses and the finding resurfaces if it still exists (ISO 8601, in the future)")
4931
+ expiresAt: z.string().datetime({ offset: true }).meta({ pattern: void 0 }).optional().describe("When the ignore lapses and the finding resurfaces if it still exists (ISO 8601, in the future)")
4957
4932
  }),
4958
4933
  annotations: OPERATE
4959
4934
  }, (params) => runTool(async () => {
@@ -4969,7 +4944,7 @@ function registerMaintenanceTools(server, client) {
4969
4944
  }));
4970
4945
  server.registerTool("taiga_fix_finding", {
4971
4946
  title: "Fix a finding",
4972
- description: "Raise an initiative that fixes a maintenance finding, at the end of the product's line; it ends in a pull request a person merges, and the initiative's review gate applies. wholeGroup: true fixes the group in one initiative: every open advisory on that package version in one bump, or every location the same rule flagged. Refused for a secret only in history, anything in a submodule, and remediableBy upstream_only.",
4947
+ description: "Raise an initiative that fixes a maintenance finding, at the end of the product's line; it ends in a pull request a person merges, and the initiative's review gate applies. wholeGroup: true fixes the group in one initiative: every open advisory on that package version in one bump, or every location the same rule flagged. Refused for a secret only in history, remediableBy upstream_only (code in a submodule), and a dependency with no fixed version yet (fixable: false).",
4973
4948
  inputSchema: z.object({
4974
4949
  productId,
4975
4950
  findingId,
@@ -5046,14 +5021,14 @@ function readWith(row, backlogRead) {
5046
5021
  case "backlog_item": return backlogRead ? "taiga_get_plan (initiativeId: subjectId)" : void 0;
5047
5022
  case "product_document": {
5048
5023
  const type = /^[^:]+:([a-z_]+):decision:/.exec(String(row["subjectId"]))?.[1];
5049
- return type ? `taiga_get_product_document (productId, type: ${type}); the decision's text is this row with full: true` : "taiga_list_product_documents (productId)";
5024
+ return type ? `taiga_get_product_document (productId, type: ${type}); the decision's text is this row with full: true` : "taiga_get_product_document (productId, no type)";
5050
5025
  }
5051
5026
  case "inbox_task": return "taiga_list_setup_tasks (productId)";
5052
5027
  case "product": return {
5053
5028
  review_backlog: backlogRead ? "taiga_list_initiatives (productId)" : void 0,
5054
5029
  review_maintenance: "taiga_list_findings (productId)",
5055
5030
  review_policy: "taiga_get_policy_standing (productId)",
5056
- review_doc: "taiga_list_product_documents (productId)"
5031
+ review_doc: "taiga_get_product_document (productId, no type)"
5057
5032
  }[String(row["kind"])];
5058
5033
  default: return;
5059
5034
  }
@@ -5366,7 +5341,7 @@ function registerInboxTools(server, client) {
5366
5341
  "done",
5367
5342
  "dismissed"
5368
5343
  ]).optional().describe("What became of it"),
5369
- snoozedUntil: z.string().datetime().nullable().optional().describe("Hide it until this time (ISO 8601 in UTC, ending in Z); null wakes it now")
5344
+ snoozedUntil: z.string().datetime().meta({ pattern: void 0 }).nullable().optional().describe("Hide it until this time (ISO 8601 in UTC, ending in Z); null wakes it now")
5370
5345
  }),
5371
5346
  annotations: OPERATE
5372
5347
  }, (params) => runTool(async () => {
@@ -5399,7 +5374,7 @@ const REPOSITORY_SUMMARY_FIELDS = [
5399
5374
  "isManaged"
5400
5375
  ];
5401
5376
  /** How many of the latest runs to carry; taiga_list_runs pages the rest. */
5402
- const BUILDS_IN_CONTEXT = 10;
5377
+ const BUILDS_IN_CONTEXT = 5;
5403
5378
  /** A run as the bundle carries it: enough to say what was built and where; taiga_get_run has the rest. */
5404
5379
  const CONTEXT_BUILD_FIELDS = [
5405
5380
  "id",
@@ -5418,6 +5393,7 @@ const CONTEXT_BUILD_FIELDS = [
5418
5393
  "prDraft",
5419
5394
  "reviewEscalation",
5420
5395
  "error",
5396
+ "hardeningResult",
5421
5397
  "createdAt"
5422
5398
  ];
5423
5399
  /**
@@ -5586,7 +5562,7 @@ async function documentsPart(client, product, depth) {
5586
5562
  }
5587
5563
  } else if (isNotFound$1(read.reason)) {
5588
5564
  out[type] = null;
5589
- notes.push(`No published ${origin} ${type}; taiga_list_product_documents shows what the product has.`);
5565
+ notes.push(`No published ${origin} ${type}; taiga_get_product_document without type shows what the product has.`);
5590
5566
  } else throw read.reason;
5591
5567
  });
5592
5568
  return {
@@ -5669,8 +5645,21 @@ async function repositoriesPart(links) {
5669
5645
  async function buildsPart(client, productId) {
5670
5646
  const rows = (await client.listBuildRuns(productId, { limit: BUILDS_IN_CONTEXT })).data;
5671
5647
  const states = await initiativeStates(client, rows);
5672
- return pickFields(rows, CONTEXT_BUILD_FIELDS).map(readableRow).map((row) => withInitiativeState(row, states));
5648
+ return pickFields(rows, CONTEXT_BUILD_FIELDS).map(readableRow).map((row) => withInitiativeState(row, states)).map((row) => pickFields([row], CONTEXT_RUN_SAID)[0]);
5673
5649
  }
5650
+ /** A run in the bundle: which one, for what, how it ended, and its pull request. taiga_get_run has the rest. */
5651
+ const CONTEXT_RUN_SAID = [
5652
+ "id",
5653
+ "runNumber",
5654
+ "backlogItemId",
5655
+ "targetTitle",
5656
+ "disposition",
5657
+ "outcome",
5658
+ "failedReasonMeans",
5659
+ "hardeningNote",
5660
+ "prUrl",
5661
+ "createdAt"
5662
+ ];
5674
5663
  /**
5675
5664
  * What waits on a person for this product, counted as taiga_list_action_required's summary counts it (PRO-2800):
5676
5665
  * the reviewing agent called four tools to answer "how does it stand", and two later reviews found this count and
@@ -5767,7 +5756,7 @@ function registerContextTools(server, client) {
5767
5756
  if (!client.hasCapability("products:read")) return;
5768
5757
  server.registerTool("taiga_get_product_context", {
5769
5758
  title: "Get product context",
5770
- description: `Read where a product stands in one call; the first read for any product question. Name it by ID, slug or git remote URL. Returns product (in setup, readiness and next move), factory with its instructions, specification and architecture outlines (section ids, sizes), policies with section counts, open initiatives with a count per board column (byColumn; taiga_list_initiatives status: 'all' has the done and canceled), repositories, the latest ten runs, and counts for Action Required, open findings and policy violations. A part that failed is under unavailable, one the key cannot read under notGranted; neither shows as empty. documents: budget returns whole sections up to about ${DOCUMENT_BUDGET / 1e3}k characters each, full returns all.`,
5759
+ description: `Read where a product stands in one call; the first read for any product question. Name it by ID, slug or git remote URL. Returns product (in setup, readiness and next move), factory with its instructions, specification and architecture outlines (section ids, sizes), policies with section counts, open initiatives with a count per board column (byColumn; taiga_list_initiatives status: 'all' has the done and canceled), repositories, the latest five runs (taiga_list_runs has more), and counts for Action Required, open findings and policy violations. A part that failed is under unavailable, one the key cannot read under notGranted; neither shows as empty. documents: budget returns whole sections up to about ${DOCUMENT_BUDGET / 1e3}k characters each, full returns all.`,
5771
5760
  inputSchema: z.object({
5772
5761
  productId: productId.optional(),
5773
5762
  slug: slug.optional(),
@@ -6252,7 +6241,7 @@ function registerObserveTools(server, client) {
6252
6241
  //#endregion
6253
6242
  //#region src/tools/product-documents.ts
6254
6243
  const EDITABLE_TYPES = PRODUCT_DOCUMENT_TYPES.filter((type) => type !== "codebase_snapshot");
6255
- const documentType = z.enum(EDITABLE_TYPES).describe("Document type -- taiga_list_product_documents shows which ones the product has");
6244
+ const documentType = z.enum(EDITABLE_TYPES).describe("Document type -- taiga_get_product_document without type shows which ones the product has");
6256
6245
  /** The documents a person reviews: the discovery chain, not the snapshot or the product's own instructions. */
6257
6246
  const reviewableType = z.enum(REVIEWABLE_PRODUCT_DOCUMENT_TYPES).describe("Document type -- one of the discovery documents a person reviews");
6258
6247
  /** The documents an agent writes from other documents; the same table the worker reads its inputs from. */
@@ -20979,7 +20968,7 @@ function registerPrompts(server, client) {
20979
20968
  client.hasCapability("factories:read") ? client.getFactoryDocument(String(product.factoryId), "factory_instructions") : Promise.reject(new NotGranted("factories:read")),
20980
20969
  client.hasCapability("tenant:documents:read") ? client.listTenantDocuments({ limit: 100 }) : Promise.reject(new NotGranted("tenant:documents:read"))
20981
20970
  ]);
20982
- const architectureText = architecture.status === "fulfilled" ? fence("markdown", renderDocument(stripProvenance(architecture.value.data)).text) : isNotFound(architecture.reason) ? `(No published ${origin} architecture; taiga_list_product_documents shows what exists.)` : unreadable("The architecture", architecture.reason);
20971
+ const architectureText = architecture.status === "fulfilled" ? fence("markdown", renderDocument(stripProvenance(architecture.value.data)).text) : isNotFound(architecture.reason) ? `(No published ${origin} architecture; taiga_get_product_document without type shows what exists.)` : unreadable("The architecture", architecture.reason);
20983
20972
  const instructionsText_ = instructions.status === "fulfilled" ? fence("markdown", instructionsText(sectionsOf(instructions.value.data.data) ?? {}) ?? "(The factory instructions are empty.)") : instructions.reason instanceof NotGranted ? instructions.reason.line("The factory instructions") : isNotFound(instructions.reason) ? "(This factory has not published instructions.)" : unreadable("The factory instructions", instructions.reason);
20984
20973
  const policiesText = policies.status === "fulfilled" ? fence("text", policies.value.data.filter((row) => POLICY_TYPES.includes(String(row.type)) && row.isDraft === false).map((row) => `- ${String(row.type)}: ${Object.keys(sectionsOf(row.data ?? {}) ?? {}).join(", ")}`).join("\n") || "(No policy is published.)") : policies.reason instanceof NotGranted ? policies.reason.line("The policies") : unreadable("The policies", policies.reason);
20985
20974
  return message([
@@ -21070,7 +21059,8 @@ const GITHUB_REPOSITORY_FIELDS = [
21070
21059
  "defaultBranch",
21071
21060
  "description",
21072
21061
  "language",
21073
- "linked"
21062
+ "linked",
21063
+ "linkedProduct"
21074
21064
  ];
21075
21065
  const repositoryId = idInput("repository link", "taiga_list_product_repositories gives it; a linkable repository id is not one");
21076
21066
  /** Round-robin over lists: the first of each, then the second of each, and so on. */
@@ -21086,7 +21076,8 @@ function interleave(lists) {
21086
21076
  */
21087
21077
  async function holderOf(client, error, productId) {
21088
21078
  if (!(error instanceof TaigaApiError) || error.status !== 409) return null;
21089
- const name = /^(\S+\/\S+) is already linked to another product\.?$/.exec(error.apiMessage ?? "")?.[1];
21079
+ if (error.details?.["holder"]) return null;
21080
+ const name = /^(\S+\/\S+) is already linked to another product\./.exec(error.apiMessage ?? "")?.[1];
21090
21081
  if (!name) return null;
21091
21082
  try {
21092
21083
  const holders = (await client.listProducts({
@@ -21148,7 +21139,7 @@ function registerRepositoryTools(server, client) {
21148
21139
  }
21149
21140
  if (client.hasCapability("vcs:read")) server.registerTool("taiga_list_linkable_repositories", {
21150
21141
  title: "Find a repository to link",
21151
- description: "List the repositories the organization's GitHub connection can reach, to link one with taiga_link_repository (it takes the row's id as linkableRepositoryId). query searches by name; account (login, owner, or installationId) reads one account, else rows come from every account in turn. linked: true means a product already links it (taiga_get_product with its URL says which). No connection means GitHub is not connected yet; someone who can install the app does that once, in the web.",
21142
+ description: "List the repositories the organization's GitHub connection can reach, to link one with taiga_link_repository (it takes the row's id as linkableRepositoryId). query searches by name; account (login, owner, or installationId) reads one account, else rows come from every account in turn. linked: true means a product already links it, and linkedProduct names it to a key that sees every product. No connection means GitHub is not connected yet; someone who can install the app does that once, in the web.",
21152
21143
  inputSchema: z.object({
21153
21144
  query: z.string().trim().min(1).max(256).optional().describe("Part of the repository name"),
21154
21145
  account: z.string().trim().min(1).max(140).optional().describe("The GitHub account login (the owner in owner/repo), case-insensitive"),
@@ -21339,7 +21330,7 @@ function registerResources(server, client) {
21339
21330
  return requireOneOf("Document type", value, PRODUCT_DOCUMENT_TYPES);
21340
21331
  };
21341
21332
  const read = async (uri, productId, type, origin) => {
21342
- const result = await served(client.getProductDocument(productId, type, origin), `No published ${origin} ${type} for this product, or the key cannot see it; taiga_list_product_documents shows what exists`);
21333
+ const result = await served(client.getProductDocument(productId, type, origin), `No published ${origin} ${type} for this product, or the key cannot see it; taiga_get_product_document without type shows what exists`);
21343
21334
  return contents(uri.href, {
21344
21335
  name: productDocumentName(type),
21345
21336
  ...result.data
@@ -21356,7 +21347,7 @@ function registerResources(server, client) {
21356
21347
  });
21357
21348
  server.registerResource("product-document", new ResourceTemplate("taiga://products/{productId}/documents/{type}", { list: void 0 }), {
21358
21349
  title: "Product document",
21359
- description: "A product document from the product's own timeline (reality for an imported product, plan otherwise; the documents a person writes are always on the plan side). taiga_list_product_documents shows what exists; add ?origin=plan or ?origin=reality to read the other side. Markdown for a section map, JSON otherwise (the read says which)."
21350
+ description: "A product document from the product's own timeline (reality for an imported product, plan otherwise; the documents a person writes are always on the plan side). taiga_get_product_document without type shows what exists; add ?origin=plan or ?origin=reality to read the other side. Markdown for a section map, JSON otherwise (the read says which)."
21360
21351
  }, async (uri, variables) => {
21361
21352
  const productId = requireId("Product ID", String(variables["productId"]));
21362
21353
  const type = documentType(String(variables["type"]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ai/mcp-server",
3
- "version": "0.24.0-rc.462",
3
+ "version": "0.25.0-dev.465",
4
4
  "description": "MCP server for Taiga -- gives AI assistants access to documents and products",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/client.ts CHANGED
@@ -345,14 +345,6 @@ export class TaigaClient {
345
345
  // Factory Documents
346
346
  // ---------------------------------------------------------------------------
347
347
 
348
- async listFactoryDocuments(factoryId: string, opts?: { limit?: number | undefined; cursor?: string | undefined }) {
349
- const params = new URLSearchParams();
350
- if (opts?.limit) params.set('limit', String(opts.limit));
351
- if (opts?.cursor) params.set('cursor', opts.cursor);
352
- const qs = params.toString();
353
- return this.request<Page<FactoryDocument>>('GET', `/factories/${seg(factoryId)}/documents${qs ? `?${qs}` : ''}`);
354
- }
355
-
356
348
  async getFactoryDocument(factoryId: string, type: string) {
357
349
  return this.request<One<FactoryDocument>>('GET', `/factories/${seg(factoryId)}/documents/${seg(type)}`);
358
350
  }