@taiga-ai/mcp-server 0.14.0-dev.428 → 0.16.0-dev.430

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.
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import {
3
3
  MAINTENANCE_FINDING_KINDS,
4
4
  MAINTENANCE_FINDING_SEVERITIES,
@@ -90,7 +90,7 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
90
90
  'quiet list from a sweep that last ran long ago, or that failed, is not an all-clear; a product never swept ' +
91
91
  'says so. Sweeps run on their own after a push and periodically; nothing here starts one. Read the ' +
92
92
  'findings with taiga_list_findings and the policy standing with taiga_get_policy_standing.',
93
- inputSchema: { productId },
93
+ inputSchema: z.object({ productId }),
94
94
  annotations: READ_ONLY,
95
95
  },
96
96
  (params) =>
@@ -121,7 +121,7 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
121
121
  'submodule or a dependency with no fixed version), the policy controls it breaches, and the initiative ' +
122
122
  'already fixing it (backlogItemId). Omit status for the findings that need action; work from the top tier ' +
123
123
  'down. Read one in full with taiga_get_finding.',
124
- inputSchema: {
124
+ inputSchema: z.object({
125
125
  productId,
126
126
  status: z
127
127
  .enum(MAINTENANCE_FINDING_STATUSES)
@@ -130,7 +130,7 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
130
130
  severity: z.enum(MAINTENANCE_FINDING_SEVERITIES).optional().describe('Only this severity'),
131
131
  kind: z.enum(MAINTENANCE_FINDING_KINDS).optional().describe('Only this kind of finding'),
132
132
  ...pagination,
133
- },
133
+ }),
134
134
  annotations: READ_ONLY,
135
135
  },
136
136
  (params) =>
@@ -153,7 +153,7 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
153
153
  'the advisory and the fixed version, the place in the code, the policy controls it breaches, why it is ' +
154
154
  'ranked where it is, its history (reintroduced, previously resolved) and the remediation under way. A ' +
155
155
  "secret's value is never stored; the finding names the rule and the location only.",
156
- inputSchema: { findingId },
156
+ inputSchema: z.object({ findingId }),
157
157
  annotations: READ_ONLY,
158
158
  },
159
159
  (params) => runTool(async () => toolResult((await client.getFinding(params.findingId)).data))
@@ -170,13 +170,13 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
170
170
  'awaiting verification), and when the documents were derived and assessed. A control with no violation ' +
171
171
  'is not listed, and it is not "passing" either: there is no such state. Pass full: true for every ' +
172
172
  "violation's evidence and the resolved ones. Fix a control with taiga_fix_policy_violation.",
173
- inputSchema: {
173
+ inputSchema: z.object({
174
174
  productId,
175
175
  full: z
176
176
  .boolean()
177
177
  .optional()
178
178
  .describe("Carry each violation's evidence, the policy excerpt and the resolved violations (default false)"),
179
- },
179
+ }),
180
180
  annotations: READ_ONLY,
181
181
  },
182
182
  (params) =>
@@ -217,7 +217,7 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
217
217
  'is only in history, rotate the credential outside Taiga, then ignore it with a reason. Ignoring hides a ' +
218
218
  "shared signal and can record the team's acceptance of a risk, which is the person's decision: ask the " +
219
219
  'person first.',
220
- inputSchema: {
220
+ inputSchema: z.object({
221
221
  findingId,
222
222
  ignored: z.boolean().describe('true ignores the finding, false reopens it'),
223
223
  reason: z.enum(MAINTENANCE_IGNORE_REASONS).optional().describe('Why it is ignored; required when ignoring'),
@@ -227,7 +227,7 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
227
227
  .datetime({ offset: true })
228
228
  .optional()
229
229
  .describe('When the ignore lapses and the finding resurfaces if it still exists (ISO 8601, in the future)'),
230
- },
230
+ }),
231
231
  annotations: OPERATE,
232
232
  },
233
233
  (params) =>
@@ -261,14 +261,14 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
261
261
  'code or infrastructure, every location the same rule flagged. Absent for a secret only in history, ' +
262
262
  'anything in a submodule, and a dependency with no fixed version (remediableBy says upstream_only); the ' +
263
263
  'API refuses those. Starts paid work; ask the person first.',
264
- inputSchema: {
264
+ inputSchema: z.object({
265
265
  productId,
266
266
  findingId,
267
267
  wholeGroup: z
268
268
  .boolean()
269
269
  .optional()
270
270
  .describe("Fix every finding in this finding's group (its package and version, or its rule) together"),
271
- },
271
+ }),
272
272
  annotations: OPERATE,
