UNPKG

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
/** * 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); } } };