@taiga-ai/mcp-server 0.13.0-dev.423 → 0.14.0-dev.425
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 +1095 -13
- package/package.json +1 -1
- package/src/client.ts +313 -5
- package/src/server.integration.test.ts +899 -5
- package/src/tools/autonomy.ts +90 -0
- package/src/tools/catalogue.test.ts +3 -0
- package/src/tools/documents.ts +28 -15
- package/src/tools/inbox.ts +159 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/instructions.ts +27 -0
- package/src/tools/knowledge.ts +257 -0
- package/src/tools/maintenance.ts +336 -0
- package/src/tools/observe.ts +241 -0
- package/src/tools/product-documents.ts +193 -0
- package/taiga-ai-mcp-server-0.14.0-dev.425.tgz +0 -0
- package/taiga-ai-mcp-server-0.13.0-dev.423.tgz +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import {
|
|
3
|
+
DOCUMENT_INPUTS,
|
|
4
|
+
type GeneratedDocumentType,
|
|
5
|
+
PRODUCT_DOCUMENT_TYPES,
|
|
6
|
+
type ProductDocumentType,
|
|
7
|
+
REVIEWABLE_PRODUCT_DOCUMENT_TYPES,
|
|
8
|
+
SETUP_REQUIRED_DOCUMENT_TYPES,
|
|
9
|
+
} from '@taiga/types';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import type { TaigaClient, TaigaDocument } from '../client.js';
|
|
12
|
+
import { OPERATE, WRITE } from './annotations.js';
|
|
13
|
+
import { type DocumentScope, publishDraft, sectionContentInput, sectionIdFrom, updateSection } from './documents.js';
|
|
14
|
+
import { runTool, toolError, toolResult } from './errors.js';
|
|
15
|
+
import { productId } from './inputs.js';
|
|
16
|
+
import { jobReport } from './jobs.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A product's own documents (docs.tai.ga, Learn: Product documents): drafts,
|
|
20
|
+
* versions and publishing on the plan timeline, the side people and the
|
|
21
|
+
* discovery agents write. A draft changes nothing downstream until it is
|
|
22
|
+
* published; documents generated from one that changed are marked outdated
|
|
23
|
+
* and regenerated, never rewritten in place; and finishing Discovery locks
|
|
24
|
+
* the set, which the API enforces. The reality timeline is derived from the
|
|
25
|
+
* code and is not edited.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
type Row = Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
/** The plan-side documents people edit; the codebase snapshot is derived from the code, never written. */
|
|
31
|
+
type EditableType = Exclude<ProductDocumentType, 'codebase_snapshot'>;
|
|
32
|
+
const EDITABLE_TYPES = PRODUCT_DOCUMENT_TYPES.filter((type): type is EditableType => type !== 'codebase_snapshot') as [
|
|
33
|
+
EditableType,
|
|
34
|
+
...EditableType[],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const documentType = z
|
|
38
|
+
.enum(EDITABLE_TYPES)
|
|
39
|
+
.describe('Document type -- taiga_list_product_documents shows which ones the product has');
|
|
40
|
+
|
|
41
|
+
/** The documents a person reviews: the discovery chain, not the snapshot or the product's own instructions. */
|
|
42
|
+
const reviewableType = z
|
|
43
|
+
.enum(REVIEWABLE_PRODUCT_DOCUMENT_TYPES)
|
|
44
|
+
.describe('Document type -- one of the discovery documents a person reviews');
|
|
45
|
+
|
|
46
|
+
/** The documents an agent writes from other documents; the same table the worker reads its inputs from. */
|
|
47
|
+
const generatedType = z
|
|
48
|
+
.enum(Object.keys(DOCUMENT_INPUTS) as [GeneratedDocumentType, ...GeneratedDocumentType[]])
|
|
49
|
+
.describe('Document type -- one generated from the documents above it');
|
|
50
|
+
|
|
51
|
+
export function registerProductDocumentWrites(server: McpServer, client: TaigaClient) {
|
|
52
|
+
if (!client.hasCapability('products:write')) return;
|
|
53
|
+
// The section edit reads the draft and the published version, and the
|
|
54
|
+
// regenerate reads the product's Discovery state, before they write.
|
|
55
|
+
const canRead = client.hasCapability('products:read');
|
|
56
|
+
|
|
57
|
+
const productScope = (product: string, type: string): DocumentScope => ({
|
|
58
|
+
getDraft: () => client.getProductDocumentDraft(product, type),
|
|
59
|
+
getPublished: async () => (await client.getProductDocument(product, type, 'plan')) as { data: TaigaDocument },
|
|
60
|
+
saveDraft: (data) => client.saveProductDocumentDraft(product, type, { data }),
|
|
61
|
+
publish: () => client.publishProductDocument(product, type),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (canRead) {
|
|
65
|
+
server.registerTool(
|
|
66
|
+
'taiga_update_product_document',
|
|
67
|
+
{
|
|
68
|
+
title: 'Update a section of a product document draft',
|
|
69
|
+
description:
|
|
70
|
+
'Replace the text of one section of a product document on its plan timeline, in the draft. Every other ' +
|
|
71
|
+
'section stays as it is. A draft changes nothing downstream: agents keep reading the published version ' +
|
|
72
|
+
'until the draft is published (taiga_publish_product_document). Refused once Discovery is finished and ' +
|
|
73
|
+
'the set is locked: reopening Discovery is done in the web. Reality documents are derived from the code ' +
|
|
74
|
+
'and not edited.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
productId,
|
|
77
|
+
type: documentType,
|
|
78
|
+
sectionId: sectionIdFrom('taiga_get_product_document (the plan timeline)'),
|
|
79
|
+
content: sectionContentInput,
|
|
80
|
+
},
|
|
81
|
+
annotations: WRITE,
|
|
82
|
+
},
|
|
83
|
+
(params) =>
|
|
84
|
+
runTool(() =>
|
|
85
|
+
updateSection(productScope(params.productId, params.type), params.type, params.sectionId, params.content)
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
server.registerTool(
|
|
91
|
+
'taiga_publish_product_document',
|
|
92
|
+
{
|
|
93
|
+
title: 'Publish a product document draft',
|
|
94
|
+
description:
|
|
95
|
+
"Publish a product document's draft as its new version, the one agents work from. Documents generated " +
|
|
96
|
+
'from it are not rewritten: each is marked outdated, and is regenerated when someone chooses to ' +
|
|
97
|
+
'(taiga_regenerate_product_document, or taiga_generate_product_documents for every outdated and missing ' +
|
|
98
|
+
'one). Ask the person before publishing.',
|
|
99
|
+
inputSchema: { productId, type: documentType },
|
|
100
|
+
annotations: OPERATE,
|
|
101
|
+
},
|
|
102
|
+
(params) => runTool(() => publishDraft(productScope(params.productId, params.type)))
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
server.registerTool(
|
|
106
|
+
'taiga_mark_product_document_reviewed',
|
|
107
|
+
{
|
|
108
|
+
title: 'Mark a product document reviewed',
|
|
109
|
+
description:
|
|
110
|
+
"Record that a person reviewed a product document's latest published version, as the Documents page " +
|
|
111
|
+
"shows it beside each version; once every published document is reviewed, the product's review item in " +
|
|
112
|
+
'Action Required resolves. The mark says a human read it, so make it only when the person tells you they ' +
|
|
113
|
+
'have: reading it yourself is not a review.',
|
|
114
|
+
inputSchema: { productId, type: reviewableType },
|
|
115
|
+
annotations: WRITE,
|
|
116
|
+
},
|
|
117
|
+
(params) =>
|
|
118
|
+
runTool(async () => toolResult((await client.markProductDocumentReviewed(params.productId, params.type)).data))
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (canRead) {
|
|
122
|
+
server.registerTool(
|
|
123
|
+
'taiga_regenerate_product_document',
|
|
124
|
+
{
|
|
125
|
+
title: 'Regenerate a product document',
|
|
126
|
+
description:
|
|
127
|
+
'Ask an agent to generate one product document again from the documents it is derived from, for one ' +
|
|
128
|
+
'marked outdated or one that needs another pass. The Specification comes from the Discovery ' +
|
|
129
|
+
'conversation and the Look & Feel from its concepts, both in the web. The documents Discovery requires ' +
|
|
130
|
+
'are regenerated while it is open only: a finished Discovery locks them, and reopening it is done in ' +
|
|
131
|
+
'the web. The optional Service Blueprint can be generated whenever someone wants one. The result is published as the new version, and ' +
|
|
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. Ask the person first.',
|
|
134
|
+
inputSchema: { productId, type: generatedType },
|
|
135
|
+
annotations: OPERATE,
|
|
136
|
+
},
|
|
137
|
+
(params) =>
|
|
138
|
+
runTool(async () => {
|
|
139
|
+
// The lock guards the draft and publish routes; a generated document
|
|
140
|
+
// is published by the worker directly, so the check is made here.
|
|
141
|
+
// It holds the documents setup requires, as the API's lock does; the
|
|
142
|
+
// optional blueprint stays open after setup.
|
|
143
|
+
const locked = (SETUP_REQUIRED_DOCUMENT_TYPES as readonly string[]).includes(params.type);
|
|
144
|
+
const product = locked ? ((await client.getProduct(params.productId)).data as { status?: unknown }) : null;
|
|
145
|
+
if (product && product.status !== 'draft') {
|
|
146
|
+
return toolError(
|
|
147
|
+
'CONFLICT',
|
|
148
|
+
'Discovery is finished for this product, so its documents are locked. Reopen Discovery in the web to regenerate one.'
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const job = (
|
|
152
|
+
await client.createAgentJob({
|
|
153
|
+
scope: 'product',
|
|
154
|
+
type: 'generate_document',
|
|
155
|
+
targetId: params.productId,
|
|
156
|
+
documentType: params.type,
|
|
157
|
+
})
|
|
158
|
+
).data as Row;
|
|
159
|
+
return toolResult(jobReport(job));
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
server.registerTool(
|
|
165
|
+
'taiga_generate_product_documents',
|
|
166
|
+
{
|
|
167
|
+
title: 'Generate the remaining product documents',
|
|
168
|
+
description:
|
|
169
|
+
'Generate remaining: every document the product still needs, and every one marked outdated, in the ' +
|
|
170
|
+
'order they build on each other, one after another (each next one starts when the one before it is ' +
|
|
171
|
+
'published). Refused (CONFLICT) while a document is already being generated. On an imported product, ' +
|
|
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. Ask the person first.',
|
|
174
|
+
inputSchema: { productId },
|
|
175
|
+
annotations: OPERATE,
|
|
176
|
+
},
|
|
177
|
+
(params) => runTool(async () => toolResult((await client.generateRemainingDocuments(params.productId)).data))
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
server.registerTool(
|
|
181
|
+
'taiga_stop_generating_documents',
|
|
182
|
+
{
|
|
183
|
+
title: 'Stop generating product documents',
|
|
184
|
+
description:
|
|
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.',
|
|
188
|
+
inputSchema: { productId },
|
|
189
|
+
annotations: OPERATE,
|
|
190
|
+
},
|
|
191
|
+
(params) => runTool(async () => toolResult((await client.stopGeneratingDocuments(params.productId)).data))
|
|
192
|
+
);
|
|
193
|
+
}
|
|
Binary file
|
|
Binary file
|