273
273
  },
274
274
  (params) =>
@@ -312,11 +312,11 @@ export function registerMaintenanceTools(server: McpServer, client: TaigaClient)
312
312
  "than one. Taiga raises one initiative and adds it to the end of the product's queue, exactly as a " +
313
313
  'finding fix does. The violation stays listed while the work is under way: only the next assessment ' +
314
314
  'can record that the documents no longer show it. Starts paid work; ask the person first.',
315
- inputSchema: {
315
+ inputSchema: z.object({
316
316
  productId,
317
317
  controlId: z.string().min(1).max(40).optional().describe("The control's identifier, e.g. SEC-3.4"),
318
318
  violationId: z.string().uuid().optional().describe('One violation -- taiga_get_policy_standing gives it'),
319
- },
319
+ }),
320
320
  annotations: OPERATE,
321
321
  },
322
322
  (params) =>
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { ENVIRONMENT_TYPES } from '@taiga/types';
3
3
  import { z } from 'zod';
4
4
  import { TaigaApiError, type TaigaClient } from '../client.js';
@@ -88,7 +88,7 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
88
88
  'history is mirrored from the provider, including deployments made outside Taiga; syncStatus says when ' +
89
89
  "it last synced. Pass environmentId for one environment's history. Taiga observes deployments and " +
90
90
  'never performs them.',
91
- inputSchema: { productId, environmentId: environmentId.optional(), ...pagination },
91
+ inputSchema: z.object({ productId, environmentId: environmentId.optional(), ...pagination }),
92
92
  annotations: READ_ONLY,
93
93
  },
94
94
  (params) =>
@@ -129,7 +129,7 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
129
129
  'rework rate, which DORA has published none for. Failures and recoveries are read from deployments, not ' +
130
130
  'incidents. A trend built on fewer than ten deployments is approximate; a metric no deployment fed says ' +
131
131
  'so instead of showing a number. Previews and checks do not count.',
132
- inputSchema: { productId, environmentId, windowDays: windowDays('30, 60 or 90') },
132
+ inputSchema: z.object({ productId, environmentId, windowDays: windowDays('30, 60 or 90') }),
133
133
  annotations: READ_ONLY,
134
134
  },
135
135
  (params) =>
@@ -154,7 +154,11 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
154
154
  'signals the beacon reports (Core Web Vitals, JavaScript errors, client-observed API health) where it is ' +
155
155
  'installed; until then they read as not set up, which means no data, not good data. Setting up the ' +
156
156
  "beacon raises an initiative from the environment's settings in the web.",
157
- inputSchema: { productId, environmentId: environmentId.optional(), windowDays: windowDays('30, 60 or 90') },
157
+ inputSchema: z.object({
158
+ productId,
159
+ environmentId: environmentId.optional(),
160
+ windowDays: windowDays('30, 60 or 90'),
161
+ }),
158
162
  annotations: READ_ONLY,
159
163
  },
160
164
  (params) =>
@@ -196,11 +200,11 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
196
200
  'learns what happened from source control. Member-level work. Give it its App URL afterwards with ' +
197
201
  'taiga_update_environment, which is all monitoring needs to start checking. Changes the ' +
198
202
  "product's setup for everybody; ask the person first.",
199
- inputSchema: {
203
+ inputSchema: z.object({
200
204
  productId,
201
205
  name: z.string().trim().min(1).max(100).describe('What the team calls it, e.g. staging'),
202
206
  type: z.enum(ENVIRONMENT_TYPES).describe('Its kind; production is the one delivery metrics report on'),
203
- },
207
+ }),
204
208
  annotations: OPERATE,
205
209
  },
206
210
  (params) =>
@@ -220,12 +224,12 @@ export function registerObserveTools(server: McpServer, client: TaigaClient) {
220
224
  "monitoring needs: once it is saved, Taiga checks the app's availability every few minutes on its own. " +
221
225
  'Marking an environment production makes it the one delivery metrics report on. Changes the ' +
222
226
  "product's setup for everybody; ask the person first.",
223
- inputSchema: {
227
+ inputSchema: z.object({
224
228
  environmentId,
225
229
  name: z.string().trim().min(1).max(100).optional().describe('A new name'),
226
230
  type: z.enum(ENVIRONMENT_TYPES).optional().describe('A new kind'),
227
231
  url: z.string().trim().max(500).url().nullable().optional().describe('The public App URL; null clears it'),
228
- },
232
+ }),
229
233
  annotations: OPERATE_SETTING,
230
234
  },
