@taiga-ai/mcp-server 0.2.3-rc.265 → 0.3.0-dev.295

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.
Files changed (47) hide show
  1. package/dist/index.js +419 -62
  2. package/package.json +13 -11
  3. package/scripts/build.mjs +37 -0
  4. package/scripts/prepare-publish.cjs +15 -7
  5. package/src/client.test.ts +12 -27
  6. package/src/client.ts +19 -43
  7. package/src/index.ts +5 -8
  8. package/src/server.integration.test.ts +13 -50
  9. package/src/tools/backlogs.ts +1 -1
  10. package/src/tools/documents.ts +1 -1
  11. package/src/tools/index.ts +1 -3
  12. package/src/tools/projects.ts +7 -3
  13. package/src/version.test.ts +10 -0
  14. package/src/version.ts +14 -0
  15. package/taiga-ai-mcp-server-0.3.0-dev.295.tgz +0 -0
  16. package/tsconfig.json +2 -1
  17. package/dist/client.d.ts +0 -109
  18. package/dist/client.d.ts.map +0 -1
  19. package/dist/client.js +0 -228
  20. package/dist/client.test.d.ts +0 -2
  21. package/dist/client.test.d.ts.map +0 -1
  22. package/dist/client.test.js +0 -203
  23. package/dist/index.d.ts +0 -3
  24. package/dist/index.d.ts.map +0 -1
  25. package/dist/server.integration.test.d.ts +0 -2
  26. package/dist/server.integration.test.d.ts.map +0 -1
  27. package/dist/server.integration.test.js +0 -358
  28. package/dist/tools/backlogs.d.ts +0 -4
  29. package/dist/tools/backlogs.d.ts.map +0 -1
  30. package/dist/tools/backlogs.js +0 -6
  31. package/dist/tools/documents.d.ts +0 -4
  32. package/dist/tools/documents.d.ts.map +0 -1
  33. package/dist/tools/documents.js +0 -97
  34. package/dist/tools/index.d.ts +0 -12
  35. package/dist/tools/index.d.ts.map +0 -1
  36. package/dist/tools/index.js +0 -18
  37. package/dist/tools/projects.d.ts +0 -4
  38. package/dist/tools/projects.d.ts.map +0 -1
  39. package/dist/tools/projects.js +0 -64
  40. package/dist/tools/skills.d.ts +0 -4
  41. package/dist/tools/skills.d.ts.map +0 -1
  42. package/dist/tools/skills.js +0 -45
  43. package/dist/tools/summarize.d.ts +0 -15
  44. package/dist/tools/summarize.d.ts.map +0 -1
  45. package/dist/tools/summarize.js +0 -26
  46. package/src/tools/skills.ts +0 -68
  47. package/taiga-ai-mcp-server-0.2.3-rc.265.tgz +0 -0
@@ -1,6 +1,10 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
2
3
  import { TaigaClient } from './client.js';
3
4
 
5
+ // Read independently of src/version.ts, so the test checks the real file.
6
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string };
7
+
4
8
  // ---------------------------------------------------------------------------
5
9
  // Helpers -- thin wrappers to reduce boilerplate, not to obscure intent
6
10
  // ---------------------------------------------------------------------------
@@ -62,7 +66,7 @@ describe('TaigaClient', () => {
62
66
 
63
67
  await makeClient().listProjects();
64
68
 
65
- expect(spy.mock.calls[0][1].headers['User-Agent']).toMatch(/^taiga-ai-mcp-server\//);
69
+ expect(spy.mock.calls[0][1].headers['User-Agent']).toBe(`taiga-ai-mcp-server/${pkg.version}`);
66
70
  });
67
71
  });
68
72
 
@@ -109,7 +113,7 @@ describe('TaigaClient', () => {
109
113
 
110
114
  await client.listProjects();
111
115
 
112
- expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/projects');
116
+ expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/products');
113
117
  });
114
118
 
