taiga-mcp-server
Version:
Full-featured MCP server for Taiga project management with advanced sprint and issue tracking. Based on mcpTAIGA by adriapedralbes, enhanced with substantial modifications. Developed collaboratively with Claude Code (AI-assisted development).
305 lines (273 loc) • 10.9 kB
JavaScript
/**
* Epic management tools for Taiga MCP Server
* Handles Epic creation, management, and User Story linking for large-scale project organization
*/
import { z } from 'zod';
import { TaigaService } from '../taigaService.js';
import { createSuccessResponse, createErrorResponse } from '../utils.js';
import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants.js';
const taigaService = new TaigaService();
/**
* Create Epic tool
* Creates a new Epic for large-scale project organization
*/
export const createEpicTool = {
name: 'createEpic',
description: 'Create a new Epic for organizing large-scale project features',
inputSchema: z.object({
project: z.number({
description: 'Project ID where the Epic will be created'
}),
subject: z.string().min(1, 'Epic subject is required'),
description: z.string().optional().describe('Optional detailed description of the Epic'),
color: z.string().optional().describe('Optional color code for the Epic (e.g., #FF5733)'),
tags: z.array(z.string()).optional().describe('Optional tags for categorization'),
}),
handler: async ({ project, subject, description, color, tags }) => {
try {
if (!taigaService.isAuthenticated()) {
return createErrorResponse(ERROR_MESSAGES.AUTHENTICATION_FAILED);
}
const epicData = {
project,
subject,
description: description || '',
color: color || '#999999',
tags: tags || []
};
const result = await taigaService.createEpic(epicData);
return createSuccessResponse(
`${SUCCESS_MESSAGES.EPIC_CREATED}\n\n` +
`🏛️ **Epic創建成功**\n` +
`- Epic ID: ${result.id}\n` +
`- 標題: ${result.subject}\n` +
`- 專案: ${result.project_extra_info?.name || project}\n` +
`- 顏色: ${result.color}\n` +
`- 創建時間: ${new Date(result.created_date).toLocaleString()}\n` +
`${result.description ? `- 描述: ${result.description}\n` : ''}` +
`${result.tags && result.tags.length > 0 ? `- 標籤: ${result.tags.join(', ')}\n` : ''}`
);
} catch (error) {
console.error('Error creating epic:', error);
return createErrorResponse(ERROR_MESSAGES.FAILED_TO_CREATE_EPIC);
}
}
};
/**
* List Epics tool
* Lists all Epics in a project
*/
export const listEpicsTool = {
name: 'listEpics',
description: 'List all Epics in a project',
inputSchema: z.object({
project: z.number({
description: 'Project ID to list Epics from'
})
}),
handler: async ({ project }) => {
try {
if (!taigaService.isAuthenticated()) {
return createErrorResponse(ERROR_MESSAGES.AUTHENTICATION_FAILED);
}
const epics = await taigaService.listEpics(project);
if (epics.length === 0) {
return createSuccessResponse(
`🏛️ **專案 #${project} Epic列表**\n\n` +
`暫無Epic`
);
}
const epicList = epics.map((epic, index) => {
const createdDate = new Date(epic.created_date).toLocaleDateString();
const storyCount = epic.user_stories_counts?.total || 0;
const statusInfo = epic.status_extra_info?.name || '未設定';
return (
`${index + 1}. **${epic.subject}** (ID: ${epic.id})\n` +
` - 狀態: ${statusInfo}\n` +
` - 用戶故事: ${storyCount} 個\n` +
` - 創建日期: ${createdDate}\n` +
` - 顏色: ${epic.color}\n` +
`${epic.description ? ` - 描述: ${epic.description.substring(0, 100)}${epic.description.length > 100 ? '...' : ''}\n` : ''}` +
`${epic.tags && epic.tags.length > 0 ? ` - 標籤: ${epic.tags.join(', ')}\n` : ''}`
);
}).join('\n');
return createSuccessResponse(
`🏛️ **專案 #${project} Epic列表** (共 ${epics.length} 個)\n\n` +
epicList
);
} catch (error) {
console.error('Error listing epics:', error);
return createErrorResponse(ERROR_MESSAGES.FAILED_TO_LIST_EPICS);
}
}
};
/**
* Get Epic details tool
* Gets detailed information about a specific Epic
*/
export const getEpicTool = {
name: 'getEpic',
description: 'Get detailed information about a specific Epic',
inputSchema: z.object({
epicId: z.number({
description: 'ID of the Epic to get details for'
})
}),
handler: async ({ epicId }) => {
try {
if (!taigaService.isAuthenticated()) {
return createErrorResponse(ERROR_MESSAGES.AUTHENTICATION_FAILED);
}
const epic = await taigaService.getEpic(epicId);
const createdDate = new Date(epic.created_date).toLocaleString();
const modifiedDate = new Date(epic.modified_date).toLocaleString();
const storyCount = epic.user_stories_counts?.total || 0;
const statusInfo = epic.status_extra_info?.name || '未設定';
const ownerInfo = epic.owner_extra_info?.full_name || '未分配';
return createSuccessResponse(
`🏛️ **Epic詳細信息**\n\n` +
`**基本信息**\n` +
`- ID: ${epic.id}\n` +
`- 標題: ${epic.subject}\n` +
`- 專案: ${epic.project_extra_info?.name || epic.project}\n` +
`- 狀態: ${statusInfo}\n` +
`- 負責人: ${ownerInfo}\n` +
`- 顏色: ${epic.color}\n\n` +
`**進度統計**\n` +
`- 關聯用戶故事: ${storyCount} 個\n` +
`- 創建時間: ${createdDate}\n` +
`- 最後修改: ${modifiedDate}\n\n` +
`${epic.description ? `**描述**\n${epic.description}\n\n` : ''}` +
`${epic.tags && epic.tags.length > 0 ? `**標籤**\n${epic.tags.join(', ')}\n\n` : ''}` +
`**項目鏈接**\n` +
`- Taiga URL: ${epic.permalink || '無'}`
);
} catch (error) {
console.error('Error getting epic:', error);
if (error.response?.status === 404) {
return createErrorResponse(ERROR_MESSAGES.EPIC_NOT_FOUND);
}
return createErrorResponse(ERROR_MESSAGES.FAILED_TO_GET_EPIC);
}
}
};
/**
* Update Epic tool
* Updates an existing Epic's information
*/
export const updateEpicTool = {
name: 'updateEpic',
description: 'Update an existing Epic\'s information',
inputSchema: z.object({
epicId: z.number({
description: 'ID of the Epic to update'
}),
subject: z.string().optional().describe('New subject/title for the Epic'),
description: z.string().optional().describe('New description for the Epic'),
color: z.string().optional().describe('New color code for the Epic'),
tags: z.array(z.string()).optional().describe('New tags for the Epic'),
status: z.number().optional().describe('New status ID for the Epic')
}),
handler: async ({ epicId, subject, description, color, tags, status }) => {
try {
if (!taigaService.isAuthenticated()) {
return createErrorResponse(ERROR_MESSAGES.AUTHENTICATION_FAILED);
}
const updateData = {};
if (subject !== undefined) updateData.subject = subject;
if (description !== undefined) updateData.description = description;
if (color !== undefined) updateData.color = color;
if (tags !== undefined) updateData.tags = tags;
if (status !== undefined) updateData.status = status;
const result = await taigaService.updateEpic(epicId, updateData);
return createSuccessResponse(
`${SUCCESS_MESSAGES.EPIC_UPDATED}\n\n` +
`🏛️ **Epic更新完成**\n` +
`- Epic ID: ${result.id}\n` +
`- 標題: ${result.subject}\n` +
`- 狀態: ${result.status_extra_info?.name || '未設定'}\n` +
`- 最後修改: ${new Date(result.modified_date).toLocaleString()}\n` +
`${result.description ? `- 描述: ${result.description.substring(0, 150)}${result.description.length > 150 ? '...' : ''}\n` : ''}`
);
} catch (error) {
console.error('Error updating epic:', error);
if (error.response?.status === 404) {
return createErrorResponse(ERROR_MESSAGES.EPIC_NOT_FOUND);
}
return createErrorResponse(ERROR_MESSAGES.FAILED_TO_UPDATE_EPIC);
}
}
};
/**
* Link User Story to Epic tool
* Links a User Story to an Epic for organization
*/
export const linkStoryToEpicTool = {
name: 'linkStoryToEpic',
description: 'Link a User Story to an Epic for better organization',
inputSchema: z.object({
userStoryId: z.number({
description: 'ID of the User Story to link'
}),
epicId: z.number({
description: 'ID of the Epic to link the story to'
})
}),
handler: async ({ userStoryId, epicId }) => {
try {
if (!taigaService.isAuthenticated()) {
return createErrorResponse(ERROR_MESSAGES.AUTHENTICATION_FAILED);
}
const result = await taigaService.linkStoryToEpic(userStoryId, epicId);
return createSuccessResponse(
`${SUCCESS_MESSAGES.STORY_LINKED_TO_EPIC}\n\n` +
`🔗 **故事連結成功**\n` +
`- User Story: #${userStoryId} "${result.subject}"\n` +
`- Epic: #${epicId} "${result.epic?.subject || 'Epic'}"\n` +
`- 連結時間: ${new Date().toLocaleString()}\n` +
`- 專案: ${result.project_extra_info?.name || result.project}`
);
} catch (error) {
console.error('Error linking story to epic:', error);
if (error.response?.status === 404) {
return createErrorResponse(ERROR_MESSAGES.USER_STORY_NOT_FOUND);
}
return createErrorResponse(ERROR_MESSAGES.FAILED_TO_LINK_STORY);
}
}
};
/**
* Unlink User Story from Epic tool
* Removes the link between a User Story and Epic
*/
export const unlinkStoryFromEpicTool = {
name: 'unlinkStoryFromEpic',
description: 'Remove the link between a User Story and Epic',
inputSchema: z.object({
userStoryId: z.number({
description: 'ID of the User Story to unlink'
})
}),
handler: async ({ userStoryId }) => {
try {
if (!taigaService.isAuthenticated()) {
return createErrorResponse(ERROR_MESSAGES.AUTHENTICATION_FAILED);
}
const result = await taigaService.unlinkStoryFromEpic(userStoryId);
return createSuccessResponse(
`${SUCCESS_MESSAGES.STORY_UNLINKED_FROM_EPIC}\n\n` +
`🔓 **故事取消連結**\n` +
`- User Story: #${userStoryId} "${result.subject}"\n` +
`- 已從Epic移除\n` +
`- 操作時間: ${new Date().toLocaleString()}\n` +
`- 專案: ${result.project_extra_info?.name || result.project}`
);
} catch (error) {
console.error('Error unlinking story from epic:', error);
if (error.response?.status === 404) {
return createErrorResponse(ERROR_MESSAGES.USER_STORY_NOT_FOUND);
}
return createErrorResponse(ERROR_MESSAGES.FAILED_TO_UNLINK_STORY);
}
}
};