@taiga-ai/mcp-server 0.0.1-rc.71 → 0.0.1-rc.73

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 CHANGED
@@ -40,6 +40,29 @@ 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
+ }>;
43
66
  listSkills(opts?: {
44
67
  scope?: string;
45
68
  scopeId?: string;
@@ -51,6 +74,19 @@ export declare class TaigaClient {
51
74
  getSkill(id: string): Promise<{
52
75
  data: unknown;
53
76
  }>;
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>>;
54
90
  listBacklogItems(projectId: string, opts?: {
55
91
  limit?: number;
56
92
  }): Promise<{
@@ -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,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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -78,6 +78,35 @@ 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
+ // ---------------------------------------------------------------------------
81
110
  // Skills (agent skills)
82
111
  // ---------------------------------------------------------------------------
83
112
  async listSkills(opts) {
@@ -94,6 +123,79 @@ export class TaigaClient {
94
123
  async getSkill(id) {
95
124
  return this.request('GET', `/agent-skills/${id}`);
96
125
  }
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
+ }
97
199
  // ---------------------------------------------------------------------------
98
200
  // Backlog
99
201
  // ---------------------------------------------------------------------------
package/dist/index.js CHANGED
@@ -20,10 +20,39 @@ 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 and instructions
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'),
27
56
  });
28
57
  registerAllTools(server, client);
29
58
  const toolCount = introspection.capabilities.length;
@@ -17,17 +17,19 @@ 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',
21
20
  'taiga_get_document',
22
- 'taiga_get_project',
21
+ 'taiga_get_project_document',
23
22
  'taiga_get_skill',
24
- 'taiga_list_backlog_items',
23
+ 'taiga_get_team_document',
25
24
  'taiga_list_documents',
26
- 'taiga_list_environments',
25
+ 'taiga_list_project_documents',
27
26
  'taiga_list_projects',
28
27
  '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',
31
33
  ];
32
34
  function makeFetchRouter(routes) {
33
35
  return vi.fn().mockImplementation(async (url, init) => {
@@ -110,7 +112,7 @@ describe('MCP server integration', () => {
110
112
  // the API key's capabilities. This is the primary security contract.
111
113
  // -------------------------------------------------------------------------
112
114
  describe('tool discovery', () => {
113
- it('should expose all 11 tools when API key has full capabilities', async () => {
115
+ it('should expose all 13 tools when API key has full capabilities', async () => {
114
116
  const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
115
117
  const { tools } = await client.listTools();
116
118
  expect(toolNames(tools)).toEqual(ALL_TOOL_NAMES);
@@ -122,15 +124,15 @@ describe('MCP server integration', () => {
122
124
  const { client } = await bootMcpPair({ capabilities: [] });
123
125
  await expect(client.listTools()).rejects.toThrow(/Method not found/);
124
126
  });
125
- it('should expose only project tools for a projects:read-only key', async () => {
126
- const { client } = await bootMcpPair({ capabilities: ['projects:read'] });
127
+ it('should expose only team tools for a teams:read-only key', async () => {
128
+ const { client } = await bootMcpPair({ capabilities: ['teams:read'] });
127
129
  const { tools } = await client.listTools();
128
130
  const names = toolNames(tools);
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');
131
+ expect(names).toContain('taiga_list_teams');
132
+ expect(names).toContain('taiga_list_team_documents');
133
+ expect(names).toContain('taiga_get_team_document');
133
134
  expect(names).not.toContain('taiga_list_documents');
135
+ expect(names).not.toContain('taiga_update_document');
134
136
  });
135
137
  it('should expose read-only document tools without write capability', async () => {
136
138
  const { client } = await bootMcpPair({ capabilities: ['tenant:documents:read'] });
@@ -142,35 +144,39 @@ describe('MCP server integration', () => {
142
144
  expect(names).toContain('taiga_get_skill');
143
145
  expect(names).not.toContain('taiga_update_document');
144
146
  });
145
- it('should expose update tool when documents:write is also granted', async () => {
147
+ it('should expose write tools when documents:write is also granted', async () => {
146
148
  const { client } = await bootMcpPair({
147
149
  capabilities: ['tenant:documents:read', 'tenant:documents:write'],
148
150
  });
149
151
  const { tools } = await client.listTools();
150
- expect(toolNames(tools)).toContain('taiga_update_document');
152
+ const names = toolNames(tools);
153
+ expect(names).toContain('taiga_update_document');
154
+ expect(names).toContain('taiga_update_skill');
151
155
  });
152
- it('should expose backlog tools only with backlog:read', async () => {
153
- const { client } = await bootMcpPair({ capabilities: ['backlog:read'] });
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
+ });
154
160
  const { tools } = await client.listTools();
155
161
  const names = toolNames(tools);
156
- expect(names).toContain('taiga_list_backlog_items');
157
- expect(names).toContain('taiga_get_backlog_item');
158
- expect(names).not.toContain('taiga_list_environments');
162
+ expect(names).toContain('taiga_update_team_document');
163
+ expect(names).toContain('taiga_list_teams');
159
164
  });
160
165
  it('should combine capabilities additively', async () => {
161
166
  const { client } = await bootMcpPair({
162
- capabilities: ['projects:read', 'backlog:read'],
167
+ capabilities: ['teams:read', 'tenant:documents:read'],
163
168
  });
164
169
  const { tools } = await client.listTools();
165
170
  const names = toolNames(tools);
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');
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');
174
180
  });
175
181
  it('should provide a description and input schema for every tool', async () => {
176
182
  const { client } = await bootMcpPair({ capabilities: ALL_CAPABILITIES });
@@ -189,11 +195,10 @@ describe('MCP server integration', () => {
189
195
  // -------------------------------------------------------------------------
190
196
  describe('capability-tool contract', () => {
191
197
  const CONTRACT = {
192
- 'projects:read': ['taiga_list_projects', 'taiga_get_project', 'taiga_list_environments'],
193
- 'teams:read': ['taiga_list_teams'],
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'],
194
200
  'tenant:documents:read': ['taiga_list_documents', 'taiga_get_document', 'taiga_list_skills', 'taiga_get_skill'],
195
- 'tenant:documents:write': ['taiga_update_document'],
196
- 'backlog:read': ['taiga_list_backlog_items', 'taiga_get_backlog_item'],
201
+ 'tenant:documents:write': ['taiga_update_document', 'taiga_update_skill'],
197
202
  };
198
203
  for (const [capability, expectedTools] of Object.entries(CONTRACT)) {
199
204
  it(`${capability} should gate: ${expectedTools.join(', ')}`, async () => {
@@ -212,31 +217,33 @@ describe('MCP server integration', () => {
212
217
  // -> (mocked) API -> response back through MCP protocol.
213
218
  // -------------------------------------------------------------------------
214
219
  describe('tool execution', () => {
215
- it('should return project list as JSON text content', async () => {
216
- const projects = [
217
- { id: 'p1', name: 'Alpha' },
218
- { id: 'p2', name: 'Beta' },
220
+ it('should return team list as JSON text content', async () => {
221
+ const teams = [
222
+ { id: 't1', name: 'Product' },
223
+ { id: 't2', name: 'Platform' },
219
224
  ];
220
225
  const { client } = await bootMcpPair({
221
226
  capabilities: ALL_CAPABILITIES,
222
- routes: { 'GET /v1/projects': { data: projects, nextCursor: null } },
227
+ routes: { 'GET /v1/teams': { data: teams, nextCursor: null } },
223
228
  });
224
- const result = await client.callTool({ name: 'taiga_list_projects', arguments: {} });
229
+ const result = await client.callTool({ name: 'taiga_list_teams', arguments: {} });
225
230
  expect(result.content).toHaveLength(1);
226
- expect(parseToolResult(result)).toEqual(projects);
231
+ expect(parseToolResult(result)).toEqual(teams);
227
232
  });
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({
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({
232
237
  capabilities: ALL_CAPABILITIES,
233
- routes: { [`GET /v1/projects/${projectId}`]: { data: project } },
238
+ routes: { [`PATCH /v1/agent-skills/${skillId}`]: { data: updated } },
234
239
  });
235
- const result = await client.callTool({
236
- name: 'taiga_get_project',
237
- arguments: { id: projectId },
240
+ await client.callTool({
241
+ name: 'taiga_update_skill',
242
+ arguments: { id: skillId, content: '# Updated' },
238
243
  });
239
- expect(parseToolResult(result)).toEqual(project);
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}`);
240
247
  });
241
248
  it('should return document content through MCP protocol', async () => {
242
249
  const docs = [{ id: 'd1', title: 'Security Policy' }];
@@ -262,18 +269,21 @@ describe('MCP server integration', () => {
262
269
  expect(putCall).toBeDefined();
263
270
  expect(putCall[0]).toContain(`/v1/tenants/current/documents/${docType}/draft`);
264
271
  });
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({
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({
269
277
  capabilities: ALL_CAPABILITIES,
270
- routes: { [`GET /v1/projects/${projectId}/backlog`]: { data: items, nextCursor: null } },
278
+ routes: { [`PUT /v1/teams/${teamId}/documents/${docType}/draft`]: { data: updated } },
271
279
  });
272
- const result = await client.callTool({
273
- name: 'taiga_list_backlog_items',
274
- arguments: { projectId },
280
+ await client.callTool({
281
+ name: 'taiga_update_team_document',
282
+ arguments: { teamId, type: docType, data: { sections: { overview: 'Updated' } } },
275
283
  });
276
- expect(parseToolResult(result)).toEqual(items);
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`);
277
287
  });
278
288
  it('should return skills through MCP protocol', async () => {
279
289
  const skills = [{ id: 's1', name: 'React Standards' }];
@@ -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(server: McpServer, client: TaigaClient): void;
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":"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"}
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"}
@@ -1,33 +1,6 @@
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
- }
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.
33
6
  }
@@ -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;AAEhD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,QAkD3E"}
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"}
@@ -1,19 +1,30 @@
1
1
  import { z } from 'zod';
2
+ import { summarizeList } from './summarize.js';
2
3
  export function registerDocumentTools(server, client) {
4
+ // ── Company-level policy documents ──────────────────────────────────
3
5
  if (client.hasCapability('tenant:documents:read')) {
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.', {
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.', {
6
10
  limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
7
- }, async (params) => {
11
+ }, { readOnlyHint: true, openWorldHint: false }, async (params) => {
8
12
  const result = await client.listTenantDocuments(params);
13
+ const summaries = summarizeList(result.data);
9
14
  return {
10
- content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
15
+ content: [{ type: 'text', text: JSON.stringify(summaries, null, 2) }],
11
16
  };
12
17
  });
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) => {
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) => {
17
28
  const result = await client.getTenantDocument(params.type);
18
29
  return {
19
30
  content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
@@ -21,10 +32,12 @@ export function registerDocumentTools(server, client) {
21
32
  });
22
33
  }
23
34
  if (client.hasCapability('tenant:documents:write')) {
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) => {
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) => {
28
41
  const { type, ...body } = params;
29
42
  const result = await client.saveTenantDocumentDraft(type, body);
30
43
  return {
@@ -32,4 +45,53 @@ export function registerDocumentTools(server, client) {
32
45
  };
33
46
  });
34
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
+ }
35
97
  }
@@ -4,6 +4,9 @@ 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.
7
10
  */
8
11
  export declare function registerAllTools(server: McpServer, client: TaigaClient): void;
9
12
  //# 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;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAK7E"}
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"}
@@ -6,6 +6,9 @@ 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.
9
12
  */
10
13
  export function registerAllTools(server, client) {
11
14
  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;AAEhD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,QAgD1E"}
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"}