115
119
  it('should omit query string when no optional params are provided', async () => {
@@ -117,7 +121,7 @@ describe('TaigaClient', () => {
117
121
 
118
122
  await makeClient().listProjects();
119
123
 
120
- expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/projects');
124
+ expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/products');
121
125
  });
122
126
 
123
127
  it('should include all optional filters as query params', async () => {
@@ -126,7 +130,7 @@ describe('TaigaClient', () => {
126
130
  await makeClient().listProjects({ teamId: 't1', status: 'active', limit: 10 });
127
131
 
128
132
  const url = spy.mock.calls[0][0] as string;
129
- expect(url).toContain('teamId=t1');
133
+ expect(url).toContain('factoryId=t1');
130
134
  expect(url).toContain('status=active');
131
135
  expect(url).toContain('limit=10');
132
136
  });
@@ -137,7 +141,7 @@ describe('TaigaClient', () => {
137
141
  await makeClient().listBacklogItems('proj-1', { limit: 20 });
138
142
 
139
143
  const url = spy.mock.calls[0][0] as string;
140
- expect(url).toContain('/v1/projects/proj-1/backlog');
144
+ expect(url).toContain('/v1/products/proj-1/backlog');
141
145
  expect(url).toContain('limit=20');
142
146
  });
143
147
 
@@ -146,7 +150,7 @@ describe('TaigaClient', () => {
146
150
 
147
151
  await makeClient().listEnvironments('proj-1');
148
152
 
149
- expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/projects/proj-1/environments');
153
+ expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/products/proj-1/environments');
150
154
  });
151
155
  });
152
156
 
@@ -207,7 +211,7 @@ describe('TaigaClient', () => {
207
211
 
208
212
  const result = await makeClient().getProject('p1');
209
213
 
210
- expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/projects/p1');
214
+ expect(spy.mock.calls[0][0]).toBe('https://api.test.com/v1/products/p1');
211
215
  expect(result.data).toEqual({ id: 'p1', name: 'Alpha' });
212
216
  });
213
217
 
@@ -231,25 +235,6 @@ describe('TaigaClient', () => {
231
235
  });
232
236
  });
