@taiga-ai/mcp-server 0.2.3-dev.255 → 0.2.3-dev.59
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 +495 -62
- package/package.json +8 -6
- package/scripts/build.mjs +37 -0
- package/scripts/prepare-publish.cjs +15 -7
- package/src/client.ts +13 -5
- package/src/index.ts +19 -7
- package/src/server.integration.test.ts +99 -45
- package/src/tools/documents.ts +1 -1
- package/src/tools/index.ts +5 -1
- package/taiga-ai-mcp-server-0.2.3-dev.59.tgz +0 -0
- package/dist/client.d.ts +0 -109
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -228
- package/dist/client.test.d.ts +0 -2
- package/dist/client.test.d.ts.map +0 -1
- package/dist/client.test.js +0 -203
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/server.integration.test.d.ts +0 -2
- package/dist/server.integration.test.d.ts.map +0 -1
- package/dist/server.integration.test.js +0 -358
- package/dist/tools/backlogs.d.ts +0 -4
- package/dist/tools/backlogs.d.ts.map +0 -1
- package/dist/tools/backlogs.js +0 -6
- package/dist/tools/documents.d.ts +0 -4
- package/dist/tools/documents.d.ts.map +0 -1
- package/dist/tools/documents.js +0 -97
- package/dist/tools/index.d.ts +0 -12
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -18
- package/dist/tools/projects.d.ts +0 -4
- package/dist/tools/projects.d.ts.map +0 -1
- package/dist/tools/projects.js +0 -64
- package/dist/tools/skills.d.ts +0 -4
- package/dist/tools/skills.d.ts.map +0 -1
- package/dist/tools/skills.js +0 -45
- 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/taiga-ai-mcp-server-0.2.3-dev.255.tgz +0 -0
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
-
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
5
|
-
import { TaigaClient } from './client.js';
|
|
6
|
-
import { registerAllTools } from './tools/index.js';
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Helpers -- thin wrappers, not abstraction layers
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
/** All capabilities the MCP server understands. */
|
|
11
|
-
const ALL_CAPABILITIES = [
|
|
12
|
-
'projects:read',
|
|
13
|
-
'teams:read',
|
|
14
|
-
'tenant:documents:read',
|
|
15
|
-
'tenant:documents:write',
|
|
16
|
-
'backlog:read',
|
|
17
|
-
];
|
|
18
|
-
/** All tool names the server can register (sorted for deterministic comparison). */
|
|
19
|
-
const ALL_TOOL_NAMES = [
|
|
20
|
-
'taiga_get_document',
|
|
21
|
-
'taiga_get_project_document',
|
|
22
|
-
'taiga_get_skill',
|
|
23
|
-
'taiga_get_team_document',
|
|
24
|
-
'taiga_list_documents',
|
|
25
|
-
'taiga_list_project_documents',
|
|
26
|
-
'taiga_list_projects',
|
|
27
|
-
'taiga_list_skills',
|
|
28
|
-
'taiga_list_team_documents',
|
|
29
|
-
'taiga_list_teams',
|
|
30
|
-
'taiga_update_document',
|
|
31
|
-
'taiga_update_skill',
|
|
32
|
-
'taiga_update_team_document',
|
|
33
|
-
];
|
|
34
|
-
function makeFetchRouter(routes) {
|
|
35
|
-
return vi.fn().mockImplementation(async (url, init) => {
|
|
36
|
-
const path = new URL(url).pathname;
|
|
37
|
-
const method = init?.method ?? 'GET';
|
|
38
|
-
const key = `${method} ${path}`;
|
|
39
|
-
const body = routes[key] ?? routes[path];
|
|
40
|
-
if (body !== undefined) {
|
|
41
|
-
return {
|
|
42
|
-
ok: true,
|
|
43
|
-
status: 200,
|
|
44
|
-
json: () => Promise.resolve(body),
|
|
45
|
-
text: () => Promise.resolve(JSON.stringify(body)),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
ok: false,
|
|
50
|
-
status: 404,
|
|
51
|
-
json: () => Promise.resolve({ error: 'Not found' }),
|
|
52
|
-
text: () => Promise.resolve('Not found'),
|
|
53
|
-
};
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
function introspectionResponse(capabilities) {
|
|
57
|
-
return {
|
|
58
|
-
data: {
|
|
59
|
-
userId: 'user-1',
|
|
60
|
-
tenantId: 'tenant-1',
|
|
61
|
-
email: 'dev@test.com',
|
|
62
|
-
role: 'member',
|
|
63
|
-
capabilities,
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Boots a full MCP Client <-> Server pair connected in-memory.
|
|
69
|
-
* The TaigaClient uses a route-based fetch mock, so no real HTTP is needed.
|
|
70
|
-
* Returns both the MCP client (for protocol calls) and the fetch spy (for
|
|
71
|
-
* verifying outgoing HTTP requests when needed).
|
|
72
|
-
*/
|
|
73
|
-
async function bootMcpPair(opts) {
|
|
74
|
-
const allRoutes = {
|
|
75
|
-
'/v1/auth/introspect': introspectionResponse(opts.capabilities),
|
|
76
|
-
...opts.routes,
|
|
77
|
-
};
|
|
78
|
-
const fetchSpy = makeFetchRouter(allRoutes);
|
|
79
|
-
vi.stubGlobal('fetch', fetchSpy);
|
|
80
|
-
// Arrange -- Taiga client, introspect, register tools
|
|
81
|
-
const taigaClient = new TaigaClient({
|
|
82
|
-
baseUrl: 'https://api.test.com',
|
|
83
|
-
apiKey: 'taiga_sk_live_test',
|
|
84
|
-
});
|
|
85
|
-
await taigaClient.introspect();
|
|
86
|
-
const server = new McpServer({ name: 'taiga-test', version: '0.0.1' });
|
|
87
|
-
registerAllTools(server, taigaClient);
|
|
88
|
-
const client = new Client({ name: 'test-client', version: '0.0.1' });
|
|
89
|
-
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
90
|
-
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
91
|
-
return { client, fetchSpy };
|
|
92
|
-
}
|
|
93
|
-
/** Extract sorted tool names from a listTools response. */
|
|
94
|
-
function toolNames(tools) {
|
|
95
|
-
return tools.map((t) => t.name).sort();
|
|
96
|
-
}
|
|
97
|
-
/** Extract parsed JSON from the first text content block of a tool call result. */
|
|
98
|
-
function parseToolResult(result) {
|
|
99
|
-
const content = result.content;
|
|
100
|
-
return JSON.parse(content[0].text);
|
|
101
|
-
}
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
// Tests
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
describe('MCP server integration', () => {
|
|
106
|
-
beforeEach(() => {
|
|
107
|
-
vi.restoreAllMocks();
|
|
108
|
-
});
|
|
109
|
-
// -------------------------------------------------------------------------
|
|
110
|
-
// Tool discovery -- the AI assistant asks "what tools do you have?"
|
|
111
|
-
// These tests verify the server exposes exactly the right tools based on
|
|
112
|
-
// the API key's capabilities. This is the primary security contract.
|
|
113
|
-
// -------------------------------------------------------------------------
|
|
114
|
-
describe('tool discovery', () => {
|
|
115
|
-
it('should expose all 13 tools when API key has full capabilities', async () => {
|
|
116
|
-
const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
|
|
117
|
-
const { tools } = await client.listTools();
|
|
118
|
-
expect(toolNames(tools)).toEqual(ALL_TOOL_NAMES);
|
|
119
|
-
});
|
|
120
|
-
it('should reject tools/list when API key has no capabilities', async () => {
|
|
121
|
-
// When zero tools are registered, the MCP SDK doesn't advertise
|
|
122
|
-
// the tools capability at all -- the protocol-correct response
|
|
123
|
-
// is "Method not found".
|
|
124
|
-
const { client } = await bootMcpPair({ capabilities: [] });
|
|
125
|
-
await expect(client.listTools()).rejects.toThrow(/Method not found/);
|
|
126
|
-
});
|
|
127
|
-
it('should expose only team tools for a teams:read-only key', async () => {
|
|
128
|
-
const { client } = await bootMcpPair({ capabilities: ['teams:read'] });
|
|
129
|
-
const { tools } = await client.listTools();
|
|
130
|
-
const names = toolNames(tools);
|
|
131
|
-
expect(names).toContain('taiga_list_teams');
|
|
132
|
-
expect(names).toContain('taiga_list_team_documents');
|
|
133
|
-
expect(names).toContain('taiga_get_team_document');
|
|
134
|
-
expect(names).not.toContain('taiga_list_documents');
|
|
135
|
-
expect(names).not.toContain('taiga_update_document');
|
|
136
|
-
});
|
|
137
|
-
it('should expose read-only document tools without write capability', async () => {
|
|
138
|
-
const { client } = await bootMcpPair({ capabilities: ['tenant:documents:read'] });
|
|
139
|
-
const { tools } = await client.listTools();
|
|
140
|
-
const names = toolNames(tools);
|
|
141
|
-
expect(names).toContain('taiga_list_documents');
|
|
142
|
-
expect(names).toContain('taiga_get_document');
|
|
143
|
-
expect(names).toContain('taiga_list_skills');
|
|
144
|
-
expect(names).toContain('taiga_get_skill');
|
|
145
|
-
expect(names).not.toContain('taiga_update_document');
|
|
146
|
-
});
|
|
147
|
-
it('should expose write tools when documents:write is also granted', async () => {
|
|
148
|
-
const { client } = await bootMcpPair({
|
|
149
|
-
capabilities: ['tenant:documents:read', 'tenant:documents:write'],
|
|
150
|
-
});
|
|
151
|
-
const { tools } = await client.listTools();
|
|
152
|
-
const names = toolNames(tools);
|
|
153
|
-
expect(names).toContain('taiga_update_document');
|
|
154
|
-
expect(names).toContain('taiga_update_skill');
|
|
155
|
-
});
|
|
156
|
-
it('should expose team document write when both teams:read and documents:write are granted', async () => {
|
|
157
|
-
const { client } = await bootMcpPair({
|
|
158
|
-
capabilities: ['teams:read', 'tenant:documents:write'],
|
|
159
|
-
});
|
|
160
|
-
const { tools } = await client.listTools();
|
|
161
|
-
const names = toolNames(tools);
|
|
162
|
-
expect(names).toContain('taiga_update_team_document');
|
|
163
|
-
expect(names).toContain('taiga_list_teams');
|
|
164
|
-
});
|
|
165
|
-
it('should combine capabilities additively', async () => {
|
|
166
|
-
const { client } = await bootMcpPair({
|
|
167
|
-
capabilities: ['teams:read', 'tenant:documents:read'],
|
|
168
|
-
});
|
|
169
|
-
const { tools } = await client.listTools();
|
|
170
|
-
const names = toolNames(tools);
|
|
171
|
-
// teams:read tools present
|
|
172
|
-
expect(names).toContain('taiga_list_teams');
|
|
173
|
-
expect(names).toContain('taiga_list_team_documents');
|
|
174
|
-
// tenant:documents:read tools present
|
|
175
|
-
expect(names).toContain('taiga_list_documents');
|
|
176
|
-
expect(names).toContain('taiga_list_skills');
|
|
177
|
-
// write tools absent
|
|
178
|
-
expect(names).not.toContain('taiga_update_document');
|
|
179
|
-
expect(names).not.toContain('taiga_update_skill');
|
|
180
|
-
});
|
|
181
|
-
it('should provide a description and input schema for every tool', async () => {
|
|
182
|
-
const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
|
|
183
|
-
const { tools } = await client.listTools();
|
|
184
|
-
for (const tool of tools) {
|
|
185
|
-
expect(tool.description, `${tool.name} missing description`).toBeTruthy();
|
|
186
|
-
expect(tool.inputSchema, `${tool.name} missing inputSchema`).toBeDefined();
|
|
187
|
-
expect(tool.inputSchema.type).toBe('object');
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
// -------------------------------------------------------------------------
|
|
192
|
-
// Capability-tool contract -- parametric tests proving each capability
|
|
193
|
-
// maps to exactly the right set of tools. If someone adds a tool but
|
|
194
|
-
// forgets the capability check, these tests catch it.
|
|
195
|
-
// -------------------------------------------------------------------------
|
|
196
|
-
describe('capability-tool contract', () => {
|
|
197
|
-
const CONTRACT = {
|
|
198
|
-
'projects:read': ['taiga_list_projects', 'taiga_list_project_documents', 'taiga_get_project_document'],
|
|
199
|
-
'teams:read': ['taiga_list_teams', 'taiga_list_team_documents', 'taiga_get_team_document'],
|
|
200
|
-
'tenant:documents:read': ['taiga_list_documents', 'taiga_get_document', 'taiga_list_skills', 'taiga_get_skill'],
|
|
201
|
-
'tenant:documents:write': ['taiga_update_document', 'taiga_update_skill'],
|
|
202
|
-
};
|
|
203
|
-
for (const [capability, expectedTools] of Object.entries(CONTRACT)) {
|
|
204
|
-
it(`${capability} should gate: ${expectedTools.join(', ')}`, async () => {
|
|
205
|
-
const { client } = await bootMcpPair({ capabilities: [capability] });
|
|
206
|
-
const { tools } = await client.listTools();
|
|
207
|
-
const names = toolNames(tools);
|
|
208
|
-
for (const expected of expectedTools) {
|
|
209
|
-
expect(names, `Expected ${expected} for ${capability}`).toContain(expected);
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
// -------------------------------------------------------------------------
|
|
215
|
-
// Tool execution -- the AI assistant calls a tool and gets data back.
|
|
216
|
-
// These test the full round-trip: MCP call -> tool handler -> TaigaClient
|
|
217
|
-
// -> (mocked) API -> response back through MCP protocol.
|
|
218
|
-
// -------------------------------------------------------------------------
|
|
219
|
-
describe('tool execution', () => {
|
|
220
|
-
it('should return team list as JSON text content', async () => {
|
|
221
|
-
const teams = [
|
|
222
|
-
{ id: 't1', name: 'Product' },
|
|
223
|
-
{ id: 't2', name: 'Platform' },
|
|
224
|
-
];
|
|
225
|
-
const { client } = await bootMcpPair({
|
|
226
|
-
capabilities: ALL_CAPABILITIES,
|
|
227
|
-
routes: { 'GET /v1/teams': { data: teams, nextCursor: null } },
|
|
228
|
-
});
|
|
229
|
-
const result = await client.callTool({ name: 'taiga_list_teams', arguments: {} });
|
|
230
|
-
expect(result.content).toHaveLength(1);
|
|
231
|
-
expect(parseToolResult(result)).toEqual(teams);
|
|
232
|
-
});
|
|
233
|
-
it('should send PATCH when updating a skill', async () => {
|
|
234
|
-
const skillId = '00000000-0000-0000-0000-000000000001';
|
|
235
|
-
const updated = { id: skillId, name: 'Updated Skill', content: '# Updated' };
|
|
236
|
-
const { client, fetchSpy } = await bootMcpPair({
|
|
237
|
-
capabilities: ALL_CAPABILITIES,
|
|
238
|
-
routes: { [`PATCH /v1/agent-skills/${skillId}`]: { data: updated } },
|
|
239
|
-
});
|
|
240
|
-
await client.callTool({
|
|
241
|
-
name: 'taiga_update_skill',
|
|
242
|
-
arguments: { id: skillId, content: '# Updated' },
|
|
243
|
-
});
|
|
244
|
-
const patchCall = fetchSpy.mock.calls.find((c) => c[1]?.method === 'PATCH');
|
|
245
|
-
expect(patchCall).toBeDefined();
|
|
246
|
-
expect(patchCall[0]).toContain(`/v1/agent-skills/${skillId}`);
|
|
247
|
-
});
|
|
248
|
-
it('should return document content through MCP protocol', async () => {
|
|
249
|
-
const docs = [{ id: 'd1', title: 'Security Policy' }];
|
|
250
|
-
const { client } = await bootMcpPair({
|
|
251
|
-
capabilities: ALL_CAPABILITIES,
|
|
252
|
-
routes: { 'GET /v1/tenants/current/documents': { data: docs, nextCursor: null } },
|
|
253
|
-
});
|
|
254
|
-
const result = await client.callTool({ name: 'taiga_list_documents', arguments: {} });
|
|
255
|
-
expect(parseToolResult(result)).toEqual(docs);
|
|
256
|
-
});
|
|
257
|
-
it('should send PUT when updating a document draft', async () => {
|
|
258
|
-
const docType = 'security_policy';
|
|
259
|
-
const updated = { type: docType, title: 'Updated Policy' };
|
|
260
|
-
const { client, fetchSpy } = await bootMcpPair({
|
|
261
|
-
capabilities: ALL_CAPABILITIES,
|
|
262
|
-
routes: { [`PUT /v1/tenants/current/documents/${docType}/draft`]: { data: updated } },
|
|
263
|
-
});
|
|
264
|
-
await client.callTool({
|
|
265
|
-
name: 'taiga_update_document',
|
|
266
|
-
arguments: { type: docType, data: { title: 'Updated' } },
|
|
267
|
-
});
|
|
268
|
-
const putCall = fetchSpy.mock.calls.find((c) => c[1]?.method === 'PUT');
|
|
269
|
-
expect(putCall).toBeDefined();
|
|
270
|
-
expect(putCall[0]).toContain(`/v1/tenants/current/documents/${docType}/draft`);
|
|
271
|
-
});
|
|
272
|
-
it('should send PUT when updating a team document draft', async () => {
|
|
273
|
-
const teamId = '00000000-0000-0000-0000-000000000001';
|
|
274
|
-
const docType = 'domain_context';
|
|
275
|
-
const updated = { type: docType, name: 'Domain Context' };
|
|
276
|
-
const { client, fetchSpy } = await bootMcpPair({
|
|
277
|
-
capabilities: ALL_CAPABILITIES,
|
|
278
|
-
routes: { [`PUT /v1/teams/${teamId}/documents/${docType}/draft`]: { data: updated } },
|
|
279
|
-
});
|
|
280
|
-
await client.callTool({
|
|
281
|
-
name: 'taiga_update_team_document',
|
|
282
|
-
arguments: { teamId, type: docType, data: { sections: { overview: 'Updated' } } },
|
|
283
|
-
});
|
|
284
|
-
const putCall = fetchSpy.mock.calls.find((c) => c[1]?.method === 'PUT' && c[0].includes('/teams/'));
|
|
285
|
-
expect(putCall).toBeDefined();
|
|
286
|
-
expect(putCall[0]).toContain(`/v1/teams/${teamId}/documents/${docType}/draft`);
|
|
287
|
-
});
|
|
288
|
-
it('should return skills through MCP protocol', async () => {
|
|
289
|
-
const skills = [{ id: 's1', name: 'React Standards' }];
|
|
290
|
-
const { client } = await bootMcpPair({
|
|
291
|
-
capabilities: ALL_CAPABILITIES,
|
|
292
|
-
routes: { 'GET /v1/agent-skills': { data: skills, nextCursor: null } },
|
|
293
|
-
});
|
|
294
|
-
const result = await client.callTool({ name: 'taiga_list_skills', arguments: {} });
|
|
295
|
-
expect(parseToolResult(result)).toEqual(skills);
|
|
296
|
-
});
|
|
297
|
-
it('should return teams through MCP protocol', async () => {
|
|
298
|
-
const teams = [{ id: 't1', name: 'Engineering' }];
|
|
299
|
-
const { client } = await bootMcpPair({
|
|
300
|
-
capabilities: ALL_CAPABILITIES,
|
|
301
|
-
routes: { 'GET /v1/teams': { data: teams, nextCursor: null } },
|
|
302
|
-
});
|
|
303
|
-
const result = await client.callTool({ name: 'taiga_list_teams', arguments: {} });
|
|
304
|
-
expect(parseToolResult(result)).toEqual(teams);
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
// -------------------------------------------------------------------------
|
|
308
|
-
// Error handling -- when the API fails, the AI assistant should get a
|
|
309
|
-
// clear error, not a silent success or cryptic crash.
|
|
310
|
-
// -------------------------------------------------------------------------
|
|
311
|
-
describe('error handling', () => {
|
|
312
|
-
it('should surface API errors as MCP error content', async () => {
|
|
313
|
-
// Introspect succeeds, but all other endpoints fail
|
|
314
|
-
const fetchSpy = vi.fn().mockImplementation(async (url) => {
|
|
315
|
-
const path = new URL(url).pathname;
|
|
316
|
-
if (path === '/v1/auth/introspect') {
|
|
317
|
-
return {
|
|
318
|
-
ok: true,
|
|
319
|
-
status: 200,
|
|
320
|
-
json: () => Promise.resolve(introspectionResponse(ALL_CAPABILITIES)),
|
|
321
|
-
text: () => Promise.resolve(''),
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
return {
|
|
325
|
-
ok: false,
|
|
326
|
-
status: 500,
|
|
327
|
-
json: () => Promise.resolve({ error: 'Internal Server Error' }),
|
|
328
|
-
text: () => Promise.resolve('Internal Server Error'),
|
|
329
|
-
};
|
|
330
|
-
});
|
|
331
|
-
vi.stubGlobal('fetch', fetchSpy);
|
|
332
|
-
// Arrange
|
|
333
|
-
const taigaClient = new TaigaClient({ baseUrl: 'https://api.test.com', apiKey: 'test' });
|
|
334
|
-
await taigaClient.introspect();
|
|
335
|
-
const server = new McpServer({ name: 'test', version: '0.0.1' });
|
|
336
|
-
registerAllTools(server, taigaClient);
|
|
337
|
-
const mcpClient = new Client({ name: 'test-client', version: '0.0.1' });
|
|
338
|
-
const [ct, st] = InMemoryTransport.createLinkedPair();
|
|
339
|
-
await Promise.all([server.connect(st), mcpClient.connect(ct)]);
|
|
340
|
-
// Act
|
|
341
|
-
const result = await mcpClient.callTool({ name: 'taiga_list_projects', arguments: {} });
|
|
342
|
-
// Assert -- the MCP SDK marks tool errors with isError
|
|
343
|
-
expect(result.isError).toBe(true);
|
|
344
|
-
});
|
|
345
|
-
it('should reject tool calls with invalid parameters', async () => {
|
|
346
|
-
const { client } = await bootMcpPair({
|
|
347
|
-
capabilities: ALL_CAPABILITIES,
|
|
348
|
-
routes: {},
|
|
349
|
-
});
|
|
350
|
-
// taiga_get_project requires a UUID, not a plain string
|
|
351
|
-
const result = await client.callTool({
|
|
352
|
-
name: 'taiga_get_project',
|
|
353
|
-
arguments: { id: 'not-a-uuid' },
|
|
354
|
-
});
|
|
355
|
-
expect(result.isError).toBe(true);
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
});
|
package/dist/tools/backlogs.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"backlogs.d.ts","sourceRoot":"","sources":["../../src/tools/backlogs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,QAG5E"}
|
package/dist/tools/backlogs.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// Backlog tools are reserved for post-beta.
|
|
2
|
-
// The beta MCP server focuses on the knowledge base (policies, skills, team docs).
|
|
3
|
-
export function registerBacklogTools(_server, _client) {
|
|
4
|
-
// No backlog tools in beta -- will be added when the MCP server
|
|
5
|
-
// expands beyond the knowledge base use case.
|
|
6
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
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;AAGhD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,QA6I3E"}
|
package/dist/tools/documents.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { summarizeList } from './summarize.js';
|
|
3
|
-
export function registerDocumentTools(server, client) {
|
|
4
|
-
// ── Company-level policy documents ──────────────────────────────────
|
|
5
|
-
if (client.hasCapability('tenant:documents:read')) {
|
|
6
|
-
server.tool('taiga_list_documents', 'List company-level policy documents (names and types only). ' +
|
|
7
|
-
'Policies define security, data privacy, SDLC, operations, architecture, and compliance rules ' +
|
|
8
|
-
'that apply across all teams and projects. ' +
|
|
9
|
-
'Use taiga_get_document with the document type to read the full policy content.', {
|
|
10
|
-
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
11
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
12
|
-
const result = await client.listTenantDocuments(params);
|
|
13
|
-
const summaries = summarizeList(result.data);
|
|
14
|
-
return {
|
|
15
|
-
content: [{ type: 'text', text: JSON.stringify(summaries, null, 2) }],
|
|
16
|
-
};
|
|
17
|
-
});
|
|
18
|
-
server.tool('taiga_get_document', 'Get the full content of a company policy document. ' +
|
|
19
|
-
'Use this when you need to understand security requirements, data handling rules, ' +
|
|
20
|
-
'architecture standards, or compliance controls that apply to your code. ' +
|
|
21
|
-
'Types: policy_security, policy_data, policy_sdlc, policy_ops, policy_tic, ' +
|
|
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) => {
|
|
28
|
-
const result = await client.getTenantDocument(params.type);
|
|
29
|
-
return {
|
|
30
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
31
|
-
};
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
if (client.hasCapability('tenant:documents:write')) {
|
|
35
|
-
server.tool('taiga_update_document', 'Update the draft content of a company policy document. ' +
|
|
36
|
-
'Only updates the draft -- does not publish. Use this when you notice a policy ' +
|
|
37
|
-
'is outdated or needs corrections while reviewing code.', {
|
|
38
|
-
type: z.string().describe('Document type (e.g. policy_security, policy_sdlc)'),
|
|
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) => {
|
|
41
|
-
const { type, ...body } = params;
|
|
42
|
-
const result = await client.saveTenantDocumentDraft(type, body);
|
|
43
|
-
return {
|
|
44
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
45
|
-
};
|
|
46
|
-
});
|
|
47
|
-
}
|
|
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
|
-
}
|
package/dist/tools/index.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type { TaigaClient } from '../client.js';
|
|
3
|
-
/**
|
|
4
|
-
* Register all MCP tools based on the API key's effective capabilities.
|
|
5
|
-
* Only tools matching the key's scopes are registered -- the AI assistant
|
|
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
|
-
*/
|
|
11
|
-
export declare function registerAllTools(server: McpServer, client: TaigaClient): void;
|
|
12
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAK7E"}
|
package/dist/tools/index.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { registerProjectTools } from './projects.js';
|
|
2
|
-
import { registerDocumentTools } from './documents.js';
|
|
3
|
-
import { registerBacklogTools } from './backlogs.js';
|
|
4
|
-
import { registerSkillTools } from './skills.js';
|
|
5
|
-
/**
|
|
6
|
-
* Register all MCP tools based on the API key's effective capabilities.
|
|
7
|
-
* Only tools matching the key's scopes are registered -- the AI assistant
|
|
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
|
-
*/
|
|
13
|
-
export function registerAllTools(server, client) {
|
|
14
|
-
registerProjectTools(server, client);
|
|
15
|
-
registerDocumentTools(server, client);
|
|
16
|
-
registerBacklogTools(server, client);
|
|
17
|
-
registerSkillTools(server, client);
|
|
18
|
-
}
|
package/dist/tools/projects.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
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;AAGhD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,QA0F1E"}
|
package/dist/tools/projects.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { summarizeList } from './summarize.js';
|
|
3
|
-
export function registerProjectTools(server, client) {
|
|
4
|
-
if (client.hasCapability('teams:read')) {
|
|
5
|
-
server.tool('taiga_list_teams', 'List all teams in the organization. Use this to find team IDs ' +
|
|
6
|
-
'for reading team documents, skills, and understanding team structure. ' +
|
|
7
|
-
'Teams own domain context, tech preferences, and architecture constraints.', {
|
|
8
|
-
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
9
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
10
|
-
const result = await client.listTeams(params);
|
|
11
|
-
return {
|
|
12
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
13
|
-
};
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
if (client.hasCapability('projects:read')) {
|
|
17
|
-
server.tool('taiga_list_projects', 'List projects in the organization. Use this to find the project you are working on ' +
|
|
18
|
-
'so you can read its specification, architecture, and threat model. ' +
|
|
19
|
-
'Can filter by team or status.', {
|
|
20
|
-
teamId: z.string().uuid().optional().describe('Filter by team ID -- get team IDs from taiga_list_teams'),
|
|
21
|
-
status: z
|
|
22
|
-
.enum(['draft', 'active', 'archived'])
|
|
23
|
-
.optional()
|
|
24
|
-
.describe('Filter by status (draft = intake in progress, active = development)'),
|
|
25
|
-
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
26
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
27
|
-
const result = await client.listProjects(params);
|
|
28
|
-
return {
|
|
29
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
server.tool('taiga_list_project_documents', 'List documents for a specific project (names and types only). ' +
|
|
33
|
-
'Project documents are generated during intake: specification (requirements), ' +
|
|
34
|
-
'architecture (system design), technology decisions, data flow, privacy assessment, ' +
|
|
35
|
-
'threat model (security risks), risk register, and user flow. ' +
|
|
36
|
-
'Use taiga_get_project_document to read the full content.', {
|
|
37
|
-
projectId: z.string().uuid().describe('Project ID -- get from taiga_list_projects'),
|
|
38
|
-
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
39
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
40
|
-
const result = await client.listProjectDocuments(params.projectId, { limit: params.limit });
|
|
41
|
-
const summaries = summarizeList(result.data);
|
|
42
|
-
return {
|
|
43
|
-
content: [{ type: 'text', text: JSON.stringify(summaries, null, 2) }],
|
|
44
|
-
};
|
|
45
|
-
});
|
|
46
|
-
server.tool('taiga_get_project_document', 'Get the full content of a specific project document. ' +
|
|
47
|
-
'Use this when you need to understand what to build (specification), ' +
|
|
48
|
-
'how the system is designed (architecture), what security risks exist (threat model), ' +
|
|
49
|
-
'or what technology choices were made (technology_decisions). ' +
|
|
50
|
-
'Types: specification, architecture, technology_decisions, data_flow, ' +
|
|
51
|
-
'privacy_assessment, threat_model, risk_register, user_flow.', {
|
|
52
|
-
projectId: z.string().uuid().describe('Project ID -- get from taiga_list_projects'),
|
|
53
|
-
type: z
|
|
54
|
-
.string()
|
|
55
|
-
.describe('Document type -- get available types from taiga_list_project_documents. ' +
|
|
56
|
-
'Common: specification, architecture, threat_model, technology_decisions'),
|
|
57
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
58
|
-
const result = await client.getProjectDocument(params.projectId, params.type);
|
|
59
|
-
return {
|
|
60
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
61
|
-
};
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
package/dist/tools/skills.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/tools/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,QA8DxE"}
|
package/dist/tools/skills.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { summarizeList } from './summarize.js';
|
|
3
|
-
export function registerSkillTools(server, client) {
|
|
4
|
-
if (client.hasCapability('tenant:documents:read')) {
|
|
5
|
-
server.tool('taiga_list_skills', 'List agent skills available in the organization (names and descriptions only). ' +
|
|
6
|
-
'Skills are reusable knowledge modules that guide coding: coding standards, ' +
|
|
7
|
-
'security requirements, architecture patterns, database conventions, etc. ' +
|
|
8
|
-
'Use taiga_get_skill with the skill ID to read the full content.', {
|
|
9
|
-
limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
10
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
11
|
-
const result = await client.listSkills(params);
|
|
12
|
-
const summaries = summarizeList(result.data);
|
|
13
|
-
return {
|
|
14
|
-
content: [{ type: 'text', text: JSON.stringify(summaries, null, 2) }],
|
|
15
|
-
};
|
|
16
|
-
});
|
|
17
|
-
server.tool('taiga_get_skill', 'Get the full content of a specific agent skill. ' +
|
|
18
|
-
'Use this when you need detailed coding guidelines, patterns, or conventions ' +
|
|
19
|
-
'for a specific topic (e.g., security requirements, API design, testing strategy). ' +
|
|
20
|
-
'Returns the complete markdown content with code examples and best practices.', {
|
|
21
|
-
id: z.string().uuid().describe('Skill ID -- get from taiga_list_skills'),
|
|
22
|
-
}, { readOnlyHint: true, openWorldHint: false }, async (params) => {
|
|
23
|
-
const result = await client.getSkill(params.id);
|
|
24
|
-
return {
|
|
25
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
26
|
-
};
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
if (client.hasCapability('tenant:documents:write')) {
|
|
30
|
-
server.tool('taiga_update_skill', 'Update an agent skill (coding standards, patterns, conventions). ' +
|
|
31
|
-
'Use this when you notice a skill is outdated, incomplete, or needs corrections ' +
|
|
32
|
-
'while working on the codebase. Changes are saved immediately.', {
|
|
33
|
-
id: z.string().uuid().describe('Skill ID -- get from taiga_list_skills'),
|
|
34
|
-
name: z.string().optional().describe('Updated skill name'),
|
|
35
|
-
description: z.string().optional().describe('Updated skill description'),
|
|
36
|
-
content: z.string().optional().describe('Updated skill content (markdown)'),
|
|
37
|
-
}, { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async (params) => {
|
|
38
|
-
const { id, ...body } = params;
|
|
39
|
-
const result = await client.updateSkill(id, body);
|
|
40
|
-
return {
|
|
41
|
-
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
42
|
-
};
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|