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).
294 lines (256 loc) • 9.74 kB
JavaScript
/**
* Batch operations MCP tools for Taiga
* Enables bulk operations for efficient project management
*/
import { z } from 'zod';
import { TaigaService } from '../taigaService.js';
import {
ERROR_MESSAGES,
SUCCESS_MESSAGES,
BATCH_OPERATIONS,
RESPONSE_TEMPLATES
} from '../constants.js';
import {
resolveProjectId,
createErrorResponse,
createSuccessResponse,
formatDateTime,
getSafeValue
} from '../utils.js';
const taigaService = new TaigaService();
/**
* Batch create multiple issues
*/
export const batchCreateIssuesTool = {
name: 'batchCreateIssues',
schema: {
projectIdentifier: z.string().describe('Project ID or slug'),
issues: z.array(z.object({
subject: z.string().describe('Issue title/subject'),
description: z.string().optional().describe('Issue description'),
type: z.string().optional().describe('Issue type (Bug, Feature, Question, etc.)'),
priority: z.string().optional().describe('Issue priority (Low, Normal, High, etc.)'),
severity: z.string().optional().describe('Issue severity'),
tags: z.array(z.string()).optional().describe('Issue tags')
})).describe('Array of issues to create')
},
handler: async ({ projectIdentifier, issues }) => {
try {
const projectId = await resolveProjectId(projectIdentifier);
if (issues.length === 0) {
return createErrorResponse(BATCH_OPERATIONS.ERROR_EMPTY_BATCH);
}
if (issues.length > BATCH_OPERATIONS.MAX_BATCH_SIZE) {
return createErrorResponse(`${BATCH_OPERATIONS.ERROR_BATCH_TOO_LARGE} (max: ${BATCH_OPERATIONS.MAX_BATCH_SIZE})`);
}
const results = [];
const errors = [];
for (let i = 0; i < issues.length; i++) {
const issue = issues[i];
try {
const createdIssue = await taigaService.createIssue({
projectId,
subject: issue.subject,
description: issue.description || '',
type: issue.type,
priority: issue.priority,
severity: issue.severity,
tags: issue.tags || []
});
results.push({
index: i + 1,
subject: issue.subject,
ref: createdIssue.ref,
id: createdIssue.id,
status: 'success'
});
} catch (error) {
errors.push({
index: i + 1,
subject: issue.subject,
error: error.message,
status: 'failed'
});
}
}
// Format response
let responseText = `${BATCH_OPERATIONS.SUCCESS_BATCH_CREATED_ISSUES}\n\n`;
if (results.length > 0) {
responseText += `✅ **成功創建 ${results.length} 個Issues:**\n`;
results.forEach(result => {
responseText += `${result.index}. ${result.subject} (#${result.ref})\n`;
});
}
if (errors.length > 0) {
responseText += `\n❌ **失敗 ${errors.length} 個Issues:**\n`;
errors.forEach(error => {
responseText += `${error.index}. ${error.subject} - ${error.error}\n`;
});
}
responseText += `\n📊 **總結:** ${results.length}/${issues.length} 成功`;
return createSuccessResponse(responseText);
} catch (error) {
return createErrorResponse(`${ERROR_MESSAGES.FAILED_TO_CREATE_ISSUE}: ${error.message}`);
}
}
};
/**
* Batch create multiple user stories
*/
export const batchCreateUserStoriesTool = {
name: 'batchCreateUserStories',
schema: {
projectIdentifier: z.string().describe('Project ID or slug'),
userStories: z.array(z.object({
subject: z.string().describe('User story title'),
description: z.string().optional().describe('User story description'),
points: z.number().optional().describe('Story points'),
tags: z.array(z.string()).optional().describe('User story tags')
})).describe('Array of user stories to create')
},
handler: async ({ projectIdentifier, userStories }) => {
try {
const projectId = await resolveProjectId(projectIdentifier);
if (userStories.length === 0) {
return createErrorResponse(BATCH_OPERATIONS.ERROR_EMPTY_BATCH);
}
if (userStories.length > BATCH_OPERATIONS.MAX_BATCH_SIZE) {
return createErrorResponse(`${BATCH_OPERATIONS.ERROR_BATCH_TOO_LARGE} (max: ${BATCH_OPERATIONS.MAX_BATCH_SIZE})`);
}
const results = [];
const errors = [];
for (let i = 0; i < userStories.length; i++) {
const story = userStories[i];
try {
const createdStory = await taigaService.createUserStory({
projectId,
subject: story.subject,
description: story.description || '',
points: story.points,
tags: story.tags || []
});
results.push({
index: i + 1,
subject: story.subject,
ref: createdStory.ref,
id: createdStory.id,
status: 'success'
});
} catch (error) {
errors.push({
index: i + 1,
subject: story.subject,
error: error.message,
status: 'failed'
});
}
}
// Format response
let responseText = `${BATCH_OPERATIONS.SUCCESS_BATCH_CREATED_STORIES}\n\n`;
if (results.length > 0) {
responseText += `✅ **成功創建 ${results.length} 個User Stories:**\n`;
results.forEach(result => {
responseText += `${result.index}. ${result.subject} (#${result.ref})\n`;
});
}
if (errors.length > 0) {
responseText += `\n❌ **失敗 ${errors.length} 個User Stories:**\n`;
errors.forEach(error => {
responseText += `${error.index}. ${error.subject} - ${error.error}\n`;
});
}
responseText += `\n📊 **總結:** ${results.length}/${userStories.length} 成功`;
return createSuccessResponse(responseText);
} catch (error) {
return createErrorResponse(`${ERROR_MESSAGES.FAILED_TO_CREATE_USER_STORY}: ${error.message}`);
}
}
};
/**
* Batch create multiple tasks
*/
export const batchCreateTasksTool = {
name: 'batchCreateTasks',
schema: {
projectIdentifier: z.string().describe('Project ID or slug'),
userStoryRef: z.string().describe('User story reference number (e.g., #123)'),
tasks: z.array(z.object({
subject: z.string().describe('Task title'),
description: z.string().optional().describe('Task description'),
tags: z.array(z.string()).optional().describe('Task tags')
})).describe('Array of tasks to create')
},
handler: async ({ projectIdentifier, userStoryRef, tasks }) => {
try {
const projectId = await resolveProjectId(projectIdentifier);
if (tasks.length === 0) {
return createErrorResponse(BATCH_OPERATIONS.ERROR_EMPTY_BATCH);
}
if (tasks.length > BATCH_OPERATIONS.MAX_BATCH_SIZE) {
return createErrorResponse(`${BATCH_OPERATIONS.ERROR_BATCH_TOO_LARGE} (max: ${BATCH_OPERATIONS.MAX_BATCH_SIZE})`);
}
// Find user story by reference
const userStories = await taigaService.listUserStories(projectId);
const refNumber = userStoryRef.replace('#', '');
const userStory = userStories.find(story => story.ref === parseInt(refNumber));
if (!userStory) {
return createErrorResponse(`User story ${userStoryRef} not found`);
}
const results = [];
const errors = [];
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
try {
const createdTask = await taigaService.createTask({
projectId,
userStoryId: userStory.id,
subject: task.subject,
description: task.description || '',
tags: task.tags || []
});
results.push({
index: i + 1,
subject: task.subject,
ref: createdTask.ref,
id: createdTask.id,
status: 'success'
});
} catch (error) {
errors.push({
index: i + 1,
subject: task.subject,
error: error.message,
status: 'failed'
});
}
}
// Format response
let responseText = `${BATCH_OPERATIONS.SUCCESS_BATCH_CREATED_TASKS}\n\n`;
responseText += `📋 **User Story:** ${userStory.subject} (${userStoryRef})\n\n`;
if (results.length > 0) {
responseText += `✅ **成功創建 ${results.length} 個Tasks:**\n`;
results.forEach(result => {
responseText += `${result.index}. ${result.subject} (#${result.ref})\n`;
});
}
if (errors.length > 0) {
responseText += `\n❌ **失敗 ${errors.length} 個Tasks:**\n`;
errors.forEach(error => {
responseText += `${error.index}. ${error.subject} - ${error.error}\n`;
});
}
responseText += `\n📊 **總結:** ${results.length}/${tasks.length} 成功`;
return createSuccessResponse(responseText);
} catch (error) {
return createErrorResponse(`${ERROR_MESSAGES.FAILED_TO_CREATE_TASK}: ${error.message}`);
}
}
};
/**
* Register all batch tools
*/
export function registerBatchTools(server) {
server.tool(batchCreateIssuesTool.name, batchCreateIssuesTool.schema, batchCreateIssuesTool.handler);
server.tool(batchCreateUserStoriesTool.name, batchCreateUserStoriesTool.schema, batchCreateUserStoriesTool.handler);
server.tool(batchCreateTasksTool.name, batchCreateTasksTool.schema, batchCreateTasksTool.handler);
}