@taiga-ai/mcp-server 0.2.3-dev.284 → 0.2.3-dev.286
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 +8 -274
- package/package.json +2 -2
- package/src/client.test.ts +0 -19
- package/src/client.ts +3 -36
- package/src/index.ts +3 -19
- package/src/server.integration.test.ts +5 -96
- package/src/tools/backlogs.ts +1 -1
- package/src/tools/index.ts +1 -7
- package/src/tools/projects.ts +1 -1
- package/taiga-ai-mcp-server-0.2.3-dev.286.tgz +0 -0
- package/src/tools/skills.ts +0 -68
- package/taiga-ai-mcp-server-0.2.3-dev.284.tgz +0 -0
package/dist/index.js
CHANGED
|
@@ -2,196 +2,6 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
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 Studio (PRO-630) — the workbench for AI-generated UI concepts of the
|
|
79
|
-
* CUSTOMER's app: the concept gallery, the sparring chat, iterate, and
|
|
80
|
-
* promoting an element into a Design Standards component. When `false` the
|
|
81
|
-
* workbench is fully dormant:
|
|
82
|
-
* - web: the concept gallery, the Studio, the initiative cockpit's
|
|
83
|
-
* direction card and the `/dev/ui-artifact-test` route are not
|
|
84
|
-
* registered
|
|
85
|
-
* - api: the Studio's own routes (persist, delete, feedback, iterate,
|
|
86
|
-
* promote-component) are not registered, and their job assemblers refuse
|
|
87
|
-
*
|
|
88
|
-
* NOT under this flag: the Look & Feel document (PRO-1376) and everything it
|
|
89
|
-
* runs on -- the concept compile/list/read/choose routes, the direction job,
|
|
90
|
-
* "Propose 2 more", the planner's direction summary and the build-time
|
|
91
|
-
* direction. Those shipped in PRO-1537. The flow surface moved out from under
|
|
92
|
-
* this flag in PRO-1029 and graduated to production in PRO-1535; it carries
|
|
93
|
-
* no flag any more.
|
|
94
|
-
*
|
|
95
|
-
* Release toggle: ON in local/dev/staging, OFF in prod until the workbench
|
|
96
|
-
* has its own rollout decision. **Remove this flag (and its gates) at GA.**
|
|
97
|
-
*/
|
|
98
|
-
uiSparring: {
|
|
99
|
-
description: "UI Studio: the workbench for AI-generated UI concepts of the customer's application, with its own API endpoints. Look & Feel is not under it.",
|
|
100
|
-
availability: "non-prod"
|
|
101
|
-
},
|
|
102
|
-
/**
|
|
103
|
-
* Customer-definable check-and-stamp checklists (PRO-946..948, design
|
|
104
|
-
* PRO-933). When `false` the feature is fully dormant:
|
|
105
|
-
* - api: the `/v1/checklist-*` endpoints are not registered
|
|
106
|
-
* - api: no checklist instances are created on project creation (PR 2/3)
|
|
107
|
-
* - web: no discovery-pipeline checklist step, no org-settings editor
|
|
108
|
-
*
|
|
109
|
-
* Release toggle: ON in local/dev/staging, OFF in prod while the three
|
|
110
|
-
* slices (schema/API → gate + project surface → template editor) land.
|
|
111
|
-
* **Remove this flag (and its gates) at GA.**
|
|
112
|
-
*/
|
|
113
|
-
checklists: {
|
|
114
|
-
description: "Customer-defined check-and-stamp checklists: the discovery pipeline step, the organization settings editor and their API endpoints.",
|
|
115
|
-
availability: "non-prod"
|
|
116
|
-
},
|
|
117
|
-
/**
|
|
118
|
-
* Factory v2 engineer pod (PRO-1313..1315, PRO-1331). Two roles:
|
|
119
|
-
* - the admin-only `/v1/engineer/*` endpoints that trigger the EC2-backed
|
|
120
|
-
* Runtime Instances probe runtime (when `false` the routes are not
|
|
121
|
-
* registered, so nothing in the product can reach that runtime);
|
|
122
|
-
* - the environment kill-switch for agent-runtime routing (PRO-1331): with
|
|
123
|
-
* it on, a tenant holding a live `engineer_runtime` entitlement has its
|
|
124
|
-
* initiative planning jobs and build runs routed to the engineer runtime
|
|
125
|
-
* (fail-closed until the run contract lands, PRO-1333); with it off,
|
|
126
|
-
* every tenant stays on the legacy agents whatever the entitlement rows
|
|
127
|
-
* say.
|
|
128
|
-
* OFF in prod until the real engineer pod ships behind its own, separate
|
|
129
|
-
* rollout decision.
|
|
130
|
-
*/
|
|
131
|
-
engineerPod: {
|
|
132
|
-
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).",
|
|
133
|
-
availability: "non-prod"
|
|
134
|
-
},
|
|
135
|
-
/**
|
|
136
|
-
* Notification delivery (PRO-1553) -- taking an inbox item that needs a human
|
|
137
|
-
* and putting it in front of them somewhere other than the app.
|
|
138
|
-
*
|
|
139
|
-
* The first (and so far only) recipient type is an "alternate contact": a
|
|
140
|
-
* verified email address attached to an org or a project rather than to a
|
|
141
|
-
* person, in the AWS alternate-contacts sense. The flagship consumer is a
|
|
142
|
-
* Slack channel's email address, which is how a customer gets Taiga
|
|
143
|
-
* notifications into Slack without us shipping a Slack app.
|
|
144
|
-
*
|
|
145
|
-
* When `false` the feature is fully dormant:
|
|
146
|
-
* - api: the `/v1/notification-recipients*` and
|
|
147
|
-
* `/v1/projects/:id/notification-recipients` endpoints are not registered,
|
|
148
|
-
* the delivery worker does not start, and the inbox raise path writes no
|
|
149
|
-
* `notification_deliveries` rows -- so nothing is queued that a later
|
|
150
|
-
* flag flip would suddenly send
|
|
151
|
-
* - web: the notification-contacts card is not rendered in project settings
|
|
152
|
-
*
|
|
153
|
-
* Release toggle: ON everywhere as of 2026-09-13. What ships is the
|
|
154
|
-
* PROJECT-scoped contact: the org scope exists in the schema, the routing
|
|
155
|
-
* and the API, but no UI offers it and the docs page does not mention it,
|
|
156
|
-
* so it is dormant capability rather than a shipped feature. Per-user
|
|
157
|
-
* recipients, the preference matrix and digests are still unbuilt; they no
|
|
158
|
-
* longer sit behind this flag and want their own gate if they need one.
|
|
159
|
-
*
|
|
160
|
-
* The gate stays in place for one release so the feature can be turned off
|
|
161
|
-
* without a revert if prod surprises us. Turning it off is safe by
|
|
162
|
-
* construction and does not strand mail: with the flag false the routing
|
|
163
|
-
* path writes no `notification_deliveries` rows at all, so nothing
|
|
164
|
-
* accumulates to flood on a later flip.
|
|
165
|
-
* **Remove this flag (and its gates) once prod has run a release with it
|
|
166
|
-
* on.**
|
|
167
|
-
*/
|
|
168
|
-
notifications: {
|
|
169
|
-
description: "Notification delivery: verified per-scope email contacts (alternate contacts) that receive inbox items, plus the delivery queue and worker behind them.",
|
|
170
|
-
availability: "everywhere"
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
/** Every flag name, in registry order. */
|
|
174
|
-
const featureFlagNames = Object.keys(featureFlagDefinitions);
|
|
175
|
-
/**
|
|
176
|
-
* Resolve the full flag set for an environment from the registry. Frozen so a
|
|
177
|
-
* resolved set cannot be mutated at runtime — changing a flag is a source edit
|
|
178
|
-
* in {@link featureFlagDefinitions}, not an assignment in an app.
|
|
179
|
-
*/
|
|
180
|
-
function resolveFeatureFlags(env) {
|
|
181
|
-
const isOn = (availability) => availability === "everywhere" || availability === "non-prod" && env !== "prod";
|
|
182
|
-
const resolved = {};
|
|
183
|
-
for (const name of featureFlagNames) resolved[name] = isOn(featureFlagDefinitions[name].availability);
|
|
184
|
-
return Object.freeze(resolved);
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Safe-default snapshot, resolved as `prod`: every still-gated release toggle
|
|
188
|
-
* is off. Prefer
|
|
189
|
-
* {@link resolveFeatureFlags} with the app's real environment. Retained for
|
|
190
|
-
* consumers that have no environment of their own — notably the published MCP
|
|
191
|
-
* server — so an unconfigured runtime never leaks a work-in-progress feature.
|
|
192
|
-
*/
|
|
193
|
-
const featureFlags = resolveFeatureFlags("prod");
|
|
194
|
-
//#endregion
|
|
195
5
|
//#region src/client.ts
|
|
196
6
|
/**
|
|
197
7
|
* HTTP client for the Taiga API.
|
|
@@ -279,33 +89,15 @@ var TaigaClient = class {
|
|
|
279
89
|
async getProjectDocument(projectId, type) {
|
|
280
90
|
return this.request("GET", `/projects/${projectId}/documents/${type}`);
|
|
281
91
|
}
|
|
282
|
-
async listSkills(opts) {
|
|
283
|
-
const params = new URLSearchParams();
|
|
284
|
-
if (opts?.scope) params.set("scope", opts.scope);
|
|
285
|
-
if (opts?.scopeId) params.set("scopeId", opts.scopeId);
|
|
286
|
-
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
287
|
-
const qs = params.toString();
|
|
288
|
-
return this.request("GET", `/agent-skills${qs ? `?${qs}` : ""}`);
|
|
289
|
-
}
|
|
290
|
-
async getSkill(id) {
|
|
291
|
-
return this.request("GET", `/agent-skills/${id}`);
|
|
292
|
-
}
|
|
293
|
-
async updateSkill(id, body) {
|
|
294
|
-
return this.request("PATCH", `/agent-skills/${id}`, body);
|
|
295
|
-
}
|
|
296
92
|
/**
|
|
297
93
|
* Fetch the full context for a project in parallel: project metadata,
|
|
298
|
-
* team documents
|
|
94
|
+
* team documents and project documents.
|
|
299
95
|
* Returns a structured bundle optimized for coding agent consumption.
|
|
300
96
|
*/
|
|
301
97
|
async getProjectContext(projectId) {
|
|
302
98
|
const project = await this.request("GET", `/projects/${projectId}`);
|
|
303
99
|
const teamId = project.data.teamId;
|
|
304
|
-
const [projectDocs, teamDocs
|
|
305
|
-
this.listProjectDocuments(projectId, { limit: 20 }).catch(() => ({ data: [] })),
|
|
306
|
-
teamId ? this.listTeamDocuments(teamId, { limit: 20 }).catch(() => ({ data: [] })) : Promise.resolve({ data: [] }),
|
|
307
|
-
featureFlags.skills ? this.listSkills({ limit: 100 }).catch(() => ({ data: [] })) : Promise.resolve({ data: [] })
|
|
308
|
-
]);
|
|
100
|
+
const [projectDocs, teamDocs] = await Promise.all([this.listProjectDocuments(projectId, { limit: 20 }).catch(() => ({ data: [] })), teamId ? this.listTeamDocuments(teamId, { limit: 20 }).catch(() => ({ data: [] })) : Promise.resolve({ data: [] })]);
|
|
309
101
|
const keyProjectDocTypes = [
|
|
310
102
|
"specification",
|
|
311
103
|
"architecture",
|
|
@@ -338,7 +130,7 @@ var TaigaClient = class {
|
|
|
338
130
|
})));
|
|
339
131
|
}
|
|
340
132
|
const fetchedDocs = await Promise.all(docFetches);
|
|
341
|
-
|
|
133
|
+
return {
|
|
342
134
|
project: project.data,
|
|
343
135
|
documents: {
|
|
344
136
|
project: Object.fromEntries(fetchedDocs.filter((d) => d.scope === "project" && d.data).map((d) => [d.type, d.data])),
|
|
@@ -359,13 +151,6 @@ var TaigaClient = class {
|
|
|
359
151
|
}))
|
|
360
152
|
}
|
|
361
153
|
};
|
|
362
|
-
if (featureFlags.skills) context.skills_summary = skills.data.filter((s) => !s.teamId || s.teamId === teamId).map((s) => ({
|
|
363
|
-
id: s.id,
|
|
364
|
-
name: s.name,
|
|
365
|
-
slug: s.slug,
|
|
366
|
-
description: s.description
|
|
367
|
-
}));
|
|
368
|
-
return context;
|
|
369
154
|
}
|
|
370
155
|
async listBacklogItems(projectId, opts) {
|
|
371
156
|
const params = new URLSearchParams();
|
|
@@ -417,7 +202,7 @@ function summarizeList(items) {
|
|
|
417
202
|
//#endregion
|
|
418
203
|
//#region src/tools/projects.ts
|
|
419
204
|
function registerProjectTools(server, client) {
|
|
420
|
-
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
|
|
205
|
+
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 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)") }, {
|
|
421
206
|
readOnlyHint: true,
|
|
422
207
|
openWorldHint: false
|
|
423
208
|
}, async (params) => {
|
|
@@ -562,63 +347,18 @@ function registerDocumentTools(server, client) {
|
|
|
562
347
|
});
|
|
563
348
|
}
|
|
564
349
|
//#endregion
|
|
565
|
-
//#region src/tools/skills.ts
|
|
566
|
-
function registerSkillTools(server, client) {
|
|
567
|
-
if (client.hasCapability("tenant:documents:read")) {
|
|
568
|
-
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)") }, {
|
|
569
|
-
readOnlyHint: true,
|
|
570
|
-
openWorldHint: false
|
|
571
|
-
}, async (params) => {
|
|
572
|
-
const summaries = summarizeList((await client.listSkills(params)).data);
|
|
573
|
-
return { content: [{
|
|
574
|
-
type: "text",
|
|
575
|
-
text: JSON.stringify(summaries, null, 2)
|
|
576
|
-
}] };
|
|
577
|
-
});
|
|
578
|
-
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") }, {
|
|
579
|
-
readOnlyHint: true,
|
|
580
|
-
openWorldHint: false
|
|
581
|
-
}, async (params) => {
|
|
582
|
-
const result = await client.getSkill(params.id);
|
|
583
|
-
return { content: [{
|
|
584
|
-
type: "text",
|
|
585
|
-
text: JSON.stringify(result.data, null, 2)
|
|
586
|
-
}] };
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
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.", {
|
|
590
|
-
id: z.string().uuid().describe("Skill ID -- get from taiga_list_skills"),
|
|
591
|
-
name: z.string().optional().describe("Updated skill name"),
|
|
592
|
-
description: z.string().optional().describe("Updated skill description"),
|
|
593
|
-
content: z.string().optional().describe("Updated skill content (markdown)")
|
|
594
|
-
}, {
|
|
595
|
-
readOnlyHint: false,
|
|
596
|
-
destructiveHint: false,
|
|
597
|
-
idempotentHint: true,
|
|
598
|
-
openWorldHint: false
|
|
599
|
-
}, async (params) => {
|
|
600
|
-
const { id, ...body } = params;
|
|
601
|
-
const result = await client.updateSkill(id, body);
|
|
602
|
-
return { content: [{
|
|
603
|
-
type: "text",
|
|
604
|
-
text: JSON.stringify(result.data, null, 2)
|
|
605
|
-
}] };
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
//#endregion
|
|
609
350
|
//#region src/tools/index.ts
|
|
610
351
|
/**
|
|
611
352
|
* Register all MCP tools based on the API key's effective capabilities.
|
|
612
353
|
* Only tools matching the key's scopes are registered -- the AI assistant
|
|
613
354
|
* never sees tools it can't use.
|
|
614
355
|
*
|
|
615
|
-
* Beta scope: Knowledge base access (policies
|
|
356
|
+
* Beta scope: Knowledge base access (policies and team documents).
|
|
616
357
|
* Post-beta: Projects, backlog items, environments, composite tools.
|
|
617
358
|
*/
|
|
618
359
|
function registerAllTools(server, client) {
|
|
619
360
|
registerProjectTools(server, client);
|
|
620
361
|
registerDocumentTools(server, client);
|
|
621
|
-
if (featureFlags.skills) registerSkillTools(server, client);
|
|
622
362
|
}
|
|
623
363
|
//#endregion
|
|
624
364
|
//#region src/index.ts
|
|
@@ -659,18 +399,12 @@ async function main() {
|
|
|
659
399
|
"Specification (what to build), architecture (how it's designed), threat model (security risks),",
|
|
660
400
|
"technology decisions, and data flow. Read these to understand the specific project context.",
|
|
661
401
|
"",
|
|
662
|
-
...featureFlags.skills ? [
|
|
663
|
-
"**Skills** (taiga_list_skills / taiga_get_skill):",
|
|
664
|
-
"Coding standards, security requirements, API design patterns, testing strategy, database patterns,",
|
|
665
|
-
"and more. These are detailed engineering guidelines with code examples.",
|
|
666
|
-
""
|
|
667
|
-
] : [],
|
|
668
402
|
"Workflow: Start with taiga_list_teams to find your team, then read the team's domain_context",
|
|
669
403
|
"and tech_preferences. For project-specific work, use taiga_list_projects to find the project,",
|
|
670
|
-
|
|
404
|
+
"then read its specification and architecture.",
|
|
671
405
|
"",
|
|
672
|
-
|
|
673
|
-
|
|
406
|
+
"When you notice a policy or team document is outdated, use the update tools",
|
|
407
|
+
"(taiga_update_document, taiga_update_team_document) to fix it.",
|
|
674
408
|
"This keeps the knowledge base accurate for everyone."
|
|
675
409
|
].join("\n") });
|
|
676
410
|
registerAllTools(server, client);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ai/mcp-server",
|
|
3
|
-
"version": "0.2.3-dev.
|
|
4
|
-
"description": "MCP server for Taiga -- gives AI assistants access to documents
|
|
3
|
+
"version": "0.2.3-dev.286",
|
|
4
|
+
"description": "MCP server for Taiga -- gives AI assistants access to documents and projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
package/src/client.test.ts
CHANGED
|
@@ -231,25 +231,6 @@ describe('TaigaClient', () => {
|
|
|
231
231
|
});
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
-
it('should GET a single skill by id from agent-skills path', async () => {
|
|
235
|
-
const spy = stubFetch(200, { data: { id: 's1' } });
|
|
236
|
-
|
|
237
|
-
await makeClient().getSkill('s1');
|
|
238
|
-
|
|
239
|
-
expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/agent-skills/s1');
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should include scope filters when listing skills', async () => {
|
|
243
|
-
const spy = stubFetch(200, { data: [], nextCursor: null });
|
|
244
|
-
|
|
245
|
-
await makeClient().listSkills({ scope: 'team', scopeId: 't1', limit: 10 });
|
|
246
|
-
|
|
247
|
-
const url = spy.mock.calls[0][0] as string;
|
|
248
|
-
expect(url).toContain('/v1/agent-skills');
|
|
249
|
-
expect(url).toContain('scope=team');
|
|
250
|
-
expect(url).toContain('scopeId=t1');
|
|
251
|
-
});
|
|
252
|
-
|
|
253
234
|
it('should GET a single backlog item by id (flat path)', async () => {
|
|
254
235
|
const spy = stubFetch(200, { data: { id: 'item-1' } });
|
|
255
236
|
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ApiKeyIntrospection } from '@taiga/types';
|
|
2
|
-
import { featureFlags } from '@taiga/feature-flags';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* HTTP client for the Taiga API.
|
|
@@ -135,34 +134,13 @@ export class TaigaClient {
|
|
|
135
134
|
return this.request<{ data: unknown }>('GET', `/projects/${projectId}/documents/${type}`);
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
// Skills (agent skills)
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
|
|
142
|
-
async listSkills(opts?: { scope?: string; scopeId?: string; limit?: number }) {
|
|
143
|
-
const params = new URLSearchParams();
|
|
144
|
-
if (opts?.scope) params.set('scope', opts.scope);
|
|
145
|
-
if (opts?.scopeId) params.set('scopeId', opts.scopeId);
|
|
146
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
147
|
-
const qs = params.toString();
|
|
148
|
-
return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/agent-skills${qs ? `?${qs}` : ''}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async getSkill(id: string) {
|
|
152
|
-
return this.request<{ data: unknown }>('GET', `/agent-skills/${id}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async updateSkill(id: string, body: { name?: string; description?: string; content?: string }) {
|
|
156
|
-
return this.request<{ data: unknown }>('PATCH', `/agent-skills/${id}`, body);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
137
|
// ---------------------------------------------------------------------------
|
|
160
138
|
// Team Documents
|
|
161
139
|
// ---------------------------------------------------------------------------
|
|
162
140
|
|
|
163
141
|
/**
|
|
164
142
|
* Fetch the full context for a project in parallel: project metadata,
|
|
165
|
-
* team documents
|
|
143
|
+
* team documents and project documents.
|
|
166
144
|
* Returns a structured bundle optimized for coding agent consumption.
|
|
167
145
|
*/
|
|
168
146
|
async getProjectContext(projectId: string): Promise<Record<string, unknown>> {
|
|
@@ -170,17 +148,12 @@ export class TaigaClient {
|
|
|
170
148
|
const project = await this.request<{ data: Record<string, unknown> }>('GET', `/projects/${projectId}`);
|
|
171
149
|
const teamId = project.data.teamId as string | undefined;
|
|
172
150
|
|
|
173
|
-
// Fetch
|
|
174
|
-
|
|
175
|
-
// the endpoint would 404 otherwise.
|
|
176
|
-
const [projectDocs, teamDocs, skills] = await Promise.all([
|
|
151
|
+
// Fetch the document lists in parallel.
|
|
152
|
+
const [projectDocs, teamDocs] = await Promise.all([
|
|
177
153
|
this.listProjectDocuments(projectId, { limit: 20 }).catch(() => ({ data: [] })),
|
|
178
154
|
teamId
|
|
179
155
|
? this.listTeamDocuments(teamId, { limit: 20 }).catch(() => ({ data: [] }))
|
|
180
156
|
: Promise.resolve({ data: [] }),
|
|
181
|
-
featureFlags.skills
|
|
182
|
-
? this.listSkills({ limit: 100 }).catch(() => ({ data: [] }))
|
|
183
|
-
: Promise.resolve({ data: [] as unknown[] }),
|
|
184
157
|
]);
|
|
185
158
|
|
|
186
159
|
// Fetch full content for key documents (in parallel)
|
|
@@ -240,12 +213,6 @@ export class TaigaClient {
|
|
|
240
213
|
},
|
|
241
214
|
};
|
|
242
215
|
|
|
243
|
-
if (featureFlags.skills) {
|
|
244
|
-
context.skills_summary = (skills.data as Record<string, unknown>[])
|
|
245
|
-
.filter((s) => !s.teamId || s.teamId === teamId)
|
|
246
|
-
.map((s) => ({ id: s.id, name: s.name, slug: s.slug, description: s.description }));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
216
|
return context;
|
|
250
217
|
}
|
|
251
218
|
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
-
import { featureFlags } from '@taiga/feature-flags';
|
|
6
5
|
import { TaigaClient } from './client.js';
|
|
7
6
|
import { registerAllTools } from './tools/index.js';
|
|
8
7
|
|
|
@@ -51,27 +50,12 @@ async function main() {
|
|
|
51
50
|
"Specification (what to build), architecture (how it's designed), threat model (security risks),",
|
|
52
51
|
'technology decisions, and data flow. Read these to understand the specific project context.',
|
|
53
52
|
'',
|
|
54
|
-
// Skills is hidden behind a feature flag (user-facing surface only).
|
|
55
|
-
...(featureFlags.skills
|
|
56
|
-
? [
|
|
57
|
-
'**Skills** (taiga_list_skills / taiga_get_skill):',
|
|
58
|
-
'Coding standards, security requirements, API design patterns, testing strategy, database patterns,',
|
|
59
|
-
'and more. These are detailed engineering guidelines with code examples.',
|
|
60
|
-
'',
|
|
61
|
-
]
|
|
62
|
-
: []),
|
|
63
53
|
"Workflow: Start with taiga_list_teams to find your team, then read the team's domain_context",
|
|
64
54
|
'and tech_preferences. For project-specific work, use taiga_list_projects to find the project,',
|
|
65
|
-
|
|
66
|
-
? 'then read its specification and architecture. Use skills for coding patterns.'
|
|
67
|
-
: 'then read its specification and architecture.',
|
|
55
|
+
'then read its specification and architecture.',
|
|
68
56
|
'',
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
: 'When you notice a policy or team document is outdated, use the update tools',
|
|
72
|
-
featureFlags.skills
|
|
73
|
-
? '(taiga_update_document, taiga_update_skill, taiga_update_team_document) to fix it.'
|
|
74
|
-
: '(taiga_update_document, taiga_update_team_document) to fix it.',
|
|
57
|
+
'When you notice a policy or team document is outdated, use the update tools',
|
|
58
|
+
'(taiga_update_document, taiga_update_team_document) to fix it.',
|
|
75
59
|
'This keeps the knowledge base accurate for everyone.',
|
|
76
60
|
].join('\n'),
|
|
77
61
|
}
|
|
@@ -5,11 +5,6 @@ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
|
5
5
|
import { TaigaClient } from './client.js';
|
|
6
6
|
import { registerAllTools } from './tools/index.js';
|
|
7
7
|
|
|
8
|
-
// Skills is hidden behind a feature flag. Mock the shared flag with a mutable
|
|
9
|
-
// object so tests can exercise both the default (off) and enabled states.
|
|
10
|
-
const { mockFeatureFlags } = vi.hoisted(() => ({ mockFeatureFlags: { skills: false } }));
|
|
11
|
-
vi.mock('@taiga/feature-flags', () => ({ featureFlags: mockFeatureFlags }));
|
|
12
|
-
|
|
13
8
|
// ---------------------------------------------------------------------------
|
|
14
9
|
// Helpers -- thin wrappers, not abstraction layers
|
|
15
10
|
// ---------------------------------------------------------------------------
|
|
@@ -24,11 +19,10 @@ const ALL_CAPABILITIES = [
|
|
|
24
19
|
];
|
|
25
20
|
|
|
26
21
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* (taiga_list_skills / taiga_get_skill / taiga_update_skill) are excluded.
|
|
22
|
+
* Every tool name the server registers for a key with all capabilities,
|
|
23
|
+
* sorted for deterministic comparison.
|
|
30
24
|
*/
|
|
31
|
-
const
|
|
25
|
+
const ALL_TOOL_NAMES = [
|
|
32
26
|
'taiga_get_document',
|
|
33
27
|
'taiga_get_project_document',
|
|
34
28
|
'taiga_get_team_document',
|
|
@@ -126,7 +120,6 @@ function parseToolResult(result: Record<string, unknown>) {
|
|
|
126
120
|
describe('MCP server integration', () => {
|
|
127
121
|
beforeEach(() => {
|
|
128
122
|
vi.restoreAllMocks();
|
|
129
|
-
mockFeatureFlags.skills = false;
|
|
130
123
|
});
|
|
131
124
|
|
|
132
125
|
// -------------------------------------------------------------------------
|
|
@@ -136,23 +129,12 @@ describe('MCP server integration', () => {
|
|
|
136
129
|
// -------------------------------------------------------------------------
|
|
137
130
|
|
|
138
131
|
describe('tool discovery', () => {
|
|
139
|
-
it('should expose
|
|
132
|
+
it('should expose every tool when API key has full capabilities', async () => {
|
|
140
133
|
const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
|
|
141
134
|
|
|
142
135
|
const { tools } = await client.listTools();
|
|
143
136
|
|
|
144
|
-
|
|
145
|
-
expect(toolNames(tools)).toEqual(BASE_TOOL_NAMES);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should not register skill tools when the skills flag is off', async () => {
|
|
149
|
-
const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
|
|
150
|
-
|
|
151
|
-
const names = toolNames((await client.listTools()).tools);
|
|
152
|
-
|
|
153
|
-
expect(names).not.toContain('taiga_list_skills');
|
|
154
|
-
expect(names).not.toContain('taiga_get_skill');
|
|
155
|
-
expect(names).not.toContain('taiga_update_skill');
|
|
137
|
+
expect(toolNames(tools)).toEqual(ALL_TOOL_NAMES);
|
|
156
138
|
});
|
|
157
139
|
|
|
158
140
|
it('should reject tools/list when API key has no capabilities', async () => {
|
|
@@ -185,9 +167,6 @@ describe('MCP server integration', () => {
|
|
|
185
167
|
|
|
186
168
|
expect(names).toContain('taiga_list_documents');
|
|
187
169
|
expect(names).toContain('taiga_get_document');
|
|
188
|
-
// Skills is flag-off by default.
|
|
189
|
-
expect(names).not.toContain('taiga_list_skills');
|
|
190
|
-
expect(names).not.toContain('taiga_get_skill');
|
|
191
170
|
expect(names).not.toContain('taiga_update_document');
|
|
192
171
|
});
|
|
193
172
|
|
|
@@ -200,8 +179,6 @@ describe('MCP server integration', () => {
|
|
|
200
179
|
const names = toolNames(tools);
|
|
201
180
|
|
|
202
181
|
expect(names).toContain('taiga_update_document');
|
|
203
|
-
// Skills is flag-off by default.
|
|
204
|
-
expect(names).not.toContain('taiga_update_skill');
|
|
205
182
|
});
|
|
206
183
|
|
|
207
184
|
it('should expose team document write when both teams:read and documents:write are granted', async () => {
|
|
@@ -229,11 +206,8 @@ describe('MCP server integration', () => {
|
|
|
229
206
|
expect(names).toContain('taiga_list_team_documents');
|
|
230
207
|
// tenant:documents:read tools present
|
|
231
208
|
expect(names).toContain('taiga_list_documents');
|
|
232
|
-
// skills flag-off by default
|
|
233
|
-
expect(names).not.toContain('taiga_list_skills');
|
|
234
209
|
// write tools absent
|
|
235
210
|
expect(names).not.toContain('taiga_update_document');
|
|
236
|
-
expect(names).not.toContain('taiga_update_skill');
|
|
237
211
|
});
|
|
238
212
|
|
|
239
213
|
it('should provide a description and input schema for every tool', async () => {
|
|
@@ -259,8 +233,6 @@ describe('MCP server integration', () => {
|
|
|
259
233
|
const CONTRACT: Record<string, string[]> = {
|
|
260
234
|
'projects:read': ['taiga_list_projects', 'taiga_list_project_documents', 'taiga_get_project_document'],
|
|
261
235
|
'teams:read': ['taiga_list_teams', 'taiga_list_team_documents', 'taiga_get_team_document'],
|
|
262
|
-
// Skills tools are flag-off by default; their capability gating is covered
|
|
263
|
-
// in the 'when the skills feature flag is enabled' block below.
|
|
264
236
|
'tenant:documents:read': ['taiga_list_documents', 'taiga_get_document'],
|
|
265
237
|
'tenant:documents:write': ['taiga_update_document'],
|
|
266
238
|
};
|
|
@@ -429,67 +401,4 @@ describe('MCP server integration', () => {
|
|
|
429
401
|
expect(result.isError).toBe(true);
|
|
430
402
|
});
|
|
431
403
|
});
|
|
432
|
-
|
|
433
|
-
// -------------------------------------------------------------------------
|
|
434
|
-
// Skills feature flag -- when enabled, the skill tools register and behave
|
|
435
|
-
// exactly like the other knowledge-base tools. This preserves coverage of
|
|
436
|
-
// the skill tools for the day the flag is flipped back on.
|
|
437
|
-
// -------------------------------------------------------------------------
|
|
438
|
-
|
|
439
|
-
describe('when the skills feature flag is enabled', () => {
|
|
440
|
-
beforeEach(() => {
|
|
441
|
-
mockFeatureFlags.skills = true;
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
it('registers read skill tools for a documents:read key', async () => {
|
|
445
|
-
const { client } = await bootMcpPair({ capabilities: ['tenant:documents:read'] });
|
|
446
|
-
|
|
447
|
-
const names = toolNames((await client.listTools()).tools);
|
|
448
|
-
|
|
449
|
-
expect(names).toContain('taiga_list_skills');
|
|
450
|
-
expect(names).toContain('taiga_get_skill');
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
it('registers taiga_update_skill with documents:write', async () => {
|
|
454
|
-
const { client } = await bootMcpPair({
|
|
455
|
-
capabilities: ['tenant:documents:read', 'tenant:documents:write'],
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
const names = toolNames((await client.listTools()).tools);
|
|
459
|
-
|
|
460
|
-
expect(names).toContain('taiga_update_skill');
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it('should return skills through MCP protocol', async () => {
|
|
464
|
-
const skills = [{ id: 's1', name: 'React Standards' }];
|
|
465
|
-
|
|
466
|
-
const { client } = await bootMcpPair({
|
|
467
|
-
capabilities: ALL_CAPABILITIES,
|
|
468
|
-
routes: { 'GET /v1/agent-skills': { data: skills, nextCursor: null } },
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
const result = await client.callTool({ name: 'taiga_list_skills', arguments: {} });
|
|
472
|
-
|
|
473
|
-
expect(parseToolResult(result)).toEqual(skills);
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
it('should send PATCH when updating a skill', async () => {
|
|
477
|
-
const skillId = '0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5c';
|
|
478
|
-
const updated = { id: skillId, name: 'Updated Skill', content: '# Updated' };
|
|
479
|
-
|
|
480
|
-
const { client, fetchSpy } = await bootMcpPair({
|
|
481
|
-
capabilities: ALL_CAPABILITIES,
|
|
482
|
-
routes: { [`PATCH /v1/agent-skills/${skillId}`]: { data: updated } },
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
await client.callTool({
|
|
486
|
-
name: 'taiga_update_skill',
|
|
487
|
-
arguments: { id: skillId, content: '# Updated' },
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
const patchCall = fetchSpy.mock.calls.find((c: unknown[]) => (c[1] as RequestInit)?.method === 'PATCH');
|
|
491
|
-
expect(patchCall).toBeDefined();
|
|
492
|
-
expect(patchCall![0]).toContain(`/v1/agent-skills/${skillId}`);
|
|
493
|
-
});
|
|
494
|
-
});
|
|
495
404
|
});
|
package/src/tools/backlogs.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Backlog tools are reserved for post-beta.
|
|
2
|
-
// The beta MCP server focuses on the knowledge base (policies,
|
|
2
|
+
// The beta MCP server focuses on the knowledge base (policies, team docs).
|
|
3
3
|
|
|
4
4
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
5
|
import type { TaigaClient } from '../client.js';
|
package/src/tools/index.ts
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { featureFlags } from '@taiga/feature-flags';
|
|
3
2
|
import type { TaigaClient } from '../client.js';
|
|
4
3
|
import { registerProjectTools } from './projects.js';
|
|
5
4
|
import { registerDocumentTools } from './documents.js';
|
|
6
5
|
import { registerBacklogTools } from './backlogs.js';
|
|
7
|
-
import { registerSkillTools } from './skills.js';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* Register all MCP tools based on the API key's effective capabilities.
|
|
11
9
|
* Only tools matching the key's scopes are registered -- the AI assistant
|
|
12
10
|
* never sees tools it can't use.
|
|
13
11
|
*
|
|
14
|
-
* Beta scope: Knowledge base access (policies
|
|
12
|
+
* Beta scope: Knowledge base access (policies and team documents).
|
|
15
13
|
* Post-beta: Projects, backlog items, environments, composite tools.
|
|
16
14
|
*/
|
|
17
15
|
export function registerAllTools(server: McpServer, client: TaigaClient): void {
|
|
18
16
|
registerProjectTools(server, client);
|
|
19
17
|
registerDocumentTools(server, client);
|
|
20
18
|
registerBacklogTools(server, client);
|
|
21
|
-
// Skills is hidden behind a feature flag (user-facing surface only).
|
|
22
|
-
if (featureFlags.skills) {
|
|
23
|
-
registerSkillTools(server, client);
|
|
24
|
-
}
|
|
25
19
|
}
|
package/src/tools/projects.ts
CHANGED
|
@@ -8,7 +8,7 @@ export function registerProjectTools(server: McpServer, client: TaigaClient) {
|
|
|
8
8
|
server.tool(
|
|
9
9
|
'taiga_list_teams',
|
|
10
10
|
'List all teams in the organization. Use this to find team IDs ' +
|
|
11
|
-
'for reading team documents
|
|
11
|
+
'for reading team documents and understanding team structure. ' +
|
|
12
12
|
'Teams own domain context, tech preferences, and architecture constraints.',
|
|
13
13
|
{
|
|
14
14
|
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
Binary file
|
package/src/tools/skills.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import type { TaigaClient } from '../client.js';
|
|
4
|
-
import { summarizeList } from './summarize.js';
|
|
5
|
-
|
|
6
|
-
export function registerSkillTools(server: McpServer, client: TaigaClient) {
|
|
7
|
-
if (client.hasCapability('tenant:documents:read')) {
|
|
8
|
-
server.tool(
|
|
9
|
-
'taiga_list_skills',
|
|
10
|
-
'List agent skills available in the organization (names and descriptions only). ' +
|
|
11
|
-
'Skills are reusable knowledge modules that guide coding: coding standards, ' +
|
|
12
|
-
'security requirements, architecture patterns, database conventions, etc. ' +
|
|
13
|
-
'Use taiga_get_skill with the skill ID to read the full content.',
|
|
14
|
-
{
|
|
15
|
-
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
16
|
-
},
|
|
17
|
-
{ readOnlyHint: true, openWorldHint: false },
|
|
18
|
-
async (params) => {
|
|
19
|
-
const result = await client.listSkills(params);
|
|
20
|
-
const summaries = summarizeList(result.data as Record<string, unknown>[]);
|
|
21
|
-
return {
|
|
22
|
-
content: [{ type: 'text' as const, text: JSON.stringify(summaries, null, 2) }],
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
server.tool(
|
|
28
|
-
'taiga_get_skill',
|
|
29
|
-
'Get the full content of a specific agent skill. ' +
|
|
30
|
-
'Use this when you need detailed coding guidelines, patterns, or conventions ' +
|
|
31
|
-
'for a specific topic (e.g., security requirements, API design, testing strategy). ' +
|
|
32
|
-
'Returns the complete markdown content with code examples and best practices.',
|
|
33
|
-
{
|
|
34
|
-
id: z.string().uuid().describe('Skill ID -- get from taiga_list_skills'),
|
|
35
|
-
},
|
|
36
|
-
{ readOnlyHint: true, openWorldHint: false },
|
|
37
|
-
async (params) => {
|
|
38
|
-
const result = await client.getSkill(params.id);
|
|
39
|
-
return {
|
|
40
|
-
content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (client.hasCapability('tenant:documents:write')) {
|
|
47
|
-
server.tool(
|
|
48
|
-
'taiga_update_skill',
|
|
49
|
-
'Update an agent skill (coding standards, patterns, conventions). ' +
|
|
50
|
-
'Use this when you notice a skill is outdated, incomplete, or needs corrections ' +
|
|
51
|
-
'while working on the codebase. Changes are saved immediately.',
|
|
52
|
-
{
|
|
53
|
-
id: z.string().uuid().describe('Skill ID -- get from taiga_list_skills'),
|
|
54
|
-
name: z.string().optional().describe('Updated skill name'),
|
|
55
|
-
description: z.string().optional().describe('Updated skill description'),
|
|
56
|
-
content: z.string().optional().describe('Updated skill content (markdown)'),
|
|
57
|
-
},
|
|
58
|
-
{ readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
59
|
-
async (params) => {
|
|
60
|
-
const { id, ...body } = params;
|
|
61
|
-
const result = await client.updateSkill(id, body);
|
|
62
|
-
return {
|
|
63
|
-
content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
Binary file
|