@taiga-ai/mcp-server 0.0.1-dev.60 → 0.0.1-dev.62

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.
@@ -1,53 +1,10 @@
1
+ // Backlog tools are reserved for post-beta.
2
+ // The beta MCP server focuses on the knowledge base (policies, skills, team docs).
3
+
1
4
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { z } from 'zod';
3
5
  import type { TaigaClient } from '../client.js';
4
6
 
5
- export function registerBacklogTools(server: McpServer, client: TaigaClient) {
6
- if (client.hasCapability('backlog:read')) {
7
- server.tool(
8
- 'taiga_list_backlog_items',
9
- 'List backlog items (features, tasks, stories) for a project. ' +
10
- 'Returns item name, status, planning status, and priority.',
11
- {
12
- projectId: z.string().uuid().describe('Project ID'),
13
- limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
14
- },
15
- async (params) => {
16
- const result = await client.listBacklogItems(params.projectId, { limit: params.limit });
17
- return {
18
- content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
19
- };
20
- }
21
- );
22
-
23
- server.tool(
24
- 'taiga_get_backlog_item',
25
- 'Get detailed information about a specific backlog item, including its active plan and steps.',
26
- {
27
- itemId: z.string().uuid().describe('Backlog item ID'),
28
- },
29
- async (params) => {
30
- const result = await client.getBacklogItem(params.itemId);
31
- return {
32
- content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
33
- };
34
- }
35
- );
36
- }
37
-
38
- if (client.hasCapability('projects:read')) {
39
- server.tool(
40
- 'taiga_list_environments',
41
- 'List deployment environments (dev, staging, production) for a project.',
42
- {
43
- projectId: z.string().uuid().describe('Project ID'),
44
- },
45
- async (params) => {
46
- const result = await client.listEnvironments(params.projectId);
47
- return {
48
- content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
49
- };
50
- }
51
- );
52
- }
7
+ export function registerBacklogTools(_server: McpServer, _client: TaigaClient) {
8
+ // No backlog tools in beta -- will be added when the MCP server
9
+ // expands beyond the knowledge base use case.
53
10
  }
@@ -1,31 +1,47 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
3
  import type { TaigaClient } from '../client.js';
4
+ import { summarizeList } from './summarize.js';
4
5
 
5
6
  export function registerDocumentTools(server: McpServer, client: TaigaClient) {
7
+ // ── Company-level policy documents ──────────────────────────────────
8
+
6
9
  if (client.hasCapability('tenant:documents:read')) {
7
10
  server.tool(
8
11
  'taiga_list_documents',
9
- 'List company-level policy documents (security, data privacy, SDLC, etc). ' +
10
- 'These are the governance documents that guide all projects.',
12
+ 'List company-level policy documents (names and types only). ' +
13
+ 'Policies define security, data privacy, SDLC, operations, architecture, and compliance rules ' +
14
+ 'that apply across all teams and projects. ' +
15
+ 'Use taiga_get_document with the document type to read the full policy content.',
11
16
  {
12
17
  limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
13
18
  },
19
+ { readOnlyHint: true, openWorldHint: false },
14
20
  async (params) => {
15
21
  const result = await client.listTenantDocuments(params);
22
+ const summaries = summarizeList(result.data as Record<string, unknown>[]);
16
23
  return {
17
- content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
24
+ content: [{ type: 'text' as const, text: JSON.stringify(summaries, null, 2) }],
18
25
  };
19
26
  }
20
27
  );
21
28
 
22
29
  server.tool(
23
30
  'taiga_get_document',
24
- 'Get the full content of a specific policy document by type. ' +
25
- 'Common types: security_policy, data_privacy_policy, sdlc_policy, architecture_standards.',
31
+ 'Get the full content of a company policy document. ' +
32
+ 'Use this when you need to understand security requirements, data handling rules, ' +
33
+ 'architecture standards, or compliance controls that apply to your code. ' +
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.',
26
36
  {
27
- type: z.string().describe('Document type (e.g. security_policy, sdlc_policy)'),
37
+ type: z
38
+ .string()
39
+ .describe(
40
+ 'Document type -- get available types from taiga_list_documents. ' +
41
+ 'Common: policy_security, policy_data, policy_sdlc, policy_architecture'
42
+ ),
28
43
  },
44
+ { readOnlyHint: true, openWorldHint: false },
29
45
  async (params) => {
30
46
  const result = await client.getTenantDocument(params.type);
31
47
  return {
@@ -38,11 +54,14 @@ export function registerDocumentTools(server: McpServer, client: TaigaClient) {
38
54
  if (client.hasCapability('tenant:documents:write')) {
39
55
  server.tool(
40
56
  'taiga_update_document',
41
- 'Update the draft content of a policy document. Only updates the draft -- does not publish.',
57
+ 'Update the draft content of a company policy document. ' +
58
+ 'Only updates the draft -- does not publish. Use this when you notice a policy ' +
59
+ 'is outdated or needs corrections while reviewing code.',
42
60
  {
43
- type: z.string().describe('Document type (e.g. security_policy)'),
44
- data: z.record(z.string(), z.unknown()).describe('Document content to save'),
61
+ type: z.string().describe('Document type (e.g. policy_security, policy_sdlc)'),
62
+ data: z.record(z.string(), z.unknown()).describe('Document content to save as JSON'),
45
63
  },
64
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
46
65
  async (params) => {
47
66
  const { type, ...body } = params;
48
67
  const result = await client.saveTenantDocumentDraft(type, body as { data: Record<string, unknown> });
@@ -52,4 +71,77 @@ export function registerDocumentTools(server: McpServer, client: TaigaClient) {
52
71
  }
53
72
  );
54
73
  }
74
+
75
+ // ── Team documents ──────────────────────────────────────────────────
76
+
77
+ if (client.hasCapability('teams:read')) {
78
+ server.tool(
79
+ 'taiga_list_team_documents',
80
+ 'List documents for a specific team (names and types only). ' +
81
+ 'Team documents define how the team works: domain context (entities, ubiquitous language), ' +
82
+ 'tech preferences (approved stack), architecture constraints, SLO targets, ' +
83
+ 'definition of done, and team charter. ' +
84
+ 'Use taiga_get_team_document to read the full content.',
85
+ {
86
+ teamId: z.string().uuid().describe('Team ID -- get from taiga_list_teams'),
87
+ limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
88
+ },
89
+ { readOnlyHint: true, openWorldHint: false },
90
+ async (params) => {
91
+ const result = await client.listTeamDocuments(params.teamId, { limit: params.limit });
92
+ const summaries = summarizeList(result.data as Record<string, unknown>[]);
93
+ return {
94
+ content: [{ type: 'text' as const, text: JSON.stringify(summaries, null, 2) }],
95
+ };
96
+ }
97
+ );
98
+
99
+ server.tool(
100
+ 'taiga_get_team_document',
101
+ 'Get the full content of a specific team document. ' +
102
+ 'Use this when you need the team domain context (ubiquitous language, entities, business rules), ' +
103
+ 'tech preferences (approved stack, patterns), or architecture constraints. ' +
104
+ 'Types: team_charter, definition_of_done, tech_preferences, ' +
105
+ 'architecture_constraints, slo_targets, domain_context.',
106
+ {
107
+ teamId: z.string().uuid().describe('Team ID -- get from taiga_list_teams'),
108
+ type: z
109
+ .string()
110
+ .describe(
111
+ 'Document type -- get available types from taiga_list_team_documents. ' +
112
+ 'Most useful: domain_context (entities, language), tech_preferences (stack, patterns)'
113
+ ),
114
+ },
115
+ { readOnlyHint: true, openWorldHint: false },
116
+ async (params) => {
117
+ const result = await client.getTeamDocument(params.teamId, params.type);
118
+ return {
119
+ content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
120
+ };
121
+ }
122
+ );
123
+ }
124
+
125
+ // Team document write requires both teams:read (to find the team) and tenant:documents:write
126
+ if (client.hasCapability('teams:read') && client.hasCapability('tenant:documents:write')) {
127
+ server.tool(
128
+ 'taiga_update_team_document',
129
+ 'Update the draft content of a team document. ' +
130
+ 'Only updates the draft -- does not publish. Use this when you notice team conventions, ' +
131
+ 'domain context, or tech preferences need updating while working on the codebase.',
132
+ {
133
+ teamId: z.string().uuid().describe('Team ID -- get from taiga_list_teams'),
134
+ type: z.string().describe('Document type (e.g. domain_context, tech_preferences)'),
135
+ data: z.record(z.string(), z.unknown()).describe('Document content to save as JSON'),
136
+ },
137
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
138
+ async (params) => {
139
+ const { teamId, type, ...body } = params;
140
+ const result = await client.saveTeamDocumentDraft(teamId, type, body as { data: Record<string, unknown> });
141
+ return {
142
+ content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
143
+ };
144
+ }
145
+ );
146
+ }
55
147
  }
@@ -9,6 +9,9 @@ import { registerSkillTools } from './skills.js';
9
9
  * Register all MCP tools based on the API key's effective capabilities.
10
10
  * Only tools matching the key's scopes are registered -- the AI assistant
11
11
  * never sees tools it can't use.
12
+ *
13
+ * Beta scope: Knowledge base access (policies, skills, team documents).
14
+ * Post-beta: Projects, backlog items, environments, composite tools.
12
15
  */
13
16
  export function registerAllTools(server: McpServer, client: TaigaClient): void {
14
17
  registerProjectTools(server, client);
@@ -1,17 +1,43 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
3
  import type { TaigaClient } from '../client.js';
4
+ import { summarizeList } from './summarize.js';
4
5
 
5
6
  export function registerProjectTools(server: McpServer, client: TaigaClient) {
7
+ if (client.hasCapability('teams:read')) {
8
+ server.tool(
9
+ 'taiga_list_teams',
10
+ 'List all teams in the organization. Use this to find team IDs ' +
11
+ 'for reading team documents, skills, and understanding team structure. ' +
12
+ 'Teams own domain context, tech preferences, and architecture constraints.',
13
+ {
14
+ limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
15
+ },
16
+ { readOnlyHint: true, openWorldHint: false },
17
+ async (params) => {
18
+ const result = await client.listTeams(params);
19
+ return {
20
+ content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
21
+ };
22
+ }
23
+ );
24
+ }
25
+
6
26
  if (client.hasCapability('projects:read')) {
7
27
  server.tool(
8
28
  'taiga_list_projects',
9
- 'List all projects in the organization. Returns project name, status, team, and metadata.',
29
+ 'List projects in the organization. Use this to find the project you are working on ' +
30
+ 'so you can read its specification, architecture, and threat model. ' +
31
+ 'Can filter by team or status.',
10
32
  {
11
- teamId: z.string().uuid().optional().describe('Filter by team ID'),
12
- status: z.enum(['draft', 'active', 'archived']).optional().describe('Filter by status'),
33
+ teamId: z.string().uuid().optional().describe('Filter by team ID -- get team IDs from taiga_list_teams'),
34
+ status: z
35
+ .enum(['draft', 'active', 'archived'])
36
+ .optional()
37
+ .describe('Filter by status (draft = intake in progress, active = development)'),
13
38
  limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
14
39
  },
40
+ { readOnlyHint: true, openWorldHint: false },
15
41
  async (params) => {
16
42
  const result = await client.listProjects(params);
17
43
  return {
@@ -21,29 +47,46 @@ export function registerProjectTools(server: McpServer, client: TaigaClient) {
21
47
  );
22
48
 
23
49
  server.tool(
24
- 'taiga_get_project',
25
- 'Get detailed information about a specific project by ID.',
50
+ 'taiga_list_project_documents',
51
+ 'List documents for a specific project (names and types only). ' +
52
+ 'Project documents are generated during intake: specification (requirements), ' +
53
+ 'architecture (system design), technology decisions, data flow, privacy assessment, ' +
54
+ 'threat model (security risks), risk register, and user flow. ' +
55
+ 'Use taiga_get_project_document to read the full content.',
26
56
  {
27
- id: z.string().uuid().describe('Project ID'),
57
+ projectId: z.string().uuid().describe('Project ID -- get from taiga_list_projects'),
58
+ limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
28
59
  },
60
+ { readOnlyHint: true, openWorldHint: false },
29
61
  async (params) => {
30
- const result = await client.getProject(params.id);
62
+ const result = await client.listProjectDocuments(params.projectId, { limit: params.limit });
63
+ const summaries = summarizeList(result.data as Record<string, unknown>[]);
31
64
  return {
32
- content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
65
+ content: [{ type: 'text' as const, text: JSON.stringify(summaries, null, 2) }],
33
66
  };
34
67
  }
35
68
  );
36
- }
37
69
 
38
- if (client.hasCapability('teams:read')) {
39
70
  server.tool(
40
- 'taiga_list_teams',
41
- 'List all teams in the organization.',
71
+ 'taiga_get_project_document',
72
+ 'Get the full content of a specific project document. ' +
73
+ 'Use this when you need to understand what to build (specification), ' +
74
+ 'how the system is designed (architecture), what security risks exist (threat model), ' +
75
+ 'or what technology choices were made (technology_decisions). ' +
76
+ 'Types: specification, architecture, technology_decisions, data_flow, ' +
77
+ 'privacy_assessment, threat_model, risk_register, user_flow.',
42
78
  {
43
- limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
79
+ projectId: z.string().uuid().describe('Project ID -- get from taiga_list_projects'),
80
+ type: z
81
+ .string()
82
+ .describe(
83
+ 'Document type -- get available types from taiga_list_project_documents. ' +
84
+ 'Common: specification, architecture, threat_model, technology_decisions'
85
+ ),
44
86
  },
87
+ { readOnlyHint: true, openWorldHint: false },
45
88
  async (params) => {
46
- const result = await client.listTeams(params);
89
+ const result = await client.getProjectDocument(params.projectId, params.type);
47
90
  return {
48
91
  content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
49
92
  };
@@ -1,33 +1,39 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
3
  import type { TaigaClient } from '../client.js';
4
+ import { summarizeList } from './summarize.js';
4
5
 
5
6
  export function registerSkillTools(server: McpServer, client: TaigaClient) {
6
- // Skills require tenant:documents:read as they're part of the knowledge base
7
7
  if (client.hasCapability('tenant:documents:read')) {
8
8
  server.tool(
9
9
  'taiga_list_skills',
10
- 'List agent skills available in the organization. ' +
11
- 'Skills are reusable knowledge modules that guide AI agents (coding standards, ' +
12
- 'architecture patterns, deployment procedures, etc).',
10
+ 'List agent skills available in the organization (names and descriptions only). ' +
11
+ 'Skills are reusable knowledge modules that guide coding: coding standards, ' +
12
+ 'security requirements, architecture patterns, database conventions, etc. ' +
13
+ 'Use taiga_get_skill with the skill ID to read the full content.',
13
14
  {
14
15
  limit: z.number().min(1).max(100).optional().describe('Max results (default 25)'),
15
16
  },
17
+ { readOnlyHint: true, openWorldHint: false },
16
18
  async (params) => {
17
19
  const result = await client.listSkills(params);
20
+ const summaries = summarizeList(result.data as Record<string, unknown>[]);
18
21
  return {
19
- content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
22
+ content: [{ type: 'text' as const, text: JSON.stringify(summaries, null, 2) }],
20
23
  };
21
24
  }
22
25
  );
23
26
 
24
27
  server.tool(
25
28
  'taiga_get_skill',
26
- 'Get the full content of a specific agent skill by ID. ' +
27
- 'Returns the skill definition including instructions, context, and metadata.',
29
+ 'Get the full content of a specific agent skill. ' +
30
+ 'Use this when you need detailed coding guidelines, patterns, or conventions ' +
31
+ 'for a specific topic (e.g., security requirements, API design, testing strategy). ' +
32
+ 'Returns the complete markdown content with code examples and best practices.',
28
33
  {
29
- id: z.string().uuid().describe('Skill ID'),
34
+ id: z.string().uuid().describe('Skill ID -- get from taiga_list_skills'),
30
35
  },
36
+ { readOnlyHint: true, openWorldHint: false },
31
37
  async (params) => {
32
38
  const result = await client.getSkill(params.id);
33
39
  return {
@@ -36,4 +42,27 @@ export function registerSkillTools(server: McpServer, client: TaigaClient) {
36
42
  }
37
43
  );
38
44
  }
45
+
46
+ if (client.hasCapability('tenant:documents:write')) {
47
+ server.tool(
48
+ 'taiga_update_skill',
49
+ 'Update an agent skill (coding standards, patterns, conventions). ' +
50
+ 'Use this when you notice a skill is outdated, incomplete, or needs corrections ' +
51
+ 'while working on the codebase. Changes are saved immediately.',
52
+ {
53
+ id: z.string().uuid().describe('Skill ID -- get from taiga_list_skills'),
54
+ name: z.string().optional().describe('Updated skill name'),
55
+ description: z.string().optional().describe('Updated skill description'),
56
+ content: z.string().optional().describe('Updated skill content (markdown)'),
57
+ },
58
+ { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
59
+ async (params) => {
60
+ const { id, ...body } = params;
61
+ const result = await client.updateSkill(id, body);
62
+ return {
63
+ content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
64
+ };
65
+ }
66
+ );
67
+ }
39
68
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Strip heavy content from list responses to avoid context window bloat.
3
+ *
4
+ * List endpoints return summaries (id, name, description, metadata).
5
+ * Get endpoints return the full content.
6
+ *
7
+ * This keeps list calls cheap (~100 tokens per item) and lets the AI
8
+ * selectively load full content only for the items it needs.
9
+ */
10
+
11
+ /** Fields to strip from list responses -- these contain the bulk content. */
12
+ const HEAVY_FIELDS = ['content', 'data', 'sections'];
13
+
14
+ /**
15
+ * Remove heavy content fields from each item in an array.
16
+ * Preserves all metadata (id, name, description, type, dates, etc).
17
+ */
18
+ export function summarizeList<T extends Record<string, unknown>>(
19
+ items: T[]
20
+ ): Omit<T, 'content' | 'data' | 'sections'>[] {
21
+ return items.map((item) => {
22
+ const summary = { ...item };
23
+ for (const field of HEAVY_FIELDS) {
24
+ if (field in summary) {
25
+ delete summary[field];
26
+ }
27
+ }
28
+ return summary;
29
+ });
30
+ }