@taiga-ai/mcp-server 0.17.0-dev.441 → 0.18.0-dev.443
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 +209 -85
- package/package.json +1 -1
- package/src/client.ts +15 -2
- package/src/server.integration.test.ts +404 -37
- package/src/tools/autonomy.ts +2 -2
- package/src/tools/backlogs.ts +67 -8
- package/src/tools/catalogue.test.ts +15 -1
- package/src/tools/context.ts +31 -18
- package/src/tools/documents.ts +13 -10
- package/src/tools/hints.ts +1 -1
- package/src/tools/inbox.ts +24 -5
- package/src/tools/index.ts +23 -2
- package/src/tools/instructions.ts +7 -6
- package/src/tools/knowledge.ts +3 -3
- package/src/tools/maintenance.ts +5 -3
- package/src/tools/observe.ts +29 -13
- package/src/tools/product-documents.ts +4 -5
- package/src/tools/products.ts +7 -11
- package/src/tools/repositories.ts +47 -16
- package/src/tools/sections.ts +17 -5
- package/src/tools/setup.ts +8 -8
- package/taiga-ai-mcp-server-0.18.0-dev.443.tgz +0 -0
- package/taiga-ai-mcp-server-0.17.0-dev.441.tgz +0 -0
|
@@ -112,7 +112,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
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
114
|
inputSchema: z.object({ productId, type: reviewableType }),
|
|
115
|
-
annotations:
|
|
115
|
+
annotations: OPERATE,
|
|
116
116
|
},
|
|
117
117
|
(params) =>
|
|
118
118
|
runTool(async () => toolResult((await client.markProductDocumentReviewed(params.productId, params.type)).data))
|
|
@@ -130,7 +130,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
130
130
|
'are regenerated while it is open only: a finished Discovery locks them, and reopening it is done in ' +
|
|
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
|
-
'returns the job; taiga_wait_for_job says when it is done.
|
|
133
|
+
'returns the job; taiga_wait_for_job says when it is done.',
|
|
134
134
|
inputSchema: z.object({ productId, type: generatedType }),
|
|
135
135
|
annotations: OPERATE,
|
|
136
136
|
},
|
|
@@ -170,7 +170,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
170
170
|
'order they build on each other, one after another (each next one starts when the one before it is ' +
|
|
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
|
-
'(jobIds); taiga_stop_generating_documents stops the run.
|
|
173
|
+
'(jobIds); taiga_stop_generating_documents stops the run.',
|
|
174
174
|
inputSchema: z.object({ productId }),
|
|
175
175
|
annotations: OPERATE,
|
|
176
176
|
},
|
|
@@ -183,8 +183,7 @@ export function registerProductDocumentWrites(server: McpServer, client: TaigaCl
|
|
|
183
183
|
title: 'Stop generating product documents',
|
|
184
184
|
description:
|
|
185
185
|
'Stop a Generate remaining run: every document of it still being generated is cancelled and nothing ' +
|
|
186
|
-
'after them starts; published documents stay. An empty list means no run was going. Interrupts agent '
|
|
187
|
-
'work; ask the person first.',
|
|
186
|
+
'after them starts; published documents stay. An empty list means no run was going. Interrupts agent work.',
|
|
188
187
|
inputSchema: z.object({ productId }),
|
|
189
188
|
annotations: OPERATE,
|
|
190
189
|
},
|
package/src/tools/products.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from './catalogue.js';
|
|
20
20
|
import { apiFailureCode, apiFailureText, isMissOrRefusal, runTool, toolError, toolResult } from './errors.js';
|
|
21
21
|
import { parseRemote } from './remote.js';
|
|
22
|
-
import { sectionReadResult, stripProvenance } from './sections.js';
|
|
22
|
+
import { sectionReadResult, sectionsInput, stripProvenance } from './sections.js';
|
|
23
23
|
import { SEARCH_NOTE, SEARCH_PAGE_SIZE, SEARCH_WITH_CURSOR, page, pickFields, searchPages } from './summarize.js';
|
|
24
24
|
|
|
25
25
|
/** What a product row needs to say for the model to pick one and read it in full. */
|
|
@@ -441,7 +441,8 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
|
|
|
441
441
|
"review and update times), and say which timeline is the product's own (defaultOrigin). " +
|
|
442
442
|
`Two timelines: ${ORIGIN_MEANING.plan} ${ORIGIN_MEANING.reality} ` +
|
|
443
443
|
'An empty side comes with a note saying why; a side the API could not list is null, with a note. ' +
|
|
444
|
-
'Use taiga_get_product_document with the type and origin to read one in full
|
|
444
|
+
'Use taiga_get_product_document with the type and origin to read one in full, or with sections: [] ' +
|
|
445
|
+
'for its outline (section ids and sizes) first.',
|
|
445
446
|
inputSchema: z.object({ productId }),
|
|
446
447
|
annotations: READ_ONLY,
|
|
447
448
|
},
|
|
@@ -463,9 +464,9 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
|
|
|
463
464
|
'Read the plan to learn what to build: specification (requirements), architecture (design), ' +
|
|
464
465
|
'threat_model (security risks), technology_decisions. Read reality, when the product has it, to ' +
|
|
465
466
|
"learn what the code actually does before changing it. Omit origin for the product's own timeline. " +
|
|
466
|
-
'A large document runs to tens of thousands of characters:
|
|
467
|
-
'
|
|
468
|
-
'and any id the document lacks (unknownSections). ' +
|
|
467
|
+
'A large document runs to tens of thousands of characters: sections: [] reads its outline (every ' +
|
|
468
|
+
'section id with its size, no text) on either timeline, then name the sections you need and the read ' +
|
|
469
|
+
'carries only those, listing the rest with their sizes and any id the document lacks (unknownSections). ' +
|
|
469
470
|
'A read that finds nothing says which documents and timelines the product does have.',
|
|
470
471
|
inputSchema: z.object({
|
|
471
472
|
productId,
|
|
@@ -473,12 +474,7 @@ export function registerProductTools(server: McpServer, client: TaigaClient) {
|
|
|
473
474
|
.enum(PRODUCT_DOCUMENT_TYPES)
|
|
474
475
|
.describe('Document type -- taiga_list_product_documents shows which ones the product has'),
|
|
475
476
|
origin: originInput,
|
|
476
|
-
sections:
|
|
477
|
-
.array(z.string().min(1).max(200))
|
|
478
|
-
.min(1)
|
|
479
|
-
.max(50)
|
|
480
|
-
.optional()
|
|
481
|
-
.describe('Only these sections, by id; omit for the whole document'),
|
|
477
|
+
sections: sectionsInput,
|
|
482
478
|
}),
|
|
483
479
|
annotations: READ_ONLY,
|
|
484
480
|
},
|
|
@@ -54,6 +54,16 @@ const repositoryId = idInput(
|
|
|
54
54
|
'taiga_list_product_repositories gives it; a GitHub repository id is not one'
|
|
55
55
|
);
|
|
56
56
|
|
|
57
|
+
/** Round-robin over lists: the first of each, then the second of each, and so on. */
|
|
58
|
+
export function interleave<T>(lists: T[][]): T[] {
|
|
59
|
+
const out: T[] = [];
|
|
60
|
+
const longest = Math.max(0, ...lists.map((list) => list.length));
|
|
61
|
+
for (let index = 0; index < longest; index++) {
|
|
62
|
+
for (const list of lists) if (index < list.length) out.push(list[index]!);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
export function registerRepositoryTools(server: McpServer, client: TaigaClient) {
|
|
58
68
|
if (client.hasCapability('products:read')) {
|
|
59
69
|
server.registerTool(
|
|
@@ -68,8 +78,12 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
68
78
|
},
|
|
69
79
|
(params) =>
|
|
70
80
|
runTool(async () => {
|
|
81
|
+
// The route answers one page of 100 and takes no cursor: past that, the list says it is cut.
|
|
71
82
|
const result = await client.listProductRepositories(params.productId);
|
|
72
|
-
return toolResult(
|
|
83
|
+
return toolResult({
|
|
84
|
+
data: pickFields(result.data as Row[], LINK_FIELDS),
|
|
85
|
+
truncated: result.nextCursor !== null,
|
|
86
|
+
});
|
|
73
87
|
})
|
|
74
88
|
);
|
|
75
89
|
|
|
@@ -87,7 +101,7 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
87
101
|
(params) =>
|
|
88
102
|
runTool(async () => {
|
|
89
103
|
const result = await client.listRepositoryBranches(params.productId, params.repositoryId);
|
|
90
|
-
return toolResult({
|
|
104
|
+
return toolResult({ data: result.data, truncated: result.truncated });
|
|
91
105
|
})
|
|
92
106
|
);
|
|
93
107
|
}
|
|
@@ -99,11 +113,21 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
99
113
|
title: 'Find a repository to link',
|
|
100
114
|
description:
|
|
101
115
|
"The repositories the organization's GitHub connection can reach, to link one to a product " +
|
|
102
|
-
'(taiga_link_repository takes its id). Searches by name with query
|
|
103
|
-
'
|
|
104
|
-
'in
|
|
116
|
+
'(taiga_link_repository takes its id). Searches by name with query. Name the GitHub account (account: ' +
|
|
117
|
+
'its login, the owner in owner/repo, or installationId) to read only its repositories; without one it ' +
|
|
118
|
+
'reads every connected account and takes rows from each in turn, so one account cannot fill the page. No ' +
|
|
119
|
+
'connection means GitHub is not connected yet: that is done once, in the web, by someone who can ' +
|
|
120
|
+
'install the app.',
|
|
105
121
|
inputSchema: z.object({
|
|
106
122
|
query: z.string().trim().min(1).max(256).optional().describe('Part of the repository name'),
|
|
123
|
+
account: z
|
|
124
|
+
.string()
|
|
125
|
+
.trim()
|
|
126
|
+
.min(1)
|
|
127
|
+
// owner/repo: a 39-character login, the slash and a 100-character name.
|
|
128
|
+
.max(140)
|
|
129
|
+
.optional()
|
|
130
|
+
.describe('The GitHub account login (the owner in owner/repo), case-insensitive'),
|
|
107
131
|
installationId: idInput('GitHub account', 'this tool lists them').optional(),
|
|
108
132
|
...pagination,
|
|
109
133
|
}),
|
|
@@ -119,9 +143,13 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
119
143
|
'GitHub is not connected for this organization. It is connected once, in the web; then link.'
|
|
120
144
|
);
|
|
121
145
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
// owner/repo names the owner too: the part before the slash is the account.
|
|
147
|
+
const login = params.account?.split('/')[0]!.toLowerCase();
|
|
148
|
+
const chosen = installations.filter(
|
|
149
|
+
(i) =>
|
|
150
|
+
(!params.installationId || i.id === params.installationId) &&
|
|
151
|
+
(!login || String(i.accountLogin ?? '').toLowerCase() === login)
|
|
152
|
+
);
|
|
125
153
|
if (chosen.length === 0) {
|
|
126
154
|
return toolError('NOT_FOUND', 'No such GitHub connection; these are the connected accounts.', { accounts });
|
|
127
155
|
}
|
|
@@ -131,7 +159,9 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
131
159
|
return toolError('VALIDATION_ERROR', SEARCH_WITH_CURSOR);
|
|
132
160
|
}
|
|
133
161
|
if (chosen.length > 1 && params.cursor !== undefined) {
|
|
134
|
-
return toolError('VALIDATION_ERROR', 'A cursor pages one account: name
|
|
162
|
+
return toolError('VALIDATION_ERROR', 'A cursor pages one account: name it (account or installationId).', {
|
|
163
|
+
accounts,
|
|
164
|
+
});
|
|
135
165
|
}
|
|
136
166
|
// `limit` is the most rows the answer carries, however many accounts
|
|
137
167
|
// are read: a search answers its ranked matches whatever the page
|
|
@@ -158,14 +188,15 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
158
188
|
const pages = await Promise.all(
|
|
159
189
|
chosen.map((i) => client.listInstallationRepositories(String(i.id), { q: params.query, limit: max }))
|
|
160
190
|
);
|
|
161
|
-
|
|
191
|
+
// In turn, one row from each account, so the first account's page
|
|
192
|
+
// cannot fill the answer and hide the others.
|
|
193
|
+
const perAccount = pages.map((p) => rows(p.data));
|
|
194
|
+
const found = interleave(perAccount);
|
|
162
195
|
const cut = found.length > max || pages.some((p) => p.nextCursor);
|
|
163
196
|
return toolResult({
|
|
164
197
|
accounts,
|
|
165
198
|
repositories: page(found.slice(0, max), null),
|
|
166
|
-
...(cut
|
|
167
|
-
? { more: 'More than the limit across accounts: name an installationId, or narrow the query.' }
|
|
168
|
-
: {}),
|
|
199
|
+
...(cut ? { more: 'More than the limit across accounts: name an account, or narrow the query.' } : {}),
|
|
169
200
|
});
|
|
170
201
|
})
|
|
171
202
|
);
|
|
@@ -182,7 +213,7 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
182
213
|
'product, or with replacesRepositoryId swap the one linked now for it in one step (naming the same ' +
|
|
183
214
|
'repository there changes nothing; linking one already linked without it is refused). A link Taiga ' +
|
|
184
215
|
'manages (isManaged) cannot be replaced or unlinked. Once linked, planning reads its code, runs open pull requests in it, ' +
|
|
185
|
-
'sweeps scan it, and its deployment history is imported in the background.
|
|
216
|
+
'sweeps scan it, and its deployment history is imported in the background.',
|
|
186
217
|
inputSchema: z.object({
|
|
187
218
|
productId,
|
|
188
219
|
githubRepositoryId: idInput('GitHub repository', 'taiga_list_linkable_repositories gives it'),
|
|
@@ -208,7 +239,7 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
208
239
|
"Choose the branch of a product's repository that Taiga branches from and opens its pull requests " +
|
|
209
240
|
"against (taiga_list_repository_branches lists them), or null to follow the repository's default " +
|
|
210
241
|
'again. A protected branch is not refused, but Taiga may not be able to merge into it: the answer says ' +
|
|
211
|
-
'when it is protected. Optionally a label for the link.
|
|
242
|
+
'when it is protected. Optionally a label for the link.',
|
|
212
243
|
inputSchema: z.object({
|
|
213
244
|
productId,
|
|
214
245
|
repositoryId,
|
|
@@ -250,7 +281,7 @@ export function registerRepositoryTools(server: McpServer, client: TaigaClient)
|
|
|
250
281
|
'Unlink a repository from a product. Without one, the product can still have documents and initiatives ' +
|
|
251
282
|
'but no changes: nothing is planned against code, built or swept. The repository itself is untouched, ' +
|
|
252
283
|
'and a repository Taiga created for the product cannot be unlinked. ' +
|
|
253
|
-
'To swap it for another, use taiga_link_repository with replacesRepositoryId instead.
|
|
284
|
+
'To swap it for another, use taiga_link_repository with replacesRepositoryId instead.',
|
|
254
285
|
inputSchema: z.object({ productId, repositoryId }),
|
|
255
286
|
annotations: OPERATE,
|
|
256
287
|
},
|
package/src/tools/sections.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { CallToolResult } from '@modelcontextprotocol/server';
|
|
15
|
+
import { z } from 'zod';
|
|
15
16
|
import { toolError, toolResult } from './errors.js';
|
|
16
17
|
|
|
17
18
|
/** A section as stored: an object with `content` and metadata, or (older rows) a bare string. */
|
|
@@ -129,20 +130,31 @@ export function budgetSections(doc: Record<string, unknown>, budget: number): Se
|
|
|
129
130
|
return cutTo(doc, sections, Object.fromEntries(kept));
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
/**
|
|
134
|
+
* The `sections` input of a document read: some sections by id, none for the
|
|
135
|
+
* outline (every id with its size and no text), or omitted for the whole.
|
|
136
|
+
*/
|
|
137
|
+
export const sectionsInput = z
|
|
138
|
+
.array(z.string().min(1).max(200))
|
|
139
|
+
.max(50)
|
|
140
|
+
.optional()
|
|
141
|
+
.describe(
|
|
142
|
+
'Only these sections, by id; [] for the outline (every section id and its size, no text); omit for the whole document'
|
|
143
|
+
);
|
|
144
|
+
|
|
132
145
|
/**
|
|
133
146
|
* The answer to a read that names sections: the ones the document has, the
|
|
134
147
|
* rest listed with their sizes, and the asked-for ids it lacks under
|
|
135
|
-
* unknownSections. One
|
|
136
|
-
*
|
|
148
|
+
* unknownSections. One shape whatever matched (PRO-2778): a wrong id does not
|
|
149
|
+
* throw away the right ones, and an answer with none right still lists every
|
|
150
|
+
* real id with its size, so the retry needs nothing else. No ids is the
|
|
151
|
+
* outline.
|
|
137
152
|
*/
|
|
138
153
|
export function sectionReadResult(doc: Record<string, unknown>, ids: readonly string[], label: string): CallToolResult {
|
|
139
154
|
const cut = pickSections(doc, new Set(ids));
|
|
140
155
|
if (!cut) return toolError('VALIDATION_ERROR', `The ${label} is not made of sections; read it without sections.`);
|
|
141
156
|
const all = Object.keys(sectionsOf(doc.data as Record<string, unknown>) ?? {});
|
|
142
157
|
const unknownSections = ids.filter((id) => !all.includes(id));
|
|
143
|
-
if (unknownSections.length === ids.length) {
|
|
144
|
-
return toolError('NOT_FOUND', `The ${label} has no section ${ids.join(', ')}; its sections are ${all.join(', ')}.`);
|
|
145
|
-
}
|
|
146
158
|
return toolResult({
|
|
147
159
|
...cut.doc,
|
|
148
160
|
omittedSections: cut.omittedSections,
|
package/src/tools/setup.ts
CHANGED
|
@@ -154,7 +154,7 @@ export function registerSetupTools(server: McpServer, client: TaigaClient) {
|
|
|
154
154
|
title: 'Rename a factory',
|
|
155
155
|
description:
|
|
156
156
|
'Rename a factory. Its products, members, knowledge and instructions stay as they are, but its slug ' +
|
|
157
|
-
'follows the new name, so links to the factory change.
|
|
157
|
+
'follows the new name, so links to the factory change.',
|
|
158
158
|
inputSchema: z.object({ factoryId, name: z.string().trim().min(1).max(255).describe('The new name') }),
|
|
159
159
|
annotations: OPERATE_SETTING,
|
|
160
160
|
},
|
|
@@ -194,7 +194,7 @@ export function registerSetupTools(server: McpServer, client: TaigaClient) {
|
|
|
194
194
|
"product (taiga_preview_product_move says who); the product's own knowledge files are re-filed onto " +
|
|
195
195
|
'the new factory, and factory knowledge and instructions stay with the factory that owns them. Refused ' +
|
|
196
196
|
'(CONFLICT) while a plan or build is in flight: stopLine: true stops the line first and then moves, which ' +
|
|
197
|
-
'interrupts that work.
|
|
197
|
+
'interrupts that work.',
|
|
198
198
|
inputSchema: z.object({
|
|
199
199
|
productId,
|
|
200
200
|
factoryId: factoryId.describe('The factory to move it to'),
|
|
@@ -230,8 +230,8 @@ export function registerSetupTools(server: McpServer, client: TaigaClient) {
|
|
|
230
230
|
title: 'Create a product',
|
|
231
231
|
description:
|
|
232
232
|
'Create a product in a factory, the way New product does. It starts one of two ways, chosen now and not ' +
|
|
233
|
-
'later: scratch, where Discovery is a conversation that writes the
|
|
234
|
-
'import (projectType brownfield), where Taiga reads an existing repository and drafts the documents from ' +
|
|
233
|
+
'later: scratch (afterwards projectType greenfield), where Discovery is a conversation that writes the ' +
|
|
234
|
+
'specification (held in the web), or import (projectType brownfield), where Taiga reads an existing repository and drafts the documents from ' +
|
|
235
235
|
'the code: link the repository (taiga_link_repository; linking is for owners and admins, so a member ' +
|
|
236
236
|
'asks one), then taiga_start_import. Also what Taiga builds for it: its ' +
|
|
237
237
|
'infrastructure code and its CI/CD pipelines, each following the organization unless decided here; when ' +
|
|
@@ -275,7 +275,7 @@ export function registerSetupTools(server: McpServer, client: TaigaClient) {
|
|
|
275
275
|
'returns one to the organization default), the sentence on how a half Taiga does not build is done today, ' +
|
|
276
276
|
"and whether it inherits the organization's design standards (null follows the default for how the " +
|
|
277
277
|
'product started). Later plans read the new answer; work already planned is not redone. Autonomy is ' +
|
|
278
|
-
'taiga_set_product_autonomy.
|
|
278
|
+
'taiga_set_product_autonomy.',
|
|
279
279
|
inputSchema: z.object({
|
|
280
280
|
productId,
|
|
281
281
|
name: z.string().trim().min(1).max(255).optional().describe('A new name'),
|
|
@@ -312,7 +312,7 @@ export function registerSetupTools(server: McpServer, client: TaigaClient) {
|
|
|
312
312
|
"the chosen branch, or the repository's default): all of them, or the ones named (with " +
|
|
313
313
|
'what they are derived from). For a document the import misread, refresh it rather than importing ' +
|
|
314
314
|
'again; unchanged sections are not republished. Runs an agent in the background and returns the job; ' +
|
|
315
|
-
'taiga_wait_for_job says when it is done.
|
|
315
|
+
'taiga_wait_for_job says when it is done.',
|
|
316
316
|
inputSchema: z.object({
|
|
317
317
|
productId,
|
|
318
318
|
docTypes: z
|
|
@@ -367,7 +367,7 @@ export function registerSetupTools(server: McpServer, client: TaigaClient) {
|
|
|
367
367
|
title: 'Reopen Discovery',
|
|
368
368
|
description:
|
|
369
369
|
"Reopen a finished product's Discovery so its documents can be amended; finish it again afterwards. " +
|
|
370
|
-
'Nothing built from them is undone.
|
|
370
|
+
'Nothing built from them is undone.',
|
|
371
371
|
inputSchema: z.object({ productId }),
|
|
372
372
|
annotations: OPERATE,
|
|
373
373
|
},
|
|
@@ -390,7 +390,7 @@ export function registerSetupTools(server: McpServer, client: TaigaClient) {
|
|
|
390
390
|
"assessed against the organization's policies, and the first initiatives are planned when Discovery is " +
|
|
391
391
|
'finished. Refused for a product started from scratch (the choice is made at creation), without a ' +
|
|
392
392
|
'linked repository, or while an import runs. Runs agents in the background and returns the job; ' +
|
|
393
|
-
'taiga_wait_for_job says when it is done (it takes a while).
|
|
393
|
+
'taiga_wait_for_job says when it is done (it takes a while).',
|
|
394
394
|
inputSchema: z.object({ productId }),
|
|
395
395
|
annotations: OPERATE,
|
|
396
396
|
},
|
|
Binary file
|
|
Binary file
|