231
235
  (params) =>
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import {
3
3
  DOCUMENT_INPUTS,
4
4
  type GeneratedDocumentType,
@@ -72,12 +72,12 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
72
72
  'until the draft is published (taiga_publish_product_document). Refused once Discovery is finished and ' +
73
73
  'the set is locked: reopening Discovery is done in the web. Reality documents are derived from the code ' +
74
74
  'and not edited.',
75
- inputSchema: {
75
+ inputSchema: z.object({
76
76
  productId,
77
77
  type: documentType,
78
78
  sectionId: sectionIdFrom('taiga_get_product_document (the plan timeline)'),
79
79
  content: sectionContentInput,
80
- },
80
+ }),
81
81
  annotations: WRITE,
82
82
  },
83
83
  (params) =>
@@ -96,7 +96,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
96
96
  'from it are not rewritten: each is marked outdated, and is regenerated when someone chooses to ' +
97
97
  '(taiga_regenerate_product_document, or taiga_generate_product_documents for every outdated and missing ' +
98
98
  'one). Ask the person before publishing.',
99
- inputSchema: { productId, type: documentType },
99
+ inputSchema: z.object({ productId, type: documentType }),
100
100
  annotations: OPERATE,
101
101
  },
102
102
  (params) => runTool(() => publishDraft(productScope(params.productId, params.type)))
@@ -111,7 +111,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
111
111
  "shows it beside each version; once every published document is reviewed, the product's review item in " +
112
112
  'Action Required resolves. The mark says a human read it, so make it only when the person tells you they ' +
113
113
  'have: reading it yourself is not a review.',
114
- inputSchema: { productId, type: reviewableType },
114
+ inputSchema: z.object({ productId, type: reviewableType }),
115
115
  annotations: WRITE,
116
116
  },
117
117
  (params) =>
@@ -131,7 +131,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
131
131
  'the web. The optional Service Blueprint can be generated whenever someone wants one. The result is published as the new version, and ' +
132
132
  'the documents generated from it are marked outdated in turn. Runs an agent in the background: this ' +
133
133
  'returns the job; taiga_wait_for_job says when it is done. Ask the person first.',
134
- inputSchema: { productId, type: generatedType },
134
+ inputSchema: z.object({ productId, type: generatedType }),
135
135
  annotations: OPERATE,
136
136
  },
137
137
  (params) =>
@@ -171,7 +171,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
171
171
  'published). Refused (CONFLICT) while a document is already being generated. On an imported product, ' +
172
172
  'it queues every unpublished document. Runs agents in the background: this returns the jobs it started ' +
173
173
  '(jobIds); taiga_stop_generating_documents stops the run. Ask the person first.',
174
- inputSchema: { productId },
174
+ inputSchema: z.object({ productId }),
175
175
  annotations: OPERATE,
176
176
  },
177
177
  (params) => runTool(async () => toolResult((await client.generateRemainingDocuments(params.productId)).data))
@@ -185,7 +185,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
185
185
  'Stop a Generate remaining run: every document of it still being generated is cancelled and nothing ' +
186
186
  'after them starts; published documents stay. An empty list means no run was going. Interrupts agent ' +
187
187
  'work; ask the person first.',
188
- inputSchema: { productId },
188
+ inputSchema: z.object({ productId }),
189
189
  annotations: OPERATE,
190
190
  },
191
191
  (params) => runTool(async () => toolResult((await client.stopGeneratingDocuments(params.productId)).data))
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { z } from 'zod';
3
3
  import { TaigaApiError, type TaigaClient } from '../client.js';
4
4
  import { READ_ONLY } from './annotations.js';
@@ -280,9 +280,9 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
280
280
  'Use this to find factory IDs for reading factory instructions. ' +
281
281
  "Factory instructions carry the factory's standing guidance: domain context, tech preferences, " +
282
282
  'and the conventions its products follow.',
283
- inputSchema: {
283
+ inputSchema: z.object({
284
284
  ...pagination,
285
- },
285
+ }),
286
286
  annotations: READ_ONLY,
287
287
  },
288
288
  (params) =>
@@ -302,7 +302,7 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
302
302
  'List products in the organization, as summaries (id, name, slug, status, factory, kind). ' +
