@taiga-ai/mcp-server 0.13.0-rc.424 → 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-rc.424.tgz +0 -0
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApiKeyIntrospection } from '@taiga/types';
|
|
1
|
+
import type { AgentJobType, ApiKeyIntrospection } from '@taiga/types';
|
|
2
2
|
import { SERVER_VERSION } from './version.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -70,6 +70,20 @@ function seg(value: string): string {
|
|
|
70
70
|
return encodeURIComponent(value);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* A query string from the defined values only, prefixed with "?" when there
|
|
75
|
+
* is one. Every value goes through URLSearchParams, so nothing a tool passes
|
|
76
|
+
* can add a parameter of its own.
|
|
77
|
+
*/
|
|
78
|
+
function query(params: Record<string, string | number | boolean | null | undefined>): string {
|
|
79
|
+
const search = new URLSearchParams();
|
|
80
|
+
for (const [key, value] of Object.entries(params)) {
|
|
81
|
+
if (value !== undefined && value !== null && value !== '') search.set(key, String(value));
|
|
82
|
+
}
|
|
83
|
+
const text = search.toString();
|
|
84
|
+
return text ? `?${text}` : '';
|
|
85
|
+
}
|
|
86
|
+
|
|
73
87
|
function parseErrorBody(body: string): { code?: string; message?: string } {
|
|
74
88
|
try {
|
|
75
89
|
const json = JSON.parse(body) as { code?: unknown; message?: unknown; details?: unknown };
|
|
@@ -133,13 +147,15 @@ export class TaigaClient {
|
|
|
133
147
|
'User-Agent': `taiga-ai-mcp-server/${SERVER_VERSION}`,
|
|
134
148
|
};
|
|
135
149
|
// Only with a body: Fastify refuses an empty body under a JSON
|
|
136
|
-
// content type, which is what a bodiless POST (publish) would send.
|
|
137
|
-
|
|
150
|
+
// content type, which is what a bodiless POST (publish) would send. A
|
|
151
|
+
// form (a file upload) sets its own multipart type with its boundary.
|
|
152
|
+
const form = body instanceof FormData;
|
|
153
|
+
if (body !== undefined && !form) headers['Content-Type'] = 'application/json';
|
|
138
154
|
|
|
139
155
|
const response = await fetch(url, {
|
|
140
156
|
method,
|
|
141
157
|
headers,
|
|
142
|
-
body: body === undefined ? null : JSON.stringify(body),
|
|
158
|
+
body: body === undefined ? null : form ? body : JSON.stringify(body),
|
|
143
159
|
// A caller that must stop waiting (a wait's deadline, a cancelled call) ends the read too.
|
|
144
160
|
...(options.signal ? { signal: options.signal } : {}),
|
|
145
161
|
});
|
|
@@ -149,6 +165,8 @@ export class TaigaClient {
|
|
|
149
165
|
throw new TaigaApiError(method, path, response.status, text);
|
|
150
166
|
}
|
|
151
167
|
|
|
168
|
+
// A delete answers 204 with no body: there is nothing to parse.
|
|
169
|
+
if (response.status === 204) return null as T;
|
|
152
170
|
const parsed = (await response.json()) as T;
|
|
153
171
|
return options.keepTenant ? parsed : withoutTenant(parsed);
|
|
154
172
|
}
|
|
@@ -313,7 +331,7 @@ export class TaigaClient {
|
|
|
313
331
|
/** Ask an agent for work: the job is queued and runs in the background. */
|
|
314
332
|
async createAgentJob(body: {
|
|
315
333
|
scope: 'product' | 'tenant';
|
|
316
|
-
type:
|
|
334
|
+
type: AgentJobType;
|
|
317
335
|
targetId: string;
|
|
318
336
|
documentType?: string | undefined;
|
|
319
337
|
metadata?: Record<string, unknown> | undefined;
|
|
@@ -325,6 +343,44 @@ export class TaigaClient {
|
|
|
325
343
|
return this.request<{ data: unknown }>('GET', `/agent-jobs/${seg(id)}`, undefined, { signal });
|
|
326
344
|
}
|
|
327
345
|
|
|
346
|
+
/** The plan-side draft of a product document, or `{ data: null }` when none exists. */
|
|
347
|
+
async getProductDocumentDraft(productId: string, type: string) {
|
|
348
|
+
return this.request<{ data: TaigaDocument | null }>(
|
|
349
|
+
'GET',
|
|
350
|
+
`/products/${seg(productId)}/documents/${seg(type)}/draft`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async saveProductDocumentDraft(productId: string, type: string, body: { data: Record<string, unknown> }) {
|
|
355
|
+
return this.request<{ data: TaigaDocument }>(
|
|
356
|
+
'PUT',
|
|
357
|
+
`/products/${seg(productId)}/documents/${seg(type)}/draft`,
|
|
358
|
+
body
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async publishProductDocument(productId: string, type: string) {
|
|
363
|
+
return this.request<{ data: TaigaDocument; warnings?: { unknownPolicyCitations?: string[] } }>(
|
|
364
|
+
'POST',
|
|
365
|
+
`/products/${seg(productId)}/documents/${seg(type)}/publish`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Mark the published version reviewed by the caller, as the Documents page shows it. */
|
|
370
|
+
async markProductDocumentReviewed(productId: string, type: string) {
|
|
371
|
+
return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/documents/${seg(type)}/review`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Generate remaining: every missing and outdated document, in chain order. */
|
|
375
|
+
async generateRemainingDocuments(productId: string) {
|
|
376
|
+
return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/documents/generate-all`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Cancel a Generate remaining run still in flight. */
|
|
380
|
+
async stopGeneratingDocuments(productId: string) {
|
|
381
|
+
return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/documents/generate-all/stop`);
|
|
382
|
+
}
|
|
383
|
+
|
|
328
384
|
async getProductDocument(productId: string, type: string, origin?: string) {
|
|
329
385
|
const qs = origin ? `?${new URLSearchParams({ origin })}` : '';
|
|
330
386
|
return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/documents/${seg(type)}${qs}`);
|
|
@@ -454,6 +510,258 @@ export class TaigaClient {
|
|
|
454
510
|
return this.request<{ data: unknown[] }>('GET', `/builds/${seg(id)}/steps`);
|
|
455
511
|
}
|
|
456
512
|
|
|
513
|
+
// ---------------------------------------------------------------------------
|
|
514
|
+
// Maintaining
|
|
515
|
+
// ---------------------------------------------------------------------------
|
|
516
|
+
|
|
517
|
+
async getMaintenanceSummary(productId: string) {
|
|
518
|
+
return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/maintenance/summary`);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async listMaintenanceSweeps(productId: string, opts?: { limit?: number | undefined }) {
|
|
522
|
+
return this.request<{ data: unknown[]; nextCursor: string | null }>(
|
|
523
|
+
'GET',
|
|
524
|
+
`/products/${seg(productId)}/maintenance/runs${query({ limit: opts?.limit })}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async listFindings(
|
|
529
|
+
productId: string,
|
|
530
|
+
opts?: {
|
|
531
|
+
status?: string | undefined;
|
|
532
|
+
severity?: string | undefined;
|
|
533
|
+
kind?: string | undefined;
|
|
534
|
+
limit?: number | undefined;
|
|
535
|
+
cursor?: string | undefined;
|
|
536
|
+
}
|
|
537
|
+
) {
|
|
538
|
+
return this.request<{ data: unknown[]; nextCursor: string | null; totalCount?: number }>(
|
|
539
|
+
'GET',
|
|
540
|
+
`/products/${seg(productId)}/maintenance/findings${query({ ...opts })}`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async getFinding(id: string) {
|
|
545
|
+
return this.request<{ data: unknown }>('GET', `/maintenance/findings/${seg(id)}`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async updateFinding(
|
|
549
|
+
id: string,
|
|
550
|
+
body: {
|
|
551
|
+
status: 'open' | 'ignored';
|
|
552
|
+
ignoreReason?: string | undefined;
|
|
553
|
+
ignoreNote?: string | null | undefined;
|
|
554
|
+
ignoreExpiresAt?: string | null | undefined;
|
|
555
|
+
}
|
|
556
|
+
) {
|
|
557
|
+
return this.request<{ data: unknown }>('PATCH', `/maintenance/findings/${seg(id)}`, body);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async remediateFinding(productId: string, findingId: string) {
|
|
561
|
+
return this.request<{ data: unknown }>(
|
|
562
|
+
'POST',
|
|
563
|
+
`/products/${seg(productId)}/maintenance/findings/${seg(findingId)}/remediate`,
|
|
564
|
+
{}
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async remediatePackage(
|
|
569
|
+
productId: string,
|
|
570
|
+
body: {
|
|
571
|
+
ecosystem: string;
|
|
572
|
+
packageName: string;
|
|
573
|
+
currentVersion?: string | null | undefined;
|
|
574
|
+
productRepositoryId?: string | null | undefined;
|
|
575
|
+
}
|
|
576
|
+
) {
|
|
577
|
+
return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/maintenance/packages/remediate`, body);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async getPolicyStanding(productId: string) {
|
|
581
|
+
return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/maintenance/policies`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async remediatePolicyControl(productId: string, controlId: string) {
|
|
585
|
+
return this.request<{ data: unknown }>(
|
|
586
|
+
'POST',
|
|
587
|
+
`/products/${seg(productId)}/maintenance/policies/controls/${seg(controlId)}/remediate`,
|
|
588
|
+
{}
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async remediatePolicyViolation(productId: string, divergenceId: string) {
|
|
593
|
+
return this.request<{ data: unknown }>(
|
|
594
|
+
'POST',
|
|
595
|
+
`/products/${seg(productId)}/maintenance/policies/divergences/${seg(divergenceId)}/remediate`,
|
|
596
|
+
{}
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ---------------------------------------------------------------------------
|
|
601
|
+
// Action Required
|
|
602
|
+
// ---------------------------------------------------------------------------
|
|
603
|
+
|
|
604
|
+
async listInbox(opts: {
|
|
605
|
+
productId?: string | undefined;
|
|
606
|
+
category?: string | undefined;
|
|
607
|
+
resolved?: boolean | undefined;
|
|
608
|
+
limit?: number | undefined;
|
|
609
|
+
cursor?: string | undefined;
|
|
610
|
+
}) {
|
|
611
|
+
const { productId, ...rest } = opts;
|
|
612
|
+
const path = productId ? `/products/${seg(productId)}/inbox` : '/inbox';
|
|
613
|
+
return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `${path}${query({ ...rest })}`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async updateInboxItem(
|
|
617
|
+
id: string,
|
|
618
|
+
body: { status?: 'in_progress' | 'done' | 'dismissed' | undefined; snoozedUntil?: string | null | undefined }
|
|
619
|
+
) {
|
|
620
|
+
return this.request<{ data: unknown }>('PATCH', `/inbox/${seg(id)}`, body);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async listSetupTasks(opts: {
|
|
624
|
+
productId?: string | undefined;
|
|
625
|
+
limit?: number | undefined;
|
|
626
|
+
cursor?: string | undefined;
|
|
627
|
+
}) {
|
|
628
|
+
const { productId, ...rest } = opts;
|
|
629
|
+
const path = productId ? `/products/${seg(productId)}/setup-tasks` : '/setup-tasks';
|
|
630
|
+
return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `${path}${query({ ...rest })}`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ---------------------------------------------------------------------------
|
|
634
|
+
// Deployments, environments and monitoring (observed, never driven)
|
|
635
|
+
// ---------------------------------------------------------------------------
|
|
636
|
+
|
|
637
|
+
async listEnvironments(productId: string, opts?: { limit?: number | undefined; cursor?: string | undefined }) {
|
|
638
|
+
return this.request<{ data: unknown[]; nextCursor: string | null }>(
|
|
639
|
+
'GET',
|
|
640
|
+
`/products/${seg(productId)}/environments${query({ ...opts })}`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async createEnvironment(productId: string, body: { name: string; type: string }) {
|
|
645
|
+
return this.request<{ data: unknown }>('POST', `/products/${seg(productId)}/environments`, body);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async updateEnvironment(
|
|
649
|
+
id: string,
|
|
650
|
+
body: { name?: string | undefined; type?: string | undefined; url?: string | null | undefined }
|
|
651
|
+
) {
|
|
652
|
+
return this.request<{ data: unknown }>('PATCH', `/environments/${seg(id)}`, body);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async getCurrentDeployments(productId: string) {
|
|
656
|
+
return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/deployments/current`);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async listDeployments(
|
|
660
|
+
productId: string,
|
|
661
|
+
opts?: { environmentId?: string | undefined; limit?: number | undefined; cursor?: string | undefined }
|
|
662
|
+
) {
|
|
663
|
+
return this.request<{ data: unknown[]; nextCursor: string | null }>(
|
|
664
|
+
'GET',
|
|
665
|
+
`/products/${seg(productId)}/deployments${query({ ...opts })}`
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async getDeploymentSyncStatus(productId: string) {
|
|
670
|
+
return this.request<{ data: unknown }>('GET', `/products/${seg(productId)}/deployments/sync-status`);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async getDeliveryMetrics(productId: string, environmentId: string, windowDays: number) {
|
|
674
|
+
return this.request<{ data: unknown }>(
|
|
675
|
+
'GET',
|
|
676
|
+
`/products/${seg(productId)}/deployments/insights${query({ environmentId, windowDays })}`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async getMonitoringHealth(productId: string) {
|
|
681
|
+
return this.request<{ data: unknown[] }>('GET', `/products/${seg(productId)}/monitoring/health`);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
async getMonitoringSignal(
|
|
685
|
+
productId: string,
|
|
686
|
+
signal: 'uptime' | 'rum' | 'client-health' | 'history',
|
|
687
|
+
environmentId: string,
|
|
688
|
+
windowDays: number
|
|
689
|
+
) {
|
|
690
|
+
return this.request<{ data: unknown }>(
|
|
691
|
+
'GET',
|
|
692
|
+
`/products/${seg(productId)}/monitoring/${signal}${query({ environmentId, windowDays })}`
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ---------------------------------------------------------------------------
|
|
697
|
+
// Knowledge
|
|
698
|
+
// ---------------------------------------------------------------------------
|
|
699
|
+
|
|
700
|
+
async listKnowledge(opts: {
|
|
701
|
+
factoryId?: string | null | undefined;
|
|
702
|
+
productId?: string | null | undefined;
|
|
703
|
+
limit?: number | undefined;
|
|
704
|
+
cursor?: string | undefined;
|
|
705
|
+
}) {
|
|
706
|
+
// The route reads the literal 'null' as "this level only, not below it".
|
|
707
|
+
const scope = (value: string | null | undefined) => (value === null ? 'null' : value);
|
|
708
|
+
return this.request<{ data: unknown[]; nextCursor: string | null }>(
|
|
709
|
+
'GET',
|
|
710
|
+
`/custom-documents${query({ ...opts, factoryId: scope(opts.factoryId), productId: scope(opts.productId) })}`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async getKnowledge(id: string) {
|
|
715
|
+
return this.request<{ data: unknown }>('GET', `/custom-documents/${seg(id)}`);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async getKnowledgeDownload(id: string) {
|
|
719
|
+
return this.request<{ data: { url?: unknown } & Record<string, unknown> }>(
|
|
720
|
+
'GET',
|
|
721
|
+
`/custom-documents/${seg(id)}/download`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** A text file filed as knowledge at one level: the organization, a factory, or a product in its factory. */
|
|
726
|
+
async addKnowledge(file: {
|
|
727
|
+
fileName: string;
|
|
728
|
+
mime: 'text/markdown' | 'text/plain';
|
|
729
|
+
content: string;
|
|
730
|
+
factoryId?: string | undefined;
|
|
731
|
+
productId?: string | undefined;
|
|
732
|
+
}) {
|
|
733
|
+
const form = new FormData();
|
|
734
|
+
// Fields before the file: the route reads them in any order, but the
|
|
735
|
+
// order a person would read them in costs nothing.
|
|
736
|
+
if (file.factoryId) form.set('factoryId', file.factoryId);
|
|
737
|
+
if (file.productId) form.set('productId', file.productId);
|
|
738
|
+
form.set('file', new Blob([file.content], { type: file.mime }), file.fileName);
|
|
739
|
+
return this.request<{ data: unknown; deduplicated?: boolean }>('POST', '/custom-documents', form);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
async retryKnowledge(id: string) {
|
|
743
|
+
return this.request<{ data: unknown }>('POST', `/custom-documents/${seg(id)}/retry`);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async deleteKnowledge(id: string) {
|
|
747
|
+
return this.request<{ data?: unknown } | null>('DELETE', `/custom-documents/${seg(id)}`);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// ---------------------------------------------------------------------------
|
|
751
|
+
// Autonomy
|
|
752
|
+
// ---------------------------------------------------------------------------
|
|
753
|
+
|
|
754
|
+
async updateProduct(
|
|
755
|
+
productId: string,
|
|
756
|
+
body: { autoMerge?: boolean | null | undefined; autoBuild?: boolean | undefined }
|
|
757
|
+
) {
|
|
758
|
+
return this.request<{ data: unknown }>('PATCH', `/products/${seg(productId)}`, body);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
async updateBacklogItem(itemId: string, body: { autoMerge?: boolean | null | undefined }) {
|
|
762
|
+
return this.request<{ data: unknown }>('PATCH', `/backlog/${seg(itemId)}`, body);
|
|
763
|
+
}
|
|
764
|
+
|
|
457
765
|
// ---------------------------------------------------------------------------
|
|
458
766
|
// Factories
|
|
459
767
|
// ---------------------------------------------------------------------------
|