@taiga-ai/mcp-server 0.0.1-rc.69 → 0.0.1-rc.71
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/client.d.ts +0 -36
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +0 -102
- package/dist/index.js +1 -30
- package/dist/server.integration.test.js +56 -66
- package/dist/tools/backlogs.d.ts +1 -1
- package/dist/tools/backlogs.d.ts.map +1 -1
- package/dist/tools/backlogs.js +32 -5
- package/dist/tools/documents.d.ts.map +1 -1
- package/dist/tools/documents.js +12 -74
- package/dist/tools/index.d.ts +0 -3
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +0 -3
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +15 -46
- package/dist/tools/skills.d.ts.map +1 -1
- package/dist/tools/skills.js +10 -30
- package/package.json +2 -2
- package/src/client.ts +0 -130
- package/src/index.ts +5 -37
- package/src/server.integration.test.ts +56 -68
- package/src/tools/backlogs.ts +49 -6
- package/src/tools/documents.ts +9 -101
- package/src/tools/index.ts +0 -3
- package/src/tools/projects.ts +14 -57
- package/src/tools/skills.ts +8 -37
- package/dist/tools/summarize.d.ts +0 -15
- package/dist/tools/summarize.d.ts.map +0 -1
- package/dist/tools/summarize.js +0 -26
- package/src/tools/summarize.ts +0 -30
package/dist/client.d.ts
CHANGED
|
@@ -40,29 +40,6 @@ export declare class TaigaClient {
|
|
|
40
40
|
}): Promise<{
|
|
41
41
|
data: unknown;
|
|
42
42
|
}>;
|
|
43
|
-
listTeamDocuments(teamId: string, opts?: {
|
|
44
|
-
limit?: number;
|
|
45
|
-
}): Promise<{
|
|
46
|
-
data: unknown[];
|
|
47
|
-
nextCursor: string | null;
|
|
48
|
-
}>;
|
|
49
|
-
getTeamDocument(teamId: string, type: string): Promise<{
|
|
50
|
-
data: unknown;
|
|
51
|
-
}>;
|
|
52
|
-
saveTeamDocumentDraft(teamId: string, type: string, body: {
|
|
53
|
-
data: Record<string, unknown>;
|
|
54
|
-
}): Promise<{
|
|
55
|
-
data: unknown;
|
|
56
|
-
}>;
|
|
57
|
-
listProjectDocuments(projectId: string, opts?: {
|
|
58
|
-
limit?: number;
|
|
59
|
-
}): Promise<{
|
|
60
|
-
data: unknown[];
|
|
61
|
-
nextCursor: string | null;
|
|
62
|
-
}>;
|
|
63
|
-
getProjectDocument(projectId: string, type: string): Promise<{
|
|
64
|
-
data: unknown;
|
|
65
|
-
}>;
|
|
66
43
|
listSkills(opts?: {
|
|
67
44
|
scope?: string;
|
|
68
45
|
scopeId?: string;
|
|
@@ -74,19 +51,6 @@ export declare class TaigaClient {
|
|
|
74
51
|
getSkill(id: string): Promise<{
|
|
75
52
|
data: unknown;
|
|
76
53
|
}>;
|
|
77
|
-
updateSkill(id: string, body: {
|
|
78
|
-
name?: string;
|
|
79
|
-
description?: string;
|
|
80
|
-
content?: string;
|
|
81
|
-
}): Promise<{
|
|
82
|
-
data: unknown;
|
|
83
|
-
}>;
|
|
84
|
-
/**
|
|
85
|
-
* Fetch the full context for a project in parallel: project metadata,
|
|
86
|
-
* team documents, project documents, and team skills.
|
|
87
|
-
* Returns a structured bundle optimized for coding agent consumption.
|
|
88
|
-
*/
|
|
89
|
-
getProjectContext(projectId: string): Promise<Record<string, unknown>>;
|
|
90
54
|
listBacklogItems(projectId: string, opts?: {
|
|
91
55
|
limit?: number;
|
|
92
56
|
}): Promise<{
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAoC;gBAE7C,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;YAKvC,OAAO;IA0Bf,UAAU,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAMhD,gBAAgB,IAAI,mBAAmB,GAAG,IAAI;IAI9C,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IASpC,YAAY,CAAC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;cAMhD,OAAO,EAAE;oBAAc,MAAM,GAAG,IAAI;;IAG5D,UAAU,CAAC,EAAE,EAAE,MAAM;cACG,OAAO;;IAO/B,mBAAmB,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;cAIrB,OAAO,EAAE;oBAAc,MAAM,GAAG,IAAI;;IAM5D,iBAAiB,CAAC,IAAI,EAAE,MAAM;cACN,OAAO;;IAG/B,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE;cACrD,OAAO;;IAO/B,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAoC;gBAE7C,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;YAKvC,OAAO;IA0Bf,UAAU,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAMhD,gBAAgB,IAAI,mBAAmB,GAAG,IAAI;IAI9C,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IASpC,YAAY,CAAC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;cAMhD,OAAO,EAAE;oBAAc,MAAM,GAAG,IAAI;;IAG5D,UAAU,CAAC,EAAE,EAAE,MAAM;cACG,OAAO;;IAO/B,mBAAmB,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;cAIrB,OAAO,EAAE;oBAAc,MAAM,GAAG,IAAI;;IAM5D,iBAAiB,CAAC,IAAI,EAAE,MAAM;cACN,OAAO;;IAG/B,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE;cACrD,OAAO;;IAO/B,UAAU,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;cAM9C,OAAO,EAAE;oBAAc,MAAM,GAAG,IAAI;;IAG5D,QAAQ,CAAC,EAAE,EAAE,MAAM;cACK,OAAO;;IAO/B,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;cAIrC,OAAO,EAAE;oBAAc,MAAM,GAAG,IAAI;;IAM5D,cAAc,CAAC,MAAM,EAAE,MAAM;cACL,OAAO;;IAO/B,gBAAgB,CAAC,SAAS,EAAE,MAAM;cACV,OAAO,EAAE;;IAOjC,SAAS,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;cAIX,OAAO,EAAE;oBAAc,MAAM,GAAG,IAAI;;CAEnE"}
|
package/dist/client.js
CHANGED
|
@@ -78,35 +78,6 @@ export class TaigaClient {
|
|
|
78
78
|
return this.request('PUT', `/tenants/current/documents/${type}/draft`, body);
|
|
79
79
|
}
|
|
80
80
|
// ---------------------------------------------------------------------------
|
|
81
|
-
// Team Documents
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
async listTeamDocuments(teamId, opts) {
|
|
84
|
-
const params = new URLSearchParams();
|
|
85
|
-
if (opts?.limit)
|
|
86
|
-
params.set('limit', String(opts.limit));
|
|
87
|
-
const qs = params.toString();
|
|
88
|
-
return this.request('GET', `/teams/${teamId}/documents${qs ? `?${qs}` : ''}`);
|
|
89
|
-
}
|
|
90
|
-
async getTeamDocument(teamId, type) {
|
|
91
|
-
return this.request('GET', `/teams/${teamId}/documents/${type}`);
|
|
92
|
-
}
|
|
93
|
-
async saveTeamDocumentDraft(teamId, type, body) {
|
|
94
|
-
return this.request('PUT', `/teams/${teamId}/documents/${type}/draft`, body);
|
|
95
|
-
}
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Project Documents
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
async listProjectDocuments(projectId, opts) {
|
|
100
|
-
const params = new URLSearchParams();
|
|
101
|
-
if (opts?.limit)
|
|
102
|
-
params.set('limit', String(opts.limit));
|
|
103
|
-
const qs = params.toString();
|
|
104
|
-
return this.request('GET', `/projects/${projectId}/documents${qs ? `?${qs}` : ''}`);
|
|
105
|
-
}
|
|
106
|
-
async getProjectDocument(projectId, type) {
|
|
107
|
-
return this.request('GET', `/projects/${projectId}/documents/${type}`);
|
|
108
|
-
}
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
81
|
// Skills (agent skills)
|
|
111
82
|
// ---------------------------------------------------------------------------
|
|
112
83
|
async listSkills(opts) {
|
|
@@ -123,79 +94,6 @@ export class TaigaClient {
|
|
|
123
94
|
async getSkill(id) {
|
|
124
95
|
return this.request('GET', `/agent-skills/${id}`);
|
|
125
96
|
}
|
|
126
|
-
async updateSkill(id, body) {
|
|
127
|
-
return this.request('PATCH', `/agent-skills/${id}`, body);
|
|
128
|
-
}
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
// Team Documents
|
|
131
|
-
// ---------------------------------------------------------------------------
|
|
132
|
-
/**
|
|
133
|
-
* Fetch the full context for a project in parallel: project metadata,
|
|
134
|
-
* team documents, project documents, and team skills.
|
|
135
|
-
* Returns a structured bundle optimized for coding agent consumption.
|
|
136
|
-
*/
|
|
137
|
-
async getProjectContext(projectId) {
|
|
138
|
-
// First fetch the project to get the teamId
|
|
139
|
-
const project = await this.request('GET', `/projects/${projectId}`);
|
|
140
|
-
const teamId = project.data.teamId;
|
|
141
|
-
// Fetch everything in parallel
|
|
142
|
-
const [projectDocs, teamDocs, skills] = await Promise.all([
|
|
143
|
-
this.listProjectDocuments(projectId, { limit: 20 }).catch(() => ({ data: [] })),
|
|
144
|
-
teamId
|
|
145
|
-
? this.listTeamDocuments(teamId, { limit: 20 }).catch(() => ({ data: [] }))
|
|
146
|
-
: Promise.resolve({ data: [] }),
|
|
147
|
-
this.listSkills({ limit: 100 }).catch(() => ({ data: [] })),
|
|
148
|
-
]);
|
|
149
|
-
// Fetch full content for key documents (in parallel)
|
|
150
|
-
const keyProjectDocTypes = ['specification', 'architecture', 'technology_decisions'];
|
|
151
|
-
const keyTeamDocTypes = ['domain_context', 'tech_preferences', 'architecture_constraints'];
|
|
152
|
-
const docFetches = [];
|
|
153
|
-
for (const docType of keyProjectDocTypes) {
|
|
154
|
-
const exists = projectDocs.data.some((d) => d.type === docType && !d.isDraft);
|
|
155
|
-
if (exists) {
|
|
156
|
-
docFetches.push(this.getProjectDocument(projectId, docType)
|
|
157
|
-
.then((r) => ({ type: docType, scope: 'project', data: r.data }))
|
|
158
|
-
.catch(() => ({ type: docType, scope: 'project', data: null })));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (teamId) {
|
|
162
|
-
for (const docType of keyTeamDocTypes) {
|
|
163
|
-
const exists = teamDocs.data.some((d) => d.type === docType && !d.isDraft);
|
|
164
|
-
if (exists) {
|
|
165
|
-
docFetches.push(this.getTeamDocument(teamId, docType)
|
|
166
|
-
.then((r) => ({ type: docType, scope: 'team', data: r.data }))
|
|
167
|
-
.catch(() => ({ type: docType, scope: 'team', data: null })));
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
const fetchedDocs = await Promise.all(docFetches);
|
|
172
|
-
// Assemble context bundle
|
|
173
|
-
const context = {
|
|
174
|
-
project: project.data,
|
|
175
|
-
documents: {
|
|
176
|
-
project: Object.fromEntries(fetchedDocs.filter((d) => d.scope === 'project' && d.data).map((d) => [d.type, d.data])),
|
|
177
|
-
team: Object.fromEntries(fetchedDocs.filter((d) => d.scope === 'team' && d.data).map((d) => [d.type, d.data])),
|
|
178
|
-
},
|
|
179
|
-
available_documents: {
|
|
180
|
-
project: projectDocs.data.map((d) => ({
|
|
181
|
-
type: d.type,
|
|
182
|
-
name: d.name,
|
|
183
|
-
version: d.version,
|
|
184
|
-
isDraft: d.isDraft,
|
|
185
|
-
})),
|
|
186
|
-
team: teamDocs.data.map((d) => ({
|
|
187
|
-
type: d.type,
|
|
188
|
-
name: d.name,
|
|
189
|
-
version: d.version,
|
|
190
|
-
isDraft: d.isDraft,
|
|
191
|
-
})),
|
|
192
|
-
},
|
|
193
|
-
skills_summary: skills.data
|
|
194
|
-
.filter((s) => !s.teamId || s.teamId === teamId)
|
|
195
|
-
.map((s) => ({ id: s.id, name: s.name, slug: s.slug, description: s.description })),
|
|
196
|
-
};
|
|
197
|
-
return context;
|
|
198
|
-
}
|
|
199
97
|
// ---------------------------------------------------------------------------
|
|
200
98
|
// Backlog
|
|
201
99
|
// ---------------------------------------------------------------------------
|
package/dist/index.js
CHANGED
|
@@ -20,39 +20,10 @@ async function main() {
|
|
|
20
20
|
console.error('Check that your TAIGA_API_KEY is valid and not expired.');
|
|
21
21
|
process.exit(1);
|
|
22
22
|
});
|
|
23
|
-
// Create MCP server with dynamic tool registration
|
|
23
|
+
// Create MCP server with dynamic tool registration
|
|
24
24
|
const server = new McpServer({
|
|
25
25
|
name: 'taiga',
|
|
26
26
|
version: '0.0.1',
|
|
27
|
-
}, {
|
|
28
|
-
instructions: [
|
|
29
|
-
'You have access to Taiga -- a policy-aware software development platform.',
|
|
30
|
-
"Taiga provides your organization's engineering knowledge base:",
|
|
31
|
-
'',
|
|
32
|
-
'**Company policies** (taiga_list_documents / taiga_get_document):',
|
|
33
|
-
'Security, data privacy, SDLC, architecture, and compliance rules that apply to all code.',
|
|
34
|
-
'Read the relevant policy BEFORE writing code that touches security, data handling, or infrastructure.',
|
|
35
|
-
'',
|
|
36
|
-
'**Team documents** (taiga_list_team_documents / taiga_get_team_document):',
|
|
37
|
-
'Domain context (ubiquitous language, entities, business rules), tech preferences (approved stack, patterns),',
|
|
38
|
-
'architecture constraints, and SLO targets. Read these to understand HOW this team builds software.',
|
|
39
|
-
'',
|
|
40
|
-
'**Project documents** (taiga_list_project_documents / taiga_get_project_document):',
|
|
41
|
-
"Specification (what to build), architecture (how it's designed), threat model (security risks),",
|
|
42
|
-
'technology decisions, and data flow. Read these to understand the specific project context.',
|
|
43
|
-
'',
|
|
44
|
-
'**Skills** (taiga_list_skills / taiga_get_skill):',
|
|
45
|
-
'Coding standards, security requirements, API design patterns, testing strategy, database patterns,',
|
|
46
|
-
'and more. These are detailed engineering guidelines with code examples.',
|
|
47
|
-
'',
|
|
48
|
-
"Workflow: Start with taiga_list_teams to find your team, then read the team's domain_context",
|
|
49
|
-
'and tech_preferences. For project-specific work, use taiga_list_projects to find the project,',
|
|
50
|
-
'then read its specification and architecture. Use skills for coding patterns.',
|
|
51
|
-
'',
|
|
52
|
-
'When you notice a policy, skill, or team document is outdated, use the update tools',
|
|
53
|
-
'(taiga_update_document, taiga_update_skill, taiga_update_team_document) to fix it.',
|
|
54
|
-
'This keeps the knowledge base accurate for everyone.',
|
|
55
|
-
].join('\n'),
|
|
56
27
|
});
|
|
57
28
|
registerAllTools(server, client);
|
|
58
29
|
const toolCount = introspection.capabilities.length;
|
|
@@ -17,19 +17,17 @@ const ALL_CAPABILITIES = [
|
|
|
17
17
|
];
|
|
18
18
|
/** All tool names the server can register (sorted for deterministic comparison). */
|
|
19
19
|
const ALL_TOOL_NAMES = [
|
|
20
|
+
'taiga_get_backlog_item',
|
|
20
21
|
'taiga_get_document',
|
|
21
|
-
'
|
|
22
|
+
'taiga_get_project',
|
|
22
23
|
'taiga_get_skill',
|
|
23
|
-
'
|
|
24
|
+
'taiga_list_backlog_items',
|
|
24
25
|
'taiga_list_documents',
|
|
25
|
-
'
|
|
26
|
+
'taiga_list_environments',
|
|
26
27
|
'taiga_list_projects',
|
|
27
28
|
'taiga_list_skills',
|
|
28
|
-
'taiga_list_team_documents',
|
|
29
29
|
'taiga_list_teams',
|
|
30
30
|
'taiga_update_document',
|
|
31
|
-
'taiga_update_skill',
|
|
32
|
-
'taiga_update_team_document',
|
|
33
31
|
];
|
|
34
32
|
function makeFetchRouter(routes) {
|
|
35
33
|
return vi.fn().mockImplementation(async (url, init) => {
|
|
@@ -112,7 +110,7 @@ describe('MCP server integration', () => {
|
|
|
112
110
|
// the API key's capabilities. This is the primary security contract.
|
|
113
111
|
// -------------------------------------------------------------------------
|
|
114
112
|
describe('tool discovery', () => {
|
|
115
|
-
it('should expose all
|
|
113
|
+
it('should expose all 11 tools when API key has full capabilities', async () => {
|
|
116
114
|
const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
|
|
117
115
|
const { tools } = await client.listTools();
|
|
118
116
|
expect(toolNames(tools)).toEqual(ALL_TOOL_NAMES);
|
|
@@ -124,15 +122,15 @@ describe('MCP server integration', () => {
|
|
|
124
122
|
const { client } = await bootMcpPair({ capabilities: [] });
|
|
125
123
|
await expect(client.listTools()).rejects.toThrow(/Method not found/);
|
|
126
124
|
});
|
|
127
|
-
it('should expose only
|
|
128
|
-
const { client } = await bootMcpPair({ capabilities: ['
|
|
125
|
+
it('should expose only project tools for a projects:read-only key', async () => {
|
|
126
|
+
const { client } = await bootMcpPair({ capabilities: ['projects:read'] });
|
|
129
127
|
const { tools } = await client.listTools();
|
|
130
128
|
const names = toolNames(tools);
|
|
131
|
-
expect(names).toContain('
|
|
132
|
-
expect(names).toContain('
|
|
133
|
-
expect(names).toContain('
|
|
129
|
+
expect(names).toContain('taiga_list_projects');
|
|
130
|
+
expect(names).toContain('taiga_get_project');
|
|
131
|
+
expect(names).toContain('taiga_list_environments');
|
|
132
|
+
expect(names).not.toContain('taiga_list_teams');
|
|
134
133
|
expect(names).not.toContain('taiga_list_documents');
|
|
135
|
-
expect(names).not.toContain('taiga_update_document');
|
|
136
134
|
});
|
|
137
135
|
it('should expose read-only document tools without write capability', async () => {
|
|
138
136
|
const { client } = await bootMcpPair({ capabilities: ['tenant:documents:read'] });
|
|
@@ -144,39 +142,35 @@ describe('MCP server integration', () => {
|
|
|
144
142
|
expect(names).toContain('taiga_get_skill');
|
|
145
143
|
expect(names).not.toContain('taiga_update_document');
|
|
146
144
|
});
|
|
147
|
-
it('should expose
|
|
145
|
+
it('should expose update tool when documents:write is also granted', async () => {
|
|
148
146
|
const { client } = await bootMcpPair({
|
|
149
147
|
capabilities: ['tenant:documents:read', 'tenant:documents:write'],
|
|
150
148
|
});
|
|
151
149
|
const { tools } = await client.listTools();
|
|
152
|
-
|
|
153
|
-
expect(names).toContain('taiga_update_document');
|
|
154
|
-
expect(names).toContain('taiga_update_skill');
|
|
150
|
+
expect(toolNames(tools)).toContain('taiga_update_document');
|
|
155
151
|
});
|
|
156
|
-
it('should expose
|
|
157
|
-
const { client } = await bootMcpPair({
|
|
158
|
-
capabilities: ['teams:read', 'tenant:documents:write'],
|
|
159
|
-
});
|
|
152
|
+
it('should expose backlog tools only with backlog:read', async () => {
|
|
153
|
+
const { client } = await bootMcpPair({ capabilities: ['backlog:read'] });
|
|
160
154
|
const { tools } = await client.listTools();
|
|
161
155
|
const names = toolNames(tools);
|
|
162
|
-
expect(names).toContain('
|
|
163
|
-
expect(names).toContain('
|
|
156
|
+
expect(names).toContain('taiga_list_backlog_items');
|
|
157
|
+
expect(names).toContain('taiga_get_backlog_item');
|
|
158
|
+
expect(names).not.toContain('taiga_list_environments');
|
|
164
159
|
});
|
|
165
160
|
it('should combine capabilities additively', async () => {
|
|
166
161
|
const { client } = await bootMcpPair({
|
|
167
|
-
capabilities: ['
|
|
162
|
+
capabilities: ['projects:read', 'backlog:read'],
|
|
168
163
|
});
|
|
169
164
|
const { tools } = await client.listTools();
|
|
170
165
|
const names = toolNames(tools);
|
|
171
|
-
//
|
|
172
|
-
expect(names).toContain('
|
|
173
|
-
expect(names).toContain('
|
|
174
|
-
//
|
|
175
|
-
expect(names).toContain('
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
expect(names).not.toContain('
|
|
179
|
-
expect(names).not.toContain('taiga_update_skill');
|
|
166
|
+
// projects:read tools present
|
|
167
|
+
expect(names).toContain('taiga_list_projects');
|
|
168
|
+
expect(names).toContain('taiga_list_environments');
|
|
169
|
+
// backlog:read tools present
|
|
170
|
+
expect(names).toContain('taiga_list_backlog_items');
|
|
171
|
+
// unrelated capabilities absent
|
|
172
|
+
expect(names).not.toContain('taiga_list_documents');
|
|
173
|
+
expect(names).not.toContain('taiga_list_teams');
|
|
180
174
|
});
|
|
181
175
|
it('should provide a description and input schema for every tool', async () => {
|
|
182
176
|
const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
|
|
@@ -195,10 +189,11 @@ describe('MCP server integration', () => {
|
|
|
195
189
|
// -------------------------------------------------------------------------
|
|
196
190
|
describe('capability-tool contract', () => {
|
|
197
191
|
const CONTRACT = {
|
|
198
|
-
'projects:read': ['taiga_list_projects', '
|
|
199
|
-
'teams:read': ['taiga_list_teams'
|
|
192
|
+
'projects:read': ['taiga_list_projects', 'taiga_get_project', 'taiga_list_environments'],
|
|
193
|
+
'teams:read': ['taiga_list_teams'],
|
|
200
194
|
'tenant:documents:read': ['taiga_list_documents', 'taiga_get_document', 'taiga_list_skills', 'taiga_get_skill'],
|
|
201
|
-
'tenant:documents:write': ['taiga_update_document'
|
|
195
|
+
'tenant:documents:write': ['taiga_update_document'],
|
|
196
|
+
'backlog:read': ['taiga_list_backlog_items', 'taiga_get_backlog_item'],
|
|
202
197
|
};
|
|
203
198
|
for (const [capability, expectedTools] of Object.entries(CONTRACT)) {
|
|
204
199
|
it(`${capability} should gate: ${expectedTools.join(', ')}`, async () => {
|
|
@@ -217,33 +212,31 @@ describe('MCP server integration', () => {
|
|
|
217
212
|
// -> (mocked) API -> response back through MCP protocol.
|
|
218
213
|
// -------------------------------------------------------------------------
|
|
219
214
|
describe('tool execution', () => {
|
|
220
|
-
it('should return
|
|
221
|
-
const
|
|
222
|
-
{ id: '
|
|
223
|
-
{ id: '
|
|
215
|
+
it('should return project list as JSON text content', async () => {
|
|
216
|
+
const projects = [
|
|
217
|
+
{ id: 'p1', name: 'Alpha' },
|
|
218
|
+
{ id: 'p2', name: 'Beta' },
|
|
224
219
|
];
|
|
225
220
|
const { client } = await bootMcpPair({
|
|
226
221
|
capabilities: ALL_CAPABILITIES,
|
|
227
|
-
routes: { 'GET /v1/
|
|
222
|
+
routes: { 'GET /v1/projects': { data: projects, nextCursor: null } },
|
|
228
223
|
});
|
|
229
|
-
const result = await client.callTool({ name: '
|
|
224
|
+
const result = await client.callTool({ name: 'taiga_list_projects', arguments: {} });
|
|
230
225
|
expect(result.content).toHaveLength(1);
|
|
231
|
-
expect(parseToolResult(result)).toEqual(
|
|
226
|
+
expect(parseToolResult(result)).toEqual(projects);
|
|
232
227
|
});
|
|
233
|
-
it('should
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
const { client
|
|
228
|
+
it('should pass project id through to the API', async () => {
|
|
229
|
+
const projectId = '00000000-0000-0000-0000-000000000001';
|
|
230
|
+
const project = { id: projectId, name: 'Alpha', status: 'active' };
|
|
231
|
+
const { client } = await bootMcpPair({
|
|
237
232
|
capabilities: ALL_CAPABILITIES,
|
|
238
|
-
routes: { [`
|
|
233
|
+
routes: { [`GET /v1/projects/${projectId}`]: { data: project } },
|
|
239
234
|
});
|
|
240
|
-
await client.callTool({
|
|
241
|
-
name: '
|
|
242
|
-
arguments: { id:
|
|
235
|
+
const result = await client.callTool({
|
|
236
|
+
name: 'taiga_get_project',
|
|
237
|
+
arguments: { id: projectId },
|
|
243
238
|
});
|
|
244
|
-
|
|
245
|
-
expect(patchCall).toBeDefined();
|
|
246
|
-
expect(patchCall[0]).toContain(`/v1/agent-skills/${skillId}`);
|
|
239
|
+
expect(parseToolResult(result)).toEqual(project);
|
|
247
240
|
});
|
|
248
241
|
it('should return document content through MCP protocol', async () => {
|
|
249
242
|
const docs = [{ id: 'd1', title: 'Security Policy' }];
|
|
@@ -269,21 +262,18 @@ describe('MCP server integration', () => {
|
|
|
269
262
|
expect(putCall).toBeDefined();
|
|
270
263
|
expect(putCall[0]).toContain(`/v1/tenants/current/documents/${docType}/draft`);
|
|
271
264
|
});
|
|
272
|
-
it('should
|
|
273
|
-
const
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
const { client, fetchSpy } = await bootMcpPair({
|
|
265
|
+
it('should pass projectId to backlog items endpoint', async () => {
|
|
266
|
+
const projectId = '00000000-0000-0000-0000-000000000001';
|
|
267
|
+
const items = [{ id: 'bi1', name: 'Feature X' }];
|
|
268
|
+
const { client } = await bootMcpPair({
|
|
277
269
|
capabilities: ALL_CAPABILITIES,
|
|
278
|
-
routes: { [`
|
|
270
|
+
routes: { [`GET /v1/projects/${projectId}/backlog`]: { data: items, nextCursor: null } },
|
|
279
271
|
});
|
|
280
|
-
await client.callTool({
|
|
281
|
-
name: '
|
|
282
|
-
arguments: {
|
|
272
|
+
const result = await client.callTool({
|
|
273
|
+
name: 'taiga_list_backlog_items',
|
|
274
|
+
arguments: { projectId },
|
|
283
275
|
});
|
|
284
|
-
|
|
285
|
-
expect(putCall).toBeDefined();
|
|
286
|
-
expect(putCall[0]).toContain(`/v1/teams/${teamId}/documents/${docType}/draft`);
|
|
276
|
+
expect(parseToolResult(result)).toEqual(items);
|
|
287
277
|
});
|
|
288
278
|
it('should return skills through MCP protocol', async () => {
|
|
289
279
|
const skills = [{ id: 's1', name: 'React Standards' }];
|
package/dist/tools/backlogs.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { TaigaClient } from '../client.js';
|
|
3
|
-
export declare function registerBacklogTools(
|
|
3
|
+
export declare function registerBacklogTools(server: McpServer, client: TaigaClient): void;
|
|
4
4
|
//# sourceMappingURL=backlogs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backlogs.d.ts","sourceRoot":"","sources":["../../src/tools/backlogs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"backlogs.d.ts","sourceRoot":"","sources":["../../src/tools/backlogs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,QAgD1E"}
|
package/dist/tools/backlogs.js
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerBacklogTools(server, client) {
|
|
3
|
+
if (client.hasCapability('backlog:read')) {
|
|
4
|
+
server.tool('taiga_list_backlog_items', 'List backlog items (features, tasks, stories) for a project. ' +
|
|
5
|
+
'Returns item name, status, planning status, and priority.', {
|
|
6
|
+
projectId: z.string().uuid().describe('Project ID'),
|
|
7
|
+
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
8
|
+
}, async (params) => {
|
|
9
|
+
const result = await client.listBacklogItems(params.projectId, { limit: params.limit });
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
server.tool('taiga_get_backlog_item', 'Get detailed information about a specific backlog item, including its active plan and steps.', {
|
|
15
|
+
itemId: z.string().uuid().describe('Backlog item ID'),
|
|
16
|
+
}, async (params) => {
|
|
17
|
+
const result = await client.getBacklogItem(params.itemId);
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (client.hasCapability('projects:read')) {
|
|
24
|
+
server.tool('taiga_list_environments', 'List deployment environments (dev, staging, production) for a project.', {
|
|
25
|
+
projectId: z.string().uuid().describe('Project ID'),
|
|
26
|
+
}, async (params) => {
|
|
27
|
+
const result = await client.listEnvironments(params.projectId);
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
6
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/tools/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/tools/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,QAkD3E"}
|
package/dist/tools/documents.js
CHANGED
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { summarizeList } from './summarize.js';
|
|
3
2
|
export function registerDocumentTools(server, client) {
|
|
4
|
-
// ── Company-level policy documents ──────────────────────────────────
|
|
5
3
|
if (client.hasCapability('tenant:documents:read')) {
|
|
6
|
-
server.tool('taiga_list_documents', 'List company-level policy documents (
|
|
7
|
-
'
|
|
8
|
-
'that apply across all teams and projects. ' +
|
|
9
|
-
'Use taiga_get_document with the document type to read the full policy content.', {
|
|
4
|
+
server.tool('taiga_list_documents', 'List company-level policy documents (security, data privacy, SDLC, etc). ' +
|
|
5
|
+
'These are the governance documents that guide all projects.', {
|
|
10
6
|
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
11
|
-
},
|
|
7
|
+
}, async (params) => {
|
|
12
8
|
const result = await client.listTenantDocuments(params);
|
|
13
|
-
const summaries = summarizeList(result.data);
|
|
14
9
|
return {
|
|
15
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
10
|
+
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
16
11
|
};
|
|
17
12
|
});
|
|
18
|
-
server.tool('taiga_get_document', 'Get the full content of a
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
|
|
22
|
-
'policy_risk, policy_architecture, policy_supply_chain, policy_acceptable_use, policy_ip.', {
|
|
23
|
-
type: z
|
|
24
|
-
.string()
|
|
25
|
-
.describe('Document type -- get available types from taiga_list_documents. ' +
|
|
26
|
-
'Common: policy_security, policy_data, policy_sdlc, policy_architecture'),
|
|
27
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
13
|
+
server.tool('taiga_get_document', 'Get the full content of a specific policy document by type. ' +
|
|
14
|
+
'Common types: security_policy, data_privacy_policy, sdlc_policy, architecture_standards.', {
|
|
15
|
+
type: z.string().describe('Document type (e.g. security_policy, sdlc_policy)'),
|
|
16
|
+
}, async (params) => {
|
|
28
17
|
const result = await client.getTenantDocument(params.type);
|
|
29
18
|
return {
|
|
30
19
|
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
@@ -32,12 +21,10 @@ export function registerDocumentTools(server, client) {
|
|
|
32
21
|
});
|
|
33
22
|
}
|
|
34
23
|
if (client.hasCapability('tenant:documents:write')) {
|
|
35
|
-
server.tool('taiga_update_document', 'Update the draft content of a
|
|
36
|
-
'
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
data: z.record(z.string(), z.unknown()).describe('Document content to save as JSON'),
|
|
40
|
-
}, { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async (params) => {
|
|
24
|
+
server.tool('taiga_update_document', 'Update the draft content of a policy document. Only updates the draft -- does not publish.', {
|
|
25
|
+
type: z.string().describe('Document type (e.g. security_policy)'),
|
|
26
|
+
data: z.record(z.string(), z.unknown()).describe('Document content to save'),
|
|
27
|
+
}, async (params) => {
|
|
41
28
|
const { type, ...body } = params;
|
|
42
29
|
const result = await client.saveTenantDocumentDraft(type, body);
|
|
43
30
|
return {
|
|
@@ -45,53 +32,4 @@ export function registerDocumentTools(server, client) {
|
|
|
45
32
|
};
|
|
46
33
|
});
|
|
47
34
|
}
|
|
48
|
-
// ── Team documents ──────────────────────────────────────────────────
|
|
49
|
-
if (client.hasCapability('teams:read')) {
|
|
50
|
-
server.tool('taiga_list_team_documents', 'List documents for a specific team (names and types only). ' +
|
|
51
|
-
'Team documents define how the team works: domain context (entities, ubiquitous language), ' +
|
|
52
|
-
'tech preferences (approved stack), architecture constraints, SLO targets, ' +
|
|
53
|
-
'definition of done, and team charter. ' +
|
|
54
|
-
'Use taiga_get_team_document to read the full content.', {
|
|
55
|
-
teamId: z.string().uuid().describe('Team ID -- get from taiga_list_teams'),
|
|
56
|
-
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
57
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
58
|
-
const result = await client.listTeamDocuments(params.teamId, { limit: params.limit });
|
|
59
|
-
const summaries = summarizeList(result.data);
|
|
60
|
-
return {
|
|
61
|
-
content: [{ type: 'text', text: JSON.stringify(summaries, null, 2) }],
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
server.tool('taiga_get_team_document', 'Get the full content of a specific team document. ' +
|
|
65
|
-
'Use this when you need the team domain context (ubiquitous language, entities, business rules), ' +
|
|
66
|
-
'tech preferences (approved stack, patterns), or architecture constraints. ' +
|
|
67
|
-
'Types: team_charter, definition_of_done, tech_preferences, ' +
|
|
68
|
-
'architecture_constraints, slo_targets, domain_context.', {
|
|
69
|
-
teamId: z.string().uuid().describe('Team ID -- get from taiga_list_teams'),
|
|
70
|
-
type: z
|
|
71
|
-
.string()
|
|
72
|
-
.describe('Document type -- get available types from taiga_list_team_documents. ' +
|
|
73
|
-
'Most useful: domain_context (entities, language), tech_preferences (stack, patterns)'),
|
|
74
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
75
|
-
const result = await client.getTeamDocument(params.teamId, params.type);
|
|
76
|
-
return {
|
|
77
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
78
|
-
};
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
// Team document write requires both teams:read (to find the team) and tenant:documents:write
|
|
82
|
-
if (client.hasCapability('teams:read') && client.hasCapability('tenant:documents:write')) {
|
|
83
|
-
server.tool('taiga_update_team_document', 'Update the draft content of a team document. ' +
|
|
84
|
-
'Only updates the draft -- does not publish. Use this when you notice team conventions, ' +
|
|
85
|
-
'domain context, or tech preferences need updating while working on the codebase.', {
|
|
86
|
-
teamId: z.string().uuid().describe('Team ID -- get from taiga_list_teams'),
|
|
87
|
-
type: z.string().describe('Document type (e.g. domain_context, tech_preferences)'),
|
|
88
|
-
data: z.record(z.string(), z.unknown()).describe('Document content to save as JSON'),
|
|
89
|
-
}, { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async (params) => {
|
|
90
|
-
const { teamId, type, ...body } = params;
|
|
91
|
-
const result = await client.saveTeamDocumentDraft(teamId, type, body);
|
|
92
|
-
return {
|
|
93
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
94
|
-
};
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
35
|
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -4,9 +4,6 @@ import type { TaigaClient } from '../client.js';
|
|
|
4
4
|
* Register all MCP tools based on the API key's effective capabilities.
|
|
5
5
|
* Only tools matching the key's scopes are registered -- the AI assistant
|
|
6
6
|
* never sees tools it can't use.
|
|
7
|
-
*
|
|
8
|
-
* Beta scope: Knowledge base access (policies, skills, team documents).
|
|
9
|
-
* Post-beta: Projects, backlog items, environments, composite tools.
|
|
10
7
|
*/
|
|
11
8
|
export declare function registerAllTools(server: McpServer, client: TaigaClient): void;
|
|
12
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMhD
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMhD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAK7E"}
|
package/dist/tools/index.js
CHANGED
|
@@ -6,9 +6,6 @@ import { registerSkillTools } from './skills.js';
|
|
|
6
6
|
* Register all MCP tools based on the API key's effective capabilities.
|
|
7
7
|
* Only tools matching the key's scopes are registered -- the AI assistant
|
|
8
8
|
* never sees tools it can't use.
|
|
9
|
-
*
|
|
10
|
-
* Beta scope: Knowledge base access (policies, skills, team documents).
|
|
11
|
-
* Post-beta: Projects, backlog items, environments, composite tools.
|
|
12
9
|
*/
|
|
13
10
|
export function registerAllTools(server, client) {
|
|
14
11
|
registerProjectTools(server, client);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,QAgD1E"}
|