@taiga-ai/mcp-server 0.18.0-dev.447 → 0.18.0-dev.448

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
@@ -2169,8 +2169,9 @@ const DISCOVERY_STEP_MEANING = "discoveryStep is where a product in setup is: co
2169
2169
  * hears only that guesses another ID, or a name. Each ID a tool takes names
2170
2170
  * the tool that lists the right ones; the tool's arguments say which apply.
2171
2171
  */
2172
+ const ITEM_ID_HINT = "taiga_list_backlog_items lists a product's initiatives with their IDs; a run or plan ID is not one.";
2172
2173
  const BY_ARGUMENT = {
2173
- itemId: "taiga_list_backlog_items lists a product's initiatives with their IDs; a run or plan ID is not one.",
2174
+ itemId: ITEM_ID_HINT,
2174
2175
  backlogItemId: "taiga_list_backlog_items lists a product's initiatives with their IDs.",
2175
2176
  buildId: "taiga_list_builds lists the runs, of one product or of every product the key can see.",
2176
2177
  planId: "taiga_get_plan gives the initiative's active plan and its ID.",
@@ -2189,7 +2190,7 @@ const BY_ARGUMENT = {
2189
2190
  /** Where an argument means something else on one tool, or the tool's 404 needs its own word. */
2190
2191
  const BY_TOOL = {
2191
2192
  taiga_resolve_action: () => "taiga_list_action_required lists the items with their IDs.",
2192
- taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_backlog_item says whether one is being generated (planningStatus planning) or failed, and taiga_plan_again asks for a new one." : BY_ARGUMENT.itemId
2193
+ taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_backlog_item says whether one is being generated (planningStatus planning) or failed, and taiga_plan_again asks for a new one." : ITEM_ID_HINT
2193
2194
  };
2194
2195
  /** The hint for a 404 from one tool call: the tool's own, else one per ID it was given. */
2195
2196
  function notFoundHint(tool, args) {
@@ -2366,6 +2367,10 @@ function webOrigin(url) {
2366
2367
  function isRecord(value) {
2367
2368
  return typeof value === "object" && value !== null && !Array.isArray(value);
2368
2369
  }
2370
+ /** A section stored as an object rather than a bare string. */
2371
+ function isSectionObject(value) {
2372
+ return isRecord(value);
2373
+ }
2369
2374
  /** The `sections` map of a document, or null when the document is not section-shaped. */
2370
2375
  function sectionsOf(data) {
2371
2376
  return isRecord(data.sections) ? data.sections : null;
@@ -2375,7 +2380,7 @@ function sectionContent(sections, sectionId) {
2375
2380
  const section = sections[sectionId];
2376
2381
  if (section === void 0 || section === null) return null;
2377
2382
  if (typeof section === "string") return section;
2378
- return isRecord(section) && typeof section.content === "string" ? section.content : "";
2383
+ return isSectionObject(section) && typeof section.content === "string" ? section.content : "";
2379
2384
  }
2380
2385
  /** The map with one section's text replaced (or the section added); every other section untouched. */
2381
2386
  function applySectionEdit(sections, sectionId, content) {
@@ -2397,8 +2402,9 @@ function applySectionEdit(sections, sectionId, content) {
2397
2402
  */
2398
2403
  function stripProvenance(doc) {
2399
2404
  const { derivedFrom: _derivedFrom, outdated: _outdated, ...rest } = doc;
2400
- if (!isRecord(rest.data)) return rest;
2401
- const sections = sectionsOf(rest.data);
2405
+ const { data } = doc;
2406
+ if (!isRecord(data)) return rest;
2407
+ const sections = sectionsOf(data);
2402
2408
  if (!sections) return rest;
2403
2409
  const stripped = Object.fromEntries(Object.entries(sections).map(([id, section]) => {
2404
2410
  if (!isRecord(section)) return [id, section];
@@ -2408,7 +2414,7 @@ function stripProvenance(doc) {
2408
2414
  return {
2409
2415
  ...rest,
2410
2416
  data: {
2411
- ...rest.data,
2417
+ ...data,
2412
2418
  sections: stripped
2413
2419
  }
2414
2420
  };
@@ -2421,7 +2427,7 @@ function stripProvenance(doc) {
2421
2427
  function instructionsText(sections) {
2422
2428
  const parts = [];
2423
2429
  for (const section of Object.values(sections)) {
2424
- const content = isRecord(section) ? section.content : void 0;
2430
+ const content = isSectionObject(section) ? section.content : void 0;
2425
2431
  if (typeof content === "string" && content.trim()) parts.push(content.trim());
2426
2432
  }
2427
2433
  return parts.join("\n\n").trim() || null;
@@ -2650,9 +2656,8 @@ async function linkedProducts(client, repository, repositoryHost) {
2650
2656
  }
2651
2657
  /** A document list row as the model sees it: the summary fields plus a display name and the staleness fact. */
2652
2658
  function documentSummaries(rows) {
2653
- const typed = rows;
2654
- return pickFields(typed, DOCUMENT_SUMMARY_FIELDS$1).map((picked, index) => {
2655
- const outdated = typed[index].outdated;
2659
+ return pickFields(rows, DOCUMENT_SUMMARY_FIELDS$1).map((picked, index) => {
2660
+ const outdated = rows[index].outdated;
2656
2661
  const fact = picked.isDraft !== false ? void 0 : outdated === null ? false : Array.isArray(outdated?.sources) ? outdated.sources.length > 0 : void 0;
2657
2662
  return {
2658
2663
  type: picked.type,
@@ -3279,7 +3284,7 @@ async function queueView(client, product, options = { name: "all" }) {
3279
3284
  const canName = client.hasCapability("backlog:read");
3280
3285
  const blocking = blockingIndex(entries, (entry) => entry.state);
3281
3286
  const toName = !canName ? [] : options.name === "all" ? entries.slice(0, NAMED_ENTRIES).map((_, index) => index) : blocking >= 0 ? [blocking] : [];
3282
- const settled = await Promise.allSettled(toName.map((index) => client.getBacklogItem(String(entries[index].backlogItemId))));
3287
+ const settled = await Promise.allSettled(toName.map((index) => client.getBacklogItem(entries[index].backlogItemId)));
3283
3288
  const reads = new Map(toName.map((index, at) => [index, settled[at]]));
3284
3289
  let unnamed = options.name === "all" ? entries.length - toName.length : 0;
3285
3290
  const rows = entries.map((entry, index) => {
@@ -3475,13 +3480,10 @@ const PLAN_STEP_SUMMARY_FIELDS = [
3475
3480
  "createsResources"
3476
3481
  ];
3477
3482
  /** The outcome fields of an initiative, with how many of its criteria are met. */
3478
- const outcomeOf = (item) => {
3479
- const row = item;
3480
- return {
3481
- ...pickFields([row], ITEM_OUTCOME_FIELDS)[0],
3482
- criteria: criteriaCounts(row)
3483
- };
3484
- };
3483
+ const outcomeOf = (item) => ({
3484
+ ...pickFields([item], ITEM_OUTCOME_FIELDS)[0],
3485
+ criteria: criteriaCounts(item)
3486
+ });
3485
3487
  const outcome = (item) => toolResult(outcomeOf(item));
3486
3488
  /**
3487
3489
  * Each tool is registered under the capability its route requires, so a
@@ -3566,7 +3568,8 @@ function registerBacklogReads(server, client) {
3566
3568
  }, (params) => runTool(async () => {
3567
3569
  const item = (await client.getBacklogItem(params.itemId)).data;
3568
3570
  if (item.planningStatus !== "failed") return toolResult(item);
3569
- const planFailure = await planFailureOf(client, params.itemId, item.planFailureCause);
3571
+ const ownCause = item.planFailureCause;
3572
+ const planFailure = await planFailureOf(client, params.itemId, ownCause);
3570
3573
  return toolResult(planFailure ? {
3571
3574
  ...item,
3572
3575
  planFailure
@@ -3659,7 +3662,7 @@ const isApiFailure$2 = (error) => error instanceof TaigaApiError || isNetworkFai
3659
3662
  async function reviewGate(client, item) {
3660
3663
  const own = typeof item.autoBuild === "boolean" ? item.autoBuild : null;
3661
3664
  try {
3662
- const product = own === null ? (await client.getProduct(String(item.productId))).data : {};
3665
+ const product = own === null ? (await client.getProduct(item.productId)).data : {};
3663
3666
  const builds = resolveAutoBuild({
3664
3667
  initiative: own,
3665
3668
  product: typeof product.autoBuild === "boolean" ? product.autoBuild : true
@@ -3677,7 +3680,7 @@ async function reviewGate(client, item) {
3677
3680
  /** Where a queued initiative stands in its product's line; null, and the outcome says so, when the queue cannot be read. */
3678
3681
  async function placeInQueue(client, item) {
3679
3682
  try {
3680
- const view = await queueView(client, String(item.productId), { name: "blocking" });
3683
+ const view = await queueView(client, item.productId, { name: "blocking" });
3681
3684
  return {
3682
3685
  position: view.entries.find((row) => row.backlogItemId === item.id)?.position ?? null,
3683
3686
  of: view.entries.length,
@@ -3853,7 +3856,7 @@ async function activityRows(client, opts) {
3853
3856
  throw error;
3854
3857
  })]);
3855
3858
  const nameById = /* @__PURE__ */ new Map();
3856
- for (const product of products?.data ?? []) nameById.set(String(product.id), {
3859
+ for (const product of products?.data ?? []) nameById.set(product.id, {
3857
3860
  name: product.name,
3858
3861
  slug: product.slug
3859
3862
  });
@@ -4081,7 +4084,7 @@ async function factoryPart(client, factoryId) {
4081
4084
  * asked. Null, with a note, where unpublished.
4082
4085
  */
4083
4086
  async function documentsPart(client, product, depth) {
4084
- const productId = String(product.id);
4087
+ const productId = product.id;
4085
4088
  const origin = defaultOriginFor(product);
4086
4089
  const reads = await Promise.allSettled(CONTEXT_DOCUMENT_TYPES.map((type) => client.getProductDocument(productId, type, origin)));
4087
4090
  const out = { origin };
@@ -4206,7 +4209,7 @@ async function buildsPart(client, productId) {
4206
4209
  return pickFields(rows, CONTEXT_BUILD_FIELDS);
4207
4210
  }
4208
4211
  async function productContext(client, product, options) {
4209
- const productId = String(product.id);
4212
+ const productId = product.id;
4210
4213
  const granted = (part) => client.hasCapability(PARTS[part]);
4211
4214
  const notGranted = Object.keys(PARTS).filter((part) => !granted(part));
4212
4215
  const fetchers = {
@@ -4276,6 +4279,14 @@ function registerContextTools(server, client) {
4276
4279
  }
4277
4280
  //#endregion
4278
4281
  //#region src/tools/autonomy.ts
4282
+ /**
4283
+ * Autonomy (docs.tai.ga, Deliver: Initiatives, Auto-merge): whether a landed
4284
+ * plan is built without a review, and whether a pull request merges on its
4285
+ * own. The organization and its factories are ceilings for merging, set by
4286
+ * an administrator in the web; a product sets defaults; an initiative's own
4287
+ * switch is the final word within the ceilings. The initiative's build gate
4288
+ * is taiga_set_auto_build.
4289
+ */
4279
4290
  function registerAutonomyTools(server, client) {
4280
4291
  if (client.hasCapability("products:write")) server.registerTool("taiga_set_product_autonomy", {
4281
4292
  title: "Set a product's autonomy defaults",
@@ -4322,6 +4333,13 @@ function registerAutonomyTools(server, client) {
4322
4333
  }
4323
4334
  //#endregion
4324
4335
  //#region src/tools/inbox.ts
4336
+ /**
4337
+ * Action Required (docs.tai.ga, Deliver): where work comes back to a person.
4338
+ * Things to review (a plan, a pull request, findings, policy violations) and
4339
+ * things to set up that only a person can (a secret, an account, a cloud
4340
+ * permission). The inbox is the team's: resolving an item clears it for
4341
+ * everybody, so these tools say what they clear.
4342
+ */
4325
4343
  const ITEM_FIELDS = [
4326
4344
  "id",
4327
4345
  "productId",
@@ -4433,6 +4451,14 @@ function registerInboxTools(server, client) {
4433
4451
  }
4434
4452
  //#endregion
4435
4453
  //#region src/tools/knowledge.ts
4454
+ /**
4455
+ * Knowledge (docs.tai.ga, Context): reference material agents search when
4456
+ * the work calls for it, at the organization, a factory or a product, and
4457
+ * narrowing as it comes down. Not instructions: a rule belongs in
4458
+ * instructions, a forty-page integration guide here. Members manage it on
4459
+ * their factories and products; organization knowledge takes an owner or
4460
+ * admin, which the API enforces.
4461
+ */
4436
4462
  const KNOWLEDGE_FIELDS = [
4437
4463
  "id",
4438
4464
  "factoryId",
@@ -4608,6 +4634,13 @@ function registerKnowledgeTools(server, client) {
4608
4634
  }
4609
4635
  //#endregion
4610
4636
  //#region src/tools/maintenance.ts
4637
+ /**
4638
+ * Maintaining (docs.tai.ga, Operate): what the sweeps found in a product's
4639
+ * repository and where it stands against the organization's own policies,
4640
+ * and the two things a person does with a finding, Ignore and Fix. Sweeps run
4641
+ * themselves and nothing is marked fixed by hand: a finding resolves when the
4642
+ * next sweep no longer detects it, so there is no tool for either.
4643
+ */
4611
4644
  /** A finding as a list row: enough to decide whether to open, fix or ignore it. */
4612
4645
  const FINDING_ROW_FIELDS = [
4613
4646
  "id",
@@ -4814,6 +4847,13 @@ function registerMaintenanceTools(server, client) {
4814
4847
  }
4815
4848
  //#endregion
4816
4849
  //#region src/tools/observe.ts
4850
+ /**
4851
+ * What runs where and how it is doing (docs.tai.ga, Deliver: Deployments;
4852
+ * Operate: Monitoring). Taiga observes deployments, it does not perform them:
4853
+ * the history is mirrored from the provider, so these tools read and never
4854
+ * deploy, approve or cancel. Importing history reaches into the connected
4855
+ * provider and is an administrator's action in the web.
4856
+ */
4817
4857
  /** A deployment as a line of history: what shipped where, how it ended, and who shipped it. */
4818
4858
  const DEPLOYMENT_FIELDS = [
4819
4859
  "id",
@@ -4849,7 +4889,7 @@ const ENVIRONMENT_FIELDS = [
4849
4889
  */
4850
4890
  function deploymentLines(rows) {
4851
4891
  return rows.map((row) => {
4852
- const repository = row.metadata?.repositoryFullName;
4892
+ const repository = row.metadata?.["repositoryFullName"];
4853
4893
  return {
4854
4894
  ...pickFields([row], DEPLOYMENT_FIELDS)[0],
4855
4895
  repository: typeof repository === "string" ? repository : null
@@ -5045,7 +5085,7 @@ function registerProductDocumentWrites(server, client) {
5045
5085
  const canRead = client.hasCapability("products:read");
5046
5086
  const productScope = (product, type) => ({
5047
5087
  getDraft: () => client.getProductDocumentDraft(product, type),
5048
- getPublished: async () => await client.getProductDocument(product, type, "plan"),
5088
+ getPublished: () => client.getProductDocument(product, type, "plan"),
5049
5089
  saveDraft: (data) => client.saveProductDocumentDraft(product, type, { data }),
5050
5090
  publish: () => client.publishProductDocument(product, type)
5051
5091
  });
@@ -19554,7 +19594,7 @@ function renderDocument(doc) {
19554
19594
  ].filter((key) => doc[key] !== void 0 && doc[key] !== null).map((key) => `${key}: ${String(doc[key])}`);
19555
19595
  const core = Object.keys(sections).filter((id) => {
19556
19596
  const section = sections[id];
19557
- return isRecord(section) && section.priority === "core";
19597
+ return isSectionObject(section) && section.priority === "core";
19558
19598
  });
19559
19599
  if (core.length > 0) facts.push(`core sections: ${core.join(", ")}`);
19560
19600
  if (facts.length > 0) lines.push("", facts.join(" | "));
@@ -19650,7 +19690,7 @@ function atxOf(depth, children) {
19650
19690
  }
19651
19691
  /** The section's title when it carries one, else its id. */
19652
19692
  function sectionTitle(section, id) {
19653
- return isRecord(section) && typeof section.title === "string" && section.title.trim() ? section.title.trim() : id;
19693
+ return isSectionObject(section) && typeof section.title === "string" && section.title.trim() ? section.title.trim() : id;
19654
19694
  }
19655
19695
  /** The mime type a read will answer with, for a list entry that must not disagree with it. */
19656
19696
  function mimeTypeOf(doc) {
@@ -19817,6 +19857,14 @@ var NotGranted = class extends Error {
19817
19857
  };
19818
19858
  //#endregion
19819
19859
  //#region src/tools/repositories.ts
19860
+ /**
19861
+ * A product's repository (docs.tai.ga, Administration: Integrations and
19862
+ * environments, Source control). A linked repository is the prerequisite for
19863
+ * most of what Taiga does: planning reads the code, runs work on a branch of
19864
+ * it, sweeps scan it and the as-built documents are derived from it. GitHub is
19865
+ * connected once for the organization, in the web; linking one of the
19866
+ * repositories it can reach to a product is a separate act, per product.
19867
+ */
19820
19868
  const LINK_FIELDS = [
19821
19869
  "id",
19822
19870
  "provider",
@@ -20038,7 +20086,7 @@ function registerResources(server, client) {
20038
20086
  title: "Organization document",
20039
20087
  description: "A published organization document: a policy, the business goals, the design or brand standards, the tone of voice or the standing instructions. Sections rendered as headings."
20040
20088
  }, async (uri, variables) => {
20041
- const type = requireOneOf("Document type", String(variables.type), TENANT_DOCUMENT_TYPES);
20089
+ const type = requireOneOf("Document type", String(variables["type"]), TENANT_DOCUMENT_TYPES);
20042
20090
  try {
20043
20091
  return contents(uri.href, (await client.getTenantDocument(type)).data);
20044
20092
  } catch (error) {
@@ -20055,7 +20103,7 @@ function registerResources(server, client) {
20055
20103
  title: "Factory instructions",
20056
20104
  description: "A factory's published standing guidance; taiga_list_factories gives the factory ID. Markdown for a section map, JSON otherwise (the read says which)."
20057
20105
  }, async (uri, variables) => {
20058
- const factoryId = requireId("Factory ID", String(variables.factoryId));
20106
+ const factoryId = requireId("Factory ID", String(variables["factoryId"]));
20059
20107
  const doc = await served(client.getFactoryDocument(factoryId, FACTORY_DOCUMENT_TYPES[0]), "This factory has not published instructions, or the key cannot see the factory");
20060
20108
  return contents(uri.href, doc.data);
20061
20109
  });
@@ -20075,17 +20123,17 @@ function registerResources(server, client) {
20075
20123
  title: "Product document on a timeline",
20076
20124
  description: "A product document from the named timeline: origin=plan (discovery) or origin=reality (derived from the code). Markdown for a section map, JSON otherwise (the read says which)."
20077
20125
  }, async (uri, variables) => {
20078
- const productId = requireId("Product ID", String(variables.productId));
20079
- const type = documentType(String(variables.type));
20080
- const origin = requireOneOf("Origin", String(variables.origin), PRODUCT_DOCUMENT_ORIGINS);
20126
+ const productId = requireId("Product ID", String(variables["productId"]));
20127
+ const type = documentType(String(variables["type"]));
20128
+ const origin = requireOneOf("Origin", String(variables["origin"]), PRODUCT_DOCUMENT_ORIGINS);
20081
20129
  return read(uri, productId, type, origin);
20082
20130
  });
20083
20131
  server.registerResource("product-document", new ResourceTemplate("taiga://products/{productId}/documents/{type}", { list: void 0 }), {
20084
20132
  title: "Product document",
20085
20133
  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)."
20086
20134
  }, async (uri, variables) => {
20087
- const productId = requireId("Product ID", String(variables.productId));
20088
- const type = documentType(String(variables.type));
20135
+ const productId = requireId("Product ID", String(variables["productId"]));
20136
+ const type = documentType(String(variables["type"]));
20089
20137
  const product = (await served(client.getProduct(productId), "No product with that ID the key can see; taiga_list_products shows them")).data;
20090
20138
  return read(uri, productId, type, defaultOriginFor(product, type));
20091
20139
  });
@@ -20095,7 +20143,7 @@ function registerResources(server, client) {
20095
20143
  description: "An initiative in full: description, acceptance criteria, technical context. taiga_list_backlog_items gives the item ID.",
20096
20144
  mimeType: "application/json"
20097
20145
  }, async (uri, variables) => {
20098
- const itemId = requireId("Backlog item ID", String(variables.itemId));
20146
+ const itemId = requireId("Backlog item ID", String(variables["itemId"]));
20099
20147
  const item = (await served(client.getBacklogItem(itemId), "No backlog item with that ID the key can see; taiga_list_backlog_items shows what exists")).data;
20100
20148
  return { contents: [{
20101
20149
  uri: uri.href,
@@ -20106,6 +20154,14 @@ function registerResources(server, client) {
20106
20154
  }
20107
20155
  //#endregion
20108
20156
  //#region src/tools/setup.ts
20157
+ /**
20158
+ * Setting a product up (docs.tai.ga, Start: Your first product, Learn: Import
20159
+ * an existing codebase, Organizations, factories and products): factories to
20160
+ * put products in, a product started from scratch or from an imported
20161
+ * codebase, what Taiga builds for it, the import, and finishing or reopening
20162
+ * Discovery. Who may reach a factory (its members) and deleting a factory or
20163
+ * a product stay in the web.
20164
+ */
20109
20165
  const PRODUCT_FIELDS = [
20110
20166
  "id",
20111
20167
  "name",
@@ -20290,7 +20346,7 @@ function registerSetupTools(server, client) {
20290
20346
  try {
20291
20347
  return toolResult(await productRow(client, (await client.completeSetup(params.productId)).data));
20292
20348
  } catch (error) {
20293
- if (!(error instanceof TaigaApiError) || error.details?.reason !== "documents_incomplete") throw error;
20349
+ if (!(error instanceof TaigaApiError) || error.details?.["reason"] !== "documents_incomplete") throw error;
20294
20350
  return toolError(error.code ?? "CONFLICT", `${withStop(error.apiMessage ?? "Required documents are not published yet")} ${MISSING_DOCUMENTS_HOW}`, error.details);
20295
20351
  }
20296
20352
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ai/mcp-server",
3
- "version": "0.18.0-dev.447",
3
+ "version": "0.18.0-dev.448",
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",
@@ -154,9 +154,9 @@ describe('TaigaClient', () => {
154
154
  it('drops tenantId at every depth and keeps an own __proto__ key as data', () => {
155
155
  const parsed = JSON.parse(
156
156
  '{"__proto__": {"x": 1}, "tenantId": "t", "a": {"tenantId": "t", "b": [{"tenantId": "t", "c": 1}, 2, null]}, "d": "s"}'
157
- ) as Record<string, unknown>;
157
+ ) as { tenantId: string; a: unknown; d: string };
158
158
 
159
- const out = withoutTenant(parsed) as Record<string, unknown>;
159
+ const out = withoutTenant(parsed);
160
160
 
161
161
  expect(JSON.stringify(out)).not.toContain('tenantId');
162
162
  expect(out.a).toEqual({ b: [{ c: 1 }, 2, null] });