303
303
  'Use this to find a product by name; inside a git checkout, taiga_resolve_product finds it from ' +
304
304
  'the remote URL instead. taiga_get_product reads one in full.',
305
- inputSchema: {
305
+ inputSchema: z.object({
306
306
  factoryId: z
307
307
  .string()
308
308
  .uuid()
@@ -316,7 +316,7 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
316
316
  'archived when retired.'
317
317
  ),
318
318
  ...pagination,
319
- },
319
+ }),
320
320
  annotations: READ_ONLY,
321
321
  },
322
322
  (params) =>
@@ -337,10 +337,10 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
337
337
  '(application, infrastructure, deployment) and its settings; the list and resolve tools carry ' +
338
338
  'summaries. Use taiga_list_product_documents for its specification and design, ' +
339
339
  'taiga_list_backlog_items for its work and taiga_list_builds for what the coding agent did.',
340
- inputSchema: {
340
+ inputSchema: z.object({
341
341
  productId: productId.optional(),
342
342
  slug: slug.optional(),
343
- },
343
+ }),
344
344
  annotations: READ_ONLY,
345
345
  },
346
346
  (params) =>
@@ -362,9 +362,9 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
362
362
  'context at once; use this when you only need the ID. Returns every product the repository is linked to (several ' +
363
363
  `when the organization shares repositories; \`truncated\` is true past ${PAGE_SIZE * RESOLVE_PAGE_LIMIT}); ` +
364
364
  'NOT_FOUND means nobody linked it, or the key cannot see the product.',
365
- inputSchema: {
365
+ inputSchema: z.object({
366
366
  remoteUrl,
367
- },
367
+ }),
368
368
  annotations: READ_ONLY,
369
369
  },
370
370
  (params) =>
@@ -403,7 +403,7 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
403
403
  `Two timelines: ${ORIGIN_MEANING.plan} ${ORIGIN_MEANING.reality} ` +
404
404
  'An empty side comes with a note saying why; a side the API could not list is null, with a note. ' +
405
405
  'Use taiga_get_product_document with the type and origin to read one in full.',
406
- inputSchema: { productId },
406
+ inputSchema: z.object({ productId }),
407
407
  annotations: READ_ONLY,
408
408
  },
409
409
  (params) =>
@@ -427,7 +427,7 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
427
427
  'A large document runs to tens of thousands of characters: name the sections you need (the ids ' +
428
428
  'taiga_get_product_context lists) and the read carries only those, listing the rest with their sizes. ' +
429
429
  'A read that finds nothing says which documents and timelines the product does have.',
430
- inputSchema: {
430
+ inputSchema: z.object({
431
431
  productId,
432
432
  type: z
433
433
  .enum(PRODUCT_DOCUMENT_TYPES)
@@ -439,7 +439,7 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
439
439
  .max(50)
440
440
  .optional()
441
441
  .describe('Only these sections, by id; omit for the whole document'),
442
- },
442
+ }),
443
443
  annotations: READ_ONLY,
444
444
  },
445
445
  (params) =>