233
237
 
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
238
  it('should GET a single backlog item by id (flat path)', async () => {
254
239
  const spy = stubFetch(200, { data: { id: 'item-1' } });
255
240
 
@@ -263,7 +248,7 @@ describe('TaigaClient', () => {
263
248
 
264
249
  await makeClient().listTeams({ limit: 50 });
265
250
 
266
- expect(spy.mock.calls[0][0]).toContain('/v1/teams?limit=50');
251
+ expect(spy.mock.calls[0][0]).toContain('/v1/factories?limit=50');
267
252
  });
268
253
 
269
254
  it('should GET documents from tenant path with optional limit', async () => {
package/src/client.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ApiKeyIntrospection } from '@taiga/types';
2
+ import { SERVER_VERSION } from './version.js';
2
3
 
3
4
  /**
4
5
  * HTTP client for the Taiga API.
@@ -19,7 +20,7 @@ export class TaigaClient {
19
20
  const headers: Record<string, string> = {
20
21
  Authorization: `Bearer ${this.apiKey}`,
21
22
  'Content-Type': 'application/json',
22
- 'User-Agent': 'taiga-ai-mcp-server/0.0.1',
23
+ 'User-Agent': `taiga-ai-mcp-server/${SERVER_VERSION}`,
23
24
  };
24
25
 
25
26
  const response = await fetch(url, {
@@ -61,15 +62,15 @@ export class TaigaClient {
61
62
 
62
63
  async listProjects(opts?: { teamId?: string; status?: string; limit?: number }) {
63
64
  const params = new URLSearchParams();
64
- if (opts?.teamId) params.set('teamId', opts.teamId);
65
+ if (opts?.teamId) params.set('factoryId', opts.teamId);
65
66
  if (opts?.status) params.set('status', opts.status);
66
67
  if (opts?.limit) params.set('limit', String(opts.limit));
67
68
  const qs = params.toString();
68
- return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/projects${qs ? `?${qs}` : ''}`);
69
+ return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/products${qs ? `?${qs}` : ''}`);
69
70
  }
70
71
 
71
72
  async getProject(id: string) {
72
- return this.request<{ data: unknown }>('GET', `/projects/${id}`);
73
+ return this.request<{ data: unknown }>('GET', `/products/${id}`);
73
74
  }
74
75
 
75
76
  // ---------------------------------------------------------------------------
@@ -104,16 +105,16 @@ export class TaigaClient {
104
105
  const qs = params.toString();
105
106
  return this.request<{ data: unknown[]; nextCursor: string | null }>(
106
107
  'GET',
107
- `/teams/${teamId}/documents${qs ? `?${qs}` : ''}`
108
+ `/factories/${teamId}/documents${qs ? `?${qs}` : ''}`
108
109
  );
109
110
  }
110
111
 
111
112
  async getTeamDocument(teamId: string, type: string) {
112
- return this.request<{ data: unknown }>('GET', `/teams/${teamId}/documents/${type}`);
113
+ return this.request<{ data: unknown }>('GET', `/factories/${teamId}/documents/${type}`);
113
114
  }
114
115
 
115
116
  async saveTeamDocumentDraft(teamId: string, type: string, body: { data: Record<string, unknown> }) {
116
- return this.request<{ data: unknown }>('PUT', `/teams/${teamId}/documents/${type}/draft`, body);
117
+ return this.request<{ data: unknown }>('PUT', `/factories/${teamId}/documents/${type}/draft`, body);
117
118
  }
118
119
 
119
120
  // ---------------------------------------------------------------------------
@@ -126,33 +127,12 @@ export class TaigaClient {
126
127
  const qs = params.toString();
127
128
  return this.request<{ data: unknown[]; nextCursor: string | null }>(
128
129
  'GET',
129
- `/projects/${projectId}/documents${qs ? `?${qs}` : ''}`
130
+ `/products/${projectId}/documents${qs ? `?${qs}` : ''}`
130
131
  );
131
132
  }
132
133
 
133
134
  async getProjectDocument(projectId: string, type: string) {
134
- return this.request<{ data: unknown }>('GET', `/projects/${projectId}/documents/${type}`);
135
- }
136
-
137
- // ---------------------------------------------------------------------------
138
- // Skills (agent skills)
139
- // ---------------------------------------------------------------------------
140
-
141
- async listSkills(opts?: { scope?: string; scopeId?: string; limit?: number }) {
142
- const params = new URLSearchParams();
143
- if (opts?.scope) params.set('scope', opts.scope);
144
- if (opts?.scopeId) params.set('scopeId', opts.scopeId);
145
- if (opts?.limit) params.set('limit', String(opts.limit));
146
- const qs = params.toString();
147
- return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/agent-skills${qs ? `?${qs}` : ''}`);
148
- }
149
-
150
- async getSkill(id: string) {
151
- return this.request<{ data: unknown }>('GET', `/agent-skills/${id}`);
152
- }
153
-
154
- async updateSkill(id: string, body: { name?: string; description?: string; content?: string }) {
155
- return this.request<{ data: unknown }>('PATCH', `/agent-skills/${id}`, body);
135
+ return this.request<{ data: unknown }>('GET', `/products/${projectId}/documents/${type}`);
156
136
  }
157
137
 
158
138
  // ---------------------------------------------------------------------------
@@ -161,21 +141,20 @@ export class TaigaClient {
161
141
 
162
142
  /**
163
143
  * Fetch the full context for a project in parallel: project metadata,
164
- * team documents, project documents, and team skills.
144
+ * team documents and project documents.
165
145
  * Returns a structured bundle optimized for coding agent consumption.
166
146
  */
167
147
  async getProjectContext(projectId: string): Promise<Record<string, unknown>> {
168
- // First fetch the project to get the teamId
169
- const project = await this.request<{ data: Record<string, unknown> }>('GET', `/projects/${projectId}`);
170
- const teamId = project.data.teamId as string | undefined;
148
+ // First fetch the project to get its factory (the API's name for a team since PRO-1668)
149
+ const project = await this.request<{ data: Record<string, unknown> }>('GET', `/products/${projectId}`);
150
+ const teamId = project.data.factoryId as string | undefined;
171
151
 
172
- // Fetch everything in parallel
173
- const [projectDocs, teamDocs, skills] = await Promise.all([
152
+ // Fetch the document lists in parallel.
153
+ const [projectDocs, teamDocs] = await Promise.all([
174
154
  this.listProjectDocuments(projectId, { limit: 20 }).catch(() => ({ data: [] })),
175
155
  teamId
176
156
  ? this.listTeamDocuments(teamId, { limit: 20 }).catch(() => ({ data: [] }))
177
157
  : Promise.resolve({ data: [] }),
178
- this.listSkills({ limit: 100 }).catch(() => ({ data: [] })),
179
158
  ]);
180
159
 
181
160
  // Fetch full content for key documents (in parallel)
@@ -233,9 +212,6 @@ export class TaigaClient {
233
212
  isDraft: d.isDraft,
234
213
  })),
235
214
  },
236
- skills_summary: (skills.data as Record<string, unknown>[])
237
- .filter((s) => !s.teamId || s.teamId === teamId)
238
- .map((s) => ({ id: s.id, name: s.name, slug: s.slug, description: s.description })),
239
215
  };
240
216
 
241
217
  return context;
@@ -251,7 +227,7 @@ export class TaigaClient {
251
227
  const qs = params.toString();
252
228
  return this.request<{ data: unknown[]; nextCursor: string | null }>(
253
229
  'GET',
254
- `/projects/${projectId}/backlog${qs ? `?${qs}` : ''}`
230
+ `/products/${projectId}/backlog${qs ? `?${qs}` : ''}`
255
231
  );
256
232
  }
257
233
 
@@ -264,7 +240,7 @@ export class TaigaClient {
264
240
  // ---------------------------------------------------------------------------
265
241
 
266
242
  async listEnvironments(projectId: string) {
267
- return this.request<{ data: unknown[] }>('GET', `/projects/${projectId}/environments`);
243
+ return this.request<{ data: unknown[] }>('GET', `/products/${projectId}/environments`);
268
244
  }
269
245
 
270
246
  // ---------------------------------------------------------------------------
@@ -275,6 +251,6 @@ export class TaigaClient {
275
251
  const params = new URLSearchParams();
276
252
  if (opts?.limit) params.set('limit', String(opts.limit));
277
253
  const qs = params.toString();
278
- return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/teams${qs ? `?${qs}` : ''}`);
254
+ return this.request<{ data: unknown[]; nextCursor: string | null }>('GET', `/factories${qs ? `?${qs}` : ''}`);
279
255
  }
280
256
  }
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
5
  import { TaigaClient } from './client.js';
6
6
  import { registerAllTools } from './tools/index.js';
7
+ import { SERVER_VERSION } from './version.js';
7
8
 
8
9
  const DEFAULT_API_URL = 'https://api.taiga.app';
9
10
 
@@ -31,7 +32,7 @@ async function main() {
31
32
  const server = new McpServer(
32
33
  {
33
34
  name: 'taiga',
34
- version: '0.0.1',
35
+ version: SERVER_VERSION,
35
36
  },
36
37
  {
37
38
  instructions: [
@@ -50,16 +51,12 @@ async function main() {
50
51
  "Specification (what to build), architecture (how it's designed), threat model (security risks),",
51
52
  'technology decisions, and data flow. Read these to understand the specific project context.',
52
53
  '',
53
- '**Skills** (taiga_list_skills / taiga_get_skill):',
54
- 'Coding standards, security requirements, API design patterns, testing strategy, database patterns,',
55
- 'and more. These are detailed engineering guidelines with code examples.',
56
- '',
57
54
  "Workflow: Start with taiga_list_teams to find your team, then read the team's domain_context",
58
55
  'and tech_preferences. For project-specific work, use taiga_list_projects to find the project,',
59
- 'then read its specification and architecture. Use skills for coding patterns.',
56
+ 'then read its specification and architecture.',
60
57
  '',
61
- 'When you notice a policy, skill, or team document is outdated, use the update tools',
62
- '(taiga_update_document, taiga_update_skill, taiga_update_team_document) to fix it.',
58
+ 'When you notice a policy or team document is outdated, use the update tools',
59
+ '(taiga_update_document, taiga_update_team_document) to fix it.',
63
60
  'This keeps the knowledge base accurate for everyone.',
64
61
  ].join('\n'),
65
62
  }
@@ -18,20 +18,20 @@ const ALL_CAPABILITIES = [
18
18
  'backlog:read',
19
19
  ];
20
20
 
21
- /** All tool names the server can register (sorted for deterministic comparison). */
21
+ /**
22
+ * Every tool name the server registers for a key with all capabilities,
23
+ * sorted for deterministic comparison.
24
+ */
22
25
  const ALL_TOOL_NAMES = [
23
26
  'taiga_get_document',
24
27
  'taiga_get_project_document',
25
- 'taiga_get_skill',
26
28
  'taiga_get_team_document',
27
29
  'taiga_list_documents',
28
30
  'taiga_list_project_documents',
29
31
  'taiga_list_projects',
30
- 'taiga_list_skills',
31
32
  'taiga_list_team_documents',
32
33
  'taiga_list_teams',
33
34
  'taiga_update_document',
34
- 'taiga_update_skill',
35
35
  'taiga_update_team_document',
36
36
  ];
37
37
 
@@ -129,7 +129,7 @@ describe('MCP server integration', () => {
129
129
  // -------------------------------------------------------------------------
130
130
 
131
131
  describe('tool discovery', () => {
132
- it('should expose all 13 tools when API key has full capabilities', async () => {
132
+ it('should expose every tool when API key has full capabilities', async () => {
133
133
  const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
134
134
 
135
135
  const { tools } = await client.listTools();
@@ -167,8 +167,6 @@ describe('MCP server integration', () => {
167
167
 
168
168
  expect(names).toContain('taiga_list_documents');
169
169
  expect(names).toContain('taiga_get_document');
170
- expect(names).toContain('taiga_list_skills');
171
- expect(names).toContain('taiga_get_skill');
172
170
  expect(names).not.toContain('taiga_update_document');
173
171
  });
174
172
 
@@ -181,7 +179,6 @@ describe('MCP server integration', () => {
181
179
  const names = toolNames(tools);
182
180
 
183
181
  expect(names).toContain('taiga_update_document');
184
- expect(names).toContain('taiga_update_skill');
185
182
  });
186
183
 
187
184
  it('should expose team document write when both teams:read and documents:write are granted', async () => {
@@ -209,10 +206,8 @@ describe('MCP server integration', () => {
209
206
  expect(names).toContain('taiga_list_team_documents');
210
207
  // tenant:documents:read tools present
211
208
  expect(names).toContain('taiga_list_documents');
212
- expect(names).toContain('taiga_list_skills');
213
209
  // write tools absent
214
210
  expect(names).not.toContain('taiga_update_document');
215
- expect(names).not.toContain('taiga_update_skill');
216
211
  });
217
212
 
218
213
  it('should provide a description and input schema for every tool', async () => {
@@ -238,8 +233,8 @@ describe('MCP server integration', () => {
238
233
  const CONTRACT: Record<string, string[]> = {
239
234
  'projects:read': ['taiga_list_projects', 'taiga_list_project_documents', 'taiga_get_project_document'],
240
235
  'teams:read': ['taiga_list_teams', 'taiga_list_team_documents', 'taiga_get_team_document'],
241
- 'tenant:documents:read': ['taiga_list_documents', 'taiga_get_document', 'taiga_list_skills', 'taiga_get_skill'],
242
- 'tenant:documents:write': ['taiga_update_document', 'taiga_update_skill'],
236
+ 'tenant:documents:read': ['taiga_list_documents', 'taiga_get_document'],
237
+ 'tenant:documents:write': ['taiga_update_document'],
243
238
  };
244
239
 
245
240
  for (const [capability, expectedTools] of Object.entries(CONTRACT)) {
@@ -271,7 +266,7 @@ describe('MCP server integration', () => {
271
266
 
272
267
  const { client } = await bootMcpPair({
273
268
  capabilities: ALL_CAPABILITIES,
274
- routes: { 'GET /v1/teams': { data: teams, nextCursor: null } },
269
+ routes: { 'GET /v1/factories': { data: teams, nextCursor: null } },
275
270
  });
276
271
 
277
272
  const result = await client.callTool({ name: 'taiga_list_teams', arguments: {} });
@@ -280,25 +275,6 @@ describe('MCP server integration', () => {
280
275
  expect(parseToolResult(result)).toEqual(teams);
281
276
  });
282
277
 
283
- it('should send PATCH when updating a skill', async () => {
284
- const skillId = '00000000-0000-0000-0000-000000000001';
285
- const updated = { id: skillId, name: 'Updated Skill', content: '# Updated' };
286
-
287
- const { client, fetchSpy } = await bootMcpPair({
288
- capabilities: ALL_CAPABILITIES,
289
- routes: { [`PATCH /v1/agent-skills/${skillId}`]: { data: updated } },
290
- });
291
-
292
- await client.callTool({
293
- name: 'taiga_update_skill',
294
- arguments: { id: skillId, content: '# Updated' },
295
- });
296
-
297
- const patchCall = fetchSpy.mock.calls.find((c: unknown[]) => (c[1] as RequestInit)?.method === 'PATCH');
298
- expect(patchCall).toBeDefined();
299
- expect(patchCall![0]).toContain(`/v1/agent-skills/${skillId}`);
300
- });
301
-
302
278
  it('should return document content through MCP protocol', async () => {
303
279
  const docs = [{ id: 'd1', title: 'Security Policy' }];
304
280
 
@@ -332,13 +308,13 @@ describe('MCP server integration', () => {
332
308
  });
333
309
 
334
310
  it('should send PUT when updating a team document draft', async () => {
335
- const teamId = '00000000-0000-0000-0000-000000000001';
311
+ const teamId = '0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b';
336
312
  const docType = 'domain_context';
337
313
  const updated = { type: docType, name: 'Domain Context' };
338
314
 
339
315
  const { client, fetchSpy } = await bootMcpPair({
340
316
  capabilities: ALL_CAPABILITIES,
341
- routes: { [`PUT /v1/teams/${teamId}/documents/${docType}/draft`]: { data: updated } },
317
+ routes: { [`PUT /v1/factories/${teamId}/documents/${docType}/draft`]: { data: updated } },
342
318
  });
343
319
 
344
320
  await client.callTool({
@@ -347,23 +323,10 @@ describe('MCP server integration', () => {
347
323
  });
348
324
 
349
325
  const putCall = fetchSpy.mock.calls.find(
350
- (c: unknown[]) => (c[1] as RequestInit)?.method === 'PUT' && (c[0] as string).includes('/teams/')
326
+ (c: unknown[]) => (c[1] as RequestInit)?.method === 'PUT' && (c[0] as string).includes('/factories/')
351
327
  );
352
328
  expect(putCall).toBeDefined();
353
- expect(putCall![0]).toContain(`/v1/teams/${teamId}/documents/${docType}/draft`);
354
- });
355
-
356
- it('should return skills through MCP protocol', async () => {
357
- const skills = [{ id: 's1', name: 'React Standards' }];
358
-
359
- const { client } = await bootMcpPair({
360
- capabilities: ALL_CAPABILITIES,
361
- routes: { 'GET /v1/agent-skills': { data: skills, nextCursor: null } },
362
- });
363
-
364
- const result = await client.callTool({ name: 'taiga_list_skills', arguments: {} });
365
-
366
- expect(parseToolResult(result)).toEqual(skills);
329
+ expect(putCall![0]).toContain(`/v1/factories/${teamId}/documents/${docType}/draft`);
367
330
  });
368
331
 
369
332
  it('should return teams through MCP protocol', async () => {
@@ -371,7 +334,7 @@ describe('MCP server integration', () => {
371
334
 
372
335
  const { client } = await bootMcpPair({
373
336
  capabilities: ALL_CAPABILITIES,
374
- routes: { 'GET /v1/teams': { data: teams, nextCursor: null } },
337
+ routes: { 'GET /v1/factories': { data: teams, nextCursor: null } },
375
338
  });
376
339
 
377
340
  const result = await client.callTool({ name: 'taiga_list_teams', arguments: {} });
@@ -1,5 +1,5 @@
1
1
  // Backlog tools are reserved for post-beta.
2
- // The beta MCP server focuses on the knowledge base (policies, skills, team docs).
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';
@@ -32,7 +32,7 @@ export function registerDocumentTools(server: McpServer, client: TaigaClient) {
32
32
  'Use this when you need to understand security requirements, data handling rules, ' +
33
33
  'architecture standards, or compliance controls that apply to your code. ' +
34
34
  'Types: policy_security, policy_data, policy_sdlc, policy_ops, policy_tic, ' +
35
- 'policy_risk, policy_architecture, policy_supply_chain, policy_acceptable_use, policy_ip.',
35
+ 'policy_risk, policy_architecture, policy_supply_chain, policy_cloud_governance, policy_acceptable_use, policy_ip.',
36
36
  {
37
37
  type: z
38
38
  .string()
@@ -3,19 +3,17 @@ import type { TaigaClient } from '../client.js';
3
3
  import { registerProjectTools } from './projects.js';
4
4
  import { registerDocumentTools } from './documents.js';
5
5
  import { registerBacklogTools } from './backlogs.js';
6
- import { registerSkillTools } from './skills.js';
7
6
 
8
7
  /**
9
8
  * Register all MCP tools based on the API key's effective capabilities.
10
9
  * Only tools matching the key's scopes are registered -- the AI assistant
11
10
  * never sees tools it can't use.
12
11
  *
13
- * Beta scope: Knowledge base access (policies, skills, team documents).
12
+ * Beta scope: Knowledge base access (policies and team documents).
14
13
  * Post-beta: Projects, backlog items, environments, composite tools.
15
14
  */
16
15
  export function registerAllTools(server: McpServer, client: TaigaClient): void {
17
16
  registerProjectTools(server, client);
18
17
  registerDocumentTools(server, client);
19
18
  registerBacklogTools(server, client);
20
- registerSkillTools(server, client);
21
19
  }
@@ -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, skills, and understanding team structure. ' +
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)'),
@@ -28,13 +28,17 @@ export function registerProjectTools(server: McpServer, client: TaigaClient) {
28
28
  'taiga_list_projects',
29
29
  'List projects in the organization. Use this to find the project you are working on ' +
30
30
  'so you can read its specification, architecture, and threat model. ' +
31
- 'Can filter by team or status.',
31
+ 'Can filter by team; a status filter also exists but no longer distinguishes anything (see its description).',
32
32
  {
33
33
  teamId: z.string().uuid().optional().describe('Filter by team ID -- get team IDs from taiga_list_teams'),
34
34
  status: z
35
35
  .enum(['draft', 'active', 'archived'])
36
36
  .optional()
37
- .describe('Filter by status (draft = intake in progress, active = development)'),
37
+ .describe(
38
+ 'Filter by status. Taiga no longer surfaces project status in its UI (PRO-543), so every ' +
39
+ 'project is currently "draft" and filtering by "active" or "archived" returns nothing. ' +
40
+ 'Omit this unless you are working with data set through the API directly.'
41
+ ),
38
42
  limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
39
43
  },
40
44
  { readOnlyHint: true, openWorldHint: false },
@@ -0,0 +1,10 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { SERVER_VERSION } from './version.js';
4
+
5
+ describe('SERVER_VERSION', () => {
6
+ it('is the version in package.json, not a hand-kept copy', () => {
7
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string };
8
+ expect(SERVER_VERSION).toBe(pkg.version);
9
+ });
10
+ });
package/src/version.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ /**
4
+ * This package's own version, read from package.json at runtime.
5
+ *
6
+ * Read at runtime rather than inlined at build: the publish workflow sets the
7
+ * final version (`0.3.0-dev.<run>`, `0.3.0-rc.<run>`) after the bundle is
8
+ * built, so only a runtime read reports what was actually published. Both
9
+ * `src/` and the bundled `dist/index.js` sit one level under the package root,
10
+ * and npm always ships package.json.
11
+ */
12
+ const pkg = createRequire(import.meta.url)('../package.json') as { version: string };
13
+
14
+ export const SERVER_VERSION: string = pkg.version;
package/tsconfig.json CHANGED
@@ -10,7 +10,8 @@
10
10
  "outDir": "./dist",
11
11
  "rootDir": "./src",
12
12
  "esModuleInterop": true,
13
- "resolveJsonModule": true
13
+ "resolveJsonModule": true,
14
+ "types": ["node"]
14
15
  },
15
16
  "include": ["src"],
16
17
  "exclude": ["node_modules", "dist"]