@taiga-ai/mcp-server 0.2.3-dev.255 → 0.2.3-dev.256

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 (42) hide show
  1. package/dist/index.js +718 -62
  2. package/package.json +12 -10
  3. package/scripts/build.mjs +37 -0
  4. package/scripts/prepare-publish.cjs +15 -7
  5. package/src/client.ts +13 -5
  6. package/src/index.ts +19 -7
  7. package/src/server.integration.test.ts +100 -46
  8. package/src/tools/documents.ts +1 -1
  9. package/src/tools/index.ts +5 -1
  10. package/src/tools/projects.ts +6 -2
  11. package/taiga-ai-mcp-server-0.2.3-dev.256.tgz +0 -0
  12. package/tsconfig.json +2 -1
  13. package/dist/client.d.ts +0 -109
  14. package/dist/client.d.ts.map +0 -1
  15. package/dist/client.js +0 -228
  16. package/dist/client.test.d.ts +0 -2
  17. package/dist/client.test.d.ts.map +0 -1
  18. package/dist/client.test.js +0 -203
  19. package/dist/index.d.ts +0 -3
  20. package/dist/index.d.ts.map +0 -1
  21. package/dist/server.integration.test.d.ts +0 -2
  22. package/dist/server.integration.test.d.ts.map +0 -1
  23. package/dist/server.integration.test.js +0 -358
  24. package/dist/tools/backlogs.d.ts +0 -4
  25. package/dist/tools/backlogs.d.ts.map +0 -1
  26. package/dist/tools/backlogs.js +0 -6
  27. package/dist/tools/documents.d.ts +0 -4
  28. package/dist/tools/documents.d.ts.map +0 -1
  29. package/dist/tools/documents.js +0 -97
  30. package/dist/tools/index.d.ts +0 -12
  31. package/dist/tools/index.d.ts.map +0 -1
  32. package/dist/tools/index.js +0 -18
  33. package/dist/tools/projects.d.ts +0 -4
  34. package/dist/tools/projects.d.ts.map +0 -1
  35. package/dist/tools/projects.js +0 -64
  36. package/dist/tools/skills.d.ts +0 -4
  37. package/dist/tools/skills.d.ts.map +0 -1
  38. package/dist/tools/skills.js +0 -45
  39. package/dist/tools/summarize.d.ts +0 -15
  40. package/dist/tools/summarize.d.ts.map +0 -1
  41. package/dist/tools/summarize.js +0 -26
  42. package/taiga-ai-mcp-server-0.2.3-dev.255.tgz +0 -0