@@ -1,17 +1,9 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { ErrorCode, type GetPromptResult } from '@modelcontextprotocol/sdk/types.js';
1
+ import { type GetPromptResult, type McpServer, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
3
2
  import { z } from 'zod';
4
3
  import { TaigaApiError, type TaigaClient } from '../client.js';
5
4
  import { POLICY_TYPES, defaultOriginFor, type ProductSettingsView } from './catalogue.js';
6
5
  import { productContext } from './context.js';
7
- import {
8
- RequestError,
9
- apiFailureCode,
10
- apiFailureText,
11
- isMissOrRefusal,
12
- isNetworkFailure,
13
- networkFailureDetail,
14
- } from './errors.js';
6
+ import { apiFailureCode, apiFailureText, isMissOrRefusal, isNetworkFailure, networkFailureDetail } from './errors.js';
15
7
  import { productId as idInput, remoteUrl, slug } from './inputs.js';
16
8
  import { fence as fenced, renderDocument } from './markdown.js';
17
9
  import { resolveProductRef } from './products.js';
@@ -51,7 +43,7 @@ function unreadable(what: string, error: unknown): string {
51
43
 
52
44
  /** A refusal, as the error the prompt protocol has: the arguments named something that is not there. */
53
45
  function refuse(refusal: { code: string; message: string }): never {
54
- throw new RequestError(ErrorCode.InvalidParams, `${refusal.code}: ${refusal.message}`);
46
+ throw new ProtocolError(ProtocolErrorCode.InvalidParams, `${refusal.code}: ${refusal.message}`);
55
47
  }
56
48
 
57
49
  /** A direct read a prompt starts from, with a miss or refusal turned into that error. */
@@ -78,11 +70,11 @@ export function registerPrompts(server: McpServer, client: TaigaClient) {
78
70
  // any request, as it is on the tools. The slug first: a client that
79
71
  // takes the arguments positionally (/server:prompt <arg>) fills the
80
72
  // first one, and the slug is the name a person knows from the URL.
81
- argsSchema: {
73
+ argsSchema: z.object({
82
74
  slug: slug.optional(),
83
75
  productId: idInput.optional(),
84
76
  remoteUrl: remoteUrl.optional(),
85
- },
77
+ }),
86
78
  },
87
79
  async (args) => {
88
80
  const resolved = await resolveProductRef(
@@ -124,7 +116,7 @@ export function registerPrompts(server: McpServer, client: TaigaClient) {
124
116
  'Implement one initiative: its description and acceptance criteria, the architecture of the product ' +
125
117
  "from its own timeline, the factory's instructions and the policy sections to check against. " +
126
118
  'taiga_list_backlog_items gives the item ID.',
127
- argsSchema: { itemId: idInput.describe('The backlog item ID') },
119
+ argsSchema: z.object({ itemId: idInput.describe('The backlog item ID') }),
128
120
  },
129
121
  async (args) => {
130
122
  const item = (
@@ -224,14 +216,16 @@ export function registerPrompts(server: McpServer, client: TaigaClient) {
224
216
  'Review the current diff against the organization policies: one policy by type, or by default the ' +
225
217
  `three a code review most often needs (${CODE_REVIEW_POLICIES.join(', ')}). Each finding names the ` +
226
218
  'policy section it rests on; the "core sections" line of each policy names the ones every organization keeps.',
227
- argsSchema: { policyType: optional(`One of ${POLICY_TYPES.join(', ')}; omit for the code-review three`) },
219
+ argsSchema: z.object({
220
+ policyType: optional(`One of ${POLICY_TYPES.join(', ')}; omit for the code-review three`),
221
+ }),
228
222
  },
229
223
  async (args) => {
230
224
  const types = args.policyType ? [args.policyType] : [...CODE_REVIEW_POLICIES];
231
225
  for (const type of types) {
232
226
  if (!(POLICY_TYPES as readonly string[]).includes(type)) {
233
- throw new RequestError(
234
- ErrorCode.InvalidParams,
227
+ throw new ProtocolError(
228
+ ProtocolErrorCode.InvalidParams,
235
229
  `Policy type "${type}" is not one of: ${POLICY_TYPES.join(', ')}`
236
230
  );
237
231
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { HALT_CAUSES, canResumeHalt, haltClearsItself, renderHalt, type Halt, type HaltCause } from '@taiga/types';
3
3
  import { TaigaApiError, type TaigaClient } from '../client.js';
4
4
  import { z } from 'zod';
@@ -187,7 +187,7 @@ export function registerQueueTools(server: McpServer, client: TaigaClient) {
187
187
  'sentence saying what the line is waiting for. The ' +
188
188
  "line advances only when the active entry's pull request merges, so an entry waiting on a review holds " +
189
189
  'everything behind it. Use this when a queued initiative is not moving.',
190
- inputSchema: { productId },
190
+ inputSchema: z.object({ productId }),
191
191
  annotations: READ_ONLY,
192
192
  },
193
193
  (params) => runTool(async () => toolResult(await queueView(client, params.productId)))
@@ -206,10 +206,10 @@ export function registerQueueTools(server: McpServer, client: TaigaClient) {
206
206
  'build on from its first incomplete step, and the line picks up where it was; a build you had stopped ' +
207
207
  'yourself before pausing stays stopped. Idempotent. Pausing interrupts work and resuming starts it: read ' +
208
208
  'taiga_get_queue first to see what it would interrupt, and ask the person.',
209
- inputSchema: {
209
+ inputSchema: z.object({
210
210
  productId,
211
211
  paused: z.boolean().describe('true pauses the line, false resumes it'),
212
- },
212
+ }),
213
213
  annotations: OPERATE_SETTING,
214
214
  },
215
215
  (params) =>