package/dist/index.js CHANGED
@@ -1,67 +1,723 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { TaigaClient } from './client.js';
5
- import { registerAllTools } from './tools/index.js';
6
- const DEFAULT_API_URL = 'https://api.taiga.app';
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ //#region ../../packages/feature-flags/dist/index.js
6
+ /**
7
+ * Feature flags shared across web, api, mcp-server, and the documentation site.
8
+ *
9
+ * {@link featureFlagDefinitions} is the single source of truth for WHICH flags
10
+ * exist, HOW each one resolves per environment, and what it is for; the
11
+ * {@link FeatureFlags} type is derived from it. Every app calls
12
+ * {@link resolveFeatureFlags} with its own environment identifier so a feature
13
+ * is shown or hidden consistently everywhere from one rule set:
14
+ * - web: `resolveFeatureFlags(config.VITE_APP_ENV)` (baked at build time)
15
+ * - api: a fail-safe wrapper (`apps/api/src/shared/config/feature-flags.ts`)
16
+ * that resolves `ENVIRONMENT` and defaults to `prod` for any unrecognized
17
+ * value when deployed, so a WIP feature is never leaked
18
+ * - docs: `apps/docs/src/content.config.ts` resolves flags for the
19
+ * environment being built and DROPS pages carrying `flag: <name>` where
20
+ * that flag is off, so docs.tai.ga never describes a feature customers
21
+ * cannot see (PRO-1033)
22
+ *
23
+ * Flags are boot-time constants — no remote source. Changing a rule here
24
+ * re-derives the flag everywhere.
25
+ *
26
+ * ## Adding a flag
27
+ *
28
+ * One entry in {@link featureFlagDefinitions}: its `availability`, a one-line
29
+ * `description` for the web dev panel, and the full account of what it gates
30
+ * and why as the JSDoc above the entry. If the flag is OFF in prod, two more
31
+ * things follow, and the first will stop you:
32
+ *
33
+ * 1. **`make docs-check-claims` fails until the flag is registered** in
34
+ * `FLAG_PHRASES` (`apps/docs/scripts/check-claims.py`). That is deliberate:
35
+ * the check used to iterate its own dict, so an unregistered flag was never
36
+ * examined and a page could describe an invisible feature. Register the
37
+ * words a page would use for the feature, or `[]` if it has a page of its
38
+ * own that declares the flag.
39
+ * 2. **A page for the feature can be written now**, carrying `flag: <name>` in
40
+ * its frontmatter. It is published on dev and staging and excluded from
41
+ * prod, and it publishes by itself when the flag graduates — no docs change.
42
+ * See the `flag:` section in `apps/docs/AGENTS.md`, which also covers the
43
+ * one constraint: nothing published may link to a flagged page.
44
+ */
45
+ /**
46
+ * THE registry: one entry per flag. Adding a flag is adding an entry here;
47
+ * {@link FeatureFlags} and {@link resolveFeatureFlags} derive from it.
48
+ */
49
+ const featureFlagDefinitions = Object.freeze({
50
+ /**
51
+ * Skills feature. When `false` the feature is fully dormant:
52
+ * - web: nav groups, dashboard governance row, and skills routes are hidden
53
+ * - api: the `/v1/agent-skills*` endpoints are not registered
54
+ * - mcp: the skill tools, their instruction mentions, and the
55
+ * `getProjectContext` skills summary are omitted
56
+ * - agent runtime: no skills are injected into agent prompts
57
+ * - seeding: no default skills are created for new tenants/teams/projects
58
+ *
59
+ * Hidden for now (not needed in the product yet; may return later).
60
+ */
61
+ skills: {
62
+ description: "Agent skills: the skills routes and pages, the MCP skill tools, and skills injected into agent prompts. Hidden in every environment for now.",
63
+ availability: "nowhere"
64
+ },
65
+ /**
66
+ * Insights surface (Learn loop) — production feedback and customer signals
67
+ * feeding the next iteration.
68
+ *
69
+ * Release toggle: ON in local/dev/staging, OFF in prod. Surfaces the
70
+ * scaffold page in lower environments only while the feature is designed.
71
+ * **Remove this flag (and its gates) at GA.**
72
+ */
73
+ insights: {
74
+ description: "Insights section of the Learn loop: production feedback and customer signals feeding the next iteration.",
75
+ availability: "non-prod"
76
+ },
77
+ /**
78
+ * UI Sparring (PRO-630) — AI-generated UI concepts for the CUSTOMER's app,
79
+ * rendered client-side in a sandboxed iframe styled with the tenant's
80
+ * design_standards tokens. When `false` the feature is fully dormant:
81
+ * - web: the concept gallery, the Studio and the `/dev/ui-artifact-test`
82
+ * route are not registered
83
+ * - api: the `/v1/projects/:projectId/ui-artifacts/*` endpoints are not
84
+ * registered
85
+ *
86
+ * The flow surface moved out from under this flag in PRO-1029 — see
87
+ * {@link FeatureFlags.flowStudio}.
88
+ *
89
+ * Release toggle: ON in local/dev/staging, OFF in prod while the feature is
90
+ * built out (foundation slice → workshop → binding).
91
+ * **Remove this flag (and its gates) at GA.**
92
+ */
93
+ uiSparring: {
94
+ description: "UI Sparring: AI-generated UI concepts for the customer's application, the concept gallery and the UI Studio, with their API endpoints.",
95
+ availability: "non-prod"
96
+ },
97
+ /**
98
+ * User Flow Studio (PRO-1029) — the conversational surface for the
99
+ * `user_flow` document: talk the flows into shape, the ux-planning agent
100
+ * drafts and revises them, every agreed change lands in the document.
101
+ *
102
+ * Split out of {@link FeatureFlags.uiSparring} because working out user flows
103
+ * is Discovery work that stands on its own: it happens before anyone asks
104
+ * what a screen looks like, and it is useful to a customer who never opens
105
+ * the UI Studio. Sharing one flag meant flows could not ship without shipping
106
+ * concept generation and the sparring chat with them.
107
+ *
108
+ * Gates the web surface only — the route and the Edit control on the user
109
+ * flows document. The streaming endpoint (`POST
110
+ * /v1/conversations/:id/workshop-stream`) is deliberately NOT gated: it
111
+ * carries a conversation, the same as the spec-chat route beside it, and
112
+ * hiding a page is not a reason to make an authenticated, capability-checked
113
+ * endpoint conditional.
114
+ *
115
+ * Release toggle: ON in local/dev/staging, OFF in prod until the surface has
116
+ * proven itself there. **Remove this flag (and its gates) at GA.**
117
+ */
118
+ flowStudio: {
119
+ description: "User Flow Studio: the conversational surface where the ux-planning agent drafts and revises the user flows document.",
120
+ availability: "non-prod"
121
+ },
122
+ /**
123
+ * Customer-definable check-and-stamp checklists (PRO-946..948, design
124
+ * PRO-933). When `false` the feature is fully dormant:
125
+ * - api: the `/v1/checklist-*` endpoints are not registered
126
+ * - api: no checklist instances are created on project creation (PR 2/3)
127
+ * - web: no discovery-pipeline checklist step, no org-settings editor
128
+ *
129
+ * Release toggle: ON in local/dev/staging, OFF in prod while the three
130
+ * slices (schema/API → gate + project surface → template editor) land.
131
+ * **Remove this flag (and its gates) at GA.**
132
+ */
133
+ checklists: {
134
+ description: "Customer-defined check-and-stamp checklists: the discovery pipeline step, the organization settings editor and their API endpoints.",
135
+ availability: "non-prod"
136
+ },
137
+ /**
138
+ * Factory v2 engineer pod (PRO-1313..1315, PRO-1331). Two roles:
139
+ * - the admin-only `/v1/engineer/*` endpoints that trigger the EC2-backed
140
+ * Runtime Instances probe runtime (when `false` the routes are not
141
+ * registered, so nothing in the product can reach that runtime);
142
+ * - the environment kill-switch for agent-runtime routing (PRO-1331): with
143
+ * it on, a tenant holding a live `engineer_runtime` entitlement has its
144
+ * initiative planning jobs and build runs routed to the engineer runtime
145
+ * (fail-closed until the run contract lands, PRO-1333); with it off,
146
+ * every tenant stays on the legacy agents whatever the entitlement rows
147
+ * say.
148
+ * OFF in prod until the real engineer pod ships behind its own, separate
149
+ * rollout decision.
150
+ */
151
+ engineerPod: {
152
+ description: "Factory v2 engineer pod: the admin-only /v1/engineer probe endpoints, and the kill-switch for routing engineer_runtime-entitled tenants to the engineer runtime (dev/staging only).",
153
+ availability: "non-prod"
154
+ },
155
+ /**
156
+ * Questionnaire filling (PRO-932) — a customer uploads their own requirement
157
+ * spreadsheet, Taiga answers what the project's evidence supports with
158
+ * `document@version` citations, a human attests, and the answers are written
159
+ * back into that same workbook. ASTORI's THL Liite 4 is the first named
160
+ * preset, not a modelled regulation.
161
+ *
162
+ * When `false` the feature is fully dormant:
163
+ * - api: the `/v1/questionnaires*` and `/v1/projects/:id/questionnaires`
164
+ * endpoints are not registered
165
+ *
166
+ * Release toggle: ON in local/dev/staging, OFF in prod while the slices
167
+ * (import → fill → attest + export) land.
168
+ * **Remove this flag (and its gates) at GA.**
169
+ */
170
+ questionnaires: {
171
+ description: "Questionnaire filling: answer an uploaded requirement spreadsheet from project evidence with citations, attest, and write the answers back.",
172
+ availability: "non-prod"
173
+ },
174
+ /**
175
+ * UI Studio Wizard (PRO-1030) — the guided three-choice route through the UI
176
+ * decisions, for the person finishing Discovery who does not want a
177
+ * workbench. When `false` the feature is fully dormant:
178
+ * - web: the `/ui-wizard` route is not registered, and Discovery's UI
179
+ * Studio card offers only the studio itself
180
+ *
181
+ * Split out of {@link FeatureFlags.uiSparring} rather than riding it, for the
182
+ * reason {@link FeatureFlags.flowStudio} was: the wizard and the studio are
183
+ * two front doors onto the same concepts, and they will not graduate on the
184
+ * same day. Everything the wizard reaches is gated on `uiSparring` server-side
185
+ * (the concept job's own assembler throws without it), so every gate on this
186
+ * flag ANDs the two — a wizard released alone would offer a button that 500s.
187
+ *
188
+ * Release toggle: ON in local/dev/staging, OFF in prod.
189
+ * **Remove this flag (and its gates) at GA.**
190
+ */
191
+ uiStudioWizard: {
192
+ description: "UI Studio Wizard: the guided three-choice route through the UI decisions. Only works when uiSparring is also on, including on the API.",
193
+ availability: "non-prod"
194
+ },
195
+ /**
196
+ * Design-system emitter (PRO-887): materialise an organisation's INTERNAL
197
+ * Design Standards into `src/styles/design-tokens.css` + `DESIGN.md` in the
198
+ * generated application at build bootstrap. Never active for a tenant with a
199
+ * linked design-system repository (that repo is the source of truth).
200
+ *
201
+ * Release toggle: ON in local/dev/staging, OFF in prod until the first
202
+ * internal-DS build has been verified end to end.
203
+ */
204
+ designEmitter: {
205
+ description: "Design-system emitter: writes an organization's internal design standards into design-tokens.css and DESIGN.md in the generated application at build bootstrap.",
206
+ availability: "non-prod"
207
+ }
208
+ });
209
+ /** Every flag name, in registry order. */
210
+ const featureFlagNames = Object.keys(featureFlagDefinitions);
211
+ /**
212
+ * Resolve the full flag set for an environment from the registry. Frozen so a
213
+ * resolved set cannot be mutated at runtime — changing a flag is a source edit
214
+ * in {@link featureFlagDefinitions}, not an assignment in an app.
215
+ */
216
+ function resolveFeatureFlags(env) {
217
+ const isOn = (availability) => availability === "everywhere" || availability === "non-prod" && env !== "prod";
218
+ const resolved = {};
219
+ for (const name of featureFlagNames) resolved[name] = isOn(featureFlagDefinitions[name].availability);
220
+ return Object.freeze(resolved);
221
+ }
222
+ /**
223
+ * Safe-default snapshot, resolved as `prod`: every still-gated release toggle
224
+ * is off. Prefer
225
+ * {@link resolveFeatureFlags} with the app's real environment. Retained for
226
+ * consumers that have no environment of their own — notably the published MCP
227
+ * server — so an unconfigured runtime never leaks a work-in-progress feature.
228
+ */
229
+ const featureFlags = resolveFeatureFlags("prod");
230
+ //#endregion
231
+ //#region src/client.ts
232
+ /**
233
+ * HTTP client for the Taiga API.
234
+ * Used by the MCP server to proxy tool calls to the backend.
235
+ */
236
+ var TaigaClient = class {
237
+ baseUrl;
238
+ apiKey;
239
+ introspection = null;
240
+ constructor(opts) {
241
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
242
+ this.apiKey = opts.apiKey;
243
+ }
244
+ async request(method, path, body) {
245
+ const url = `${this.baseUrl}/v1${path}`;
246
+ const headers = {
247
+ Authorization: `Bearer ${this.apiKey}`,
248
+ "Content-Type": "application/json",
249
+ "User-Agent": "taiga-ai-mcp-server/0.0.1"
250
+ };
251
+ const response = await fetch(url, {
252
+ method,
253
+ headers,
254
+ body: body ? JSON.stringify(body) : void 0
255
+ });
256
+ if (!response.ok) {
257
+ const text = await response.text().catch(() => "");
258
+ throw new Error(`Taiga API ${method} ${path} failed (${response.status}): ${text}`);
259
+ }
260
+ return response.json();
261
+ }
262
+ async introspect() {
263
+ const result = await this.request("GET", "/auth/introspect");
264
+ this.introspection = result.data;
265
+ return result.data;
266
+ }
267
+ getIntrospection() {
268
+ return this.introspection;
269
+ }
270
+ hasCapability(capability) {
271
+ if (!this.introspection) return false;
272
+ return this.introspection.capabilities.includes(capability);
273
+ }
274
+ async listProjects(opts) {
275
+ const params = new URLSearchParams();
276
+ if (opts?.teamId) params.set("teamId", opts.teamId);
277
+ if (opts?.status) params.set("status", opts.status);
278
+ if (opts?.limit) params.set("limit", String(opts.limit));
279
+ const qs = params.toString();
280
+ return this.request("GET", `/projects${qs ? `?${qs}` : ""}`);
281
+ }
282
+ async getProject(id) {
283
+ return this.request("GET", `/projects/${id}`);
284
+ }
285
+ async listTenantDocuments(opts) {
286
+ const params = new URLSearchParams();
287
+ if (opts?.limit) params.set("limit", String(opts.limit));
288
+ const qs = params.toString();
289
+ return this.request("GET", `/tenants/current/documents${qs ? `?${qs}` : ""}`);
290
+ }
291
+ async getTenantDocument(type) {
292
+ return this.request("GET", `/tenants/current/documents/${type}`);
293
+ }
294
+ async saveTenantDocumentDraft(type, body) {
295
+ return this.request("PUT", `/tenants/current/documents/${type}/draft`, body);
296
+ }
297
+ async listTeamDocuments(teamId, opts) {
298
+ const params = new URLSearchParams();
299
+ if (opts?.limit) params.set("limit", String(opts.limit));
300
+ const qs = params.toString();
301
+ return this.request("GET", `/teams/${teamId}/documents${qs ? `?${qs}` : ""}`);
302
+ }
303
+ async getTeamDocument(teamId, type) {
304
+ return this.request("GET", `/teams/${teamId}/documents/${type}`);
305
+ }
306
+ async saveTeamDocumentDraft(teamId, type, body) {
307
+ return this.request("PUT", `/teams/${teamId}/documents/${type}/draft`, body);
308
+ }
309
+ async listProjectDocuments(projectId, opts) {
310
+ const params = new URLSearchParams();
311
+ if (opts?.limit) params.set("limit", String(opts.limit));
312
+ const qs = params.toString();
313
+ return this.request("GET", `/projects/${projectId}/documents${qs ? `?${qs}` : ""}`);
314
+ }
315
+ async getProjectDocument(projectId, type) {
316
+ return this.request("GET", `/projects/${projectId}/documents/${type}`);
317
+ }
318
+ async listSkills(opts) {
319
+ const params = new URLSearchParams();
320
+ if (opts?.scope) params.set("scope", opts.scope);
321
+ if (opts?.scopeId) params.set("scopeId", opts.scopeId);
322
+ if (opts?.limit) params.set("limit", String(opts.limit));
323
+ const qs = params.toString();
324
+ return this.request("GET", `/agent-skills${qs ? `?${qs}` : ""}`);
325
+ }
326
+ async getSkill(id) {
327
+ return this.request("GET", `/agent-skills/${id}`);
328
+ }
329
+ async updateSkill(id, body) {
330
+ return this.request("PATCH", `/agent-skills/${id}`, body);
331
+ }
332
+ /**
333
+ * Fetch the full context for a project in parallel: project metadata,
334
+ * team documents, project documents, and team skills.
335
+ * Returns a structured bundle optimized for coding agent consumption.
336
+ */
337
+ async getProjectContext(projectId) {
338
+ const project = await this.request("GET", `/projects/${projectId}`);
339
+ const teamId = project.data.teamId;
340
+ const [projectDocs, teamDocs, skills] = await Promise.all([
341
+ this.listProjectDocuments(projectId, { limit: 20 }).catch(() => ({ data: [] })),
342
+ teamId ? this.listTeamDocuments(teamId, { limit: 20 }).catch(() => ({ data: [] })) : Promise.resolve({ data: [] }),
343
+ featureFlags.skills ? this.listSkills({ limit: 100 }).catch(() => ({ data: [] })) : Promise.resolve({ data: [] })
344
+ ]);
345
+ const keyProjectDocTypes = [
346
+ "specification",
347
+ "architecture",
348
+ "technology_decisions"
349
+ ];
350
+ const keyTeamDocTypes = [
351
+ "domain_context",
352
+ "tech_preferences",
353
+ "architecture_constraints"
354
+ ];
355
+ const docFetches = [];
356
+ for (const docType of keyProjectDocTypes) if (projectDocs.data.some((d) => d.type === docType && !d.isDraft)) docFetches.push(this.getProjectDocument(projectId, docType).then((r) => ({
357
+ type: docType,
358
+ scope: "project",
359
+ data: r.data
360
+ })).catch(() => ({
361
+ type: docType,
362
+ scope: "project",
363
+ data: null
364
+ })));
365
+ if (teamId) {
366
+ for (const docType of keyTeamDocTypes) if (teamDocs.data.some((d) => d.type === docType && !d.isDraft)) docFetches.push(this.getTeamDocument(teamId, docType).then((r) => ({
367
+ type: docType,
368
+ scope: "team",
369
+ data: r.data
370
+ })).catch(() => ({
371
+ type: docType,
372
+ scope: "team",
373
+ data: null
374
+ })));
375
+ }
376
+ const fetchedDocs = await Promise.all(docFetches);
377
+ const context = {
378
+ project: project.data,
379
+ documents: {
380
+ project: Object.fromEntries(fetchedDocs.filter((d) => d.scope === "project" && d.data).map((d) => [d.type, d.data])),
381
+ team: Object.fromEntries(fetchedDocs.filter((d) => d.scope === "team" && d.data).map((d) => [d.type, d.data]))
382
+ },
383
+ available_documents: {
384
+ project: projectDocs.data.map((d) => ({
385
+ type: d.type,
386
+ name: d.name,
387
+ version: d.version,
388
+ isDraft: d.isDraft
389
+ })),
390
+ team: teamDocs.data.map((d) => ({
391
+ type: d.type,
392
+ name: d.name,
393
+ version: d.version,
394
+ isDraft: d.isDraft
395
+ }))
396
+ }
397
+ };
398
+ if (featureFlags.skills) context.skills_summary = skills.data.filter((s) => !s.teamId || s.teamId === teamId).map((s) => ({
399
+ id: s.id,
400
+ name: s.name,
401
+ slug: s.slug,
402
+ description: s.description
403
+ }));
404
+ return context;
405
+ }
406
+ async listBacklogItems(projectId, opts) {
407
+ const params = new URLSearchParams();
408
+ if (opts?.limit) params.set("limit", String(opts.limit));
409
+ const qs = params.toString();
410
+ return this.request("GET", `/projects/${projectId}/backlog${qs ? `?${qs}` : ""}`);
411
+ }
412
+ async getBacklogItem(itemId) {
413
+ return this.request("GET", `/backlog/${itemId}`);
414
+ }
415
+ async listEnvironments(projectId) {
416
+ return this.request("GET", `/projects/${projectId}/environments`);
417
+ }
418
+ async listTeams(opts) {
419
+ const params = new URLSearchParams();
420
+ if (opts?.limit) params.set("limit", String(opts.limit));
421
+ const qs = params.toString();
422
+ return this.request("GET", `/teams${qs ? `?${qs}` : ""}`);
423
+ }
424
+ };
425
+ //#endregion
426
+ //#region src/tools/summarize.ts
427
+ /**
428
+ * Strip heavy content from list responses to avoid context window bloat.
429
+ *
430
+ * List endpoints return summaries (id, name, description, metadata).
431
+ * Get endpoints return the full content.
432
+ *
433
+ * This keeps list calls cheap (~100 tokens per item) and lets the AI
434
+ * selectively load full content only for the items it needs.
435
+ */
436
+ /** Fields to strip from list responses -- these contain the bulk content. */
437
+ const HEAVY_FIELDS = [
438
+ "content",
439
+ "data",
440
+ "sections"
441
+ ];
442
+ /**
443
+ * Remove heavy content fields from each item in an array.
444
+ * Preserves all metadata (id, name, description, type, dates, etc).
445
+ */
446
+ function summarizeList(items) {
447
+ return items.map((item) => {
448
+ const summary = { ...item };
449
+ for (const field of HEAVY_FIELDS) if (field in summary) delete summary[field];
450
+ return summary;
451
+ });
452
+ }
453
+ //#endregion
454
+ //#region src/tools/projects.ts
455
+ function registerProjectTools(server, client) {
456
+ if (client.hasCapability("teams:read")) server.tool("taiga_list_teams", "List all teams in the organization. Use this to find team IDs for reading team documents, skills, and understanding team structure. Teams own domain context, tech preferences, and architecture constraints.", { limit: z.number().min(1).max(100).optional().describe("Max results (default 25)") }, {
457
+ readOnlyHint: true,
458
+ openWorldHint: false
459
+ }, async (params) => {
460
+ const result = await client.listTeams(params);
461
+ return { content: [{
462
+ type: "text",
463
+ text: JSON.stringify(result.data, null, 2)
464
+ }] };
465
+ });
466
+ if (client.hasCapability("projects:read")) {
467
+ server.tool("taiga_list_projects", "List projects in the organization. Use this to find the project you are working on so you can read its specification, architecture, and threat model. Can filter by team; a status filter also exists but no longer distinguishes anything (see its description).", {
468
+ teamId: z.string().uuid().optional().describe("Filter by team ID -- get team IDs from taiga_list_teams"),
469
+ status: z.enum([
470
+ "draft",
471
+ "active",
472
+ "archived"
473
+ ]).optional().describe("Filter by status. Taiga no longer surfaces project status in its UI (PRO-543), so every project is currently \"draft\" and filtering by \"active\" or \"archived\" returns nothing. Omit this unless you are working with data set through the API directly."),
474
+ limit: z.number().min(1).max(100).optional().describe("Max results (default 25)")
475
+ }, {
476
+ readOnlyHint: true,
477
+ openWorldHint: false
478
+ }, async (params) => {
479
+ const result = await client.listProjects(params);
480
+ return { content: [{
481
+ type: "text",
482
+ text: JSON.stringify(result.data, null, 2)
483
+ }] };
484
+ });
485
+ server.tool("taiga_list_project_documents", "List documents for a specific project (names and types only). Project documents are generated during intake: specification (requirements), architecture (system design), technology decisions, data flow, privacy assessment, threat model (security risks), risk register, and user flow. Use taiga_get_project_document to read the full content.", {
486
+ projectId: z.string().uuid().describe("Project ID -- get from taiga_list_projects"),
487
+ limit: z.number().min(1).max(100).optional().describe("Max results (default 25)")
488
+ }, {
489
+ readOnlyHint: true,
490
+ openWorldHint: false
491
+ }, async (params) => {
492
+ const summaries = summarizeList((await client.listProjectDocuments(params.projectId, { limit: params.limit })).data);
493
+ return { content: [{
494
+ type: "text",
495
+ text: JSON.stringify(summaries, null, 2)
496
+ }] };
497
+ });
498
+ server.tool("taiga_get_project_document", "Get the full content of a specific project document. Use this when you need to understand what to build (specification), how the system is designed (architecture), what security risks exist (threat model), or what technology choices were made (technology_decisions). Types: specification, architecture, technology_decisions, data_flow, privacy_assessment, threat_model, risk_register, user_flow.", {
499
+ projectId: z.string().uuid().describe("Project ID -- get from taiga_list_projects"),
500
+ type: z.string().describe("Document type -- get available types from taiga_list_project_documents. Common: specification, architecture, threat_model, technology_decisions")
501
+ }, {
502
+ readOnlyHint: true,
503
+ openWorldHint: false
504
+ }, async (params) => {
505
+ const result = await client.getProjectDocument(params.projectId, params.type);
506
+ return { content: [{
507
+ type: "text",
508
+ text: JSON.stringify(result.data, null, 2)
509
+ }] };
510
+ });
511
+ }
512
+ }
513
+ //#endregion
514
+ //#region src/tools/documents.ts
515
+ function registerDocumentTools(server, client) {
516
+ if (client.hasCapability("tenant:documents:read")) {
517
+ server.tool("taiga_list_documents", "List company-level policy documents (names and types only). Policies define security, data privacy, SDLC, operations, architecture, and compliance rules that apply across all teams and projects. Use taiga_get_document with the document type to read the full policy content.", { limit: z.number().min(1).max(100).optional().describe("Max results (default 25)") }, {
518
+ readOnlyHint: true,
519
+ openWorldHint: false
520
+ }, async (params) => {
521
+ const summaries = summarizeList((await client.listTenantDocuments(params)).data);
522
+ return { content: [{
523
+ type: "text",
524
+ text: JSON.stringify(summaries, null, 2)
525
+ }] };
526
+ });
527
+ server.tool("taiga_get_document", "Get the full content of a company policy document. Use this when you need to understand security requirements, data handling rules, architecture standards, or compliance controls that apply to your code. Types: policy_security, policy_data, policy_sdlc, policy_ops, policy_tic, policy_risk, policy_architecture, policy_supply_chain, policy_cloud_governance, policy_acceptable_use, policy_ip.", { type: z.string().describe("Document type -- get available types from taiga_list_documents. Common: policy_security, policy_data, policy_sdlc, policy_architecture") }, {
528
+ readOnlyHint: true,
529
+ openWorldHint: false
530
+ }, async (params) => {
531
+ const result = await client.getTenantDocument(params.type);
532
+ return { content: [{
533
+ type: "text",
534
+ text: JSON.stringify(result.data, null, 2)
535
+ }] };
536
+ });
537
+ }
538
+ if (client.hasCapability("tenant:documents:write")) server.tool("taiga_update_document", "Update the draft content of a company policy document. Only updates the draft -- does not publish. Use this when you notice a policy is outdated or needs corrections while reviewing code.", {
539
+ type: z.string().describe("Document type (e.g. policy_security, policy_sdlc)"),
540
+ data: z.record(z.string(), z.unknown()).describe("Document content to save as JSON")
541
+ }, {
542
+ readOnlyHint: false,
543
+ destructiveHint: false,
544
+ idempotentHint: true,
545
+ openWorldHint: false
546
+ }, async (params) => {
547
+ const { type, ...body } = params;
548
+ const result = await client.saveTenantDocumentDraft(type, body);
549
+ return { content: [{
550
+ type: "text",
551
+ text: JSON.stringify(result.data, null, 2)
552
+ }] };
553
+ });
554
+ if (client.hasCapability("teams:read")) {
555
+ server.tool("taiga_list_team_documents", "List documents for a specific team (names and types only). Team documents define how the team works: domain context (entities, ubiquitous language), tech preferences (approved stack), architecture constraints, SLO targets, definition of done, and team charter. Use taiga_get_team_document to read the full content.", {
556
+ teamId: z.string().uuid().describe("Team ID -- get from taiga_list_teams"),
557
+ limit: z.number().min(1).max(100).optional().describe("Max results (default 25)")
558
+ }, {
559
+ readOnlyHint: true,
560
+ openWorldHint: false
561
+ }, async (params) => {
562
+ const summaries = summarizeList((await client.listTeamDocuments(params.teamId, { limit: params.limit })).data);
563
+ return { content: [{
564
+ type: "text",
565
+ text: JSON.stringify(summaries, null, 2)
566
+ }] };
567
+ });
568
+ server.tool("taiga_get_team_document", "Get the full content of a specific team document. Use this when you need the team domain context (ubiquitous language, entities, business rules), tech preferences (approved stack, patterns), or architecture constraints. Types: team_charter, definition_of_done, tech_preferences, architecture_constraints, slo_targets, domain_context.", {
569
+ teamId: z.string().uuid().describe("Team ID -- get from taiga_list_teams"),
570
+ type: z.string().describe("Document type -- get available types from taiga_list_team_documents. Most useful: domain_context (entities, language), tech_preferences (stack, patterns)")
571
+ }, {
572
+ readOnlyHint: true,
573
+ openWorldHint: false
574
+ }, async (params) => {
575
+ const result = await client.getTeamDocument(params.teamId, params.type);
576
+ return { content: [{
577
+ type: "text",
578
+ text: JSON.stringify(result.data, null, 2)
579
+ }] };
580
+ });
581
+ }
582
+ if (client.hasCapability("teams:read") && client.hasCapability("tenant:documents:write")) server.tool("taiga_update_team_document", "Update the draft content of a team document. Only updates the draft -- does not publish. Use this when you notice team conventions, domain context, or tech preferences need updating while working on the codebase.", {
583
+ teamId: z.string().uuid().describe("Team ID -- get from taiga_list_teams"),
584
+ type: z.string().describe("Document type (e.g. domain_context, tech_preferences)"),
585
+ data: z.record(z.string(), z.unknown()).describe("Document content to save as JSON")
586
+ }, {
587
+ readOnlyHint: false,
588
+ destructiveHint: false,
589
+ idempotentHint: true,
590
+ openWorldHint: false
591
+ }, async (params) => {
592
+ const { teamId, type, ...body } = params;
593
+ const result = await client.saveTeamDocumentDraft(teamId, type, body);
594
+ return { content: [{
595
+ type: "text",
596
+ text: JSON.stringify(result.data, null, 2)
597
+ }] };
598
+ });
599
+ }
600
+ //#endregion
601
+ //#region src/tools/skills.ts
602
+ function registerSkillTools(server, client) {
603
+ if (client.hasCapability("tenant:documents:read")) {
604
+ server.tool("taiga_list_skills", "List agent skills available in the organization (names and descriptions only). Skills are reusable knowledge modules that guide coding: coding standards, security requirements, architecture patterns, database conventions, etc. Use taiga_get_skill with the skill ID to read the full content.", { limit: z.number().min(1).max(100).optional().describe("Max results (default 25)") }, {
605
+ readOnlyHint: true,
606
+ openWorldHint: false
607
+ }, async (params) => {
608
+ const summaries = summarizeList((await client.listSkills(params)).data);
609
+ return { content: [{
610
+ type: "text",
611
+ text: JSON.stringify(summaries, null, 2)
612
+ }] };
613
+ });
614
+ server.tool("taiga_get_skill", "Get the full content of a specific agent skill. Use this when you need detailed coding guidelines, patterns, or conventions for a specific topic (e.g., security requirements, API design, testing strategy). Returns the complete markdown content with code examples and best practices.", { id: z.string().uuid().describe("Skill ID -- get from taiga_list_skills") }, {
615
+ readOnlyHint: true,
616
+ openWorldHint: false
617
+ }, async (params) => {
618
+ const result = await client.getSkill(params.id);
619
+ return { content: [{
620
+ type: "text",
621
+ text: JSON.stringify(result.data, null, 2)
622
+ }] };
623
+ });
624
+ }
625
+ if (client.hasCapability("tenant:documents:write")) server.tool("taiga_update_skill", "Update an agent skill (coding standards, patterns, conventions). Use this when you notice a skill is outdated, incomplete, or needs corrections while working on the codebase. Changes are saved immediately.", {
626
+ id: z.string().uuid().describe("Skill ID -- get from taiga_list_skills"),
627
+ name: z.string().optional().describe("Updated skill name"),
628
+ description: z.string().optional().describe("Updated skill description"),
629
+ content: z.string().optional().describe("Updated skill content (markdown)")
630
+ }, {
631
+ readOnlyHint: false,
632
+ destructiveHint: false,
633
+ idempotentHint: true,
634
+ openWorldHint: false
635
+ }, async (params) => {
636
+ const { id, ...body } = params;
637
+ const result = await client.updateSkill(id, body);
638
+ return { content: [{
639
+ type: "text",
640
+ text: JSON.stringify(result.data, null, 2)
641
+ }] };
642
+ });
643
+ }
644
+ //#endregion
645
+ //#region src/tools/index.ts
646
+ /**
647
+ * Register all MCP tools based on the API key's effective capabilities.
648
+ * Only tools matching the key's scopes are registered -- the AI assistant
649
+ * never sees tools it can't use.
650
+ *
651
+ * Beta scope: Knowledge base access (policies, skills, team documents).
652
+ * Post-beta: Projects, backlog items, environments, composite tools.
653
+ */
654
+ function registerAllTools(server, client) {
655
+ registerProjectTools(server, client);
656
+ registerDocumentTools(server, client);
657
+ if (featureFlags.skills) registerSkillTools(server, client);
658
+ }
659
+ //#endregion
660
+ //#region src/index.ts
661
+ const DEFAULT_API_URL = "https://api.taiga.app";
7
662
  async function main() {
8
- const apiKey = process.env.TAIGA_API_KEY;
9
- if (!apiKey) {
10
- console.error('Error: TAIGA_API_KEY environment variable is required.');
11
- console.error('Create an API key at https://app.taiga.app/settings/api-keys');
12
- process.exit(1);
13
- }
14
- const apiUrl = process.env.TAIGA_API_URL ?? DEFAULT_API_URL;
15
- const client = new TaigaClient({ baseUrl: apiUrl, apiKey });
16
- // Introspect the API key to discover available capabilities
17
- const introspection = await client.introspect().catch((error) => {
18
- const message = error instanceof Error ? error.message : String(error);
19
- console.error(`Error: Failed to validate API key: ${message}`);
20
- console.error('Check that your TAIGA_API_KEY is valid and not expired.');
21
- process.exit(1);
22
- });
23
- // Create MCP server with dynamic tool registration and instructions
24
- const server = new McpServer({
25
- name: 'taiga',
26
- version: '0.0.1',
27
- }, {
28
- instructions: [
29
- 'You have access to Taiga -- a policy-aware software development platform.',
30
- "Taiga provides your organization's engineering knowledge base:",
31
- '',
32
- '**Company policies** (taiga_list_documents / taiga_get_document):',
33
- 'Security, data privacy, SDLC, architecture, and compliance rules that apply to all code.',
34
- 'Read the relevant policy BEFORE writing code that touches security, data handling, or infrastructure.',
35
- '',
36
- '**Team documents** (taiga_list_team_documents / taiga_get_team_document):',
37
- 'Domain context (ubiquitous language, entities, business rules), tech preferences (approved stack, patterns),',
38
- 'architecture constraints, and SLO targets. Read these to understand HOW this team builds software.',
39
- '',
40
- '**Project documents** (taiga_list_project_documents / taiga_get_project_document):',
41
- "Specification (what to build), architecture (how it's designed), threat model (security risks),",
42
- 'technology decisions, and data flow. Read these to understand the specific project context.',
43
- '',
44
- '**Skills** (taiga_list_skills / taiga_get_skill):',
45
- 'Coding standards, security requirements, API design patterns, testing strategy, database patterns,',
46
- 'and more. These are detailed engineering guidelines with code examples.',
47
- '',
48
- "Workflow: Start with taiga_list_teams to find your team, then read the team's domain_context",
49
- 'and tech_preferences. For project-specific work, use taiga_list_projects to find the project,',
50
- 'then read its specification and architecture. Use skills for coding patterns.',
51
- '',
52
- 'When you notice a policy, skill, or team document is outdated, use the update tools',
53
- '(taiga_update_document, taiga_update_skill, taiga_update_team_document) to fix it.',
54
- 'This keeps the knowledge base accurate for everyone.',
55
- ].join('\n'),
56
- });
57
- registerAllTools(server, client);
58
- const toolCount = introspection.capabilities.length;
59
- console.error(`Taiga MCP server started (${toolCount} capabilities, user: ${introspection.email}, role: ${introspection.role})`);
60
- // Connect via stdio transport
61
- const transport = new StdioServerTransport();
62
- await server.connect(transport);
663
+ const apiKey = process.env.TAIGA_API_KEY;
664
+ if (!apiKey) {
665
+ console.error("Error: TAIGA_API_KEY environment variable is required.");
666
+ console.error("Create an API key at https://app.taiga.app/settings/api-keys");
667
+ process.exit(1);
668
+ }
669
+ const client = new TaigaClient({
670
+ baseUrl: process.env.TAIGA_API_URL ?? DEFAULT_API_URL,
671
+ apiKey
672
+ });
673
+ const introspection = await client.introspect().catch((error) => {
674
+ const message = error instanceof Error ? error.message : String(error);
675
+ console.error(`Error: Failed to validate API key: ${message}`);
676
+ console.error("Check that your TAIGA_API_KEY is valid and not expired.");
677
+ process.exit(1);
678
+ });
679
+ const server = new McpServer({
680
+ name: "taiga",
681
+ version: "0.0.1"
682
+ }, { instructions: [
683
+ "You have access to Taiga -- a policy-aware software development platform.",
684
+ "Taiga provides your organization's engineering knowledge base:",
685
+ "",
686
+ "**Company policies** (taiga_list_documents / taiga_get_document):",
687
+ "Security, data privacy, SDLC, architecture, and compliance rules that apply to all code.",
688
+ "Read the relevant policy BEFORE writing code that touches security, data handling, or infrastructure.",
689
+ "",
690
+ "**Team documents** (taiga_list_team_documents / taiga_get_team_document):",
691
+ "Domain context (ubiquitous language, entities, business rules), tech preferences (approved stack, patterns),",
692
+ "architecture constraints, and SLO targets. Read these to understand HOW this team builds software.",
693
+ "",
694
+ "**Project documents** (taiga_list_project_documents / taiga_get_project_document):",
695
+ "Specification (what to build), architecture (how it's designed), threat model (security risks),",
696
+ "technology decisions, and data flow. Read these to understand the specific project context.",
697
+ "",
698
+ ...featureFlags.skills ? [
699
+ "**Skills** (taiga_list_skills / taiga_get_skill):",
700
+ "Coding standards, security requirements, API design patterns, testing strategy, database patterns,",
701
+ "and more. These are detailed engineering guidelines with code examples.",
702
+ ""
703
+ ] : [],
704
+ "Workflow: Start with taiga_list_teams to find your team, then read the team's domain_context",
705
+ "and tech_preferences. For project-specific work, use taiga_list_projects to find the project,",
706
+ featureFlags.skills ? "then read its specification and architecture. Use skills for coding patterns." : "then read its specification and architecture.",
707
+ "",
708
+ featureFlags.skills ? "When you notice a policy, skill, or team document is outdated, use the update tools" : "When you notice a policy or team document is outdated, use the update tools",
709
+ featureFlags.skills ? "(taiga_update_document, taiga_update_skill, taiga_update_team_document) to fix it." : "(taiga_update_document, taiga_update_team_document) to fix it.",
710
+ "This keeps the knowledge base accurate for everyone."
711
+ ].join("\n") });
712
+ registerAllTools(server, client);
713
+ const toolCount = introspection.capabilities.length;
714
+ console.error(`Taiga MCP server started (${toolCount} capabilities, user: ${introspection.email}, role: ${introspection.role})`);
715
+ const transport = new StdioServerTransport();
716
+ await server.connect(transport);
63
717
  }
64
718
  main().catch((error) => {
65
- console.error('Fatal error:', error);
66
- process.exit(1);
719
+ console.error("Fatal error:", error);
720
+ process.exit(1);
67
721
  });
722
+ //#endregion
723
+ export {};